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