Make DestroyWindow() not mess with a window focus, this should be
[wine] / dlls / user / tests / dialog.c
1 /* Unit test suite for the dialog functions.
2  *
3  * Copyright 2004 Bill Medland
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  *
20  *
21  * This test suite currently works by building a quite complex hierarchy of
22  * objects in a variety of styles and then performs a limited number of tests
23  * for the previous and next dialog group or tab items.
24  *
25  * The test specifically does not test all possibilities at this time since
26  * there are several cases where the Windows behaviour is rather strange and
27  * significant work would be required to get the Wine code to duplicate the
28  * strangeness, especially since most are in situations that would not 
29  * normally be met.
30  */
31
32 #include <assert.h>
33 #include <stdio.h>
34 #include <stdarg.h>
35
36 #include "wine/test.h"
37 #include "windef.h"
38 #include "winbase.h"
39 #include "winuser.h"
40
41 #define MAXHWNDS 1024
42 static HWND hwnd [MAXHWNDS];
43 static int numwnds=1; /* 0 is reserved for null */
44
45 /* Global handles */
46 static HINSTANCE g_hinst;                          /* This application's HINSTANCE */
47 static HWND g_hwndMain, g_hwndButton1, g_hwndButton2, g_hwndButtonCancel;
48 static int g_terminated;
49
50 typedef struct {
51     int id;
52     int parent;
53     DWORD style;
54     DWORD exstyle;
55 } h_entry;
56
57 static const h_entry hierarchy [] = {
58     /* 0 is reserved for the null window */
59     {  1,  0, WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS, WS_EX_WINDOWEDGE},
60     { 20,  1,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
61     {  2,  1,  WS_CHILD | WS_VISIBLE, WS_EX_CONTROLPARENT},
62     { 60,  2,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
63     /* What happens with groups when the parent is disabled */
64     {  8,  2,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, WS_EX_CONTROLPARENT},
65     { 85,  8,  WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_GROUP, 0},
66     {  9,  8,  WS_CHILD, WS_EX_CONTROLPARENT},
67     { 86,  9,  WS_CHILD | WS_VISIBLE, 0}, 
68     { 87,  9,  WS_CHILD | WS_VISIBLE, 0},
69     { 31,  8,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
70     { 10,  2,  WS_CHILD | WS_VISIBLE, WS_EX_CONTROLPARENT},
71     { 88, 10,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
72     { 11, 10,  WS_CHILD, WS_EX_CONTROLPARENT},
73     { 89, 11,  WS_CHILD | WS_VISIBLE, 0},
74     { 32, 11,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
75     { 90, 11,  WS_CHILD | WS_VISIBLE, 0},
76     { 33, 10,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
77     { 21,  2,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
78     { 61,  2,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
79     {  3,  1,  WS_CHILD | WS_VISIBLE | DS_CONTROL, 0},
80     { 22,  3,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
81     { 62,  3,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
82     {  7,  3,  WS_CHILD | WS_VISIBLE, WS_EX_CONTROLPARENT},
83     {  4,  7,  WS_CHILD | WS_VISIBLE | DS_CONTROL, 0},
84     { 83,  4,  WS_CHILD | WS_VISIBLE, 0},
85     {  5,  4,  WS_CHILD | WS_VISIBLE | DS_CONTROL, 0},
86     /* A couple of controls around the main dialog */
87     { 29,  5,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
88     { 81,  5,  WS_CHILD | WS_VISIBLE, 0},
89     /* The main dialog with lots of controls */
90     {  6,  5,  WS_CHILD | WS_VISIBLE, WS_EX_CONTROLPARENT},
91         /* At the start of a dialog */
92         /* Disabled controls are skipped */
93     { 63,  6,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 0},
94         /* Invisible controls are skipped */
95     { 64,  6,  WS_CHILD | WS_TABSTOP, 0},
96         /* Invisible disabled controls are skipped */
97     { 65,  6,  WS_CHILD | WS_DISABLED | WS_TABSTOP, 0},
98         /* Non-tabstop controls are skipped for tabs but not for groups */
99     { 66,  6,  WS_CHILD | WS_VISIBLE, 0},
100         /* End of first group, with no tabstops in it */
101     { 23,  6,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
102         /* At last a tabstop */
103     { 67,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
104     /* A group that is totally disabled or invisible */
105     { 24,  6,  WS_CHILD | WS_DISABLED | WS_GROUP, 0},
106     { 68,  6,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 0},
107     { 69,  6,  WS_CHILD | WS_TABSTOP, 0},
108     /* A valid group in the middle of the dialog (not the first nor last group*/
109     { 25,  6,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
110         /* A non-tabstop item will be skipped for tabs */
111     { 70,  6,  WS_CHILD | WS_VISIBLE, 0},
112         /* A disabled item will be skipped for tabs and groups */
113     { 71,  6,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 0},
114         /* A valid item will be found for tabs and groups */
115     { 72,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
116         /* A disabled item to skip when looking for the next group item */
117     { 73,  6,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 0},
118     /* The next group begins with an enabled visible label */
119     { 26,  6,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
120     { 74,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
121     { 75,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
122     /* That group is terminated by a disabled label */
123     { 27,  6,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_GROUP, 0},
124     { 76,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
125     { 77,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
126     /* That group is terminated by an invisible label */
127     { 28,  6,  WS_CHILD | WS_GROUP, 0},
128     /* The end of the dialog with item for loop and recursion testing */
129     { 78,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
130     /* No tabstop so skipped for prev tab, but found for prev group */
131     { 79,  6,  WS_CHILD | WS_VISIBLE, 0},
132     { 80,  6,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 0},
133     /* A couple of controls after the main dialog */
134     { 82,  5,  WS_CHILD | WS_VISIBLE, 0},
135     { 30,  5,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
136     /* And around them */
137     { 84,  4,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
138     {0, 0, 0, 0}
139 };
140
141 static BOOL CreateWindows (HINSTANCE hinst)
142 {
143     const h_entry *p = hierarchy;
144
145     while (p->id != 0)
146     {
147         DWORD style, exstyle;
148         char ctrlname[9];
149
150         /* Basically assert that the hierarchy is valid and track the
151          * maximum control number
152          */
153         if (p->id >= numwnds)
154         {
155             if (p->id >=  sizeof(hwnd)/sizeof(hwnd[0]))
156             {
157                 trace ("Control %d is out of range\n", p->id);
158                 return FALSE;
159             }
160             else
161                 numwnds = p->id+1;
162         }
163         if (p->id <= 0)
164         {
165             trace ("Control %d is out of range\n", p->id);
166             return FALSE;
167         }
168         if (hwnd[p->id] != 0)
169         {
170             trace ("Control %d is used more than once\n", p->id);
171             return FALSE;
172         }
173
174         /* Create the control */
175         sprintf (ctrlname, "ctrl%4.4d", p->id);
176         hwnd[p->id] = CreateWindowEx (p->exstyle, TEXT(p->parent ? "static" : "GetNextDlgItemWindowClass"), TEXT(ctrlname), p->style, 10, 10, 10, 10, hwnd[p->parent], p->parent ? (HMENU) (2000 + p->id) : 0, hinst, 0);
177         if (!hwnd[p->id])
178         {
179             trace ("Failed to create control %d\n", p->id);
180             return FALSE;
181         }
182
183         /* Check that the styles are as we specified (except the main one
184          * which is quite frequently messed up).  If this keeps breaking then 
185          * we could mask out the bits that don't concern us.
186          */
187         if (p->parent)
188         {
189             style = GetWindowLong (hwnd[p->id], GWL_STYLE);
190             exstyle = GetWindowLong (hwnd[p->id], GWL_EXSTYLE);
191             if (style == p->style && exstyle == p->exstyle)
192             {
193                 trace ("Style mismatch at %d: %8.8lx %8.8lx cf %8.8lx %8.8lx\n", p->id, style, exstyle, p->style, p->exstyle);
194             }
195         }
196         p++;
197     }
198
199     return TRUE;
200 }
201
202 /* Form the lParam of a WM_KEYDOWN message */
203 static DWORD KeyDownData (int repeat, int scancode, int extended, int wasdown)
204 {
205     return ((repeat & 0x0000FFFF) | ((scancode & 0x00FF) >> 16) |
206             (extended ? 0x01000000 : 0) | (wasdown ? 0x40000000 : 0));
207 }
208
209 /* Form a WM_KEYDOWN VK_TAB message to the specified window */
210 static void FormTabMsg (MSG *pMsg, HWND hwnd)
211 {
212     pMsg->hwnd = hwnd;
213     pMsg->message = WM_KEYDOWN;
214     pMsg->wParam = VK_TAB;
215     pMsg->lParam = KeyDownData (1, 0x0F, 0, 0);
216     /* pMsg->time is not set.  It shouldn't be needed */
217     /* pMsg->pt is ignored */
218 }
219
220 /* Form a WM_KEYDOWN VK_RETURN message to the specified window */
221 static void FormEnterMsg (MSG *pMsg, HWND hwnd)
222 {
223     pMsg->hwnd = hwnd;
224     pMsg->message = WM_KEYDOWN;
225     pMsg->wParam = VK_RETURN;
226     pMsg->lParam = KeyDownData (1, 0x1C, 0, 0);
227     /* pMsg->time is not set.  It shouldn't be needed */
228     /* pMsg->pt is ignored */
229 }
230
231 /***********************************************************************
232  *
233  * The actual tests
234  */
235
236 typedef struct
237 {
238     int isok; /* or is it todo */
239     int test;
240     int dlg;
241     int ctl;
242     int tab;
243     int prev;
244     int res;
245 } test_record;
246
247 static int id (HWND h)
248 {
249     int i;
250     for (i = 0; i < numwnds; i++)
251         if (hwnd[i] == h)
252             return i;
253     return -1;
254 }
255
256 /* Tests
257  *
258  * Tests 1-8 test the hCtl argument of null or the dialog itself.
259  *
260  *   1. Prev Group of null is null
261  *   2. Prev Tab of null is null
262  *   3. Prev Group of hDlg in hDlg is null
263  *   4. Prev Tab of hDlg in hDlg is null
264  *   5. Next Group of null is first visible enabled child
265  *      Check it skips invisible, diabled and both.
266  *   6. Next Tab of null is first visible enabled tabstop
267  *      Check it skips invisible, disabled, nontabstop, and in combination.
268  *   7. Next Group of hDlg in hDlg is as of null
269  *   8. Next Tab of hDlg in hDlg is as of null
270  *
271  * Tests 9-14 test descent
272  *
273  *   9. DS_CONTROL does not result in descending the hierarchy for Tab Next
274  *  10. DS_CONTROL does not result in descending the hierarchy for Group Next
275  *  11. WS_EX_CONTROLPARENT results in descending the hierarchy for Tab Next
276  *  12. WS_EX_CONTROLPARENT results in descending the hierarchy for Group Next
277  *  13. WS_EX_CONTROLPARENT results in descending the hierarchy for Tab Prev
278  *  14. WS_EX_CONTROLPARENT results in descending the hierarchy for Group Prev
279  *
280  * Tests 15-24 are the basic Prev/Next Group tests
281  *
282  *  15. Next Group of a visible enabled non-group control is the next visible
283  *      enabled non-group control, if there is one before the next group
284  *  16. Next Group of a visible enabled non-group control wraps around to the
285  *      beginning of the group on finding a control that starts another group.
286  *      Note that the group is in the middle of the dialog.
287  *  17. As 16 except note that the next group is started with a disabled
288  *      visible control.
289  *  18. As 16 except note that the next group is started with an invisible
290  *      enabled control.
291  *  19. Next Group wraps around the controls of the dialog
292  *  20. Next Group is the same even if the initial control is disabled.
293  *  21. Next Group is the same even if the initial control is invisible.
294  *  22. Next Group is the same even if the initial control has the group style
295  *  23. Next Group returns the initial control if there is no visible enabled
296  *      control in the group. (Initial control disabled and not group style).
297  *  24. Prev version of test 16.
298  *      Prev Group of a visible enabled non-group control wraps around to the
299  *      beginning of the group on finding a control that starts the group.
300  *      Note that the group is in the middle of the dialog.
301  *
302  * In tests 25 to 28 the control is sitting under dialogs which do not have
303  * the WS_EX_CONTROLPARENT style and so cannot be reached from the top of
304  * the dialog.
305  *
306  *  25. Next Group of an inaccessible control is as if it were accessible
307  *  26. Prev Group of an inaccessible control begins searching at the highest
308  *      level ancestor that did not permit recursion down the hierarchy
309  *  27. Next Tab of an inaccessible control is as if it were accessible
310  *  28. Prev Tab of an inaccessible control begins searching at the highest 
311  *      level ancestor that did not permit recursion down the hierarchy.
312  *
313  * Tests 29- are the basic Tab tests
314  *
315  *  29. Next Tab of a control is the next visible enabled control with the 
316  *      Tabstop style (N.B. skips disabled, invisible and non-tabstop)
317  *  30. Prev Tab of a control is the previous visible enabled control with the
318  *      Tabstop style (N.B. skips disabled, invisible and non-tabstop)
319  *  31. Next Tab test with at least two layers of descent and finding the 
320  *      result not at the first control.
321  *  32. Next Tab test with at least two layers of descent with the descent and
322  *      control at the start of each level.
323  *  33. Prev Tab test with at least two layers of descent and finding the 
324  *      result not at the last control.
325  *  34. Prev Tab test with at least two layers of descent with the descent and
326  *      control at the end of each level.
327  *
328  *  35. Passing NULL may result in the first child being the one returned.
329  *      (group test)
330  *  36. Passing NULL may result in the first child being the one returned.
331  *      (tab test)
332  */
333
334 static void GetNextDlgItemTest (void)
335 {
336     static test_record test [] = 
337     {
338         /* isok test dlg  ctl  tab  prev res  */
339
340         {   1,   1,    6,   0,   0,   1,   0},
341         {   1,   2,    6,   0,   1,   1,   0},
342         {   1,   3,    6,   6,   0,   1,   0},
343         {   1,   4,    6,   6,   1,   1,   0},
344         {   1,   5,    6,   0,   0,   0,  66},
345         {   1,   6,    6,   0,   1,   0,  67},
346         {   1,   7,    6,   6,   0,   0,  66},
347         {   1,   8,    6,   6,   1,   0,  67},
348
349         {   1,   9,    4,  83,   1,   0,  84},
350         {   1,  10,    4,  83,   0,   0,   5},
351         {   1,  11,    5,  81,   1,   0,  67},
352         {   1,  12,    5,  81,   0,   0,  66},
353         {   1,  13,    5,  82,   1,   1,  78},
354
355         {   1,  14,    5,  82,   0,   1,  79},
356         {   1,  15,    6,  70,   0,   0,  72},
357         {   1,  16,    6,  72,   0,   0,  25},
358         {   1,  17,    6,  75,   0,   0,  26},
359         {   1,  18,    6,  77,   0,   0,  76},
360         {   1,  19,    6,  79,   0,   0,  66},
361         {   1,  20,    6,  71,   0,   0,  72},
362         {   1,  21,    6,  64,   0,   0,  66},
363
364         {   1,  22,    6,  25,   0,   0,  70},
365         {   1,  23,    6,  68,   0,   0,  68},
366         {   1,  24,    6,  25,   0,   1,  72},
367         {   1,  25,    1,  70,   0,   0,  72},
368         {   0,  26,    1,  70,   0,   1,   3},
369         {   1,  27,    1,  70,   1,   0,  72},
370         {   0,  28,    1,  70,   1,   1,  61},
371
372         {   1,  29,    6,  67,   1,   0,  72},
373         {   1,  30,    6,  72,   1,   1,  67},
374
375         {   1,  35,    2,   0,   0,   0,  60},
376         {   1,  36,    2,   0,   1,   0,  60},
377
378         {   0,   0,    0,   0,   0,   0,   0}  /* End of test */
379     };
380     const test_record *p = test;
381
382     ok (CreateWindows (g_hinst), "Could not create test windows\n");
383
384     while (p->dlg)
385     {
386         HWND a;
387         a = (p->tab ? GetNextDlgTabItem : GetNextDlgGroupItem) (hwnd[p->dlg], hwnd[p->ctl], p->prev);
388         if (p->isok)
389         {
390             ok (a == hwnd[p->res], "Test %d: %s %s item of %d in %d was %d instead of %d\n", p->test, p->prev ? "Prev" : "Next", p->tab ? "Tab" : "Group", p->ctl, p->dlg, id(a), p->res);
391         }
392         else
393         {
394             todo_wine
395             {
396                 ok (a == hwnd[p->res], "Test %d: %s %s item of %d in %d was actually  %d matching expected %d\n", p->test, p->prev ? "Prev" : "Next", p->tab ? "Tab" : "Group", p->ctl, p->dlg, id(a), p->res);
397             }
398         }
399         p++;
400     }
401 }
402
403 /*
404  *  OnMainWindowCreate
405  */
406 static BOOL OnMainWindowCreate (HWND hwnd, LPCREATESTRUCT lpcs)
407 {
408     g_hwndButton1 = CreateWindow (TEXT("button"), TEXT("Button &1"),
409             WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON | BS_TEXT,
410             10, 10, 80, 80, hwnd, (HMENU)100, g_hinst, 0);
411     if (!g_hwndButton1) return FALSE;
412
413     g_hwndButton2 = CreateWindow (TEXT("button"), TEXT("Button &2"),
414             WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_TEXT,
415             110, 10, 80, 80, hwnd, (HMENU)200, g_hinst, 0);
416     if (!g_hwndButton2) return FALSE;
417
418     g_hwndButtonCancel = CreateWindow (TEXT("button"), TEXT("Cancel"),
419             WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_TEXT,
420             210, 10, 80, 80, hwnd, (HMENU)IDCANCEL, g_hinst, 0);
421     if (!g_hwndButtonCancel) return FALSE;
422
423     return TRUE;
424 }
425
426
427 static LRESULT CALLBACK main_window_procA (HWND hwnd, UINT uiMsg, WPARAM wParam,
428         LPARAM lParam)
429 {
430     LRESULT result;
431     switch (uiMsg)
432     {
433         /* Add blank case statements for these to ensure we don't use them
434          * by mistake.
435          */
436         case DM_GETDEFID: break;
437         case DM_SETDEFID: break;
438
439         case WM_CREATE:
440             return (OnMainWindowCreate (hwnd,
441                     (LPCREATESTRUCTA) lParam) ? 0 : (LRESULT) -1);
442         case WM_COMMAND:
443             if (wParam == IDCANCEL)
444             {
445                 g_terminated = TRUE;
446                 return 0;
447             }
448             break;
449     }
450
451     result=DefWindowProcA (hwnd, uiMsg, wParam, lParam);
452     return result;
453 }
454
455
456 static BOOL RegisterWindowClasses (void)
457 {
458     WNDCLASSA cls;
459
460     cls.style = 0;
461     cls.lpfnWndProc = DefWindowProcA;
462     cls.cbClsExtra = 0;
463     cls.cbWndExtra = 0;
464     cls.hInstance = g_hinst;
465     cls.hIcon = NULL;
466     cls.hCursor = LoadCursorA (NULL, IDC_ARROW);
467     cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
468     cls.lpszMenuName = NULL;
469     cls.lpszClassName = "GetNextDlgItemWindowClass";
470
471     if (!RegisterClassA (&cls)) return FALSE;
472
473     cls.lpfnWndProc = main_window_procA;
474     cls.lpszClassName = "IsDialogMessageWindowClass";
475
476     if (!RegisterClassA (&cls)) return FALSE;
477
478     return TRUE;
479 }
480
481 static void IsDialogMessageWTest (void)
482 {
483     MSG msg;
484
485     g_hwndMain = CreateWindow ("IsDialogMessageWindowClass", "IsDialogMessageWindowClass",
486             WS_OVERLAPPEDWINDOW,
487             CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
488             NULL, NULL, g_hinst, 0);
489
490     assert (g_hwndMain);
491     assert (g_hwndButton1);
492     assert (g_hwndButtonCancel);
493
494     /* The focus should initially be nowhere.  The first TAB should take it
495      * to the first button.  The second TAB should take it to the Cancel
496      * button.
497      */
498     FormTabMsg (&msg, g_hwndMain);
499     ok (IsDialogMessage (g_hwndMain, &msg), "Did not handle first TAB\n");
500     ok ((GetFocus() == g_hwndButton1), "Focus did not move to first button\n");
501     FormTabMsg (&msg, g_hwndButton1);
502     ok (IsDialogMessage (g_hwndMain, &msg), "Did not handle second TAB\n");
503     ok ((GetFocus() == g_hwndButtonCancel),
504             "Focus did not move to cancel button\n");
505     FormEnterMsg (&msg, g_hwndButtonCancel);
506     ok (IsDialogMessage (g_hwndMain, &msg), "Did not handle the ENTER\n");
507     ok (g_terminated, "ENTER did not terminate\n");
508 }
509
510 START_TEST(dialog)
511 {
512     g_hinst = GetModuleHandleA (0);
513
514     if (!RegisterWindowClasses()) assert(0);
515
516     GetNextDlgItemTest();
517     IsDialogMessageWTest();
518 }