#include "ctlspriv.h" ///////////////////////////////////////////////////////////////////////////// // // updown.c : A micro-scrollbar control; useful for increment/decrement. // ///////////////////////////////////////////////////////////////////////////// #define NUM_UDACCELS 3 #define DONTCARE 0 #define SIGNED 1 #define UNSIGNED 2 #define UD_HITNOWHERE 0 #define UD_HITDOWN 1 #define UD_HITUP 2 typedef struct { CCONTROLINFO ci; HWND hwndBuddy; unsigned fUp : 1; unsigned fDown : 1; unsigned fUnsigned : 1; unsigned fSharedBorder : 1; unsigned fSunkenBorder : 1; unsigned fUpDownDestroyed : 1; // This tells the buddy that updown destoryed. BOOL fTrackSet: 1; unsigned fSubclassed:1; // did we subclass the buddy? UINT nBase; int nUpper; int nLower; int nPos; UINT uClass; BOOL bDown; DWORD dwStart; UINT nAccel; UDACCEL *udAccel; UINT uHot; int cReenterSetint; // To avoid recursion death in setint() HTHEME hTheme; HTHEME hThemeBuddy; } UDSTATE, *PUDSTATE; // Constants: // #define CLASS_UNKNOWN 0 #define CLASS_EDIT 1 #define CLASS_LISTBOX 2 #define MAX_INTLENGTH 18 // big enough for all intl stuff, too // this is the space to the left and right of the arrow (in pixels) #define XBORDER 0 #define BASE_DECIMAL 10 #define BASE_HEX 16 // Declarations: // LRESULT CALLBACK ArrowKeyProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, ULONG_PTR dwRefData); ///////////////////////////////////////////////////////////////////////////// // // ***** Internal workhorses ***** // // Validates the buddy. // void isgoodbuddy(PUDSTATE np) { if (!np->hwndBuddy) return; if (!IsWindow(np->hwndBuddy)) { #if defined(DEBUG) && !defined(WIN32) DebugOutput(DBF_ERROR | DBF_USER, TEXT("UpDown: invalid buddy handle 0x04X; ") TEXT("resetting to NULL"), np->hwndBuddy); #endif np->hwndBuddy = NULL; np->uClass = CLASS_UNKNOWN; } if (GetParent(np->hwndBuddy) != np->ci.hwndParent) { #if defined(DEBUG) && !defined(WIN32) DebugOutput(DBF_ERROR | DBF_USER, TEXT("UpDown: buddy has different parent; ") TEXT("resetting to NULL")); #endif np->hwndBuddy = NULL; np->uClass = CLASS_UNKNOWN; } } // Picks a good buddy. // void pickbuddy(PUDSTATE np) { if (np->ci.style & UDS_AUTOBUDDY) np->hwndBuddy = GetWindow(np->ci.hwnd, GW_HWNDPREV); } void unachor(PUDSTATE np) { RECT rc; RECT rcBuddy; RECT rcUD; if ( np->hwndBuddy && (np->ci.style & (UDS_ALIGNLEFT | UDS_ALIGNRIGHT))) { GetWindowRect(np->hwndBuddy, &rcBuddy); GetWindowRect(np->ci.hwnd, &rcUD); UnionRect(&rc, &rcUD, &rcBuddy); MapWindowRect(NULL, np->ci.hwndParent, &rc); MoveWindow(np->hwndBuddy, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, FALSE); } } // Anchor this control to the buddy's edge, if appropriate. // void anchor(PUDSTATE np) { BOOL bAlignToBuddy; int nOver = 0, nHasBorder; RECT rc, rcBuddy; int nHeight, nWidth; np->fSharedBorder = FALSE; isgoodbuddy(np); nHasBorder = (np->ci.style & WS_BORDER) == WS_BORDER; bAlignToBuddy = np->hwndBuddy && (np->ci.style & (UDS_ALIGNLEFT | UDS_ALIGNRIGHT)); if (bAlignToBuddy) { if ((np->uClass == CLASS_EDIT) || (GetWindowLong(np->hwndBuddy, GWL_EXSTYLE) & WS_EX_CLIENTEDGE)) { np->fSunkenBorder = TRUE; } GetWindowRect(np->hwndBuddy, &rc); if ((np->uClass == CLASS_EDIT) || (GetWindowLong(np->hwndBuddy, GWL_STYLE) & WS_BORDER)) { // FEATURE: for full generalization, should handle border AND clientedge nOver = g_cxBorder * (np->fSunkenBorder ? 2 : 1); np->fSharedBorder = TRUE; // turn off border styles... np->ci.style &= ~WS_BORDER; SetWindowLong(np->ci.hwnd, GWL_STYLE, np->ci.style); SetWindowLong(np->ci.hwnd, GWL_EXSTYLE, GetWindowLong(np->ci.hwnd, GWL_EXSTYLE) & ~(WS_EX_CLIENTEDGE)); } } else { GetWindowRect(np->ci.hwnd, &rc); } nHeight = rc.bottom - rc.top; nWidth = rc.right - rc.left; // // If the parent is RTL mirrored, then placement of the // child (i.e. anchor point) should be relative to the visual // right edge (near edge). [samera] // if (IS_WINDOW_RTL_MIRRORED(np->ci.hwndParent)) { rc.left = rc.right; } ScreenToClient(np->ci.hwndParent, (LPPOINT)&rc.left); rc.right = rc.left + nWidth; if (bAlignToBuddy) { nWidth = g_cxVScroll - g_cxBorder; if (nWidth > nHeight) { // don't let the aspect ratio nWidth = nHeight; // get worse than square } nWidth += nOver; rcBuddy = rc; if (np->ci.style & UDS_ALIGNLEFT) { // size buddy to right rcBuddy.left += nWidth - nOver; rc.right = rc.left + nWidth; } else { // size buddy to left rcBuddy.right -= nWidth - nOver; rc.left = rc.right - nWidth; } // size the buddy to fit the updown on the appropriate side MoveWindow(np->hwndBuddy, rcBuddy.left, rcBuddy.top, rcBuddy.right - rcBuddy.left, nHeight, TRUE); } else if (!(np->ci.style & UDS_HORZ)) { nWidth = g_cxVScroll + 2 * nHasBorder; } SetWindowPos(np->ci.hwnd, NULL, rc.left, rc.top, nWidth, nHeight, SWP_DRAWFRAME | SWP_NOZORDER | SWP_NOACTIVATE); } // Use this to make any and all comparisons involving the nPos, // nUpper or nLower fields of the PUDSTATE. It determines // whether to do a signed or unsigned comparison and returns // > 0 for (x > y) // < 0 for (x < y) // == 0 for (x == y). // // fCompareType is SIGNED to force a signed comparison, // fCompareType is UNSIGNED to force an unsigned comparison, // fCompareType is DONTCARE to use the np->fUnsigned flag to decide. // // In comments, comparison operators are suffixed with "D", "U" or "S" // to emphasize whether the comparison is DONTCARE, UNSIGNED, or SIGNED. // For example "x fUnsigned) && !(fCompareType == SIGNED)) ) { // Do unsigned comparisons if ((UINT)x > (UINT)y) return 1; else if ((UINT)x < (UINT)y) return -1; } else { // Do signed comparisons if (x > y) return 1; else if (x < y) return -1; } return 0; } // Use this after any pos change to make sure pos stays in range. // Wraps as necessary. // returns nonzero if the current value was out of range (and therefore // got changed so it fit into range again) // BOOL nudge(PUDSTATE np) { BOOL bOutOfRange = TRUE; int min = np->nUpper; int max = np->nLower; // if (max ci.style & UDS_WRAP) { // if (nPos D max) nPos = min -- wrap from above to below if ((compare(np, np->nPos, min, DONTCARE) < 0)) np->nPos = max; else if ((compare(np, np->nPos, max, DONTCARE) > 0)) np->nPos = min; else bOutOfRange = FALSE; } else { // if (nPos D max) nPos = max -- pin at max if (compare(np,np->nPos,min, DONTCARE) < 0) np->nPos = min; else if (compare(np,np->nPos,max, DONTCARE) > 0) np->nPos = max; else bOutOfRange = FALSE; } return(bOutOfRange); } // Sets the state of the buttons (pushed, released). // void squish(PUDSTATE np, UINT bTop, UINT bBottom) { BOOL bInvalidate = FALSE; if (np->nUpper == np->nLower || !IsWindowEnabled(np->ci.hwnd)) { bTop = FALSE; bBottom = FALSE; } else { bTop = !!bTop; bBottom = !!bBottom; } if (np->fUp != bTop) { np->fUp = bTop; bInvalidate = TRUE; NotifyWinEvent(EVENT_OBJECT_STATECHANGE, np->ci.hwnd, OBJID_CLIENT, 1); } if (np->fDown != bBottom) { np->fDown = bBottom; bInvalidate = TRUE; NotifyWinEvent(EVENT_OBJECT_STATECHANGE, np->ci.hwnd, OBJID_CLIENT, 2); } if (bInvalidate) { np->dwStart = GetTickCount(); InvalidateRect(np->ci.hwnd, NULL, FALSE); } } // Gets the intl 1000 separator // void getthousands(LPTSTR pszThousand) { #ifdef WIN32 if (!GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, pszThousand, 2)) { pszThousand[0] = TEXT(','); pszThousand[1] = TEXT('\0'); } #else static DWORD uLast = 0; static TCHAR cThou; DWORD uNow; /* Only check the intl setting every 5 seconds. */ uNow = GetTickCount(); if (uNow - uLast > 5000) { if (!GetProfileString(TEXT("intl"), TEXT("sThousand"), pszThousand, pszThousand, 2)) { pszThousand[0] = TEXT(','); pszThousand[1] = TEXT('\0'); } cThou = pszThousand[0]; uLast = uNow; } else { pszThousand[0] = cThou; pszThousand[1] = 0; } #endif } // // Obtain NLS info about how numbers should be grouped. // // The annoying thing is that LOCALE_SGROUPING and NUMBERFORMAT // have different ways of specifying number grouping. // // LOCALE NUMBERFMT Sample Country // // 3;0 3 1,234,567 United States // 3;2;0 32 12,34,567 India // 3 30 1234,567 ?? // // Not my idea. That's the way it works. // // Bonus treat - Win9x doesn't support complex number formats, // so we return only the first number. // UINT getgrouping(void) { UINT grouping; LPTSTR psz; TCHAR szGrouping[32]; // If no locale info, then assume Western style thousands if (!GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, szGrouping, ARRAYSIZE(szGrouping))) return 3; grouping = 0; psz = szGrouping; for (;;) { if (*psz == '0') break; // zero - stop else if ((UINT)(*psz - '0') < 10) // digit - accumulate it grouping = grouping * 10 + (UINT)(*psz - '0'); else if (*psz) // punctuation - ignore it { } else // end of string, no "0" found { grouping = grouping * 10; // put zero on end (see examples) break; // and finished } psz++; } return grouping; } // Gets the caption of the buddy // Returns the current position of the updown control // and sets *pfError on error. // LRESULT getint(PUDSTATE np, BOOL *pfError) { TCHAR szInt[MAX_INTLENGTH]; // big enough for all intl stuff, too TCHAR szThousand[2]; TCHAR cTemp; int nPos; int sign = 1; LPTSTR p = szInt; BOOL bInValid = TRUE; isgoodbuddy(np); if (np->hwndBuddy && np->ci.style & UDS_SETBUDDYINT) { if (np->uClass == CLASS_LISTBOX) { np->nPos = (int)SendMessage(np->hwndBuddy, LB_GETCURSEL, 0, 0L); bInValid = nudge(np); } else { GetWindowText(np->hwndBuddy, szInt, ARRAYSIZE(szInt)); switch (np->nBase) { case BASE_HEX: if ((*p == TEXT('x')) || (*p == TEXT('X'))) // ignore first character p++; else if ((*p == TEXT('0')) && ((*(p + 1) == TEXT('x')) || (*(p + 1) == TEXT('X')))) // ignore first two characters (TEXT("0x") or "0X") p += 2; for (nPos = 0; *p; p++) { if ((*p >= TEXT('A')) && (*p <= TEXT('F'))) cTemp = (TCHAR)(*p - TEXT('A') + 10); else if ((*p >= TEXT('a')) && (*p <= TEXT('f'))) cTemp = (TCHAR)(*p - TEXT('a') + 10); else if ((*p >= TEXT('0')) && (*p <= TEXT('9'))) cTemp = (TCHAR)(*p - TEXT('0')); else goto BadValue; nPos = (nPos * 16) + cTemp; } np->nPos = nPos; break; case BASE_DECIMAL: default: getthousands(szThousand); if (*p == TEXT('-') && !np->fUnsigned) { sign = -1; ++p; } for (nPos=0; *p; p++) { cTemp = *p; // If there is a thousand separator, just ignore it. // Do not validate that it's in the right place, // because it prevents the user from editing the // middle of a number. if (cTemp == szThousand[0]) { continue; } cTemp -= TEXT('0'); if ((UINT)cTemp > 9) { goto BadValue; } nPos = (nPos*10) + cTemp; } np->nPos = nPos*sign; break; } bInValid = nudge(np); } } BadValue: if (pfError) *pfError = bInValid; return np->nPos; } // Sets the caption of the buddy if appropriate. // void setint(PUDSTATE np) { TCHAR szInt[MAX_INTLENGTH]; TCHAR szThousand[2]; int pos = np->nPos; LPTSTR p = szInt; isgoodbuddy(np); if (np->hwndBuddy && np->ci.style & UDS_SETBUDDYINT) { BOOL fError; /* * If we have reentered, then maybe the app has set up a loop. * Check to see if the value has actually changed. If not, * then there's no need to set it again. This breaks the * recursion. */ if (np->cReenterSetint && (LRESULT)pos==getint(np, &fError) && !fError) { return; } np->nPos = pos; np->cReenterSetint++; if (np->uClass == CLASS_LISTBOX) { SendMessage(np->hwndBuddy, LB_SETCURSEL, pos, 0L); FORWARD_WM_COMMAND(GetParent(np->hwndBuddy), GetDlgCtrlID(np->hwndBuddy), np->hwndBuddy, LBN_SELCHANGE, SendMessage); } else { switch (np->nBase) { case BASE_HEX: if ((np->nUpper | np->nLower) >= 0x00010000) wsprintf(p, TEXT("0x%08X"), pos); else wsprintf(p, TEXT("0x%04X"), pos); break; case BASE_DECIMAL: default: if (pos < 0 && !np->fUnsigned) { *p++ = TEXT('-'); pos = -pos; } if (pos >= 1000 && !(np->ci.style & UDS_NOTHOUSANDS)) { TCHAR szFmt[MAX_INTLENGTH]; NUMBERFMT nf; nf.NumDigits = 0; // no digits after decimal point nf.LeadingZero = 0; // no leading zeros nf.Grouping = getgrouping(); nf.lpDecimalSep = TEXT(""); // no decimal point nf.lpThousandSep = szThousand; nf.NegativeOrder = 0; // (not used - we always pass positive numbers) getthousands(szThousand); wsprintf(szFmt, TEXT("%u"), pos); GetNumberFormat(LOCALE_USER_DEFAULT, 0, szFmt, &nf, p, MAX_INTLENGTH - 1); } else { wsprintf(p, TEXT("%u"), pos); } break; } SetWindowText(np->hwndBuddy, szInt); } np->cReenterSetint; } } // Use this to click the pos up or down by one. // void bump(PUDSTATE np) { BOOL bChanged = FALSE; UINT uElapsed, increment; int direction, i; /* So I'm not really getting seconds here; it's close enough, and * dividing by 1024 keeps __aFuldiv from being needed. */ uElapsed = (UINT)((GetTickCount() - np->dwStart) / 1024); if (np->udAccel != NULL) { increment = np->udAccel[0].nInc; for (i=np->nAccel-1; i>=0; --i) { if (np->udAccel[i].nSec <= uElapsed) { increment = np->udAccel[i].nInc; break; } } } else { increment = 1; } if (increment == 0) { DebugMsg(DM_ERROR, TEXT("bad accelerator value")); return; } direction = compare(np,np->nUpper,np->nLower, DONTCARE) < 0 ? -1 : 1; if (np->fUp) { bChanged = TRUE; } if (np->fDown) { direction = -direction; bChanged = TRUE; } if (bChanged) { /* Make sure we have a multiple of the increment * Note that we should loop only when the increment changes */ NM_UPDOWN nm; nm.iPos = np->nPos; nm.iDelta = increment*direction; if (CCSendNotify(&np->ci, UDN_DELTAPOS, &nm.hdr)) return; np->nPos += nm.iDelta; for ( ; ; ) { if (!((int)np->nPos % (int)increment)) { break; } np->nPos += direction; } nudge(np); setint(np); if (np->ci.style & UDS_HORZ) FORWARD_WM_HSCROLL(np->ci.hwndParent, np->ci.hwnd, SB_THUMBPOSITION, np->nPos, SendMessage); else FORWARD_WM_VSCROLL(np->ci.hwndParent, np->ci.hwnd, SB_THUMBPOSITION, np->nPos, SendMessage); NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, np->ci.hwnd, OBJID_CLIENT, 0); } } //#pragma data_seg(DATASEG_READONLY) const TCHAR c_szListbox[] = TEXT("listbox"); //#pragma data_seg() // Sets the new buddy // LRESULT setbuddy(PUDSTATE np, HWND hwndBuddy) { HWND hOldBuddy; TCHAR szClName[10]; hOldBuddy = np->hwndBuddy; if (np->hThemeBuddy) { CloseThemeData(np->hThemeBuddy); np->hThemeBuddy = NULL; } if ((np->hwndBuddy = hwndBuddy) == NULL) { pickbuddy(np); hwndBuddy = np->hwndBuddy; } if ((hOldBuddy != hwndBuddy) && np->fSubclassed) { ASSERT(hOldBuddy); RemoveWindowSubclass(hOldBuddy, ArrowKeyProc, 0); np->fSubclassed = FALSE; } np->uClass = CLASS_UNKNOWN; if (hwndBuddy) { if (np->ci.style & UDS_ARROWKEYS) { np->fSubclassed = TRUE; SetWindowSubclass(hwndBuddy, ArrowKeyProc, 0, (ULONG_PTR)np); } GetClassName(hwndBuddy, szClName, ARRAYSIZE(szClName)); if (!lstrcmpi(szClName, c_szEdit)) { np->uClass = CLASS_EDIT; np->hThemeBuddy = OpenThemeData(hwndBuddy, WC_EDIT); } else if (!lstrcmpi(szClName, c_szListbox)) { np->uClass = CLASS_LISTBOX; } } anchor(np); return (LRESULT)hOldBuddy; } // // This is how CCThemeDrawEdge should be implemented once DrawThemeLine supports part and // state ids // // BOOL UpDown_ThemeDrawEdge(HTHEME hTheme, HDC hdc, PRECT prc, int iPartId, int iStateId, UINT uFlags) { BOOL fRet = FALSE; RECT rc; int cxBorder, cyBorder; if (SUCCEEDED(GetThemeInt(hTheme, iPartId, iStateId, TMT_SIZINGBORDERWIDTH, &cxBorder))) { cyBorder = cxBorder; } else { cxBorder = g_cxBorder; cyBorder = g_cyBorder; } rc = *prc; if (uFlags & BF_LEFT) { rc.left += cxBorder; } if (uFlags & BF_TOP) { rc.top += cyBorder; } if (uFlags & BF_RIGHT) { rc.right -= cxBorder; } if (uFlags & BF_BOTTOM) { rc.bottom -= cyBorder; } ExcludeClipRect(hdc, rc.left, rc.top, rc.right, rc.bottom); if (SUCCEEDED(DrawThemeBackground(hTheme, hdc, iPartId, iStateId, prc, 0))) { fRet = TRUE; if (uFlags & BF_ADJUST) { *prc = rc; } } SelectClipRgn(hdc, NULL); return fRet; } // Paint the whole control // // PaintUpDownControl is theme aware void PaintUpDownControl(PUDSTATE np, HDC hdc) { UINT uFlags; PAINTSTRUCT ps; RECT rcBtn; RECT rc; int iPartId; int iStateId; BOOL bEnabled = (np->nUpper != np->nLower) && IsWindowEnabled(np->ci.hwnd); if (np->hwndBuddy) bEnabled = bEnabled && IsWindowEnabled(np->hwndBuddy); if (hdc) ps.hdc = hdc; else BeginPaint(np->ci.hwnd, &ps); GetClientRect(np->ci.hwnd, &rcBtn); // if we are autobuddy'd and anchored to a sunken-edge control, we draw the // "nonclient" area of ourselves to blend in with our buddy. if (!np->hTheme || (np->hThemeBuddy && (np->uClass == CLASS_EDIT))) { if (np->fSharedBorder && np->fSunkenBorder) { UINT bf = BF_TOP | BF_BOTTOM | BF_ADJUST | (np->ci.style & UDS_ALIGNLEFT ? BF_LEFT : 0) | (np->ci.style & UDS_ALIGNRIGHT ? BF_RIGHT : 0); if (!np->hThemeBuddy) { DrawEdge(ps.hdc, &rcBtn, EDGE_SUNKEN, bf); } else { UpDown_ThemeDrawEdge(np->hThemeBuddy, ps.hdc, &rcBtn, EP_EDITTEXT, bEnabled ? ETS_NORMAL : ETS_DISABLED, bf); } } } // with remaining space, draw appropriate scrollbar arrow controls in // upper and lower halves rc = rcBtn; if (np->ci.style & UDS_HORZ) { iPartId = SPNP_DOWNHORZ; // Down horizontal iStateId = DNHZS_NORMAL; uFlags = DFCS_SCROLLLEFT; if (np->fDown) { uFlags |= DFCS_PUSHED; iStateId = DNHZS_PRESSED; } if (!bEnabled) { uFlags |= DFCS_INACTIVE; iStateId = DNHZS_DISABLED; } if (np->uHot == UD_HITDOWN) { uFlags |= DFCS_HOT; if (iStateId == DNHZS_NORMAL) iStateId = DNHZS_HOT; } // Horizontal ones rc.right = (rcBtn.right + rcBtn.left) / 2; if (np->hTheme) { DrawThemeBackground(np->hTheme, ps.hdc, iPartId, iStateId, &rc, 0); } else { DrawFrameControl(ps.hdc, &rc, DFC_SCROLL, uFlags); } iPartId = SPNP_UPHORZ; // Up horizontal iStateId = UPHZS_NORMAL; uFlags = DFCS_SCROLLRIGHT; if (np->fUp) { uFlags |= DFCS_PUSHED; iStateId = UPHZS_PRESSED; } if (!bEnabled) { uFlags |= DFCS_INACTIVE; iStateId = UPHZS_DISABLED; } if (np->uHot == UD_HITUP) { uFlags |= DFCS_HOT; if (iStateId == UPHZS_NORMAL) iStateId = UPHZS_HOT; } rc.left = rcBtn.right - (rc.right - rc.left); // handles odd-x case, too rc.right = rcBtn.right; if (np->hTheme) { DrawThemeBackground(np->hTheme, ps.hdc, iPartId, iStateId, &rc, 0); } else { DrawFrameControl(ps.hdc, &rc, DFC_SCROLL, uFlags); } } else { iPartId = SPNP_UP; // Up vertical iStateId = UPS_NORMAL; uFlags = DFCS_SCROLLUP; if (np->fUp) { uFlags |= DFCS_PUSHED; iStateId = UPS_PRESSED; } if (!bEnabled) { uFlags |= DFCS_INACTIVE; iStateId = UPS_DISABLED; } if (np->uHot == UD_HITUP) { uFlags |= DFCS_HOT; if (iStateId == UPS_NORMAL) iStateId = UPS_HOT; } rc.bottom = (rcBtn.bottom + rcBtn.top) / 2; if (np->hTheme) { DrawThemeBackground(np->hTheme, ps.hdc, iPartId, iStateId, &rc, 0); } else { DrawFrameControl(ps.hdc, &rc, DFC_SCROLL, uFlags); } iPartId = SPNP_DOWN; // Down vertical iStateId = DNS_NORMAL; uFlags = DFCS_SCROLLDOWN; if (np->fDown) { uFlags |= DFCS_PUSHED; iStateId = DNS_PRESSED; } if (!bEnabled) { uFlags |= DFCS_INACTIVE; iStateId = DNS_DISABLED; } if (np->uHot == UD_HITDOWN) { uFlags |= DFCS_HOT; if (iStateId == DNS_NORMAL) iStateId = DNS_HOT; } rc.top = rcBtn.bottom - (rc.bottom - rc.top); // handles odd-y case, too rc.bottom = rcBtn.bottom; if (np->hTheme) { DrawThemeBackground(np->hTheme, ps.hdc, iPartId, iStateId, &rc, 0); } else { DrawFrameControl(ps.hdc, &rc, DFC_SCROLL, uFlags); } } if (hdc == NULL) EndPaint(np->ci.hwnd, &ps); } LRESULT CALLBACK ArrowKeyProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, ULONG_PTR dwRefData) { PUDSTATE np = (PUDSTATE)dwRefData; int cDetants; HRGN hrgnEdit = NULL; LRESULT lResult; switch (uMsg) { case WM_NCDESTROY: RemoveWindowSubclass(hWnd, ArrowKeyProc, 0); np->fSubclassed = FALSE; np->hwndBuddy = NULL; if (np->fUpDownDestroyed) { // The buddy was destroyed after updown so free the memory now // And pass off to the message to who we subclassed... LocalFree((HLOCAL)np); } break; case WM_GETDLGCODE: return (DefSubclassProc(hWnd, uMsg, wParam, lParam) | DLGC_WANTARROWS); case WM_KEYDOWN: switch (wParam) { case VK_UP: case VK_DOWN: if (GetCapture() != np->ci.hwnd) { /* Get the value from the buddy if this is the first key down */ if (!(lParam&(1L<<30))) { getint(np, NULL); } /* Update the visuals and bump the value */ np->bDown = (wParam == VK_DOWN); squish(np, !np->bDown, np->bDown); bump(np); //notify of navigation key usage CCNotifyNavigationKeyUsage(&(np->ci), UISF_HIDEFOCUS); } return(0L); default: break; } break; case WM_KEYUP: switch (wParam) { case VK_UP: case VK_DOWN: if (GetCapture() != np->ci.hwnd) { squish(np, FALSE, FALSE); } return(0L); default: break; } break; // this is dumb. // wm_char's aren't sent for arrow commands.. // what you're really eating here is & and (. #if 0 case WM_CHAR: switch (wParam) { case VK_UP: case VK_DOWN: return(0L); default: break; } break; #endif case WM_KILLFOCUS: // Reset wheel scroll amount gcWheelDelta = 0; break; case WM_NCPAINT: if (np->hTheme && (np->uClass == CLASS_EDIT)) { RECT rc; HRGN hrgnSpin; // // exclude the updown window rect from the edit painting region // GetWindowRect(np->ci.hwnd, &rc); hrgnSpin = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom); if (hrgnSpin) { switch (wParam) { case 0: case 1: // // update the entire edit nc area // GetWindowRect(hWnd, &rc); hrgnEdit = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom); if (!hrgnEdit) { break; } wParam = (WPARAM)hrgnEdit; // fall through default: // // exclude spin rgn from edit rgn // CombineRgn((HRGN)wParam, (HRGN)wParam, hrgnSpin, RGN_DIFF); } DeleteObject(hrgnSpin); } } break; default: if ((uMsg == g_msgMSWheel) && (GetCapture() != np->ci.hwnd)) { int iWheelDelta = GET_WHEEL_DELTA_WPARAM(wParam); // Update count of scroll amount gcWheelDelta -= iWheelDelta; cDetants = gcWheelDelta / WHEEL_DELTA; if (cDetants != 0) { gcWheelDelta %= WHEEL_DELTA; if (GET_KEYSTATE_WPARAM(wParam) & (MK_SHIFT | MK_CONTROL)) { break; } getint(np, NULL); np->bDown = (cDetants > 0); cDetants = abs(cDetants); while (cDetants-- > 0) { squish(np, !np->bDown, np->bDown); bump(np); } squish(np, FALSE, FALSE); } return 1; } break; } lResult = DefSubclassProc(hWnd, uMsg, wParam, lParam); if (hrgnEdit) { DeleteObject(hrgnEdit); } return lResult; } UINT setbase(PUDSTATE np, UINT wNewBase) { UINT wOldBase; switch (wNewBase) { case BASE_DECIMAL: case BASE_HEX: np->fUnsigned = (wNewBase != BASE_DECIMAL); wOldBase = np->nBase; np->nBase = wNewBase; setint(np); return wOldBase; } return 0; } ///////////////////////////////////////////////////////////////////////////// HWND WINAPI CreateUpDownControl(DWORD dwStyle, int x, int y, int cx, int cy, HWND hParent, int nID, HINSTANCE hInst, HWND hwndBuddy, int nUpper, int nLower, int nPos) { HWND hWnd = CreateWindow(s_szUpdownClass, NULL, dwStyle, x, y, cx, cy, hParent, IntToPtr_(HMENU, nID), hInst, 0L); if (hWnd) { SendMessage(hWnd, UDM_SETBUDDY, (WPARAM)hwndBuddy, 0L); SendMessage(hWnd, UDM_SETRANGE, 0, MAKELONG(nUpper, nLower)); SendMessage(hWnd, UDM_SETPOS, 0, MAKELONG(nPos, 0)); } return hWnd; } UINT UD_HitTest(PUDSTATE np, int x, int y) { RECT rc; GetClientRect(np->ci.hwnd, &rc); if (np->ci.style & UDS_HORZ) { // Horizontal placement if (x < (rc.right / 2)) { return UD_HITDOWN; } else if (x > (rc.right / 2)) { return UD_HITUP; } } else { if (y > (rc.bottom / 2)) { return UD_HITDOWN; } else if (y < (rc.bottom / 2)) { return UD_HITUP; } } return UD_HITNOWHERE; } void UD_Invalidate(PUDSTATE np, UINT uWhich, BOOL fErase) { int iMid; RECT rc; GetClientRect(np->ci.hwnd, &rc); if (np->ci.style & UDS_HORZ) { iMid = rc.right / 2; if (uWhich == UD_HITDOWN) { rc.right = iMid; } else if (uWhich == UD_HITUP) { rc.left = iMid; } else return; } else { iMid = rc.bottom /2; if (uWhich == UD_HITDOWN) { rc.top = iMid; } else if (uWhich == UD_HITUP){ rc.bottom = iMid; } else return; } InvalidateRect(np->ci.hwnd, &rc, fErase); } void UD_OnMouseMove(PUDSTATE np, DWORD dwPos) { if (np->ci.style & UDS_HOTTRACK) { UINT uHot = UD_HitTest(np, GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos)); if (uHot != np->uHot) { UD_Invalidate(np, np->uHot, FALSE); UD_Invalidate(np, uHot, FALSE); np->uHot = uHot; } } } ///////////////////////////////////////////////////////////////////////////// // UpDownWndProc: // // UpDownWndProc is theme aware LRESULT CALLBACK UpDownWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { RECT rc; int i; BOOL f; LRESULT lres; PUDSTATE np = GetWindowPtr(hwnd, 0); if (np) { if ((uMsg >= WM_MOUSEFIRST) && (uMsg <= WM_MOUSELAST) && (np->ci.style & UDS_HOTTRACK) && !np->fTrackSet) { TRACKMOUSEEVENT tme; np->fTrackSet = TRUE; tme.cbSize = sizeof(tme); tme.hwndTrack = np->ci.hwnd; tme.dwFlags = TME_LEAVE; TrackMouseEvent(&tme); } else if (uMsg == WM_THEMECHANGED) // Check for theme changes { if (np->hTheme) CloseThemeData(np->hTheme); np->hTheme = OpenThemeData(np->ci.hwnd, L"Spin"); if (np->hTheme) { // Ensure style is applied np->ci.style |= UDS_HOTTRACK; } if (np->hThemeBuddy) { CloseThemeData(np->hThemeBuddy); np->hThemeBuddy = NULL; } if (np->hwndBuddy && (np->uClass == CLASS_EDIT)) { np->hThemeBuddy = OpenThemeData(np->hwndBuddy, WC_EDIT); } InvalidateRect(np->ci.hwnd, NULL, TRUE); } } else if (uMsg != WM_CREATE) goto DoDefault; switch (uMsg) { case WM_MOUSEMOVE: UD_OnMouseMove(np, (DWORD) lParam); break; case WM_MOUSELEAVE: np->fTrackSet = FALSE; UD_Invalidate(np, np->uHot, FALSE); np->uHot = UD_HITNOWHERE; break; case WM_LBUTTONDOWN: { // Don't set a timer if on the middle border BOOL bTimeIt = TRUE; if (np->hwndBuddy && !IsWindowEnabled(np->hwndBuddy)) break; SetCapture(hwnd); getint(np, NULL); switch (np->uClass) { case CLASS_EDIT: case CLASS_LISTBOX: SetFocus(np->hwndBuddy); break; } switch(UD_HitTest(np, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) { case UD_HITDOWN: np->bDown = TRUE; squish(np, FALSE, TRUE); break; case UD_HITUP: np->bDown = FALSE; squish(np, TRUE, FALSE); break; case UD_HITNOWHERE: bTimeIt = FALSE; break; } if (bTimeIt) { SetTimer(hwnd, 1, GetProfileInt(TEXT("windows"), TEXT("CursorBlinkRate"), 530), NULL); bump(np); } break; } case WM_TIMER: { POINT pt; if (GetCapture() != hwnd) { goto EndScroll; } SetTimer(hwnd, 1, 100, NULL); GetWindowRect(hwnd, &rc); if (np->ci.style & UDS_HORZ) { i = (rc.left + rc.right) / 2; if (np->bDown) { rc.right = i; } else { rc.left = i; } } else { i = (rc.top + rc.bottom) / 2; if (np->bDown) { rc.top = i; } else { rc.bottom = i; } } InflateRect(&rc, (g_cxFrame+1)/2, (g_cyFrame+1)/2); GetCursorPos(&pt); if (PtInRect(&rc, pt)) { squish(np, !np->bDown, np->bDown); bump(np); } else { squish(np, FALSE, FALSE); } break; } case WM_LBUTTONUP: if (np->hwndBuddy && !IsWindowEnabled(np->hwndBuddy)) break; if (GetCapture() == hwnd) { EndScroll: squish(np, FALSE, FALSE); // We cannot call CCReleaseCapture() here, because it busts a lot of apps. ReleaseCapture(); KillTimer(hwnd, 1); if (np->uClass == CLASS_EDIT) Edit_SetSel(np->hwndBuddy, 0, -1); if (np->ci.style & UDS_HORZ) FORWARD_WM_HSCROLL(np->ci.hwndParent, np->ci.hwnd, SB_ENDSCROLL, np->nPos, SendMessage); else FORWARD_WM_VSCROLL(np->ci.hwndParent, np->ci.hwnd, SB_ENDSCROLL, np->nPos, SendMessage); } break; case WM_ENABLE: InvalidateRect(hwnd, NULL, TRUE); break; case WM_WININICHANGE: if (np && (!wParam || (wParam == SPI_SETNONCLIENTMETRICS) || (wParam == SPI_SETICONTITLELOGFONT))) { InitGlobalMetrics(wParam); unachor(np); anchor(np); } break; case WM_PRINTCLIENT: case WM_PAINT: PaintUpDownControl(np, (HDC)wParam); break; case WM_UPDATEUISTATE: //not sure need to set bit, will probably not use it, on the other hand this // is consistent with remaining of common controls and not very expensive CCOnUIState(&(np->ci), WM_UPDATEUISTATE, wParam, lParam); goto DoDefault; case UDM_SETRANGE: np->nUpper = GET_X_LPARAM(lParam); np->nLower = GET_Y_LPARAM(lParam); nudge(np); break; case UDM_SETRANGE32: np->nUpper = (int)lParam; np->nLower = (int)wParam; break; case UDM_GETRANGE32: if (lParam) { *((LPINT)lParam) = np->nUpper; } if (wParam) { *((LPINT)wParam) = np->nLower; } break; case UDM_GETRANGE: return MAKELONG(np->nUpper, np->nLower); case UDM_SETBASE: // wParam: new base // lParam: not used // return: 0 if invalid base is specified, // previous base otherwise return (LRESULT)setbase(np, (UINT)wParam); case UDM_GETBASE: return np->nBase; case UDM_SETPOS: lParam = GET_X_LPARAM(lParam); // FALL THROUGH case UDM_SETPOS32: { int iNewPos = (int)lParam; if (compare(np, np->nLower, np->nUpper, DONTCARE) < 0) { if (compare(np, iNewPos, np->nUpper, DONTCARE) > 0) { iNewPos = np->nUpper; } if (compare(np, iNewPos, np->nLower, DONTCARE) < 0) { iNewPos = np->nLower; } } else { if (compare(np, iNewPos, np->nUpper, DONTCARE) < 0) { iNewPos = np->nUpper; } if (compare(np, iNewPos, np->nLower, DONTCARE) > 0) { iNewPos = np->nLower; } } i = np->nPos; np->nPos = iNewPos; setint(np); NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, np->ci.hwnd, OBJID_CLIENT, 0); return (LRESULT)i; } case UDM_GETPOS: lres = getint(np, &f); return MAKELRESULT(lres, f); case UDM_GETPOS32: return getint(np, (BOOL *)lParam); case UDM_SETBUDDY: return setbuddy(np, (HWND)wParam); case UDM_GETBUDDY: return (LRESULT)np->hwndBuddy; case UDM_SETACCEL: if (wParam == 0) { return FALSE; } if (wParam >= NUM_UDACCELS) { UDACCEL *puda; puda = (UDACCEL *)LocalReAlloc((HLOCAL)np->udAccel, sizeof(UDACCEL)*wParam, LMEM_MOVEABLE); if (!puda) { return FALSE; } else { np->udAccel = puda; } } if (np->udAccel != NULL) { np->nAccel = (UINT)wParam; for (i = 0; i < (int)wParam; i++) { np->udAccel[i] = ((LPUDACCEL)lParam)[i]; } } return TRUE; case UDM_GETACCEL: if (wParam > np->nAccel) { wParam = np->nAccel; } if (np->udAccel) { for (i=0; i<(int)wParam; ++i) { ((LPUDACCEL)lParam)[i] = np->udAccel[i]; } } return np->nAccel; case WM_NOTIFYFORMAT: return CIHandleNotifyFormat(&np->ci, lParam); case WM_CREATE: // Allocate the instance data space. np = (PUDSTATE)LocalAlloc(LPTR, sizeof(UDSTATE)); if (!np) return -1; SetWindowPtr(hwnd, 0, np); #define lpCreate ((CREATESTRUCT *)lParam) CIInitialize(&np->ci, hwnd, lpCreate); np->hTheme = OpenThemeData(np->ci.hwnd, L"Spin"); // np->fUp = // np->fDown = // np->fUnsigned = // np->fSharedBorder = // np->fSunkenBorder = // FALSE; if (lpCreate->style & UDS_UNSIGNED) np->fUnsigned = TRUE; if (lpCreate->dwExStyle & WS_EX_CLIENTEDGE) np->fSunkenBorder = TRUE; np->nBase = BASE_DECIMAL; np->nUpper = 0; np->nLower = 100; np->nPos = 0; np->hwndBuddy = NULL; np->uClass = CLASS_UNKNOWN; ASSERT(np->cReenterSetint == 0); np->udAccel = (UDACCEL *)LocalAlloc(LPTR, sizeof(UDACCEL) * NUM_UDACCELS); if (np->udAccel) { np->nAccel = NUM_UDACCELS; np->udAccel[0].nSec = 0; np->udAccel[0].nInc = 1; np->udAccel[1].nSec = 2; np->udAccel[1].nInc = 5; np->udAccel[2].nSec = 5; np->udAccel[2].nInc = 20; } else { np->nAccel = 0; } /* This does the pickbuddy and anchor */ setbuddy(np, NULL); setint(np); // Automatically enable hot tracking if themes are being used if (np->hTheme) np->ci.style |= UDS_HOTTRACK; break; case WM_DESTROY: if (np) { if (np->hTheme) { CloseThemeData(np->hTheme); np->hTheme = NULL; } if (np->hThemeBuddy) { CloseThemeData(np->hThemeBuddy); np->hThemeBuddy = NULL; } if (np->udAccel) { LocalFree((HLOCAL)np->udAccel); } if (np->hwndBuddy) { // Our buddy needs to be unsubclassed, which we'll do // in response to WM_NCDESTROY; doing so now would // bust any subsequent call to the suclass proc. DebugMsg(DM_TRACE, TEXT("UpDown Destroyed while buddy subclassed")); np->fUpDownDestroyed = TRUE; } else { LocalFree((HLOCAL)np); } SetWindowPtr(hwnd, 0, 0); } break; case WM_GETOBJECT: if( lParam == OBJID_QUERYCLASSNAMEIDX ) return MSAA_CLASSNAMEIDX_UPDOWN; goto DoDefault; default: { if (CCWndProc(&np->ci, uMsg, wParam, lParam, &lres)) return lres; } DoDefault: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0L; } ///////////////////////////////////////////////////////////////////////////// // InitUpDownClass: // Adds our WNDCLASS to the system. // #pragma code_seg(CODESEG_INIT) BOOL InitUpDownClass(HINSTANCE hInst) { WNDCLASS wndclass; wndclass.lpfnWndProc = UpDownWndProc; wndclass.lpszClassName = s_szUpdownClass; wndclass.hInstance = hInst; wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hIcon = NULL; wndclass.lpszMenuName = NULL; wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = sizeof(PUDSTATE); if (!RegisterClass(&wndclass) && !GetClassInfo(hInst, s_szUpdownClass, &wndclass)) return FALSE; return TRUE; } #pragma code_seg()