#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

typedef struct {
    HWND    hwnd;
    LONG    style;
    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.
    UINT     nBase;
    int      nUpper;
    int      nLower;
    int      nPos;
    HWND     hwndParent;
    UINT     uClass;
    BOOL     bDown;
    DWORD    dwStart;
    WNDPROC  lpfnDefProc;
    UINT     nAccel;
    UDACCEL  udAccel[NUM_UDACCELS];
} 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);


/////////////////////////////////////////////////////////////////////////////

//
// ***** 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->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->style & UDS_AUTOBUDDY)
        np->hwndBuddy = GetWindow(np->hwnd, GW_HWNDPREV);
}

void NEAR PASCAL unachor(PUDSTATE np)
{
    RECT rc;
    RECT rcBuddy;
    RECT rcUD;

    if ( np->hwndBuddy && (np->style & (UDS_ALIGNLEFT | UDS_ALIGNRIGHT))) {
        GetWindowRect(np->hwndBuddy, &rcBuddy);
        GetWindowRect(np->hwnd, &rcUD);
        UnionRect(&rc, &rcUD, &rcBuddy);
        MapWindowRect(NULL, np->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->style & WS_BORDER) == WS_BORDER;

    bAlignToBuddy = np->hwndBuddy && (np->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->style &= ~WS_BORDER;

            SetWindowLong(np->hwnd, GWL_STYLE, np->style);
            SetWindowLong(np->hwnd, GWL_EXSTYLE, GetWindowLong(np->hwnd, GWL_EXSTYLE) & ~(WS_EX_CLIENTEDGE));
        }
    }
    else
    {
        GetWindowRect(np->hwnd, &rc);
    }

    nHeight = rc.bottom - rc.top;
    nWidth = rc.right - rc.left;

    ScreenToClient(np->hwndParent, (LPPOINT)&rc.left);
    rc.right = rc.left + nWidth;

    if (bAlignToBuddy)
    {
        nWidth = g_cxVScroll - g_cxBorder + nOver;
	rcBuddy = rc;

        if (np->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->style & UDS_HORZ))
    {
        nWidth = g_cxVScroll + 2 * nHasBorder;
    }

    SetWindowPos(np->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).

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.
//
BOOL NEAR PASCAL nudge(PUDSTATE np)
{
    BOOL bOutOfRange = TRUE;
    int min = np->nUpper;
    int max = np->nLower;

    if (compare(np,max,min, DONTCARE) < 0)
    {
        int t;
        t = min;
        min = max;
        max = t;
    }


    if (np->style & UDS_WRAP)
    {
        if ((compare(np, np->nPos, min, np->fUnsigned ? UNSIGNED : SIGNED) < 0))
            np->nPos = max;
        else if ((compare(np, np->nPos, max, np->fUnsigned ? UNSIGNED : SIGNED) > 0))
            np->nPos = min;
        else bOutOfRange = FALSE;
    }
    else
    {
        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->hwnd))
    {
        bTop = FALSE;
        bBottom = FALSE;
    }
    else
    {
        bTop = !!bTop;
        bBottom = !!bBottom;
    }

    if (np->fUp != bTop)
    {
        np->fUp = bTop;
        bInvalidate = TRUE;
    }
    if (np->fDown != bBottom)
    {
        np->fDown = bBottom;
        bInvalidate = TRUE;
    }

    if (bInvalidate)
    {
        np->dwStart = GetTickCount();
        InvalidateRect(np->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
}

// Gets the caption of the buddy
//
LRESULT NEAR PASCAL getint(PUDSTATE np)
{
    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->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('-'))
                    {
                        sign = -1;
                        ++p;
                    }

                    for (nPos=0; *p; p++)
                    {
                        cTemp = *p;

                        // If there is a thousand separator, make sure it is in the
                        // right place
                        if (cTemp == szThousand[0] && lstrlen(p)==4)
                        {
                            continue;
                        }

                        cTemp -= TEXT('0');
                        if ((UINT)cTemp > 9)
                        {
                            goto BadValue;
                        }
                        nPos = (nPos*10) + cTemp;
                    }

                    np->nPos = nPos*sign;
                    break;
            }
            bInValid = nudge(np);
        }
    }

