4 * Copyright 1997, 2002 Dimitrie O. Paun
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.
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.
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
33 #include "wine/unicode.h"
34 #include "wine/debug.h"
36 WINE_DEFAULT_DEBUG_CHANNEL(updown);
40 HWND Self; /* Handle to this up-down control */
41 UINT AccelCount; /* Number of elements in AccelVect */
42 UDACCEL* AccelVect; /* Vector containing AccelCount elements */
43 INT AccelIndex; /* Current accel index, -1 if not accel'ing */
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 INT BuddyType; /* Remembers the buddy type BUDDY_TYPE_* */
50 INT Flags; /* Internal Flags FLAG_* */
51 BOOL UnicodeFormat; /* Marks the use of Unicode internally */
54 /* Control configuration constants */
56 #define INITIAL_DELAY 500 /* initial timer until auto-inc kicks in */
57 #define AUTOPRESS_DELAY 250 /* time to keep arrow pressed on KEY_DOWN */
58 #define REPEAT_DELAY 50 /* delay between auto-increments */
60 #define DEFAULT_WIDTH 14 /* default width of the ctrl */
61 #define DEFAULT_XSEP 0 /* default separation between buddy and ctrl */
62 #define DEFAULT_ADDTOP 0 /* amount to extend above the buddy window */
63 #define DEFAULT_ADDBOT 0 /* amount to extend below the buddy window */
64 #define DEFAULT_BUDDYBORDER 2 /* Width/height of the buddy border */
65 #define DEFAULT_BUDDYSPACER 2 /* Spacer between the buddy and the ctrl */
70 #define FLAG_INCR 0x01
71 #define FLAG_DECR 0x02
72 #define FLAG_MOUSEIN 0x04
73 #define FLAG_PRESSED 0x08
74 #define FLAG_ARROW (FLAG_INCR | FLAG_DECR)
76 #define BUDDY_TYPE_UNKNOWN 0
77 #define BUDDY_TYPE_LISTBOX 1
78 #define BUDDY_TYPE_EDIT 2
80 #define TIMER_AUTOREPEAT 1
82 #define TIMER_AUTOPRESS 3
84 #define BUDDY_UPDOWN_HWND "buddyUpDownHWND"
85 #define BUDDY_SUPERCLASS_WNDPROC "buddySupperClassWndProc"
87 #define UNKNOWN_PARAM(msg, wParam, lParam) WARN(\
88 "Unknown parameter(s) for message " #msg \
89 "(%04x): wp=%04x lp=%08lx\n", msg, wParam, lParam);
91 #define UPDOWN_GetInfoPtr(hwnd) ((UPDOWN_INFO *)GetWindowLongA (hwnd,0))
92 #define COUNT_OF(a) (sizeof(a)/sizeof(a[0]))
94 static void UPDOWN_DoAction (UPDOWN_INFO *infoPtr, int delta, int action);
96 /***********************************************************************
98 * Tests if our buddy is an edit control.
100 static inline BOOL UPDOWN_IsBuddyEdit(UPDOWN_INFO *infoPtr)
102 return infoPtr->BuddyType == BUDDY_TYPE_EDIT;
105 /***********************************************************************
106 * UPDOWN_IsBuddyListbox
107 * Tests if our buddy is a listbox control.
109 static inline BOOL UPDOWN_IsBuddyListbox(UPDOWN_INFO *infoPtr)
111 return infoPtr->BuddyType == BUDDY_TYPE_LISTBOX;
114 /***********************************************************************
116 * Tests if a given value 'val' is between the Min&Max limits
118 static BOOL UPDOWN_InBounds(UPDOWN_INFO *infoPtr, int val)
120 if(infoPtr->MaxVal > infoPtr->MinVal)
121 return (infoPtr->MinVal <= val) && (val <= infoPtr->MaxVal);
123 return (infoPtr->MaxVal <= val) && (val <= infoPtr->MinVal);
126 /***********************************************************************
128 * Change the current value by delta.
129 * It returns TRUE is the value was changed successfuly, or FALSE
130 * if the value was not changed, as it would go out of bounds.
132 static BOOL UPDOWN_OffsetVal(UPDOWN_INFO *infoPtr, int delta)
134 /* check if we can do the modification first */
135 if(!UPDOWN_InBounds (infoPtr, infoPtr->CurVal+delta)) {
136 if (GetWindowLongW (infoPtr->Self, GWL_STYLE) & UDS_WRAP) {
137 delta += (delta < 0 ? -1 : 1) *
138 (infoPtr->MaxVal < infoPtr->MinVal ? -1 : 1) *
139 (infoPtr->MinVal - infoPtr->MaxVal) +
140 (delta < 0 ? 1 : -1);
144 infoPtr->CurVal += delta;
148 /***********************************************************************
149 * UPDOWN_HasBuddyBorder
151 * When we have a buddy set and that we are aligned on our buddy, we
152 * want to draw a sunken edge to make like we are part of that control.
154 static BOOL UPDOWN_HasBuddyBorder(UPDOWN_INFO* infoPtr)
156 DWORD dwStyle = GetWindowLongW (infoPtr->Self, GWL_STYLE);
158 return ( ((dwStyle & (UDS_ALIGNLEFT | UDS_ALIGNRIGHT)) != 0) &&
159 UPDOWN_IsBuddyEdit(infoPtr) );
162 /***********************************************************************
163 * UPDOWN_GetArrowRect
164 * wndPtr - pointer to the up-down wnd
165 * rect - will hold the rectangle
166 * arrow - FLAG_INCR to get the "increment" rect (up or right)
167 * FLAG_DECR to get the "decrement" rect (down or left)
168 * If both flags are pressent, the envelope is returned.
170 static void UPDOWN_GetArrowRect (UPDOWN_INFO* infoPtr, RECT *rect, int arrow)
172 DWORD dwStyle = GetWindowLongW (infoPtr->Self, GWL_STYLE);
174 GetClientRect (infoPtr->Self, rect);
177 * Make sure we calculate the rectangle to fit even if we draw the
180 if (UPDOWN_HasBuddyBorder(infoPtr)) {
181 if (dwStyle & UDS_ALIGNLEFT)
182 rect->left += DEFAULT_BUDDYBORDER;
184 rect->right -= DEFAULT_BUDDYBORDER;
186 InflateRect(rect, 0, -DEFAULT_BUDDYBORDER);
189 /* now figure out if we need a space away from the buddy */
190 if ( IsWindow(infoPtr->Buddy) ) {
191 if (dwStyle & UDS_ALIGNLEFT) rect->right -= DEFAULT_BUDDYSPACER;
192 else rect->left += DEFAULT_BUDDYSPACER;
196 * We're calculating the midpoint to figure-out where the
197 * separation between the buttons will lay. We make sure that we
198 * round the uneven numbers by adding 1.
200 if (dwStyle & UDS_HORZ) {
201 int len = rect->right - rect->left + 1; /* compute the width */
202 if (arrow & FLAG_INCR)
203 rect->left = rect->left + len/2;
204 if (arrow & FLAG_DECR)
205 rect->right = rect->left + len/2 - 1;
207 int len = rect->bottom - rect->top + 1; /* compute the height */
208 if (arrow & FLAG_INCR)
209 rect->bottom = rect->top + len/2 - 1;
210 if (arrow & FLAG_DECR)
211 rect->top = rect->top + len/2;
215 /***********************************************************************
216 * UPDOWN_GetArrowFromPoint
217 * Returns the rectagle (for the up or down arrow) that contains pt.
218 * If it returns the up rect, it returns TRUE.
219 * If it returns the down rect, it returns FALSE.
221 static BOOL UPDOWN_GetArrowFromPoint (UPDOWN_INFO* infoPtr, RECT *rect, POINT pt)
223 UPDOWN_GetArrowRect (infoPtr, rect, FLAG_INCR);
224 if(PtInRect(rect, pt)) return FLAG_INCR;
226 UPDOWN_GetArrowRect (infoPtr, rect, FLAG_DECR);
227 if(PtInRect(rect, pt)) return FLAG_DECR;
233 /***********************************************************************
234 * UPDOWN_GetThousandSep
235 * Returns the thousand sep. If an error occurs, it returns ','.
237 static WCHAR UPDOWN_GetThousandSep()
241 if(GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, sep, 2) != 1)
247 /***********************************************************************
249 * Tries to read the pos from the buddy window and if it succeeds,
250 * it stores it in the control's CurVal
252 * TRUE - if it read the integer from the buddy successfully
253 * FALSE - if an error occurred
255 static BOOL UPDOWN_GetBuddyInt (UPDOWN_INFO *infoPtr)
257 WCHAR txt[20], sep, *src, *dst;
260 if (!IsWindow(infoPtr->Buddy))
263 /*if the buddy is a list window, we must set curr index */
264 if (UPDOWN_IsBuddyListbox(infoPtr)) {
265 newVal = SendMessageW(infoPtr->Buddy, LB_GETCARETINDEX, 0, 0);
266 if(newVal < 0) return FALSE;
268 /* we have a regular window, so will get the text */
269 if (!GetWindowTextW(infoPtr->Buddy, txt, COUNT_OF(txt))) return FALSE;
271 sep = UPDOWN_GetThousandSep();
273 /* now get rid of the separators */
274 for(src = dst = txt; *src; src++)
275 if(*src != sep) *dst++ = *src;
278 /* try to convert the number and validate it */
279 newVal = strtolW(txt, &src, infoPtr->Base);
280 if(*src || !UPDOWN_InBounds (infoPtr, newVal)) return FALSE;
283 TRACE("new value(%d) from buddy (old=%d)\n", newVal, infoPtr->CurVal);
284 infoPtr->CurVal = newVal;
289 /***********************************************************************
291 * Tries to set the pos to the buddy window based on current pos
293 * TRUE - if it set the caption of the buddy successfully
294 * FALSE - if an error occurred
296 static BOOL UPDOWN_SetBuddyInt (UPDOWN_INFO *infoPtr)
298 WCHAR fmt[3] = { '%', 'd', '\0' };
302 if (!IsWindow(infoPtr->Buddy)) return FALSE;
304 TRACE("set new value(%d) to buddy.\n", infoPtr->CurVal);
306 /*if the buddy is a list window, we must set curr index */
307 if (UPDOWN_IsBuddyListbox(infoPtr)) {
308 return SendMessageW(infoPtr->Buddy, LB_SETCURSEL, infoPtr->CurVal, 0) != LB_ERR;
311 /* Regular window, so set caption to the number */
312 if (infoPtr->Base == 16) fmt[1] = 'X';
313 len = swprintf(txt, fmt, infoPtr->CurVal);
316 /* Do thousands seperation if necessary */
317 if (!(GetWindowLongW (infoPtr->Self, GWL_STYLE) & UDS_NOTHOUSANDS) && (len > 3)) {
318 WCHAR tmp[COUNT_OF(txt)], *src = tmp, *dst = txt;
319 WCHAR sep = UPDOWN_GetThousandSep();
322 memcpy(tmp, txt, sizeof(txt));
323 if (start == 0) start = 3;
326 for (len=0; *src; len++) {
327 if (len % 3 == 0) *dst++ = sep;
333 return SetWindowTextW(infoPtr->Buddy, txt);
336 /***********************************************************************
339 * Draw the arrows. The background need not be erased.
341 static LRESULT UPDOWN_Draw (UPDOWN_INFO *infoPtr, HDC hdc)
343 DWORD dwStyle = GetWindowLongW (infoPtr->Self, GWL_STYLE);
347 /* Draw the common border between ourselves and our buddy */
348 if (UPDOWN_HasBuddyBorder(infoPtr)) {
349 GetClientRect(infoPtr->Self, &rect);
350 DrawEdge(hdc, &rect, EDGE_SUNKEN,
352 (dwStyle & UDS_ALIGNLEFT ? BF_LEFT : BF_RIGHT));
355 /* Draw the incr button */
356 UPDOWN_GetArrowRect (infoPtr, &rect, FLAG_INCR);
357 pressed = (infoPtr->Flags & FLAG_PRESSED) && (infoPtr->Flags & FLAG_INCR);
358 hot = (infoPtr->Flags & FLAG_INCR) && (infoPtr->Flags & FLAG_MOUSEIN);
359 DrawFrameControl(hdc, &rect, DFC_SCROLL,
360 (dwStyle & UDS_HORZ ? DFCS_SCROLLRIGHT : DFCS_SCROLLUP) |
361 ((dwStyle & UDS_HOTTRACK) && hot ? DFCS_HOT : 0) |
362 (pressed ? DFCS_PUSHED : 0) |
363 (dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) );
365 /* Draw the decr button */
366 UPDOWN_GetArrowRect(infoPtr, &rect, FLAG_DECR);
367 pressed = (infoPtr->Flags & FLAG_PRESSED) && (infoPtr->Flags & FLAG_DECR);
368 hot = (infoPtr->Flags & FLAG_DECR) && (infoPtr->Flags & FLAG_MOUSEIN);
369 DrawFrameControl(hdc, &rect, DFC_SCROLL,
370 (dwStyle & UDS_HORZ ? DFCS_SCROLLLEFT : DFCS_SCROLLDOWN) |
371 ((dwStyle & UDS_HOTTRACK) && hot ? DFCS_HOT : 0) |
372 (pressed ? DFCS_PUSHED : 0) |
373 (dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) );
378 /***********************************************************************
381 * Asynchronous drawing (must ONLY be used in WM_PAINT).
384 static LRESULT UPDOWN_Paint (UPDOWN_INFO *infoPtr, HDC hdc)
387 if (hdc) return UPDOWN_Draw (infoPtr, hdc);
388 hdc = BeginPaint (infoPtr->Self, &ps);
389 UPDOWN_Draw (infoPtr, hdc);
390 EndPaint (infoPtr->Self, &ps);
394 /***********************************************************************
397 * Handle key presses (up & down) when we have to do so
399 static LRESULT UPDOWN_KeyPressed(UPDOWN_INFO *infoPtr, int key)
403 if (key == VK_UP) arrow = FLAG_INCR;
404 else if (key == VK_DOWN) arrow = FLAG_DECR;
407 UPDOWN_GetBuddyInt (infoPtr);
408 infoPtr->Flags &= ~FLAG_ARROW;
409 infoPtr->Flags |= FLAG_PRESSED | arrow;
410 InvalidateRect (infoPtr->Self, NULL, FALSE);
411 SetTimer(infoPtr->Self, TIMER_AUTOPRESS, AUTOPRESS_DELAY, 0);
412 UPDOWN_DoAction (infoPtr, 1, arrow);
416 /***********************************************************************
417 * UPDOWN_Buddy_SubclassProc used to handle messages sent to the buddy
420 static LRESULT CALLBACK
421 UPDOWN_Buddy_SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
423 WNDPROC superClassWndProc = (WNDPROC)GetPropA(hwnd, BUDDY_SUPERCLASS_WNDPROC);
424 TRACE("hwnd=%04x, wndProc=%d, uMsg=%04x, wParam=%d, lParam=%d\n",
425 hwnd, (INT)superClassWndProc, uMsg, wParam, (UINT)lParam);
427 if (uMsg == WM_KEYDOWN) {
428 HWND upDownHwnd = GetPropA(hwnd, BUDDY_UPDOWN_HWND);
430 UPDOWN_KeyPressed(UPDOWN_GetInfoPtr(upDownHwnd), (int)wParam);
433 return CallWindowProcW( superClassWndProc, hwnd, uMsg, wParam, lParam);
436 /***********************************************************************
438 * Tests if 'bud' is a valid window handle. If not, returns FALSE.
439 * Else, sets it as a new Buddy.
440 * Then, it should subclass the buddy
441 * If window has the UDS_ARROWKEYS, it subcalsses the buddy window to
442 * process the UP/DOWN arrow keys.
443 * If window has the UDS_ALIGNLEFT or UDS_ALIGNRIGHT style
444 * the size/pos of the buddy and the control are adjusted accordingly.
446 static BOOL UPDOWN_SetBuddy (UPDOWN_INFO* infoPtr, HWND bud)
448 DWORD dwStyle = GetWindowLongW (infoPtr->Self, GWL_STYLE);
449 RECT budRect; /* new coord for the buddy */
450 int x, width; /* new x position and width for the up-down */
451 WNDPROC baseWndProc, currWndProc;
454 /* Is it a valid bud? */
455 if(!IsWindow(bud)) return FALSE;
457 TRACE("(hwnd=%04x, bud=%04x)\n", infoPtr->Self, bud);
459 /* there is already a body assigned */
460 if (infoPtr->Buddy) RemovePropA(infoPtr->Buddy, BUDDY_UPDOWN_HWND);
462 /* Store buddy window handle */
463 infoPtr->Buddy = bud;
465 /* keep upDown ctrl hwnd in a buddy property */
466 SetPropA( bud, BUDDY_UPDOWN_HWND, infoPtr->Self);
468 /* Store buddy window class type */
469 infoPtr->BuddyType = BUDDY_TYPE_UNKNOWN;
470 if (GetClassNameA(bud, buddyClass, COUNT_OF(buddyClass))) {
471 if (lstrcmpiA(buddyClass, "Edit") == 0)
472 infoPtr->BuddyType = BUDDY_TYPE_EDIT;
473 else if (lstrcmpiA(buddyClass, "Listbox") == 0)
474 infoPtr->BuddyType = BUDDY_TYPE_LISTBOX;
477 if(dwStyle & UDS_ARROWKEYS){
478 /* Note that I don't clear the BUDDY_SUPERCLASS_WNDPROC property
479 when we reset the upDown ctrl buddy to another buddy because it is not
480 good to break the window proc chain. */
481 currWndProc = (WNDPROC) GetWindowLongW(bud, GWL_WNDPROC);
482 if (currWndProc != UPDOWN_Buddy_SubclassProc) {
483 baseWndProc = (WNDPROC)SetWindowLongW(bud, GWL_WNDPROC, (LPARAM)UPDOWN_Buddy_SubclassProc);
484 SetPropA(bud, BUDDY_SUPERCLASS_WNDPROC, (HANDLE)baseWndProc);
488 /* Get the rect of the buddy relative to its parent */
489 GetWindowRect(infoPtr->Buddy, &budRect);
490 MapWindowPoints(HWND_DESKTOP, GetParent(infoPtr->Buddy), (POINT *)(&budRect.left), 2);
492 /* now do the positioning */
493 if (dwStyle & UDS_ALIGNLEFT) {
495 budRect.left += DEFAULT_WIDTH + DEFAULT_XSEP;
496 } else if (dwStyle & UDS_ALIGNRIGHT) {
497 budRect.right -= DEFAULT_WIDTH + DEFAULT_XSEP;
498 x = budRect.right+DEFAULT_XSEP;
500 x = budRect.right+DEFAULT_XSEP;
503 /* first adjust the buddy to accomodate the up/down */
504 SetWindowPos(infoPtr->Buddy, 0, budRect.left, budRect.top,
505 budRect.right - budRect.left, budRect.bottom - budRect.top,
506 SWP_NOACTIVATE|SWP_NOZORDER);
508 /* now position the up/down */
509 /* Since the UDS_ALIGN* flags were used, */
510 /* we will pick the position and size of the window. */
511 width = DEFAULT_WIDTH;
514 * If the updown has a buddy border, it has to overlap with the buddy
515 * to look as if it is integrated with the buddy control.
516 * We nudge the control or change it size to overlap.
518 if (UPDOWN_HasBuddyBorder(infoPtr)) {
519 if(dwStyle & UDS_ALIGNLEFT)
520 width += DEFAULT_BUDDYBORDER;
522 x -= DEFAULT_BUDDYBORDER;
525 SetWindowPos(infoPtr->Self, infoPtr->Buddy, x,
526 budRect.top - DEFAULT_ADDTOP, width,
527 budRect.bottom - budRect.top + DEFAULT_ADDTOP + DEFAULT_ADDBOT,
533 /***********************************************************************
536 * This function increments/decrements the CurVal by the
537 * 'delta' amount according to the 'action' flag which can be a
538 * combination of FLAG_INCR and FLAG_DECR
539 * It notifies the parent as required.
540 * It handles wraping and non-wraping correctly.
541 * It is assumed that delta>0
543 static void UPDOWN_DoAction (UPDOWN_INFO *infoPtr, int delta, int action)
545 DWORD dwStyle = GetWindowLongW (infoPtr->Self, GWL_STYLE);
548 TRACE("%d by %d\n", action, delta);
550 /* check if we can do the modification first */
551 delta *= (action & FLAG_INCR ? 1 : -1) * (infoPtr->MaxVal < infoPtr->MinVal ? -1 : 1);
552 if ( (action & FLAG_INCR) && (action & FLAG_DECR) ) delta = 0;
554 /* We must notify parent now to obtain permission */
555 ni.iPos = infoPtr->CurVal;
557 ni.hdr.hwndFrom = infoPtr->Self;
558 ni.hdr.idFrom = GetWindowLongW (infoPtr->Self, GWL_ID);
559 ni.hdr.code = UDN_DELTAPOS;
560 if (!SendMessageW(GetParent (infoPtr->Self), WM_NOTIFY,
561 (WPARAM)ni.hdr.idFrom, (LPARAM)&ni)) {
562 /* Parent said: OK to adjust */
564 /* Now adjust value with (maybe new) delta */
565 if (UPDOWN_OffsetVal (infoPtr, ni.iDelta)) {
566 /* Now take care about our buddy */
567 if (dwStyle & UDS_SETBUDDYINT) UPDOWN_SetBuddyInt (infoPtr);
571 /* Also, notify it. This message is sent in any case. */
572 SendMessageW( GetParent(infoPtr->Self),
573 dwStyle & UDS_HORZ ? WM_HSCROLL : WM_VSCROLL,
574 MAKELONG(SB_THUMBPOSITION, infoPtr->CurVal), infoPtr->Self);
577 /***********************************************************************
580 * Returns TRUE if it is enabled as well as its buddy (if any)
583 static BOOL UPDOWN_IsEnabled (UPDOWN_INFO *infoPtr)
585 if(GetWindowLongW (infoPtr->Self, GWL_STYLE) & WS_DISABLED)
588 return IsWindowEnabled(infoPtr->Buddy);
592 /***********************************************************************
595 * Deletes any timers, releases the mouse and does redraw if necessary.
596 * If the control is not in "capture" mode, it does nothing.
597 * If the control was not in cancel mode, it returns FALSE.
598 * If the control was in cancel mode, it returns TRUE.
600 static BOOL UPDOWN_CancelMode (UPDOWN_INFO *infoPtr)
602 if (!(infoPtr->Flags & FLAG_PRESSED)) return FALSE;
604 KillTimer (infoPtr->Self, TIMER_AUTOREPEAT);
605 KillTimer (infoPtr->Self, TIMER_ACCEL);
606 KillTimer (infoPtr->Self, TIMER_AUTOPRESS);
608 if (GetCapture() == infoPtr->Self) {
610 hdr.hwndFrom = infoPtr->Self;
611 hdr.idFrom = GetWindowLongW (infoPtr->Self, GWL_ID);
612 hdr.code = NM_RELEASEDCAPTURE;
613 SendMessageW(GetParent (infoPtr->Self), WM_NOTIFY, hdr.idFrom, (LPARAM)&hdr);
617 infoPtr->Flags &= ~FLAG_PRESSED;
618 InvalidateRect (infoPtr->Self, NULL, FALSE);
623 /***********************************************************************
624 * UPDOWN_HandleMouseEvent
626 * Handle a mouse event for the updown.
627 * 'pt' is the location of the mouse event in client or
628 * windows coordinates.
630 static void UPDOWN_HandleMouseEvent (UPDOWN_INFO *infoPtr, UINT msg, POINT pt)
632 DWORD dwStyle = GetWindowLongW (infoPtr->Self, GWL_STYLE);
638 case WM_LBUTTONDOWN: /* Initialise mouse tracking */
639 /* If we are inside an arrow, then nothing to do */
640 if(!(infoPtr->Flags & FLAG_MOUSEIN)) return;
642 /* If the buddy is an edit, will set focus to it */
643 if (UPDOWN_IsBuddyEdit(infoPtr)) SetFocus(infoPtr->Buddy);
645 /* Now see which one is the 'active' arrow */
646 if (infoPtr->Flags & FLAG_ARROW) {
648 /* Update the CurVal if necessary */
649 if (dwStyle & UDS_SETBUDDYINT) UPDOWN_GetBuddyInt (infoPtr);
651 /* Set up the correct flags */
652 infoPtr->Flags |= FLAG_PRESSED;
654 /* repaint the control */
655 InvalidateRect (infoPtr->Self, NULL, FALSE);
657 /* process the click */
658 UPDOWN_DoAction (infoPtr, 1, infoPtr->Flags & FLAG_ARROW);
660 /* now capture all mouse messages */
661 SetCapture (infoPtr->Self);
663 /* and startup the first timer */
664 SetTimer(infoPtr->Self, TIMER_AUTOREPEAT, INITIAL_DELAY, 0);
669 /* save the flags to see if any got modified */
670 temp = infoPtr->Flags;
672 /* Now see which one is the 'active' arrow */
673 arrow = UPDOWN_GetArrowFromPoint (infoPtr, &rect, pt);
675 /* Update the flags if we are in/out */
676 infoPtr->Flags &= ~(FLAG_MOUSEIN | FLAG_ARROW);
678 infoPtr->Flags |= FLAG_MOUSEIN | arrow;
680 if(infoPtr->AccelIndex != -1) infoPtr->AccelIndex = 0;
683 /* If state changed, redraw the control */
684 if(temp != infoPtr->Flags)
685 InvalidateRect (infoPtr->Self, &rect, FALSE);
689 ERR("Impossible case (msg=%x)!\n", msg);
694 /***********************************************************************
697 static LRESULT WINAPI UpDownWindowProc(HWND hwnd, UINT message, WPARAM wParam,
700 UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
701 DWORD dwStyle = GetWindowLongW (hwnd, GWL_STYLE);
704 if (!infoPtr && (message != WM_CREATE))
705 return DefWindowProcW (hwnd, message, wParam, lParam);
710 SetWindowLongW (hwnd, GWL_STYLE, dwStyle & ~WS_BORDER);
711 infoPtr = (UPDOWN_INFO*)COMCTL32_Alloc (sizeof(UPDOWN_INFO));
712 SetWindowLongW (hwnd, 0, (DWORD)infoPtr);
714 /* initialize the info struct */
715 infoPtr->Self = hwnd;
716 infoPtr->AccelCount = 0;
717 infoPtr->AccelVect = 0;
718 infoPtr->AccelIndex = -1;
721 infoPtr->MaxVal = 9999;
722 infoPtr->Base = 10; /* Default to base 10 */
723 infoPtr->Buddy = 0; /* No buddy window yet */
724 infoPtr->Flags = 0; /* And no flags */
726 /* Do we pick the buddy win ourselves? */
727 if (dwStyle & UDS_AUTOBUDDY)
728 UPDOWN_SetBuddy (infoPtr, GetWindow (hwnd, GW_HWNDPREV));
730 TRACE("UpDown Ctrl creation, hwnd=%04x\n", hwnd);
734 if(infoPtr->AccelVect) COMCTL32_Free (infoPtr->AccelVect);
736 if(infoPtr->Buddy) RemovePropA(infoPtr->Buddy, BUDDY_UPDOWN_HWND);
738 COMCTL32_Free (infoPtr);
739 SetWindowLongW (hwnd, 0, 0);
740 TRACE("UpDown Ctrl destruction, hwnd=%04x\n", hwnd);
744 if (dwStyle & WS_DISABLED) UPDOWN_CancelMode (infoPtr);
745 InvalidateRect (infoPtr->Self, NULL, FALSE);
749 /* is this the auto-press timer? */
750 if(wParam == TIMER_AUTOPRESS) {
751 KillTimer(hwnd, TIMER_AUTOPRESS);
752 infoPtr->Flags &= ~(FLAG_PRESSED | FLAG_ARROW);
753 InvalidateRect(infoPtr->Self, NULL, FALSE);
756 /* if initial timer, kill it and start the repeat timer */
757 if(wParam == TIMER_AUTOREPEAT) {
758 KillTimer(hwnd, TIMER_AUTOREPEAT);
759 /* if no accel info given, used default timer */
760 if(infoPtr->AccelCount==0 || infoPtr->AccelVect==0) {
761 infoPtr->AccelIndex = -1;
764 infoPtr->AccelIndex = 0; /* otherwise, use it */
765 temp = infoPtr->AccelVect[infoPtr->AccelIndex].nSec * 1000 + 1;
767 SetTimer(hwnd, TIMER_ACCEL, temp, 0);
770 /* now, if the mouse is above us, do the thing...*/
771 if(infoPtr->Flags & FLAG_MOUSEIN) {
772 temp = infoPtr->AccelIndex == -1 ? 1 : infoPtr->AccelVect[infoPtr->AccelIndex].nInc;
773 UPDOWN_DoAction(infoPtr, temp, infoPtr->Flags & FLAG_ARROW);
775 if(infoPtr->AccelIndex != -1 && infoPtr->AccelIndex < infoPtr->AccelCount-1) {
776 KillTimer(hwnd, TIMER_ACCEL);
777 infoPtr->AccelIndex++; /* move to the next accel info */
778 temp = infoPtr->AccelVect[infoPtr->AccelIndex].nSec * 1000 + 1;
779 /* make sure we have at least 1ms intervals */
780 SetTimer(hwnd, TIMER_ACCEL, temp, 0);
786 return UPDOWN_CancelMode (infoPtr);
789 if (GetCapture() != infoPtr->Self) break;
791 if ( (infoPtr->Flags & FLAG_MOUSEIN) &&
792 (infoPtr->Flags & FLAG_ARROW) ) {
794 SendMessageW( GetParent(hwnd),
795 dwStyle & UDS_HORZ ? WM_HSCROLL : WM_VSCROLL,
796 MAKELONG(SB_ENDSCROLL, infoPtr->CurVal), hwnd);
797 if (UPDOWN_IsBuddyEdit(infoPtr))
798 SendMessageW(infoPtr->Buddy, EM_SETSEL, 0, MAKELONG(0, -1));
800 UPDOWN_CancelMode(infoPtr);
805 if(UPDOWN_IsEnabled(infoPtr)){
807 pt.x = SLOWORD(lParam);
808 pt.y = SHIWORD(lParam);
809 UPDOWN_HandleMouseEvent (infoPtr, message, pt );
814 if((dwStyle & UDS_ARROWKEYS) && UPDOWN_IsEnabled(infoPtr)) {
815 return UPDOWN_KeyPressed(infoPtr, (int)wParam);
820 return UPDOWN_Paint (infoPtr, (HDC)wParam);
823 if (wParam==0 && lParam==0) return infoPtr->AccelCount;
824 if (wParam && lParam) {
825 temp = min(infoPtr->AccelCount, wParam);
826 memcpy((void *)lParam, infoPtr->AccelVect, temp*sizeof(UDACCEL));
829 UNKNOWN_PARAM(UDM_GETACCEL, wParam, lParam);
833 TRACE("UpDown Ctrl new accel info, hwnd=%04x\n", hwnd);
834 if(infoPtr->AccelVect) {
835 COMCTL32_Free (infoPtr->AccelVect);
836 infoPtr->AccelCount = 0;
837 infoPtr->AccelVect = 0;
839 if(wParam==0) return TRUE;
840 infoPtr->AccelVect = COMCTL32_Alloc (wParam*sizeof(UDACCEL));
841 if(infoPtr->AccelVect == 0) return FALSE;
842 memcpy(infoPtr->AccelVect, (void*)lParam, wParam*sizeof(UDACCEL));
846 if (wParam || lParam) UNKNOWN_PARAM(UDM_GETBASE, wParam, lParam);
847 return infoPtr->Base;
850 TRACE("UpDown Ctrl new base(%d), hwnd=%04x\n", wParam, hwnd);
851 if ( !(wParam==10 || wParam==16) || lParam)
852 UNKNOWN_PARAM(UDM_SETBASE, wParam, lParam);
853 if (wParam==10 || wParam==16) {
854 temp = infoPtr->Base;
855 infoPtr->Base = wParam;
861 if (wParam || lParam) UNKNOWN_PARAM(UDM_GETBUDDY, wParam, lParam);
862 return infoPtr->Buddy;
865 if (lParam) UNKNOWN_PARAM(UDM_SETBUDDY, wParam, lParam);
866 temp = infoPtr->Buddy;
867 UPDOWN_SetBuddy (infoPtr, wParam);
871 if (wParam || lParam) UNKNOWN_PARAM(UDM_GETPOS, wParam, lParam);
872 temp = UPDOWN_GetBuddyInt (infoPtr);
873 return MAKELONG(infoPtr->CurVal, temp ? 0 : 1);
876 if (wParam || HIWORD(lParam)) UNKNOWN_PARAM(UDM_GETPOS, wParam, lParam);
877 temp = SLOWORD(lParam);
878 TRACE("UpDown Ctrl new value(%d), hwnd=%04x\n", temp, hwnd);
879 if(!UPDOWN_InBounds(infoPtr, temp)) {
880 if(temp < infoPtr->MinVal) temp = infoPtr->MinVal;
881 if(temp > infoPtr->MaxVal) temp = infoPtr->MaxVal;
883 wParam = infoPtr->CurVal;
884 infoPtr->CurVal = temp;
885 if(dwStyle & UDS_SETBUDDYINT) UPDOWN_SetBuddyInt (infoPtr);
886 return wParam; /* return prev value */
889 if (wParam || lParam) UNKNOWN_PARAM(UDM_GETRANGE, wParam, lParam);
890 return MAKELONG(infoPtr->MaxVal, infoPtr->MinVal);
893 if (wParam) UNKNOWN_PARAM(UDM_SETRANGE, wParam, lParam);
895 infoPtr->MaxVal = SLOWORD(lParam); /* UD_MINVAL <= Max <= UD_MAXVAL */
896 infoPtr->MinVal = SHIWORD(lParam); /* UD_MINVAL <= Min <= UD_MAXVAL */
897 /* |Max-Min| <= UD_MAXVAL */
898 TRACE("UpDown Ctrl new range(%d to %d), hwnd=%04x\n",
899 infoPtr->MinVal, infoPtr->MaxVal, hwnd);
903 if (wParam) *(LPINT)wParam = infoPtr->MinVal;
904 if (lParam) *(LPINT)lParam = infoPtr->MaxVal;
908 infoPtr->MinVal = (INT)wParam;
909 infoPtr->MaxVal = (INT)lParam;
910 if (infoPtr->MaxVal <= infoPtr->MinVal)
911 infoPtr->MaxVal = infoPtr->MinVal + 1;
912 TRACE("UpDown Ctrl new range(%d to %d), hwnd=%04x\n",
913 infoPtr->MinVal, infoPtr->MaxVal, hwnd);
917 if ((LPBOOL)lParam != NULL) *((LPBOOL)lParam) = TRUE;
918 return infoPtr->CurVal;
921 if(!UPDOWN_InBounds(infoPtr, (int)lParam)) {
922 if((int)lParam < infoPtr->MinVal) lParam = infoPtr->MinVal;
923 if((int)lParam > infoPtr->MaxVal) lParam = infoPtr->MaxVal;
925 temp = infoPtr->CurVal; /* save prev value */
926 infoPtr->CurVal = (int)lParam; /* set the new value */
927 if(dwStyle & UDS_SETBUDDYINT) UPDOWN_SetBuddyInt (infoPtr);
928 return temp; /* return prev value */
930 case UDM_GETUNICODEFORMAT:
931 if (wParam || lParam) UNKNOWN_PARAM(UDM_GETUNICODEFORMAT, wParam, lParam);
932 /* we lie a bit here, we're always using Unicode internally */
933 return infoPtr->UnicodeFormat;
935 case UDM_SETUNICODEFORMAT:
936 if (lParam) UNKNOWN_PARAM(UDM_SETUNICODEFORMAT, wParam, lParam);
937 /* do we really need to honour this flag? */
938 temp = infoPtr->UnicodeFormat;
939 infoPtr->UnicodeFormat = (BOOL)wParam;
943 if (message >= WM_USER)
944 ERR("unknown msg %04x wp=%04x lp=%08lx\n", message, wParam, lParam);
945 return DefWindowProcW (hwnd, message, wParam, lParam);
951 /***********************************************************************
952 * UPDOWN_Register [Internal]
954 * Registers the updown window class.
958 UPDOWN_Register(void)
962 ZeroMemory( &wndClass, sizeof( WNDCLASSW ) );
963 wndClass.style = CS_GLOBALCLASS | CS_VREDRAW;
964 wndClass.lpfnWndProc = (WNDPROC)UpDownWindowProc;
965 wndClass.cbClsExtra = 0;
966 wndClass.cbWndExtra = sizeof(UPDOWN_INFO*);
967 wndClass.hCursor = LoadCursorW( 0, IDC_ARROWW );
968 wndClass.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
969 wndClass.lpszClassName = UPDOWN_CLASSW;
971 RegisterClassW( &wndClass );
975 /***********************************************************************
976 * UPDOWN_Unregister [Internal]
978 * Unregisters the updown window class.
982 UPDOWN_Unregister (void)
984 UnregisterClassW (UPDOWN_CLASSW, (HINSTANCE)NULL);