1 /* Unit tests for the up-down control
2  *
3  * Copyright 2005 C. Scott Ananian
4  * Copyright (C) 2007 James Hawkins
5  * Copyright (C) 2007 Leslie Choong
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 /* TO TEST:
23  *   - send click messages to the up-down control, check the current position
24  *   - up-down control automatically positions itself next to its buddy window
25  *   - up-down control sets the caption of the buddy window
26  *   - test CreateUpDownControl API
27  *   - check UDS_AUTOBUDDY style, up-down control selects previous window in z-order
28  *   - check UDM_SETBUDDY message
29  *   - check UDM_GETBUDDY message
30  *   - up-down control and buddy control must have the same parent
31  *   - check UDS_ALIGN[LEFT,RIGHT]...check that width of buddy window is decreased
32  *   - check that UDS_SETBUDDYINT sets the caption of the buddy window when it is changed
33  *   - check that the thousands operator is set for large numbers
34  *   - check that the thousands operator is not set with UDS_NOTHOUSANDS
35  *   - check UDS_ARROWKEYS, control subclasses the buddy window so that it processes the keys when it has focus
36  *   - check UDS_HORZ
37  *   - check changing past min/max values
38  *   - check UDS_WRAP wraps values past min/max, incrementing past upper value wraps position to lower value
39  *   - can change control's position, min/max pos, radix
40  *   - check UDM_GETPOS, for up-down control with a buddy window, position is the caption of the buddy window, so change the
41  *     caption of the buddy window then call UDM_GETPOS
42  *   - check UDM_SETRANGE, max can be less than min, so clicking the up arrow decreases the current position
43  *   - more stuff to test
44  */
45 
46 #include <windows.h>
47 #include <commctrl.h>
48 #include <stdio.h>
49 
50 #include "wine/test.h"
51 #include "msg.h"
52 
53 #define expect(EXPECTED,GOT) ok((GOT)==(EXPECTED), "Expected %d, got %d\n", (EXPECTED), (GOT))
54 
55 #define NUM_MSG_SEQUENCES   3
56 #define PARENT_SEQ_INDEX    0
57 #define EDIT_SEQ_INDEX      1
58 #define UPDOWN_SEQ_INDEX    2
59 
60 #define UPDOWN_ID           0
61 #define BUDDY_ID            1
62 
63 static HWND parent_wnd, g_edit;
64 
65 static HWND (WINAPI *pCreateUpDownControl)(DWORD, INT, INT, INT, INT,
66     HWND, INT, HINSTANCE, HWND, INT, INT, INT);
67 static BOOL (WINAPI *pSetWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR, DWORD_PTR);
68 
69 static struct msg_sequence *sequences[NUM_MSG_SEQUENCES];
70 
71 static const struct message add_updown_with_edit_seq[] = {
72     { WM_WINDOWPOSCHANGING, sent },
73     { WM_NCCALCSIZE, sent|wparam, TRUE },
74     { WM_WINDOWPOSCHANGED, sent },
75     { WM_SIZE, sent|wparam|defwinproc, SIZE_RESTORED /*, MAKELONG(91, 75) exact size depends on font */ },
76     { 0 }
77 };
78 
79 static const struct message add_updown_to_parent_seq[] = {
80     { WM_NOTIFYFORMAT, sent|lparam, 0, NF_QUERY },
81     { WM_QUERYUISTATE, sent|optional },
82     { WM_PARENTNOTIFY, sent|wparam, MAKELONG(WM_CREATE, WM_CREATE) },
83     { 0 }
84 };
85 
86 static const struct message get_edit_text_seq[] = {
87     { WM_GETTEXT, sent },
88     { 0 }
89 };
90 
91 static const struct message test_updown_pos_seq[] = {
92     { UDM_SETRANGE, sent|lparam, 0, MAKELONG(100,0) },
93     { UDM_GETRANGE, sent},
94     { UDM_SETPOS, sent|lparam, 0, 5},
95     { UDM_GETPOS, sent},
96     { UDM_SETPOS, sent|lparam, 0, 0},
97     { UDM_GETPOS, sent},
98     { UDM_SETPOS, sent|lparam, 0, MAKELONG(-1,0)},
99     { UDM_GETPOS, sent},
100     { UDM_SETPOS, sent|lparam, 0, 100},
101     { UDM_GETPOS, sent},
102     { UDM_SETPOS, sent|lparam, 0, 101},
103     { UDM_GETPOS, sent},
104     { 0 }
105 };
106 
107 static const struct message test_updown_pos32_seq[] = {
108     { UDM_SETRANGE32, sent|lparam, 0, 1000 },
109     { UDM_GETRANGE32, sent}, /* Cannot check wparam and lparam as they are ptrs */
110     { UDM_SETPOS32, sent|lparam, 0, 500 },
111     { UDM_GETPOS32, sent},
112     { UDM_SETPOS32, sent|lparam, 0, 0 },
113     { UDM_GETPOS32, sent},
114     { UDM_SETPOS32, sent|lparam, 0, -1 },
115     { UDM_GETPOS32, sent},
116     { UDM_SETPOS32, sent|lparam, 0, 1000 },
117     { UDM_GETPOS32, sent},
118     { UDM_SETPOS32, sent|lparam, 0, 1001 },
119     { UDM_GETPOS32, sent},
120     { 0 }
121 };
122 
123 static const struct message test_updown_buddy_seq[] = {
124     { UDM_GETBUDDY, sent },
125     { UDM_SETBUDDY, sent },
126     { WM_STYLECHANGING, sent|defwinproc },
127     { WM_STYLECHANGED, sent|defwinproc },
128     { WM_STYLECHANGING, sent|defwinproc },
129     { WM_STYLECHANGED, sent|defwinproc },
130     { WM_WINDOWPOSCHANGING, sent|defwinproc },
131     { WM_NCCALCSIZE, sent|wparam|optional|defwinproc, 1 },
132     { WM_WINDOWPOSCHANGED, sent|defwinproc },
133     { WM_MOVE, sent|defwinproc },
134     { UDM_GETBUDDY, sent },
135     { 0 }
136 };
137 
138 static const struct message test_updown_base_seq[] = {
139     { UDM_SETBASE, sent|wparam, 10 },
140     { UDM_GETBASE, sent },
141     { UDM_SETBASE, sent|wparam, 80 },
142     { UDM_GETBASE, sent },
143     { UDM_SETBASE, sent|wparam, 16 },
144     { UDM_GETBASE, sent },
145     { UDM_SETBASE, sent|wparam, 80 },
146     { UDM_GETBASE, sent },
147     { UDM_SETBASE, sent|wparam, 10 },
148     { UDM_GETBASE, sent },
149     { 0 }
150 };
151 
152 static const struct message test_updown_unicode_seq[] = {
153     { UDM_SETUNICODEFORMAT, sent|wparam, 0 },
154     { UDM_GETUNICODEFORMAT, sent },
155     { UDM_SETUNICODEFORMAT, sent|wparam, 1 },
156     { UDM_GETUNICODEFORMAT, sent },
157     { UDM_SETUNICODEFORMAT, sent|wparam, 0 },
158     { UDM_GETUNICODEFORMAT, sent },
159     { 0 }
160 };
161 
162 static const struct message test_updown_pos_nochange_seq[] = {
163     { WM_GETTEXT, sent|id, 0, 0, BUDDY_ID },
164     { 0 }
165 };
166 
167 static const struct message test_updown_pos_notifications_seq[] = {
168     { WM_CTLCOLOREDIT, sent|optional },
169     { WM_COMMAND, sent|wparam, MAKELONG(0, EN_SETFOCUS) },
170     { WM_NOTIFY, sent|id, 0, 0, UDN_DELTAPOS },
171     { WM_COMMAND, sent|wparam, MAKELONG(0, EN_UPDATE) },
172     { WM_COMMAND, sent|wparam, MAKELONG(0, EN_CHANGE) },
173     { WM_VSCROLL, sent|wparam, MAKELONG(SB_THUMBPOSITION, 51) },
174     { WM_CTLCOLOREDIT, sent|optional },
175     { WM_VSCROLL, sent|wparam, MAKELONG(SB_ENDSCROLL, 51) },
176     /* no WM_NOTIFY(NM_RELEASEDCAPTURE) message */
177     { 0 }
178 };
179 
180 static const struct message test_updown_pos_notifications_horz_seq[] = {
181     { WM_CTLCOLOREDIT, sent|optional },
182     { WM_COMMAND, sent|wparam, MAKELONG(0, EN_SETFOCUS) },
183     { WM_NOTIFY, sent|id, 0, 0, UDN_DELTAPOS },
184     { WM_COMMAND, sent|wparam, MAKELONG(0, EN_UPDATE) },
185     { WM_COMMAND, sent|wparam, MAKELONG(0, EN_CHANGE) },
186     { WM_HSCROLL, sent|wparam, MAKELONG(SB_THUMBPOSITION, 51) },
187     { WM_CTLCOLOREDIT, sent|optional },
188     { WM_HSCROLL, sent|wparam, MAKELONG(SB_ENDSCROLL, 51) },
189     /* no WM_NOTIFY(NM_RELEASEDCAPTURE) message */
190     { 0 }
191 };
192 
193 static LRESULT WINAPI parent_wnd_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
194 {
195     static LONG defwndproc_counter = 0;
196     struct message msg = { 0 };
197     LRESULT ret;
198 
199     /* log system messages, except for painting */
200     if (message < WM_USER &&
201         message != WM_PAINT &&
202         message != WM_ERASEBKGND &&
203         message != WM_NCPAINT &&
204         message != WM_NCHITTEST &&
205         message != WM_GETTEXT &&
206         message != WM_GETICON &&
207         message != WM_DEVICECHANGE)
208     {
209         msg.message = message;
210         msg.flags = sent|wparam|lparam;
211         if (defwndproc_counter) msg.flags |= defwinproc;
212         msg.wParam = wParam;
213         msg.lParam = lParam;
214         if (message == WM_NOTIFY && lParam)
215             msg.id = ((NMHDR*)lParam)->code;
216         add_message(sequences, PARENT_SEQ_INDEX, &msg);
217     }
218 
219     defwndproc_counter++;
220     ret = DefWindowProcA(hwnd, message, wParam, lParam);
221     defwndproc_counter--;
222 
223     return ret;
224 }
225 
226 static BOOL register_parent_wnd_class(void)
227 {
228     WNDCLASSA cls;
229 
230     cls.style = 0;
231     cls.lpfnWndProc = parent_wnd_proc;
232     cls.cbClsExtra = 0;
233     cls.cbWndExtra = 0;
234     cls.hInstance = GetModuleHandleA(NULL);
235     cls.hIcon = 0;
236     cls.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW);
237     cls.hbrBackground = GetStockObject(WHITE_BRUSH);
238     cls.lpszMenuName = NULL;
239     cls.lpszClassName = "Up-Down test parent class";
240     return RegisterClassA(&cls);
241 }
242 
243 static HWND create_parent_window(void)
244 {
245     if (!register_parent_wnd_class())
246         return NULL;
247 
248     return CreateWindowExA(0, "Up-Down test parent class",
249                           "Up-Down test parent window",
250                           WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX |
251                           WS_MAXIMIZEBOX | WS_VISIBLE,
252                           0, 0, 100, 100,
253                           GetDesktopWindow(), NULL, GetModuleHandleA(NULL), NULL);
254 }
255 
256 static LRESULT WINAPI edit_subclass_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
257 {
258     WNDPROC oldproc = (WNDPROC)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
259     static LONG defwndproc_counter = 0;
260     struct message msg = { 0 };
261     LRESULT ret;
262 
263     msg.message = message;
264     msg.flags = sent|wparam|lparam;
265     if (defwndproc_counter) msg.flags |= defwinproc;
266     msg.wParam = wParam;
267     msg.lParam = lParam;
268     msg.id     = BUDDY_ID;
269     add_message(sequences, EDIT_SEQ_INDEX, &msg);
270 
271     defwndproc_counter++;
272     ret = CallWindowProcA(oldproc, hwnd, message, wParam, lParam);
273     defwndproc_counter--;
274     return ret;
275 }
276 
277 static HWND create_edit_control(void)
278 {
279     WNDPROC oldproc;
280     HWND hwnd;
281     RECT rect;
282 
283     GetClientRect(parent_wnd, &rect);
284     hwnd = CreateWindowExA(0, WC_EDITA, NULL, WS_CHILD | WS_BORDER | WS_VISIBLE,
285                            0, 0, rect.right, rect.bottom,
286                            parent_wnd, NULL, GetModuleHandleA(NULL), NULL);
287     if (!hwnd) return NULL;
288 
289     oldproc = (WNDPROC)SetWindowLongPtrA(hwnd, GWLP_WNDPROC,
290                                          (LONG_PTR)edit_subclass_proc);
291     SetWindowLongPtrA(hwnd, GWLP_USERDATA, (LONG_PTR)oldproc);
292 
293     return hwnd;
294 }
295 
296 static LRESULT WINAPI updown_subclass_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
297 {
298     WNDPROC oldproc = (WNDPROC)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
299     static LONG defwndproc_counter = 0;
300     struct message msg = { 0 };
301     LRESULT ret;
302 
303     msg.message = message;
304     msg.flags = sent|wparam|lparam;
305     if (defwndproc_counter) msg.flags |= defwinproc;
306     msg.wParam = wParam;
307     msg.lParam = lParam;
308     msg.id     = UPDOWN_ID;
309     add_message(sequences, UPDOWN_SEQ_INDEX, &msg);
310 
311     defwndproc_counter++;
312     ret = CallWindowProcA(oldproc, hwnd, message, wParam, lParam);
313     defwndproc_counter--;
314 
315     return ret;
316 }
317 
318 static HWND create_updown_control(DWORD style, HWND buddy)
319 {
320     WNDPROC oldproc;
321     HWND updown;
322     RECT rect;
323 
324     GetClientRect(parent_wnd, &rect);
325     updown = CreateWindowExA(0, UPDOWN_CLASSA, NULL, WS_CHILD | WS_BORDER | WS_VISIBLE | style,
326                            0, 0, rect.right, rect.bottom,
327                            parent_wnd, (HMENU)1, GetModuleHandleA(NULL), NULL);
328     ok(updown != NULL, "Failed to create UpDown control.\n");
329     if (!updown) return NULL;
330 
331     SendMessageA(updown, UDM_SETBUDDY, (WPARAM)buddy, 0);
332     SendMessageA(updown, UDM_SETRANGE, 0, MAKELONG(100, 0));
333     SendMessageA(updown, UDM_SETPOS, 0, MAKELONG(50, 0));
334 
335     oldproc = (WNDPROC)SetWindowLongPtrA(updown, GWLP_WNDPROC,
336                                          (LONG_PTR)updown_subclass_proc);
337     SetWindowLongPtrA(updown, GWLP_USERDATA, (LONG_PTR)oldproc);
338 
339     return updown;
340 }
341 
342 static void test_updown_pos(void)
343 {
344     HWND updown;
345     int r;
346 
347     updown = create_updown_control(UDS_ALIGNRIGHT, g_edit);
348 
349     flush_sequences(sequences, NUM_MSG_SEQUENCES);
350 
351     /* Set Range from 0 to 100 */
352     SendMessageA(updown, UDM_SETRANGE, 0 , MAKELONG(100,0) );
353     r = SendMessageA(updown, UDM_GETRANGE, 0,0);
354     expect(100,LOWORD(r));
355     expect(0,HIWORD(r));
356 
357     /* Set the position to 5, return is not checked as it was set before func call */
358     SendMessageA(updown, UDM_SETPOS, 0 , MAKELONG(5,0) );
359     /* Since UDM_SETBUDDYINT was not set at creation HIWORD(r) will always be 1 as a return from UDM_GETPOS */
360     /* Get the position, which should be 5 */
361     r = SendMessageA(updown, UDM_GETPOS, 0 , 0 );
362     expect(5,LOWORD(r));
363     expect(1,HIWORD(r));
364 
365     /* Set the position to 0, return should be 5 */
366     r = SendMessageA(updown, UDM_SETPOS, 0 , MAKELONG(0,0) );
367     expect(5,r);
368     /* Get the position, which should be 0 */
369     r = SendMessageA(updown, UDM_GETPOS, 0 , 0 );
370     expect(0,LOWORD(r));
371     expect(1,HIWORD(r));
372 
373     /* Set the position to -1, return should be 0 */
374     r = SendMessageA(updown, UDM_SETPOS, 0 , MAKELONG(-1,0) );
375     expect(0,r);
376     /* Get the position, which should be 0 */
377     r = SendMessageA(updown, UDM_GETPOS, 0 , 0 );
378     expect(0,LOWORD(r));
379     expect(1,HIWORD(r));
380 
381     /* Set the position to 100, return should be 0 */
382     r = SendMessageA(updown, UDM_SETPOS, 0 , MAKELONG(100,0) );
383     expect(0,r);
384     /* Get the position, which should be 100 */
385     r = SendMessageA(updown, UDM_GETPOS, 0 , 0 );
386     expect(100,LOWORD(r));
387     expect(1,HIWORD(r));
388 
389     /* Set the position to 101, return should be 100 */
390     r = SendMessageA(updown, UDM_SETPOS, 0 , MAKELONG(101,0) );
391     expect(100,r);
392     /* Get the position, which should be 100 */
393     r = SendMessageA(updown, UDM_GETPOS, 0 , 0 );
394     expect(100,LOWORD(r));
395     expect(1,HIWORD(r));
396 
397     ok_sequence(sequences, UPDOWN_SEQ_INDEX, test_updown_pos_seq , "test updown pos", FALSE);
398 
399     DestroyWindow(updown);
400 
401     /* there's no attempt to update buddy Edit if text didn't change */
402     SetWindowTextA(g_edit, "50");
403     updown = create_updown_control(UDS_ALIGNRIGHT | UDS_SETBUDDYINT | UDS_ARROWKEYS, g_edit);
404 
405     /* test sequence only on 5.8x versions */
406     r = SendMessageA(updown, UDM_GETPOS32, 0, 0);
407     if (r)
408     {
409         UDACCEL accel;
410 
411         flush_sequences(sequences, NUM_MSG_SEQUENCES);
412 
413         r = SendMessageA(updown, UDM_SETPOS, 0, 50);
414         expect(50,r);
415 
416         ok_sequence(sequences, EDIT_SEQ_INDEX, test_updown_pos_nochange_seq,
417                     "test updown pos, no change", FALSE);
418 
419         SendMessageA(updown, UDM_SETRANGE, 0, MAKELONG(1, 40));
420         r = SendMessageA(updown, UDM_GETRANGE, 0, 0);
421         expect(1, LOWORD(r));
422         expect(40, HIWORD(r));
423 
424         accel.nSec = 0;
425         accel.nInc = 5;
426         r = SendMessageA(updown, UDM_SETACCEL, 1, (LPARAM)&accel);
427         expect(TRUE, r);
428 
429         r = SendMessageA(updown, UDM_GETPOS, 0, 0);
430         expect(40, LOWORD(r));
431         expect(1, HIWORD(r));
432 
433         r = SendMessageA(updown, UDM_SETPOS, 0, MAKELONG(0, 0));
434         expect(40, LOWORD(r));
435         expect(0, HIWORD(r));
436 
437         r = SendMessageA(updown, UDM_GETPOS, 0, 0);
438         expect(1, LOWORD(r));
439         expect(0, HIWORD(r));
440 
441         r = SendMessageA(updown, UDM_SETPOS, 0, MAKELONG(2, 0));
442         expect(1, LOWORD(r));
443         expect(0, HIWORD(r));
444 
445         r = SendMessageA(g_edit, WM_KEYDOWN, VK_UP, 0);
446         expect(0, r);
447         r = SendMessageA(updown, UDM_GETPOS, 0, 0);
448         expect(1, LOWORD(r));
449         expect(0, HIWORD(r));
450 
451         r = SendMessageA(updown, UDM_SETPOS, 0, MAKELONG(50, 0));
452         expect(1, LOWORD(r));
453         expect(0, HIWORD(r));
454 
455         r = SendMessageA(updown, UDM_GETPOS, 0, 0);
456         expect(40, LOWORD(r));
457         expect(0, HIWORD(r));
458     }
459 
460     DestroyWindow(updown);
461 }
462 
463 static void test_updown_pos32(void)
464 {
465     HWND updown;
466     int r;
467     int low, high;
468 
469     updown = create_updown_control(UDS_ALIGNRIGHT, g_edit);
470 
471     flush_sequences(sequences, NUM_MSG_SEQUENCES);
472 
473     /* Set the position to 0 to 1000 */
474     SendMessageA(updown, UDM_SETRANGE32, 0 , 1000 );
475 
476     low = high = -1;
477     r = SendMessageA(updown, UDM_GETRANGE32, (WPARAM) &low , (LPARAM) &high );
478     expect(0,r);
479     if (low == -1)
480     {
481         win_skip("UDM_SETRANGE32/UDM_GETRANGE32 not available\n");
482         DestroyWindow(updown);
483         return;
484     }
485 
486     expect(0,low);
487     expect(1000,high);
488 
489     /* Set position to 500 */
490     r = SendMessageA(updown, UDM_SETPOS32, 0 , 500 );
491     if (!r)
492     {
493         win_skip("UDM_SETPOS32 and UDM_GETPOS32 need 5.80\n");
494         DestroyWindow(updown);
495         return;
496     }
497     expect(50,r);
498 
499     /* Since UDM_SETBUDDYINT was not set at creation bRet will always be true as a return from UDM_GETPOS32 */
500 
501     r = SendMessageA(updown, UDM_GETPOS32, 0 , (LPARAM) &high );
502     expect(500,r);
503     expect(1,high);
504 
505     /* Set position to 0, return should be 500 */
506     r = SendMessageA(updown, UDM_SETPOS32, 0 , 0 );
507     expect(500,r);
508     r = SendMessageA(updown, UDM_GETPOS32, 0 , (LPARAM) &high );
509     expect(0,r);
510     expect(1,high);
511 
512     /* Set position to -1 which should become 0, return should be 0 */
513     r = SendMessageA(updown, UDM_SETPOS32, 0 , -1 );
514     expect(0,r);
515     r = SendMessageA(updown, UDM_GETPOS32, 0 , (LPARAM) &high );
516     expect(0,r);
517     expect(1,high);
518 
519     /* Set position to 1000, return should be 0 */
520     r = SendMessageA(updown, UDM_SETPOS32, 0 , 1000 );
521     expect(0,r);
522     r = SendMessageA(updown, UDM_GETPOS32, 0 , (LPARAM) &high );
523     expect(1000,r);
524     expect(1,high);
525 
526     /* Set position to 1001 which should become 1000, return should be 1000 */
527     r = SendMessageA(updown, UDM_SETPOS32, 0 , 1001 );
528     expect(1000,r);
529     r = SendMessageA(updown, UDM_GETPOS32, 0 , (LPARAM) &high );
530     expect(1000,r);
531     expect(1,high);
532 
533     ok_sequence(sequences, UPDOWN_SEQ_INDEX, test_updown_pos32_seq, "test updown pos32", FALSE);
534 
535     DestroyWindow(updown);
536 
537     /* there's no attempt to update buddy Edit if text didn't change */
538     SetWindowTextA(g_edit, "50");
539     updown = create_updown_control(UDS_ALIGNRIGHT | UDS_SETBUDDYINT, g_edit);
540 
541     flush_sequences(sequences, NUM_MSG_SEQUENCES);
542 
543     r = SendMessageA(updown, UDM_SETPOS32, 0, 50);
544     expect(50,r);
545     ok_sequence(sequences, EDIT_SEQ_INDEX, test_updown_pos_nochange_seq,
546                 "test updown pos, no change", FALSE);
547 
548     DestroyWindow(updown);
549 }
550 
551 static void test_updown_buddy(void)
552 {
553     HWND updown, buddyReturn, buddy;
554     RECT rect, rect2;
555     WNDPROC proc;
556     DWORD style;
557 
558     updown = create_updown_control(UDS_ALIGNRIGHT, g_edit);
559 
560     flush_sequences(sequences, NUM_MSG_SEQUENCES);
561 
562     buddyReturn = (HWND)SendMessageA(updown, UDM_GETBUDDY, 0 , 0 );
563     ok(buddyReturn == g_edit, "Expected edit handle\n");
564 
565     buddyReturn = (HWND)SendMessageA(updown, UDM_SETBUDDY, (WPARAM) g_edit, 0);
566     ok(buddyReturn == g_edit, "Expected edit handle\n");
567 
568     buddyReturn = (HWND)SendMessageA(updown, UDM_GETBUDDY, 0 , 0 );
569     ok(buddyReturn == g_edit, "Expected edit handle\n");
570 
571     ok_sequence(sequences, UPDOWN_SEQ_INDEX, test_updown_buddy_seq, "test updown buddy", TRUE);
572     ok_sequence(sequences, EDIT_SEQ_INDEX, add_updown_with_edit_seq, "test updown buddy_edit", FALSE);
573 
574     DestroyWindow(updown);
575 
576     buddy = create_edit_control();
577     proc  = (WNDPROC)GetWindowLongPtrA(buddy, GWLP_WNDPROC);
578 
579     updown= create_updown_control(UDS_ALIGNRIGHT, buddy);
580     ok(proc == (WNDPROC)GetWindowLongPtrA(buddy, GWLP_WNDPROC), "No subclassing expected\n");
581 
582     style = GetWindowLongA(updown, GWL_STYLE);
583     SetWindowLongA(updown, GWL_STYLE, style | UDS_ARROWKEYS);
584     style = GetWindowLongA(updown, GWL_STYLE);
585     ok(style & UDS_ARROWKEYS, "Expected UDS_ARROWKEYS\n");
586     /* no subclass if UDS_ARROWKEYS set after creation */
587     ok(proc == (WNDPROC)GetWindowLongPtrA(buddy, GWLP_WNDPROC), "No subclassing expected\n");
588 
589     DestroyWindow(updown);
590 
591     updown= create_updown_control(UDS_ALIGNRIGHT | UDS_ARROWKEYS, buddy);
592     ok(proc != (WNDPROC)GetWindowLongPtrA(buddy, GWLP_WNDPROC), "Subclassing expected\n");
593 
594     if (pSetWindowSubclass)
595     {
596         /* updown uses subclass helpers for buddy on >5.8x systems */
597         ok(GetPropA(buddy, "CC32SubclassInfo") != NULL, "Expected CC32SubclassInfo property\n");
598     }
599 
600     DestroyWindow(updown);
601     DestroyWindow(buddy);
602 
603     /* Create with buddy and UDS_HORZ, reset buddy. */
604     updown = create_updown_control(UDS_HORZ, g_edit);
605 
606     buddyReturn = (HWND)SendMessageA(updown, UDM_GETBUDDY, 0, 0);
607     ok(buddyReturn == g_edit, "Unexpected buddy window.\n");
608 
609     GetClientRect(updown, &rect);
610 
611     buddyReturn = (HWND)SendMessageA(updown, UDM_SETBUDDY, 0, 0);
612     ok(buddyReturn == g_edit, "Unexpected buddy window.\n");
613 
614     GetClientRect(updown, &rect2);
615     ok(EqualRect(&rect, &rect2), "Unexpected window rect.\n");
616 
617     /* Remove UDS_HORZ, reset buddy again. */
618     style = GetWindowLongA(updown, GWL_STYLE);
619     SetWindowLongA(updown, GWL_STYLE, style & ~UDS_HORZ);
620     style = GetWindowLongA(updown, GWL_STYLE);
621     ok(!(style & UDS_HORZ), "Unexpected style.\n");
622 
623     buddyReturn = (HWND)SendMessageA(updown, UDM_SETBUDDY, 0, 0);
624     ok(buddyReturn == NULL, "Unexpected buddy window.\n");
625 
626     GetClientRect(updown, &rect2);
627     ok(EqualRect(&rect, &rect2), "Unexpected window rect.\n");
628 
629     DestroyWindow(updown);
630 
631     /* Without UDS_HORZ. */
632     updown = create_updown_control(0, g_edit);
633 
634     buddyReturn = (HWND)SendMessageA(updown, UDM_GETBUDDY, 0, 0);
635     ok(buddyReturn == g_edit, "Unexpected buddy window.\n");
636 
637     GetClientRect(updown, &rect);
638 
639     buddyReturn = (HWND)SendMessageA(updown, UDM_SETBUDDY, 0, 0);
640     ok(buddyReturn == g_edit, "Unexpected buddy window.\n");
641 
642     GetClientRect(updown, &rect2);
643     ok(EqualRect(&rect, &rect2), "Unexpected window rect.\n");
644 
645     DestroyWindow(updown);
646 
647     /* Create without buddy. */
648     GetClientRect(parent_wnd, &rect);
649     updown = CreateWindowExA(0, UPDOWN_CLASSA, NULL, WS_CHILD | WS_BORDER | WS_VISIBLE | UDS_HORZ,
650         0, 0, rect.right, rect.bottom, parent_wnd, (HMENU)1, GetModuleHandleA(NULL), NULL);
651     ok(updown != NULL, "Failed to create UpDown control.\n");
652 
653     GetClientRect(updown, &rect);
654     buddyReturn = (HWND)SendMessageA(updown, UDM_SETBUDDY, 0, 0);
655     ok(buddyReturn == NULL, "Unexpected buddy window.\n");
656     GetClientRect(updown, &rect2);
657 
658     ok(EqualRect(&rect, &rect2), "Unexpected window rect.\n");
659 
660     style = GetWindowLongA(updown, GWL_STYLE);
661     SetWindowLongA(updown, GWL_STYLE, style & ~UDS_HORZ);
662 
663     GetClientRect(updown, &rect2);
664     ok(EqualRect(&rect, &rect2), "Unexpected window rect.\n");
665 
666     buddyReturn = (HWND)SendMessageA(updown, UDM_SETBUDDY, (WPARAM)g_edit, 0);
667     ok(buddyReturn == NULL, "Unexpected buddy window.\n");
668     GetClientRect(updown, &rect);
669 
670     buddyReturn = (HWND)SendMessageA(updown, UDM_SETBUDDY, 0, 0);
671     ok(buddyReturn == g_edit, "Unexpected buddy window.\n");
672     GetClientRect(updown, &rect2);
673 todo_wine
674     ok(EqualRect(&rect, &rect2), "Unexpected window rect.\n");
675 
676     DestroyWindow(updown);
677 }
678 
679 static void test_updown_base(void)
680 {
681     HWND updown;
682     int r;
683     CHAR text[10];
684 
685     updown = create_updown_control(UDS_ALIGNRIGHT, g_edit);
686 
687     flush_sequences(sequences, NUM_MSG_SEQUENCES);
688 
689     SendMessageA(updown, UDM_SETBASE, 10 , 0);
690     r = SendMessageA(updown, UDM_GETBASE, 0 , 0);
691     expect(10,r);
692 
693     /* Set base to an invalid value, should return 0 and stay at 10 */
694     r = SendMessageA(updown, UDM_SETBASE, 80 , 0);
695     expect(0,r);
696     r = SendMessageA(updown, UDM_GETBASE, 0 , 0);
697     expect(10,r);
698 
699     /* Set base to 16 now, should get 16 as the return */
700     r = SendMessageA(updown, UDM_SETBASE, 16 , 0);
701     expect(10,r);
702     r = SendMessageA(updown, UDM_GETBASE, 0 , 0);
703     expect(16,r);
704 
705     /* Set base to an invalid value, should return 0 and stay at 16 */
706     r = SendMessageA(updown, UDM_SETBASE, 80 , 0);
707     expect(0,r);
708     r = SendMessageA(updown, UDM_GETBASE, 0 , 0);
709     expect(16,r);
710 
711     /* Set base back to 10, return should be 16 */
712     r = SendMessageA(updown, UDM_SETBASE, 10 , 0);
713     expect(16,r);
714     r = SendMessageA(updown, UDM_GETBASE, 0 , 0);
715     expect(10,r);
716 
717     ok_sequence(sequences, UPDOWN_SEQ_INDEX, test_updown_base_seq, "test updown base", FALSE);
718 
719     DestroyWindow(updown);
720 
721     /* switch base with buddy attached */
722     updown = create_updown_control(UDS_SETBUDDYINT | UDS_ALIGNRIGHT, g_edit);
723 
724     r = SendMessageA(updown, UDM_SETPOS, 0, 10);
725     expect(50, r);
726 
727     GetWindowTextA(g_edit, text, ARRAY_SIZE(text));
728     ok(lstrcmpA(text, "10") == 0, "Expected '10', got '%s'\n", text);
729 
730     r = SendMessageA(updown, UDM_SETBASE, 16, 0);
731     expect(10, r);
732 
733     GetWindowTextA(g_edit, text, ARRAY_SIZE(text));
734     /* FIXME: currently hex output isn't properly formatted, but for this
735        test only change from initial text matters */
736     ok(lstrcmpA(text, "10") != 0, "Expected '0x000A', got '%s'\n", text);
737 
738     DestroyWindow(updown);
739 }
740 
741 static void test_updown_unicode(void)
742 {
743     HWND updown;
744     int r;
745 
746     updown = create_updown_control(UDS_ALIGNRIGHT, g_edit);
747 
748     flush_sequences(sequences, NUM_MSG_SEQUENCES);
749 
750     /* Set it to ANSI, don't check return as we don't know previous state */
751     SendMessageA(updown, UDM_SETUNICODEFORMAT, 0 , 0);
752     r = SendMessageA(updown, UDM_GETUNICODEFORMAT, 0 , 0);
753     expect(0,r);
754 
755     /* Now set it to Unicode format */
756     r = SendMessageA(updown, UDM_SETUNICODEFORMAT, 1 , 0);
757     expect(0,r);
758     r = SendMessageA(updown, UDM_GETUNICODEFORMAT, 0 , 0);
759     if (!r)
760     {
761         win_skip("UDM_SETUNICODEFORMAT not available\n");
762         DestroyWindow(updown);
763         return;
764     }
765     expect(1,r);
766 
767     /* And now set it back to ANSI */
768     r = SendMessageA(updown, UDM_SETUNICODEFORMAT, 0 , 0);
769     expect(1,r);
770     r = SendMessageA(updown, UDM_GETUNICODEFORMAT, 0 , 0);
771     expect(0,r);
772 
773     ok_sequence(sequences, UPDOWN_SEQ_INDEX, test_updown_unicode_seq, "test updown unicode", FALSE);
774 
775     DestroyWindow(updown);
776 }
777 
778 static void test_updown_create(void)
779 {
780     CHAR text[MAX_PATH];
781     HWND updown;
782     RECT r;
783 
784     flush_sequences(sequences, NUM_MSG_SEQUENCES);
785 
786     updown = create_updown_control(UDS_ALIGNRIGHT, g_edit);
787     ok(updown != NULL, "Failed to create updown control\n");
788     ok_sequence(sequences, PARENT_SEQ_INDEX, add_updown_to_parent_seq, "add updown control to parent", TRUE);
789     ok_sequence(sequences, EDIT_SEQ_INDEX, add_updown_with_edit_seq, "add updown control with edit", FALSE);
790 
791     flush_sequences(sequences, NUM_MSG_SEQUENCES);
792 
793     GetWindowTextA(g_edit, text, MAX_PATH);
794     ok(lstrlenA(text) == 0, "Expected empty string\n");
795     ok_sequence(sequences, EDIT_SEQ_INDEX, get_edit_text_seq, "get edit text", FALSE);
796 
797     DestroyWindow(updown);
798 
799     /* create with zero width */
800     updown = CreateWindowA (UPDOWN_CLASSA, 0, WS_CHILD | WS_BORDER | WS_VISIBLE, 0, 0, 0, 0,
801                    parent_wnd, (HMENU)(DWORD_PTR)1, GetModuleHandleA(NULL), 0);
802     ok(updown != NULL, "Failed to create updown control\n");
803     r.right = 0;
804     GetClientRect(updown, &r);
805     ok(r.right > 0, "Expected default width, got %d\n", r.right);
806     DestroyWindow(updown);
807     /* create with really small width */
808     updown = CreateWindowA (UPDOWN_CLASSA, 0, WS_CHILD | WS_BORDER | WS_VISIBLE, 0, 0, 2, 0,
809                    parent_wnd, (HMENU)(DWORD_PTR)1, GetModuleHandleA(NULL), 0);
810     ok(updown != NULL, "Failed to create updown control\n");
811     r.right = 0;
812     GetClientRect(updown, &r);
813     ok(r.right != 2 && r.right > 0, "Expected default width, got %d\n", r.right);
814     DestroyWindow(updown);
815     /* create with width greater than default */
816     updown = CreateWindowA (UPDOWN_CLASSA, 0, WS_CHILD | WS_BORDER | WS_VISIBLE, 0, 0, 100, 0,
817                    parent_wnd, (HMENU)(DWORD_PTR)1, GetModuleHandleA(NULL), 0);
818     ok(updown != NULL, "Failed to create updown control\n");
819     r.right = 0;
820     GetClientRect(updown, &r);
821     ok(r.right < 100 && r.right > 0, "Expected default width, got %d\n", r.right);
822     DestroyWindow(updown);
823     /* create with zero height, UDS_HORZ */
824     updown = CreateWindowA (UPDOWN_CLASSA, 0, UDS_HORZ | WS_CHILD | WS_BORDER | WS_VISIBLE, 0, 0, 0, 0,
825                    parent_wnd, (HMENU)(DWORD_PTR)1, GetModuleHandleA(NULL), 0);
826     ok(updown != NULL, "Failed to create updown control\n");
827     r.bottom = 0;
828     GetClientRect(updown, &r);
829     ok(r.bottom == 0, "Expected zero height, got %d\n", r.bottom);
830     DestroyWindow(updown);
831     /* create with really small height, UDS_HORZ */
832     updown = CreateWindowA (UPDOWN_CLASSA, 0, UDS_HORZ | WS_CHILD | WS_BORDER | WS_VISIBLE, 0, 0, 0, 2,
833                    parent_wnd, (HMENU)(DWORD_PTR)1, GetModuleHandleA(NULL), 0);
834     ok(updown != NULL, "Failed to create updown control\n");
835     r.bottom = 0;
836     GetClientRect(updown, &r);
837     ok(r.bottom == 0, "Expected zero height, got %d\n", r.bottom);
838     DestroyWindow(updown);
839     /* create with height greater than default, UDS_HORZ */
840     updown = CreateWindowA (UPDOWN_CLASSA, 0, UDS_HORZ | WS_CHILD | WS_BORDER | WS_VISIBLE, 0, 0, 0, 100,
841                    parent_wnd, (HMENU)(DWORD_PTR)1, GetModuleHandleA(NULL), 0);
842     ok(updown != NULL, "Failed to create updown control\n");
843     r.bottom = 0;
844     GetClientRect(updown, &r);
845     ok(r.bottom < 100 && r.bottom > 0, "Expected default height, got %d\n", r.bottom);
846     DestroyWindow(updown);
847 }
848 
849 static void test_UDS_SETBUDDYINT(void)
850 {
851     HWND updown;
852     DWORD style, ret;
853     CHAR text[10];
854 
855     /* cleanup buddy */
856     text[0] = '\0';
857     SetWindowTextA(g_edit, text);
858 
859     /* creating without UDS_SETBUDDYINT */
860     updown = create_updown_control(UDS_ALIGNRIGHT, g_edit);
861     /* try to set UDS_SETBUDDYINT after creation */
862     style = GetWindowLongA(updown, GWL_STYLE);
863     SetWindowLongA(updown, GWL_STYLE, style | UDS_SETBUDDYINT);
864     style = GetWindowLongA(updown, GWL_STYLE);
865     ok(style & UDS_SETBUDDYINT, "Expected UDS_SETBUDDY to be set\n");
866     SendMessageA(updown, UDM_SETPOS, 0, 20);
867     GetWindowTextA(g_edit, text, ARRAY_SIZE(text));
868     ok(lstrlenA(text) == 0, "Expected empty string\n");
869     DestroyWindow(updown);
870 
871     /* creating with UDS_SETBUDDYINT */
872     updown = create_updown_control(UDS_SETBUDDYINT | UDS_ALIGNRIGHT, g_edit);
873     GetWindowTextA(g_edit, text, ARRAY_SIZE(text));
874     /* 50 is initial value here */
875     ok(lstrcmpA(text, "50") == 0, "Expected '50', got '%s'\n", text);
876     /* now remove style flag */
877     style = GetWindowLongA(updown, GWL_STYLE);
878     SetWindowLongA(updown, GWL_STYLE, style & ~UDS_SETBUDDYINT);
879     SendMessageA(updown, UDM_SETPOS, 0, 20);
880     GetWindowTextA(g_edit, text, ARRAY_SIZE(text));
881     ok(lstrcmpA(text, "20") == 0, "Expected '20', got '%s'\n", text);
882     /* set edit text directly, check position */
883     strcpy(text, "10");
884     SetWindowTextA(g_edit, text);
885     ret = SendMessageA(updown, UDM_GETPOS, 0, 0);
886     expect(10, ret);
887     strcpy(text, "11");
888     SetWindowTextA(g_edit, text);
889     ret = SendMessageA(updown, UDM_GETPOS, 0, 0);
890     expect(11, LOWORD(ret));
891     expect(0,  HIWORD(ret));
892     /* set to invalid value */
893     strcpy(text, "21st");
894     SetWindowTextA(g_edit, text);
895     ret = SendMessageA(updown, UDM_GETPOS, 0, 0);
896     expect(11, LOWORD(ret));
897     expect(TRUE, HIWORD(ret));
898     /* set style back */
899     style = GetWindowLongA(updown, GWL_STYLE);
900     SetWindowLongA(updown, GWL_STYLE, style | UDS_SETBUDDYINT);
901     SendMessageA(updown, UDM_SETPOS, 0, 30);
902     GetWindowTextA(g_edit, text, ARRAY_SIZE(text));
903     ok(lstrcmpA(text, "30") == 0, "Expected '30', got '%s'\n", text);
904     DestroyWindow(updown);
905 }
906 
907 static void test_CreateUpDownControl(void)
908 {
909     HWND updown, buddy;
910     DWORD range, pos;
911     RECT rect;
912 
913     GetClientRect(parent_wnd, &rect);
914     updown = pCreateUpDownControl(WS_CHILD | WS_BORDER | WS_VISIBLE,
915         0, 0, rect.right, rect.bottom, parent_wnd, 1, GetModuleHandleA(NULL), g_edit, 100, 10, 50);
916     ok(updown != NULL, "Failed to create control.\n");
917 
918     buddy = (HWND)SendMessageA(updown, UDM_GETBUDDY, 0, 0);
919     ok(buddy == g_edit, "Unexpected buddy window.\n");
920 
921     range = SendMessageA(updown, UDM_GETRANGE, 0, 0);
922     ok(range == MAKELONG(100, 10), "Unexpected range.\n");
923 
924     pos = SendMessageA(updown, UDM_GETPOS, 0, 0);
925     ok(pos == MAKELONG(50, 1), "Unexpected position.\n");
926 
927     DestroyWindow(updown);
928 }
929 
930 static void test_updown_pos_notifications(void)
931 {
932     HWND updown;
933     RECT rect;
934     UINT x, y;
935     int result;
936 
937     /* test updown control notifications without UDS_HORZ style */
938     updown = create_updown_control(UDS_ALIGNRIGHT | UDS_SETBUDDYINT, g_edit);
939     SetFocus(updown);
940     flush_sequences(sequences, NUM_MSG_SEQUENCES);
941 
942     /* click on the up-arrow button */
943     GetClientRect(updown, &rect);
944     x = rect.left + (rect.right - rect.left) / 2;
945     y = rect.top + (rect.bottom - rect.top) / 4;
946     result = SendMessageA(updown, WM_LBUTTONDOWN, 0, MAKELPARAM(x, y));
947     expect(result, 0);
948     result = SendMessageA(updown, WM_LBUTTONUP, 0, MAKELPARAM(x, y));
949     expect(result, 0);
950 
951     ok_sequence(sequences, PARENT_SEQ_INDEX, test_updown_pos_notifications_seq,
952                 "test updown to parent notify (vertical)", FALSE);
953 
954     DestroyWindow(updown);
955 
956     /* test updown control notifications with UDS_HORZ style */
957     updown = create_updown_control(UDS_ALIGNRIGHT | UDS_SETBUDDYINT | UDS_HORZ, g_edit);
958     SetFocus(updown);
959     flush_sequences(sequences, NUM_MSG_SEQUENCES);
960 
961     /* click on the right-arrow button */
962     GetClientRect(updown, &rect);
963     x = rect.left + (rect.right - rect.left) * 3 / 4;
964     y = rect.top + (rect.bottom - rect.top) / 2;
965     result = SendMessageA(updown, WM_LBUTTONDOWN, 0, MAKELPARAM(x, y));
966     expect(result, 0);
967     result = SendMessageA(updown, WM_LBUTTONUP, 0, MAKELPARAM(x, y));
968     expect(result, 0);
969 
970     ok_sequence(sequences, PARENT_SEQ_INDEX, test_updown_pos_notifications_horz_seq,
971                 "test updown to parent notify (horizontal)", FALSE);
972 
973     DestroyWindow(updown);
974 }
975 
976 static void init_functions(void)
977 {
978     HMODULE hComCtl32 = LoadLibraryA("comctl32.dll");
979 
980 #define X(f) p##f = (void*)GetProcAddress(hComCtl32, #f);
981 #define X2(f, ord) p##f = (void*)GetProcAddress(hComCtl32, (const char *)ord);
982     X(CreateUpDownControl);
983     X2(SetWindowSubclass, 410);
984 #undef X
985 #undef X2
986 }
987 
988 START_TEST(updown)
989 {
990     init_functions();
991 
992     init_msg_sequences(sequences, NUM_MSG_SEQUENCES);
993 
994     parent_wnd = create_parent_window();
995     ok(parent_wnd != NULL, "Failed to create parent window!\n");
996     g_edit = create_edit_control();
997     ok(g_edit != NULL, "Failed to create edit control\n");
998 
999     test_updown_create();
1000     test_updown_pos();
1001     test_updown_pos32();
1002     test_updown_buddy();
1003     test_updown_base();
1004     test_updown_unicode();
1005     test_UDS_SETBUDDYINT();
1006     test_CreateUpDownControl();
1007     test_updown_pos_notifications();
1008 
1009     DestroyWindow(g_edit);
1010     DestroyWindow(parent_wnd);
1011 }
1012