BadValue:
    return(MAKELRESULT(np->nPos, bInValid));
}

// Sets the caption of the buddy if appropriate.
//
void NEAR PASCAL setint(PUDSTATE np)
{
    static int cReenter = 0;

    TCHAR szInt[MAX_INTLENGTH];
    TCHAR szThousand[2];
    int pos = np->nPos;
    LPTSTR p = szInt;

    isgoodbuddy(np);
    if (np->hwndBuddy && np->style & UDS_SETBUDDYINT)
    {
        /* If we have reentered, then maybe the app has set up a loop.
         * Check to see if the value really needs to be set.
         */
        if (cReenter && (LRESULT)pos==getint(np))
        {
            return;
        }
        np->nPos = pos;

        ++cReenter;

        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:
                    wsprintf(p, TEXT("0x%04X"), pos);
                    break;

                case BASE_DECIMAL:
        default:
                    if (pos < 0)
                    {
                        *p++ = TEXT('-');
                        pos = -pos;
                    }

                    if (pos >= 1000 && !(np->style & UDS_NOTHOUSANDS))
                    {
                        getthousands(szThousand);
                        p += wsprintf(p, TEXT("%d"), pos / 1000);
                        if (szThousand[0])
                            *p++ = szThousand[0];
                        wsprintf(p, TEXT("%03d"), pos % 1000);
                    }
                    else
                    {
                        wsprintf(p, TEXT("%d"), pos);
                    }
                    break;
            }

            SetWindowText(np->hwndBuddy, szInt);
        }

        --cReenter;
    }
}

// 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 (SendNotifyEx(np->hwndParent, np->hwnd, UDN_DELTAPOS, &nm.hdr, FALSE))
            return;

        np->nPos += nm.iDelta;
        for ( ; ; )
        {
            if (!((int)np->nPos % (int)increment))
            {
                break;
            }
            np->nPos += direction;
        }

        nudge(np);
        setint(np);
        if (np->style & UDS_HORZ)
            FORWARD_WM_HSCROLL(np->hwndParent, np->hwnd, SB_THUMBPOSITION, np->nPos, SendMessage);
        else
            FORWARD_WM_VSCROLL(np->hwndParent, np->hwnd, SB_THUMBPOSITION, np->nPos, SendMessage);
    }
}

//#pragma data_seg(DATASEG_READONLY)
const TCHAR c_szEdit[] = TEXT("edit");
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 (hOldBuddy && GetProp(hOldBuddy, s_szUpdownClass))
    {
        SetWindowLong(hOldBuddy, GWL_WNDPROC, (LONG)np->lpfnDefProc);
        RemoveProp(hOldBuddy, s_szUpdownClass);
        np->lpfnDefProc = NULL;
    }

    np->hwndBuddy = hwndBuddy;
    if (!hwndBuddy)
    {
        pickbuddy(np);
        hwndBuddy = np->hwndBuddy;
    }

    np->uClass = CLASS_UNKNOWN;
    if (hwndBuddy)
    {
        if (np->style & UDS_ARROWKEYS)
        {
            if (GetProp(hwndBuddy, s_szUpdownClass) == NULL)
            {
                np->lpfnDefProc = (WNDPROC)SetWindowLong(hwndBuddy, GWL_WNDPROC, (LONG)ArrowKeyProc);
                SetProp(hwndBuddy, s_szUpdownClass, (HANDLE)np);
            }
            else
            {
                DebugMsg(DM_ERROR, TEXT("SetBuddy called on already subclassed buddy"));
            }
        }

        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 MAKELRESULT(hOldBuddy, 0);
}


