Fix (well kludge around) FreeBSD's `Invalid address' errors.
[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 clas name */
366   GetClassNameA (hwndBud, infoPtr->szBuddyClass, 40);
367
368   if(dwStyle & UDS_ARROWKEYS){
369     FIXME("we need to subclass the buddy to process the arrow keys.\n");
370   }
371
372   /* do we need to do any adjustments? */
373   if(!(dwStyle & (UDS_ALIGNLEFT | UDS_ALIGNRIGHT)))
374     return TRUE;
375
376   infoPtr->Buddy = hwndBud;
377
378   /* Get the rect of the buddy relative to its parent */
379   GetWindowRect(infoPtr->Buddy, &budRect);
380   MapWindowPoints(HWND_DESKTOP, GetParent(infoPtr->Buddy),
381                   (POINT *)(&budRect.left), 2);
382
383   /* now do the positioning */
384   if(dwStyle & UDS_ALIGNRIGHT){
385     budRect.right -= DEFAULT_WIDTH+DEFAULT_XSEP;
386     x  = budRect.right+DEFAULT_XSEP;
387   }
388   else{ /* UDS_ALIGNLEFT */
389     x  = budRect.left;
390     budRect.left += DEFAULT_WIDTH+DEFAULT_XSEP;
391   }
392
393   /* first adjust the buddy to accomodate the up/down */
394   SetWindowPos(infoPtr->Buddy, 0, budRect.left, budRect.top,
395                budRect.right  - budRect.left, budRect.bottom - budRect.top, 
396                SWP_NOACTIVATE|SWP_NOZORDER);
397
398   /* now position the up/down */
399   /* Since the UDS_ALIGN* flags were used, */
400   /* we will pick the position and size of the window. */
401
402   SetWindowPos (hwnd, 0, x, budRect.top-DEFAULT_ADDTOP,DEFAULT_WIDTH,
403                  (budRect.bottom-budRect.top)+DEFAULT_ADDTOP+DEFAULT_ADDBOT,
404                  SWP_NOACTIVATE|SWP_NOZORDER);
405
406   return TRUE;
407 }         
408
409 /***********************************************************************
410  *           UPDOWN_DoAction
411  *
412  * This function increments/decrements the CurVal by the 
413  * 'delta' amount according to the 'incr' flag
414  * It notifies the parent as required.
415  * It handles wraping and non-wraping correctly.
416  * It is assumed that delta>0
417  */
418 static void UPDOWN_DoAction (HWND hwnd, int delta, BOOL incr)
419 {
420   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd); 
421   DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
422   int old_val = infoPtr->CurVal;
423   NM_UPDOWN ni;
424
425   TRACE("%s by %d\n", incr ? "inc" : "dec", delta);
426
427   /* check if we can do the modification first */
428   delta *= (incr ? 1 : -1) * (infoPtr->MaxVal < infoPtr->MinVal ? -1 : 1);
429   if(!UPDOWN_OffsetVal (hwnd, delta))
430     return;
431
432   /* so, if we can do the change, recompute delta and restore old value */
433   delta = infoPtr->CurVal - old_val;
434   infoPtr->CurVal = old_val;
435
436   /* We must notify parent now to obtain permission */
437   ni.iPos = infoPtr->CurVal;
438   ni.iDelta = delta;
439   ni.hdr.hwndFrom = hwnd;
440   ni.hdr.idFrom   = GetWindowLongA (hwnd, GWL_ID);
441   ni.hdr.code = UDN_DELTAPOS; 
442   if (SendMessageA(GetParent (hwnd), WM_NOTIFY,
443                    (WPARAM)ni.hdr.idFrom, (LPARAM)&ni))
444     return; /* we are not allowed to change */
445   
446   /* Now adjust value with (maybe new) delta */
447   if (!UPDOWN_OffsetVal (hwnd, ni.iDelta))
448     return;
449
450   /* Now take care about our buddy */
451   if(!IsWindow(infoPtr->Buddy)) 
452     return; /* Nothing else to do */
453
454
455   if (dwStyle & UDS_SETBUDDYINT)
456     UPDOWN_SetBuddyInt (hwnd);
457
458   /* Also, notify it */
459   /* FIXME: do we need to send the notification only if
460             we do not have the UDS_SETBUDDYINT style set? */
461
462   SendMessageA (GetParent (hwnd), 
463                 dwStyle & UDS_HORZ ? WM_HSCROLL : WM_VSCROLL, 
464                  MAKELONG(incr ? SB_LINEUP : SB_LINEDOWN, infoPtr->CurVal),
465                 hwnd);
466 }
467
468 /***********************************************************************
469  *           UPDOWN_IsEnabled
470  *
471  * Returns TRUE if it is enabled as well as its buddy (if any)
472  *         FALSE otherwise
473  */
474 static BOOL UPDOWN_IsEnabled (HWND hwnd)
475 {
476   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
477
478   if(GetWindowLongA (hwnd, GWL_STYLE) & WS_DISABLED)
479     return FALSE;
480   return IsWindowEnabled(infoPtr->Buddy);
481 }
482
483 /***********************************************************************
484  *           UPDOWN_CancelMode
485  *
486  * Deletes any timers, releases the mouse and does  redraw if necessary.
487  * If the control is not in "capture" mode, it does nothing.
488  * If the control was not in cancel mode, it returns FALSE. 
489  * If the control was in cancel mode, it returns TRUE.
490  */
491 static BOOL UPDOWN_CancelMode (HWND hwnd)
492 {
493   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
494  
495   /* if not in 'capture' mode, do nothing */
496   if(!(infoPtr->Flags & FLAG_CLICKED))
497     return FALSE;
498
499   KillTimer (hwnd, TIMERID1); /* kill all possible timers */
500   KillTimer (hwnd, TIMERID2);
501   
502   if (GetCapture() == hwnd)    /* let the mouse go         */
503     ReleaseCapture();          /* if we still have it      */  
504   
505   infoPtr->Flags = 0;          /* get rid of any flags     */
506   UPDOWN_Refresh (hwnd);       /* redraw the control just in case */
507   
508   return TRUE;
509 }
510
511 /***********************************************************************
512  *           UPDOWN_HandleMouseEvent
513  *
514  * Handle a mouse event for the updown.
515  * 'pt' is the location of the mouse event in client or
516  * windows coordinates. 
517  */
518 static void UPDOWN_HandleMouseEvent (HWND hwnd, UINT msg, POINT pt)
519 {
520   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
521   DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
522   RECT rect;
523   int temp;
524
525   switch(msg)
526     {
527     case WM_LBUTTONDOWN:  /* Initialise mouse tracking */
528       /* If we are already in the 'clicked' mode, then nothing to do */
529       if(infoPtr->Flags & FLAG_CLICKED)
530         return;
531
532       /* If the buddy is an edit, will set focus to it */
533       if (!lstrcmpA (infoPtr->szBuddyClass, "Edit"))
534         SetFocus(infoPtr->Buddy);
535
536       /* Now see which one is the 'active' arrow */
537       temp = UPDOWN_GetArrowFromPoint (hwnd, &rect, pt);
538
539       /* Update the CurVal if necessary */
540       if (dwStyle & UDS_SETBUDDYINT)
541         UPDOWN_GetBuddyInt (hwnd);
542         
543       /* Before we proceed, see if we can spin... */
544       if(!(dwStyle & UDS_WRAP))
545         if(( temp && infoPtr->CurVal==infoPtr->MaxVal) ||
546            (!temp && infoPtr->CurVal==infoPtr->MinVal))
547           return;
548
549       /* Set up the correct flags */
550       infoPtr->Flags  = 0; 
551       infoPtr->Flags |= temp ? FLAG_INCR : FLAG_DECR;
552       infoPtr->Flags |= FLAG_MOUSEIN;
553       
554       /* repaint the control */
555       UPDOWN_Refresh (hwnd);
556
557       /* process the click */
558       UPDOWN_DoAction (hwnd, 1, infoPtr->Flags & FLAG_INCR);
559
560       /* now capture all mouse messages */
561       SetCapture (hwnd);
562
563       /* and startup the first timer */
564       SetTimer(hwnd, TIMERID1, INITIAL_DELAY, 0); 
565       break;
566
567     case WM_MOUSEMOVE:
568       /* If we are not in the 'clicked' mode, then nothing to do */
569       if(!(infoPtr->Flags & FLAG_CLICKED))
570         return;
571
572       /* save the flags to see if any got modified */
573       temp = infoPtr->Flags;
574
575       /* Now get the 'active' arrow rectangle */
576       if (infoPtr->Flags & FLAG_INCR)
577         UPDOWN_GetArrowRect (hwnd, &rect, TRUE);
578       else
579         UPDOWN_GetArrowRect (hwnd, &rect, FALSE);
580
581       /* Update the flags if we are in/out */
582       if(PtInRect(&rect, pt))
583         infoPtr->Flags |=  FLAG_MOUSEIN;
584       else{
585         infoPtr->Flags &= ~FLAG_MOUSEIN;
586         if(accelIndex != -1) /* if we have accel info */
587           accelIndex = 0;    /* reset it              */
588       }
589       /* If state changed, redraw the control */
590       if(temp != infoPtr->Flags)
591         UPDOWN_Refresh (hwnd);
592       break;
593
594       default:
595         ERR("Impossible case!\n");
596     }
597
598 }
599
600 /***********************************************************************
601  *           UpDownWndProc
602  */
603 LRESULT WINAPI UpDownWindowProc(HWND hwnd, UINT message, WPARAM wParam,
604                                 LPARAM lParam)
605 {
606   UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
607   DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
608   int temp;
609
610   switch(message)
611     {
612     case WM_NCCREATE:
613       /* get rid of border, if any */
614       SetWindowLongA (hwnd, GWL_STYLE, dwStyle & ~WS_BORDER);
615       return TRUE;
616
617     case WM_CREATE:
618       infoPtr = (UPDOWN_INFO*)COMCTL32_Alloc (sizeof(UPDOWN_INFO));
619       SetWindowLongA (hwnd, 0, (DWORD)infoPtr);
620
621       /* initialize the info struct */
622       infoPtr->AccelCount=0; infoPtr->AccelVect=0; 
623       infoPtr->CurVal=0; infoPtr->MinVal=0; infoPtr->MaxVal=100; /*FIXME*/
624       infoPtr->Base  = 10; /* Default to base 10  */
625       infoPtr->Buddy = 0;  /* No buddy window yet */
626       infoPtr->Flags = 0;  /* And no flags        */
627
628       /* Do we pick the buddy win ourselves? */
629       if (dwStyle & UDS_AUTOBUDDY)
630         UPDOWN_SetBuddy (hwnd, GetWindow (hwnd, GW_HWNDPREV));
631         
632       TRACE("UpDown Ctrl creation, hwnd=%04x\n", hwnd);
633       break;
634     
635     case WM_DESTROY:
636       if(infoPtr->AccelVect)
637         COMCTL32_Free (infoPtr->AccelVect);
638
639       COMCTL32_Free (infoPtr);
640
641       TRACE("UpDown Ctrl destruction, hwnd=%04x\n", hwnd);
642       break;
643         
644     case WM_ENABLE:
645       if (dwStyle & WS_DISABLED)
646         UPDOWN_CancelMode (hwnd);
647       UPDOWN_Paint (hwnd);
648       break;
649
650     case WM_TIMER:
651       /* if initial timer, kill it and start the repeat timer */
652       if(wParam == TIMERID1){
653         KillTimer(hwnd, TIMERID1);
654         /* if no accel info given, used default timer */
655         if(infoPtr->AccelCount==0 || infoPtr->AccelVect==0){
656           accelIndex = -1;
657           temp = REPEAT_DELAY;
658         }
659         else{
660           accelIndex = 0; /* otherwise, use it */
661           temp = infoPtr->AccelVect[accelIndex].nSec * 1000 + 1;
662         }
663         SetTimer(hwnd, TIMERID2, temp, 0);
664       }
665
666       /* now, if the mouse is above us, do the thing...*/
667       if(infoPtr->Flags & FLAG_MOUSEIN){
668         temp = accelIndex==-1 ? 1 : infoPtr->AccelVect[accelIndex].nInc;
669         UPDOWN_DoAction(hwnd, temp, infoPtr->Flags & FLAG_INCR);
670         
671         if(accelIndex!=-1 && accelIndex < infoPtr->AccelCount-1){
672           KillTimer(hwnd, TIMERID2);
673           accelIndex++; /* move to the next accel info */
674           temp = infoPtr->AccelVect[accelIndex].nSec * 1000 + 1;
675           /* make sure we have at least 1ms intervals */
676           SetTimer(hwnd, TIMERID2, temp, 0);        
677         }
678       }
679       break;
680
681     case WM_CANCELMODE:
682       UPDOWN_CancelMode (hwnd);
683       break;
684
685     case WM_LBUTTONUP:
686       if(!UPDOWN_CancelMode(hwnd))
687         break;
688       /*If we released the mouse and our buddy is an edit */
689       /* we must select all text in it.                   */
690       if (!lstrcmpA (infoPtr->szBuddyClass, "Edit"))
691           SendMessageA(infoPtr->Buddy, EM_SETSEL, 0, MAKELONG(0, -1));
692       break;
693       
694     case WM_LBUTTONDOWN:
695     case WM_MOUSEMOVE:
696       if(UPDOWN_IsEnabled(hwnd)){
697         POINT pt;
698         CONV_POINT16TO32( (POINT16 *)&lParam, &pt );
699         UPDOWN_HandleMouseEvent (hwnd, message, pt );
700       }
701     break;
702
703     case WM_KEYDOWN:
704       if((dwStyle & UDS_ARROWKEYS) && UPDOWN_IsEnabled(hwnd)){
705         switch(wParam){
706         case VK_UP:  
707         case VK_DOWN:
708           UPDOWN_GetBuddyInt (hwnd);
709           UPDOWN_DoAction (hwnd, 1, wParam==VK_UP);
710           break;
711         }
712       }
713       break;
714       
715     case WM_PAINT:
716       UPDOWN_Paint (hwnd);
717       break;
718     
719     case UDM_GETACCEL:
720       if (wParam==0 && lParam==0)    /*if both zero, */
721         return infoPtr->AccelCount;  /*just return the accel count*/
722       if (wParam || lParam){
723         UNKNOWN_PARAM(UDM_GETACCEL, wParam, lParam);
724         return 0;
725       }
726       temp = MIN(infoPtr->AccelCount, wParam);
727       memcpy((void *)lParam, infoPtr->AccelVect, temp*sizeof(UDACCEL));
728       return temp;
729
730     case UDM_SETACCEL:
731       TRACE("UpDown Ctrl new accel info, hwnd=%04x\n", hwnd);
732       if(infoPtr->AccelVect){
733         COMCTL32_Free (infoPtr->AccelVect);
734         infoPtr->AccelCount = 0;
735         infoPtr->AccelVect  = 0;
736       }
737       if(wParam==0)
738         return TRUE;
739       infoPtr->AccelVect = COMCTL32_Alloc (wParam*sizeof(UDACCEL));
740       if(infoPtr->AccelVect==0)
741         return FALSE;
742       memcpy(infoPtr->AccelVect, (void*)lParam, wParam*sizeof(UDACCEL));
743       return TRUE;
744
745     case UDM_GETBASE:
746       if (wParam || lParam)
747         UNKNOWN_PARAM(UDM_GETBASE, wParam, lParam);
748       return infoPtr->Base;
749
750     case UDM_SETBASE:
751       TRACE("UpDown Ctrl new base(%d), hwnd=%04x\n", 
752                      wParam, hwnd);
753       if ( !(wParam==10 || wParam==16) || lParam)
754         UNKNOWN_PARAM(UDM_SETBASE, wParam, lParam);
755       if (wParam==10 || wParam==16){
756         temp = infoPtr->Base;
757         infoPtr->Base = wParam;
758         return temp;       /* return the prev base */
759       }
760       break;
761
762     case UDM_GETBUDDY:
763       if (wParam || lParam)
764         UNKNOWN_PARAM(UDM_GETBUDDY, wParam, lParam);
765       return infoPtr->Buddy;
766
767     case UDM_SETBUDDY:
768       if (lParam)
769         UNKNOWN_PARAM(UDM_SETBUDDY, wParam, lParam);
770       temp = infoPtr->Buddy;
771       infoPtr->Buddy = wParam;
772       UPDOWN_SetBuddy (hwnd, wParam);
773       TRACE("UpDown Ctrl new buddy(%04x), hwnd=%04x\n", 
774                      infoPtr->Buddy, hwnd);
775       return temp;
776
777     case UDM_GETPOS:
778       if (wParam || lParam)
779         UNKNOWN_PARAM(UDM_GETPOS, wParam, lParam);
780       temp = UPDOWN_GetBuddyInt (hwnd);
781       return MAKELONG(infoPtr->CurVal, temp ? 0 : 1);
782
783     case UDM_SETPOS:
784       if (wParam || HIWORD(lParam))
785         UNKNOWN_PARAM(UDM_GETPOS, wParam, lParam);
786       temp = SLOWORD(lParam);
787       TRACE("UpDown Ctrl new value(%d), hwnd=%04x\n",
788                      temp, hwnd);
789       if(!UPDOWN_InBounds(hwnd, temp)){
790         if(temp < infoPtr->MinVal)  
791           temp = infoPtr->MinVal;
792         if(temp > infoPtr->MaxVal)
793           temp = infoPtr->MaxVal;
794       }
795       wParam = infoPtr->CurVal; /* save prev value   */
796       infoPtr->CurVal = temp;   /* set the new value */
797       if(dwStyle & UDS_SETBUDDYINT)
798         UPDOWN_SetBuddyInt (hwnd);
799       return wParam;            /* return prev value */
800       
801     case UDM_GETRANGE:
802       if (wParam || lParam)
803         UNKNOWN_PARAM(UDM_GETRANGE, wParam, lParam);
804       return MAKELONG(infoPtr->MaxVal, infoPtr->MinVal);
805
806     case UDM_SETRANGE:
807       if (wParam)
808         UNKNOWN_PARAM(UDM_SETRANGE, wParam, lParam); /* we must have:     */
809       infoPtr->MaxVal = SLOWORD(lParam); /* UD_MINVAL <= Max <= UD_MAXVAL */
810       infoPtr->MinVal = SHIWORD(lParam); /* UD_MINVAL <= Min <= UD_MAXVAL */
811                                          /* |Max-Min| <= UD_MAXVAL        */
812       TRACE("UpDown Ctrl new range(%d to %d), hwnd=%04x\n", 
813                      infoPtr->MinVal, infoPtr->MaxVal, hwnd);
814       break;                             
815
816     case UDM_GETRANGE32:
817       if (wParam)
818         *(LPINT)wParam = infoPtr->MinVal;
819       if (lParam)
820         *(LPINT)lParam = infoPtr->MaxVal;
821       break;
822
823     case UDM_SETRANGE32:
824       infoPtr->MinVal = (INT)wParam;
825       infoPtr->MaxVal = (INT)lParam;
826       if (infoPtr->MaxVal <= infoPtr->MinVal)
827         infoPtr->MaxVal = infoPtr->MinVal + 1;
828       TRACE("UpDown Ctrl new range(%d to %d), hwnd=%04x\n", 
829                      infoPtr->MinVal, infoPtr->MaxVal, hwnd);
830       break;
831
832     default: 
833       if (message >= WM_USER) 
834         ERR("unknown msg %04x wp=%04x lp=%08lx\n", 
835              message, wParam, lParam);
836       return DefWindowProcA (hwnd, message, wParam, lParam); 
837     } 
838
839     return 0;
840 }
841
842
843 /***********************************************************************
844  *              UPDOWN_Register [Internal]
845  *
846  * Registers the updown window class.
847  */
848
849 VOID
850 UPDOWN_Register(void)
851 {
852     WNDCLASSA wndClass;
853
854     if( GlobalFindAtomA( UPDOWN_CLASSA ) ) return;
855
856     ZeroMemory( &wndClass, sizeof( WNDCLASSA ) );
857     wndClass.style         = CS_GLOBALCLASS | CS_VREDRAW;
858     wndClass.lpfnWndProc   = (WNDPROC)UpDownWindowProc;
859     wndClass.cbClsExtra    = 0;
860     wndClass.cbWndExtra    = sizeof(UPDOWN_INFO*);
861     wndClass.hCursor       = LoadCursorA( 0, IDC_ARROWA );
862     wndClass.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
863     wndClass.lpszClassName = UPDOWN_CLASSA;
864  
865     RegisterClassA( &wndClass );
866 }
867
868
869 /***********************************************************************
870  *              UPDOWN_Unregister       [Internal]
871  *
872  * Unregisters the updown window class.
873  */
874
875 VOID
876 UPDOWN_Unregister (void)
877 {
878     if (GlobalFindAtomA (UPDOWN_CLASSA))
879         UnregisterClassA (UPDOWN_CLASSA, (HINSTANCE)NULL);
880 }
881