xref: /reactos/dll/win32/comctl32/updown.c (revision 84344399)
1 /*
2  * Updown control
3  *
4  * Copyright 1997, 2002 Dimitrie O. Paun
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #include <assert.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 
27 #include "windef.h"
28 #include "winbase.h"
29 #include "wingdi.h"
30 #include "winuser.h"
31 #include "winnls.h"
32 #include "commctrl.h"
33 #include "comctl32.h"
34 #include "uxtheme.h"
35 #include "vssym32.h"
36 #include "wine/heap.h"
37 #include "wine/debug.h"
38 
39 WINE_DEFAULT_DEBUG_CHANNEL(updown);
40 
41 typedef struct
42 {
43     HWND      Self;            /* Handle to this up-down control */
44     HWND      Notify;          /* Handle to the parent window */
45     DWORD     dwStyle;         /* The GWL_STYLE for this window */
46     UINT      AccelCount;      /* Number of elements in AccelVect */
47     UDACCEL*  AccelVect;       /* Vector containing AccelCount elements */
48     INT       AccelIndex;      /* Current accel index, -1 if not accel'ing */
49     INT       Base;            /* Base to display nr in the buddy window */
50     INT       CurVal;          /* Current up-down value */
51     INT       MinVal;          /* Minimum up-down value */
52     INT       MaxVal;          /* Maximum up-down value */
53     HWND      Buddy;           /* Handle to the buddy window */
54     INT       BuddyType;       /* Remembers the buddy type BUDDY_TYPE_* */
55     INT       Flags;           /* Internal Flags FLAG_* */
56     BOOL      UnicodeFormat;   /* Marks the use of Unicode internally */
57 } UPDOWN_INFO;
58 
59 /* Control configuration constants */
60 
61 #define INITIAL_DELAY	500    /* initial timer until auto-inc kicks in */
62 #define AUTOPRESS_DELAY	250    /* time to keep arrow pressed on KEY_DOWN */
63 #define REPEAT_DELAY	50     /* delay between auto-increments */
64 
65 #define DEFAULT_WIDTH	    16 /* default width of the ctrl */
66 #define DEFAULT_XSEP         0 /* default separation between buddy and ctrl */
67 #define DEFAULT_ADDTOP       0 /* amount to extend above the buddy window */
68 #define DEFAULT_ADDBOT       0 /* amount to extend below the buddy window */
69 #define DEFAULT_BUDDYBORDER  2 /* Width/height of the buddy border */
70 #define DEFAULT_BUDDYSPACER  2 /* Spacer between the buddy and the ctrl */
71 #define DEFAULT_BUDDYBORDER_THEMED  1 /* buddy border when theming is enabled */
72 #define DEFAULT_BUDDYSPACER_THEMED  0 /* buddy spacer when theming is enabled */
73 
74 /* Work constants */
75 
76 #define FLAG_INCR	0x01
77 #define FLAG_DECR	0x02
78 #define FLAG_MOUSEIN	0x04
79 #define FLAG_PRESSED	0x08
80 #define FLAG_BUDDYINT	0x10 /* UDS_SETBUDDYINT was set on creation */
81 #define FLAG_ARROW	(FLAG_INCR | FLAG_DECR)
82 
83 #define BUDDY_TYPE_UNKNOWN 0
84 #define BUDDY_TYPE_LISTBOX 1
85 #define BUDDY_TYPE_EDIT    2
86 
87 #define TIMER_AUTOREPEAT   1
88 #define TIMER_ACCEL        2
89 #define TIMER_AUTOPRESS    3
90 
91 #define UPDOWN_GetInfoPtr(hwnd) ((UPDOWN_INFO *)GetWindowLongPtrW (hwnd,0))
92 
93 /* id used for SetWindowSubclass */
94 #define BUDDY_SUBCLASSID   1
95 
96 static void UPDOWN_DoAction (UPDOWN_INFO *infoPtr, int delta, int action);
97 
98 /***********************************************************************
99  *           UPDOWN_IsBuddyEdit
100  * Tests if our buddy is an edit control.
101  */
102 static inline BOOL UPDOWN_IsBuddyEdit(const UPDOWN_INFO *infoPtr)
103 {
104     return infoPtr->BuddyType == BUDDY_TYPE_EDIT;
105 }
106 
107 /***********************************************************************
108  *           UPDOWN_IsBuddyListbox
109  * Tests if our buddy is a listbox control.
110  */
111 static inline BOOL UPDOWN_IsBuddyListbox(const UPDOWN_INFO *infoPtr)
112 {
113     return infoPtr->BuddyType == BUDDY_TYPE_LISTBOX;
114 }
115 
116 /***********************************************************************
117  *           UPDOWN_InBounds
118  * Tests if a given value 'val' is between the Min&Max limits
119  */
120 static BOOL UPDOWN_InBounds(const UPDOWN_INFO *infoPtr, int val)
121 {
122     if(infoPtr->MaxVal > infoPtr->MinVal)
123         return (infoPtr->MinVal <= val) && (val <= infoPtr->MaxVal);
124     else
125         return (infoPtr->MaxVal <= val) && (val <= infoPtr->MinVal);
126 }
127 
128 /***********************************************************************
129  *           UPDOWN_OffsetVal
130  * Change the current value by delta.
131  * It returns TRUE is the value was changed successfully, or FALSE
132  * if the value was not changed, as it would go out of bounds.
133  */
134 static BOOL UPDOWN_OffsetVal(UPDOWN_INFO *infoPtr, int delta)
135 {
136     /* check if we can do the modification first */
137     if(!UPDOWN_InBounds (infoPtr, infoPtr->CurVal+delta)) {
138         if (infoPtr->dwStyle & UDS_WRAP) {
139             delta += (delta < 0 ? -1 : 1) *
140 		     (infoPtr->MaxVal < infoPtr->MinVal ? -1 : 1) *
141 		     (infoPtr->MinVal - infoPtr->MaxVal) +
142 		     (delta < 0 ? 1 : -1);
143         } else if ((infoPtr->MaxVal > infoPtr->MinVal && infoPtr->CurVal+delta > infoPtr->MaxVal)
144                 || (infoPtr->MaxVal < infoPtr->MinVal && infoPtr->CurVal+delta < infoPtr->MaxVal)) {
145             delta = infoPtr->MaxVal - infoPtr->CurVal;
146         } else {
147             delta = infoPtr->MinVal - infoPtr->CurVal;
148         }
149     }
150 
151     infoPtr->CurVal += delta;
152     return delta != 0;
153 }
154 
155 /***********************************************************************
156  * UPDOWN_HasBuddyBorder
157  *
158  * When we have a buddy set and that we are aligned on our buddy, we
159  * want to draw a sunken edge to make like we are part of that control.
160  */
161 static BOOL UPDOWN_HasBuddyBorder(const UPDOWN_INFO *infoPtr)
162 {
163     return  ( ((infoPtr->dwStyle & (UDS_ALIGNLEFT | UDS_ALIGNRIGHT)) != 0) &&
164 	      UPDOWN_IsBuddyEdit(infoPtr) );
165 }
166 
167 /***********************************************************************
168  *           UPDOWN_GetArrowRect
169  * wndPtr   - pointer to the up-down wnd
170  * rect     - will hold the rectangle
171  * arrow    - FLAG_INCR to get the "increment" rect (up or right)
172  *            FLAG_DECR to get the "decrement" rect (down or left)
173  */
174 static void UPDOWN_GetArrowRect (const UPDOWN_INFO* infoPtr, RECT *rect, unsigned int arrow)
175 {
176     HTHEME theme = GetWindowTheme (infoPtr->Self);
177     const int border = theme ? DEFAULT_BUDDYBORDER_THEMED : DEFAULT_BUDDYBORDER;
178     const int spacer = theme ? DEFAULT_BUDDYSPACER_THEMED : DEFAULT_BUDDYSPACER;
179     int size;
180 
181     assert(arrow && (arrow & (FLAG_INCR | FLAG_DECR)) != (FLAG_INCR | FLAG_DECR));
182 
183     GetClientRect (infoPtr->Self, rect);
184 
185     /*
186      * Make sure we calculate the rectangle to fit even if we draw the
187      * border.
188      */
189     if (UPDOWN_HasBuddyBorder(infoPtr)) {
190         if (infoPtr->dwStyle & UDS_ALIGNLEFT)
191             rect->left += border;
192         else
193             rect->right -= border;
194 
195         InflateRect(rect, 0, -border);
196     }
197 
198     /* now figure out if we need a space away from the buddy */
199     if (IsWindow(infoPtr->Buddy) ) {
200 	if (infoPtr->dwStyle & UDS_ALIGNLEFT) rect->right -= spacer;
201 	else if (infoPtr->dwStyle & UDS_ALIGNRIGHT) rect->left += spacer;
202     }
203 
204     /*
205      * We're calculating the midpoint to figure-out where the
206      * separation between the buttons will lay.
207      */
208     if (infoPtr->dwStyle & UDS_HORZ) {
209         size = (rect->right - rect->left) / 2;
210         if (arrow & FLAG_INCR)
211             rect->left = rect->right - size;
212         else if (arrow & FLAG_DECR)
213             rect->right = rect->left + size;
214     } else {
215         size = (rect->bottom - rect->top) / 2;
216         if (arrow & FLAG_INCR)
217             rect->bottom = rect->top + size;
218         else if (arrow & FLAG_DECR)
219             rect->top = rect->bottom - size;
220     }
221 }
222 
223 /***********************************************************************
224  *           UPDOWN_GetArrowFromPoint
225  * Returns the rectagle (for the up or down arrow) that contains pt.
226  * If it returns the up rect, it returns FLAG_INCR.
227  * If it returns the down rect, it returns FLAG_DECR.
228  */
229 static INT UPDOWN_GetArrowFromPoint (const UPDOWN_INFO *infoPtr, RECT *rect, POINT pt)
230 {
231     UPDOWN_GetArrowRect (infoPtr, rect, FLAG_INCR);
232     if(PtInRect(rect, pt)) return FLAG_INCR;
233 
234     UPDOWN_GetArrowRect (infoPtr, rect, FLAG_DECR);
235     if(PtInRect(rect, pt)) return FLAG_DECR;
236 
237     return 0;
238 }
239 
240 
241 /***********************************************************************
242  *           UPDOWN_GetThousandSep
243  * Returns the thousand sep. If an error occurs, it returns ','.
244  */
245 static WCHAR UPDOWN_GetThousandSep(void)
246 {
247     WCHAR sep[2];
248 
249     if(GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, sep, 2) != 1)
250         sep[0] = ',';
251 
252     return sep[0];
253 }
254 
255 /***********************************************************************
256  *           UPDOWN_GetBuddyInt
257  * Tries to read the pos from the buddy window and if it succeeds,
258  * it stores it in the control's CurVal
259  * returns:
260  *   TRUE  - if it read the integer from the buddy successfully
261  *   FALSE - if an error occurred
262  */
263 static BOOL UPDOWN_GetBuddyInt (UPDOWN_INFO *infoPtr)
264 {
265     WCHAR txt[20], sep, *src, *dst;
266     int newVal;
267 
268     if (!((infoPtr->Flags & FLAG_BUDDYINT) && IsWindow(infoPtr->Buddy)))
269         return FALSE;
270 
271     /*if the buddy is a list window, we must set curr index */
272     if (UPDOWN_IsBuddyListbox(infoPtr)) {
273         newVal = SendMessageW(infoPtr->Buddy, LB_GETCARETINDEX, 0, 0);
274         if(newVal < 0) return FALSE;
275     } else {
276         /* we have a regular window, so will get the text */
277         /* note that a zero-length string is a legitimate value for 'txt',
278          * and ought to result in a successful conversion to '0'. */
279         if (GetWindowTextW(infoPtr->Buddy, txt, ARRAY_SIZE(txt)) < 0)
280             return FALSE;
281 
282         sep = UPDOWN_GetThousandSep();
283 
284         /* now get rid of the separators */
285         for(src = dst = txt; *src; src++)
286             if(*src != sep) *dst++ = *src;
287         *dst = 0;
288 
289         /* try to convert the number and validate it */
290         newVal = wcstol(txt, &src, infoPtr->Base);
291         if(*src || !UPDOWN_InBounds (infoPtr, newVal)) return FALSE;
292     }
293 
294     TRACE("new value(%d) from buddy (old=%d)\n", newVal, infoPtr->CurVal);
295     infoPtr->CurVal = newVal;
296     return TRUE;
297 }
298 
299 
300 /***********************************************************************
301  *           UPDOWN_SetBuddyInt
302  * Tries to set the pos to the buddy window based on current pos
303  * returns:
304  *   TRUE  - if it set the caption of the  buddy successfully
305  *   FALSE - if an error occurred
306  */
307 static BOOL UPDOWN_SetBuddyInt (const UPDOWN_INFO *infoPtr)
308 {
309     static const WCHAR fmt_hex[] = { '0', 'x', '%', '0', '4', 'X', 0 };
310     static const WCHAR fmt_dec_oct[] = { '%', 'd', '\0' };
311     const WCHAR *fmt;
312     WCHAR txt[20], txt_old[20] = { 0 };
313     int len;
314 
315     if (!((infoPtr->Flags & FLAG_BUDDYINT) && IsWindow(infoPtr->Buddy)))
316         return FALSE;
317 
318     TRACE("set new value(%d) to buddy.\n", infoPtr->CurVal);
319 
320     /*if the buddy is a list window, we must set curr index */
321     if (UPDOWN_IsBuddyListbox(infoPtr)) {
322         return SendMessageW(infoPtr->Buddy, LB_SETCURSEL, infoPtr->CurVal, 0) != LB_ERR;
323     }
324 
325     /* Regular window, so set caption to the number */
326     fmt = (infoPtr->Base == 16) ? fmt_hex : fmt_dec_oct;
327     len = wsprintfW(txt, fmt, infoPtr->CurVal);
328 
329 
330     /* Do thousands separation if necessary */
331     if ((infoPtr->Base == 10) && !(infoPtr->dwStyle & UDS_NOTHOUSANDS) && (len > 3)) {
332         WCHAR tmp[ARRAY_SIZE(txt)], *src = tmp, *dst = txt;
333         WCHAR sep = UPDOWN_GetThousandSep();
334 	int start = len % 3;
335 
336 	memcpy(tmp, txt, sizeof(txt));
337 	if (start == 0) start = 3;
338 	dst += start;
339 	src += start;
340         for (len=0; *src; len++) {
341 	    if (len % 3 == 0) *dst++ = sep;
342 	    *dst++ = *src++;
343         }
344         *dst = 0;
345     }
346 
347     /* if nothing changed exit earlier */
348     GetWindowTextW(infoPtr->Buddy, txt_old, ARRAY_SIZE(txt_old));
349     if (lstrcmpiW(txt_old, txt) == 0) return FALSE;
350 
351     return SetWindowTextW(infoPtr->Buddy, txt);
352 }
353 
354 /***********************************************************************
355  * UPDOWN_DrawBuddyBackground
356  *
357  * Draw buddy background for visual integration.
358  */
359 static BOOL UPDOWN_DrawBuddyBackground (const UPDOWN_INFO *infoPtr, HDC hdc)
360 {
361     RECT br, r;
362     HTHEME buddyTheme = GetWindowTheme (infoPtr->Buddy);
363     if (!buddyTheme) return FALSE;
364 
365     GetWindowRect (infoPtr->Buddy, &br);
366     MapWindowPoints (NULL, infoPtr->Self, (POINT*)&br, 2);
367     GetClientRect (infoPtr->Self, &r);
368 
369     if (infoPtr->dwStyle & UDS_ALIGNLEFT)
370         br.left = r.left;
371     else if (infoPtr->dwStyle & UDS_ALIGNRIGHT)
372         br.right = r.right;
373     /* FIXME: take disabled etc. into account */
374     DrawThemeBackground (buddyTheme, hdc, 0, 0, &br, NULL);
375     return TRUE;
376 }
377 
378 /***********************************************************************
379  * UPDOWN_Draw
380  *
381  * Draw the arrows. The background need not be erased.
382  */
383 static LRESULT UPDOWN_Draw (const UPDOWN_INFO *infoPtr, HDC hdc)
384 {
385     BOOL uPressed, uHot, dPressed, dHot;
386     RECT rect;
387     HTHEME theme = GetWindowTheme (infoPtr->Self);
388     int uPart = 0, uState = 0, dPart = 0, dState = 0;
389     BOOL needBuddyBg = FALSE;
390 
391     uPressed = (infoPtr->Flags & FLAG_PRESSED) && (infoPtr->Flags & FLAG_INCR);
392     uHot = (infoPtr->Flags & FLAG_INCR) && (infoPtr->Flags & FLAG_MOUSEIN);
393     dPressed = (infoPtr->Flags & FLAG_PRESSED) && (infoPtr->Flags & FLAG_DECR);
394     dHot = (infoPtr->Flags & FLAG_DECR) && (infoPtr->Flags & FLAG_MOUSEIN);
395     if (theme) {
396         uPart = (infoPtr->dwStyle & UDS_HORZ) ? SPNP_UPHORZ : SPNP_UP;
397         uState = (infoPtr->dwStyle & WS_DISABLED) ? DNS_DISABLED
398             : (uPressed ? DNS_PRESSED : (uHot ? DNS_HOT : DNS_NORMAL));
399         dPart = (infoPtr->dwStyle & UDS_HORZ) ? SPNP_DOWNHORZ : SPNP_DOWN;
400         dState = (infoPtr->dwStyle & WS_DISABLED) ? DNS_DISABLED
401             : (dPressed ? DNS_PRESSED : (dHot ? DNS_HOT : DNS_NORMAL));
402         needBuddyBg = IsWindow (infoPtr->Buddy)
403             && (IsThemeBackgroundPartiallyTransparent (theme, uPart, uState)
404               || IsThemeBackgroundPartiallyTransparent (theme, dPart, dState));
405     }
406 
407     /* Draw the common border between ourselves and our buddy */
408     if (UPDOWN_HasBuddyBorder(infoPtr) || needBuddyBg) {
409         if (!theme || !UPDOWN_DrawBuddyBackground (infoPtr, hdc)) {
410             GetClientRect(infoPtr->Self, &rect);
411 	    DrawEdge(hdc, &rect, EDGE_SUNKEN,
412 		     BF_BOTTOM | BF_TOP |
413 		     (infoPtr->dwStyle & UDS_ALIGNLEFT ? BF_LEFT : BF_RIGHT));
414         }
415     }
416 
417     /* Draw the incr button */
418     UPDOWN_GetArrowRect (infoPtr, &rect, FLAG_INCR);
419     if (theme) {
420         DrawThemeBackground(theme, hdc, uPart, uState, &rect, NULL);
421     } else {
422         DrawFrameControl(hdc, &rect, DFC_SCROLL,
423             (infoPtr->dwStyle & UDS_HORZ ? DFCS_SCROLLRIGHT : DFCS_SCROLLUP) |
424             ((infoPtr->dwStyle & UDS_HOTTRACK) && uHot ? DFCS_HOT : 0) |
425             (uPressed ? DFCS_PUSHED : 0) |
426             (infoPtr->dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) );
427     }
428 
429     /* Draw the decr button */
430     UPDOWN_GetArrowRect(infoPtr, &rect, FLAG_DECR);
431     if (theme) {
432         DrawThemeBackground(theme, hdc, dPart, dState, &rect, NULL);
433     } else {
434         DrawFrameControl(hdc, &rect, DFC_SCROLL,
435             (infoPtr->dwStyle & UDS_HORZ ? DFCS_SCROLLLEFT : DFCS_SCROLLDOWN) |
436             ((infoPtr->dwStyle & UDS_HOTTRACK) && dHot ? DFCS_HOT : 0) |
437             (dPressed ? DFCS_PUSHED : 0) |
438             (infoPtr->dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) );
439     }
440 
441     return 0;
442 }
443 
444 /***********************************************************************
445  * UPDOWN_Paint
446  *
447  * Asynchronous drawing (must ONLY be used in WM_PAINT).
448  * Calls UPDOWN_Draw.
449  */
450 static LRESULT UPDOWN_Paint (const UPDOWN_INFO *infoPtr, HDC hdc)
451 {
452     PAINTSTRUCT ps;
453     if (hdc) return UPDOWN_Draw (infoPtr, hdc);
454     hdc = BeginPaint (infoPtr->Self, &ps);
455     UPDOWN_Draw (infoPtr, hdc);
456     EndPaint (infoPtr->Self, &ps);
457     return 0;
458 }
459 
460 /***********************************************************************
461  * UPDOWN_KeyPressed
462  *
463  * Handle key presses (up & down) when we have to do so
464  */
465 static LRESULT UPDOWN_KeyPressed(UPDOWN_INFO *infoPtr, int key)
466 {
467     int arrow, accel;
468 
469     if (key == VK_UP) arrow = FLAG_INCR;
470     else if (key == VK_DOWN) arrow = FLAG_DECR;
471     else return 1;
472 
473     UPDOWN_GetBuddyInt (infoPtr);
474     infoPtr->Flags &= ~FLAG_ARROW;
475     infoPtr->Flags |= FLAG_PRESSED | arrow;
476     InvalidateRect (infoPtr->Self, NULL, FALSE);
477     SetTimer(infoPtr->Self, TIMER_AUTOPRESS, AUTOPRESS_DELAY, 0);
478     accel = (infoPtr->AccelCount && infoPtr->AccelVect) ? infoPtr->AccelVect[0].nInc : 1;
479     UPDOWN_DoAction (infoPtr, accel, arrow);
480     return 0;
481 }
482 
483 static int UPDOWN_GetPos(UPDOWN_INFO *infoPtr, BOOL *err)
484 {
485     BOOL succ = UPDOWN_GetBuddyInt(infoPtr);
486     int val = infoPtr->CurVal;
487 
488     if(!UPDOWN_InBounds(infoPtr, val)) {
489         if((infoPtr->MinVal < infoPtr->MaxVal && val < infoPtr->MinVal)
490                 || (infoPtr->MinVal > infoPtr->MaxVal && val > infoPtr->MinVal))
491             val = infoPtr->MinVal;
492         else
493             val = infoPtr->MaxVal;
494 
495         succ = FALSE;
496     }
497 
498     if(err) *err = !succ;
499     return val;
500 }
501 
502 static int UPDOWN_SetPos(UPDOWN_INFO *infoPtr, int pos)
503 {
504     int ret = infoPtr->CurVal;
505 
506     if(!UPDOWN_InBounds(infoPtr, pos)) {
507         if((infoPtr->MinVal < infoPtr->MaxVal && pos < infoPtr->MinVal)
508                 || (infoPtr->MinVal > infoPtr->MaxVal && pos > infoPtr->MinVal))
509             pos = infoPtr->MinVal;
510         else
511             pos = infoPtr->MaxVal;
512     }
513 
514     infoPtr->CurVal = pos;
515     UPDOWN_SetBuddyInt(infoPtr);
516 
517     if(!UPDOWN_InBounds(infoPtr, ret)) {
518         if((infoPtr->MinVal < infoPtr->MaxVal && ret < infoPtr->MinVal)
519                 || (infoPtr->MinVal > infoPtr->MaxVal && ret > infoPtr->MinVal))
520             ret = infoPtr->MinVal;
521         else
522             ret = infoPtr->MaxVal;
523     }
524     return ret;
525 }
526 
527 
528 /***********************************************************************
529  * UPDOWN_SetRange
530  *
531  * Handle UDM_SETRANGE, UDM_SETRANGE32
532  *
533  * FIXME: handle Max == Min properly:
534  *        - arrows should be disabled (without WS_DISABLED set),
535  *          visually they can't be pressed and don't respond;
536  *        - all input messages should still pass in.
537  */
538 static LRESULT UPDOWN_SetRange(UPDOWN_INFO *infoPtr, INT Max, INT Min)
539 {
540     infoPtr->MaxVal = Max;
541     infoPtr->MinVal = Min;
542 
543     TRACE("UpDown Ctrl new range(%d to %d), hwnd=%p\n",
544            infoPtr->MinVal, infoPtr->MaxVal, infoPtr->Self);
545 
546     return 0;
547 }
548 
549 /***********************************************************************
550  * UPDOWN_MouseWheel
551  *
552  * Handle mouse wheel scrolling
553  */
554 static LRESULT UPDOWN_MouseWheel(UPDOWN_INFO *infoPtr, WPARAM wParam)
555 {
556     int iWheelDelta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;
557 
558     if (wParam & (MK_SHIFT | MK_CONTROL))
559         return 0;
560 
561     if (iWheelDelta != 0)
562     {
563         UPDOWN_GetBuddyInt(infoPtr);
564         UPDOWN_DoAction(infoPtr, abs(iWheelDelta), iWheelDelta > 0 ? FLAG_INCR : FLAG_DECR);
565     }
566 
567     return 1;
568 }
569 
570 
571 /***********************************************************************
572  * UPDOWN_Buddy_SubclassProc used to handle messages sent to the buddy
573  *                           control.
574  */
575 static LRESULT CALLBACK
576 UPDOWN_Buddy_SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
577                           UINT_PTR uId, DWORD_PTR ref_data)
578 {
579     UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr((HWND)ref_data);
580 
581     TRACE("hwnd=%p, uMsg=%04x, wParam=%08lx, lParam=%08lx\n",
582           hwnd, uMsg, wParam, lParam);
583 
584     switch(uMsg)
585     {
586     case WM_KEYDOWN:
587         if (infoPtr)
588         {
589             UPDOWN_KeyPressed(infoPtr, (int)wParam);
590             if (wParam == VK_UP || wParam == VK_DOWN)
591                 return 0;
592         }
593         break;
594 
595     case WM_MOUSEWHEEL:
596         if (infoPtr)
597             UPDOWN_MouseWheel(infoPtr, (int)wParam);
598         break;
599 
600     case WM_NCDESTROY:
601         RemoveWindowSubclass(hwnd, UPDOWN_Buddy_SubclassProc, BUDDY_SUBCLASSID);
602         break;
603     default:
604 	break;
605     }
606 
607     return DefSubclassProc(hwnd, uMsg, wParam, lParam);
608 }
609 
610 static void UPDOWN_ResetSubclass (UPDOWN_INFO *infoPtr)
611 {
612     SetWindowSubclass(infoPtr->Buddy, UPDOWN_Buddy_SubclassProc, BUDDY_SUBCLASSID, 0);
613 }
614 
615 /***********************************************************************
616  *           UPDOWN_SetBuddy
617  *
618  * Sets bud as a new Buddy.
619  * Then, it should subclass the buddy
620  * If window has the UDS_ARROWKEYS, it subclasses the buddy window to
621  * process the UP/DOWN arrow keys.
622  * If window has the UDS_ALIGNLEFT or UDS_ALIGNRIGHT style
623  * the size/pos of the buddy and the control are adjusted accordingly.
624  */
625 static HWND UPDOWN_SetBuddy (UPDOWN_INFO* infoPtr, HWND bud)
626 {
627     RECT  budRect;  /* new coord for the buddy */
628     int   x, width;  /* new x position and width for the up-down */
629     WCHAR buddyClass[40];
630     HWND old_buddy;
631 
632     TRACE("(hwnd=%p, bud=%p)\n", infoPtr->Self, bud);
633 
634     old_buddy = infoPtr->Buddy;
635 
636     UPDOWN_ResetSubclass (infoPtr);
637 
638     if (!IsWindow(bud)) bud = NULL;
639 
640     /* Store buddy window handle */
641     infoPtr->Buddy = bud;
642 
643     if(bud) {
644         /* Store buddy window class type */
645         infoPtr->BuddyType = BUDDY_TYPE_UNKNOWN;
646         if (GetClassNameW(bud, buddyClass, ARRAY_SIZE(buddyClass))) {
647             if (lstrcmpiW(buddyClass, WC_EDITW) == 0)
648                 infoPtr->BuddyType = BUDDY_TYPE_EDIT;
649             else if (lstrcmpiW(buddyClass, WC_LISTBOXW) == 0)
650                 infoPtr->BuddyType = BUDDY_TYPE_LISTBOX;
651         }
652 
653         if (infoPtr->dwStyle & UDS_ARROWKEYS)
654             SetWindowSubclass(bud, UPDOWN_Buddy_SubclassProc, BUDDY_SUBCLASSID,
655                               (DWORD_PTR)infoPtr->Self);
656 
657         /* Get the rect of the buddy relative to its parent */
658         GetWindowRect(infoPtr->Buddy, &budRect);
659         MapWindowPoints(HWND_DESKTOP, GetParent(infoPtr->Buddy), (POINT *)(&budRect.left), 2);
660 
661         /* now do the positioning */
662         if  (infoPtr->dwStyle & UDS_ALIGNLEFT) {
663             x  = budRect.left;
664             budRect.left += DEFAULT_WIDTH + DEFAULT_XSEP;
665         } else if (infoPtr->dwStyle & UDS_ALIGNRIGHT) {
666             budRect.right -= DEFAULT_WIDTH + DEFAULT_XSEP;
667             x  = budRect.right+DEFAULT_XSEP;
668         } else {
669             /* nothing to do */
670             return old_buddy;
671         }
672 
673         /* first adjust the buddy to accommodate the up/down */
674         SetWindowPos(infoPtr->Buddy, 0, budRect.left, budRect.top,
675                      budRect.right  - budRect.left, budRect.bottom - budRect.top,
676                      SWP_NOACTIVATE|SWP_NOZORDER);
677 
678         /* now position the up/down */
679         /* Since the UDS_ALIGN* flags were used, */
680         /* we will pick the position and size of the window. */
681         width = DEFAULT_WIDTH;
682 
683         /*
684          * If the updown has a buddy border, it has to overlap with the buddy
685          * to look as if it is integrated with the buddy control.
686          * We nudge the control or change its size to overlap.
687          */
688         if (UPDOWN_HasBuddyBorder(infoPtr)) {
689             if(infoPtr->dwStyle & UDS_ALIGNLEFT)
690                 width += DEFAULT_BUDDYBORDER;
691             else
692                 x -= DEFAULT_BUDDYBORDER;
693         }
694 
695         SetWindowPos(infoPtr->Self, 0, x,
696                      budRect.top - DEFAULT_ADDTOP, width,
697                      budRect.bottom - budRect.top + DEFAULT_ADDTOP + DEFAULT_ADDBOT,
698                      SWP_NOACTIVATE|SWP_FRAMECHANGED|SWP_NOZORDER);
699     } else if (!(infoPtr->dwStyle & UDS_HORZ) && old_buddy != NULL) {
700         RECT rect;
701         GetWindowRect(infoPtr->Self, &rect);
702         MapWindowPoints(HWND_DESKTOP, GetParent(infoPtr->Self), (POINT *)&rect, 2);
703         SetWindowPos(infoPtr->Self, 0, rect.left, rect.top, DEFAULT_WIDTH, rect.bottom - rect.top,
704                      SWP_NOACTIVATE|SWP_FRAMECHANGED|SWP_NOZORDER);
705     }
706 
707     return old_buddy;
708 }
709 
710 /***********************************************************************
711  *           UPDOWN_DoAction
712  *
713  * This function increments/decrements the CurVal by the
714  * 'delta' amount according to the 'action' flag which can be a
715  * combination of FLAG_INCR and FLAG_DECR
716  * It notifies the parent as required.
717  * It handles wrapping and non-wrapping correctly.
718  * It is assumed that delta>0
719  */
720 static void UPDOWN_DoAction (UPDOWN_INFO *infoPtr, int delta, int action)
721 {
722     NM_UPDOWN ni;
723 
724     TRACE("%d by %d\n", action, delta);
725 
726     /* check if we can do the modification first */
727     delta *= (action & FLAG_INCR ? 1 : -1) * (infoPtr->MaxVal < infoPtr->MinVal ? -1 : 1);
728     if ( (action & FLAG_INCR) && (action & FLAG_DECR) ) delta = 0;
729 
730     TRACE("current %d, delta: %d\n", infoPtr->CurVal, delta);
731 
732     /* We must notify parent now to obtain permission */
733     ni.iPos = infoPtr->CurVal;
734     ni.iDelta = delta;
735     ni.hdr.hwndFrom = infoPtr->Self;
736     ni.hdr.idFrom   = GetWindowLongPtrW (infoPtr->Self, GWLP_ID);
737     ni.hdr.code = UDN_DELTAPOS;
738     if (!SendMessageW(infoPtr->Notify, WM_NOTIFY, ni.hdr.idFrom, (LPARAM)&ni)) {
739         /* Parent said: OK to adjust */
740 
741         /* Now adjust value with (maybe new) delta */
742         if (UPDOWN_OffsetVal (infoPtr, ni.iDelta)) {
743             TRACE("new %d, delta: %d\n", infoPtr->CurVal, ni.iDelta);
744 
745             /* Now take care about our buddy */
746             UPDOWN_SetBuddyInt (infoPtr);
747         }
748     }
749 
750     /* Also, notify it. This message is sent in any case. */
751     SendMessageW( infoPtr->Notify, (infoPtr->dwStyle & UDS_HORZ) ? WM_HSCROLL : WM_VSCROLL,
752 		  MAKELONG(SB_THUMBPOSITION, infoPtr->CurVal), (LPARAM)infoPtr->Self);
753 }
754 
755 /***********************************************************************
756  *           UPDOWN_IsEnabled
757  *
758  * Returns TRUE if it is enabled as well as its buddy (if any)
759  *         FALSE otherwise
760  */
761 static BOOL UPDOWN_IsEnabled (const UPDOWN_INFO *infoPtr)
762 {
763     if (!IsWindowEnabled(infoPtr->Self))
764         return FALSE;
765     if(infoPtr->Buddy)
766         return IsWindowEnabled(infoPtr->Buddy);
767     return TRUE;
768 }
769 
770 /***********************************************************************
771  *           UPDOWN_CancelMode
772  *
773  * Deletes any timers, releases the mouse and does  redraw if necessary.
774  * If the control is not in "capture" mode, it does nothing.
775  * If the control was not in cancel mode, it returns FALSE.
776  * If the control was in cancel mode, it returns TRUE.
777  */
778 static BOOL UPDOWN_CancelMode (UPDOWN_INFO *infoPtr)
779 {
780     if (!(infoPtr->Flags & FLAG_PRESSED)) return FALSE;
781 
782     KillTimer (infoPtr->Self, TIMER_AUTOREPEAT);
783     KillTimer (infoPtr->Self, TIMER_ACCEL);
784     KillTimer (infoPtr->Self, TIMER_AUTOPRESS);
785 
786     if (GetCapture() == infoPtr->Self)
787         ReleaseCapture();
788 
789     infoPtr->Flags &= ~FLAG_PRESSED;
790     InvalidateRect (infoPtr->Self, NULL, FALSE);
791 
792     return TRUE;
793 }
794 
795 /***********************************************************************
796  *           UPDOWN_HandleMouseEvent
797  *
798  * Handle a mouse event for the updown.
799  * 'pt' is the location of the mouse event in client or
800  * windows coordinates.
801  */
802 static void UPDOWN_HandleMouseEvent (UPDOWN_INFO *infoPtr, UINT msg, INT x, INT y)
803 {
804     POINT pt = { x, y };
805     RECT rect;
806     int temp, arrow;
807     TRACKMOUSEEVENT tme;
808 
809     TRACE("msg %04x point %s\n", msg, wine_dbgstr_point(&pt));
810 
811     switch(msg)
812     {
813         case WM_LBUTTONDOWN:  /* Initialise mouse tracking */
814 
815             /* If the buddy is an edit, will set focus to it */
816 	    if (UPDOWN_IsBuddyEdit(infoPtr)) SetFocus(infoPtr->Buddy);
817 
818             /* Now see which one is the 'active' arrow */
819             arrow = UPDOWN_GetArrowFromPoint (infoPtr, &rect, pt);
820 
821             /* Update the flags if we are in/out */
822             infoPtr->Flags &= ~(FLAG_MOUSEIN | FLAG_ARROW);
823             if (arrow)
824                 infoPtr->Flags |= FLAG_MOUSEIN | arrow;
825             else
826                 if (infoPtr->AccelIndex != -1) infoPtr->AccelIndex = 0;
827 
828 	    if (infoPtr->Flags & FLAG_ARROW) {
829 
830             	/* Update the CurVal if necessary */
831             	UPDOWN_GetBuddyInt (infoPtr);
832 
833             	/* Set up the correct flags */
834             	infoPtr->Flags |= FLAG_PRESSED;
835 
836             	/* repaint the control */
837 	    	InvalidateRect (infoPtr->Self, NULL, FALSE);
838 
839             	/* process the click */
840 		temp = (infoPtr->AccelCount && infoPtr->AccelVect) ? infoPtr->AccelVect[0].nInc : 1;
841             	UPDOWN_DoAction (infoPtr, temp, infoPtr->Flags & FLAG_ARROW);
842 
843             	/* now capture all mouse messages */
844             	SetCapture (infoPtr->Self);
845 
846             	/* and startup the first timer */
847             	SetTimer(infoPtr->Self, TIMER_AUTOREPEAT, INITIAL_DELAY, 0);
848 	    }
849             break;
850 
851 	case WM_MOUSEMOVE:
852             /* save the flags to see if any got modified */
853             temp = infoPtr->Flags;
854 
855             /* Now see which one is the 'active' arrow */
856             arrow = UPDOWN_GetArrowFromPoint (infoPtr, &rect, pt);
857 
858             /* Update the flags if we are in/out */
859 	    infoPtr->Flags &= ~(FLAG_MOUSEIN | FLAG_ARROW);
860             if(arrow) {
861 	        infoPtr->Flags |=  FLAG_MOUSEIN | arrow;
862             } else {
863 	        if(infoPtr->AccelIndex != -1) infoPtr->AccelIndex = 0;
864             }
865 
866             /* If state changed, redraw the control */
867             if(temp != infoPtr->Flags)
868 		 InvalidateRect (infoPtr->Self, NULL, FALSE);
869 
870             /* Set up tracking so the mousein flags can be reset when the
871              * mouse leaves the control */
872             tme.cbSize = sizeof( tme );
873             tme.dwFlags = TME_LEAVE;
874             tme.hwndTrack = infoPtr->Self;
875             TrackMouseEvent (&tme);
876 
877             break;
878         case WM_MOUSELEAVE:
879 	    infoPtr->Flags &= ~(FLAG_MOUSEIN | FLAG_ARROW);
880             InvalidateRect (infoPtr->Self, NULL, FALSE);
881             break;
882 
883 	default:
884 	    ERR("Impossible case (msg=%x)!\n", msg);
885     }
886 
887 }
888 
889 /***********************************************************************
890  *           UpDownWndProc
891  */
892 static LRESULT WINAPI UpDownWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
893 {
894     UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
895     static const WCHAR themeClass[] = {'S','p','i','n',0};
896     HTHEME theme;
897 
898     TRACE("hwnd=%p msg=%04x wparam=%08lx lparam=%08lx\n", hwnd, message, wParam, lParam);
899 
900     if (!infoPtr && (message != WM_CREATE))
901         return DefWindowProcW (hwnd, message, wParam, lParam);
902 
903     switch(message)
904     {
905         case WM_CREATE:
906 	    {
907 	    CREATESTRUCTW *pcs = (CREATESTRUCTW*)lParam;
908 
909             infoPtr = heap_alloc_zero(sizeof(*infoPtr));
910 	    SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
911 
912 	    /* initialize the info struct */
913 	    infoPtr->Self = hwnd;
914 	    infoPtr->Notify  = pcs->hwndParent;
915 	    infoPtr->dwStyle = pcs->style;
916 	    infoPtr->AccelCount = 0;
917 	    infoPtr->AccelVect = 0;
918 	    infoPtr->AccelIndex = -1;
919 	    infoPtr->CurVal = 0;
920 	    infoPtr->MinVal = 100;
921 	    infoPtr->MaxVal = 0;
922 	    infoPtr->Base  = 10; /* Default to base 10  */
923 	    infoPtr->Buddy = 0;  /* No buddy window yet */
924 	    infoPtr->Flags = (infoPtr->dwStyle & UDS_SETBUDDYINT) ? FLAG_BUDDYINT : 0;
925 
926             SetWindowLongW (hwnd, GWL_STYLE, infoPtr->dwStyle & ~WS_BORDER);
927 	    if (!(infoPtr->dwStyle & UDS_HORZ))
928 	        SetWindowPos (hwnd, NULL, 0, 0, DEFAULT_WIDTH, pcs->cy,
929 	                      SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE);
930 
931             /* Do we pick the buddy win ourselves? */
932 	    if (infoPtr->dwStyle & UDS_AUTOBUDDY)
933 		UPDOWN_SetBuddy (infoPtr, GetWindow (hwnd, GW_HWNDPREV));
934 
935 	    OpenThemeData (hwnd, themeClass);
936 
937 	    TRACE("UpDown Ctrl creation, hwnd=%p\n", hwnd);
938 	    }
939 	    break;
940 
941 	case WM_DESTROY:
942 	    heap_free (infoPtr->AccelVect);
943             UPDOWN_ResetSubclass (infoPtr);
944 	    heap_free (infoPtr);
945 	    SetWindowLongPtrW (hwnd, 0, 0);
946             theme = GetWindowTheme (hwnd);
947             CloseThemeData (theme);
948 	    TRACE("UpDown Ctrl destruction, hwnd=%p\n", hwnd);
949 	    break;
950 
951 	case WM_ENABLE:
952 	    if (wParam) {
953 		infoPtr->dwStyle &= ~WS_DISABLED;
954 	    } else {
955 		infoPtr->dwStyle |= WS_DISABLED;
956 	    	UPDOWN_CancelMode (infoPtr);
957 	    }
958 	    InvalidateRect (infoPtr->Self, NULL, FALSE);
959 	    break;
960 
961         case WM_STYLECHANGED:
962             if (wParam == GWL_STYLE) {
963                 infoPtr->dwStyle = ((LPSTYLESTRUCT)lParam)->styleNew;
964 	        InvalidateRect (infoPtr->Self, NULL, FALSE);
965             }
966             break;
967 
968         case WM_THEMECHANGED:
969             theme = GetWindowTheme (hwnd);
970             CloseThemeData (theme);
971             OpenThemeData (hwnd, themeClass);
972             InvalidateRect (hwnd, NULL, FALSE);
973             break;
974 
975 	case WM_TIMER:
976 	   /* is this the auto-press timer? */
977 	   if(wParam == TIMER_AUTOPRESS) {
978 		KillTimer(hwnd, TIMER_AUTOPRESS);
979 		infoPtr->Flags &= ~(FLAG_PRESSED | FLAG_ARROW);
980 		InvalidateRect(infoPtr->Self, NULL, FALSE);
981 	   }
982 
983 	   /* if initial timer, kill it and start the repeat timer */
984   	   if(wParam == TIMER_AUTOREPEAT) {
985 		INT delay;
986 
987 		KillTimer(hwnd, TIMER_AUTOREPEAT);
988 		/* if no accel info given, used default timer */
989 		if(infoPtr->AccelCount==0 || infoPtr->AccelVect==0) {
990 		    infoPtr->AccelIndex = -1;
991 		    delay = REPEAT_DELAY;
992 		} else {
993 		    infoPtr->AccelIndex = 0; /* otherwise, use it */
994 		    delay = infoPtr->AccelVect[infoPtr->AccelIndex].nSec * 1000 + 1;
995 		}
996 		SetTimer(hwnd, TIMER_ACCEL, delay, 0);
997       	    }
998 
999 	    /* now, if the mouse is above us, do the thing...*/
1000 	    if(infoPtr->Flags & FLAG_MOUSEIN) {
1001 		int temp;
1002 
1003 		temp = infoPtr->AccelIndex == -1 ? 1 : infoPtr->AccelVect[infoPtr->AccelIndex].nInc;
1004 		UPDOWN_DoAction(infoPtr, temp, infoPtr->Flags & FLAG_ARROW);
1005 
1006 		if(infoPtr->AccelIndex != -1 && infoPtr->AccelIndex < infoPtr->AccelCount-1) {
1007 		    KillTimer(hwnd, TIMER_ACCEL);
1008 		    infoPtr->AccelIndex++; /* move to the next accel info */
1009 		    temp = infoPtr->AccelVect[infoPtr->AccelIndex].nSec * 1000 + 1;
1010 	  	    /* make sure we have at least 1ms intervals */
1011 		    SetTimer(hwnd, TIMER_ACCEL, temp, 0);
1012 		}
1013 	    }
1014 	    break;
1015 
1016 	case WM_CANCELMODE:
1017 	  return UPDOWN_CancelMode (infoPtr);
1018 
1019 	case WM_LBUTTONUP:
1020 	    if (GetCapture() != infoPtr->Self) break;
1021 
1022 	    if ( (infoPtr->Flags & FLAG_MOUSEIN) &&
1023 		 (infoPtr->Flags & FLAG_ARROW) ) {
1024 
1025 	    	SendMessageW( infoPtr->Notify,
1026 			      (infoPtr->dwStyle & UDS_HORZ) ? WM_HSCROLL : WM_VSCROLL,
1027                   	      MAKELONG(SB_ENDSCROLL, infoPtr->CurVal),
1028 			      (LPARAM)hwnd);
1029 		if (UPDOWN_IsBuddyEdit(infoPtr))
1030 		    SendMessageW(infoPtr->Buddy, EM_SETSEL, 0, MAKELONG(0, -1));
1031 	    }
1032 	    UPDOWN_CancelMode(infoPtr);
1033 	    break;
1034 
1035 	case WM_LBUTTONDOWN:
1036 	case WM_MOUSEMOVE:
1037         case WM_MOUSELEAVE:
1038 	    if(UPDOWN_IsEnabled(infoPtr))
1039 		UPDOWN_HandleMouseEvent (infoPtr, message, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1040 	    break;
1041 
1042         case WM_MOUSEWHEEL:
1043             UPDOWN_MouseWheel(infoPtr, wParam);
1044             break;
1045 
1046 	case WM_KEYDOWN:
1047 	    if((infoPtr->dwStyle & UDS_ARROWKEYS) && UPDOWN_IsEnabled(infoPtr))
1048 		return UPDOWN_KeyPressed(infoPtr, (int)wParam);
1049 	    break;
1050 
1051 	case WM_PRINTCLIENT:
1052 	case WM_PAINT:
1053 	    return UPDOWN_Paint (infoPtr, (HDC)wParam);
1054 
1055 	case UDM_GETACCEL:
1056 	    if (wParam==0 && lParam==0) return infoPtr->AccelCount;
1057 	    if (wParam && lParam) {
1058 		int temp = min(infoPtr->AccelCount, wParam);
1059 	        memcpy((void *)lParam, infoPtr->AccelVect, temp*sizeof(UDACCEL));
1060 	        return temp;
1061       	    }
1062 	    return 0;
1063 
1064 	case UDM_SETACCEL:
1065 	{
1066 	    TRACE("UDM_SETACCEL\n");
1067 
1068 	    if(infoPtr->AccelVect) {
1069 		heap_free (infoPtr->AccelVect);
1070 		infoPtr->AccelCount = 0;
1071 		infoPtr->AccelVect  = 0;
1072       	    }
1073 	    if(wParam==0) return TRUE;
1074 	    infoPtr->AccelVect = heap_alloc(wParam*sizeof(UDACCEL));
1075 	    if(!infoPtr->AccelVect) return FALSE;
1076 	    memcpy(infoPtr->AccelVect, (void*)lParam, wParam*sizeof(UDACCEL));
1077             infoPtr->AccelCount = wParam;
1078 
1079             if (TRACE_ON(updown))
1080             {
1081                 UINT i;
1082 
1083                 for (i = 0; i < wParam; i++)
1084                     TRACE("%u: nSec %u nInc %u\n", i,
1085                         infoPtr->AccelVect[i].nSec, infoPtr->AccelVect[i].nInc);
1086             }
1087 
1088     	    return TRUE;
1089 	}
1090 	case UDM_GETBASE:
1091 	    return infoPtr->Base;
1092 
1093 	case UDM_SETBASE:
1094 	    TRACE("UpDown Ctrl new base(%ld), hwnd=%p\n", wParam, hwnd);
1095 	    if (wParam==10 || wParam==16) {
1096 		WPARAM old_base = infoPtr->Base;
1097 		infoPtr->Base = wParam;
1098 
1099 		if (old_base != infoPtr->Base)
1100 		    UPDOWN_SetBuddyInt(infoPtr);
1101 
1102 		return old_base;
1103 	    }
1104 	    break;
1105 
1106 	case UDM_GETBUDDY:
1107 	    return (LRESULT)infoPtr->Buddy;
1108 
1109 	case UDM_SETBUDDY:
1110 	    return (LRESULT)UPDOWN_SetBuddy (infoPtr, (HWND)wParam);
1111 
1112 	case UDM_GETPOS:
1113 	{
1114             BOOL err;
1115             int pos;
1116 
1117             pos = UPDOWN_GetPos(infoPtr, &err);
1118             return MAKELONG(pos, err);
1119 	}
1120 	case UDM_SETPOS:
1121 	{
1122             return UPDOWN_SetPos(infoPtr, (short)LOWORD(lParam));
1123 	}
1124 	case UDM_GETRANGE:
1125 	    return MAKELONG(infoPtr->MaxVal, infoPtr->MinVal);
1126 
1127 	case UDM_SETRANGE:
1128 	    /* we must have:
1129 	    UD_MINVAL <= Max <= UD_MAXVAL
1130 	    UD_MINVAL <= Min <= UD_MAXVAL
1131 	    |Max-Min| <= UD_MAXVAL */
1132 	    UPDOWN_SetRange(infoPtr, (short)lParam, (short)HIWORD(lParam));
1133 	    break;
1134 
1135 	case UDM_GETRANGE32:
1136 	    if (wParam) *(LPINT)wParam = infoPtr->MinVal;
1137 	    if (lParam) *(LPINT)lParam = infoPtr->MaxVal;
1138 	    break;
1139 
1140 	case UDM_SETRANGE32:
1141 	    UPDOWN_SetRange(infoPtr, (INT)lParam, (INT)wParam);
1142 	    break;
1143 
1144 	case UDM_GETPOS32:
1145 	{
1146             return UPDOWN_GetPos(infoPtr, (BOOL*)lParam);
1147 	}
1148 	case UDM_SETPOS32:
1149 	{
1150             return UPDOWN_SetPos(infoPtr, (int)lParam);
1151 	}
1152 	case UDM_GETUNICODEFORMAT:
1153 	    /* we lie a bit here, we're always using Unicode internally */
1154 	    return infoPtr->UnicodeFormat;
1155 
1156 	case UDM_SETUNICODEFORMAT:
1157 	{
1158 	    /* do we really need to honour this flag? */
1159 	    int temp = infoPtr->UnicodeFormat;
1160 	    infoPtr->UnicodeFormat = (BOOL)wParam;
1161 	    return temp;
1162 	}
1163 	default:
1164 	    if ((message >= WM_USER) && (message < WM_APP) && !COMCTL32_IsReflectedMessage(message))
1165 		ERR("unknown msg %04x wp=%04lx lp=%08lx\n", message, wParam, lParam);
1166 	    return DefWindowProcW (hwnd, message, wParam, lParam);
1167     }
1168 
1169     return 0;
1170 }
1171 
1172 /***********************************************************************
1173  *		UPDOWN_Register	[Internal]
1174  *
1175  * Registers the updown window class.
1176  */
1177 void UPDOWN_Register(void)
1178 {
1179     WNDCLASSW wndClass;
1180 
1181     ZeroMemory( &wndClass, sizeof( WNDCLASSW ) );
1182     wndClass.style         = CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
1183     wndClass.lpfnWndProc   = UpDownWindowProc;
1184     wndClass.cbClsExtra    = 0;
1185     wndClass.cbWndExtra    = sizeof(UPDOWN_INFO*);
1186     wndClass.hCursor       = LoadCursorW( 0, (LPWSTR)IDC_ARROW );
1187     wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
1188     wndClass.lpszClassName = UPDOWN_CLASSW;
1189 
1190     RegisterClassW( &wndClass );
1191 }
1192 
1193 
1194 /***********************************************************************
1195  *		UPDOWN_Unregister	[Internal]
1196  *
1197  * Unregisters the updown window class.
1198  */
1199 void UPDOWN_Unregister (void)
1200 {
1201     UnregisterClassW (UPDOWN_CLASSW, NULL);
1202 }
1203