windows-nt/Source/XPSP1/NT/shell/comctl32/v5/updown.c
2020-09-26 16:20:57 +08:00

1536 lines
40 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 {
CONTROLINFO ci;
HWND hwndBuddy;
unsigned fUp : 1;
unsigned fDown : 1;
unsigned fUnsigned : 1; // BUGBUG: no way to turn this on
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[NUM_UDACCELS];
UINT uHot;
int cReenterSetint; // To avoid recursion death in setint()
} UDSTATE, NEAR *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 NEAR PASCAL 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 NEAR PASCAL pickbuddy(PUDSTATE np)
{
if (np->ci.style & UDS_AUTOBUDDY)
np->hwndBuddy = GetWindow(np->ci.hwnd, GW_HWNDPREV);
}
void NEAR PASCAL 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 NEAR PASCAL 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))
{
// BUGBUG 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 NEAR PASCAL 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)
//
// BUGBUG -- doesn't handle values that exceed MAXINT or MININT.
BOOL NEAR PASCAL 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 NEAR PASCAL 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;
MyNotifyWinEvent(EVENT_OBJECT_STATECHANGE, np->ci.hwnd, OBJID_CLIENT, 1);
}
if (np->fDown != bBottom)
{
np->fDown = bBottom;
bInvalidate = TRUE;
MyNotifyWinEvent(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 NEAR PASCAL 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;
#ifdef WINNT
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++;
}
#else
// Win9x - take only the first grouping
grouping = StrToInt(szGrouping);
#endif
return grouping;
}
// Gets the caption of the buddy
// Returns the current position of the updown control
// and sets *pfError on error.
//
LRESULT NEAR PASCAL 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 NEAR PASCAL 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 NEAR PASCAL 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);
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;
}
}
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);
MyNotifyWinEvent(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 NEAR PASCAL setbuddy(PUDSTATE np, HWND hwndBuddy)
{
HWND hOldBuddy;
TCHAR szClName[10];
hOldBuddy = np->hwndBuddy;
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;
}
else if (!lstrcmpi(szClName, c_szListbox))
{
np->uClass = CLASS_LISTBOX;
}
}
anchor(np);
return (LRESULT)hOldBuddy;
}
// Paint the whole control
//
void NEAR PASCAL PaintUpDownControl(PUDSTATE np, HDC hdc)
{
UINT uFlags;
PAINTSTRUCT ps;
RECT rcBtn;
RECT rc;
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->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);
DrawEdge(ps.hdc, &rcBtn, EDGE_SUNKEN, bf);
}
// with remaining space, draw appropriate scrollbar arrow controls in
// upper and lower halves
rc = rcBtn;
if (np->ci.style & UDS_HORZ)
{
uFlags = DFCS_SCROLLLEFT;
if (np->fDown)
uFlags |= DFCS_PUSHED;
if (!bEnabled)
uFlags |= DFCS_INACTIVE;
if (g_bRunOnNT5 || g_bRunOnMemphis)
{
if (np->uHot == UD_HITDOWN)
uFlags |= DFCS_HOT;
}
// Horizontal ones
rc.right = (rcBtn.right + rcBtn.left) / 2;
DrawFrameControl(ps.hdc, &rc, DFC_SCROLL,
uFlags);
uFlags = DFCS_SCROLLRIGHT;
if (np->fUp)
uFlags |= DFCS_PUSHED;
if (!bEnabled)
uFlags |= DFCS_INACTIVE;
if (g_bRunOnNT5 || g_bRunOnMemphis)
{
if (np->uHot == UD_HITUP)
uFlags |= DFCS_HOT;
}
rc.left = rcBtn.right - (rc.right - rc.left); // handles odd-x case, too
rc.right = rcBtn.right;
DrawFrameControl(ps.hdc, &rc, DFC_SCROLL, uFlags);
}
else
{
uFlags = DFCS_SCROLLUP;
if (np->fUp)
uFlags |= DFCS_PUSHED;
if (!bEnabled)
uFlags |= DFCS_INACTIVE;
if (g_bRunOnNT5 || g_bRunOnMemphis)
{
if (np->uHot == UD_HITUP)
uFlags |= DFCS_HOT;
}
rc.bottom = (rcBtn.bottom + rcBtn.top) / 2;
DrawFrameControl(ps.hdc, &rc, DFC_SCROLL, uFlags);
uFlags = DFCS_SCROLLDOWN;
if (np->fDown)
uFlags |= DFCS_PUSHED;
if (!bEnabled)
uFlags |= DFCS_INACTIVE;
if (g_bRunOnNT5 || g_bRunOnMemphis)
{
if (np->uHot == UD_HITDOWN)
uFlags |= DFCS_HOT;
}
rc.top = rcBtn.bottom - (rc.bottom - rc.top); // handles odd-y case, too
rc.bottom = rcBtn.bottom;
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;
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);
#ifdef KEYBOARDCUES
//notify of navigation key usage
CCNotifyNavigationKeyUsage(&(np->ci), UISF_HIDEFOCUS);
#endif
}
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_SETFOCUS:
ASSERT(gcWheelDelta == 0);
break;
default:
if (uMsg == g_msgMSWheel && GetCapture() != np->ci.hwnd) {
int iWheelDelta;
if (g_bRunOnNT || g_bRunOnMemphis)
{
iWheelDelta = (int)(short)HIWORD(wParam);
}
else
{
iWheelDelta = (int)wParam;
}
// Update count of scroll amount
gcWheelDelta -= iWheelDelta;
cDetants = gcWheelDelta / WHEEL_DELTA;
if (cDetants != 0) {
gcWheelDelta %= WHEEL_DELTA;
if (g_bRunOnNT || g_bRunOnMemphis)
{
if (wParam & (MK_SHIFT | MK_CONTROL))
break;
}
else
{
if (GetKeyState(VK_SHIFT) < 0 || GetKeyState(VK_CONTROL) < 0)
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;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
UINT NEAR PASCAL 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:
//
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_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;
#ifdef KEYBOARDCUES
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;
#endif
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);
MyNotifyWinEvent(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)
{
HANDLE npPrev = (HANDLE)np;
np = (PUDSTATE)LocalReAlloc((HLOCAL)npPrev, sizeof(UDSTATE)+(wParam-NUM_UDACCELS)*sizeof(UDACCEL),
LMEM_MOVEABLE);
if (!np)
{
return(FALSE);
}
else
{
SetWindowPtr(hwnd, 0, np);
if ((np->ci.style & UDS_ARROWKEYS) && np->hwndBuddy)
{
np->fSubclassed = TRUE;
SetWindowSubclass(np->hwndBuddy, ArrowKeyProc, 0,
(ULONG_PTR)np);
}
}
}
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;
}
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:
CCCreateWindow();
// Allocate the instance data space.
np = (PUDSTATE)LocalAlloc(LPTR, sizeof(UDSTATE));
if (!np)
return -1;
SetWindowPtr(hwnd, 0, np);
#define lpCreate ((CREATESTRUCT FAR *)lParam)
CIInitialize(&np->ci, hwnd, lpCreate);
// 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->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;
/* This does the pickbuddy and anchor
*/
setbuddy(np, NULL);
setint(np);
break;
case WM_DESTROY:
CCDestroyWindow();
if (np) {
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:
{
LRESULT lres;
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 FAR PASCAL InitUpDownClass(HINSTANCE hInst)
{
WNDCLASS wndclass;
if (!GetClassInfo(hInst, s_szUpdownClass, &wndclass))
{
#ifndef WIN32
extern LRESULT CALLBACK _UpDownWndProc(HWND, UINT, WPARAM, LPARAM);
wndclass.lpfnWndProc = _UpDownWndProc;
#else
wndclass.lpfnWndProc = UpDownWndProc;
#endif
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);
return RegisterClass(&wndclass);
}
return TRUE;
}
#pragma code_seg()