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