Subversion Repositories Games.Chess Giants

Rev

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

Rev Author Line No. Line
1 pmbaty 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
193 pmbaty 154
      memset (&nm, 0, sizeof (nm));
1 pmbaty 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)
193 pmbaty 372
   memset (&tie, 0, sizeof (tie));
1 pmbaty 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);
193 pmbaty 400
   memset (&nmh, 0, sizeof (nmh));
1 pmbaty 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
}