- // tabcontrol.cpp 
-   
- #include <windows.h> 
- #include <commctrl.h> 
-   
-   
- typedef struct tabpage_s 
- { 
-    HWND hWnd; // window handle of the controls tab page sub-window 
- } tabpage_t; 
-   
-   
- typedef struct tabcontrol_s  
- { 
-    HWND hWnd; // window handle of the tab control 
-    tabpage_t *pages; // mallocated array of pages in this tab control 
-    int page_count; // number of pages in this tab control 
-    int activepage_index; // index of the active page 
-    WNDPROC ParentProc; // function pointer to Parent Dialog Proc 
- } tabcontrol_t; 
-   
-   
- // prototypes of exported functions 
- void *TabControl_New (HWND hTabControlWnd, WNDPROC ParentProc); 
- void TabControl_AddPage (void *tab_control, wchar_t *page_name, int dialog_id); 
- void TabControl_SelectTab (void *tab_control, int page_index); 
- HWND TabControl_GetItem (void *tab_control, int item_id); 
- void TabControl_Destroy (void *tab_control); 
- bool TabControl_Notify (NMHDR *notification); 
-   
-   
- // prototypes of local functions 
- static void TabControl_SetFocusOnFirstTabStop (HWND hWnd); 
- static void TabControl_GetClientRect (HWND hWnd, RECT *client_rect); 
- static int CALLBACK TabPage_DlgProc (HWND hwndDlg, unsigned int message, WPARAM wParam, LPARAM lParam); 
- static void TabPage_MessageLoop (HWND hWnd); 
-   
-   
- static void TabControl_SetFocusOnFirstTabStop (HWND hWnd) 
- { 
-    // this function ensures focus on first tab stop when entering a tab page. 
-    // WM_NEXTDLGCTL wparam = 1 shifts focus back to the LAST tabstop in this tabpage. 
-    // Posting a VK_TAB returns focus to the first tab stop and causes it to be recognized 
-    // as the first tabstop by the WM_KEYDOWN sniffer in the TabPageMessageLoop(). 
-   
-    SendMessage (hWnd, WM_NEXTDLGCTL, 1, FALSE); 
-    PostMessage (GetFocus (), WM_KEYDOWN, VK_TAB, 0); 
-   
-    return; 
- } 
-   
-   
- static void TabControl_GetClientRect (HWND hWnd, RECT *client_rect) 
- { 
-    // returns the client rectangle for the tab control under every possible configuration 
-    // (normal tabs, tabs with buttons, vertical tabs, etc.) 
-    // IMPORTANT: the rectangle structure is populated as follows: 
-    // left = left, top = top, right = WIDTH, bottom = HEIGHT 
-   
-    long tab_style; 
-    RECT tab_rect; 
-     
-    tab_style = GetWindowLong (hWnd, GWL_STYLE); 
-   
-    // calculate the tab control's display area 
-    GetWindowRect (hWnd, client_rect); 
-    ScreenToClient (GetParent (hWnd), (POINT *) &client_rect->left); 
-    ScreenToClient (hWnd, (POINT *) &client_rect->right); 
-    TabCtrl_GetItemRect (hWnd, 0, &tab_rect); // the tab itself 
-   
-    // vertical tabs to the right ? 
-    if ((tab_style & TCS_BOTTOM) && (tab_style & TCS_VERTICAL)) 
-    { 
-       client_rect->top += 6; // x coord 
-       client_rect->left += 4; // y coord 
-       client_rect->bottom -= 12; // height 
-       client_rect->right -= (12 + tab_rect.right - tab_rect.left); // width 
-    } 
-   
-    // vertical tabs to the left ? 
-    else if (tab_style & TCS_VERTICAL) 
-    { 
-       client_rect->top += 6; //x coord 
-       client_rect->left += (4 + tab_rect.right - tab_rect.left); // y coord 
-       client_rect->bottom -= 12; // height 
-       client_rect->right -= (12 + tab_rect.right - tab_rect.left); // width 
-    } 
-   
-    // horizontal tabs at the bottom ? 
-    else if (tab_style & TCS_BOTTOM) 
-    { 
-       client_rect->top += 6; // x coord 
-       client_rect->left += 4; // y coord 
-       client_rect->bottom -= (16 + tab_rect.bottom - tab_rect.top); // height 
-       client_rect->right -= 12; // width 
-    } 
-   
-    // else it must be horizontal tabs at the top (default style) 
-    else 
-    { 
-       client_rect->top += (6 + tab_rect.bottom - tab_rect.top); // x coord 
-       client_rect->left += 4; // y coord 
-       client_rect->bottom -= (16 + tab_rect.bottom - tab_rect.top); // height 
-       client_rect->right -= 12; // width 
-    } 
-   
-    return; // finished populating the rectangle 
- } 
-   
-   
- static int CALLBACK TabPage_DlgProc (HWND hwndDlg, unsigned int message, WPARAM wParam, LPARAM lParam) 
- { 
-    // window procedure for tab page dialogs 
-   
-    tabcontrol_t *tab_control; 
-    NMTCKEYDOWN nm; 
-    HWND hWndTab; 
-   
-    // get a pointer to the tab container window 
-    hWndTab = (HWND) GetWindowLong (hwndDlg, GWL_USERDATA); 
-    if (hWndTab == NULL) 
-       return (0); // consistency check 
-   
-    // now get a pointer to the tab container's structure 
-    tab_control = (tabcontrol_t *) GetWindowLong (hWndTab, GWL_USERDATA); 
-   
-    // is this page being initialized ? 
-    if (message == WM_INITDIALOG) 
-       return (DefWindowProc (hwndDlg, WM_INITDIALOG, wParam, lParam)); // if so, don't hook its initialization message 
-   
-    // else is a control being acted upon in this page ? 
-    else if (message == WM_COMMAND) 
-    { 
-       // forward all commands to the parent proc 
-       tab_control->ParentProc (hwndDlg, message, wParam, lParam); 
-   
-       // is this WM_COMMAND message NOT a notification to the parent window ? 
-       if (HIWORD (wParam) == 0) 
-       { 
-          // mouse clicks on a control should engage the Message Loop 
-          SetFocus ((HWND) lParam); 
-          TabControl_SetFocusOnFirstTabStop (hwndDlg); 
-          TabPage_MessageLoop (hwndDlg); 
-       } 
-   
-       SetWindowLongPtr (hwndDlg, DWLP_MSGRESULT, 0); 
-       return (1); // return TRUE so as to notify we handled this message 
-    } 
-   
-    // else is the mouse clicking something ? 
-    else if (message == WM_LBUTTONDOWN) 
-    { 
-       // simulate keypress access so that everything is handled in the same consistent way 
-       memset (&nm, 0, sizeof (nm)); 
-       nm.hdr.hwndFrom = tab_control->hWnd; 
-       nm.hdr.idFrom = GetDlgCtrlID (tab_control->hWnd); 
-       nm.hdr.code = TCN_KEYDOWN; 
-       nm.wVKey = (GetWindowLong (tab_control->hWnd, GWL_STYLE) & TCS_VERTICAL ? VK_LEFT : VK_DOWN); 
-       nm.flags = 0; 
-       SendMessage (nm.hdr.hwndFrom, WM_NOTIFY, nm.hdr.idFrom, (LPARAM) &nm); 
-   
-       SetWindowLongPtr (hwndDlg, DWLP_MSGRESULT, 0); 
-       return (1); // return TRUE so as to notify we handled this message 
-    } 
-   
-    // forward all other messages to the parent proc 
-    tab_control->ParentProc (hwndDlg, message, wParam, lParam); 
-    return (0); 
- } 
-   
- /**************************************************************************** 
- *     PURPOSE:  Monitor and respond to user keyboard input and system messages. 
- * 
- *     PARAMS:   HWND hwnd - handle to the currently visible tab page 
- * 
- *     COMMENTS: Send PostQuitMessage(0); from any cancel or exit event.  
- *               Failure to do so will leave the process running even after 
- *               application exit. 
- \****************************************************************************/ 
-   
- static void TabPage_MessageLoop (HWND hWnd) 
- { 
-         MSG msg; 
-         int status; 
-         HWND hFirstStop = NULL; 
-    HWND hWndTab; 
-   
-         while ((status = GetMessage (&msg, NULL, 0, 0))) 
-         { 
-                 if (status == -1) 
-                         return; // Exception 
-   
-       // if this page is being closed, stop the loop 
-                 if ((msg.message == WM_SHOWWINDOW) && (msg.wParam == FALSE)) 
-                         return; 
-   
-                 //IsDialogMessage() dispatches WM_KEYDOWN to the tab page's child controls 
-                 // so we'll sniff them out before they are translated and dispatched. 
-                 if ((msg.message == WM_KEYDOWN) && (msg.wParam == VK_TAB)) 
-                 { 
-                         //Tab each tabstop in a tab page once and then return to to 
-                         // the tabCtl selected tab 
-                         if (hFirstStop == NULL) 
-                                 hFirstStop = msg.hwnd; 
-                         else if (hFirstStop == msg.hwnd) 
-                         { 
-                                 // Tab off the tab page and stop the loop 
-                                 hWndTab = (HWND) GetWindowLong (GetParent (msg.hwnd), GWL_USERDATA); 
-                                 if (hWndTab != NULL) 
-                                 SetFocus (hWndTab); // tab off the tab page 
-                                 return; // and stop the loop 
-                         } 
-                 } 
-   
-       // Perform default dialog message processing using IsDialogMessage... 
-                 // Non dialog message handled in the standard way. 
-                 if (!IsDialogMessage (hWnd, &msg)) 
-                 { 
-                         TranslateMessage(&msg); 
-                         DispatchMessage(&msg); 
-                 } 
-         } 
-         // If we get here window is closing 
-         PostQuitMessage(0); 
-         return; 
- } 
-   
-   
- bool TabControl_Notify (NMHDR *pnm) 
- { 
-    // handle WM_NOTIFY messages. Returns TRUE if message was handled. 
-   
-    tabcontrol_t *tab_control; 
-    int currentpage_index; 
-    TC_KEYDOWN *tk; 
-   
-    // get the tab control structure 
-    tab_control = (tabcontrol_t *) GetWindowLong (pnm->hwndFrom, GWL_USERDATA); 
-   
-    // is it a "key down" notification ? 
-    if (pnm->code == TCN_KEYDOWN) 
-    { 
-       // handle key presses in the tab control (but not the tab pages) 
-       tk = (TC_KEYDOWN *) pnm; 
-   
-       // is there more than one tab ? 
-       if (TabCtrl_GetItemCount (tk->hdr.hwndFrom) > 1) 
-       { 
-          currentpage_index = TabCtrl_GetCurSel (tk->hdr.hwndFrom); 
-   
-          // are tabs vertical ? 
-          if (GetWindowLong (tab_control->hWnd, GWL_STYLE) & TCS_VERTICAL) 
-          { 
-             if ((tk->wVKey == VK_PRIOR) && (currentpage_index > 0)) 
-             { 
-                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index - 1); // select the previous page 
-                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index - 1); 
-             } 
-             else if ((tk->wVKey == VK_UP) && (currentpage_index > 0)) 
-             { 
-                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index - 1); // select the previous page 
-                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index); 
-             } 
-             else if (tk->wVKey == VK_NEXT) 
-             { 
-                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index + 1); // select the next page 
-                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index + 1); 
-             } 
-             else if (tk->wVKey == VK_DOWN) 
-             { 
-                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index + 1); // select the next page 
-                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index); 
-             } 
-             else if ((tk->wVKey == VK_LEFT) || (tk->wVKey == VK_RIGHT)) 
-             { 
-                SetFocus (tab_control->pages[currentpage_index].hWnd); // navigate within selected child tab page 
-                TabControl_SetFocusOnFirstTabStop (tab_control->pages[currentpage_index].hWnd); 
-                TabPage_MessageLoop (tab_control->pages[currentpage_index].hWnd); 
-             } 
-          } 
-   
-          // else tabs must be horizontal (default style) 
-          else 
-          { 
-             if ((tk->wVKey == VK_NEXT) && (currentpage_index > 0)) 
-             { 
-                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index - 1); // select the previous page 
-                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index - 1); 
-             } 
-             else if ((tk->wVKey == VK_LEFT) && (currentpage_index > 0)) 
-             { 
-                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index - 1); // select the previous page 
-                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index); 
-             } 
-             else if (tk->wVKey == VK_PRIOR) 
-             { 
-                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index + 1); // select the next page 
-                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index + 1); 
-             } 
-             else if (tk->wVKey == VK_RIGHT) 
-             { 
-                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index + 1); // select the next page 
-                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index); 
-             } 
-             else if ((tk->wVKey == VK_UP) || (tk->wVKey == VK_DOWN)) 
-             { 
-                SetFocus (tab_control->pages[currentpage_index].hWnd); // navigate within selected child tab page 
-                TabControl_SetFocusOnFirstTabStop (tab_control->pages[currentpage_index].hWnd); 
-                TabPage_MessageLoop (tab_control->pages[currentpage_index].hWnd); 
-             } 
-          } 
-       } 
-   
-       return (false); // we didn't handle the message, return FALSE 
-    } 
-   
-    // else is it a tab page change notification ? 
-    else if (pnm->code == TCN_SELCHANGE) 
-    { 
-       // handle TCN_SELCHANGE notification, which is sent when a tab has been pressed. 
-       currentpage_index = TabCtrl_GetCurSel (tab_control->hWnd); // get the new current tab 
-       ShowWindow (tab_control->pages[tab_control->activepage_index].hWnd, SW_HIDE); // hide the current child dialog box, if any 
-   
-       // ShowWindow() does not seem to post the WM_SHOWWINDOW message to the tab page. Since we use 
-       // the hiding of the window as an indication to stop the message loop, let's post it explicitly 
-       PostMessage (tab_control->pages[tab_control->activepage_index].hWnd, WM_SHOWWINDOW, FALSE, 0); 
-       ShowWindow (tab_control->pages[currentpage_index].hWnd, SW_SHOWNORMAL); // show the new child dialog box 
-       tab_control->activepage_index = currentpage_index; // save the current child 
-   
-       return (true); // message was handled, return TRUE 
-    } 
-   
-    return (false); // we didn't handle the message, return FALSE 
- } 
-   
-   
- void *TabControl_New (HWND hTabControlWnd, WNDPROC ParentProc) 
- { 
-    // initialize the hTabControlWnd control handle as a tab control, and return an opaque pointer to it 
-   
-    tabcontrol_t *the_control; 
-   
-    the_control = (tabcontrol_t *) malloc (sizeof (tabcontrol_t)); 
-    the_control->hWnd = hTabControlWnd; // save tab control's window handle 
-    the_control->ParentProc = ParentProc; // save tab control parent's window procedure 
-    the_control->pages = NULL; 
-    the_control->page_count = 0; 
-    the_control->activepage_index = -1; 
-    SetWindowLong (the_control->hWnd, GWL_USERDATA, (LONG) the_control); // save tab control structure address to GWL_USERDATA 
-   
-    return (the_control); // tab control is created, return an opaque pointer to it 
- } 
-   
-   
- void TabControl_AddPage (void *tab_control, wchar_t *page_name, int dialog_id) 
- { 
-    // add a page to a tab control from a dialog ID 
-   
-    tabcontrol_t *the_control; 
-    RECT client_rect; 
-    TCITEM tie; 
-   
-    the_control = (tabcontrol_t *) tab_control; // quick access to tab control 
-   
-    // get tab control's client rectangle 
-    TabControl_GetClientRect (the_control->hWnd, &client_rect); // left, top, width, height 
-   
-    // resize pages array based on number of pages 
-    the_control->pages = (tabpage_t *) realloc (the_control->pages, (the_control->page_count + 1) * sizeof (tabpage_t *)); 
-   
-    // Add a tab for each name in tabnames (list ends with 0) 
-    memset (&tie, 0, sizeof (tie)); 
-    tie.mask = TCIF_TEXT | TCIF_IMAGE; 
-    tie.iImage = -1; 
-    tie.pszText = page_name; 
-    TabCtrl_InsertItem (the_control->hWnd, the_control->page_count, &tie); 
-   
-    // create the page dialog with the tab parent's as parent, and save this tab's "real" parent, which is our tab container, in GWL_USERDATA 
-    the_control->pages[the_control->page_count].hWnd = CreateDialog (GetModuleHandle (NULL), MAKEINTRESOURCE (dialog_id), GetParent (the_control->hWnd), (DLGPROC) TabPage_DlgProc); 
-    SetWindowLong (the_control->pages[the_control->page_count].hWnd, GWL_USERDATA, (LONG) the_control->hWnd); 
-    SetWindowPos (the_control->pages[the_control->page_count].hWnd, HWND_TOP, client_rect.left, client_rect.top, client_rect.right, client_rect.bottom, 0); 
-    the_control->page_count++; // tab control has now one page more 
-   
-    // save the current visible page and show it 
-    the_control->activepage_index = 0; 
-    ShowWindow (the_control->pages[the_control->activepage_index].hWnd, SW_SHOW); 
-   
-    return; // finished, page has been added 
- } 
-   
-   
- void TabControl_SelectTab (void *tab_control, int page_index) 
- { 
-    tabcontrol_t *the_control; 
-    NMHDR nmh; 
-   
-    the_control = (tabcontrol_t *) tab_control; // quick access to tab control 
-   
-    TabCtrl_SetCurSel (the_control->hWnd, page_index); 
-    memset (&nmh, 0, sizeof (nmh)); 
-    nmh.hwndFrom = the_control->hWnd; 
-    nmh.idFrom = GetDlgCtrlID (the_control->hWnd); 
-    nmh.code = TCN_SELCHANGE; 
-   
-    SendMessage (the_control->hWnd, WM_NOTIFY, (WPARAM) nmh.idFrom, (LPARAM) &nmh); 
-    return; 
- } 
-   
-   
- HWND TabControl_GetItem (void *tab_control, int item_id) 
- { 
-    // helper function to set a tab control item's text 
-   
-    tabcontrol_t *the_control; 
-    int page_index; 
-   
-    the_control = (tabcontrol_t *) tab_control; // quick access to tab control 
-   
-    // cycle through all pages and return the one which has the desired item 
-    for (page_index = 0; page_index < the_control->page_count; page_index++) 
-       if (IsWindow (GetDlgItem (the_control->pages[page_index].hWnd, item_id))) 
-          return (GetDlgItem (the_control->pages[page_index].hWnd, item_id)); 
-   
-    return (NULL); // this item was not found in tab control 
- } 
-   
-   
- void TabControl_Destroy (void *tab_control) 
- { 
-    // destroy the tab page dialogs and free the list of pointers to the dialogs 
-   
-    tabcontrol_t *the_control; 
-    int page_index; 
-   
-    the_control = (tabcontrol_t *) tab_control; // quick access to tab control 
-   
-    // destroy each window of this tab successively 
-    for (page_index = 0; page_index < the_control->page_count; page_index++) 
-       DestroyWindow (the_control->pages[page_index].hWnd); 
-   
-    free (the_control->pages); 
-    free (the_control); 
-    return; // finished, the tab control structure's allocated elements are freed 
- } 
-