4 * Copyright 1997 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
21 * - I think I do not handle correctly the WS_BORDER style.
22 * (Should be fixed. <ekohl@abo.rhein-zeitung.de>)
25 * Not much. The following have not been tested at all:
27 * - listbox as buddy window
30 * - integers with thousand separators.
31 * (fixed bugs. <noel@macadamian.com>)
33 * Even though the above list seems rather large, the control seems to
34 * behave very well so I am confident it does work in most (all) of the
48 #include "wine/debug.h"
50 WINE_DEFAULT_DEBUG_CHANNEL(updown);
52 #define UPDOWN_BUDDYCLASSNAMELEN 40
56 HWND Self; /* Handle to this up-down control */
57 UINT AccelCount; /* Number of elements in AccelVect */
58 UDACCEL* AccelVect; /* Vector containing AccelCount elements */
59 INT AccelIndex; /* Current accel index, -1 if not accelerating */
60 INT Base; /* Base to display nr in the buddy window */
61 INT CurVal; /* Current up-down value */
62 INT MinVal; /* Minimum up-down value */
63 INT MaxVal; /* Maximum up-down value */
64 HWND Buddy; /* Handle to the buddy window */
65 CHAR szBuddyClass[UPDOWN_BUDDYCLASSNAMELEN]; /* Buddy window class name */
66 INT Flags; /* Internal Flags FLAG_* */
69 /* Control configuration constants */
71 #define INITIAL_DELAY 500 /* initial timer until auto-increment kicks in */
72 #define REPEAT_DELAY 50 /* delay between auto-increments */
74 #define DEFAULT_WIDTH 14 /* default width of the ctrl */
75 #define DEFAULT_XSEP 0 /* default separation between buddy and crtl */
76 #define DEFAULT_ADDTOP 0 /* amount to extend above the buddy window */
77 #define DEFAULT_ADDBOT 0 /* amount to extend below the buddy window */
78 #define DEFAULT_BUDDYBORDER 2 /* Width/height of the buddy border */
83 #define FLAG_INCR 0x01
84 #define FLAG_DECR 0x02
85 #define FLAG_MOUSEIN 0x04
86 #define FLAG_CLICKED (FLAG_INCR | FLAG_DECR)
90 #define BUDDY_UPDOWN_HWND "buddyUpDownHWND"
91 #define BUDDY_SUPERCLASS_WNDPROC "buddySupperClassWndProc"
93 #define UNKNOWN_PARAM(msg, wParam, lParam) WARN(\
94 "Unknown parameter(s) for message " #msg \
95 "(%04x): wp=%04x lp=%08lx\n", msg, wParam, lParam);
97 #define UPDOWN_GetInfoPtr(hwnd) ((UPDOWN_INFO *)GetWindowLongA (hwnd,0))
99 static LRESULT CALLBACK
100 UPDOWN_Buddy_SubclassProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
102 /***********************************************************************
104 * Tests if a given value 'val' is between the Min&Max limits
106 static BOOL UPDOWN_InBounds(UPDOWN_INFO *infoPtr, int val)
108 if(infoPtr->MaxVal > infoPtr->MinVal)
109 return (infoPtr->MinVal <= val) && (val <= infoPtr->MaxVal);
111 return (infoPtr->MaxVal <= val) && (val <= infoPtr->MinVal);
114 /***********************************************************************
116 * Tests if we can change the current value by delta. If so, it changes
117 * it and returns TRUE. Else, it leaves it unchanged and returns FALSE.
119 static BOOL UPDOWN_OffsetVal(UPDOWN_INFO *infoPtr, int delta)
121 /* check if we can do the modification first */
122 if(!UPDOWN_InBounds (infoPtr, infoPtr->CurVal+delta)){
123 if (GetWindowLongW (infoPtr->Self, GWL_STYLE) & UDS_WRAP)
125 delta += (delta < 0 ? -1 : 1) *
126 (infoPtr->MaxVal < infoPtr->MinVal ? -1 : 1) *
127 (infoPtr->MinVal - infoPtr->MaxVal) +
128 (delta < 0 ? 1 : -1);
134 infoPtr->CurVal += delta;
138 /***********************************************************************
139 * UPDOWN_HasBuddyBorder [Internal]
141 * When we have a buddy set and that we are aligned on our buddy, we
142 * want to draw a sunken edge to make like we are part of that control.
144 static BOOL UPDOWN_HasBuddyBorder(UPDOWN_INFO* infoPtr)
146 DWORD dwStyle = GetWindowLongW (infoPtr->Self, GWL_STYLE);
148 return ( ((dwStyle & (UDS_ALIGNLEFT | UDS_ALIGNRIGHT)) != 0) &&
149 (SendMessageW(infoPtr->Self, UDM_GETBUDDY, 0, 0) != 0) &&
150 (lstrcmpiA(infoPtr->szBuddyClass, "EDIT") == 0 ) );
153 /***********************************************************************
154 * UPDOWN_GetArrowRect
155 * wndPtr - pointer to the up-down wnd
156 * rect - will hold the rectangle
157 * incr - TRUE get the "increment" rect (up or right)
158 * FALSE get the "decrement" rect (down or left)
160 static void UPDOWN_GetArrowRect (UPDOWN_INFO* infoPtr, RECT *rect, BOOL incr)
162 DWORD dwStyle = GetWindowLongW (infoPtr->Self, GWL_STYLE);
163 int len; /* will hold the width or height */
165 GetClientRect (infoPtr->Self, rect);
168 * Make sure we calculate the rectangle to fit even if we draw the
171 if (UPDOWN_HasBuddyBorder(infoPtr)) {
172 if (dwStyle & UDS_ALIGNLEFT)
173 rect->left += DEFAULT_BUDDYBORDER;
175 rect->right -= DEFAULT_BUDDYBORDER;
177 InflateRect(rect, 0, -DEFAULT_BUDDYBORDER);
181 * We're calculating the midpoint to figure-out where the
182 * separation between the buttons will lay. We make sure that we
183 * round the uneven numbers by adding 1.
185 if (dwStyle & UDS_HORZ) {
186 len = rect->right - rect->left + 1; /* compute the width */
188 rect->left = rect->left + len/2;
190 rect->right = rect->left + len/2;
192 len = rect->bottom - rect->top + 1; /* compute the height */
194 rect->bottom = rect->top + len/2;
196 rect->top = rect->top + len/2;
200 /***********************************************************************
201 * UPDOWN_GetArrowFromPoint
202 * Returns the rectagle (for the up or down arrow) that contains pt.
203 * If it returns the up rect, it returns TRUE.
204 * If it returns the down rect, it returns FALSE.
206 static BOOL UPDOWN_GetArrowFromPoint (UPDOWN_INFO* infoPtr, RECT *rect, POINT pt)
208 UPDOWN_GetArrowRect (infoPtr, rect, TRUE);
209 if(PtInRect(rect, pt)) return TRUE;
211 UPDOWN_GetArrowRect (infoPtr, rect, FALSE);
216 /***********************************************************************
217 * UPDOWN_GetThousandSep
218 * Returns the thousand sep. If an error occurs, it returns ','.
220 static CHAR UPDOWN_GetThousandSep()
224 if(GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, sep, 2) != 1)
230 /***********************************************************************
232 * Tries to read the pos from the buddy window and if it succeeds,
233 * it stores it in the control's CurVal
235 * TRUE - if it read the integer from the buddy successfully
236 * FALSE - if an error occurred
238 static BOOL UPDOWN_GetBuddyInt (UPDOWN_INFO *infoPtr)
240 char txt[20], sep, *src, *dst;
243 if (!IsWindow(infoPtr->Buddy))
246 /*if the buddy is a list window, we must set curr index */
247 if (!lstrcmpA (infoPtr->szBuddyClass, "ListBox")){
248 newVal = SendMessageA(infoPtr->Buddy, LB_GETCARETINDEX, 0, 0);
253 /* we have a regular window, so will get the text */
254 if (!GetWindowTextA(infoPtr->Buddy, txt, sizeof(txt)))
257 sep = UPDOWN_GetThousandSep();
259 /* now get rid of the separators */
260 for(src = dst = txt; *src; src++)
261 if(*src != sep) *dst++ = *src;
264 /* try to convert the number and validate it */
265 newVal = strtol(txt, &src, infoPtr->Base);
266 if(*src || !UPDOWN_InBounds (infoPtr, newVal))
269 TRACE("new value(%d) from buddy (old=%d)\n", newVal, infoPtr->CurVal);
272 infoPtr->CurVal = newVal;
277 /***********************************************************************
279 * Tries to set the pos to the buddy window based on current pos
281 * TRUE - if it set the caption of the buddy successfully
282 * FALSE - if an error occurred
284 static BOOL UPDOWN_SetBuddyInt (UPDOWN_INFO *infoPtr)
289 if (!IsWindow(infoPtr->Buddy))
292 TRACE("set new value(%d) to buddy.\n", infoPtr->CurVal);
294 /*if the buddy is a list window, we must set curr index */
295 if(!lstrcmpA (infoPtr->szBuddyClass, "ListBox")){
296 SendMessageA(infoPtr->Buddy, LB_SETCURSEL, infoPtr->CurVal, 0);
298 else{ /* Regular window, so set caption to the number */
299 len = sprintf(txt1, (infoPtr->Base==16) ? "%X" : "%d", infoPtr->CurVal);
301 sep = UPDOWN_GetThousandSep();
303 /* Do thousands seperation if necessary */
304 if (!(GetWindowLongA (infoPtr->Self, GWL_STYLE) & UDS_NOTHOUSANDS) && (len > 3)) {
305 char txt2[20], *src = txt1, *dst = txt2;
307 lstrcpynA (dst, src, len%3 + 1); /* need to include the null */
311 for(len=0; *src; len++) {
312 if(len%3==0) *dst++ = sep;
315 *dst = 0; /* null terminate it */
316 strcpy(txt1, txt2); /* move it to the proper place */
318 SetWindowTextA(infoPtr->Buddy, txt1);
324 /***********************************************************************
325 * UPDOWN_DrawBuddyBorder [Internal]
327 * When we have a buddy set and that we are aligned on our buddy, we
328 * want to draw a sunken edge to make like we are part of that control.
330 static void UPDOWN_DrawBuddyBorder (UPDOWN_INFO *infoPtr, HDC hdc)
332 DWORD dwStyle = GetWindowLongA (infoPtr->Self, GWL_STYLE);
335 GetClientRect(infoPtr->Self, &clientRect);
337 if (dwStyle & UDS_ALIGNLEFT)
338 DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_BOTTOM | BF_LEFT | BF_TOP);
340 DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_BOTTOM | BF_RIGHT | BF_TOP);
343 /***********************************************************************
344 * UPDOWN_Draw [Internal]
346 * Draw the arrows. The background need not be erased.
348 static void UPDOWN_Draw (UPDOWN_INFO *infoPtr, HDC hdc)
350 DWORD dwStyle = GetWindowLongA (infoPtr->Self, GWL_STYLE);
355 * Draw the common border between ourselves and our buddy.
357 if (UPDOWN_HasBuddyBorder(infoPtr))
358 UPDOWN_DrawBuddyBorder(infoPtr, hdc);
360 /* Draw the incr button */
361 UPDOWN_GetArrowRect (infoPtr, &rect, TRUE);
362 prssed = (infoPtr->Flags & FLAG_INCR) && (infoPtr->Flags & FLAG_MOUSEIN);
363 DrawFrameControl(hdc, &rect, DFC_SCROLL,
364 (dwStyle & UDS_HORZ ? DFCS_SCROLLRIGHT : DFCS_SCROLLUP) |
365 (prssed ? DFCS_PUSHED : 0) |
366 (dwStyle&WS_DISABLED ? DFCS_INACTIVE : 0) );
368 /* Draw the space between the buttons */
369 rect.top = rect.bottom; rect.bottom++;
370 DrawEdge(hdc, &rect, 0, BF_MIDDLE);
372 /* Draw the decr button */
373 UPDOWN_GetArrowRect(infoPtr, &rect, FALSE);
374 prssed = (infoPtr->Flags & FLAG_DECR) && (infoPtr->Flags & FLAG_MOUSEIN);
375 DrawFrameControl(hdc, &rect, DFC_SCROLL,
376 (dwStyle & UDS_HORZ ? DFCS_SCROLLLEFT : DFCS_SCROLLDOWN) |
377 (prssed ? DFCS_PUSHED : 0) |
378 (dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) );
381 /***********************************************************************
382 * UPDOWN_Refresh [Internal]
384 * Synchronous drawing (must NOT be used in WM_PAINT).
387 static void UPDOWN_Refresh (UPDOWN_INFO *infoPtr)
389 HDC hdc = GetDC (infoPtr->Self);
390 UPDOWN_Draw (infoPtr, hdc);
391 ReleaseDC (infoPtr->Self, hdc);
395 /***********************************************************************
396 * UPDOWN_Paint [Internal]
398 * Asynchronous drawing (must ONLY be used in WM_PAINT).
401 static void UPDOWN_Paint (UPDOWN_INFO *infoPtr, HDC hdc)
404 UPDOWN_Draw (infoPtr, hdc);
408 hdc = BeginPaint (infoPtr->Self, &ps);
409 UPDOWN_Draw (infoPtr, hdc);
410 EndPaint (infoPtr->Self, &ps);
414 /***********************************************************************
416 * Tests if 'bud' is a valid window handle. If not, returns FALSE.
417 * Else, sets it as a new Buddy.
418 * Then, it should subclass the buddy
419 * If window has the UDS_ARROWKEYS, it subcalsses the buddy window to
420 * process the UP/DOWN arrow keys.
421 * If window has the UDS_ALIGNLEFT or UDS_ALIGNRIGHT style
422 * the size/pos of the buddy and the control are adjusted accordingly.
424 static BOOL UPDOWN_SetBuddy (UPDOWN_INFO* infoPtr, HWND bud)
426 DWORD dwStyle = GetWindowLongA (infoPtr->Self, GWL_STYLE);
427 RECT budRect; /* new coord for the buddy */
428 int x,width; /* new x position and width for the up-down */
429 WNDPROC baseWndProc, currWndProc;
431 /* Is it a valid bud? */
432 if(!IsWindow(bud)) return FALSE;
434 /* there is already a body assigned */
435 if ( infoPtr->Buddy )
436 RemovePropA(infoPtr->Buddy, BUDDY_UPDOWN_HWND);
438 /* Store buddy window handle */
439 infoPtr->Buddy = bud;
441 /* keep upDown ctrl hwnd in a buddy property */
442 SetPropA( bud, BUDDY_UPDOWN_HWND, infoPtr->Self);
444 /* Store buddy window clas name */
445 memset(infoPtr->szBuddyClass, 0, UPDOWN_BUDDYCLASSNAMELEN);
446 GetClassNameA (bud, infoPtr->szBuddyClass, UPDOWN_BUDDYCLASSNAMELEN-1);
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. */
453 currWndProc = (WNDPROC) GetWindowLongA(bud, GWL_WNDPROC);
454 if (currWndProc != UPDOWN_Buddy_SubclassProc)
456 // replace the buddy's WndProc with ours
457 baseWndProc = (WNDPROC)SetWindowLongA(bud, GWL_WNDPROC,
458 (LPARAM)UPDOWN_Buddy_SubclassProc);
459 // and save the base class' WndProc
460 SetPropA(bud, BUDDY_SUPERCLASS_WNDPROC, (HANDLE)baseWndProc);
463 // its already been subclassed, don't overwrite BUDDY_SUPERCLASS_WNDPROC
466 /* Get the rect of the buddy relative to its parent */
467 GetWindowRect(infoPtr->Buddy, &budRect);
468 MapWindowPoints(HWND_DESKTOP, GetParent(infoPtr->Buddy),
469 (POINT *)(&budRect.left), 2);
471 /* now do the positioning */
472 if (dwStyle & UDS_ALIGNLEFT) {
474 budRect.left += DEFAULT_WIDTH+DEFAULT_XSEP;
476 else if (dwStyle & UDS_ALIGNRIGHT){
477 budRect.right -= DEFAULT_WIDTH+DEFAULT_XSEP;
478 x = budRect.right+DEFAULT_XSEP;
481 x = budRect.right+DEFAULT_XSEP;
484 /* first adjust the buddy to accomodate the up/down */
485 SetWindowPos(infoPtr->Buddy, 0, budRect.left, budRect.top,
486 budRect.right - budRect.left, budRect.bottom - budRect.top,
487 SWP_NOACTIVATE|SWP_NOZORDER);
489 /* now position the up/down */
490 /* Since the UDS_ALIGN* flags were used, */
491 /* we will pick the position and size of the window. */
492 width = DEFAULT_WIDTH;
495 * If the updown has a buddy border, it has to overlap with the buddy
496 * to look as if it is integrated with the buddy control.
497 * We nudge the control or change it size to overlap.
499 if (UPDOWN_HasBuddyBorder(infoPtr))
501 if(dwStyle & UDS_ALIGNLEFT)
502 width+=DEFAULT_BUDDYBORDER;
504 x-=DEFAULT_BUDDYBORDER;
507 SetWindowPos (infoPtr->Self, infoPtr->Buddy,
508 x, budRect.top-DEFAULT_ADDTOP,
509 width, (budRect.bottom-budRect.top)+DEFAULT_ADDTOP+DEFAULT_ADDBOT,
515 /***********************************************************************
518 * This function increments/decrements the CurVal by the
519 * 'delta' amount according to the 'incr' flag
520 * It notifies the parent as required.
521 * It handles wraping and non-wraping correctly.
522 * It is assumed that delta>0
524 static void UPDOWN_DoAction (UPDOWN_INFO *infoPtr, int delta, BOOL incr)
526 DWORD dwStyle = GetWindowLongA (infoPtr->Self, GWL_STYLE);
529 TRACE("%s by %d\n", incr ? "inc" : "dec", delta);
531 /* check if we can do the modification first */
532 delta *= (incr ? 1 : -1) * (infoPtr->MaxVal < infoPtr->MinVal ? -1 : 1);
534 /* We must notify parent now to obtain permission */
535 ni.iPos = infoPtr->CurVal;
537 ni.hdr.hwndFrom = infoPtr->Self;
538 ni.hdr.idFrom = GetWindowLongA (infoPtr->Self, GWL_ID);
539 ni.hdr.code = UDN_DELTAPOS;
540 if (!SendMessageA(GetParent (infoPtr->Self), WM_NOTIFY,
541 (WPARAM)ni.hdr.idFrom, (LPARAM)&ni))
543 /* Parent said: OK to adjust */
545 /* Now adjust value with (maybe new) delta */
546 if (UPDOWN_OffsetVal (infoPtr, ni.iDelta))
548 /* Now take care about our buddy */
549 if(infoPtr->Buddy && IsWindow(infoPtr->Buddy)
550 && (dwStyle & UDS_SETBUDDYINT) )
551 UPDOWN_SetBuddyInt (infoPtr);
555 /* Also, notify it. This message is sent in any case. */
556 SendMessageA (GetParent (infoPtr->Self),
557 dwStyle & UDS_HORZ ? WM_HSCROLL : WM_VSCROLL,
558 MAKELONG(SB_THUMBPOSITION, infoPtr->CurVal), infoPtr->Self);
561 /***********************************************************************
564 * Returns TRUE if it is enabled as well as its buddy (if any)
567 static BOOL UPDOWN_IsEnabled (UPDOWN_INFO *infoPtr)
569 if(GetWindowLongA (infoPtr->Self, GWL_STYLE) & WS_DISABLED)
572 return IsWindowEnabled(infoPtr->Buddy);
576 /***********************************************************************
579 * Deletes any timers, releases the mouse and does redraw if necessary.
580 * If the control is not in "capture" mode, it does nothing.
581 * If the control was not in cancel mode, it returns FALSE.
582 * If the control was in cancel mode, it returns TRUE.
584 static BOOL UPDOWN_CancelMode (UPDOWN_INFO *infoPtr)
586 /* if not in 'capture' mode, do nothing */
587 if(!(infoPtr->Flags & FLAG_CLICKED))
590 KillTimer (infoPtr->Self, TIMERID1); /* kill all possible timers */
591 KillTimer (infoPtr->Self, TIMERID2);
593 if (GetCapture() == infoPtr->Self) /* let the mouse go */
594 ReleaseCapture(); /* if we still have it */
596 infoPtr->Flags = 0; /* get rid of any flags */
597 UPDOWN_Refresh (infoPtr); /* redraw the control just in case */
602 /***********************************************************************
603 * UPDOWN_HandleMouseEvent
605 * Handle a mouse event for the updown.
606 * 'pt' is the location of the mouse event in client or
607 * windows coordinates.
609 static void UPDOWN_HandleMouseEvent (UPDOWN_INFO *infoPtr, UINT msg, POINT pt)
611 DWORD dwStyle = GetWindowLongA (infoPtr->Self, GWL_STYLE);
617 case WM_LBUTTONDOWN: /* Initialise mouse tracking */
618 /* If we are already in the 'clicked' mode, then nothing to do */
619 if(infoPtr->Flags & FLAG_CLICKED)
622 /* If the buddy is an edit, will set focus to it */
623 if (!lstrcmpA (infoPtr->szBuddyClass, "Edit"))
624 SetFocus(infoPtr->Buddy);
626 /* Now see which one is the 'active' arrow */
627 temp = UPDOWN_GetArrowFromPoint (infoPtr, &rect, pt);
629 /* Update the CurVal if necessary */
630 if (dwStyle & UDS_SETBUDDYINT)
631 UPDOWN_GetBuddyInt (infoPtr);
633 /* Set up the correct flags */
635 infoPtr->Flags |= temp ? FLAG_INCR : FLAG_DECR;
636 infoPtr->Flags |= FLAG_MOUSEIN;
638 /* repaint the control */
639 UPDOWN_Refresh (infoPtr);
641 /* process the click */
642 UPDOWN_DoAction (infoPtr, 1, infoPtr->Flags & FLAG_INCR);
644 /* now capture all mouse messages */
645 SetCapture (infoPtr->Self);
647 /* and startup the first timer */
648 SetTimer(infoPtr->Self, TIMERID1, INITIAL_DELAY, 0);
652 /* If we are not in the 'clicked' mode, then nothing to do */
653 if(!(infoPtr->Flags & FLAG_CLICKED))
656 /* save the flags to see if any got modified */
657 temp = infoPtr->Flags;
659 /* Now get the 'active' arrow rectangle */
660 if (infoPtr->Flags & FLAG_INCR)
661 UPDOWN_GetArrowRect (infoPtr, &rect, TRUE);
663 UPDOWN_GetArrowRect (infoPtr, &rect, FALSE);
665 /* Update the flags if we are in/out */
666 if(PtInRect(&rect, pt))
667 infoPtr->Flags |= FLAG_MOUSEIN;
669 infoPtr->Flags &= ~FLAG_MOUSEIN;
670 if(infoPtr->AccelIndex != -1) /* if we have accel info */
671 infoPtr->AccelIndex = 0; /* reset it */
673 /* If state changed, redraw the control */
674 if(temp != infoPtr->Flags)
675 UPDOWN_Refresh (infoPtr);
679 ERR("Impossible case!\n");
684 /***********************************************************************
687 static LRESULT WINAPI UpDownWindowProc(HWND hwnd, UINT message, WPARAM wParam,
690 UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr (hwnd);
691 DWORD dwStyle = GetWindowLongA (hwnd, GWL_STYLE);
693 if (!infoPtr && (message != WM_CREATE) && (message != WM_NCCREATE))
694 return DefWindowProcA (hwnd, message, wParam, lParam);
698 /* get rid of border, if any */
699 SetWindowLongA (hwnd, GWL_STYLE, dwStyle & ~WS_BORDER);
703 infoPtr = (UPDOWN_INFO*)COMCTL32_Alloc (sizeof(UPDOWN_INFO));
704 SetWindowLongA (hwnd, 0, (DWORD)infoPtr);
706 /* initialize the info struct */
707 infoPtr->Self = hwnd;
708 infoPtr->AccelCount = 0;
709 infoPtr->AccelVect = 0;
710 infoPtr->AccelIndex = -1;
713 infoPtr->MaxVal = 9999;
714 infoPtr->Base = 10; /* Default to base 10 */
715 infoPtr->Buddy = 0; /* No buddy window yet */
716 infoPtr->Flags = 0; /* And no flags */
718 /* Do we pick the buddy win ourselves? */
719 if (dwStyle & UDS_AUTOBUDDY)
720 UPDOWN_SetBuddy (infoPtr, GetWindow (hwnd, GW_HWNDPREV));
722 TRACE("UpDown Ctrl creation, hwnd=%04x\n", hwnd);
726 if(infoPtr->AccelVect)
727 COMCTL32_Free (infoPtr->AccelVect);
729 if ( IsWindow(infoPtr->Buddy) ) /* Cleanup */
730 RemovePropA(infoPtr->Buddy, BUDDY_UPDOWN_HWND);
732 COMCTL32_Free (infoPtr);
733 SetWindowLongA (hwnd, 0, 0);
734 TRACE("UpDown Ctrl destruction, hwnd=%04x\n", hwnd);
738 if (dwStyle & WS_DISABLED)
739 UPDOWN_CancelMode (infoPtr);
741 UPDOWN_Refresh (infoPtr);
745 /* if initial timer, kill it and start the repeat timer */
746 if(wParam == TIMERID1){
747 KillTimer(hwnd, TIMERID1);
748 /* if no accel info given, used default timer */
749 if(infoPtr->AccelCount==0 || infoPtr->AccelVect==0){
750 infoPtr->AccelIndex = -1;
754 infoPtr->AccelIndex = 0; /* otherwise, use it */
755 temp = infoPtr->AccelVect[infoPtr->AccelIndex].nSec * 1000 + 1;
757 SetTimer(hwnd, TIMERID2, temp, 0);
760 /* now, if the mouse is above us, do the thing...*/
761 if(infoPtr->Flags & FLAG_MOUSEIN){
762 temp = infoPtr->AccelIndex == -1 ? 1 : infoPtr->AccelVect[infoPtr->AccelIndex].nInc;
763 UPDOWN_DoAction(infoPtr, temp, infoPtr->Flags & FLAG_INCR);
765 if(infoPtr->AccelIndex != -1 && infoPtr->AccelIndex < infoPtr->AccelCount-1){
766 KillTimer(hwnd, TIMERID2);
767 infoPtr->AccelIndex++; /* move to the next accel info */
768 temp = infoPtr->AccelVect[infoPtr->AccelIndex].nSec * 1000 + 1;
769 /* make sure we have at least 1ms intervals */
770 SetTimer(hwnd, TIMERID2, temp, 0);
776 UPDOWN_CancelMode (infoPtr);
780 if(!UPDOWN_CancelMode(infoPtr))
783 SendMessageA(GetParent(hwnd), dwStyle & UDS_HORZ ? WM_HSCROLL : WM_VSCROLL,
784 MAKELONG(SB_ENDSCROLL, infoPtr->CurVal), hwnd);
786 /*If we released the mouse and our buddy is an edit */
787 /* we must select all text in it. */
788 if (!lstrcmpA (infoPtr->szBuddyClass, "Edit"))
789 SendMessageA(infoPtr->Buddy, EM_SETSEL, 0, MAKELONG(0, -1));
794 if(UPDOWN_IsEnabled(infoPtr)){
796 pt.x = SLOWORD(lParam);
797 pt.y = SHIWORD(lParam);
798 UPDOWN_HandleMouseEvent (infoPtr, message, pt );
803 if((dwStyle & UDS_ARROWKEYS) && UPDOWN_IsEnabled(infoPtr)){
807 UPDOWN_GetBuddyInt (infoPtr);
808 /* FIXME: Paint the according button pressed for some time, like win95 does*/
809 UPDOWN_DoAction (infoPtr, 1, wParam==VK_UP);
816 UPDOWN_Paint (infoPtr, (HDC)wParam);
820 if (wParam==0 && lParam==0) /*if both zero, */
821 return infoPtr->AccelCount; /*just return the accel count*/
822 if (wParam || lParam){
823 UNKNOWN_PARAM(UDM_GETACCEL, wParam, lParam);
826 temp = min(infoPtr->AccelCount, wParam);
827 memcpy((void *)lParam, infoPtr->AccelVect, temp*sizeof(UDACCEL));
831 TRACE("UpDown Ctrl new accel info, hwnd=%04x\n", hwnd);
832 if(infoPtr->AccelVect){
833 COMCTL32_Free (infoPtr->AccelVect);
834 infoPtr->AccelCount = 0;
835 infoPtr->AccelVect = 0;
839 infoPtr->AccelVect = COMCTL32_Alloc (wParam*sizeof(UDACCEL));
840 if(infoPtr->AccelVect==0)
842 memcpy(infoPtr->AccelVect, (void*)lParam, wParam*sizeof(UDACCEL));
846 if (wParam || lParam)
847 UNKNOWN_PARAM(UDM_GETBASE, wParam, lParam);
848 return infoPtr->Base;
851 TRACE("UpDown Ctrl new base(%d), hwnd=%04x\n", wParam, hwnd);
852 if ( !(wParam==10 || wParam==16) || lParam)
853 UNKNOWN_PARAM(UDM_SETBASE, wParam, lParam);
854 if (wParam==10 || wParam==16){
855 temp = infoPtr->Base;
856 infoPtr->Base = wParam;
857 return temp; /* return the prev base */
862 if (wParam || lParam)
863 UNKNOWN_PARAM(UDM_GETBUDDY, wParam, lParam);
864 return infoPtr->Buddy;
868 UNKNOWN_PARAM(UDM_SETBUDDY, wParam, lParam);
869 temp = infoPtr->Buddy;
870 UPDOWN_SetBuddy (infoPtr, wParam);
871 TRACE("UpDown Ctrl new buddy(%04x), hwnd=%04x\n", infoPtr->Buddy, hwnd);
875 if (wParam || lParam)
876 UNKNOWN_PARAM(UDM_GETPOS, wParam, lParam);
877 temp = UPDOWN_GetBuddyInt (infoPtr);
878 return MAKELONG(infoPtr->CurVal, temp ? 0 : 1);
881 if (wParam || HIWORD(lParam))
882 UNKNOWN_PARAM(UDM_GETPOS, wParam, lParam);
883 temp = SLOWORD(lParam);
884 TRACE("UpDown Ctrl new value(%d), hwnd=%04x\n", temp, hwnd);
885 if(!UPDOWN_InBounds(infoPtr, temp)){
886 if(temp < infoPtr->MinVal)
887 temp = infoPtr->MinVal;
888 if(temp > infoPtr->MaxVal)
889 temp = infoPtr->MaxVal;
891 wParam = infoPtr->CurVal; /* save prev value */
892 infoPtr->CurVal = temp; /* set the new value */
893 if(dwStyle & UDS_SETBUDDYINT)
894 UPDOWN_SetBuddyInt (infoPtr);
895 return wParam; /* return prev value */
898 if (wParam || lParam)
899 UNKNOWN_PARAM(UDM_GETRANGE, wParam, lParam);
900 return MAKELONG(infoPtr->MaxVal, infoPtr->MinVal);
904 UNKNOWN_PARAM(UDM_SETRANGE, wParam, lParam); /* we must have: */
905 infoPtr->MaxVal = SLOWORD(lParam); /* UD_MINVAL <= Max <= UD_MAXVAL */
906 infoPtr->MinVal = SHIWORD(lParam); /* UD_MINVAL <= Min <= UD_MAXVAL */
907 /* |Max-Min| <= UD_MAXVAL */
908 TRACE("UpDown Ctrl new range(%d to %d), hwnd=%04x\n",
909 infoPtr->MinVal, infoPtr->MaxVal, hwnd);
914 *(LPINT)wParam = infoPtr->MinVal;
916 *(LPINT)lParam = infoPtr->MaxVal;
920 infoPtr->MinVal = (INT)wParam;
921 infoPtr->MaxVal = (INT)lParam;
922 if (infoPtr->MaxVal <= infoPtr->MinVal)
923 infoPtr->MaxVal = infoPtr->MinVal + 1;
924 TRACE("UpDown Ctrl new range(%d to %d), hwnd=%04x\n",
925 infoPtr->MinVal, infoPtr->MaxVal, hwnd);
929 if ((LPBOOL)lParam != NULL)
930 *((LPBOOL)lParam) = TRUE;
931 return infoPtr->CurVal;
934 if(!UPDOWN_InBounds(infoPtr, (int)lParam)){
935 if((int)lParam < infoPtr->MinVal)
936 lParam = infoPtr->MinVal;
937 if((int)lParam > infoPtr->MaxVal)
938 lParam = infoPtr->MaxVal;
940 temp = infoPtr->CurVal; /* save prev value */
941 infoPtr->CurVal = (int)lParam; /* set the new value */
942 if(dwStyle & UDS_SETBUDDYINT)
943 UPDOWN_SetBuddyInt (infoPtr);
944 return temp; /* return prev value */
947 if (message >= WM_USER)
948 ERR("unknown msg %04x wp=%04x lp=%08lx\n", message, wParam, lParam);
949 return DefWindowProcA (hwnd, message, wParam, lParam);
955 /***********************************************************************
956 * UPDOWN_Buddy_SubclassProc used to handle messages sent to the buddy
960 UPDOWN_Buddy_SubclassProc (
966 WNDPROC superClassWndProc = (WNDPROC)GetPropA(hwnd, BUDDY_SUPERCLASS_WNDPROC);
967 TRACE("hwnd=%04x, wndProc=%d, uMsg=%04x, wParam=%d, lParam=%d\n",
968 hwnd, (INT)superClassWndProc, uMsg, wParam, (UINT)lParam);
974 if ( ((int)wParam == VK_UP ) || ((int)wParam == VK_DOWN ) )
976 HWND upDownHwnd = GetPropA(hwnd, BUDDY_UPDOWN_HWND);
977 UPDOWN_INFO *infoPtr = UPDOWN_GetInfoPtr(upDownHwnd);
979 if (!lstrcmpA (infoPtr->szBuddyClass, "ListBox"))
981 /* if the buddy is a list window, we must update curr index */
982 INT oldVal = SendMessageA(hwnd, LB_GETCURSEL, 0, 0);
983 SendMessageA(hwnd, LB_SETCURSEL, oldVal+1, 0);
987 UPDOWN_GetBuddyInt(infoPtr);
988 UPDOWN_DoAction(infoPtr, 1, wParam==VK_UP);
993 /* else Fall Through */
996 return CallWindowProcA( superClassWndProc, hwnd, uMsg, wParam, lParam);
999 /***********************************************************************
1000 * UPDOWN_Register [Internal]
1002 * Registers the updown window class.
1006 UPDOWN_Register(void)
1010 ZeroMemory( &wndClass, sizeof( WNDCLASSA ) );
1011 wndClass.style = CS_GLOBALCLASS | CS_VREDRAW;
1012 wndClass.lpfnWndProc = (WNDPROC)UpDownWindowProc;
1013 wndClass.cbClsExtra = 0;
1014 wndClass.cbWndExtra = sizeof(UPDOWN_INFO*);
1015 wndClass.hCursor = LoadCursorA( 0, IDC_ARROWA );
1016 wndClass.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
1017 wndClass.lpszClassName = UPDOWN_CLASSA;
1019 RegisterClassA( &wndClass );
1023 /***********************************************************************
1024 * UPDOWN_Unregister [Internal]
1026 * Unregisters the updown window class.
1030 UPDOWN_Unregister (void)
1032 UnregisterClassA (UPDOWN_CLASSA, (HINSTANCE)NULL);