// Paint the whole control
//
void NEAR PASCAL PaintUpDownControl(PUDSTATE np, HDC hdc)
{
    PAINTSTRUCT ps;
    RECT rcBtn;
    RECT rc;

    BOOL bEnabled = (np->nUpper != np->nLower) && IsWindowEnabled(np->hwnd);

    if (np->hwndBuddy)
        bEnabled = bEnabled && IsWindowEnabled(np->hwndBuddy);

    if (hdc)
        ps.hdc = hdc;
    else
        BeginPaint(np->hwnd, &ps);

    GetClientRect(np->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->style & UDS_ALIGNLEFT ? BF_LEFT : 0) |
            (np->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->style & UDS_HORZ)
        {
                // Horizontal ones
        rc.right = (rcBtn.right + rcBtn.left) / 2;
        DrawFrameControl(ps.hdc, &rc, DFC_SCROLL,
                 (DFCS_SCROLLLEFT |
                 (np->fDown ? DFCS_PUSHED : 0) |
                 (bEnabled ? 0 : DFCS_INACTIVE)));
        rc.left = rcBtn.right - (rc.right - rc.left); // handles odd-x case, too
        rc.right = rcBtn.right;
        DrawFrameControl(ps.hdc, &rc, DFC_SCROLL,
                 (DFCS_SCROLLRIGHT |
                 (np->fUp ? DFCS_PUSHED : 0) |
                 (bEnabled ? 0 : DFCS_INACTIVE)));
        }
        else
        {
        rc.bottom = (rcBtn.bottom + rcBtn.top) / 2;
        DrawFrameControl(ps.hdc, &rc, DFC_SCROLL,
                 (DFCS_SCROLLUP |
                 (np->fUp ? DFCS_PUSHED : 0) |
                 (bEnabled ? 0 : DFCS_INACTIVE)));
        rc.top = rcBtn.bottom - (rc.bottom - rc.top); // handles odd-y case, too
        rc.bottom = rcBtn.bottom;
        DrawFrameControl(ps.hdc, &rc, DFC_SCROLL,
                 (DFCS_SCROLLDOWN |
                 (np->fDown ? DFCS_PUSHED : 0) |
                 (bEnabled ? 0 : DFCS_INACTIVE)));
        }

    if (hdc == NULL)
        EndPaint(np->hwnd, &ps);
}


