setupgui/windows/TabCtrl.cpp (380 lines of code) (raw):
// Copyright (c) 2007, 2024, Oracle and/or its affiliates.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, version 2.0, as
// published by the Free Software Foundation.
//
// This program is designed to work with certain software (including
// but not limited to OpenSSL) that is licensed under separate terms, as
// designated in a particular file or component or in included license
// documentation. The authors of MySQL hereby grant you an additional
// permission to link the program and your derivative works with the
// separately licensed software that they have either included with
// the program or referenced in the documentation.
//
// Without limiting anything contained in the foregoing, this file,
// which is part of Connector/ODBC, is also subject to the
// Universal FOSS Exception, version 1.0, a copy of which can be found at
// https://oss.oracle.com/licenses/universal-foss-exception.
//
// 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, version 2.0, 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 = (LONG)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]);
}