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