LRESULT CALLBACK ArrowKeyProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    PUDSTATE np = (PUDSTATE)GetProp(hWnd, s_szUpdownClass);
    WNDPROC  lpfnDefProc;

    switch (uMsg)
    {
    case WM_NCDESTROY:
        /* Restore the window proc.
         */
        lpfnDefProc = np->lpfnDefProc;
        SetWindowLong(hWnd, GWL_WNDPROC, (LONG)lpfnDefProc);
        RemoveProp(hWnd, s_szUpdownClass);
        np->hwndBuddy = NULL;
        np->lpfnDefProc = 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);
        }
        return CallWindowProc(lpfnDefProc, hWnd, uMsg, wParam, lParam);

    case WM_GETDLGCODE:
        return CallWindowProc(np->lpfnDefProc, hWnd, uMsg, wParam, lParam) | DLGC_WANTARROWS;

    case WM_KEYDOWN:
        switch (wParam)
        {
        case VK_UP:
        case VK_DOWN:
            if (GetCapture() != np->hwnd)
            {
                /* Get the value from the buddy if this is the first key down
                 */
                if (!(lParam&(1L<<30)))
                {
                    getint(np);
                }

                /* Update the visuals and bump the value
                 */
                np->bDown = (wParam == VK_DOWN);
                squish(np, !np->bDown, np->bDown);
                bump(np);
            }
            return(0L);

        default:
            break;
        }
        break;

    case WM_KEYUP:
        switch (wParam)
        {
        case VK_UP:
        case VK_DOWN:
            if (GetCapture() != np->hwnd)
            {
                squish(np, FALSE, FALSE);
            }
            return(0L);

        default:
            break;
        }
        break;

    case WM_CHAR:
        switch (wParam)
        {
        case VK_UP:
        case VK_DOWN:
            return(0L);

        default:
            break;
        }
        break;

    default:
        break;
    }

    return CallWindowProc(np->lpfnDefProc, 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, (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;
}

/////////////////////////////////////////////////////////////////////////////

// UpDownWndProc:
//
LRESULT CALLBACK UpDownWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    RECT rc;
    int i;
    PUDSTATE np = (PUDSTATE)GetWindowInt(hWnd, 0);

    switch (uMsg)
    {
    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);

        switch (np->uClass)
        {
        case CLASS_EDIT:
        case CLASS_LISTBOX:
            SetFocus(np->hwndBuddy);
            break;
        }

        GetClientRect(hWnd, &rc);
        if (np->style & UDS_HORZ)
        {
                // Horizontal placement
            if ((int)LOWORD(lParam) < (rc.right / 2))
            {
                np->bDown = TRUE;
                squish(np, FALSE, TRUE);
            }
            else if ((int)LOWORD(lParam) > (rc.right / 2))
            {
                np->bDown = FALSE;
                squish(np, TRUE, FALSE);
            }
            else
                bTimeIt = FALSE;
        }
        else
        {
            if ((int)HIWORD(lParam) > (rc.bottom / 2))
            {
                np->bDown = TRUE;
                squish(np, FALSE, TRUE);
            }
            else if ((int)HIWORD(lParam) < (rc.bottom / 2))
            {
                np->bDown = FALSE;
                squish(np, TRUE, FALSE);
            }
            else
                bTimeIt = FALSE;
        }

        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->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);
            ReleaseCapture();
            KillTimer(hWnd, 1);

            if (np->uClass == CLASS_EDIT)
                Edit_SetSel(np->hwndBuddy, 0, -1);

                        if (np->style & UDS_HORZ)
                            FORWARD_WM_HSCROLL(np->hwndParent, np->hwnd,
                                      SB_ENDSCROLL, np->nPos, SendMessage);
                        else
                            FORWARD_WM_VSCROLL(np->hwndParent, np->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 UDM_SETRANGE:
        np->nUpper = (int)(SHORT)LOWORD(lParam);
        np->nLower = (int)(SHORT)HIWORD(lParam);
        nudge(np);
        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:
    {
        int iNewPos = (int)(SHORT)LOWORD(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);
        return (LRESULT)i;
    }

    case UDM_GETPOS:
        return getint(np);

    case UDM_SETBUDDY:
        return setbuddy(np, (HWND)wParam);

    case UDM_GETBUDDY:
        return (LRESULT)(int)np->hwndBuddy;

    case UDM_SETACCEL:
            if (wParam == 0)
                return(FALSE);
            if (wParam >= NUM_UDACCELS)
            {
                HANDLE npPrev = (HANDLE)np;
                np = (PUDSTATE)LocalReAlloc((HLOCAL)np, sizeof(UDSTATE)+(wParam-NUM_UDACCELS)*sizeof(UDACCEL),
                    LMEM_MOVEABLE);
                if (!np)
                {
                    return(FALSE);
                }
                else
                {
                    SetWindowInt(hWnd, 0, (int)np);

                    if ((np->style & UDS_ARROWKEYS) && np->hwndBuddy)
                    {
                        // Update the property of our buddy.
                        if ((HANDLE)GetProp(np->hwndBuddy, s_szUpdownClass) == npPrev)
                        {
                            RemoveProp(np->hwndBuddy, s_szUpdownClass);
                            SetProp(np->hwndBuddy, s_szUpdownClass, (HANDLE)np);
                        }
                    }
                }
            }

            np->nAccel = 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_CREATE:
        // Allocate the instance data space.
        np = (PUDSTATE)LocalAlloc(LPTR, sizeof(UDSTATE));
        if (!np)
            return -1;

        SetWindowInt(hWnd, 0, (int)np);

            #define lpCreate ((CREATESTRUCT FAR *)lParam)

        np->hwnd = hWnd;
            np->hwndParent = lpCreate->hwndParent;
            np->style = lpCreate->style;

        // np->fUp =
        // np->fDown =
            // np->fUnsigned =
            // np->fSharedBorder =
            // np->fSunkenBorder =
        //  FALSE;

        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;

            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:
        if (np) {
            if (np->hwndBuddy || np->lpfnDefProc)
            {
                // Make sure that our buddy is unsubclassed.
                DebugMsg(DM_ERROR, TEXT("UpDown Destroyed while buddy subclassed"));
                np->fUpDownDestroyed = TRUE;
            }
            else
                LocalFree((HLOCAL)np);
            SetWindowInt(hWnd, 0, 0);
        }
        break;

    default:
        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    = (WNDPROC)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()