1 /* Unit test suite for Button control.
2  *
3  * Copyright 1999 Ove Kaaven
4  * Copyright 2003 Dimitrie O. Paun
5  * Copyright 2004, 2005 Dmitry Timoshkov
6  * Copyright 2014 Nikolay Sivov for CodeWeavers
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22 
23 #undef USE_WINE_TODOS
24 
25 #include <windows.h>
26 #include <commctrl.h>
27 
28 #include "wine/test.h"
29 #include "v6util.h"
30 #include "msg.h"
31 
32 #define IS_WNDPROC_HANDLE(x) (((ULONG_PTR)(x) >> 16) == (~0u >> 16))
33 
34 static BOOL (WINAPI *pSetWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR, DWORD_PTR);
35 static BOOL (WINAPI *pRemoveWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR);
36 static LRESULT (WINAPI *pDefSubclassProc)(HWND, UINT, WPARAM, LPARAM);
37 
38 /****************** button message test *************************/
39 #define ID_BUTTON 0x000e
40 
41 #define COMBINED_SEQ_INDEX 0
42 #define NUM_MSG_SEQUENCES  1
43 
44 static struct msg_sequence *sequences[NUM_MSG_SEQUENCES];
45 
46 struct wndclass_redirect_data
47 {
48     ULONG size;
49     DWORD res;
50     ULONG name_len;
51     ULONG name_offset;
52     ULONG module_len;
53     ULONG module_offset;
54 };
55 
56 /* returned pointer is valid as long as activation context is alive */
57 static WCHAR* get_versioned_classname(const WCHAR *name)
58 {
59     struct wndclass_redirect_data *wnddata;
60     ACTCTX_SECTION_KEYED_DATA data;
61     BOOL ret;
62 
63     memset(&data, 0, sizeof(data));
64     data.cbSize = sizeof(data);
65     ret = FindActCtxSectionStringW(0, NULL, ACTIVATION_CONTEXT_SECTION_WINDOW_CLASS_REDIRECTION, name, &data);
66     ok(ret, "Failed to find class redirection section, error %u\n", GetLastError());
67     wnddata = (struct wndclass_redirect_data*)data.lpData;
68     return (WCHAR*)((BYTE*)wnddata + wnddata->name_offset);
69 }
70 
71 static void init_functions(void)
72 {
73     HMODULE hmod = GetModuleHandleA("comctl32.dll");
74     ok(hmod != NULL, "got %p\n", hmod);
75 
76 #define MAKEFUNC_ORD(f, ord) (p##f = (void*)GetProcAddress(hmod, (LPSTR)(ord)))
77     MAKEFUNC_ORD(SetWindowSubclass, 410);
78     MAKEFUNC_ORD(RemoveWindowSubclass, 412);
79     MAKEFUNC_ORD(DefSubclassProc, 413);
80 #undef MAKEFUNC_ORD
81 }
82 
83 /* try to make sure pending X events have been processed before continuing */
84 static void flush_events(void)
85 {
86     MSG msg;
87     int diff = 200;
88     int min_timeout = 100;
89     DWORD time = GetTickCount() + diff;
90 
91     while (diff > 0)
92     {
93         if (MsgWaitForMultipleObjects( 0, NULL, FALSE, min_timeout, QS_ALLINPUT ) == WAIT_TIMEOUT) break;
94         while (PeekMessageA( &msg, 0, 0, 0, PM_REMOVE )) DispatchMessageA( &msg );
95         diff = time - GetTickCount();
96     }
97 }
98 
99 static BOOL ignore_message( UINT message )
100 {
101     /* these are always ignored */
102     return (message >= 0xc000 ||
103             message == WM_GETICON ||
104             message == WM_GETOBJECT ||
105             message == WM_TIMECHANGE ||
106             message == WM_DISPLAYCHANGE ||
107             message == WM_DEVICECHANGE ||
108             message == WM_DWMNCRENDERINGCHANGED ||
109             message == WM_GETTEXTLENGTH ||
110             message == WM_GETTEXT);
111 }
112 
113 static LRESULT CALLBACK button_subclass_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR ref_data)
114 {
115     static LONG defwndproc_counter = 0;
116     struct message msg = { 0 };
117     LRESULT ret;
118 
119     if (ignore_message( message )) return pDefSubclassProc(hwnd, message, wParam, lParam);
120 
121     switch (message)
122     {
123     case WM_SYNCPAINT:
124         break;
125     case BM_SETSTATE:
126         if (GetCapture())
127             ok(GetCapture() == hwnd, "GetCapture() = %p\n", GetCapture());
128         /* fall through */
129     default:
130         msg.message = message;
131         msg.flags = sent|wparam|lparam;
132         if (defwndproc_counter) msg.flags |= defwinproc;
133         msg.wParam = wParam;
134         msg.lParam = lParam;
135         add_message(sequences, COMBINED_SEQ_INDEX, &msg);
136     }
137 
138     if (message == WM_NCDESTROY)
139         pRemoveWindowSubclass(hwnd, button_subclass_proc, 0);
140 
141     defwndproc_counter++;
142     ret = pDefSubclassProc(hwnd, message, wParam, lParam);
143     defwndproc_counter--;
144 
145     return ret;
146 }
147 
148 static LRESULT WINAPI test_parent_wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
149 {
150     static LONG defwndproc_counter = 0;
151     static LONG beginpaint_counter = 0;
152     struct message msg = { 0 };
153     LRESULT ret;
154 
155     if (ignore_message( message )) return 0;
156 
157     if (message == WM_PARENTNOTIFY || message == WM_CANCELMODE ||
158         message == WM_SETFOCUS || message == WM_KILLFOCUS ||
159         message == WM_ENABLE || message == WM_ENTERIDLE ||
160         message == WM_DRAWITEM || message == WM_COMMAND ||
161         message == WM_IME_SETCONTEXT)
162     {
163         msg.message = message;
164         msg.flags = sent|parent|wparam|lparam;
165         if (defwndproc_counter) msg.flags |= defwinproc;
166         if (beginpaint_counter) msg.flags |= beginpaint;
167         msg.wParam = wParam;
168         msg.lParam = lParam;
169         add_message(sequences, COMBINED_SEQ_INDEX, &msg);
170     }
171 
172     if (message == WM_PAINT)
173     {
174         PAINTSTRUCT ps;
175         beginpaint_counter++;
176         BeginPaint( hwnd, &ps );
177         beginpaint_counter--;
178         EndPaint( hwnd, &ps );
179         return 0;
180     }
181 
182     defwndproc_counter++;
183     ret = DefWindowProcA(hwnd, message, wParam, lParam);
184     defwndproc_counter--;
185 
186     return ret;
187 }
188 
189 static const struct message setfocus_seq[] =
190 {
191     { WM_IME_SETCONTEXT, sent|wparam|optional, 1 },
192     { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 2 },
193     { BM_GETSTATE, sent|optional }, /* when touchscreen is present */
194     { WM_SETFOCUS, sent|wparam },
195     { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_SETFOCUS) },
196     { WM_APP, sent|wparam|lparam },
197     { WM_PAINT, sent },
198     { 0 }
199 };
200 
201 static const struct message killfocus_seq[] =
202 {
203     { WM_KILLFOCUS, sent|wparam, 0 },
204     { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_KILLFOCUS) },
205     { WM_IME_SETCONTEXT, sent|wparam|optional, 0 },
206     { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 1 },
207     { WM_APP, sent|wparam|lparam, 0, 0 },
208     { WM_PAINT, sent },
209     { 0 }
210 };
211 
212 static const struct message setfocus_static_seq[] =
213 {
214     { WM_IME_SETCONTEXT, sent|wparam|optional, 1 },
215     { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 2 },
216     { BM_GETSTATE, sent|optional }, /* when touchscreen is present */
217     { WM_SETFOCUS, sent|wparam, 0 },
218     { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_SETFOCUS) },
219     { WM_COMMAND, sent|wparam|parent|optional, MAKEWPARAM(ID_BUTTON, BN_CLICKED) }, /* radio button */
220     { WM_APP, sent|wparam|lparam, 0, 0 },
221     { WM_PAINT, sent },
222     { 0 }
223 };
224 
225 static const struct message setfocus_groupbox_seq[] =
226 {
227     { WM_IME_SETCONTEXT, sent|wparam|optional, 1 },
228     { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 2 },
229     { BM_GETSTATE, sent|optional }, /* when touchscreen is present */
230     { WM_SETFOCUS, sent|wparam, 0 },
231     { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_SETFOCUS) },
232     { WM_COMMAND, sent|wparam|parent|optional, MAKEWPARAM(ID_BUTTON, BN_CLICKED) }, /* radio button */
233     { WM_APP, sent|wparam|lparam, 0, 0 },
234     { WM_PAINT, sent },
235     { 0 }
236 };
237 
238 static const struct message killfocus_static_seq[] =
239 {
240     { WM_KILLFOCUS, sent|wparam, 0 },
241     { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_KILLFOCUS) },
242     { WM_IME_SETCONTEXT, sent|wparam|optional, 0 },
243     { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 1 },
244     { WM_APP, sent|wparam|lparam, 0, 0 },
245     { WM_PAINT, sent },
246     { 0 }
247 };
248 
249 static const struct message setfocus_ownerdraw_seq[] =
250 {
251     { WM_IME_SETCONTEXT, sent|wparam|optional, 1 },
252     { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 2 },
253     { BM_GETSTATE, sent|optional }, /* when touchscreen is present */
254     { WM_SETFOCUS, sent|wparam, 0 },
255     { WM_DRAWITEM, sent|wparam|parent, ID_BUTTON },
256     { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_SETFOCUS) },
257     { WM_APP, sent|wparam|lparam, 0, 0 },
258     { WM_IME_SETCONTEXT, sent|wparam|optional, 1 },
259     { 0 }
260 };
261 
262 static const struct message killfocus_ownerdraw_seq[] =
263 {
264     { WM_KILLFOCUS, sent|wparam, 0 },
265     { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_KILLFOCUS) },
266     { WM_IME_SETCONTEXT, sent|wparam|optional, 0 },
267     { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 1 },
268     { WM_APP, sent|wparam|lparam, 0, 0 },
269     { WM_PAINT, sent },
270     { WM_DRAWITEM, sent|wparam|parent, ID_BUTTON },
271     { 0 }
272 };
273 
274 static const struct message lbuttondown_seq[] =
275 {
276     { WM_LBUTTONDOWN, sent|wparam|lparam, 0, 0 },
277     { WM_IME_SETCONTEXT, sent|wparam|defwinproc|optional, 1 },
278     { WM_IME_NOTIFY, sent|wparam|defwinproc|optional, 2 },
279     { BM_GETSTATE, sent|defwinproc|optional }, /* when touchscreen is present */
280     { WM_SETFOCUS, sent|wparam|defwinproc, 0 },
281     { BM_SETSTATE, sent|wparam|defwinproc, TRUE },
282     { 0 }
283 };
284 
285 static const struct message lbuttonup_seq[] =
286 {
287     { WM_LBUTTONUP, sent|wparam|lparam, 0, 0 },
288     { BM_SETSTATE, sent|wparam|defwinproc, FALSE },
289     { WM_CAPTURECHANGED, sent|wparam|defwinproc, 0 },
290     { WM_COMMAND, sent|wparam|defwinproc, 0 },
291     { 0 }
292 };
293 
294 static const struct message setfont_seq[] =
295 {
296     { WM_SETFONT, sent },
297     { 0 }
298 };
299 
300 static const struct message setstyle_seq[] =
301 {
302     { BM_SETSTYLE, sent },
303     { WM_STYLECHANGING, sent|wparam|defwinproc, GWL_STYLE },
304     { WM_STYLECHANGED, sent|wparam|defwinproc, GWL_STYLE },
305     { WM_APP, sent|wparam|lparam, 0, 0 },
306     { WM_PAINT, sent },
307     { WM_NCPAINT, sent|defwinproc|optional }, /* FIXME: Wine sends it */
308     { WM_ERASEBKGND, sent|defwinproc|optional },
309     { WM_PAINT, sent|optional },
310     { 0 }
311 };
312 
313 static const struct message setstyle_static_seq[] =
314 {
315     { BM_SETSTYLE, sent },
316     { WM_STYLECHANGING, sent|wparam|defwinproc, GWL_STYLE },
317     { WM_STYLECHANGED, sent|wparam|defwinproc, GWL_STYLE },
318     { WM_APP, sent|wparam|lparam, 0, 0 },
319     { WM_PAINT, sent },
320     { WM_NCPAINT, sent|defwinproc|optional }, /* FIXME: Wine sends it */
321     { WM_ERASEBKGND, sent|defwinproc|optional },
322     { 0 }
323 };
324 
325 static const struct message setstyle_user_seq[] =
326 {
327     { BM_SETSTYLE, sent },
328     { WM_STYLECHANGING, sent|wparam|defwinproc, GWL_STYLE },
329     { WM_STYLECHANGED, sent|wparam|defwinproc, GWL_STYLE },
330     { WM_APP, sent|wparam|lparam, 0, 0 },
331     { WM_PAINT, sent },
332     { WM_NCPAINT, sent|defwinproc|optional }, /* FIXME: Wine sends it */
333     { WM_ERASEBKGND, sent|defwinproc|optional },
334     { 0 }
335 };
336 
337 static const struct message setstyle_ownerdraw_seq[] =
338 {
339     { BM_SETSTYLE, sent },
340     { WM_STYLECHANGING, sent|wparam|defwinproc, GWL_STYLE },
341     { WM_STYLECHANGED, sent|wparam|defwinproc, GWL_STYLE },
342     { WM_APP, sent|wparam|lparam, 0, 0 },
343     { WM_PAINT, sent },
344     { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
345     { WM_ERASEBKGND, sent|defwinproc|optional },
346     { WM_DRAWITEM, sent|wparam|parent, ID_BUTTON },
347     { 0 }
348 };
349 
350 static const struct message setstate_seq[] =
351 {
352     { BM_SETSTATE, sent },
353     { WM_APP, sent|wparam|lparam, 0, 0 },
354     { WM_PAINT, sent },
355     { WM_PAINT, sent|optional },
356     { 0 }
357 };
358 
359 static const struct message setstate_static_seq[] =
360 {
361     { BM_SETSTATE, sent },
362     { WM_APP, sent|wparam|lparam, 0, 0 },
363     { WM_PAINT, sent },
364     { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
365     { WM_ERASEBKGND, sent|defwinproc|optional },
366     { 0 }
367 };
368 
369 static const struct message setstate_user_seq[] =
370 {
371     { BM_SETSTATE, sent },
372     { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_HILITE) },
373     { WM_APP, sent|wparam|lparam, 0, 0 },
374     { WM_PAINT, sent },
375     { 0 }
376 };
377 
378 static const struct message setstate_ownerdraw_seq[] =
379 {
380     { BM_SETSTATE, sent },
381     { WM_APP, sent|wparam|lparam, 0, 0 },
382     { WM_PAINT, sent },
383     { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
384     { WM_ERASEBKGND, sent|defwinproc|optional },
385     { WM_DRAWITEM, sent|wparam|parent, ID_BUTTON },
386     { 0 }
387 };
388 
389 static const struct message clearstate_seq[] =
390 {
391     { BM_SETSTATE, sent },
392     { WM_COMMAND, sent|wparam|parent, MAKEWPARAM(ID_BUTTON, BN_UNHILITE) },
393     { WM_APP, sent|wparam|lparam, 0, 0 },
394     { WM_PAINT, sent },
395     { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
396     { WM_ERASEBKGND, sent|defwinproc|optional },
397     { 0 }
398 };
399 
400 static const struct message clearstate_ownerdraw_seq[] =
401 {
402     { BM_SETSTATE, sent },
403     { WM_APP, sent|wparam|lparam, 0, 0 },
404     { WM_PAINT, sent },
405     { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
406     { WM_ERASEBKGND, sent|defwinproc|optional },
407     { WM_DRAWITEM, sent|wparam|parent, ID_BUTTON },
408     { 0 }
409 };
410 
411 static const struct message setcheck_ignored_seq[] =
412 {
413     { BM_SETCHECK, sent },
414     { WM_APP, sent|wparam|lparam, 0, 0 },
415     { WM_PAINT, sent|optional },
416     { 0 }
417 };
418 
419 static const struct message setcheck_static_seq[] =
420 {
421     { BM_SETCHECK, sent },
422     { WM_APP, sent|wparam|lparam, 0, 0 },
423     { WM_PAINT, sent },
424     { WM_NCPAINT, sent|optional }, /* FIXME: Wine sends it */
425     { WM_ERASEBKGND, sent|defwinproc|optional },
426     { 0 }
427 };
428 
429 static const struct message setcheck_radio_seq[] =
430 {
431     { BM_SETCHECK, sent },
432     { WM_STYLECHANGING, sent|wparam|defwinproc, GWL_STYLE },
433     { WM_STYLECHANGED, sent|wparam|defwinproc, GWL_STYLE },
434     { WM_APP, sent|wparam|lparam, 0, 0 },
435     { 0 }
436 };
437 
438 static const struct message setcheck_radio_redraw_seq[] =
439 {
440     { BM_SETCHECK, sent },
441     { WM_STYLECHANGING, sent|wparam|defwinproc, GWL_STYLE },
442     { WM_STYLECHANGED, sent|wparam|defwinproc, GWL_STYLE },
443     { WM_APP, sent|wparam|lparam, 0, 0 },
444     { WM_PAINT, sent },
445     { WM_NCPAINT, sent|defwinproc|optional }, /* FIXME: Wine sends it */
446     { 0 }
447 };
448 
449 static HWND create_button(DWORD style, HWND parent)
450 {
451     HMENU menuid = 0;
452     HWND hwnd;
453 
454     if (parent)
455     {
456         style |= WS_CHILD|BS_NOTIFY;
457         menuid = (HMENU)ID_BUTTON;
458     }
459     hwnd = CreateWindowExA(0, WC_BUTTONA, "test", style, 0, 0, 50, 14, parent, menuid, 0, NULL);
460     ok(hwnd != NULL, "failed to create a button, 0x%08x, %p\n", style, parent);
461     pSetWindowSubclass(hwnd, button_subclass_proc, 0, 0);
462     return hwnd;
463 }
464 
465 static void test_button_messages(void)
466 {
467     static const struct
468     {
469         DWORD style;
470         DWORD dlg_code;
471         const struct message *setfocus;
472         const struct message *killfocus;
473         const struct message *setstyle;
474         const struct message *setstate;
475         const struct message *clearstate;
476         const struct message *setcheck;
477     } button[] = {
478         { BS_PUSHBUTTON, DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON,
479           setfocus_seq, killfocus_seq, setstyle_seq,
480           setstate_seq, setstate_seq, setcheck_ignored_seq },
481         { BS_DEFPUSHBUTTON, DLGC_BUTTON | DLGC_DEFPUSHBUTTON,
482           setfocus_seq, killfocus_seq, setstyle_seq,
483           setstate_seq, setstate_seq, setcheck_ignored_seq },
484         { BS_CHECKBOX, DLGC_BUTTON,
485           setfocus_static_seq, killfocus_static_seq, setstyle_static_seq,
486           setstate_static_seq, setstate_static_seq, setcheck_static_seq },
487         { BS_AUTOCHECKBOX, DLGC_BUTTON,
488           setfocus_static_seq, killfocus_static_seq, setstyle_static_seq,
489           setstate_static_seq, setstate_static_seq, setcheck_static_seq },
490         { BS_RADIOBUTTON, DLGC_BUTTON | DLGC_RADIOBUTTON,
491           setfocus_static_seq, killfocus_static_seq, setstyle_static_seq,
492           setstate_static_seq, setstate_static_seq, setcheck_radio_redraw_seq },
493         { BS_3STATE, DLGC_BUTTON,
494           setfocus_static_seq, killfocus_static_seq, setstyle_static_seq,
495           setstate_static_seq, setstate_static_seq, setcheck_static_seq },
496         { BS_AUTO3STATE, DLGC_BUTTON,
497           setfocus_static_seq, killfocus_static_seq, setstyle_static_seq,
498           setstate_static_seq, setstate_static_seq, setcheck_static_seq },
499         { BS_GROUPBOX, DLGC_STATIC,
500           setfocus_groupbox_seq, killfocus_static_seq, setstyle_static_seq,
501           setstate_static_seq, setstate_static_seq, setcheck_ignored_seq },
502         { BS_USERBUTTON, DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON,
503           setfocus_seq, killfocus_seq, setstyle_user_seq,
504           setstate_user_seq, clearstate_seq, setcheck_ignored_seq },
505         { BS_AUTORADIOBUTTON, DLGC_BUTTON | DLGC_RADIOBUTTON,
506           setfocus_static_seq, killfocus_static_seq, setstyle_static_seq,
507           setstate_static_seq, setstate_static_seq, setcheck_radio_redraw_seq },
508         { BS_OWNERDRAW, DLGC_BUTTON,
509           setfocus_ownerdraw_seq, killfocus_ownerdraw_seq, setstyle_ownerdraw_seq,
510           setstate_ownerdraw_seq, clearstate_ownerdraw_seq, setcheck_ignored_seq },
511         { BS_SPLITBUTTON, DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON | DLGC_WANTARROWS,
512           setfocus_seq, killfocus_seq, setstyle_seq,
513           setstate_seq, setstate_seq, setcheck_ignored_seq },
514         { BS_DEFSPLITBUTTON, DLGC_BUTTON | DLGC_DEFPUSHBUTTON | DLGC_WANTARROWS,
515           setfocus_seq, killfocus_seq, setstyle_seq,
516           setstate_seq, setstate_seq, setcheck_ignored_seq },
517         { BS_COMMANDLINK, DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON,
518           setfocus_seq, killfocus_seq, setstyle_seq,
519           setstate_seq, setstate_seq, setcheck_ignored_seq },
520         { BS_DEFCOMMANDLINK, DLGC_BUTTON | DLGC_DEFPUSHBUTTON,
521           setfocus_seq, killfocus_seq, setstyle_seq,
522           setstate_seq, setstate_seq, setcheck_ignored_seq },
523     };
524     LOGFONTA logfont = { 0 };
525     const struct message *seq;
526     HFONT zfont, hfont2;
527     unsigned int i;
528     HWND hwnd, parent;
529     DWORD dlg_code;
530     BOOL todo;
531 
532     /* selection with VK_SPACE should capture button window */
533     hwnd = create_button(BS_CHECKBOX | WS_VISIBLE | WS_POPUP, NULL);
534     ok(hwnd != 0, "Failed to create button window\n");
535     ReleaseCapture();
536     SetFocus(hwnd);
537     SendMessageA(hwnd, WM_KEYDOWN, VK_SPACE, 0);
538     ok(GetCapture() == hwnd, "Should be captured on VK_SPACE WM_KEYDOWN\n");
539     SendMessageA(hwnd, WM_KEYUP, VK_SPACE, 0);
540     DestroyWindow(hwnd);
541 
542     parent = CreateWindowExA(0, "TestParentClass", "Test parent", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
543                              100, 100, 200, 200, 0, 0, 0, NULL);
544     ok(parent != 0, "Failed to create parent window\n");
545 
546     logfont.lfHeight = -12;
547     logfont.lfWeight = FW_NORMAL;
548     strcpy(logfont.lfFaceName, "Tahoma");
549 
550     hfont2 = CreateFontIndirectA(&logfont);
551     ok(hfont2 != NULL, "Failed to create Tahoma font\n");
552 
553     for (i = 0; i < sizeof(button)/sizeof(button[0]); i++)
554     {
555         HFONT prevfont, hfont;
556         MSG msg;
557         DWORD style, state;
558         HDC hdc;
559 
560         trace("%d: button test sequence\n", i);
561         hwnd = create_button(button[i].style, parent);
562 
563         style = GetWindowLongA(hwnd, GWL_STYLE);
564         style &= ~(WS_CHILD | BS_NOTIFY);
565         /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
566         if (button[i].style == BS_USERBUTTON)
567             ok(style == BS_PUSHBUTTON, "expected style BS_PUSHBUTTON got %x\n", style);
568         else
569             ok(style == button[i].style, "expected style %x got %x\n", button[i].style, style);
570 
571         dlg_code = SendMessageA(hwnd, WM_GETDLGCODE, 0, 0);
572         if (button[i].style == BS_SPLITBUTTON ||
573                 button[i].style == BS_DEFSPLITBUTTON ||
574                 button[i].style == BS_COMMANDLINK ||
575                 button[i].style == BS_DEFCOMMANDLINK)
576         {
577             ok(dlg_code == button[i].dlg_code || broken(dlg_code == DLGC_BUTTON) /* WinXP */, "%u: wrong dlg_code %08x\n", i, dlg_code);
578         }
579         else
580             ok(dlg_code == button[i].dlg_code, "%u: wrong dlg_code %08x\n", i, dlg_code);
581 
582         ShowWindow(hwnd, SW_SHOW);
583         UpdateWindow(hwnd);
584         SetFocus(0);
585         flush_events();
586         SetFocus(0);
587         flush_sequences(sequences, NUM_MSG_SEQUENCES);
588 
589         todo = button[i].style != BS_OWNERDRAW;
590         ok(GetFocus() == 0, "expected focus 0, got %p\n", GetFocus());
591         SetFocus(hwnd);
592         SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
593         while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
594         ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].setfocus, "SetFocus(hwnd) on a button", todo);
595 
596         todo = button[i].style == BS_OWNERDRAW;
597         SetFocus(0);
598         SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
599         while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
600         ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].killfocus, "SetFocus(0) on a button", todo);
601         ok(GetFocus() == 0, "expected focus 0, got %p\n", GetFocus());
602 
603         SendMessageA(hwnd, BM_SETSTYLE, button[i].style | BS_BOTTOM, TRUE);
604         SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
605         while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
606         todo = button[i].style == BS_OWNERDRAW;
607         ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].setstyle, "BM_SETSTYLE on a button", todo);
608 
609         style = GetWindowLongA(hwnd, GWL_STYLE);
610         style &= ~(WS_VISIBLE | WS_CHILD | BS_NOTIFY);
611         /* XP doesn't turn a BS_USERBUTTON into BS_PUSHBUTTON here! */
612         ok(style == button[i].style, "expected style %04x got %04x\n", button[i].style, style);
613 
614         state = SendMessageA(hwnd, BM_GETSTATE, 0, 0);
615         ok(state == 0, "expected state 0, got %04x\n", state);
616 
617         flush_sequences(sequences, NUM_MSG_SEQUENCES);
618 
619         SendMessageA(hwnd, BM_SETSTATE, TRUE, 0);
620         SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
621         while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
622         ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].setstate, "BM_SETSTATE/TRUE on a button", FALSE);
623 
624         state = SendMessageA(hwnd, BM_GETSTATE, 0, 0);
625         ok(state == BST_PUSHED, "expected state 0x0004, got %04x\n", state);
626 
627         style = GetWindowLongA(hwnd, GWL_STYLE);
628         style &= ~(WS_CHILD | BS_NOTIFY | WS_VISIBLE);
629         ok(style == button[i].style, "expected style %04x got %04x\n", button[i].style, style);
630 
631         flush_sequences(sequences, NUM_MSG_SEQUENCES);
632 
633         SendMessageA(hwnd, BM_SETSTATE, FALSE, 0);
634         SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
635         while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
636         ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].clearstate, "BM_SETSTATE/FALSE on a button", FALSE);
637 
638         state = SendMessageA(hwnd, BM_GETSTATE, 0, 0);
639         ok(state == 0, "expected state 0, got %04x\n", state);
640 
641         style = GetWindowLongA(hwnd, GWL_STYLE);
642         style &= ~(WS_CHILD | BS_NOTIFY | WS_VISIBLE);
643         ok(style == button[i].style, "expected style %04x got %04x\n", button[i].style, style);
644 
645         state = SendMessageA(hwnd, BM_GETCHECK, 0, 0);
646         ok(state == BST_UNCHECKED, "expected BST_UNCHECKED, got %04x\n", state);
647 
648         flush_sequences(sequences, NUM_MSG_SEQUENCES);
649 
650         if (button[i].style == BS_RADIOBUTTON ||
651             button[i].style == BS_AUTORADIOBUTTON)
652         {
653             seq = setcheck_radio_seq;
654         }
655         else
656             seq = setcheck_ignored_seq;
657 
658         SendMessageA(hwnd, BM_SETCHECK, BST_UNCHECKED, 0);
659         SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
660         while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
661         ok_sequence(sequences, COMBINED_SEQ_INDEX, seq, "BM_SETCHECK on a button", FALSE);
662 
663         state = SendMessageA(hwnd, BM_GETCHECK, 0, 0);
664         ok(state == BST_UNCHECKED, "expected BST_UNCHECKED, got %04x\n", state);
665 
666         style = GetWindowLongA(hwnd, GWL_STYLE);
667         style &= ~(WS_CHILD | BS_NOTIFY | WS_VISIBLE);
668         ok(style == button[i].style, "expected style %04x got %04x\n", button[i].style, style);
669 
670         flush_sequences(sequences, NUM_MSG_SEQUENCES);
671 
672         SendMessageA(hwnd, BM_SETCHECK, BST_CHECKED, 0);
673         SendMessageA(hwnd, WM_APP, 0, 0); /* place a separator mark here */
674         while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
675         ok_sequence(sequences, COMBINED_SEQ_INDEX, button[i].setcheck, "BM_SETCHECK on a button", FALSE);
676 
677         state = SendMessageA(hwnd, BM_GETCHECK, 0, 0);
678         if (button[i].style == BS_PUSHBUTTON ||
679             button[i].style == BS_DEFPUSHBUTTON ||
680             button[i].style == BS_GROUPBOX ||
681             button[i].style == BS_USERBUTTON ||
682             button[i].style == BS_OWNERDRAW ||
683             button[i].style == BS_SPLITBUTTON ||
684             button[i].style == BS_DEFSPLITBUTTON ||
685             button[i].style == BS_COMMANDLINK ||
686             button[i].style == BS_DEFCOMMANDLINK)
687         {
688             ok(state == BST_UNCHECKED, "expected check BST_UNCHECKED, got %04x\n", state);
689         }
690         else
691             ok(state == BST_CHECKED, "expected check BST_CHECKED, got %04x\n", state);
692 
693         style = GetWindowLongA(hwnd, GWL_STYLE);
694         style &= ~(WS_CHILD | BS_NOTIFY | WS_VISIBLE);
695         if (button[i].style == BS_RADIOBUTTON ||
696             button[i].style == BS_AUTORADIOBUTTON)
697             ok(style == (button[i].style | WS_TABSTOP), "expected style %04x | WS_TABSTOP got %04x\n", button[i].style, style);
698         else
699             ok(style == button[i].style, "expected style %04x got %04x\n", button[i].style, style);
700 
701         /* Test that original font is not selected back after painting */
702         hfont = (HFONT)SendMessageA(hwnd, WM_GETFONT, 0, 0);
703         ok(hfont == NULL, "Unexpected control font.\n");
704 
705         SendMessageA(hwnd, WM_SETFONT, (WPARAM)GetStockObject(SYSTEM_FONT), 0);
706 
707         hdc = CreateCompatibleDC(0);
708 
709         prevfont = SelectObject(hdc, hfont2);
710         SendMessageA(hwnd, WM_PRINTCLIENT, (WPARAM)hdc, 0);
711         ok(hfont2 != GetCurrentObject(hdc, OBJ_FONT) || broken(hfont2 == GetCurrentObject(hdc, OBJ_FONT)) /* WinXP */,
712             "button[%u]: unexpected font selected after WM_PRINTCLIENT\n", i);
713         SelectObject(hdc, prevfont);
714 
715         prevfont = SelectObject(hdc, hfont2);
716         SendMessageA(hwnd, WM_PAINT, (WPARAM)hdc, 0);
717         ok(hfont2 != GetCurrentObject(hdc, OBJ_FONT) || broken(hfont2 == GetCurrentObject(hdc, OBJ_FONT)) /* WinXP */,
718             "button[%u]: unexpected font selected after WM_PAINT\n", i);
719         SelectObject(hdc, prevfont);
720 
721         DeleteDC(hdc);
722 
723         DestroyWindow(hwnd);
724     }
725 
726     DeleteObject(hfont2);
727     DestroyWindow(parent);
728 
729     hwnd = create_button(BS_PUSHBUTTON, NULL);
730 
731     SetForegroundWindow(hwnd);
732     flush_events();
733 
734     SetActiveWindow(hwnd);
735     SetFocus(0);
736     flush_sequences(sequences, NUM_MSG_SEQUENCES);
737 
738     SendMessageA(hwnd, WM_LBUTTONDOWN, 0, 0);
739     ok_sequence(sequences, COMBINED_SEQ_INDEX, lbuttondown_seq, "WM_LBUTTONDOWN on a button", FALSE);
740 
741     SendMessageA(hwnd, WM_LBUTTONUP, 0, 0);
742     ok_sequence(sequences, COMBINED_SEQ_INDEX, lbuttonup_seq, "WM_LBUTTONUP on a button", TRUE);
743 
744     flush_sequences(sequences, NUM_MSG_SEQUENCES);
745     zfont = GetStockObject(SYSTEM_FONT);
746     SendMessageA(hwnd, WM_SETFONT, (WPARAM)zfont, TRUE);
747     UpdateWindow(hwnd);
748     ok_sequence(sequences, COMBINED_SEQ_INDEX, setfont_seq, "WM_SETFONT on a button", FALSE);
749 
750     DestroyWindow(hwnd);
751 }
752 
753 static void test_button_class(void)
754 {
755     static const WCHAR testW[] = {'t','e','s','t',0};
756     WNDCLASSEXW exW, ex2W;
757     WNDCLASSEXA exA;
758     char buffA[100];
759     WCHAR *nameW;
760     HWND hwnd;
761     BOOL ret;
762     int len;
763 
764     ret = GetClassInfoExA(NULL, WC_BUTTONA, &exA);
765     ok(ret, "got %d\n", ret);
766     ok(IS_WNDPROC_HANDLE(exA.lpfnWndProc), "got %p\n", exA.lpfnWndProc);
767     ok(exA.cbClsExtra == 0, "Unexpected class bytes %d.\n", exA.cbClsExtra);
768     ok(exA.cbWndExtra == sizeof(void *), "Unexpected window bytes %d.\n", exA.cbWndExtra);
769 
770     ret = GetClassInfoExW(NULL, WC_BUTTONW, &exW);
771     ok(ret, "got %d\n", ret);
772     ok(!IS_WNDPROC_HANDLE(exW.lpfnWndProc), "got %p\n", exW.lpfnWndProc);
773     ok(exW.cbClsExtra == 0, "Unexpected class bytes %d.\n", exW.cbClsExtra);
774     ok(exW.cbWndExtra == sizeof(void *), "Unexpected window bytes %d.\n", exW.cbWndExtra);
775 
776     /* check that versioned class is also accessible */
777     nameW = get_versioned_classname(WC_BUTTONW);
778     ok(lstrcmpW(nameW, WC_BUTTONW), "got %s\n", wine_dbgstr_w(nameW));
779 
780     ret = GetClassInfoExW(NULL, nameW, &ex2W);
781     ok(ret, "got %d\n", ret);
782     ok(ex2W.lpfnWndProc == exW.lpfnWndProc, "got %p, %p\n", exW.lpfnWndProc, ex2W.lpfnWndProc);
783 
784     /* Check reported class name */
785     hwnd = create_button(BS_CHECKBOX, NULL);
786     len = GetClassNameA(hwnd, buffA, sizeof(buffA));
787     ok(len == strlen(buffA), "got %d\n", len);
788     ok(!strcmp(buffA, "Button"), "got %s\n", buffA);
789 
790     len = RealGetWindowClassA(hwnd, buffA, sizeof(buffA));
791     ok(len == strlen(buffA), "got %d\n", len);
792     ok(!strcmp(buffA, "Button"), "got %s\n", buffA);
793     DestroyWindow(hwnd);
794 
795     /* explicitly create with versioned class name */
796     hwnd = CreateWindowExW(0, nameW, testW, BS_CHECKBOX, 0, 0, 50, 14, NULL, 0, 0, NULL);
797     ok(hwnd != NULL, "failed to create a window %s\n", wine_dbgstr_w(nameW));
798 
799     len = GetClassNameA(hwnd, buffA, sizeof(buffA));
800     ok(len == strlen(buffA), "got %d\n", len);
801     ok(!strcmp(buffA, "Button"), "got %s\n", buffA);
802 
803     len = RealGetWindowClassA(hwnd, buffA, sizeof(buffA));
804     ok(len == strlen(buffA), "got %d\n", len);
805     ok(!strcmp(buffA, "Button"), "got %s\n", buffA);
806 
807     DestroyWindow(hwnd);
808 }
809 
810 static void register_parent_class(void)
811 {
812     WNDCLASSA cls;
813 
814     cls.style = 0;
815     cls.lpfnWndProc = test_parent_wndproc;
816     cls.cbClsExtra = 0;
817     cls.cbWndExtra = 0;
818     cls.hInstance = GetModuleHandleA(0);
819     cls.hIcon = 0;
820     cls.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW);
821     cls.hbrBackground = GetStockObject(WHITE_BRUSH);
822     cls.lpszMenuName = NULL;
823     cls.lpszClassName = "TestParentClass";
824     RegisterClassA(&cls);
825 }
826 
827 START_TEST(button)
828 {
829     ULONG_PTR ctx_cookie;
830     HANDLE hCtx;
831 
832     if (!load_v6_module(&ctx_cookie, &hCtx))
833         return;
834 
835     register_parent_class();
836 
837     init_functions();
838     init_msg_sequences(sequences, NUM_MSG_SEQUENCES);
839 
840     test_button_class();
841     test_button_messages();
842 
843     unload_v6_module(ctx_cookie, hCtx);
844 }
845