Compiler warnings fix.
[wine] / dlls / comctl32 / updown.c
1 /*              
2  * Updown control
3  *
4  * Copyright 1997 Dimitrie O. Paun
5  *
6  * TODO:
7  *   - subclass the buddy window (in UPDOWN_SetBuddy) to process the
8  *     arrow keys
9  *   - I am not sure about the default values for the Min, Max, Pos
10  *     (in the UPDOWN_INFO the fields: MinVal, MaxVal, CurVal)
11  *   - I think I do not handle correctly the WS_BORDER style.
12  *     (Should be fixed. <ekohl@abo.rhein-zeitung.de>)
13  *
14  * Testing:
15  *   Not much. The following  have not been tested at all:
16  *     - horizontal arrows
17  *     - listbox as buddy window
18  *     - acceleration
19  *     - base 16
20  *     - UDS_ALIGNLEFT, ~UDS_WRAP
21  *       (tested - they work)
22  *     - integers with thousand separators.
23  *       (fixed bugs. <noel@macadamian.com>)
24  *
25  *   Even though the above list seems rather large, the control seems to
26  *   behave very well so I am confident it does work in most (all) of the
27  *   untested cases.
28  * Problems:
29  *   I do not like the arrows yet, I'll work more on them later on.
30  */
31
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include "winbase.h"
36 #include "winuser.h"
37 #include "commctrl.h"
38 #include "winnls.h"
39 #include "updown.h"
40 #include "debugtools.h"
41
42 DEFAULT_DEBUG_CHANNEL(updown)
43
44 /* Control configuration constants */
45
46 #define INITIAL_DELAY    500 /* initial timer until auto-increment kicks in */
47 #define REPEAT_DELAY     50  /* delay between auto-increments */
48
49 #define DEFAULT_WIDTH    14  /* default width of the ctrl */
50 #define DEFAULT_XSEP      0  /* default separation between buddy and crtl */
51 #define DEFAULT_ADDTOP    0  /* amount to extend above the buddy window */
52 #define DEFAULT_ADDBOT    0  /* amount to extend below the buddy window */
53
54
55 /* Work constants */
56
57 #define FLAG_INCR        0x01
58 #define FLAG_DECR        0x02
59 #define FLAG_MOUSEIN     0x04
60 #define FLAG_CLICKED     (FLAG_INCR | FLAG_DECR)
61
62 #define TIMERID1         1
63 #define TIMERID2         2
64
65 static int accelIndex = -1;
66
67 #define UNKNOWN_PARAM(msg, wParam, lParam) WARN(\
68         "UpDown Ctrl: Unknown parameter(s) for message " #msg     \
69         "(%04x): wp=%04x lp=%08lx\n", msg, wParam, lParam);
70
71 #define UPDOWN_GetInfoPtr(hwnd) ((UPDOWN_INFO *)GetWindowLongA (hwnd,0))
72
73
74 /***********************************************************************
75  *           UPDOWN_InBounds
76  * Tests if a given value 'val' is between the Min&Max limits
77  */
78 static BOOL UPDOWN_InBounds(HWND hwnd, int val)
79 {
80   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
81
82   if(infoPtr->MaxVal > infoPtr->MinVal)
83     return (infoPtr->MinVal <= val) && (val <= infoPtr->MaxVal);
84   else
85     return (infoPtr->MaxVal <= val) && (val <= infoPtr->MinVal);
86 }
87
88 /***********************************************************************
89  *           UPDOWN_OffsetVal
90  * Tests if we can change the current value by delta. If so, it changes
91  * it and returns TRUE. Else, it leaves it unchanged and returns FALSE.
92  */
93 static BOOL UPDOWN_OffsetVal(HWND hwnd, int delta)
94 {
95   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
96
97   /* check if we can do the modification first */
98   if(!UPDOWN_InBounds (hwnd, infoPtr->CurVal+delta)){
99     if (GetWindowLongA (hwnd, GWL_STYLE) & UDS_WRAP)
100     {
101       delta += (delta < 0 ? -1 : 1) *
102         (infoPtr->MaxVal < infoPtr->MinVal ? -1 : 1) *
103         (infoPtr->MinVal - infoPtr->MaxVal) +
104         (delta < 0 ? 1 : -1);
105     }
106     else
107       return FALSE;
108   }
109
110   infoPtr->CurVal += delta;
111   return TRUE;
112 }
113
114 /***********************************************************************
115  *           UPDOWN_GetArrowRect
116  * wndPtr   - pointer to the up-down wnd
117  * rect     - will hold the rectangle
118  * incr     - TRUE  get the "increment" rect (up or right)
119  *            FALSE get the "decrement" rect (down or left)
120  *          
121  */
122 static void UPDOWN_GetArrowRect (HWND hwnd, RECT *rect, BOOL incr)
123 {
124   int len; /* will hold the width or height */
125
126   GetClientRect (hwnd, rect);
127
128   if (GetWindowLongA (hwnd, GWL_STYLE) & UDS_HORZ) {
129     len = rect->right - rect->left; /* compute the width */
130     if (incr)
131       rect->left = len/2+1; 
132     else
133       rect->right = len/2;
134   }
135   else {
136     len = rect->bottom - rect->top; /* compute the height */
137     if (incr)
138       rect->bottom = len/2;
139     else
140       rect->top = len/2+1;
141   }
142 }
143
144 /***********************************************************************
145  *           UPDOWN_GetArrowFromPoint
146  * Returns the rectagle (for the up or down arrow) that contains pt.
147  * If it returns the up rect, it returns TRUE.
148  * If it returns the down rect, it returns FALSE.
149  */
150 static BOOL
151 UPDOWN_GetArrowFromPoint (HWND hwnd, RECT *rect, POINT pt)
152 {
153     UPDOWN_GetArrowRect (hwnd, rect, TRUE);
154   if(PtInRect(rect, pt))
155     return TRUE;
156
157     UPDOWN_GetArrowRect (hwnd, rect, FALSE);
158   return FALSE;
159 }
160
161
162 /***********************************************************************
163  *           UPDOWN_GetThousandSep
164  * Returns the thousand sep. If an error occurs, it returns ','.
165  */
166 static char UPDOWN_GetThousandSep()
167 {
168   char sep[2];
169
170   if(GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, 
171                       sep, sizeof(sep)) != 1)
172     return ',';
173
174   return sep[0];
175 }
176
177 /***********************************************************************
178  *           UPDOWN_GetBuddyInt
179  * Tries to read the pos from the buddy window and if it succeeds,
180  * it stores it in the control's CurVal
181  * returns:
182  *   TRUE  - if it read the integer from the buddy successfully
183  *   FALSE - if an error occured
184  */
185 static BOOL UPDOWN_GetBuddyInt (HWND hwnd)
186 {
187   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
188   char txt[20], sep, *src, *dst;
189   int newVal;
190
191   if (!IsWindow(infoPtr->Buddy))
192     return FALSE;
193
194   /*if the buddy is a list window, we must set curr index */
195   if (!lstrcmpA (infoPtr->szBuddyClass, "ListBox")){
196     newVal = SendMessageA(infoPtr->Buddy, LB_GETCARETINDEX, 0, 0);
197     if(newVal < 0)
198       return FALSE;
199   }
200   else{
201     /* we have a regular window, so will get the text */
202     if (!GetWindowTextA(infoPtr->Buddy, txt, sizeof(txt)))
203       return FALSE;
204
205     sep = UPDOWN_GetThousandSep(); 
206
207     /* now get rid of the separators */
208     for(src = dst = txt; *src; src++)
209       if(*src != sep)
210         *dst++ = *src;
211     *dst = 0;
212
213     /* try to convert the number and validate it */
214     newVal = strtol(txt, &src, infoPtr->Base);
215     if(*src || !UPDOWN_InBounds (hwnd, newVal)) 
216       return FALSE;
217
218     TRACE("new value(%d) read from buddy (old=%d)\n", 
219                  newVal, infoPtr->CurVal);
220   }
221   
222   infoPtr->CurVal = newVal;
223   return TRUE;
224 }
225
226
227 /***********************************************************************
228  *           UPDOWN_SetBuddyInt
229  * Tries to set the pos to the buddy window based on current pos
230  * returns:
231  *   TRUE  - if it set the caption of the  buddy successfully
232  *   FALSE - if an error occured
233  */
234 static BOOL UPDOWN_SetBuddyInt (HWND hwnd)
235 {
236   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
237   char txt1[20], sep;
238   int len;
239
240   if (!IsWindow(infoPtr->Buddy)) 
241     return FALSE;
242
243   TRACE("set new value(%d) to buddy.\n",
244                infoPtr->CurVal);
245
246   /*if the buddy is a list window, we must set curr index */
247   if(!lstrcmpA (infoPtr->szBuddyClass, "ListBox")){
248     SendMessageA(infoPtr->Buddy, LB_SETCURSEL, infoPtr->CurVal, 0);
249   }
250   else{ /* Regular window, so set caption to the number */
251     len = sprintf(txt1, (infoPtr->Base==16) ? "%X" : "%d", infoPtr->CurVal);
252
253     sep = UPDOWN_GetThousandSep(); 
254
255     /* Do thousands seperation if necessary */
256     if (!(GetWindowLongA (hwnd, GWL_STYLE) & UDS_NOTHOUSANDS) && (len > 3)) {
257       char txt2[20], *src = txt1, *dst = txt2;
258       if(len%3 > 0){
259         lstrcpynA (dst, src, len%3 + 1);      /* need to include the null */ 
260         dst += len%3;
261         src += len%3;
262       }
263       for(len=0; *src; len++){
264         if(len%3==0)
265           *dst++ = sep;
266         *dst++ = *src++;
267       }
268       *dst = 0;           /* null terminate it */
269       strcpy(txt1, txt2); /* move it to the proper place */
270     }
271     SetWindowTextA(infoPtr->Buddy, txt1);
272   }
273
274   return TRUE;
275
276
277 /***********************************************************************
278  * UPDOWN_Draw [Internal]
279  *
280  * Draw the arrows. The background need not be erased.
281  */
282 static void UPDOWN_Draw (HWND hwnd, HDC hdc)
283 {
284   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
285   DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
286   BOOL prssed;
287   RECT rect;
288   
289   /* Draw the incr button */
290   UPDOWN_GetArrowRect (hwnd, &rect, TRUE);
291   prssed = (infoPtr->Flags & FLAG_INCR) && (infoPtr->Flags & FLAG_MOUSEIN);
292   DrawFrameControl(hdc, &rect, DFC_SCROLL, 
293         (dwStyle & UDS_HORZ ? DFCS_SCROLLLEFT : DFCS_SCROLLUP) |
294         (prssed ? DFCS_PUSHED : 0) |
295         (dwStyle&WS_DISABLED ? DFCS_INACTIVE : 0) );
296
297   /* Draw the space between the buttons */
298   rect.top = rect.bottom; rect.bottom++;
299   DrawEdge(hdc, &rect, 0, BF_MIDDLE);
300                     
301   /* Draw the decr button */
302   UPDOWN_GetArrowRect(hwnd, &rect, FALSE);
303   prssed = (infoPtr->Flags & FLAG_DECR) && (infoPtr->Flags & FLAG_MOUSEIN);
304   DrawFrameControl(hdc, &rect, DFC_SCROLL, 
305         (dwStyle & UDS_HORZ ? DFCS_SCROLLRIGHT : DFCS_SCROLLDOWN) |
306         (prssed ? DFCS_PUSHED : 0) |
307         (dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) );
308 }
309
310 /***********************************************************************
311  * UPDOWN_Refresh [Internal]
312  *
313  * Synchronous drawing (must NOT be used in WM_PAINT).
314  * Calls UPDOWN_Draw.
315  */
316 static void UPDOWN_Refresh (HWND hwnd)
317 {
318     HDC hdc;
319   
320     hdc = GetDC (hwnd);
321     UPDOWN_Draw (hwnd, hdc);
322     ReleaseDC (hwnd, hdc);
323 }
324
325
326 /***********************************************************************
327  * UPDOWN_Paint [Internal]
328  *
329  * Asynchronous drawing (must ONLY be used in WM_PAINT).
330  * Calls UPDOWN_Draw.
331  */
332 static void UPDOWN_Paint (HWND hwnd)
333 {
334     PAINTSTRUCT ps;
335     HDC hdc;
336   
337     hdc = BeginPaint (hwnd, &ps);
338     UPDOWN_Draw (hwnd, hdc);
339     EndPaint (hwnd, &ps);
340 }
341
342 /***********************************************************************
343  *           UPDOWN_SetBuddy
344  * Tests if 'hwndBud' is a valid window handle. If not, returns FALSE.
345  * Else, sets it as a new Buddy.
346  * Then, it should subclass the buddy 
347  * If window has the UDS_ARROWKEYS, it subcalsses the buddy window to
348  * process the UP/DOWN arrow keys.
349  * If window has the UDS_ALIGNLEFT or UDS_ALIGNRIGHT style
350  * the size/pos of the buddy and the control are adjusted accordingly.
351  */
352 static BOOL UPDOWN_SetBuddy (HWND hwnd, HWND hwndBud)
353 {
354   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd); 
355   DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
356   RECT budRect; /* new coord for the buddy */
357   int x;          /* new x position and width for the up-down */
358           
359   *infoPtr->szBuddyClass = '\0';
360
361   /* Is is a valid bud? */
362   if(!IsWindow(hwndBud))
363     return FALSE;
364
365   /* Store buddy window handle */
366   infoPtr->Buddy = hwndBud;
367
368   /* Store buddy window clas name */
369   GetClassNameA (hwndBud, infoPtr->szBuddyClass, 40);
370
371   if(dwStyle & UDS_ARROWKEYS){
372     FIXME("we need to subclass the buddy to process the arrow keys.\n");
373   }
374
375   /* do we need to do any adjustments? */
376   if(!(dwStyle & (UDS_ALIGNLEFT | UDS_ALIGNRIGHT)))
377     return TRUE;
378
379   infoPtr->Buddy = hwndBud;
380
381   /* Get the rect of the buddy relative to its parent */
382   GetWindowRect(infoPtr->Buddy, &budRect);
383   MapWindowPoints(HWND_DESKTOP, GetParent(infoPtr->Buddy),
384                   (POINT *)(&budRect.left), 2);
385
386   /* now do the positioning */
387   if(dwStyle & UDS_ALIGNRIGHT){
388     budRect.right -= DEFAULT_WIDTH+DEFAULT_XSEP;
389     x  = budRect.right+DEFAULT_XSEP;
390   }
391   else{ /* UDS_ALIGNLEFT */
392     x  = budRect.left;
393     budRect.left += DEFAULT_WIDTH+DEFAULT_XSEP;
394   }
395
396   /* first adjust the buddy to accomodate the up/down */
397   SetWindowPos(infoPtr->Buddy, 0, budRect.left, budRect.top,
398                budRect.right  - budRect.left, budRect.bottom - budRect.top, 
399                SWP_NOACTIVATE|SWP_NOZORDER);
400
401   /* now position the up/down */
402   /* Since the UDS_ALIGN* flags were used, */
403   /* we will pick the position and size of the window. */
404
405   SetWindowPos (hwnd, 0, x, budRect.top-DEFAULT_ADDTOP,DEFAULT_WIDTH,
406                  (budRect.bottom-budRect.top)+DEFAULT_ADDTOP+DEFAULT_ADDBOT,
407                  SWP_NOACTIVATE|SWP_NOZORDER);
408
409   return TRUE;
410 }         
411
412 /***********************************************************************
413  *           UPDOWN_DoAction
414  *
415  * This function increments/decrements the CurVal by the 
416  * 'delta' amount according to the 'incr' flag
417  * It notifies the parent as required.
418  * It handles wraping and non-wraping correctly.
419  * It is assumed that delta>0
420  */
421 static void UPDOWN_DoAction (HWND hwnd, int delta, BOOL incr)
422 {
423   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd); 
424   DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
425   int old_val = infoPtr->CurVal;
426   NM_UPDOWN ni;
427
428   TRACE("%s by %d\n", incr ? "inc" : "dec", delta);
429
430   /* check if we can do the modification first */
431   delta *= (incr ? 1 : -1) * (infoPtr->MaxVal < infoPtr->MinVal ? -1 : 1);
432   if(!UPDOWN_OffsetVal (hwnd, delta))
433     return;
434
435   /* so, if we can do the change, recompute delta and restore old value */
436   delta = infoPtr->CurVal - old_val;
437   infoPtr->CurVal = old_val;
438
439   /* We must notify parent now to obtain permission */
440   ni.iPos = infoPtr->CurVal;
441   ni.iDelta = delta;
442   ni.hdr.hwndFrom = hwnd;
443   ni.hdr.idFrom   = GetWindowLongA (hwnd, GWL_ID);
444   ni.hdr.code = UDN_DELTAPOS; 
445   if (SendMessageA(GetParent (hwnd), WM_NOTIFY,
446                    (WPARAM)ni.hdr.idFrom, (LPARAM)&ni))
447     return; /* we are not allowed to change */
448   
449   /* Now adjust value with (maybe new) delta */
450   if (!UPDOWN_OffsetVal (hwnd, ni.iDelta))
451     return;
452
453   /* Now take care about our buddy */
454   if(!IsWindow(infoPtr->Buddy)) 
455     return; /* Nothing else to do */
456
457
458   if (dwStyle & UDS_SETBUDDYINT)
459     UPDOWN_SetBuddyInt (hwnd);
460
461   /* Also, notify it */
462   /* FIXME: do we need to send the notification only if
463             we do not have the UDS_SETBUDDYINT style set? */
464
465   SendMessageA (GetParent (hwnd), 
466                 dwStyle & UDS_HORZ ? WM_HSCROLL : WM_VSCROLL, 
467                  MAKELONG(incr ? SB_LINEUP : SB_LINEDOWN, infoPtr->CurVal),
468                 hwnd);
469 }
470
471 /***********************************************************************
472  *           UPDOWN_IsEnabled
473  *
474  * Returns TRUE if it is enabled as well as its buddy (if any)
475  *         FALSE otherwise
476  */
477 static BOOL UPDOWN_IsEnabled (HWND hwnd)
478 {
479   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
480
481   if(GetWindowLongA (hwnd, GWL_STYLE) & WS_DISABLED)
482     return FALSE;
483   return IsWindowEnabled(infoPtr->Buddy);
484 }
485
486 /***********************************************************************
487  *           UPDOWN_CancelMode
488  *
489  * Deletes any timers, releases the mouse and does  redraw if necessary.
490  * If the control is not in "capture" mode, it does nothing.
491  * If the control was not in cancel mode, it returns FALSE. 
492  * If the control was in cancel mode, it returns TRUE.
493  */
494 static BOOL UPDOWN_CancelMode (HWND hwnd)
495 {
496   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
497  
498   /* if not in 'capture' mode, do nothing */
499   if(!(infoPtr->Flags & FLAG_CLICKED))
500     return FALSE;
501
502   KillTimer (hwnd, TIMERID1); /* kill all possible timers */
503   KillTimer (hwnd, TIMERID2);
504   
505   if (GetCapture() == hwnd)    /* let the mouse go         */
506     ReleaseCapture();          /* if we still have it      */  
507   
508   infoPtr->Flags = 0;          /* get rid of any flags     */
509   UPDOWN_Refresh (hwnd);       /* redraw the control just in case */
510   
511   return TRUE;
512 }
513
514 /***********************************************************************
515  *           UPDOWN_HandleMouseEvent
516  *
517  * Handle a mouse event for the updown.
518  * 'pt' is the location of the mouse event in client or
519  * windows coordinates. 
520  */
521 static void UPDOWN_HandleMouseEvent (HWND hwnd, UINT msg, POINT pt)
522 {
523   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
524   DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
525   RECT rect;
526   int temp;
527
528   switch(msg)
529     {
530     case WM_LBUTTONDOWN:  /* Initialise mouse tracking */
531       /* If we are already in the 'clicked' mode, then nothing to do */
532       if(infoPtr->Flags & FLAG_CLICKED)
533         return;
534
535       /* If the buddy is an edit, will set focus to it */
536       if (!lstrcmpA (infoPtr->szBuddyClass, "Edit"))
537         SetFocus(infoPtr->Buddy);
538
539       /* Now see which one is the 'active' arrow */
540       temp = UPDOWN_GetArrowFromPoint (hwnd, &rect, pt);
541
542       /* Update the CurVal if necessary */
543       if (dwStyle & UDS_SETBUDDYINT)
544         UPDOWN_GetBuddyInt (hwnd);
545         
546       /* Before we proceed, see if we can spin... */
547       if(!(dwStyle & UDS_WRAP))
548         if(( temp && infoPtr->CurVal==infoPtr->MaxVal) ||
549            (!temp && infoPtr->CurVal==infoPtr->MinVal))
550           return;
551
552       /* Set up the correct flags */
553       infoPtr->Flags  = 0; 
554       infoPtr->Flags |= temp ? FLAG_INCR : FLAG_DECR;
555       infoPtr->Flags |= FLAG_MOUSEIN;
556       
557       /* repaint the control */
558       UPDOWN_Refresh (hwnd);
559
560       /* process the click */
561       UPDOWN_DoAction (hwnd, 1, infoPtr->Flags & FLAG_INCR);
562
563       /* now capture all mouse messages */
564       SetCapture (hwnd);
565
566       /* and startup the first timer */
567       SetTimer(hwnd, TIMERID1, INITIAL_DELAY, 0); 
568       break;
569
570     case WM_MOUSEMOVE:
571       /* If we are not in the 'clicked' mode, then nothing to do */
572       if(!(infoPtr->Flags & FLAG_CLICKED))
573         return;
574
575       /* save the flags to see if any got modified */
576       temp = infoPtr->Flags;
577
578       /* Now get the 'active' arrow rectangle */
579       if (infoPtr->Flags & FLAG_INCR)
580         UPDOWN_GetArrowRect (hwnd, &rect, TRUE);
581       else
582         UPDOWN_GetArrowRect (hwnd, &rect, FALSE);
583
584       /* Update the flags if we are in/out */
585       if(PtInRect(&rect, pt))
586         infoPtr->Flags |=  FLAG_MOUSEIN;
587       else{
588         infoPtr->Flags &= ~FLAG_MOUSEIN;
589         if(accelIndex != -1) /* if we have accel info */
590           accelIndex = 0;    /* reset it              */
591       }
592       /* If state changed, redraw the control */
593       if(temp != infoPtr->Flags)
594         UPDOWN_Refresh (hwnd);
595       break;
596
597       default:
598         ERR("Impossible case!\n");
599     }
600
601 }
602
603 /***********************************************************************
604  *           UpDownWndProc
605  */
606 LRESULT WINAPI UpDownWindowProc(HWND hwnd, UINT message, WPARAM wParam,
607                                 LPARAM lParam)
608 {
609   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
610   DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
611   int temp;
612
613   switch(message)
614     {
615     case WM_NCCREATE:
616       /* get rid of border, if any */
617       SetWindowLongA (hwnd, GWL_STYLE, dwStyle & ~WS_BORDER);
618       return TRUE;
619
620     case WM_CREATE:
621       infoPtr = (UPDOWN_INFO*)COMCTL32_Alloc (sizeof(UPDOWN_INFO));
622       SetWindowLongA (hwnd, 0, (DWORD)infoPtr);
623
624       /* initialize the info struct */
625       infoPtr->AccelCount=0; infoPtr->AccelVect=0; 
626       infoPtr->CurVal=0; infoPtr->MinVal=0; infoPtr->MaxVal=100; /*FIXME*/
627       infoPtr->Base  = 10; /* Default to base 10  */
628       infoPtr->Buddy = 0;  /* No buddy window yet */
629       infoPtr->Flags = 0;  /* And no flags        */
630
631       /* Do we pick the buddy win ourselves? */
632       if (dwStyle & UDS_AUTOBUDDY)
633         UPDOWN_SetBuddy (hwnd, GetWindow (hwnd, GW_HWNDPREV));
634         
635       TRACE("UpDown Ctrl creation, hwnd=%04x\n", hwnd);
636       break;
637     
638     case WM_DESTROY:
639       if(infoPtr->AccelVect)
640         COMCTL32_Free (infoPtr->AccelVect);
641
642       COMCTL32_Free (infoPtr);
643
644       TRACE("UpDown Ctrl destruction, hwnd=%04x\n", hwnd);
645       break;
646         
647     case WM_ENABLE:
648       if (dwStyle & WS_DISABLED)
649         UPDOWN_CancelMode (hwnd);
650       UPDOWN_Paint (hwnd);
651       break;
652
653     case WM_TIMER:
654       /* if initial timer, kill it and start the repeat timer */
655       if(wParam == TIMERID1){
656         KillTimer(hwnd, TIMERID1);
657         /* if no accel info given, used default timer */
658         if(infoPtr->AccelCount==0 || infoPtr->AccelVect==0){
659           accelIndex = -1;
660           temp = REPEAT_DELAY;
661         }
662         else{
663           accelIndex = 0; /* otherwise, use it */
664           temp = infoPtr->AccelVect[accelIndex].nSec * 1000 + 1;
665         }
666         SetTimer(hwnd, TIMERID2, temp, 0);
667       }
668
669       /* now, if the mouse is above us, do the thing...*/
670       if(infoPtr->Flags & FLAG_MOUSEIN){
671         temp = accelIndex==-1 ? 1 : infoPtr->AccelVect[accelIndex].nInc;
672         UPDOWN_DoAction(hwnd, temp, infoPtr->Flags & FLAG_INCR);
673         
674         if(accelIndex!=-1 && accelIndex < infoPtr->AccelCount-1){
675           KillTimer(hwnd, TIMERID2);
676           accelIndex++; /* move to the next accel info */
677           temp = infoPtr->AccelVect[accelIndex].nSec * 1000 + 1;
678           /* make sure we have at least 1ms intervals */
679           SetTimer(hwnd, TIMERID2, temp, 0);        
680         }
681       }
682       break;
683
684     case WM_CANCELMODE:
685       UPDOWN_CancelMode (hwnd);
686       break;
687
688     case WM_LBUTTONUP:
689       if(!UPDOWN_CancelMode(hwnd))
690         break;
691       /*If we released the mouse and our buddy is an edit */
692       /* we must select all text in it.                   */
693       if (!lstrcmpA (infoPtr->szBuddyClass, "Edit"))
694           SendMessageA(infoPtr->Buddy, EM_SETSEL, 0, MAKELONG(0, -1));
695       break;
696       
697     case WM_LBUTTONDOWN:
698     case WM_MOUSEMOVE:
699       if(UPDOWN_IsEnabled(hwnd)){
700         POINT pt;
701         CONV_POINT16TO32( (POINT16 *)&lParam, &pt );
702         UPDOWN_HandleMouseEvent (hwnd, message, pt );
703       }
704     break;
705
706     case WM_KEYDOWN:
707       if((dwStyle & UDS_ARROWKEYS) && UPDOWN_IsEnabled(hwnd)){
708         switch(wParam){
709         case VK_UP:  
710         case VK_DOWN:
711           UPDOWN_GetBuddyInt (hwnd);
712           UPDOWN_DoAction (hwnd, 1, wParam==VK_UP);
713           break;
714         }
715       }
716       break;
717       
718     case WM_PAINT:
719       UPDOWN_Paint (hwnd);
720       break;
721     
722     case UDM_GETACCEL:
723       if (wParam==0 && lParam==0)    /*if both zero, */
724         return infoPtr->AccelCount;  /*just return the accel count*/
725       if (wParam || lParam){
726         UNKNOWN_PARAM(UDM_GETACCEL, wParam, lParam);
727         return 0;
728       }
729       temp = MIN(infoPtr->AccelCount, wParam);
730       memcpy((void *)lParam, infoPtr->AccelVect, temp*sizeof(UDACCEL));
731       return temp;
732
733     case UDM_SETACCEL:
734       TRACE("UpDown Ctrl new accel info, hwnd=%04x\n", hwnd);
735       if(infoPtr->AccelVect){
736         COMCTL32_Free (infoPtr->AccelVect);
737         infoPtr->AccelCount = 0;
738         infoPtr->AccelVect  = 0;
739       }
740       if(wParam==0)
741         return TRUE;
742       infoPtr->AccelVect = COMCTL32_Alloc (wParam*sizeof(UDACCEL));
743       if(infoPtr->AccelVect==0)
744         return FALSE;
745       memcpy(infoPtr->AccelVect, (void*)lParam, wParam*sizeof(UDACCEL));
746       return TRUE;
747
748     case UDM_GETBASE:
749       if (wParam || lParam)
750         UNKNOWN_PARAM(UDM_GETBASE, wParam, lParam);
751       return infoPtr->Base;
752
753     case UDM_SETBASE:
754       TRACE("UpDown Ctrl new base(%d), hwnd=%04x\n", 
755                      wParam, hwnd);
756       if ( !(wParam==10 || wParam==16) || lParam)
757         UNKNOWN_PARAM(UDM_SETBASE, wParam, lParam);
758       if (wParam==10 || wParam==16){
759         temp = infoPtr->Base;
760         infoPtr->Base = wParam;
761         return temp;       /* return the prev base */
762       }
763       break;
764
765     case UDM_GETBUDDY:
766       if (wParam || lParam)
767         UNKNOWN_PARAM(UDM_GETBUDDY, wParam, lParam);
768       return infoPtr->Buddy;
769
770     case UDM_SETBUDDY:
771       if (lParam)
772         UNKNOWN_PARAM(UDM_SETBUDDY, wParam, lParam);
773       temp = infoPtr->Buddy;
774       infoPtr->Buddy = wParam;
775       UPDOWN_SetBuddy (hwnd, wParam);
776       TRACE("UpDown Ctrl new buddy(%04x), hwnd=%04x\n", 
777                      infoPtr->Buddy, hwnd);
778       return temp;
779
780     case UDM_GETPOS:
781       if (wParam || lParam)
782         UNKNOWN_PARAM(UDM_GETPOS, wParam, lParam);
783       temp = UPDOWN_GetBuddyInt (hwnd);
784       return MAKELONG(infoPtr->CurVal, temp ? 0 : 1);
785
786     case UDM_SETPOS:
787       if (wParam || HIWORD(lParam))
788         UNKNOWN_PARAM(UDM_GETPOS, wParam, lParam);
789       temp = SLOWORD(lParam);
790       TRACE("UpDown Ctrl new value(%d), hwnd=%04x\n",
791                      temp, hwnd);
792       if(!UPDOWN_InBounds(hwnd, temp)){
793         if(temp < infoPtr->MinVal)  
794           temp = infoPtr->MinVal;
795         if(temp > infoPtr->MaxVal)
796           temp = infoPtr->MaxVal;
797       }
798       wParam = infoPtr->CurVal; /* save prev value   */
799       infoPtr->CurVal = temp;   /* set the new value */
800       if(dwStyle & UDS_SETBUDDYINT)
801         UPDOWN_SetBuddyInt (hwnd);
802       return wParam;            /* return prev value */
803       
804     case UDM_GETRANGE:
805       if (wParam || lParam)
806         UNKNOWN_PARAM(UDM_GETRANGE, wParam, lParam);
807       return MAKELONG(infoPtr->MaxVal, infoPtr->MinVal);
808
809     case UDM_SETRANGE:
810       if (wParam)
811         UNKNOWN_PARAM(UDM_SETRANGE, wParam, lParam); /* we must have:     */
812       infoPtr->MaxVal = SLOWORD(lParam); /* UD_MINVAL <= Max <= UD_MAXVAL */
813       infoPtr->MinVal = SHIWORD(lParam); /* UD_MINVAL <= Min <= UD_MAXVAL */
814                                          /* |Max-Min| <= UD_MAXVAL        */
815       TRACE("UpDown Ctrl new range(%d to %d), hwnd=%04x\n", 
816                      infoPtr->MinVal, infoPtr->MaxVal, hwnd);
817       break;                             
818
819     case UDM_GETRANGE32:
820       if (wParam)
821         *(LPINT)wParam = infoPtr->MinVal;
822       if (lParam)
823         *(LPINT)lParam = infoPtr->MaxVal;
824       break;
825
826     case UDM_SETRANGE32:
827       infoPtr->MinVal = (INT)wParam;
828       infoPtr->MaxVal = (INT)lParam;
829       if (infoPtr->MaxVal <= infoPtr->MinVal)
830         infoPtr->MaxVal = infoPtr->MinVal + 1;
831       TRACE("UpDown Ctrl new range(%d to %d), hwnd=%04x\n", 
832                      infoPtr->MinVal, infoPtr->MaxVal, hwnd);
833       break;
834
835     default: 
836       if (message >= WM_USER) 
837         ERR("unknown msg %04x wp=%04x lp=%08lx\n", 
838              message, wParam, lParam);
839       return DefWindowProcA (hwnd, message, wParam, lParam); 
840     } 
841
842     return 0;
843 }
844
845
846 /***********************************************************************
847  *              UPDOWN_Register [Internal]
848  *
849  * Registers the updown window class.
850  */
851
852 VOID
853 UPDOWN_Register(void)
854 {
855     WNDCLASSA wndClass;
856
857     if( GlobalFindAtomA( UPDOWN_CLASSA ) ) return;
858
859     ZeroMemory( &wndClass, sizeof( WNDCLASSA ) );
860     wndClass.style         = CS_GLOBALCLASS | CS_VREDRAW;
861     wndClass.lpfnWndProc   = (WNDPROC)UpDownWindowProc;
862     wndClass.cbClsExtra    = 0;
863     wndClass.cbWndExtra    = sizeof(UPDOWN_INFO*);
864     wndClass.hCursor       = LoadCursorA( 0, IDC_ARROWA );
865     wndClass.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
866     wndClass.lpszClassName = UPDOWN_CLASSA;
867  
868     RegisterClassA( &wndClass );
869 }
870
871
872 /***********************************************************************
873  *              UPDOWN_Unregister       [Internal]
874  *
875  * Unregisters the updown window class.
876  */
877
878 VOID
879 UPDOWN_Unregister (void)
880 {
881     if (GlobalFindAtomA (UPDOWN_CLASSA))
882         UnregisterClassA (UPDOWN_CLASSA, (HINSTANCE)NULL);
883 }
884