1818 lines
45 KiB
C
1818 lines
45 KiB
C
#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 <U y" means "x < y as UNSIGNED".
|
|
|
|
int compare(PUDSTATE np, int x, int y, UINT fCompareType)
|
|
{
|
|
if ((fCompareType == UNSIGNED) || ((np->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 <D min) swap(min, max)
|
|
if (compare(np,max,min, DONTCARE) < 0)
|
|
{
|
|
int t;
|
|
t = min;
|
|
min = max;
|
|
max = t;
|
|
}
|
|
|
|
|
|
if (np->ci.style & UDS_WRAP)
|
|
{
|
|
// if (nPos <D min) nPos = max -- wrap from below to above
|
|
// else 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 min) nPos = min -- pin at min
|
|
// 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()
|