/*
  Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.

  The MySQL Connector/ODBC is licensed under the terms of the GPLv2
  <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  MySQL Connectors. There are special exceptions to the terms and
  conditions of the GPLv2 as it is applied to this software, see the
  FLOSS License Exception
  <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published
  by the Free Software Foundation; version 2 of the License.
  
  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  for more details.
  
  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
*/

/**
 @file TabCtrl.cpp
 @brief Tab Control Enhanced.

 Make Creating and modifying Tab Control Property pages a snap.

 (c) 2006 David MacDermot

 This module is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
#include "resource.h"
#include "TabCtrl.h"


/** Global defs *************************************************************/

#define CMD_VK_UP		101
#define CMD_VK_DOWN		102
#define CMD_VK_RIGHT	103
#define CMD_VK_LEFT		104

/** Global variables ********************************************************/

static LPTABCTRL This;
static BOOL stopTabPageMessageLoop=FALSE;

/** Prototypes **************************************************************/

static void TabPageMessageLoop (HWND);
static void ResetTabPageMessageLoop (HWND);

/** Macroes *****************************************************************/

#define Refresh(A) RedrawWindow(A,NULL,NULL,RDW_ERASE|RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_UPDATENOW);

/** Functions **************************************************************/

void TabControl_GetClientRect(HWND hwnd,RECT* prc)
{
	//////////////////////////////////////////////
	// The standard GetClientRect doesn't return the
	// desired rectangle under every possible tab position
	//
	// Note: This function does not populate a standard rectangle format but rather
	// prc.left = left, prc.top = top, prc.right = width, and prc.bottom = height

	RECT rtab_0;
	LONG lStyle= GetWindowLongPtr(hwnd,GWL_STYLE);

	// Calculate the tab control's display area
	GetWindowRect(hwnd, prc);
	ScreenToClient(GetParent(hwnd), (POINT*)&prc->left);
	ScreenToClient(hwnd, (POINT*)&prc->right);
	TabCtrl_GetItemRect(hwnd,0,&rtab_0); //The tab itself

	if((lStyle & TCS_BOTTOM) && (lStyle & TCS_VERTICAL)) //Tabs to Right
	{
		prc->top = prc->top + 6; //x coord
		prc->left = prc->left + 4; //y coord
		prc->bottom = prc->bottom - 12; // height
		prc->right = prc->right - (12 + rtab_0.right-rtab_0.left); // width
	}
	else if(lStyle & TCS_VERTICAL) //Tabs to Left
	{
		prc->top = prc->top + 6; //x coord
		prc->left = prc->left + (4 + rtab_0.right-rtab_0.left); //y coord
		prc->bottom = prc->bottom - 12; // height
		prc->right = prc->right - (12 + rtab_0.right-rtab_0.left); // width
	}
	else if(lStyle & TCS_BOTTOM) //Tabs on Bottom
	{
		prc->top = prc->top + 6; //x coord
		prc->left = prc->left + 4; //y coord
		prc->bottom = prc->bottom - (16 + rtab_0.bottom-rtab_0.top); // height
		prc->right = prc->right - 12; // width
	}
	else //Tabs on top
	{
		prc->top = prc->top + (6 + rtab_0.bottom-rtab_0.top); //x coord
		prc->left = prc->left + 4; //y coord
		prc->bottom = prc->bottom - (16 + rtab_0.bottom-rtab_0.top); // height
		prc->right = prc->right - 12; // width
	}
}

BOOL CenterTabPage (HWND hPage)
{
	/////////////////////////////////////////////////////
	// Center the tab page in the tab control's display area
	//
	RECT rect, rclient;
 	TabControl_GetClientRect(This->hTab, &rect); // left, top, width, height

	// Get the tab page size
	GetClientRect(hPage, &rclient);
	rclient.right=rclient.right-rclient.left;// width
	rclient.bottom=rclient.bottom-rclient.top;// height
	rclient.left= rect.left;
	rclient.top= rect.top;

	// Center the tab page, or cut it off at the edge of the tab control(bad)
	if(rclient.right<rect.right)
		rclient.left += (rect.right-rclient.right)/2;

	if(rclient.bottom<rect.bottom)
		rclient.top += (rect.bottom-rclient.bottom)/2;

	// Move the child and put it on top
	return SetWindowPos(hPage, HWND_TOP,
			rclient.left, rclient.top, rclient.right, rclient.bottom,
			0);
}

BOOL StretchTabPage (HWND hPage)
{
	/////////////////////////////////////////////////////
	// Stretch the tab page to fit the tab control's display area
	//
	RECT rect;
 	TabControl_GetClientRect(This->hTab, &rect); // left, top, width, height

	// Move the child and put it on top
	return SetWindowPos(hPage, HWND_TOP,
			rect.left, rect.top, rect.right, rect.bottom,
			0);
}

