Subversion Repositories Games.Chess Giants

Rev

Go to most recent revision | 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.       nm.hdr.hwndFrom = tab_control->hWnd;
  155.       nm.hdr.idFrom = GetDlgCtrlID (tab_control->hWnd);
  156.       nm.hdr.code = TCN_KEYDOWN;
  157.       nm.wVKey = (GetWindowLong (tab_control->hWnd, GWL_STYLE) & TCS_VERTICAL ? VK_LEFT : VK_DOWN);
  158.       nm.flags = 0;
  159.       SendMessage (nm.hdr.hwndFrom, WM_NOTIFY, nm.hdr.idFrom, (LPARAM) &nm);
  160.  
  161.       SetWindowLongPtr (hwndDlg, DWLP_MSGRESULT, 0);
  162.       return (1); // return TRUE so as to notify we handled this message
  163.    }
  164.  
  165.    // forward all other messages to the parent proc
  166.    tab_control->ParentProc (hwndDlg, message, wParam, lParam);
  167.    return (0);
  168. }
  169.  
  170. /****************************************************************************
  171. *     PURPOSE:  Monitor and respond to user keyboard input and system messages.
  172. *
  173. *     PARAMS:   HWND hwnd - handle to the currently visible tab page
  174. *
  175. *     COMMENTS: Send PostQuitMessage(0); from any cancel or exit event.
  176. *               Failure to do so will leave the process running even after
  177. *               application exit.
  178. \****************************************************************************/
  179.  
  180. static void TabPage_MessageLoop (HWND hWnd)
  181. {
  182.         MSG msg;
  183.         int status;
  184.         HWND hFirstStop = NULL;
  185.    HWND hWndTab;
  186.  
  187.         while ((status = GetMessage (&msg, NULL, 0, 0)))
  188.         {
  189.                 if (status == -1)
  190.                         return; // Exception
  191.  
  192.       // if this page is being closed, stop the loop
  193.                 if ((msg.message == WM_SHOWWINDOW) && (msg.wParam == FALSE))
  194.                         return;
  195.  
  196.                 //IsDialogMessage() dispatches WM_KEYDOWN to the tab page's child controls
  197.                 // so we'll sniff them out before they are translated and dispatched.
  198.                 if ((msg.message == WM_KEYDOWN) && (msg.wParam == VK_TAB))
  199.                 {
  200.                         //Tab each tabstop in a tab page once and then return to to
  201.                         // the tabCtl selected tab
  202.                         if (hFirstStop == NULL)
  203.                                 hFirstStop = msg.hwnd;
  204.                         else if (hFirstStop == msg.hwnd)
  205.                         {
  206.                                 // Tab off the tab page and stop the loop
  207.                                 hWndTab = (HWND) GetWindowLong (GetParent (msg.hwnd), GWL_USERDATA);
  208.                                 if (hWndTab != NULL)
  209.                                 SetFocus (hWndTab); // tab off the tab page
  210.                                 return; // and stop the loop
  211.                         }
  212.                 }
  213.  
  214.       // Perform default dialog message processing using IsDialogMessage...
  215.                 // Non dialog message handled in the standard way.
  216.                 if (!IsDialogMessage (hWnd, &msg))
  217.                 {
  218.                         TranslateMessage(&msg);
  219.                         DispatchMessage(&msg);
  220.                 }
  221.         }
  222.         // If we get here window is closing
  223.         PostQuitMessage(0);
  224.         return;
  225. }
  226.  
  227.  
  228. bool TabControl_Notify (NMHDR *pnm)
  229. {
  230.    // handle WM_NOTIFY messages. Returns TRUE if message was handled.
  231.  
  232.    tabcontrol_t *tab_control;
  233.    int currentpage_index;
  234.    TC_KEYDOWN *tk;
  235.  
  236.    // get the tab control structure
  237.    tab_control = (tabcontrol_t *) GetWindowLong (pnm->hwndFrom, GWL_USERDATA);
  238.  
  239.    // is it a "key down" notification ?
  240.    if (pnm->code == TCN_KEYDOWN)
  241.    {
  242.       // handle key presses in the tab control (but not the tab pages)
  243.       tk = (TC_KEYDOWN *) pnm;
  244.  
  245.       // is there more than one tab ?
  246.       if (TabCtrl_GetItemCount (tk->hdr.hwndFrom) > 1)
  247.       {
  248.          currentpage_index = TabCtrl_GetCurSel (tk->hdr.hwndFrom);
  249.  
  250.          // are tabs vertical ?
  251.          if (GetWindowLong (tab_control->hWnd, GWL_STYLE) & TCS_VERTICAL)
  252.          {
  253.             if ((tk->wVKey == VK_PRIOR) && (currentpage_index > 0))
  254.             {
  255.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index - 1); // select the previous page
  256.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index - 1);
  257.             }
  258.             else if ((tk->wVKey == VK_UP) && (currentpage_index > 0))
  259.             {
  260.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index - 1); // select the previous page
  261.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index);
  262.             }
  263.             else if (tk->wVKey == VK_NEXT)
  264.             {
  265.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index + 1); // select the next page
  266.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index + 1);
  267.             }
  268.             else if (tk->wVKey == VK_DOWN)
  269.             {
  270.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index + 1); // select the next page
  271.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index);
  272.             }
  273.             else if ((tk->wVKey == VK_LEFT) || (tk->wVKey == VK_RIGHT))
  274.             {
  275.                SetFocus (tab_control->pages[currentpage_index].hWnd); // navigate within selected child tab page
  276.                TabControl_SetFocusOnFirstTabStop (tab_control->pages[currentpage_index].hWnd);
  277.                TabPage_MessageLoop (tab_control->pages[currentpage_index].hWnd);
  278.             }
  279.          }
  280.  
  281.          // else tabs must be horizontal (default style)
  282.          else
  283.          {
  284.             if ((tk->wVKey == VK_NEXT) && (currentpage_index > 0))
  285.             {
  286.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index - 1); // select the previous page
  287.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index - 1);
  288.             }
  289.             else if ((tk->wVKey == VK_LEFT) && (currentpage_index > 0))
  290.             {
  291.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index - 1); // select the previous page
  292.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index);
  293.             }
  294.             else if (tk->wVKey == VK_PRIOR)
  295.             {
  296.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index + 1); // select the next page
  297.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index + 1);
  298.             }
  299.             else if (tk->wVKey == VK_RIGHT)
  300.             {
  301.                TabCtrl_SetCurSel (tk->hdr.hwndFrom, currentpage_index + 1); // select the next page
  302.                TabCtrl_SetCurFocus (tk->hdr.hwndFrom, currentpage_index);
  303.             }
  304.             else if ((tk->wVKey == VK_UP) || (tk->wVKey == VK_DOWN))
  305.             {
  306.                SetFocus (tab_control->pages[currentpage_index].hWnd); // navigate within selected child tab page
  307.                TabControl_SetFocusOnFirstTabStop (tab_control->pages[currentpage_index].hWnd);
  308.                TabPage_MessageLoop (tab_control->pages[currentpage_index].hWnd);
  309.             }
  310.          }
  311.       }
  312.  
  313.       return (false); // we didn't handle the message, return FALSE
  314.    }
  315.  
  316.    // else is it a tab page change notification ?
  317.    else if (pnm->code == TCN_SELCHANGE)
  318.    {
  319.       // handle TCN_SELCHANGE notification, which is sent when a tab has been pressed.
  320.       currentpage_index = TabCtrl_GetCurSel (tab_control->hWnd); // get the new current tab
  321.       ShowWindow (tab_control->pages[tab_control->activepage_index].hWnd, SW_HIDE); // hide the current child dialog box, if any
  322.  
  323.       // ShowWindow() does not seem to post the WM_SHOWWINDOW message to the tab page. Since we use
  324.       // the hiding of the window as an indication to stop the message loop, let's post it explicitly
  325.       PostMessage (tab_control->pages[tab_control->activepage_index].hWnd, WM_SHOWWINDOW, FALSE, 0);
  326.       ShowWindow (tab_control->pages[currentpage_index].hWnd, SW_SHOWNORMAL); // show the new child dialog box
  327.       tab_control->activepage_index = currentpage_index; // save the current child
  328.  
  329.       return (true); // message was handled, return TRUE
  330.    }
  331.  
  332.    return (false); // we didn't handle the message, return FALSE
  333. }
  334.  
  335.  
  336. void *TabControl_New (HWND hTabControlWnd, WNDPROC ParentProc)
  337. {
  338.    // initialize the hTabControlWnd control handle as a tab control, and return an opaque pointer to it
  339.  
  340.    tabcontrol_t *the_control;
  341.  
  342.    the_control = (tabcontrol_t *) malloc (sizeof (tabcontrol_t));
  343.    the_control->hWnd = hTabControlWnd; // save tab control's window handle
  344.    the_control->ParentProc = ParentProc; // save tab control parent's window procedure
  345.    the_control->pages = NULL;
  346.    the_control->page_count = 0;
  347.    the_control->activepage_index = -1;
  348.    SetWindowLong (the_control->hWnd, GWL_USERDATA, (LONG) the_control); // save tab control structure address to GWL_USERDATA
  349.  
  350.    return (the_control); // tab control is created, return an opaque pointer to it
  351. }
  352.  
  353.  
  354. void TabControl_AddPage (void *tab_control, wchar_t *page_name, int dialog_id)
  355. {
  356.    // add a page to a tab control from a dialog ID
  357.  
  358.    tabcontrol_t *the_control;
  359.    RECT client_rect;
  360.    TCITEM tie;
  361.  
  362.    the_control = (tabcontrol_t *) tab_control; // quick access to tab control
  363.  
  364.    // get tab control's client rectangle
  365.    TabControl_GetClientRect (the_control->hWnd, &client_rect); // left, top, width, height
  366.  
  367.    // resize pages array based on number of pages
  368.    the_control->pages = (tabpage_t *) realloc (the_control->pages, (the_control->page_count + 1) * sizeof (tabpage_t *));
  369.  
  370.    // Add a tab for each name in tabnames (list ends with 0)
  371.    tie.mask = TCIF_TEXT | TCIF_IMAGE;
  372.    tie.iImage = -1;
  373.    tie.pszText = page_name;
  374.    TabCtrl_InsertItem (the_control->hWnd, the_control->page_count, &tie);
  375.  
  376.    // 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
  377.    the_control->pages[the_control->page_count].hWnd = CreateDialog (GetModuleHandle (NULL), MAKEINTRESOURCE (dialog_id), GetParent (the_control->hWnd), (DLGPROC) TabPage_DlgProc);
  378.    SetWindowLong (the_control->pages[the_control->page_count].hWnd, GWL_USERDATA, (LONG) the_control->hWnd);
  379.    SetWindowPos (the_control->pages[the_control->page_count].hWnd, HWND_TOP, client_rect.left, client_rect.top, client_rect.right, client_rect.bottom, 0);
  380.    the_control->page_count++; // tab control has now one page more
  381.  
  382.    // save the current visible page and show it
  383.    the_control->activepage_index = 0;
  384.    ShowWindow (the_control->pages[the_control->activepage_index].hWnd, SW_SHOW);
  385.  
  386.    return; // finished, page has been added
  387. }
  388.  
  389.  
  390. void TabControl_SelectTab (void *tab_control, int page_index)
  391. {
  392.    tabcontrol_t *the_control;
  393.    NMHDR nmh;
  394.  
  395.    the_control = (tabcontrol_t *) tab_control; // quick access to tab control
  396.  
  397.    TabCtrl_SetCurSel (the_control->hWnd, page_index);
  398.    nmh.hwndFrom = the_control->hWnd;
  399.    nmh.idFrom = GetDlgCtrlID (the_control->hWnd);
  400.    nmh.code = TCN_SELCHANGE;
  401.  
  402.    SendMessage (the_control->hWnd, WM_NOTIFY, (WPARAM) nmh.idFrom, (LPARAM) &nmh);
  403.    return;
  404. }
  405.  
  406.  
  407. HWND TabControl_GetItem (void *tab_control, int item_id)
  408. {
  409.    // helper function to set a tab control item's text
  410.  
  411.    tabcontrol_t *the_control;
  412.    int page_index;
  413.  
  414.    the_control = (tabcontrol_t *) tab_control; // quick access to tab control
  415.  
  416.    // cycle through all pages and return the one which has the desired item
  417.    for (page_index = 0; page_index < the_control->page_count; page_index++)
  418.       if (IsWindow (GetDlgItem (the_control->pages[page_index].hWnd, item_id)))
  419.          return (GetDlgItem (the_control->pages[page_index].hWnd, item_id));
  420.  
  421.    return (NULL); // this item was not found in tab control
  422. }
  423.  
  424.  
  425. void TabControl_Destroy (void *tab_control)
  426. {
  427.    // destroy the tab page dialogs and free the list of pointers to the dialogs
  428.  
  429.    tabcontrol_t *the_control;
  430.    int page_index;
  431.  
  432.    the_control = (tabcontrol_t *) tab_control; // quick access to tab control
  433.  
  434.    // destroy each window of this tab successively
  435.    for (page_index = 0; page_index < the_control->page_count; page_index++)
  436.       DestroyWindow (the_control->pages[page_index].hWnd);
  437.  
  438.    free (the_control->pages);
  439.    free (the_control);
  440.    return; // finished, the tab control structure's allocated elements are freed
  441. }
  442.