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