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