If a dialog with the DS_CONTROL style is not visible then ignore
[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 unsigned 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 HWND g_hwndTestDlg, g_hwndTestDlgBut1, g_hwndTestDlgBut2, g_hwndTestDlgEdit;
49 static HWND g_hwndInitialFocusT1, g_hwndInitialFocusT2, g_hwndInitialFocusGroupBox;
50
51 static LONG g_styleInitialFocusT1, g_styleInitialFocusT2;
52 static BOOL g_bInitialFocusInitDlgResult;
53
54 static int g_terminated;
55
56 typedef struct {
57     unsigned int id;
58     int parent;
59     DWORD style;
60     DWORD exstyle;
61 } h_entry;
62
63 static const h_entry hierarchy [] = {
64     /* 0 is reserved for the null window */
65     {  1,  0, WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS, WS_EX_WINDOWEDGE},
66     { 20,  1,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
67     {  2,  1,  WS_CHILD | WS_VISIBLE, WS_EX_CONTROLPARENT},
68     { 60,  2,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
69     /* What happens with groups when the parent is disabled */
70     {  8,  2,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, WS_EX_CONTROLPARENT},
71     { 85,  8,  WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_GROUP, 0},
72     {  9,  8,  WS_CHILD, WS_EX_CONTROLPARENT},
73     { 86,  9,  WS_CHILD | WS_VISIBLE, 0},
74     { 87,  9,  WS_CHILD | WS_VISIBLE, 0},
75     { 31,  8,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
76     { 10,  2,  WS_CHILD | WS_VISIBLE, WS_EX_CONTROLPARENT},
77     { 88, 10,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
78     { 11, 10,  WS_CHILD, WS_EX_CONTROLPARENT},
79     { 89, 11,  WS_CHILD | WS_VISIBLE, 0},
80     { 32, 11,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
81     { 90, 11,  WS_CHILD | WS_VISIBLE, 0},
82     { 33, 10,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
83     { 21,  2,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
84     { 61,  2,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
85     {  3,  1,  WS_CHILD | WS_VISIBLE | DS_CONTROL, 0},
86     { 22,  3,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
87     { 62,  3,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
88     {  7,  3,  WS_CHILD | WS_VISIBLE, WS_EX_CONTROLPARENT},
89     {  4,  7,  WS_CHILD | WS_VISIBLE | DS_CONTROL, 0},
90     { 83,  4,  WS_CHILD | WS_VISIBLE, 0},
91     {  5,  4,  WS_CHILD | WS_VISIBLE | DS_CONTROL, 0},
92     /* A couple of controls around the main dialog */
93     { 29,  5,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
94     { 81,  5,  WS_CHILD | WS_VISIBLE, 0},
95     /* The main dialog with lots of controls */
96     {  6,  5,  WS_CHILD | WS_VISIBLE, WS_EX_CONTROLPARENT},
97         /* At the start of a dialog */
98         /* Disabled controls are skipped */
99     { 63,  6,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 0},
100         /* Invisible controls are skipped */
101     { 64,  6,  WS_CHILD | WS_TABSTOP, 0},
102         /* Invisible disabled controls are skipped */
103     { 65,  6,  WS_CHILD | WS_DISABLED | WS_TABSTOP, 0},
104         /* Non-tabstop controls are skipped for tabs but not for groups */
105     { 66,  6,  WS_CHILD | WS_VISIBLE, 0},
106         /* End of first group, with no tabstops in it */
107     { 23,  6,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
108         /* At last a tabstop */
109     { 67,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
110     /* A group that is totally disabled or invisible */
111     { 24,  6,  WS_CHILD | WS_DISABLED | WS_GROUP, 0},
112     { 68,  6,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 0},
113     { 69,  6,  WS_CHILD | WS_TABSTOP, 0},
114     /* A valid group in the middle of the dialog (not the first nor last group*/
115     { 25,  6,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
116         /* A non-tabstop item will be skipped for tabs */
117     { 70,  6,  WS_CHILD | WS_VISIBLE, 0},
118         /* A disabled item will be skipped for tabs and groups */
119     { 71,  6,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 0},
120         /* A valid item will be found for tabs and groups */
121     { 72,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
122         /* A disabled item to skip when looking for the next group item */
123     { 73,  6,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 0},
124     /* The next group begins with an enabled visible label */
125     { 26,  6,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
126     { 74,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
127     { 75,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
128     /* That group is terminated by a disabled label */
129     { 27,  6,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_GROUP, 0},
130     { 76,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
131     { 77,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
132     /* That group is terminated by an invisible label */
133     { 28,  6,  WS_CHILD | WS_GROUP, 0},
134     /* The end of the dialog with item for loop and recursion testing */
135     { 78,  6,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
136     /* No tabstop so skipped for prev tab, but found for prev group */
137     { 79,  6,  WS_CHILD | WS_VISIBLE, 0},
138     { 80,  6,  WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 0},
139     /* A couple of controls after the main dialog */
140     { 82,  5,  WS_CHILD | WS_VISIBLE, 0},
141     { 30,  5,  WS_CHILD | WS_VISIBLE | WS_GROUP, 0},
142     /* And around them */
143     { 84,  4,  WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0},
144     {0, 0, 0, 0}
145 };
146
147 static BOOL CreateWindows (HINSTANCE hinst)
148 {
149     const h_entry *p = hierarchy;
150
151     while (p->id != 0)
152     {
153         DWORD style, exstyle;
154         char ctrlname[9];
155
156         /* Basically assert that the hierarchy is valid and track the
157          * maximum control number
158          */
159         if (p->id >= numwnds)
160         {
161             if (p->id >=  sizeof(hwnd)/sizeof(hwnd[0]))
162             {
163                 trace ("Control %d is out of range\n", p->id);
164                 return FALSE;
165             }
166             else
167                 numwnds = p->id+1;
168         }
169         if (p->id <= 0)
170         {
171             trace ("Control %d is out of range\n", p->id);
172             return FALSE;
173         }
174         if (hwnd[p->id] != 0)
175         {
176             trace ("Control %d is used more than once\n", p->id);
177             return FALSE;
178         }
179
180         /* Create the control */
181         sprintf (ctrlname, "ctrl%4.4d", p->id);
182         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);
183         if (!hwnd[p->id])
184         {
185             trace ("Failed to create control %d\n", p->id);
186             return FALSE;
187         }
188
189         /* Check that the styles are as we specified (except the main one
190          * which is quite frequently messed up).  If this keeps breaking then
191          * we could mask out the bits that don't concern us.
192          */
193         if (p->parent)
194         {
195             style = GetWindowLong (hwnd[p->id], GWL_STYLE);
196             exstyle = GetWindowLong (hwnd[p->id], GWL_EXSTYLE);
197             if (style == p->style && exstyle == p->exstyle)
198             {
199                 trace ("Style mismatch at %d: %8.8lx %8.8lx cf %8.8lx %8.8lx\n", p->id, style, exstyle, p->style, p->exstyle);
200             }
201         }
202         p++;
203     }
204
205     return TRUE;
206 }
207
208 /* Form the lParam of a WM_KEYDOWN message */
209 static DWORD KeyDownData (int repeat, int scancode, int extended, int wasdown)
210 {
211     return ((repeat & 0x0000FFFF) | ((scancode & 0x00FF) >> 16) |
212             (extended ? 0x01000000 : 0) | (wasdown ? 0x40000000 : 0));
213 }
214
215 /* Form a WM_KEYDOWN VK_TAB message to the specified window */
216 static void FormTabMsg (MSG *pMsg, HWND hwnd)
217 {
218     pMsg->hwnd = hwnd;
219     pMsg->message = WM_KEYDOWN;
220     pMsg->wParam = VK_TAB;
221     pMsg->lParam = KeyDownData (1, 0x0F, 0, 0);
222     /* pMsg->time is not set.  It shouldn't be needed */
223     /* pMsg->pt is ignored */
224 }
225
226 /* Form a WM_KEYDOWN VK_RETURN message to the specified window */
227 static void FormEnterMsg (MSG *pMsg, HWND hwnd)
228 {
229     pMsg->hwnd = hwnd;
230     pMsg->message = WM_KEYDOWN;
231     pMsg->wParam = VK_RETURN;
232     pMsg->lParam = KeyDownData (1, 0x1C, 0, 0);
233     /* pMsg->time is not set.  It shouldn't be needed */
234     /* pMsg->pt is ignored */
235 }
236
237 /***********************************************************************
238  *
239  * The actual tests
240  */
241
242 typedef struct
243 {
244     int isok; /* or is it todo */
245     int test;
246     int dlg;
247     int ctl;
248     int tab;
249     int prev;
250     int res;
251 } test_record;
252
253 static int id (HWND h)
254 {
255     unsigned int i;
256     for (i = 0; i < numwnds; i++)
257         if (hwnd[i] == h)
258             return i;
259     return -1;
260 }
261
262 /* Tests
263  *
264  * Tests 1-8 test the hCtl argument of null or the dialog itself.
265  *
266  *   1. Prev Group of null is null
267  *   2. Prev Tab of null is null
268  *   3. Prev Group of hDlg in hDlg is null
269  *   4. Prev Tab of hDlg in hDlg is null
270  *   5. Next Group of null is first visible enabled child
271  *      Check it skips invisible, diabled and both.
272  *   6. Next Tab of null is first visible enabled tabstop
273  *      Check it skips invisible, disabled, nontabstop, and in combination.
274  *   7. Next Group of hDlg in hDlg is as of null
275  *   8. Next Tab of hDlg in hDlg is as of null
276  *
277  * Tests 9-14 test descent
278  *
279  *   9. DS_CONTROL does not result in descending the hierarchy for Tab Next
280  *  10. DS_CONTROL does not result in descending the hierarchy for Group Next
281  *  11. WS_EX_CONTROLPARENT results in descending the hierarchy for Tab Next
282  *  12. WS_EX_CONTROLPARENT results in descending the hierarchy for Group Next
283  *  13. WS_EX_CONTROLPARENT results in descending the hierarchy for Tab Prev
284  *  14. WS_EX_CONTROLPARENT results in descending the hierarchy for Group Prev
285  *
286  * Tests 15-24 are the basic Prev/Next Group tests
287  *
288  *  15. Next Group of a visible enabled non-group control is the next visible
289  *      enabled non-group control, if there is one before the next group
290  *  16. Next Group of a visible enabled non-group control wraps around to the
291  *      beginning of the group on finding a control that starts another group.
292  *      Note that the group is in the middle of the dialog.
293  *  17. As 16 except note that the next group is started with a disabled
294  *      visible control.
295  *  18. As 16 except note that the next group is started with an invisible
296  *      enabled control.
297  *  19. Next Group wraps around the controls of the dialog
298  *  20. Next Group is the same even if the initial control is disabled.
299  *  21. Next Group is the same even if the initial control is invisible.
300  *  22. Next Group is the same even if the initial control has the group style
301  *  23. Next Group returns the initial control if there is no visible enabled
302  *      control in the group. (Initial control disabled and not group style).
303  *  24. Prev version of test 16.
304  *      Prev Group of a visible enabled non-group control wraps around to the
305  *      beginning of the group on finding a control that starts the group.
306  *      Note that the group is in the middle of the dialog.
307  *
308  * In tests 25 to 28 the control is sitting under dialogs which do not have
309  * the WS_EX_CONTROLPARENT style and so cannot be reached from the top of
310  * the dialog.
311  *
312  *  25. Next Group of an inaccessible control is as if it were accessible
313  *  26. Prev Group of an inaccessible control begins searching at the highest
314  *      level ancestor that did not permit recursion down the hierarchy
315  *  27. Next Tab of an inaccessible control is as if it were accessible
316  *  28. Prev Tab of an inaccessible control begins searching at the highest
317  *      level ancestor that did not permit recursion down the hierarchy.
318  *
319  * Tests 29- are the basic Tab tests
320  *
321  *  29. Next Tab of a control is the next visible enabled control with the
322  *      Tabstop style (N.B. skips disabled, invisible and non-tabstop)
323  *  30. Prev Tab of a control is the previous visible enabled control with the
324  *      Tabstop style (N.B. skips disabled, invisible and non-tabstop)
325  *  31. Next Tab test with at least two layers of descent and finding the
326  *      result not at the first control.
327  *  32. Next Tab test with at least two layers of descent with the descent and
328  *      control at the start of each level.
329  *  33. Prev Tab test with at least two layers of descent and finding the
330  *      result not at the last control.
331  *  34. Prev Tab test with at least two layers of descent with the descent and
332  *      control at the end of each level.
333  *
334  *  35. Passing NULL may result in the first child being the one returned.
335  *      (group test)
336  *  36. Passing NULL may result in the first child being the one returned.
337  *      (tab test)
338  */
339
340 static void GetNextDlgItemTest (void)
341 {
342     static test_record test [] =
343     {
344         /* isok test dlg  ctl  tab  prev res  */
345
346         {   1,   1,    6,   0,   0,   1,   0},
347         {   1,   2,    6,   0,   1,   1,   0},
348         {   1,   3,    6,   6,   0,   1,   0},
349         {   1,   4,    6,   6,   1,   1,   0},
350         {   1,   5,    6,   0,   0,   0,  66},
351         {   1,   6,    6,   0,   1,   0,  67},
352         {   1,   7,    6,   6,   0,   0,  66},
353         {   1,   8,    6,   6,   1,   0,  67},
354
355         {   1,   9,    4,  83,   1,   0,  84},
356         {   1,  10,    4,  83,   0,   0,   5},
357         {   1,  11,    5,  81,   1,   0,  67},
358         {   1,  12,    5,  81,   0,   0,  66},
359         {   1,  13,    5,  82,   1,   1,  78},
360
361         {   1,  14,    5,  82,   0,   1,  79},
362         {   1,  15,    6,  70,   0,   0,  72},
363         {   1,  16,    6,  72,   0,   0,  25},
364         {   1,  17,    6,  75,   0,   0,  26},
365         {   1,  18,    6,  77,   0,   0,  76},
366         {   1,  19,    6,  79,   0,   0,  66},
367         {   1,  20,    6,  71,   0,   0,  72},
368         {   1,  21,    6,  64,   0,   0,  66},
369
370         {   1,  22,    6,  25,   0,   0,  70},
371         {   1,  23,    6,  68,   0,   0,  68},
372         {   1,  24,    6,  25,   0,   1,  72},
373         {   1,  25,    1,  70,   0,   0,  72},
374         /*{   0,  26,    1,  70,   0,   1,   3}, Crashes Win95*/
375         {   1,  27,    1,  70,   1,   0,  72},
376         /*{   0,  28,    1,  70,   1,   1,  61}, Crashes Win95*/
377
378         {   1,  29,    6,  67,   1,   0,  72},
379         {   1,  30,    6,  72,   1,   1,  67},
380
381         {   1,  35,    2,   0,   0,   0,  60},
382         {   1,  36,    2,   0,   1,   0,  60},
383
384         {   0,   0,    0,   0,   0,   0,   0}  /* End of test */
385     };
386     const test_record *p = test;
387
388     ok (CreateWindows (g_hinst), "Could not create test windows\n");
389
390     while (p->dlg)
391     {
392         HWND a;
393         a = (p->tab ? GetNextDlgTabItem : GetNextDlgGroupItem) (hwnd[p->dlg], hwnd[p->ctl], p->prev);
394         if (p->isok)
395         {
396             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);
397         }
398         else
399         {
400             todo_wine
401             {
402                 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);
403             }
404         }
405         p++;
406     }
407 }
408
409 /*
410  *  OnMainWindowCreate
411  */
412 static BOOL OnMainWindowCreate (HWND hwnd, LPCREATESTRUCT lpcs)
413 {
414     g_hwndButton1 = CreateWindow (TEXT("button"), TEXT("Button &1"),
415             WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON | BS_TEXT,
416             10, 10, 80, 80, hwnd, (HMENU)100, g_hinst, 0);
417     if (!g_hwndButton1) return FALSE;
418
419     g_hwndButton2 = CreateWindow (TEXT("button"), TEXT("Button &2"),
420             WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_TEXT,
421             110, 10, 80, 80, hwnd, (HMENU)200, g_hinst, 0);
422     if (!g_hwndButton2) return FALSE;
423
424     g_hwndButtonCancel = CreateWindow (TEXT("button"), TEXT("Cancel"),
425             WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_TEXT,
426             210, 10, 80, 80, hwnd, (HMENU)IDCANCEL, g_hinst, 0);
427     if (!g_hwndButtonCancel) return FALSE;
428
429     return TRUE;
430 }
431
432
433 /*
434  *  OnTestDlgCreate
435  */
436
437 static BOOL OnTestDlgCreate (HWND hwnd, LPCREATESTRUCT lpcs)
438 {
439     g_hwndTestDlgEdit = CreateWindowEx ( WS_EX_LEFT | WS_EX_LTRREADING |
440             WS_EX_RIGHTSCROLLBAR | WS_EX_NOPARENTNOTIFY | WS_EX_CLIENTEDGE,
441             TEXT("Edit"), TEXT("Edit"),
442             WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL,
443             16,33,184,24, hwnd, (HMENU)101, g_hinst, 0);
444     if (!g_hwndTestDlgEdit) return FALSE;
445
446     g_hwndTestDlgBut1 = CreateWindowEx ( WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR
447             | WS_EX_NOPARENTNOTIFY,
448             TEXT("button"), TEXT("Button &1"),
449             WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_TEXT,
450             204,33,30,24, hwnd, (HMENU)201, g_hinst, 0);
451     if (!g_hwndTestDlgBut1) return FALSE;
452
453     g_hwndTestDlgBut2 = CreateWindowEx ( WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR
454             | WS_EX_NOPARENTNOTIFY, TEXT("button"),
455             TEXT("Button &2"),
456             WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON | BS_TEXT,
457             90,102,80,24, hwnd, (HMENU)IDCANCEL, g_hinst, 0);
458     if (!g_hwndTestDlgBut2) return FALSE;
459
460     return TRUE;
461 }
462
463 static LRESULT CALLBACK main_window_procA (HWND hwnd, UINT uiMsg, WPARAM wParam,
464         LPARAM lParam)
465 {
466     LRESULT result;
467     switch (uiMsg)
468     {
469         /* Add blank case statements for these to ensure we don't use them
470          * by mistake.
471          */
472         case DM_GETDEFID: break;
473         case DM_SETDEFID: break;
474
475         case WM_CREATE:
476             return (OnMainWindowCreate (hwnd,
477                     (LPCREATESTRUCTA) lParam) ? 0 : (LRESULT) -1);
478         case WM_COMMAND:
479             if (wParam == IDCANCEL)
480             {
481                 g_terminated = TRUE;
482                 return 0;
483             }
484             break;
485     }
486
487     result=DefWindowProcA (hwnd, uiMsg, wParam, lParam);
488     return result;
489 }
490
491 static LRESULT CALLBACK testDlgWinProc (HWND hwnd, UINT uiMsg, WPARAM wParam,
492         LPARAM lParam)
493 {
494     LRESULT result;
495     switch (uiMsg)
496     {
497         /* Add blank case statements for these to ensure we don't use them
498          * by mistake.
499          */
500         case DM_GETDEFID: break;
501         case DM_SETDEFID: break;
502
503         case WM_CREATE:
504             return (OnTestDlgCreate (hwnd,
505                     (LPCREATESTRUCTA) lParam) ? 0 : (LRESULT) -1);
506     }
507
508     result=DefWindowProcA (hwnd, uiMsg, wParam, lParam);
509     return result;
510 }
511
512 static BOOL RegisterWindowClasses (void)
513 {
514     WNDCLASSA cls;
515
516     cls.style = 0;
517     cls.lpfnWndProc = DefWindowProcA;
518     cls.cbClsExtra = 0;
519     cls.cbWndExtra = 0;
520     cls.hInstance = g_hinst;
521     cls.hIcon = NULL;
522     cls.hCursor = LoadCursorA (NULL, IDC_ARROW);
523     cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
524     cls.lpszMenuName = NULL;
525     cls.lpszClassName = "GetNextDlgItemWindowClass";
526
527     if (!RegisterClassA (&cls)) return FALSE;
528
529     cls.lpfnWndProc = main_window_procA;
530     cls.lpszClassName = "IsDialogMessageWindowClass";
531
532     if (!RegisterClassA (&cls)) return FALSE;
533
534     GetClassInfoA(0, "#32770", &cls);
535     cls.lpfnWndProc = testDlgWinProc;
536     cls.lpszClassName = "WM_NEXTDLGCTLWndClass";
537     if (!RegisterClassA (&cls)) return FALSE;
538
539     return TRUE;
540 }
541
542 static void WM_NEXTDLGCTLTest(void)
543 {
544     DWORD dwVal;
545
546     g_hwndTestDlg = CreateWindowEx( WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR
547               | WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CONTROLPARENT | WS_EX_APPWINDOW,
548               "WM_NEXTDLGCTLWndClass",
549               "WM_NEXTDLGCTL Message test window",
550               WS_POPUPWINDOW | WS_CLIPSIBLINGS | WS_DLGFRAME | WS_OVERLAPPED |
551               WS_MINIMIZEBOX | WS_MAXIMIZEBOX | DS_3DLOOK | DS_SETFONT | DS_MODALFRAME,
552               0, 0, 235, 135,
553               NULL, NULL, g_hinst, 0);
554
555     assert (g_hwndTestDlg);
556     assert (g_hwndTestDlgBut1);
557     assert (g_hwndTestDlgBut2);
558     assert (g_hwndTestDlgEdit);
559
560     /*
561      * Test message DM_SETDEFID
562      */
563
564     DefDlgProcA( g_hwndTestDlg, DM_SETDEFID, IDCANCEL, 0 );
565     DefDlgProcA( g_hwndTestDlgBut1, BM_SETSTYLE, BS_DEFPUSHBUTTON, FALSE );
566     dwVal = DefDlgProcA(g_hwndTestDlg, DM_GETDEFID, 0, 0);
567
568     ok ( IDCANCEL == (LOWORD(dwVal)), "Did not set default ID\n" );
569
570     /*
571      * Check whether message WM_NEXTDLGCTL is changing the focus to next control and if
572      * the destination control is a button, style of the button should be changed to
573      * BS_DEFPUSHBUTTON with out making it default.
574      */
575
576     /*
577      * Keep the focus on Edit control.
578      */
579
580     if ( SetFocus( g_hwndTestDlgEdit ) )
581     {
582          ok ((GetFocus() == g_hwndTestDlgEdit), "Focus didn't set on Edit control\n");
583
584         /*
585          * Test message WM_NEXTDLGCTL
586          */
587         DefDlgProcA( g_hwndTestDlg, WM_NEXTDLGCTL, 0, 0 );
588         ok ((GetFocus() == g_hwndTestDlgBut1), "Focus didn't move to first button\n");
589
590         /*
591          * Check whether the default button ID got changed by sending message "WM_NEXTDLGCTL"
592          */
593         dwVal = DefDlgProcA(g_hwndTestDlg, DM_GETDEFID, 0, 0);
594         ok ( IDCANCEL == (LOWORD(dwVal)), "WM_NEXTDLGCTL changed default button\n");
595
596         /*
597          * Check whether the style of the button which got the focus, changed to BS_DEFPUSHBUTTON and
598          * the style of default button changed to BS_PUSHBUTTON.
599          */
600         if ( IDCANCEL == (LOWORD(dwVal)) )
601         {
602                 ok ( ((GetWindowLong( g_hwndTestDlgBut1, GWL_STYLE)) & BS_DEFPUSHBUTTON),
603                         "Button1 style not set to BS_DEFPUSHBUTTON\n" );
604
605                 ok ( !((GetWindowLong( g_hwndTestDlgBut2, GWL_STYLE)) & BS_DEFPUSHBUTTON),
606                         "Button2's style not chaged to BS_PUSHBUTTON\n" );
607         }
608
609         /*
610          * Move focus to Button2 using "WM_NEXTDLGCTL"
611          */
612         DefDlgProcA( g_hwndTestDlg, WM_NEXTDLGCTL, 0, 0 );
613         ok ((GetFocus() == g_hwndTestDlgBut2), "Focus didn't move to second button\n");
614
615         /*
616          * Check whether the default button ID got changed by sending message "WM_NEXTDLGCTL"
617          */
618         dwVal = DefDlgProcA(g_hwndTestDlg, DM_GETDEFID, 0, 0);
619         ok ( IDCANCEL == (LOWORD(dwVal)), "WM_NEXTDLGCTL changed default button\n");
620
621         /*
622          * Check whether the style of the button which got the focus, changed to BS_DEFPUSHBUTTON and
623          * the style of button which lost the focus changed to BS_PUSHBUTTON.
624          */
625         if ( IDCANCEL == (LOWORD(dwVal)) )
626         {
627                 ok ( ((GetWindowLong( g_hwndTestDlgBut2, GWL_STYLE)) & BS_DEFPUSHBUTTON),
628                         "Button2 style not set to BS_DEFPUSHBUTTON\n" );
629
630                 ok ( !((GetWindowLong( g_hwndTestDlgBut1, GWL_STYLE)) & BS_DEFPUSHBUTTON),
631                         "Button1's style not chaged to BS_PUSHBUTTON\n" );
632         }
633
634         /*
635          * Move focus to Edit control using "WM_NEXTDLGCTL"
636          */
637         DefDlgProcA( g_hwndTestDlg, WM_NEXTDLGCTL, 0, 0 );
638         ok ((GetFocus() == g_hwndTestDlgEdit), "Focus didn't move to Edit control\n");
639
640         /*
641          * Check whether the default button ID got changed by sending message "WM_NEXTDLGCTL"
642          */
643         dwVal = DefDlgProcA(g_hwndTestDlg, DM_GETDEFID, 0, 0);
644         ok ( IDCANCEL == (LOWORD(dwVal)), "WM_NEXTDLGCTL changed default button\n");
645     }
646     DestroyWindow(g_hwndTestDlg);
647 }
648
649 static void IsDialogMessageWTest (void)
650 {
651     MSG msg;
652
653     g_hwndMain = CreateWindow ("IsDialogMessageWindowClass", "IsDialogMessageWindowClass",
654             WS_OVERLAPPEDWINDOW,
655             CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
656             NULL, NULL, g_hinst, 0);
657
658     assert (g_hwndMain);
659     assert (g_hwndButton1);
660     assert (g_hwndButtonCancel);
661
662     /* The focus should initially be nowhere.  The first TAB should take it
663      * to the first button.  The second TAB should take it to the Cancel
664      * button.
665      */
666     FormTabMsg (&msg, g_hwndMain);
667     ok (IsDialogMessage (g_hwndMain, &msg), "Did not handle first TAB\n");
668     ok ((GetFocus() == g_hwndButton1), "Focus did not move to first button\n");
669     FormTabMsg (&msg, g_hwndButton1);
670     ok (IsDialogMessage (g_hwndMain, &msg), "Did not handle second TAB\n");
671     ok ((GetFocus() == g_hwndButtonCancel),
672             "Focus did not move to cancel button\n");
673     FormEnterMsg (&msg, g_hwndButtonCancel);
674     ok (IsDialogMessage (g_hwndMain, &msg), "Did not handle the ENTER\n");
675     ok (g_terminated, "ENTER did not terminate\n");
676 }
677
678
679 static LRESULT CALLBACK delayFocusDlgWinProc (HWND hDlg, UINT uiMsg, WPARAM wParam,
680         LPARAM lParam)
681 {
682     switch (uiMsg)
683     {
684     case WM_INITDIALOG:
685         g_hwndMain = hDlg;
686        g_hwndInitialFocusGroupBox = GetDlgItem(hDlg,100);
687        g_hwndButton1 = GetDlgItem(hDlg,200);
688        g_hwndButton2 = GetDlgItem(hDlg,201);
689        g_hwndButtonCancel = GetDlgItem(hDlg,IDCANCEL);
690        g_styleInitialFocusT1 = GetWindowLong(g_hwndInitialFocusGroupBox, GWL_STYLE);
691
692        /* Initially check the second radio button */
693        SendMessage(g_hwndButton1, BM_SETCHECK, BST_UNCHECKED, 0);
694        SendMessage(g_hwndButton2, BM_SETCHECK, BST_CHECKED  , 0);
695        /* Continue testing after dialog initialization */
696        PostMessage(hDlg, WM_USER, 0, 0);
697        return g_bInitialFocusInitDlgResult;
698
699     case WM_COMMAND:
700         if (LOWORD(wParam) == IDCANCEL)
701        {
702            EndDialog(hDlg, LOWORD(wParam));
703            return TRUE;
704        }
705        return FALSE;
706
707     case WM_USER:
708        g_styleInitialFocusT2 = GetWindowLong(hDlg, GWL_STYLE);
709         g_hwndInitialFocusT1 = GetFocus();
710        SetFocus(hDlg);
711         g_hwndInitialFocusT2 = GetFocus();
712        PostMessage(hDlg, WM_COMMAND, IDCANCEL, 0);
713        return TRUE;
714     }
715
716     return FALSE;
717 }
718
719 static LRESULT CALLBACK focusDlgWinProc (HWND hDlg, UINT uiMsg, WPARAM wParam,
720         LPARAM lParam)
721 {
722     switch (uiMsg)
723     {
724     case WM_INITDIALOG:
725        return TRUE;
726
727     case WM_COMMAND:
728        if (LOWORD(wParam) == IDCANCEL)
729        {
730            EndDialog(hDlg, LOWORD(wParam));
731            return TRUE;
732        }
733        else if (LOWORD(wParam) == 200)
734        {
735            if (HIWORD(wParam) == EN_SETFOCUS)
736                g_hwndInitialFocusT1 = (HWND)lParam;
737        }
738        return FALSE;
739     }
740
741     return FALSE;
742 }
743
744 /* Helper for InitialFocusTest */
745 static const char * GetHwndString(HWND hw)
746 {
747   if (hw == NULL)
748     return "a null handle";
749   if (hw == g_hwndMain)
750     return "the dialog handle";
751   if (hw == g_hwndInitialFocusGroupBox)
752     return "the group box control";
753   if (hw == g_hwndButton1)
754     return "the first button";
755   if (hw == g_hwndButton2)
756     return "the second button";
757   if (hw == g_hwndButtonCancel)
758     return "the cancel button";
759
760   return "unknown handle";
761 }
762
763 static void InitialFocusTest (void)
764 {
765     /* Test 1:
766      * This test intentionally returns FALSE in response to WM_INITDIALOG
767      * without setting focus to a control. This is not allowed according to
768      * MSDN, but it is exactly what MFC's CFormView does.
769      *
770      * Since the WM_INITDIALOG handler returns FALSE without setting the focus,
771      * the focus should initially be NULL. Later, when we manually set focus to
772      * the dialog, the default handler should set focus to the first control that
773      * is "visible, not disabled, and has the WS_TABSTOP style" (MSDN). Because the
774      * second radio button has been checked, it should be the first control
775      * that meets these criteria and should receive the focus.
776      */
777
778     g_bInitialFocusInitDlgResult = FALSE;
779     g_hwndInitialFocusT1 = (HWND) -1;
780     g_hwndInitialFocusT2 = (HWND) -1;
781     g_styleInitialFocusT1 = -1;
782     g_styleInitialFocusT2 = -1;
783
784     DialogBoxA(g_hinst, "RADIO_TEST_DIALOG", NULL, (DLGPROC)delayFocusDlgWinProc);
785
786     ok (((g_styleInitialFocusT1 & WS_TABSTOP) == 0),
787        "Error in wrc - Detected WS_TABSTOP as default style for GROUPBOX\n");
788
789     ok (((g_styleInitialFocusT2 & WS_VISIBLE) == 0),
790        "Modal dialogs should not be shown until the message queue first goes empty\n");
791
792     ok ((g_hwndInitialFocusT1 == NULL),
793         "Error in initial focus when WM_INITDIALOG returned FALSE: "
794         "Expected NULL focus, got %s (%p).\n",
795         GetHwndString(g_hwndInitialFocusT1), g_hwndInitialFocusT1);
796
797     ok ((g_hwndInitialFocusT2 == g_hwndButton2),
798         "Error after first SetFocus() when WM_INITDIALOG returned FALSE: "
799         "Expected the second button (%p), got %s (%p).\n",
800         g_hwndButton2, GetHwndString(g_hwndInitialFocusT2),
801         g_hwndInitialFocusT2);
802
803     /* Test 2:
804      * This is the same as above, except WM_INITDIALOG is made to return TRUE.
805      * This should cause the focus to go to the second radio button right away
806      * and stay there (until the user indicates otherwise).
807      */
808
809     g_bInitialFocusInitDlgResult = TRUE;
810     g_hwndInitialFocusT1 = (HWND) -1;
811     g_hwndInitialFocusT2 = (HWND) -1;
812     g_styleInitialFocusT1 = -1;
813     g_styleInitialFocusT2 = -1;
814
815     DialogBoxA(g_hinst, "RADIO_TEST_DIALOG", NULL, (DLGPROC)delayFocusDlgWinProc);
816
817     ok ((g_hwndInitialFocusT1 == g_hwndButton2),
818        "Error in initial focus when WM_INITDIALOG returned TRUE: "
819        "Expected the second button (%p), got %s (%p).\n",
820        g_hwndButton2, GetHwndString(g_hwndInitialFocusT2),
821        g_hwndInitialFocusT2);
822
823     ok ((g_hwndInitialFocusT2 == g_hwndButton2),
824        "Error after first SetFocus() when WM_INITDIALOG returned TRUE: "
825        "Expected the second button (%p), got %s (%p).\n",
826        g_hwndButton2, GetHwndString(g_hwndInitialFocusT2),
827        g_hwndInitialFocusT2);
828
829     /* Test 3:
830      * If the dialog has DS_CONTROL and it's not visible then we shouldn't change focus */
831     {
832         HWND hDlg;
833         HRSRC hResource;
834         HANDLE hTemplate;
835         DLGTEMPLATE* pTemplate;
836
837         hResource = FindResourceA(g_hinst,"FOCUS_TEST_DIALOG", (LPSTR)RT_DIALOG);
838         hTemplate = LoadResource(g_hinst, hResource);
839         pTemplate = (LPDLGTEMPLATEA)LockResource(hTemplate);
840
841         g_hwndInitialFocusT1 = 0;
842         hDlg = CreateDialogIndirectParamW(g_hinst, pTemplate, NULL, (DLGPROC)focusDlgWinProc,0);
843         ok (hDlg != 0, "Failed to create test dialog.\n");
844
845         ok ((g_hwndInitialFocusT1 == 0),
846             "Focus should not be set for an invisible DS_CONTROL dialog %p.\n", g_hwndInitialFocusT1);
847
848         DestroyWindow(hDlg);
849     }
850 }
851
852
853 START_TEST(dialog)
854 {
855     g_hinst = GetModuleHandleA (0);
856
857     if (!RegisterWindowClasses()) assert(0);
858
859     GetNextDlgItemTest();
860     IsDialogMessageWTest();
861     WM_NEXTDLGCTLTest();
862     InitialFocusTest();
863 }