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