/****************************************************************************
 *                                                                          *
 * Function: OnKeyDown	       		                    					*
 *                                                                          *
 * Purpose : Handle key presses in the tab control (but not the tab pages)	*
 *                                                                          *
 * History : Date      Reason                                               *
 *           00/00/00  Created                                              *
 *                                                                          *
 ****************************************************************************/

BOOL OnKeyDown(LPARAM lParam)
{
	BOOL verticalTabs;
	TC_KEYDOWN *tk=(TC_KEYDOWN *)lParam;
	int itemCount=TabCtrl_GetItemCount(tk->hdr.hwndFrom);
	int currentSel=TabCtrl_GetCurSel(tk->hdr.hwndFrom);

	if(itemCount <= 1) return FALSE; // Ignore if only one TabPage

	verticalTabs= GetWindowLongPtr(This->hTab, GWL_STYLE) & TCS_VERTICAL;

	if(verticalTabs)
	{
		switch (tk->wVKey)
		{
			case VK_PRIOR: //select the previous page
			{
				if(0==currentSel) return TRUE;
				TabCtrl_SetCurSel(tk->hdr.hwndFrom, currentSel-1);
				TabCtrl_SetCurFocus(tk->hdr.hwndFrom,currentSel-1);
			}
			return TRUE;
			case VK_UP: //select the previous page
			{
				if(0==currentSel) return TRUE;
				TabCtrl_SetCurSel(tk->hdr.hwndFrom, currentSel-1);
				TabCtrl_SetCurFocus(tk->hdr.hwndFrom, currentSel);
			}
			return TRUE;
			case VK_NEXT:  //select the next page
			{
				TabCtrl_SetCurSel(tk->hdr.hwndFrom, currentSel+1);
				TabCtrl_SetCurFocus(tk->hdr.hwndFrom, currentSel+1);
			}
			return TRUE;
			case VK_DOWN: //select the next page
			{
				TabCtrl_SetCurSel(tk->hdr.hwndFrom, currentSel+1);
				TabCtrl_SetCurFocus(tk->hdr.hwndFrom,currentSel);
			}
			return TRUE;
			case VK_LEFT: //navagate within selected child tab page
			{
				SetFocus(This->hTabPages[currentSel]); // focus to child tab page
				TabPageMessageLoop (This->hTabPages[currentSel]); //start message loop
			}
			return TRUE;
			case VK_RIGHT: //navagate within selected child tab page
			{
				SetFocus(This->hTabPages[currentSel]);
				TabPageMessageLoop (This->hTabPages[currentSel]);
			}
			return TRUE;
			default: return FALSE;
		}
	} // if(verticalTabs)

	else // horizontal Tabs
    {
		switch (tk->wVKey)
		{
			case VK_NEXT: //select the previous page
			{
				if(0==currentSel) return TRUE;
				TabCtrl_SetCurSel(tk->hdr.hwndFrom, currentSel-1);
				TabCtrl_SetCurFocus(tk->hdr.hwndFrom, currentSel-1);
			}
			return TRUE;
			case VK_LEFT: //select the previous page
			{
				if(0==currentSel) return TRUE;
				TabCtrl_SetCurSel(tk->hdr.hwndFrom, currentSel-1);
				TabCtrl_SetCurFocus(tk->hdr.hwndFrom, currentSel);
			}
			return TRUE;
			case VK_PRIOR: //select the next page
			{
				TabCtrl_SetCurSel(tk->hdr.hwndFrom, currentSel+1);
				TabCtrl_SetCurFocus(tk->hdr.hwndFrom,currentSel+1);
			}
			return TRUE;
			case VK_RIGHT: //select the next page
			{
				TabCtrl_SetCurSel(tk->hdr.hwndFrom, currentSel+1);
				TabCtrl_SetCurFocus(tk->hdr.hwndFrom,currentSel);
			}
			return TRUE;
			case VK_UP: //navagate within selected child tab page
			{
				SetFocus(This->hTabPages[currentSel]);
				TabPageMessageLoop (This->hTabPages[currentSel]);
			}
			return TRUE;
			case VK_DOWN: //navagate within selected child tab page
			{
				SetFocus(This->hTabPages[currentSel]);
				TabPageMessageLoop (This->hTabPages[currentSel]);
			}
			return TRUE;
			default: return FALSE;
		}
	} //else // horizontal Tabs
}

/****************************************************************************
 *                                                                          *
 * Functions: CreateAccTable & TabPage_OnCommand           					*
 *                                                                          *
 * Purpose : Get and handle selected key presses within the child tab pages.*
 *             The WM_KEYUP/WM_KEYDOWN messages are not directly accessable	*
 *             in a dialog.  The method for capturing desired keystrokes is *
 *             to create an Accelerator Table of the desired keys and then  *
 *             supplying the table handle to the TranslateAccelerator macro *
 *             of the Tab Page Message Loop.  These key strokes are then	*
 *             handled in TabPage_OnCommand.								*
 *                                                                          *
 * History : Date      Reason                                               *
 *           00/00/00  Created                                              *
 *                                                                          *
 ****************************************************************************/

