Subversion Repositories Games.Chess Giants

Rev

Rev 1 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. // tabcontrol.cpp
  2.  
  3. #include <windows.h>
  4. #include <commctrl.h>
  5.  
  6.  
  7. typedef struct tabpage_s
  8. {
  9.    HWND hWnd; // window handle of the controls tab page sub-window
  10. } tabpage_t;
  11.  
  12.  
  13. typedef struct tabcontrol_s
  14. {
  15.    HWND hWnd; // window handle of the tab control
  16.    tabpage_t *pages; // mallocated array of pages in this tab control
  17.    int page_count; // number of pages in this tab control
  18.    int activepage_index; // index of the active page
  19.    WNDPROC ParentProc; // function pointer to Parent Dialog Proc
  20. } tabcontrol_t;
  21.  
  22.  
  23. // prototypes of exported functions
  24. void *TabControl_New (HWND hTabControlWnd, WNDPROC ParentProc);
  25. void TabControl_AddPage (void *tab_control, wchar_t *page_name, int dialog_id);
  26. void TabControl_SelectTab (void *tab_control, int page_index);
  27. HWND TabControl_GetItem (void *tab_control, int item_id);
  28. void TabControl_Destroy (void *tab_control);
  29. bool TabControl_Notify (NMHDR *notification);
  30.  
  31.  
  32. // prototypes of local functions
  33. static void TabControl_SetFocusOnFirstTabStop (HWND hWnd);
  34. static void TabControl_GetClientRect (HWND hWnd, RECT *client_rect);
  35. static int CALLBACK TabPage_DlgProc (HWND hwndDlg, unsigned int message, WPARAM wParam, LPARAM lParam);
  36. static void TabPage_MessageLoop (HWND hWnd);
  37.  
  38.  
  39. static void TabControl_SetFocusOnFirstTabStop (HWND hWnd)
  40. {
  41.    // this function ensures focus on first tab stop when entering a tab page.
  42.    // WM_NEXTDLGCTL wparam = 1 shifts focus back to the LAST tabstop in this tabpage.
  43.    // Posting a VK_TAB returns focus to the first tab stop and causes it to be recognized
  44.    // as the first tabstop by the WM_KEYDOWN sniffer in the TabPageMessageLoop().
  45.  
  46.    SendMessage (hWnd, WM_NEXTDLGCTL, 1, FALSE);
  47.    PostMessage (GetFocus (), WM_KEYDOWN, VK_TAB, 0);
  48.  
  49.    return;
  50. }
  51.  
  52.  
  53. static void TabControl_GetClientRect (HWND hWnd, RECT *client_rect)
  54. {
  55.    // returns the client rectangle for the tab control under every possible configuration
  56.    // (normal tabs, tabs with buttons, vertical tabs, etc.)
  57.    // IMPORTANT: the rectangle structure is populated as follows:
  58.    // left = left, top = top, right = WIDTH, bottom = HEIGHT
  59.  
  60.    long tab_style;
  61.    RECT tab_rect;
  62.    
  63.    tab_style = GetWindowLong (hWnd, GWL_STYLE);
  64.  
  65.    // calculate the tab control's display area
  66.    GetWindowRect (hWnd, client_rect);
  67.    ScreenToClient (GetParent (hWnd), (POINT *) &client_rect->left);
  68.    ScreenToClient (hWnd, (POINT *) &client_rect->right);
  69.    TabCtrl_GetItemRect (hWnd, 0, &tab_rect); // the tab itself
  70.  
  71.    // vertical tabs to the right ?
  72.    if ((tab_style & TCS_BOTTOM) && (tab_style & TCS_VERTICAL))
  73.    {
  74.       client_rect->top += 6; // x coord
  75.       client_rect->left += 4; // y coord
  76.       client_rect->bottom -= 12; // height
  77.       client_rect->right -= (12 + tab_rect.right - tab_rect.left); // width
  78.    }
  79.  
  80.    // vertical tabs to the left ?
  81.    else if (tab_style & TCS_VERTICAL)
  82.    {
  83.       client_rect->top += 6; //x coord
  84.       client_rect->left += (4 + tab_rect.right - tab_rect.left); // y coord
  85.       client_rect->bottom -= 12; // height
  86.       client_rect->right -= (12 + tab_rect.right - tab_rect.left); // width
  87.    }
  88.  
  89.    // horizontal tabs at the bottom ?
  90.    else if (tab_style & TCS_BOTTOM)
  91.    {
  92.       client_rect->top += 6; // x coord
  93.       client_rect->left += 4; // y coord
  94.       client_rect->bottom -= (16 + tab_rect.bottom - tab_rect.top); // height
  95.       client_rect->right -= 12; // width
  96.    }
  97.  
  98.    // else it must be horizontal tabs at the top (default style)
  99.    else
  100.    {
  101.       client_rect->top += (6 + tab_rect.bottom - tab_rect.top); // x coord
  102.       client_rect->left += 4; // y coord
  103.       client_rect->bottom -= (16 + tab_rect.bottom - tab_rect.top); // height
  104.       client_rect->right -= 12; // width
  105.    }
  106.  
  107.    return; // finished populating the rectangle
  108. }
  109.  
  110.  
  111. static int CALLBACK TabPage_DlgProc (HWND hwndDlg, unsigned int message, WPARAM wParam, LPARAM lParam)
  112. {
  113.    // window procedure for tab page dialogs
  114.  
  115.    tabcontrol_t *tab_control;
  116.    NMTCKEYDOWN nm;
  117.    HWND hWndTab;
  118.  
  119.    // get a pointer to the tab container window
  120.    hWndTab = (HWND) GetWindowLong (hwndDlg, GWL_USERDATA);
  121.    if (hWndTab == NULL)
  122.       return (0); // consistency check
  123.  
  124.    // now get a pointer to the tab container's structure
  125.    tab_control = (tabcontrol_t *) GetWindowLong (hWndTab, GWL_USERDATA);
  126.  
  127.    // is this page being initialized ?
  128.    if (message == WM_INITDIALOG)
  129.       return (DefWindowProc (hwndDlg, WM_INITDIALOG, wParam, lParam)); // if so, don't hook its initialization message
  130.  
  131.    // else is a control being acted upon in this page ?
  132.    else if (message == WM_COMMAND)
  133.    {
  134.       // forward all commands to the parent proc
  135.       tab_control->ParentProc (hwndDlg, message, wParam, lParam);
  136.  
  137.       // is this WM_COMMAND message NOT a notification to the parent window ?
  138.       if (HIWORD (wParam) == 0)
  139.       {
  140.          // mouse clicks on a control should engage the Message Loop
  141.          SetFocus ((HWND) lParam);
  142.          TabControl_SetFocusOnFirstTabStop (hwndDlg);
  143.          TabPage_MessageLoop (hwndDlg);
  144.       }
  145.  
  146.       SetWindowLongPtr (hwndDlg, DWLP_MSGRESULT, 0);
  147.       return (1); // return TRUE so as to notify we handled this message
  148.    }
  149.  
  150.    // else is the mouse clicking something ?
  151.    else if (message == WM_LBUTTONDOWN)
  152.    {
  153.       // simulate keypress access so that everything is handled in the same consistent way
  154.       memset (&nm, 0, sizeof (nm));
  155.       nm.hdr.hwndFrom = tab_control->hWnd;
  156.       nm.hdr.idFrom = GetDlgCtrlID (tab_control->hWnd);
  157.       nm.hdr.code = TCN_KEYDOWN;
  158.       nm.wVKey = (GetWindowLong (tab_control->hWnd, GWL_STYLE) & TCS_VERTICAL ? VK_LEFT : VK_DOWN);
  159.       nm.flags = 0;
  160.       SendMessage (nm.hdr.hwndFrom, WM_NOTIFY, nm.hdr.idFrom, (LPARAM) &nm);
  161.  
  162.       SetWindowLongPtr (hwndDlg, DWLP_MSGRESULT, 0);
  163.       return (1); // return TRUE so as to notify we handled this message
  164.    }
  165.  
  166.    // forward all other messages to the parent proc
  167.    tab_control->ParentProc (hwndDlg, message, wParam, lParam);
  168.    return (0);
  169. }
  170.  
  171. /****************************************************************************
  172. *     PURPOSE:  Monitor and respond to user keyboard input and system messages.
  173. *
  174. *     PARAMS:   HWND hwnd - handle to the currently visible tab page
  175. *
  176. *     COMMENTS: Send PostQuitMessage(0); from any cancel or exit event.
  177. *               Failure to do so will leave the process running even after
  178. *               application exit.
  179. \****************************************************************************/
  180.  
  181. static void TabPage_MessageLoop (HWND hWnd)
  182. {
  183.         MSG msg;
  184.         int status;
  185.         HWND hFirstStop = NULL;
  186.    HWND hWndTab;
  187.  
  188.         while ((status = GetMessage (&msg, NULL, 0, 0)))
  189.         {
  190.                 if (status == -1)
  191.                         return; // Exception
  192.  
  193.       // if this page is being closed, stop the loop
  194.                 if ((msg.message == WM_SHOWWINDOW) && (msg.wParam == FALSE))
  195.                         return;
  196.  
  197.                 //IsDialogMessage() dispatches WM_KEYDOWN to the tab page's child controls
  198.                 // so we'll sniff them out before they are translated and dispatched.
  199.                 if ((msg.message == WM_KEYDOWN) && (msg.wParam == VK_TAB))
  200.                 {
  201.                         //Tab each tabstop in a tab page once and then return to to
  202.                         // the tabCtl selected tab
  203.                         if (hFirstStop == NULL)
  204.                                 hFirstStop = msg.hwnd;
  205.                         else if (hFirstStop == msg.hwnd)
  206.                         {
  207.                                 // Tab off the tab page and stop the loop
  208.                                 hWndTab = (HWND) GetWindowLong (GetParent (msg.hwnd), GWL_USERDATA);
  209.                                 if (hWndTab != NULL)
  210.                                 SetFocus (hWndTab); // tab off the tab page
  211.                                 return; // and stop the loop
  212.                         }
  213.                 }
  214.  
  215.       // Perform default dialog message processing using IsDialogMessage...
  216.                 // Non dialog message handled in the standard way.
  217.                 if (!IsDialogMessage (hWnd, &msg))
  218.                 {
  219.                         TranslateMessage(&msg);
  220.                         DispatchMessage(&msg);
  221.                 }
  222.         }
  223.         // If we get here window is closing
  224.         PostQuitMessage(0);
  225.         return;
  226. }
  227.  
  228.  
  229. bool TabControl_Notify (NMHDR *pnm)
  230. {
  231.    // handle WM_NOTIFY messages. Returns TRUE if message was handled.
  232.  
  233.    tabcontrol_t *tab_control;
  234.    int currentpage_index;
  235.    TC_KEYDOWN *tk;
  236.  
  237.    // get the tab control structure
  238.    tab_control = (tabcontrol_t *) GetWindowLong (pnm->hwndFrom, GWL_USERDATA);
  239.  
  240.    // is it a "key down" notification ?
  241.    if (pnm->code == TCN_KEYDOWN)
  242.    {
  243.       // handle key presses in the tab control (but not the tab pages)
  244.       tk = (TC_KEYDOWN *) pnm;
  245.  
  246.       // is there more than one tab ?
  247.       if (TabCtrl_GetItemCount (tk->hdr.hwndFrom) > 1)
  248.       {
  249.          currentpage_index = TabCtrl_GetCurSel (tk->hdr.hwndFrom);
  250.  
  251.          // are tabs vertical ?
  252.          if (GetWindowLong (tab_control->hWnd, GWL_STYLE) & TCS_VERTICAL)
  253.          {
  254.             if ((tk->wVKey == VK_PRIOR) && (currentpage_index > 0))
  255.             {
  256.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index - 1); // select the previous page
  257.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index - 1);
  258.             }
  259.             else if ((tk->wVKey == VK_UP) && (currentpage_index > 0))
  260.             {
  261.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index - 1); // select the previous page
  262.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index);
  263.             }
  264.             else if (tk->wVKey == VK_NEXT)
  265.             {
  266.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index + 1); // select the next page
  267.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index + 1);
  268.             }
  269.             else if (tk->wVKey == VK_DOWN)
  270.             {
  271.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index + 1); // select the next page
  272.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index);
  273.             }
  274.             else if ((tk->wVKey == VK_LEFT) || (tk->wVKey == VK_RIGHT))
  275.             {
  276.                SetFocus (tab_control->pages[currentpage_index].hWnd); // navigate within selected child tab page
  277.                TabControl_SetFocusOnFirstTabStop (tab_control->pages[currentpage_index].hWnd);
  278.                TabPage_MessageLoop (tab_control->pages[currentpage_index].hWnd);
  279.             }
  280.          }
  281.  
  282.          // else tabs must be horizontal (default style)
  283.          else
  284.          {
  285.             if ((tk->wVKey == VK_NEXT) && (currentpage_index > 0))
  286.             {
  287.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index - 1); // select the previous page
  288.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index - 1);
  289.             }
  290.             else if ((tk->wVKey == VK_LEFT) && (currentpage_index > 0))
  291.             {
  292.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index - 1); // select the previous page
  293.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index);
  294.             }
  295.             else if (tk->wVKey == VK_PRIOR)
  296.             {
  297.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index + 1); // select the next page
  298.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index + 1);
  299.             }
  300.             else if (tk->wVKey == VK_RIGHT)
  301.             {
  302.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index + 1); // select the next page
  303.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index);
  304.             }
  305.             else if ((tk->wVKey == VK_UP) || (tk->wVKey == VK_DOWN))
  306.             {
  307.                SetFocus (tab_control->pages[currentpage_index].hWnd); // navigate within selected child tab page
  308.                TabControl_SetFocusOnFirstTabStop (tab_control->pages[currentpage_index].hWnd);
  309.                TabPage_MessageLoop (tab_control->pages[currentpage_index].hWnd);
  310.             }
  311.          }
  312.       }
  313.  
  314.       return (false); // we didn't handle the message, return FALSE
  315.    }
  316.  
  317.    // else is it a tab page change notification ?
  318.    else if (pnm->code == TCN_SELCHANGE)
  319.    {
  320.       // handle TCN_SELCHANGE notification, which is sent when a tab has been pressed.
  321.       currentpage_index = TabCtrl_GetCurSel (tab_control->hWnd); // get the new current tab
  322.       ShowWindow (tab_control->pages[tab_control->activepage_index].hWnd, SW_HIDE); // hide the current child dialog box, if any
  323.  
  324.       // ShowWindow() does not seem to post the WM_SHOWWINDOW message to the tab page. Since we use
  325.       // the hiding of the window as an indication to stop the message loop, let's post it explicitly
  326.       PostMessage (tab_control->pages[tab_control->activepage_index].hWnd, WM_SHOWWINDOW, FALSE, 0);
  327.       ShowWindow (tab_control->pages[currentpage_index].hWnd, SW_SHOWNORMAL); // show the new child dialog box
  328.       tab_control->activepage_index = currentpage_index; // save the current child
  329.  
  330.       return (true); // message was handled, return TRUE
  331.    }
  332.  
  333.    return (false); // we didn't handle the message, return FALSE
  334. }
  335.  
  336.  
  337. void *TabControl_New (HWND hTabControlWnd, WNDPROC ParentProc)
  338. {
  339.    // initialize the hTabControlWnd control handle as a tab control, and return an opaque pointer to it
  340.  
  341.    tabcontrol_t *the_control;
  342.  
  343.    the_control = (tabcontrol_t *) malloc (sizeof (tabcontrol_t));
  344.    the_control->hWnd = hTabControlWnd; // save tab control's window handle
  345.    the_control->ParentProc = ParentProc; // save tab control parent's window procedure
  346.    the_control->pages = NULL;
  347.    the_control->page_count = 0;
  348.    the_control->activepage_index = -1;
  349.    SetWindowLong (the_control->hWnd, GWL_USERDATA, (LONG) the_control); // save tab control structure address to GWL_USERDATA
  350.  
  351.    return (the_control); // tab control is created, return an opaque pointer to it
  352. }
  353.  
  354.  
  355. void TabControl_AddPage (void *tab_control, wchar_t *page_name, int dialog_id)
  356. {
  357.    // add a page to a tab control from a dialog ID
  358.  
  359.    tabcontrol_t *the_control;
  360.    RECT client_rect;
  361.    TCITEM tie;
  362.  
  363.    the_control = (tabcontrol_t *) tab_control; // quick access to tab control
  364.  
  365.    // get tab control's client rectangle
  366.    TabControl_GetClientRect (the_control->hWnd, &client_rect); // left, top, width, height
  367.  
  368.    // resize pages array based on number of pages
  369.    the_control->pages = (tabpage_t *) realloc (the_control->pages, (the_control->page_count + 1) * sizeof (tabpage_t *));
  370.  
  371.    // Add a tab for each name in tabnames (list ends with 0)
  372.    memset (&tie, 0, sizeof (tie));
  373.    tie.mask = TCIF_TEXT | TCIF_IMAGE;
  374.    tie.iImage = -1;
  375.    tie.pszText = page_name;
  376.    TabCtrl_InsertItem (the_control->hWnd, the_control->page_count, &tie);
  377.  
  378.    // 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
  379.    the_control->pages[the_control->page_count].hWnd = CreateDialog (GetModuleHandle (NULL), MAKEINTRESOURCE (dialog_id), GetParent (the_control->hWnd), (DLGPROC) TabPage_DlgProc);
  380.    SetWindowLong (the_control->pages[the_control->page_count].hWnd, GWL_USERDATA, (LONG) the_control->hWnd);
  381.    SetWindowPos (the_control->pages[the_control->page_count].hWnd, HWND_TOP, client_rect.left, client_rect.top, client_rect.right, client_rect.bottom, 0);
  382.    the_control->page_count++; // tab control has now one page more
  383.  
  384.    // save the current visible page and show it
  385.    the_control->activepage_index = 0;
  386.    ShowWindow (the_control->pages[the_control->activepage_index].hWnd, SW_SHOW);
  387.  
  388.    return; // finished, page has been added
  389. }
  390.  
  391.  
  392. void TabControl_SelectTab (void *tab_control, int page_index)
  393. {
  394.    tabcontrol_t *the_control;
  395.    NMHDR nmh;
  396.  
  397.    the_control = (tabcontrol_t *) tab_control; // quick access to tab control
  398.  
  399.    TabCtrl_SetCurSel (the_control->hWnd, page_index);
  400.    memset (&nmh, 0, sizeof (nmh));
  401.    nmh.hwndFrom = the_control->hWnd;
  402.    nmh.idFrom = GetDlgCtrlID (the_control->hWnd);
  403.    nmh.code = TCN_SELCHANGE;
  404.  
  405.    SendMessage (the_control->hWnd, WM_NOTIFY, (WPARAM) nmh.idFrom, (LPARAM) &nmh);
  406.    return;
  407. }
  408.  
  409.  
  410. HWND TabControl_GetItem (void *tab_control, int item_id)
  411. {
  412.    // helper function to set a tab control item's text
  413.  
  414.    tabcontrol_t *the_control;
  415.    int page_index;
  416.  
  417.    the_control = (tabcontrol_t *) tab_control; // quick access to tab control
  418.  
  419.    // cycle through all pages and return the one which has the desired item
  420.    for (page_index = 0; page_index < the_control->page_count; page_index++)
  421.       if (IsWindow (GetDlgItem (the_control->pages[page_index].hWnd, item_id)))
  422.          return (GetDlgItem (the_control->pages[page_index].hWnd, item_id));
  423.  
  424.    return (NULL); // this item was not found in tab control
  425. }
  426.  
  427.  
  428. void TabControl_Destroy (void *tab_control)
  429. {
  430.    // destroy the tab page dialogs and free the list of pointers to the dialogs
  431.  
  432.    tabcontrol_t *the_control;
  433.    int page_index;
  434.  
  435.    the_control = (tabcontrol_t *) tab_control; // quick access to tab control
  436.  
  437.    // destroy each window of this tab successively
  438.    for (page_index = 0; page_index < the_control->page_count; page_index++)
  439.       DestroyWindow (the_control->pages[page_index].hWnd);
  440.  
  441.    free (the_control->pages);
  442.    free (the_control);
  443.    return; // finished, the tab control structure's allocated elements are freed
  444. }
  445.