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