HACCEL CreateAccTable (void)
{
	static  ACCEL  aAccel[4];
	static  HACCEL  hAccel;
	int i;

	for(i=0;i<4;i++)aAccel[i].fVirt=FVIRTKEY;

	aAccel[0].key=VK_UP;
	aAccel[0].cmd=CMD_VK_UP;

	aAccel[1].key=VK_DOWN;
	aAccel[1].cmd=CMD_VK_DOWN;

	aAccel[2].key=VK_RIGHT;
	aAccel[2].cmd=CMD_VK_RIGHT;

	aAccel[3].key=VK_LEFT;
	aAccel[3].cmd=CMD_VK_LEFT;

	hAccel=CreateAcceleratorTable(aAccel,4);
	return hAccel;
}

void TabPage_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
	// Handle the Dialog virtual keys
	// Forward the rest of the commands to ParentProc
	BOOL verticalTabs= (GetWindowLongPtr(This->hTab, GWL_STYLE) &
                            TCS_VERTICAL);
	if(verticalTabs)
	{
		switch (id)
		{
			case CMD_VK_UP: //select the previous control
				SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)0, FALSE);
			return;
			case CMD_VK_DOWN: //select the next control
				SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)1, FALSE);
			return;
			case CMD_VK_RIGHT:
			{
				SetFocus(This->hTab); // focus to tab control
				stopTabPageMessageLoop=TRUE; // cause message loop to return
			}
			return;
			case CMD_VK_LEFT:
			{
				SetFocus(This->hTab);
				stopTabPageMessageLoop=TRUE;
			}
			return;
		}
	}
	else // horizontal Tabs
	{
		switch (id)
		{
			case CMD_VK_RIGHT:
				SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)0, FALSE);
			return;
			case CMD_VK_LEFT:
				SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)1, FALSE);
			return;
			case CMD_VK_UP:
			{
				SetFocus(This->hTab);
				stopTabPageMessageLoop=TRUE;
			}
			return;
			case CMD_VK_DOWN:
			{
				SetFocus(This->hTab);
				stopTabPageMessageLoop=TRUE;
			}
			return;
		}
	} //else // horizontal Tabs

	//Forward all other commands
	FORWARD_WM_COMMAND (hwnd,id,hwndCtl,codeNotify,This->ParentProc);

	// Mouse clicks on a control should engage the Message Loop

	// If This WM_COMMAND message is a notification to parent window
	// ie: EN_SETFOCUS being sent when an edit control is initialized
	// do not engage the Message Loop.
	if(codeNotify!=0) return;

	// Toggling WM_NEXTDLGCTL ensures that default focus moves to selected
	// Control
	SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)0, FALSE);
	SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)1, FALSE);
	ResetTabPageMessageLoop (hwnd);

}

void TabPage_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{
	// If Mouse click in tab page but not on control
	ResetTabPageMessageLoop (hwnd);
}

BOOL TabPage_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
	// We handle This message so that it is not sent to the main dlg proc
	// each time a tab page is initialized.
	return TRUE;
}

BOOL CALLBACK TabPage_DlgProc (HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
		HANDLE_MSG (hwndDlg, WM_INITDIALOG, TabPage_OnInitDialog);
		HANDLE_MSG (hwndDlg, WM_COMMAND, TabPage_OnCommand);
		HANDLE_MSG (hwndDlg, WM_LBUTTONDOWN, TabPage_OnLButtonDown);

		//// TODO: Add TabPage dialog message crackers here...

		default: return This->ParentProc (hwndDlg, msg, wParam, lParam);
	}
}

/****************************************************************************
 *                                                                          *
 * Function: Tab Page Message loop	       		                    		*
 *                                                                          *
 * Purpose : Monitor and respond to user keyboard input and system messages *
 *			 Note: Send PostQuitMessage(0); from any cancel or exit event.	*
 *			 Failure to do so will leave the process running even after		*
 *			 application exit.												*
 *                                                                          *
 * History : Date      Reason                                               *
 *           00/00/00  Created                                              *
 *                                                                          *
 ****************************************************************************/

static void TabPageMessageLoop (HWND hwnd)
{
	MSG	msg;
	int	status;
	BOOL handled = FALSE;

	// Create Accelerator table
	HACCEL hAccTable = CreateAccTable();

	while((status = GetMessage(&msg, NULL, 0, 0 )) != 0 && !stopTabPageMessageLoop)
	{
	    if (status == -1) // Exception
	    {
	        return;
	    }
		else
	    {
			// Dialogs do not have a WM_KEYDOWN message so we will seperate
			// the desired keyboard events here
			handled = TranslateAccelerator(hwnd,hAccTable,&msg);

			// Perform default dialog message processing using IsDialogM. . .
			if(!handled) handled=IsDialogMessage(hwnd,&msg);

			// Non dialog message handled in the standard way.
	        if(!handled)
	        {
	            TranslateMessage(&msg);
	            DispatchMessage(&msg);
	        }
	    }
	}
	if(stopTabPageMessageLoop) //Reset: do not PostQuitMessage(0)
	{
		DestroyAcceleratorTable(hAccTable);
		stopTabPageMessageLoop = FALSE;
		return;
	}

	// Default: Re-post the Quit message
	DestroyAcceleratorTable(hAccTable);
	PostQuitMessage(0);
	return;
}

static void ResetTabPageMessageLoop (HWND hwnd)
{
	//Toggle kill sw
	stopTabPageMessageLoop=TRUE;
	stopTabPageMessageLoop=FALSE;

	TabPageMessageLoop(hwnd);
}


BOOL OnSelChanged(void)
{
  if (This->hTabPages)
  {
    /*
     A tab has been pressed (TCN_SELCHANGE) Using GWL_USERDATA of the tab
     control to keep the current visible child of the tab control
    */
    HWND hVisible= (HWND)GetWindowLongPtr(This->hTab, GWLP_USERDATA);
    int iSel= TabCtrl_GetCurSel(This->hTab);

    // Hide the current child dialog box, if any.
    ShowWindow(hVisible, FALSE);

    // Show the new child dialog box.
    ShowWindow(This->hTabPages[iSel], TRUE);

    // Save the current child
    SetWindowLongPtr(This->hTab, GWLP_USERDATA, (LONG_PTR)This->hTabPages[iSel]);
  }

  return TRUE;
}


void TabControl_Select(LPTABCTRL tc)
{
	// When coding more than one tab control use This function to update "This" pointer
	This=tc;
}

void TabControl_Destroy(LPTABCTRL tc)
{
	//////////////////////////////////////
	// Destroy the tab page dialogs and
	// free the list of pointers to the dialogs
	for (int i=0;i<tc->tabPageCount;i++)
		DestroyWindow(tc->hTabPages[i]);

	SendMessage(tc->hTab, WM_QUIT, 0, 0);
	stopTabPageMessageLoop = TRUE;
	free (tc->hTabPages);
	tc->hTabPages = 0;
	tc->tabPageCount = 0;
}

extern HINSTANCE ghInstance;

void New_TabControl(LPTABCTRL tc,
					HWND hTab,
					PWSTR *tabNames,
					PWSTR *dlgNames,
					BOOL (*ParentProc)(HWND, UINT, WPARAM, LPARAM),
					VOID (*TabPage_OnSize)(HWND, UINT, int, int),
					BOOL fStretch)
{
	static TCITEM tie;
	This=tc;

	This->hTab=hTab;
	This->tabNames=tabNames;
	This->dlgNames=dlgNames;
	This->blStretchTabs=fStretch;

	// Point to external functions
	This->ParentProc=ParentProc;

	// Point to internal public functions
	This->OnKeyDown=&OnKeyDown;
	This->OnSelChanged=&OnSelChanged;
	This->StretchTabPage=&StretchTabPage;
	This->CenterTabPage=&CenterTabPage;

	// Determine number of tab pages to insert based on DlgNames
	This->tabPageCount = 0;
	PWSTR* ptr=This->tabNames;
	while(*ptr++) This->tabPageCount++;

	//create array based on number of pages
	This->hTabPages = (HWND*)malloc(This->tabPageCount * sizeof(HWND*));

	// Add a tab for each name in tabnames (list ends with 0)
	tie.mask = TCIF_TEXT | TCIF_IMAGE;
	tie.iImage = -1;
	for (int i = 0; i< This->tabPageCount; i++)
	{
		tie.pszText = This->tabNames[i];
		TabCtrl_InsertItem(This->hTab, i, &tie);

		// Add page to each tab
		This->hTabPages[i] = CreateDialog(ghInstance,
										  This->dlgNames[i],
										  GetParent(This->hTab),
										  (DLGPROC)TabPage_DlgProc);
		// Set initial tab page position
		if(This->blStretchTabs)
			This->StretchTabPage(This->hTabPages[i]);
		else
			This->CenterTabPage(This->hTabPages[i]);
	}
	// Show first tab
	ShowWindow(This->hTabPages[0],SW_SHOW);

	// Save the current child
	SetWindowLongPtr(This->hTab, GWLP_USERDATA,
                         (LONG_PTR)This->hTabPages[0]);
}
