#include "ctlspriv.h"
#include "flat_sb.h"

//  Following interfaces are imported from newwsbctl.c and newsb.c. These
//  functions are for internal use only.

void FlatSB_Internal_CalcSBStuff(WSBState *, BOOL);
void FlatSB_Internal_DoScroll(WSBState *, int, int, BOOL);
void FlatSB_Internal_EndScroll(WSBState *, BOOL);
void FlatSB_Internal_DrawArrow(WSBState *, HDC, CONST RECT *, int, int);
void FlatSB_Internal_DrawElevator(WSBState *, HDC, LPRECT, BOOL);
void FlatSB_Internal_DrawGroove(WSBState *, HDC, LPRECT, BOOL);
void FlatSB_Internal_DrawSize(WSBState *, HDC, int, int);
void FlatSB_Internal_DrawScrollBar(WSBState *, HDC, BOOL, BOOL);
void FlatSB_Internal_DrawThumb(WSBState *, BOOL);
void FlatSB_Internal_DrawThumb2(WSBState *, HDC, BOOL, UINT);
UINT FlatSB_Internal_GetSBFlags(WSBState *, BOOL);
BOOL FlatSB_Internal_EnableScrollBar(WSBState *, int, UINT);
WSBState * FlatSB_Internal_InitPwSB(HWND);
void FlatSB_Internal_RedrawScrollBar(WSBState *, BOOL);
void FlatSB_Internal_SBTrackInit(WSBState *, HWND, LPARAM, int, BOOL);
void FlatSB_Internal_TrackBox(WSBState *, int message, WPARAM, LPARAM);
void FlatSB_Internal_TrackThumb(WSBState *, int message, WPARAM, LPARAM);
BOOL FlatSB_Internal_IsSizeBox(HWND);

LRESULT FlatSB_Internal_SetScrollBar(WSBState *, int, LPSCROLLINFO, BOOL);
LRESULT CALLBACK FlatSB_SubclassWndProc(HWND, UINT, WPARAM, LPARAM, WPARAM, ULONG_PTR);

void FlatSB_Internal_NotifyWinEvent(WSBState *pWState, UINT event, LONG_PTR idChild)
{
    MyNotifyWinEvent(event, pWState->sbHwnd,
                     pWState->fTrackVert ? OBJID_VSCROLL : OBJID_HSCROLL,
                     idChild);
}

#define IsHottrackable(STYLE)   ((STYLE == FSB_FLAT_MODE) || (STYLE == FSB_ENCARTA_MODE))

HRESULT WINAPI UninitializeFlatSB(HWND hwnd)
{
    SCROLLINFO hsi, vsi;
    WSBState * pWState;
    int style, vFlags, hFlags;
    BOOL hValid = FALSE, vValid = FALSE;

    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if (pWState == (WSBState *)NULL)
        return S_FALSE;
    else if (pWState == WSB_UNINIT_HANDLE)   {
        RemoveWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0);
        return S_FALSE;
    }

    if (pWState->fTracking)
        return E_FAIL;          //  Can't do this!

    style = pWState->style;
    vsi.cbSize = hsi.cbSize = sizeof(SCROLLINFO);
    vsi.fMask = hsi.fMask = SIF_ALL | SIF_DISABLENOSCROLL;

    hValid = FlatSB_GetScrollInfo(hwnd, SB_HORZ, &hsi);
    hFlags = FlatSB_Internal_GetSBFlags(pWState, SB_HORZ);
    vValid = FlatSB_GetScrollInfo(hwnd, SB_VERT, &vsi);
    vFlags = FlatSB_Internal_GetSBFlags(pWState, SB_VERT);

    DeleteObject(pWState->hbm_Bkg);
    DeleteObject(pWState->hbr_Bkg);
    LocalFree((HLOCAL)pWState);
    RemoveWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0);

    if (vValid) {
        SetScrollInfo(hwnd, SB_VERT, &vsi, FALSE);
        EnableScrollBar(hwnd, SB_VERT, vFlags);
    }

    if (hValid) {
        SetScrollInfo(hwnd, SB_HORZ, &hsi, FALSE);
        EnableScrollBar(hwnd, SB_HORZ, hFlags);
    }

    SetWindowBits(hwnd, GWL_STYLE, WS_HSCROLL | WS_VSCROLL, style & (WS_HSCROLL | WS_VSCROLL));

    //  Force the WM_NCCALCSIZE/WM_NCPAINT to be sent.
    CCInvalidateFrame(hwnd);
    return S_OK;
}

//
//  For accessibility - We keep the original USER scrollbars around and
//  keep USER's view of the scrollbar in sync with the flat view.  This
//  means keeping the WS_[HV]SCROLL styles on the window, forwarding
//  all scrollbar APIs into USER, etc.  That way, when OLEACC asks USER
//  for the scrollbar state, USER returns values that match the flat_sb
//  values.
//
//  Even though the styles are enabled, the UI isn't affected since we
//  take over all nonclient painting and hit-testing so USER never gets
//  a chance to paint or hit-test the scrollbars that we took over.
//
BOOL WINAPI InitializeFlatSB(HWND hwnd)
{
    int newStyle, style;
    SCROLLINFO hsi, vsi, siTmp;
    WSBState * pWState;
    BOOL hValid = FALSE, vValid = FALSE;

    style = GetWindowLong(hwnd, GWL_STYLE);
    siTmp.cbSize = vsi.cbSize = hsi.cbSize = sizeof(SCROLLINFO);
    vsi.fMask = hsi.fMask = SIF_ALL | SIF_DISABLENOSCROLL;

    if (style & WS_HSCROLL)
        hValid = GetScrollInfo(hwnd, SB_HORZ, &hsi);

    if (style & WS_VSCROLL)
        vValid = GetScrollInfo(hwnd, SB_VERT, &vsi);

    newStyle = style & (WS_VSCROLL | WS_HSCROLL);
    style &= ~(WS_VSCROLL | WS_HSCROLL);

    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if (!vValid && !hValid)   {
        if (NULL == pWState)    {
            if (!SetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR)WSB_UNINIT_HANDLE))
                return FALSE;
        } else  {
            //  It seems to me unreasonable to do nothing while the caller wants
            //  to init again the flat SB we are already using.
        }
        return TRUE;
    }

    if ((NULL == pWState) || (WSB_UNINIT_HANDLE == pWState))    {
        pWState = FlatSB_Internal_InitPwSB(hwnd);
        if ((WSBState *)NULL == pWState)
            return FALSE;

        if (!SetWindowSubclass(hwnd,FlatSB_SubclassWndProc, 0,(ULONG_PTR)pWState)) {
            DeleteObject(pWState->hbm_Bkg);
            DeleteObject(pWState->hbr_Bkg);
            LocalFree((HLOCAL)pWState);
            return FALSE;
        }
    }

    pWState->style = newStyle;
    if (hValid)
        FlatSB_Internal_SetScrollBar(pWState, SB_HORZ, &hsi, FALSE);

    if (vValid)
        FlatSB_Internal_SetScrollBar(pWState, SB_VERT, &vsi, FALSE);

    //  Force the WM_NCCALCSIZE/WM_NCPAINT to be sent.
    CCInvalidateFrame(hwnd);

    return TRUE;
}


LRESULT FlatSB_NCDestroyProc(WSBState * pWState, HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    ASSERT(pWState);

    if (pWState != WSB_UNINIT_HANDLE)   {
        DeleteObject(pWState->hbm_Bkg);
        DeleteObject(pWState->hbr_Bkg);
        LocalFree((HLOCAL)pWState);
    }

    RemoveWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0);
    return DefSubclassProc(hwnd, WM_NCDESTROY, wParam, lParam);
}

LRESULT FlatSB_NCCalcProc(WSBState * pWState, HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    RECT * rc = (RECT *) lParam;
    NCCALCSIZE_PARAMS * pnc = (NCCALCSIZE_PARAMS *)lParam;
    RECT    rcClient, rcWin;
    LRESULT lres;
    DWORD dwStyle;

    //  ZDC:
    //
    //  Note:
    //      It's said that if wParam is true, new rgrc[1|2] are also
    //      computed. Since I didn't see the implementation in the 'user'
    //      code, I leave it unimplemented.


    if ((BOOL)wParam == TRUE)
        CopyRect(&rcWin, &(pnc->rgrc[0]));
    else
        CopyRect(&rcWin, rc);

    dwStyle = SetWindowBits(hwnd, GWL_STYLE, WS_VSCROLL | WS_HSCROLL, 0);

    // Save pnc->rgrc[0] to keep USER happy (see below)
    CopyRect(&rcClient, &pnc->rgrc[0]);

    lres = DefSubclassProc(hwnd, WM_NCCALCSIZE, wParam, lParam);

    SetWindowBits(hwnd, GWL_STYLE, WS_VSCROLL | WS_HSCROLL, dwStyle);

    // USER does funky internal state munging during the WM_NCCALCSIZE
    // and we want USER's internal state to see the scrollbars even though
    // we're drawing them.  So give USER one last look at the original
    // values so he will think the scroll bars are really there.  This
    // sets internal WFVPRESENT and WFHPRESENT flags that OLEACC secretly
    // looks at via the undocumented GetScrollBarInfo().
    DefSubclassProc(hwnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rcClient);

    if ((BOOL)wParam == TRUE)
        CopyRect(&rcClient, &(pnc->rgrc[0]));
    else
        CopyRect(&rcClient, rc);

    pWState->style &= ~(WFVPRESENT | WFHPRESENT);
    if (TestSTYLE(pWState->style, WS_VSCROLL)
        && (rcClient.right - rcClient.left >= pWState->x_VSBArrow)) {
        pWState->style |= WFVPRESENT;
        rcClient.right -= pWState->x_VSBArrow;
    }

    if (TestSTYLE(pWState->style, WS_HSCROLL)
        && (rcClient.bottom - rcClient.top > pWState->y_HSBArrow)) {
        pWState->style |= WFHPRESENT;
        rcClient.bottom -= pWState->y_HSBArrow;
    }

    if ((BOOL)wParam == TRUE)
        CopyRect(&(pnc->rgrc[0]), &rcClient);
    else
        CopyRect(rc, &rcClient);

    pWState->rcClient.top = rcClient.top - rcWin.top;
    pWState->rcClient.bottom = rcClient.bottom - rcWin.top;
    pWState->rcClient.left = rcClient.left - rcWin.left;
    pWState->rcClient.right = rcClient.right - rcWin.left;

    return lres;
}


LRESULT FlatSB_NCPaintProc(WSBState * pWState, HWND hwnd, WPARAM wParam,  LPARAM lParam)
{
    HDC     hdc;
    int     oldLoc, newLoc;
    LRESULT lres;
    DWORD dwStyle;
    RECT rcClient;

    ASSERT(pWState);
    ASSERT(pWState != WSB_UNINIT_HANDLE);

    //
    //  DefWindowProc(WM_NCPAINT) is going to try to draw USER's scrollbars,
    //  and will draw them in the wrong place if our scrollbar width is
    //  different from the system default width.  (Argh.)
    //
    //  So remove the scrollbar styles, do the paint, then put them back.
    //
    dwStyle = SetWindowBits(hwnd, GWL_STYLE, WS_VSCROLL | WS_HSCROLL, 0);

    GetWindowRect(hwnd, &rcClient);
    DefSubclassProc(hwnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rcClient);

    lres = DefSubclassProc(hwnd, WM_NCPAINT, wParam, lParam);

    SetWindowBits(hwnd, GWL_STYLE, WS_VSCROLL | WS_HSCROLL, dwStyle);

    GetWindowRect(hwnd, &rcClient);
    DefSubclassProc(hwnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rcClient);

//  hdc = GetDCEx(hwnd, (HRGN) wParam, DCX_WINDOW |
//                DCX_INTERSECTRGN | DCX_LOCKWINDOWUPDATE);

    //  ZDC:
    //
    //  Note:
    //      For some reason(wParam == 1) the statements above did not give
    //      the result we expected. I am not sure if it's the only case that
    //      GetDCEx will disappoint us.

    hdc = GetWindowDC(hwnd);
    newLoc = WSB_MOUSELOC_OUTSIDE;
    oldLoc = pWState->locMouse;

    if (TestSTYLE(pWState->style, WFHPRESENT)
        && TestSTYLE(pWState->style, WFVPRESENT))   {
        int cxFrame, cyFrame;

        cxFrame = pWState->rcClient.right;
        cyFrame = pWState->rcClient.bottom;
        FlatSB_Internal_DrawSize(pWState, hdc, cxFrame, cyFrame);
    }

    if (TestSTYLE(pWState->style, WFHPRESENT))  {
        FlatSB_Internal_DrawScrollBar(pWState, hdc, FALSE, FALSE);
        if (pWState->fHActive)
            newLoc = pWState->locMouse;
    }

    if (TestSTYLE(pWState->style, WFVPRESENT))  {
        pWState->locMouse = oldLoc;
        FlatSB_Internal_DrawScrollBar(pWState, hdc, TRUE, FALSE);
        if (pWState->fVActive)
            newLoc = pWState->locMouse;
    }
    pWState->locMouse = newLoc;

    ReleaseDC(hwnd, hdc);

    return lres;
}

LRESULT FlatSB_NCHitTestProc(WSBState *pWState, HWND hwnd, WPARAM wParam, LPARAM lParam, BOOL fTrack);

VOID CALLBACK TimerMouseLeave(
    HWND hwnd,  // handle of window for timer messages
    UINT uMsg,  // WM_TIMER message
    UINT_PTR idEvent,  // timer identifier
    DWORD dwTime   // current system time
)
{
    WSBState * pWState;

    if (idEvent != IDWSB_TRACK)
        return;

    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if ((pWState == (WSBState *)NULL) || (pWState == WSB_UNINIT_HANDLE))    {
        KillTimer(hwnd, IDWSB_TRACK);
        return;
    }

    if (pWState->fTracking) {
        return;
    }

    FlatSB_NCHitTestProc(pWState, hwnd, 0, 0, TRUE);
    return;
}

LRESULT FlatSB_NCHitTestProc(WSBState *pWState, HWND hwnd, WPARAM wParam, LPARAM lParam, BOOL fTTrack)
{
    LRESULT lres, lHTCode=HTBOTTOMRIGHT;
    RECT    rcTest, rcWindow;
    POINT   pt;
    BOOL    fVChanged = FALSE, fHChanged = FALSE;
    BOOL    fWinActive = ChildOfActiveWindow(hwnd);
    int     newLoc, oldLoc;

    ASSERT(pWState);
    ASSERT(pWState != WSB_UNINIT_HANDLE);

    GetWindowRect(hwnd, &rcWindow);
    if (fTTrack) {
        lres = HTNOWHERE;
        if (fWinActive)
            GetCursorPos(&pt);
        else    {
            pt.x = rcWindow.left - 1;      //  NOWHERE --- to fool CalcSBtuff2
            pt.y = rcWindow.top - 1;
        }
    } else    {
        lres = DefSubclassProc(hwnd, WM_NCHITTEST, wParam, lParam);
        pt.x = GET_X_LPARAM(lParam);
        pt.y = GET_Y_LPARAM(lParam);
    }

    //
    // If this is a RTL mirrored window, then measure
    // the client coordinates from the visual right edge.
    // [samera]
    //
    if (IS_WINDOW_RTL_MIRRORED(hwnd)) {
        pt.x = rcWindow.right - pt.x;
        lHTCode = HTBOTTOMLEFT;
    } else {
        pt.x -= rcWindow.left;
    }
    pt.y -= rcWindow.top;

    if (fTTrack && fWinActive && (pt.x == pWState->ptMouse.x) && (pt.y == pWState->ptMouse.y))
        return lres /* Meaningless result*/;

    //  We shouldn't get HTVSCROLL / HTHSCROLL for system scrollbar here.
    if (lres != HTNOWHERE)  {
        goto Redraw;
    }

    if (TestSTYLE(pWState->style, WFVPRESENT))  {
        rcTest.left = pWState->rcClient.right;
        rcTest.right = pWState->rcClient.right + pWState->x_VSBArrow;
        rcTest.top = pWState->rcClient.top;
        rcTest.bottom = pWState->rcClient.bottom;

        if (PtInRect(&rcTest, pt))  {
            lres = HTVSCROLL;
            goto Redraw;
        }
    }

    if (TestSTYLE(pWState->style, WFHPRESENT))  {
        rcTest.left = pWState->rcClient.left;
        rcTest.right = pWState->rcClient.right;
        rcTest.top = pWState->rcClient.bottom;
        rcTest.bottom = pWState->rcClient.bottom + pWState->y_HSBArrow;

        if (PtInRect(&rcTest, pt))  {
            lres = HTHSCROLL;
            goto Redraw;
        }
    }

    if (TestSTYLE(pWState->style, WFHPRESENT) && TestSTYLE(pWState->style, WFVPRESENT))
    {
        rcTest.left = pWState->rcClient.right;
        rcTest.right = pWState->rcClient.right + pWState->x_VSBArrow;
        rcTest.top = pWState->rcClient.bottom;
        rcTest.bottom = pWState->rcClient.bottom + pWState->y_HSBArrow;

        if (PtInRect(&rcTest, pt))  {
            if (!FlatSB_Internal_IsSizeBox(hwnd))
                lres = HTSIZE;
            else
                lres = lHTCode;
            goto Redraw;
        }
    }

    lres = HTNOWHERE;

Redraw:
    if(pWState->fTracking)
        return lres;

    if (!fWinActive) {
        fVChanged = pWState->fVActive; pWState->fVActive = FALSE;
        fHChanged = pWState->fHActive; pWState->fHActive = FALSE;
    } else  {
        switch (lres)   {
        case HTVSCROLL:
            fVChanged = TRUE; pWState->fVActive = TRUE;
            fHChanged = pWState->fHActive; pWState->fHActive = FALSE;
            break;
        case HTHSCROLL:
            fVChanged = pWState->fVActive; pWState->fVActive = FALSE;
            fHChanged = TRUE; pWState->fHActive = TRUE;
            break;
        default:
            fVChanged = pWState->fVActive; pWState->fVActive = FALSE;
            fHChanged = pWState->fHActive; pWState->fHActive = FALSE;
            break;
        }
    }

    pWState->ptMouse.x = pt.x;
    pWState->ptMouse.y = pt.y;

    newLoc = WSB_MOUSELOC_OUTSIDE;
    oldLoc = pWState->locMouse;
    if (fVChanged && IsHottrackable(pWState->vStyle))  {

        FlatSB_Internal_RedrawScrollBar(pWState, TRUE);
        if (pWState->fVActive)
            newLoc = pWState->locMouse;
    }

    if (fHChanged && IsHottrackable(pWState->hStyle))  {
        pWState->locMouse = oldLoc;
        FlatSB_Internal_RedrawScrollBar(pWState, FALSE);
        if (pWState->fHActive)
            newLoc = pWState->locMouse;
    }
    pWState->locMouse = newLoc;

    if (pWState->fVActive || pWState->fHActive) {
        if (pWState->hTrackSB == 0)
            pWState->hTrackSB = SetTimer(hwnd, IDWSB_TRACK,
                        GetDoubleClickTime()/2,
                        TimerMouseLeave);
    } else  {
        if (pWState->hTrackSB)  {
            KillTimer(hwnd, IDWSB_TRACK);
            pWState->hTrackSB = 0;
        }
    }

    return lres;
}

LRESULT FlatSB_SysCommandProc(WSBState * pWState, HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    LRESULT lres;
    unsigned uCmdType;
    int     hitArea;

    ASSERT(pWState);
    ASSERT(pWState != WSB_UNINIT_HANDLE);

    uCmdType = (unsigned) wParam & 0xFFF0;        // type of system command requested
    hitArea = (int) wParam & 0x000F;
    if (uCmdType != SC_HSCROLL && uCmdType != SC_VSCROLL)
        return DefSubclassProc(hwnd, WM_SYSCOMMAND, wParam, lParam);
    else
        //  There are some initialization we may need.
#define SC_INVALID 0
        lres = DefSubclassProc(hwnd, WM_SYSCOMMAND, (WPARAM)SC_INVALID, lParam);
#undef  SC_INVALID

    FlatSB_Internal_SBTrackInit(pWState, hwnd, lParam, hitArea, GetKeyState(VK_SHIFT) < 0);
    return 0;
}

LRESULT FlatSB_CancelModeProc(WSBState * pWState, HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    LRESULT lres;

    ASSERT(pWState);
    ASSERT(pWState != WSB_UNINIT_HANDLE);

    lres = DefSubclassProc(hwnd, WM_CANCELMODE, wParam, lParam);

    //  A good citizen of Subclass, we have to wait the DefSubclassProc
    //  release capture first!

    if (pWState->pfnSB)
        FlatSB_Internal_EndScroll(pWState, TRUE);

    return lres;
}

//
//  This updates the system metrics and points pPWState->pwmet at the
//  application metrics or system metrics, depending on whether a
//  screenreader is running.
//
void FlatSB_InitWSBMetrics(WSBState *pWState)
{
    BOOL fScreenRead;

    pWState->metSys.cxHSBThumb = GetSystemMetrics(SM_CXHTHUMB);
    pWState->metSys.cyVSBThumb = GetSystemMetrics(SM_CYVTHUMB);
    pWState->metSys.cxVSBArrow = GetSystemMetrics(SM_CXVSCROLL);
    pWState->metSys.cyVSBArrow = GetSystemMetrics(SM_CYVSCROLL);
    pWState->metSys.cxHSBArrow = GetSystemMetrics(SM_CXHSCROLL);
    pWState->metSys.cyHSBArrow = GetSystemMetrics(SM_CYHSCROLL);

    fScreenRead = FALSE;
    SystemParametersInfo(SPI_GETSCREENREADER, 0, &fScreenRead, 0);

    // If a screen reader is running, then the active metrics are the
    // system metrics; otherwise, it's the app metrics.
    pWState->pmet = fScreenRead ? &pWState->metSys : &pWState->metApp;

}

LRESULT FlatSB_OnSettingChangeProc(WSBState *pWState, HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    ASSERT(pWState);
    ASSERT(pWState != WSB_UNINIT_HANDLE);

    FlatSB_InitWSBMetrics(pWState);

    // These new metrics will most likely have altered our frame, so
    // recompute our frame stuff too
    CCInvalidateFrame(hwnd);

    return DefSubclassProc(hwnd, WM_SETTINGCHANGE, wParam, lParam);
}

LRESULT FlatSB_OnScrollProc(WSBState *pWState, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if (GET_WM_HSCROLL_HWND(wParam, lParam) == NULL && !pWState->fInDoScroll) {
        // Somebody on the outside (probably USER) changed our scroll stuff,
        // so re-sync with the USER values.
        if (GET_WM_HSCROLL_CODE(wParam, lParam) == SB_ENDSCROLL)
            FlatSB_NCPaintProc(pWState, hwnd, (WPARAM)1, 0);
    }
    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK FlatSB_SubclassWndProc
(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam,
    WPARAM uIdSubclass,
    ULONG_PTR dwRefData
)
{
    WSBState * pWState = (WSBState *)dwRefData;

    ASSERT (dwRefData);

    if (pWState == (WSBState *)NULL)
        return DefSubclassProc(hwnd, uMsg, wParam, lParam);
    else if (pWState == WSB_UNINIT_HANDLE && uMsg != WM_NCDESTROY)
        return DefSubclassProc(hwnd, uMsg, wParam, lParam);

    switch (uMsg)
    {
    case WM_NCDESTROY:
        return FlatSB_NCDestroyProc(pWState, hwnd, wParam, lParam);
    case WM_NCCALCSIZE:
        return FlatSB_NCCalcProc(pWState, hwnd, wParam, lParam);
    case WM_NCPAINT:
        return FlatSB_NCPaintProc(pWState, hwnd, wParam, lParam);
    case WM_NCHITTEST:
        return FlatSB_NCHitTestProc(pWState, hwnd, wParam, lParam, FALSE);
    case WM_SYSCOMMAND:
        return FlatSB_SysCommandProc(pWState, hwnd, wParam, lParam);
    case WM_CANCELMODE:
        return FlatSB_CancelModeProc(pWState, hwnd, wParam, lParam);
    case WM_SETTINGCHANGE:
        return FlatSB_OnSettingChangeProc(pWState, hwnd, wParam, lParam);

    case WM_VSCROLL:
    case WM_HSCROLL:
        return FlatSB_OnScrollProc(pWState, hwnd, uMsg, wParam, lParam);
    }
    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}


//=------------------------------------------------------------------
//  Start of drawing functions.
//=-------------------------------------------------------------------


#define WSB_BUTTON_UPARROW      DFCS_SCROLLUP
#define WSB_BUTTON_DOWNARROW    DFCS_SCROLLDOWN
#define WSB_BUTTON_LEFTARROW    DFCS_SCROLLLEFT
#define WSB_BUTTON_RIGHTARROW   DFCS_SCROLLRIGHT

#define WSB_RESTING_MODE        0
#define WSB_HOTTRACKED_MODE     1
#define WSB_MOUSEDOWN_MODE      2
#define WSB_DISABLED_MODE       3

void FlatSB_Internal_DrawBox(HDC hdc, CONST RECT * prct, int mode)
{
    HBRUSH hbrOld, hbrEdge, hbrFace;
    int w, h, l, t;

    if (prct->left > prct->right)
        return;
    else if (prct->top > prct->bottom)
        return;

    l = prct->left;
    t = prct->top;
    w = prct->right - prct->left;
    h = prct->bottom - prct->top;

    switch (mode)   {
    case WSB_HOTTRACKED_MODE:
        hbrEdge = GetSysColorBrush(COLOR_3DSHADOW);
        hbrFace = hbrEdge;
        break;
    case WSB_MOUSEDOWN_MODE:
        hbrEdge = GetSysColorBrush(COLOR_3DSHADOW);
        hbrFace = (HBRUSH)GetStockObject(BLACK_BRUSH);
        break;
    case WSB_DISABLED_MODE:
        hbrEdge = GetSysColorBrush(COLOR_3DHILIGHT);
        hbrFace = GetSysColorBrush(COLOR_3DFACE);
        break;
    case WSB_RESTING_MODE:
    default:
        hbrEdge = GetSysColorBrush(COLOR_3DSHADOW);
        hbrFace = GetSysColorBrush(COLOR_3DFACE);
        break;
    }
    hbrOld = SelectObject(hdc, hbrEdge);
    PatBlt(hdc, l, t, w, 1, PATCOPY);
    PatBlt(hdc, l, t, 1, h, PATCOPY);
    PatBlt(hdc, l, t + h - 1, w, 1, PATCOPY);
    PatBlt(hdc, l + w - 1, t, 1, h, PATCOPY);

    SelectObject(hdc, hbrFace);
    PatBlt(hdc, l + 1, t + 1, w - 2, h - 2, PATCOPY);
    SelectObject(hdc, hbrOld);
}

void FlatSB_Internal_DrawEncartaBox(HDC hdc, CONST RECT * prct, int mode)
{
    HBRUSH hbrOld, hbrLite, hbrDark, hbrFace;

    int w, h, l, t;

    if (prct->left > prct->right)
        return;
    else if (prct->top > prct->bottom)
        return;

    l = prct->left;
    t = prct->top;
    w = prct->right - prct->left;
    h = prct->bottom - prct->top;

    switch (mode)   {
    case WSB_HOTTRACKED_MODE:
        hbrLite = GetSysColorBrush(COLOR_3DHILIGHT);
        hbrDark = GetSysColorBrush(COLOR_3DSHADOW);
        break;
    case WSB_MOUSEDOWN_MODE:
        hbrDark = GetSysColorBrush(COLOR_3DHILIGHT);
        hbrLite = GetSysColorBrush(COLOR_3DSHADOW);
        break;
    case WSB_DISABLED_MODE:
        hbrDark = hbrLite = GetSysColorBrush(COLOR_3DHILIGHT);
        break;
    case WSB_RESTING_MODE:
    default:
        hbrDark = hbrLite = GetSysColorBrush(COLOR_3DSHADOW);
        break;
    }

    hbrFace = GetSysColorBrush(COLOR_3DFACE);

    hbrOld = SelectObject(hdc, hbrLite);
    PatBlt(hdc, l, t, w, 1, PATCOPY);
    PatBlt(hdc, l, t, 1, h, PATCOPY);

    SelectObject(hdc, hbrDark);
    PatBlt(hdc, l, t + h - 1, w, 1, PATCOPY);
    PatBlt(hdc, l + w - 1, t, 1, h, PATCOPY);

    SelectObject(hdc, hbrFace);
    PatBlt(hdc, l + 1, t + 1, w - 2, h - 2, PATCOPY);

    SelectObject(hdc, hbrOld);
}

void FlatSB_Internal_DrawArrow(WSBState * pWState, HDC hdc, CONST RECT * rcArrow, int buttonIndex, int extraModeBits)
{
    COLORREF rgb;
    LPCTSTR strIndex;
    HFONT   hFont, hOldFont;
    int     x, y, cx, cy, iOldBk, c;
    BOOL    fDisabled = extraModeBits & DFCS_INACTIVE;
    BOOL    fMouseDown = extraModeBits & DFCS_PUSHED;
    BOOL    fHotTracked;
    int     mode, style;

    if (rcArrow->left >= rcArrow->right)
        return;
    else if (rcArrow->top >= rcArrow->bottom)
        return;

    if (buttonIndex == WSB_BUTTON_LEFTARROW || buttonIndex == WSB_BUTTON_RIGHTARROW)
        style = pWState->hStyle;
    else
        style = pWState->vStyle;

    switch (buttonIndex)    {
    case WSB_BUTTON_LEFTARROW:
        fHotTracked = (pWState->locMouse == WSB_MOUSELOC_ARROWLF);
        strIndex = TEXT("3");
        break;
    case WSB_BUTTON_RIGHTARROW:
        fHotTracked = (pWState->locMouse == WSB_MOUSELOC_ARROWRG);
        strIndex = TEXT("4");
        break;
    case WSB_BUTTON_UPARROW:
        fHotTracked = (pWState->locMouse == WSB_MOUSELOC_ARROWUP);
        strIndex = TEXT("5");
        break;
    case WSB_BUTTON_DOWNARROW:
        fHotTracked = (pWState->locMouse == WSB_MOUSELOC_ARROWDN);
        strIndex = TEXT("6");
        break;
    default:
        return;
    }

    if (!fDisabled && fHotTracked && pWState->fHitOld)
        fMouseDown = TRUE;

    if (style == FSB_REGULAR_MODE) {
        RECT rc;

        CopyRect(&rc, rcArrow);
        if (fDisabled)
            DrawFrameControl(hdc, &rc, DFC_SCROLL, buttonIndex | DFCS_INACTIVE);
        else if (fMouseDown)
            DrawFrameControl(hdc, &rc, DFC_SCROLL, buttonIndex | DFCS_FLAT);
        else
            DrawFrameControl(hdc, &rc, DFC_SCROLL, buttonIndex);
        return;
    }

    if (fDisabled)
        mode = WSB_DISABLED_MODE;
    else if (fMouseDown)
        mode = WSB_MOUSEDOWN_MODE;
    else if (fHotTracked)
        mode = WSB_HOTTRACKED_MODE;
    else
        mode = WSB_RESTING_MODE;

    if (style == FSB_ENCARTA_MODE)  {
        FlatSB_Internal_DrawEncartaBox(hdc, rcArrow, mode);
    } else  {
        FlatSB_Internal_DrawBox(hdc, rcArrow, mode);
    }

    cx = rcArrow->right - rcArrow->left;
    cy = rcArrow->bottom - rcArrow->top;
    c = min(cx, cy);

    if (c < 4)      // Couldn't fill in a char after drawing the edges.
        return;

    x = rcArrow->left + ((cx - c) / 2) + 2;
    y = rcArrow->top + ((cy - c) / 2) + 2;

    c -= 4;

    if (style == FSB_FLAT_MODE) {
        switch (mode)   {
        case WSB_RESTING_MODE:
            rgb = RGB(0, 0, 0);
            break;
        case WSB_HOTTRACKED_MODE:
        case WSB_MOUSEDOWN_MODE:
            rgb = RGB(255, 255, 255);
            break;
        case WSB_DISABLED_MODE:
            rgb = GetSysColor(COLOR_3DSHADOW);
            break;
        default:
            rgb = RGB(0, 0, 0);
            break;
        }
    } else  {   //  FSB_ENCARTA_MODE
        switch (mode)   {
        case WSB_DISABLED_MODE:
            rgb = GetSysColor(COLOR_3DSHADOW);
            break;
        case WSB_RESTING_MODE:
        case WSB_HOTTRACKED_MODE:
        case WSB_MOUSEDOWN_MODE:
        default:
            rgb = RGB(0, 0, 0);
            break;
        }
    }

    hFont = CreateFont(c, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0, WSB_SYS_FONT);
    iOldBk = SetBkMode(hdc, TRANSPARENT);
    hOldFont = SelectObject(hdc, hFont);

    rgb = SetTextColor(hdc, rgb);
    TextOut(hdc, x, y, strIndex, 1);

    SetBkMode(hdc, iOldBk);
    SelectObject(hdc, hOldFont);
    DeleteObject(hFont);

    return;
}

void FlatSB_Internal_DrawElevator(WSBState * pWState, HDC hdc, LPRECT lprc, BOOL fVert)
{
    BOOL    fHit;
    int     mode;
    int     style;

    fHit = (fVert)?(pWState->locMouse == WSB_MOUSELOC_V_THUMB)
                  :(pWState->locMouse == WSB_MOUSELOC_H_THUMB);

    style = (fVert)?pWState->vStyle:pWState->hStyle;
    switch (style)  {
    case FSB_FLAT_MODE:
    case FSB_ENCARTA_MODE:
        if ((pWState->cmdSB == SB_THUMBPOSITION) && (fVert == pWState->fTrackVert))
            mode = WSB_HOTTRACKED_MODE;
        else
            mode = (fHit)?WSB_HOTTRACKED_MODE:WSB_RESTING_MODE;

        if (style == FSB_FLAT_MODE)
            FlatSB_Internal_DrawBox(hdc, lprc, mode);
        else
            FlatSB_Internal_DrawEncartaBox(hdc, lprc, mode);
        break;
    case FSB_REGULAR_MODE:
    default:
        {
            RECT rc;

            CopyRect(&rc, lprc);
            DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONPUSH);
        }
        break;
    }
}

//=-------------------------------------------------------------
//  FlatSB_Internal_DrawSize
//      Draw the size grip if needed.
//=-------------------------------------------------------------

void FlatSB_Internal_DrawSize(WSBState * pWState, HDC hdc, int x, int y)
{
    HBRUSH  hbrSave, hbr3DFACE;
    RECT    rcWindow;
    HWND    hwnd = pWState->sbHwnd;
    int     style;

    style = GetWindowLong(hwnd, GWL_STYLE);
    if (!FlatSB_Internal_IsSizeBox(hwnd))
    {
        hbr3DFACE = GetSysColorBrush(COLOR_3DFACE);
        hbrSave = SelectObject(hdc, hbr3DFACE);
        PatBlt(hdc, x, y, pWState->x_VSBArrow, pWState->y_HSBArrow, PATCOPY);
        SelectBrush(hdc, hbrSave);
    }
    else
    {
        rcWindow.left = x;
        rcWindow.right = x + pWState->x_VSBArrow;
        rcWindow.top = y;
        rcWindow.bottom = y + pWState->y_HSBArrow;
        DrawFrameControl(hdc, &rcWindow, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
    }
}

//=-------------------------------------------------------------
//  FlatSB_Internal_DrawGroove
//      Draw lines & middle of the thumb groove
//=-------------------------------------------------------------

void FlatSB_Internal_DrawGroove(WSBState * pWState, HDC hdc, LPRECT prct, BOOL fVert)
{
    HBRUSH   hbrLight;
    COLORREF cBkg, cFg;
    HPALETTE oldPal = 0;

    if (fVert)  {
        hbrLight = pWState->hbr_VSBBkg;
        cBkg = pWState->col_VSBBkg;
    } else  {
        hbrLight = pWState->hbr_HSBBkg;
        cBkg = pWState->col_HSBBkg;
    }

    if (hbrLight == (HBRUSH)NULL)   {
        hbrLight = GetSysColorBrush(COLOR_3DLIGHT);
        FillRect(hdc, prct, hbrLight);
        return;
    }

    if (pWState->hPalette != (HPALETTE)NULL)    {
        oldPal = SelectPalette(hdc, pWState->hPalette, TRUE);
        RealizePalette(hdc);
    }

    cFg = SetTextColor(hdc, GetSysColor(COLOR_3DFACE));
    cBkg = SetBkColor(hdc, cBkg);
    FillRect(hdc, prct, hbrLight);
    if (oldPal != (HPALETTE)NULL)
        SelectPalette(hdc, oldPal, TRUE);

    SetTextColor(hdc, cFg);
    SetBkColor(hdc, cBkg);
}


//=-------------------------------------------------------------------
//  Following functions are ported from winsbctl.c in user code.
//=-------------------------------------------------------------------


//=-------------------------------------------------------------------------
//  SBPosFromPx() -
//=-------------------------------------------------------------------------

int FlatSB_Internal_SBPosFromPx(WSBState * pWState, int px)
{
    int * pw;

    if (pWState->fTrackVert)
        pw = &(pWState->sbVMinPos);
    else
        pw = &(pWState->sbHMinPos);

    if (px < pWState->pxUpArrow)
        return pw[SBO_MIN];

    if (px >= pWState->pxDownArrow)
            return (pw[SBO_MAX] - (pw[SBO_PAGE]?pw[SBO_PAGE] - 1 : 0));

    return (pw[SBO_MIN] + DMultDiv(pw[SBO_MAX] - pw[SBO_MIN] - (pw[SBO_PAGE]?pw[SBO_PAGE] - 1 : 0),
                                   px - pWState->pxUpArrow,
                                   pWState->cpxSpace)
           );
}

//=-------------------------------------------------------------------------
//  InvertScrollHilite()
//=-------------------------------------------------------------------------

void FlatSB_Internal_InvertScrollHilite(WSBState * pWState)
{
    HWND hwnd = pWState->sbHwnd;
    HDC hdc;

    // Don't invert if the thumb is all the way at the top or bottom
    // or you will end up inverting the line between the arrow and the thumb.
    if (!IsRectEmpty(&(pWState->rcTrack)))
    {
        hdc = GetWindowDC(hwnd);
        InvertRect(hdc, &(pWState->rcTrack));
        ReleaseDC(hwnd, hdc);
    }
}

//=-------------------------------------------------------------------------
//  FlatSB_Internal_MoveThumb()
//=-------------------------------------------------------------------------

void FlatSB_Internal_MoveThumb(WSBState * pWState, int px)
{
    HWND    hwnd = pWState->sbHwnd;
    HDC     hdc;

    if (px == pWState->pxOld)
        return;

pxReCalc:

    pWState->posNew = FlatSB_Internal_SBPosFromPx(pWState, px);

    /* Tentative position changed -- notify the guy. */
    if (pWState->posNew != pWState->posOld) {
        FlatSB_Internal_DoScroll(pWState, SB_THUMBTRACK, pWState->posNew, pWState->fTrackVert);
        if (!pWState->fTracking)
            return;

        pWState->posOld = pWState->posNew;

        //
        // Anything can happen after the SendMessage above in DoScroll!
        // Make sure that the SBINFO structure contains data for the
        // window being tracked -- if not, recalculate data in SBINFO
        // If fVertSB is TRUE, the last CalcSBStuff call is for SB_VERT.
        // If fTrackVert != fVertSB, we got garbage in pWState.
        //

        if (pWState->fTrackVert != pWState->fVertSB)
            FlatSB_Internal_CalcSBStuff(pWState, pWState->fTrackVert);

        // when we yield, our range can get messed with
        // so make sure we handle this

        if (px >= pWState->pxDownArrow - pWState->cpxThumb) {
            px = pWState->pxDownArrow - pWState->cpxThumb;
            goto pxReCalc;
        }
    }

    hdc = GetWindowDC(hwnd);

    pWState->pxThumbTop = px;
    pWState->pxThumbBottom = pWState->pxThumbTop + pWState->cpxThumb;

    //  At this point, the disable flags are always going to be 0 --
    //  we're in the middle of tracking.

    //  We are Okay in this case, since in DrawElevator we decide the mode by
    //  cmd == SB_THUMBPOSITION.
    FlatSB_Internal_DrawThumb2(pWState, hdc, pWState->fTrackVert, 0);
    ReleaseDC(hwnd, hdc);

    pWState->pxOld = px;
}

//=-------------------------------------------------------------------------
//  DrawInvertScrollArea() -
//=-------------------------------------------------------------------------

void FlatSB_Internal_DrawInvertScrollArea(WSBState * pWState, BOOL fHit, int cmd)
{
    HWND hwnd = pWState->sbHwnd;
    HDC  hdc;

    if ((cmd != SB_LINEUP) && (cmd != SB_LINEDOWN))
    {
        FlatSB_Internal_InvertScrollHilite(pWState);
        FlatSB_Internal_NotifyWinEvent(pWState, EVENT_OBJECT_STATECHANGE,
                         cmd == SB_PAGEUP ? INDEX_SCROLLBAR_UPPAGE
                                          : INDEX_SCROLLBAR_DOWNPAGE);
        return;
    }

    hdc = GetWindowDC(hwnd);
    if (cmd == SB_LINEUP) {
        if (pWState->fTrackVert)   {
            FlatSB_Internal_DrawArrow(pWState, hdc, &(pWState->rcTrack), DFCS_SCROLLUP, (fHit) ? DFCS_PUSHED : 0);
        } else  {
            FlatSB_Internal_DrawArrow(pWState, hdc, &(pWState->rcTrack), DFCS_SCROLLLEFT, (fHit) ? DFCS_PUSHED : 0);
        }
    } else {
        if (pWState->fTrackVert)   {
            FlatSB_Internal_DrawArrow(pWState, hdc, &(pWState->rcTrack), DFCS_SCROLLDOWN, (fHit) ? DFCS_PUSHED : 0);
        } else  {
            FlatSB_Internal_DrawArrow(pWState, hdc, &(pWState->rcTrack), DFCS_SCROLLRIGHT, (fHit) ? DFCS_PUSHED : 0);
        }
    }

    FlatSB_Internal_NotifyWinEvent(pWState, EVENT_OBJECT_STATECHANGE,
                     cmd == SB_LINEUP ? INDEX_SCROLLBAR_UP : INDEX_SCROLLBAR_DOWN);

    ReleaseDC(hwnd, hdc);

}

//=-------------------------------------------------------------------------
//  FlatSB_Internal_EndScroll() -
//=-------------------------------------------------------------------------

void FlatSB_Internal_EndScroll(WSBState * pWState, BOOL fCancel)
{
    HWND hwnd = pWState->sbHwnd;
    BOOL fVert = pWState->fTrackVert;
    int oldcmd;

    if (pWState->fTracking)
    {
        oldcmd = pWState->cmdSB;
        pWState->cmdSB = 0;

        //  will not have capture if called by CancelModeProc
        if (GetCapture() == hwnd)
            ReleaseCapture();

        if (pWState->pfnSB == FlatSB_Internal_TrackThumb)
        {
            if (fCancel)    {
                pWState->posOld = pWState->posStart;
            }

            FlatSB_Internal_DoScroll(pWState, SB_THUMBPOSITION, pWState->posOld, fVert);
            FlatSB_Internal_DrawThumb(pWState, fVert);
        }
        else if (pWState->pfnSB == FlatSB_Internal_TrackBox)
        {
            DWORD   lpt;
            RECT    rcWindow;
            POINT   pt;

            if (pWState->hTimerSB)
                KillTimer(hwnd, IDSYS_SCROLL);

            lpt = GetMessagePos();

            ASSERT(hwnd != GetDesktopWindow());

            GetWindowRect(hwnd, &rcWindow);
            pt.x = GET_X_LPARAM(lpt) - rcWindow.left;
            pt.y = GET_Y_LPARAM(lpt) - rcWindow.top;

            if (PtInRect(&(pWState->rcTrack), pt))  {
                pWState->fHitOld = FALSE;
                FlatSB_Internal_DrawInvertScrollArea(pWState, FALSE, oldcmd);
            }
        }

        //  Always send SB_ENDSCROLL message.
        pWState->pfnSB = NULL;

        //  Anything can happen here. Client can call GetScrollInfo for THUMBPOSITION, and we
        //  should return 0, so we should set pfnSB to NULL first.
        FlatSB_Internal_DoScroll(pWState, SB_ENDSCROLL, 0, fVert);
        pWState->fTracking = FALSE;
        pWState->fHitOld = FALSE;

        FlatSB_Internal_NotifyWinEvent(pWState, EVENT_SYSTEM_SCROLLINGEND,
                                       INDEXID_CONTAINER);
        //  Redraw the components.
        FlatSB_NCHitTestProc(pWState, hwnd, 0, 0, TRUE);
    }
}

//=-------------------------------------------------------------------------
//  FlatSB_Internal_DoScroll() -
//=-------------------------------------------------------------------------

void FlatSB_Internal_DoScroll(WSBState *pWState, int cmd, int pos, BOOL fVert)
{
    if (pWState->sbHwnd)
    {
        pWState->fInDoScroll++;
        SendMessage(pWState->sbHwnd, (fVert ? WM_VSCROLL : WM_HSCROLL), (WPARAM)(LOWORD(pos) << 16 | (cmd & 0xffff)), (LPARAM)NULL);
        pWState->fInDoScroll--;
    }
}


//=-------------------------------------------------------------------------
//  TimerScroll()
//=--------------------------------------------------------------------------

VOID CALLBACK TimerScroll(HWND hwnd, UINT message, UINT_PTR id, DWORD time)
{
    LONG    pos;
    POINT   pt;
    UINT    dblClkTime, dtScroll;
    WSBState * pWState;
    RECT    rcWindow;


    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if ((pWState == (WSBState *)NULL) || (pWState == WSB_UNINIT_HANDLE))    {
        KillTimer(hwnd, IDSYS_SCROLL);
        return;
    }

    ASSERT(hwnd != GetDesktopWindow());

    pos = GetMessagePos();
    pt.x = GET_X_LPARAM(pos), pt.y = GET_Y_LPARAM(pos);
    dblClkTime = GetDoubleClickTime();
    dtScroll = (dblClkTime * 4) / 5;
    GetWindowRect(hwnd, &rcWindow);

    pt.x -= rcWindow.left;
    pt.y -= rcWindow.top;

    pos = LOWORD(pt.y) << 16 | LOWORD(pt.x);
    FlatSB_Internal_TrackBox(pWState, WM_NULL, 0, (LPARAM) pos);

    if (pWState->fHitOld)
    {
        pWState->hTimerSB = SetTimer(hwnd, IDSYS_SCROLL, dtScroll / 8, TimerScroll);
        FlatSB_Internal_DoScroll(pWState, pWState->cmdSB, 0, pWState->fTrackVert);
    }
    return;
}

//=-------------------------------------------------------------------------
//  FlatSB_Internal_TrackBox() -
//=-------------------------------------------------------------------------

void FlatSB_Internal_TrackBox(WSBState * pWState, int message, WPARAM wParam, LPARAM lParam)
{
    HWND hwnd = pWState->sbHwnd;
    BOOL fHit, fVert = pWState->fTrackVert;
    BOOL fHitOld = pWState->fHitOld;
    POINT pt;
    int cmsTimer;
    UINT dblClkTime, dtScroll;

    if (message && (message < WM_MOUSEFIRST || message > WM_MOUSELAST))
        return;

    dblClkTime = GetDoubleClickTime();
    dtScroll = (dblClkTime * 4) / 5;

    pt.x = GET_X_LPARAM(lParam);
    pt.y = GET_Y_LPARAM(lParam);

    fHit = PtInRect(&(pWState->rcTrack), pt);

    if (fHit != fHitOld)   {
        pWState->fHitOld = fHit;
        FlatSB_Internal_DrawInvertScrollArea(pWState, fHit, pWState->cmdSB);
    }

    cmsTimer = dtScroll / 8;

    switch (message)
    {
        case WM_LBUTTONUP:
            FlatSB_Internal_EndScroll(pWState, FALSE);
            break;

        case WM_LBUTTONDOWN:
            pWState->hTimerSB = 0;
            cmsTimer = dtScroll;

            /*** FALL THRU ***/

        case WM_MOUSEMOVE:
            if (fHit && (fHit != fHitOld))
            {
                /* We moved back into the normal rectangle: reset timer */
                pWState->hTimerSB = SetTimer(hwnd, IDSYS_SCROLL, cmsTimer, TimerScroll);
                FlatSB_Internal_DoScroll(pWState, pWState->cmdSB, 0, fVert);
            }
    }
}

//=-------------------------------------------------------------------------
//  FlatSB_Internal_TrackThumb() -
//=-------------------------------------------------------------------------

void FlatSB_Internal_TrackThumb(WSBState * pWState, int message, WPARAM wParam, LPARAM lParam)
{
    HWND    hwnd = pWState->sbHwnd;
    BOOL    fVert = pWState->fTrackVert;
    POINT   pt;

    if (message < WM_MOUSEFIRST || message > WM_MOUSELAST)
        return;

    // Make sure that the SBINFO structure contains data for the
    // window being tracked -- if not, recalculate data in SBINFO
    if (pWState->fTrackVert != pWState->fVertSB)
        FlatSB_Internal_CalcSBStuff(pWState, pWState->fTrackVert);

    pt.x = GET_X_LPARAM(lParam);
    pt.y = GET_Y_LPARAM(lParam);
     if (!PtInRect(&(pWState->rcTrack), pt))
        pWState->px = pWState->pxStart;
    else
    {
        pWState->px = (fVert ? GET_Y_LPARAM(lParam) : GET_X_LPARAM(lParam)) + pWState->dpxThumb;
        if (pWState->px < pWState->pxUpArrow)
            pWState->px = pWState->pxUpArrow;
        else if (pWState->px >= (pWState->pxDownArrow - pWState->cpxThumb))
            pWState->px = pWState->pxDownArrow - pWState->cpxThumb;
    }

    FlatSB_Internal_MoveThumb(pWState, pWState->px);

    if (message == WM_LBUTTONUP)
        FlatSB_Internal_EndScroll(pWState, FALSE);
}

//=-------------------------------------------------------------------------
//  FlatSB_Internal_SBTrackLoop() -
//=-------------------------------------------------------------------------

void FlatSB_Internal_SBTrackLoop(WSBState * pWState, LPARAM lParam)
{
    HWND    hwnd = pWState->sbHwnd;
    MSG     msg;
    int     cmd, newlParam;
    POINT   pt;

    if (!pWState->fTracking)
        return;

    FlatSB_Internal_NotifyWinEvent(pWState, EVENT_SYSTEM_SCROLLINGSTART,
                                   INDEXID_CONTAINER);

    (*(pWState->pfnSB))(pWState, WM_LBUTTONDOWN, 0, lParam);

    while (GetCapture() == hwnd)
    {
        if (!GetMessage(&msg, NULL, 0, 0))
            break;

        if (!CallMsgFilter(&msg, MSGF_SCROLLBAR)) {
            cmd = msg.message;

            if (msg.hwnd == hwnd &&
                ((cmd >= WM_MOUSEFIRST && cmd <= WM_MOUSELAST) ||
                (cmd >= WM_KEYFIRST && cmd <= WM_KEYLAST  )    ))
            {
            // Process Key
#define ALT_PRESSED 0x20000000L
                if (cmd >= WM_SYSKEYDOWN
                    && cmd <= WM_SYSDEADCHAR
                    && msg.lParam & ALT_PRESSED)
                    cmd -= (WM_SYSKEYDOWN - WM_KEYDOWN);
#undef ALT_PRESSED
                if (!pWState->fTracking)
                    return;

                // Change to coordinates according to left-top corner of the window.
                pt.x = GET_X_LPARAM(msg.lParam) + pWState->rcClient.left;
                pt.y = GET_Y_LPARAM(msg.lParam) + pWState->rcClient.top;

                newlParam = LOWORD(pt.y) << 16 | LOWORD(pt.x);

                (*(pWState->pfnSB))(pWState, cmd, msg.wParam, (LPARAM)newlParam);
            } else {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    }
}

//=-------------------------------------------------------------------------
//  FlatSB_Internal_SBTrackInit() -
//=-------------------------------------------------------------------------

void FlatSB_Internal_SBTrackInit(WSBState * pWState, HWND hwnd, LPARAM lParam, int hitArea, BOOL fDirect)
{
    int     hitX = GET_X_LPARAM(lParam);
    int     hitY = GET_Y_LPARAM(lParam);
    int     px;
    int    *pwX;
    int    *pwY;
    int     wDisable;   // Scroll bar disable flags;
    RECT    rcWindow;
    BOOL    fVert;
    POINT   pt;

    // hitArea = 0 indicates a scroll bar control
    // otherwise, curArea will have the hit test area

    if (hitArea == HTHSCROLL)
        fVert = FALSE;
    else if (hitArea == HTVSCROLL)
        fVert = TRUE;
    else
        return;

    ASSERT(hwnd != GetDesktopWindow());

    GetWindowRect(hwnd, &rcWindow);
    pt.x = GET_X_LPARAM(lParam) - rcWindow.left;
    pt.y = GET_Y_LPARAM(lParam) - rcWindow.top;
    lParam = LOWORD(pt.y) << 16 | LOWORD(pt.x);

    wDisable = FlatSB_Internal_GetSBFlags(pWState, fVert);

    if ((wDisable & ESB_DISABLE_BOTH) == ESB_DISABLE_BOTH)  {
        // Whole Scroll Bar is disabled -- do not respond
        pWState->pfnSB = NULL;
        pWState->fTracking = FALSE;
        return;
    }

    pWState->hTimerSB = 0;
    pWState->fHitOld = FALSE;
    pWState->fTracking = FALSE;

    //  For the case we click on scroll bar of a nonactive window. The mode is set to FLAT
    //  by HitTestProc. This will work because we set the tracking flag right away.
    if (fVert)  {
        pWState->fVActive = TRUE;   pWState->fHActive = FALSE;
    } else  {
        pWState->fHActive = TRUE;   pWState->fVActive = FALSE;
    }

    //  This will give us the right locMouse. We will keep it till EndScroll.
    FlatSB_Internal_CalcSBStuff(pWState, fVert);

    //  From now till EndScroll, CalcSBStuff won't compute new locMouse.
    pWState->pfnSB = FlatSB_Internal_TrackBox;
    pWState->fTracking = TRUE;

    // Initialize rcSB to the Rectangle of the Entire Scroll Bar
    pwX = (int *)&(pWState->rcSB);
    pwY = pwX + 1;

    if (!fVert)
        pwX = pwY--;

    pwX[0] = pWState->pxLeft;
    pwY[0] = pWState->pxTop;
    pwX[2] = pWState->pxRight;
    pwY[2] = pWState->pxBottom;

    px = (fVert ? pt.y : pt.x);

    pWState->px = px;
    if (px < pWState->pxUpArrow)
    {   // The click occurred on Left/Up arrow
        if(wDisable & LTUPFLAG)
        {   // Disabled -- do not respond
            pWState->pfnSB = NULL;
            pWState->fTracking = FALSE;
            return;
        }

        // LINEUP -- make rcSB the Up Arrow's Rectangle
        pWState->cmdSB = SB_LINEUP;
        pwY[2] = pWState->pxUpArrow;
    }
    else if (px >= pWState->pxDownArrow)
    {   // The click occurred on Right/Down arrow
        if(wDisable & RTDNFLAG)
        {   // Disabled -- do not respond
            pWState->pfnSB = NULL;
            pWState->fTracking = FALSE;
            return;
        }

        // LINEDOWN -- make rcSB the Down Arrow's Rectangle
        pWState->cmdSB = SB_LINEDOWN;
        pwY[0] = pWState->pxDownArrow;
    }
    else if (px < pWState->pxThumbTop)
    {
        // PAGEUP -- make rcSB the rectangle between Up Arrow and Thumb
        pWState->cmdSB = SB_PAGEUP;

        pwY[0] = pWState->pxUpArrow;
        pwY[2] = pWState->pxThumbTop;
    }
    else if (px < pWState->pxThumbBottom)
    {
DoThumbPos:
        if (pWState->pxDownArrow - pWState->pxUpArrow <= pWState->cpxThumb) {
            // Not enough room -- elevator isn't there
            pWState->pfnSB = NULL;
            pWState->fTracking = FALSE;
            return;
        }
        // THUMBPOSITION -- we're tracking with the thumb
        pWState->cmdSB = SB_THUMBPOSITION;
        pWState->fTrackVert = fVert;
        CopyRect(&(pWState->rcTrack), &(pWState->rcSB));

        if (pWState->sbGutter < 0) {
            // Negative gutter means "infinite size"
            pWState->rcTrack.top = MINLONG;
            pWState->rcTrack.left = MINLONG;
            pWState->rcTrack.right = MAXLONG;
            pWState->rcTrack.bottom = MAXLONG;
        } else
        if (fVert)
            InflateRect(&(pWState->rcTrack),
                        (pWState->rcTrack.right - pWState->rcTrack.left) * pWState->sbGutter,
                        pWState->y_VSBThumb * pWState->sbGutter);
        else
            InflateRect(&(pWState->rcTrack),
                        pWState->x_HSBThumb * pWState->sbGutter,
                        (pWState->rcTrack.bottom - pWState->rcTrack.top) * pWState->sbGutter);

        pWState->pfnSB = FlatSB_Internal_TrackThumb;
        pWState->pxOld  = pWState->pxStart  = pWState->pxThumbTop;
        pWState->posOld = pWState->posNew = pWState->posStart = fVert?pWState->sbVThumbPos:pWState->sbHThumbPos;
        pWState->dpxThumb = pWState->pxThumbTop - pWState->px;

        SetCapture(hwnd);
        FlatSB_Internal_DoScroll(pWState, SB_THUMBTRACK, pWState->posOld, fVert);
        FlatSB_Internal_DrawThumb(pWState, fVert);
    }
    else if (px < pWState->pxDownArrow)
    {
        // PAGEDOWN -- make rcSB the rectangle between Thumb and Down Arrow
        pWState->cmdSB = SB_PAGEDOWN;

        pwY[0] = pWState->pxThumbBottom;
        pwY[2] = pWState->pxDownArrow;
    }

    // NT5-style tracking:  Shift+Click = "Go here"
    if (g_bRunOnNT5 && fDirect && pWState->cmdSB != SB_LINEUP && pWState->cmdSB != SB_LINEDOWN) {
        if (pWState->cmdSB != SB_THUMBPOSITION) {
            goto DoThumbPos;
        }
        pWState->dpxThumb = -(pWState->cpxThumb / 2);
    }

    if (pWState->cmdSB != SB_THUMBPOSITION) {
        pWState->fTrackVert = fVert;
        SetCapture(hwnd);
        CopyRect(&(pWState->rcTrack), &(pWState->rcSB));
    }

    FlatSB_Internal_SBTrackLoop(pWState, lParam);
}

//=-------------------------------------------------------------------------
//  GetScroll...() -
//=-------------------------------------------------------------------------

int WINAPI FlatSB_GetScrollPos(HWND hwnd, int code)
{
    WSBState * pWState;

    ASSERT (code != SB_CTL);

    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if (pWState == (WSBState *)NULL)    {
        return GetScrollPos(hwnd, code);
    } else if (pWState == WSB_UNINIT_HANDLE) {
        return 0;
    } else if (pWState->sbHwnd != hwnd) {
        return 0;
    } else  {
        return ((code == SB_VERT)?pWState->sbVThumbPos:pWState->sbHThumbPos);
    }
}

BOOL WINAPI FlatSB_GetScrollPropPtr(HWND hwnd, int propIndex, PINT_PTR pValue)
{
    WSBState * pWState;

    if (!pValue)
        return FALSE;
    else
        *pValue = 0;    //  If we can't set it, we reset it.

    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if (pWState == (WSBState *)NULL)    {
        return FALSE;
    } else if (pWState == WSB_UNINIT_HANDLE) {
        pWState = FlatSB_Internal_InitPwSB(hwnd);
        if (pWState == (WSBState *)NULL)
            return FALSE;
        else if (!SetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0,  (ULONG_PTR)pWState)) {
            DeleteObject(pWState->hbm_Bkg);
            DeleteObject(pWState->hbr_Bkg);
            LocalFree((HLOCAL)pWState);
            return FALSE;
        } else  {
        //  Fall through.
        }
    } else if (pWState->sbHwnd != hwnd) {
        return FALSE;
    }

    switch (propIndex)  {
    case WSB_PROP_CYVSCROLL:
        *pValue = pWState->metApp.cyVSBArrow;
        break;
    case WSB_PROP_CXVSCROLL:
        *pValue = pWState->metApp.cxVSBArrow;
        break;
    case WSB_PROP_CYHSCROLL:
        *pValue = pWState->metApp.cyHSBArrow;
        break;
    case WSB_PROP_CXHSCROLL:
        *pValue = pWState->metApp.cxHSBArrow;
        break;
    case WSB_PROP_CXHTHUMB:
        *pValue = pWState->metApp.cxHSBThumb;
        break;
    case WSB_PROP_CYVTHUMB:
        *pValue = pWState->metApp.cyVSBThumb;
        break;
    case WSB_PROP_WINSTYLE:
        //  To check if a scrollbar is present, the WF(HV)PRESENT bits may
        //  be more useful than WS_(HV)SCROLL bits.
        *pValue = pWState->style;
        break;
    case WSB_PROP_HSTYLE:
        *pValue = pWState->hStyle;
        break;
    case WSB_PROP_VSTYLE:
        *pValue = pWState->vStyle;
        break;
    case WSB_PROP_HBKGCOLOR:
        *pValue = pWState->col_HSBBkg;
        break;
    case WSB_PROP_VBKGCOLOR:
        *pValue = pWState->col_VSBBkg;
        break;
    case WSB_PROP_PALETTE:
        *pValue = (INT_PTR)pWState->hPalette;
        break;
    case WSB_PROP_GUTTER:
        *pValue = pWState->sbGutter;
        break;
    default:
        return FALSE;
    }
    return TRUE;
}

#ifdef _WIN64

BOOL WINAPI FlatSB_GetScrollProp(HWND hwnd, int propIndex, LPINT pValue)
{
    INT_PTR iValue;
    BOOL fRc;

    if (!pValue)
        return FALSE;

#ifdef DEBUG
    if (propIndex == WSB_PROP_PALETTE)
    {
        TraceMsg(TF_ERROR, "FlatSB_GetScrollProp(WSB_PROP_PALETTE): Use GetScrollPropPtr for Win64 compat");
    }
#endif

    fRc = FlatSB_GetScrollPropPtr(hwnd, propIndex, &iValue);
    *pValue = (int)iValue;

    return fRc;
}
#endif

BOOL WINAPI FlatSB_GetScrollRange(HWND hwnd, int code, LPINT lpposMin, LPINT lpposMax)
{
    int     *pw;
    WSBState * pWState;

    ASSERT(code != SB_CTL);
    if (!lpposMin || !lpposMax)
        return FALSE;

    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if (pWState == (WSBState *)NULL)    {
        return GetScrollRange(hwnd, code, lpposMin, lpposMax);
//        *lpposMin = 0;
//        *lpposMax = 0;
    } else if (pWState == WSB_UNINIT_HANDLE) {
        *lpposMin = 0;
        *lpposMax = 0;
    } else if (pWState->sbHwnd != hwnd) {
        return FALSE;
    } else {
        pw = (code == SB_VERT) ? &(pWState->sbVMinPos) : &(pWState->sbHMinPos);
        *lpposMin = pw[SBO_MIN];
        *lpposMax = pw[SBO_MAX];
    }

    return TRUE;
}

BOOL WINAPI FlatSB_GetScrollInfo(HWND hwnd, int fnBar, LPSCROLLINFO lpsi)
{
    int *pw;
    WSBState * pWState;

    ASSERT(fnBar != SB_CTL);

    //  ZDC@Oct. 10, Detect GP faults here.
    if ((LPSCROLLINFO)NULL == lpsi)
        return FALSE;

    if (lpsi->cbSize < sizeof (SCROLLINFO))
        return FALSE;

    //  ZDC@Oct. 11, Don't zero out buffer anymore.
    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if (pWState == (WSBState *)NULL)    {
        return GetScrollInfo(hwnd, fnBar, lpsi);
    } else if (pWState == WSB_UNINIT_HANDLE) {
        return FALSE;
    } else if (pWState->sbHwnd != hwnd) {
        return FALSE;
    } else if (fnBar == SB_VERT)    {
        pw = &(pWState->sbVMinPos);
    } else if (fnBar == SB_HORZ)    {
        pw = &(pWState->sbHMinPos);
    } else {
        return FALSE;
    }

    if (lpsi->fMask & SIF_RANGE)
        lpsi->nMin = pw[SBO_MIN], lpsi->nMax = pw[SBO_MAX];
    if (lpsi->fMask & SIF_POS)
        lpsi->nPos = pw[SBO_POS];
    if (lpsi->fMask & SIF_PAGE)
        lpsi->nPage = pw[SBO_PAGE];
    // ZDC@Oct 9, Add support for SIF_TRACKPOS
    if (lpsi->fMask & SIF_TRACKPOS) {
        //  This is the olny place that pfnSB is used instead of fTracking.
        if (pWState->pfnSB != NULL) {
            if ((fnBar == SB_VERT) && pWState->fTrackVert)
                lpsi->nTrackPos = pWState->posNew;
            else if ((fnBar == SB_HORZ) && !(pWState->fTrackVert))
                lpsi->nTrackPos = pWState->posNew;
            else
                lpsi->nTrackPos = pw[SBO_POS];
        } else
            lpsi->nTrackPos = pw[SBO_POS];
    }

    return TRUE;
}

BOOL WINAPI FlatSB_ShowScrollBar(HWND hwnd, int fnBar, BOOL fShow)
{
    BOOL fChanged = FALSE;
    int newStyle = 0;
    WSBState * pWState;

    ASSERT(fnBar != SB_CTL);

    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if (pWState == (WSBState *)NULL)
        return ShowScrollBar(hwnd, fnBar, fShow);

    switch (fnBar) {
    case SB_VERT:
        newStyle = WS_VSCROLL;
        break;
    case SB_HORZ:
        newStyle = WS_HSCROLL;
        break;
    case SB_BOTH:
        newStyle = WS_VSCROLL | WS_HSCROLL;
        break;
    default:
        return FALSE;
    }

    if (pWState == WSB_UNINIT_HANDLE) {
        if (fShow)  {
            pWState = FlatSB_Internal_InitPwSB(hwnd);
            if (pWState == (WSBState *)NULL)
                return FALSE;
            else if (!SetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0,  (ULONG_PTR)pWState)) {
                DeleteObject(pWState->hbm_Bkg);
                DeleteObject(pWState->hbr_Bkg);
                LocalFree((HLOCAL)pWState);
                return FALSE;
            }
        } else  {
            return FALSE;
        }
    }

    if (!fShow) {
        if (pWState->style & newStyle) {
            fChanged = TRUE;
            pWState->style &= ~newStyle;
        }
    } else {
        if ((pWState->style & newStyle) != newStyle)    {
            fChanged = TRUE;
            pWState->style |= newStyle;
        }
    }

    if (fChanged) {
        // Keep USER scrollbars in sync for accessibility
        ShowScrollBar(hwnd, fnBar, fShow);
        CCInvalidateFrame(hwnd);
    }

    return TRUE;
}

//=------------------------------------------------------------------
//  Following functions are ported from winsb.c in user code.
//=------------------------------------------------------------------

//=--------------------------------------------------------------
// InitPwSB
//     [in]    hwnd
// Note:
//     This function is only a memory allocating func. It won't
//     do any check. On the other hand, this function should be
//     called before any consequent functions are used.
//=--------------------------------------------------------------

WSBState * FlatSB_Internal_InitPwSB(HWND hwnd)
{
    int     patGray[4];
    HBITMAP hbm;
    WSBState * pw;

    pw = (WSBState *)LocalAlloc(LPTR, sizeof(WSBState));
    // The buffer should already be zero-out.

    if (pw == (WSBState *)NULL)
        return pw;

    patGray[0] = 0x005500AA;
    patGray[1] = 0x005500AA;
    patGray[2] = 0x005500AA;
    patGray[3] = 0x005500AA;

    pw->sbVMaxPos = pw->sbHMaxPos = 100;
    pw->sbHwnd = hwnd;

    // We start out with app metrics equal to system metrics
    FlatSB_InitWSBMetrics(pw);
    pw->metApp = pw->metSys;

    //
    //  NT5's gutter is 8; Win9x's and NT4's gutter is 2.
    //
    pw->sbGutter = g_bRunOnNT5 ? 8 : 2;

    // ZDC
    //     make sure get hbm_Bkg and hbr_Bkg deleted.
    hbm = CreateBitmap(8, 8, 1, 1, (LPSTR)patGray);

    if ((HBITMAP)NULL == hbm)   {
        LocalFree((HLOCAL)pw);
        return NULL;
    }

    pw->hbr_VSBBkg = CreatePatternBrush(hbm);
    if ((HBRUSH)NULL == pw->hbr_VSBBkg)  {
        DeleteObject(hbm);
        LocalFree((HLOCAL)pw);
        return NULL;
    }

    pw->hbr_Bkg = pw->hbr_HSBBkg = pw->hbr_VSBBkg;
    pw->col_VSBBkg = pw->col_HSBBkg = RGB(255, 255, 255);
    pw->hbm_Bkg = hbm;
    pw->hStyle = pw->vStyle = FSB_FLAT_MODE;    //  Default state: Flat.
    pw->ptMouse.x = -1;
    pw->ptMouse.y = -1;

    return(pw);
}

void FlatSB_Internal_RedrawScrollBar(WSBState * pWState, BOOL fVert)
{
    HDC hdc;

    hdc = GetWindowDC(pWState->sbHwnd);
    FlatSB_Internal_DrawScrollBar(pWState, hdc, fVert, TRUE);
    ReleaseDC(pWState->sbHwnd, hdc);
}

//=-------------------------------------------------------------
// FlatSB_Internal_GetSBFlags
//=-------------------------------------------------------------

UINT FlatSB_Internal_GetSBFlags(WSBState * pWState, BOOL fVert)
{
    int wFlags;

    if (pWState == (WSBState *)NULL)    {
        return(0);
    }

    wFlags = pWState->sbFlags;

    return(fVert ? (wFlags & WSB_VERT) >> 2 : wFlags & WSB_HORZ);
}

//=--------------------------------------------------------------
//  return TRUE if there is a change.
//=--------------------------------------------------------------

BOOL WINAPI FlatSB_EnableScrollBar(HWND hwnd, int wSBflags, UINT wArrows)
{
    WSBState * pWState;

    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if (pWState == (WSBState *)NULL)    {
        return EnableScrollBar(hwnd, wSBflags, wArrows);
    } else if (pWState == WSB_UNINIT_HANDLE) {
        if (wArrows == ESB_ENABLE_BOTH)
            //  Leave it to later calls.
            return FALSE;
        else    {
            pWState = FlatSB_Internal_InitPwSB(hwnd);
            if (pWState == (WSBState *)NULL)
                return FALSE;
            else if (!SetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0,  (ULONG_PTR)pWState)) {
                DeleteObject(pWState->hbm_Bkg);
                DeleteObject(pWState->hbr_Bkg);
                LocalFree((HLOCAL)pWState);
                return FALSE;
            }
        }
    } else if (hwnd != pWState->sbHwnd) {
        return FALSE;
    }

    return FlatSB_Internal_EnableScrollBar(pWState, wSBflags, wArrows);
}

//=-------------------------------------------------------------
// FlatSB_Internal_EnableScrollBar
//
// Note:
//     The func will simply fail in case of uninitialized pointer
//     pWState is passed.
//     Since we now use WSBState * as handle, we always hope it's
//     valid already.
//
//     The following func is implemented following the comments in
//     winsbctl.c and the comment of the in MSDN library. In
//     access\inc16\windows.h you can find:
//     #define SB_DISABLE_MASK ESB_DISABLE_BOTH    // 0x03
//
//     The sbFlags is slightly different with rgwScroll[SB_FLAGS].
//=-------------------------------------------------------------

BOOL FlatSB_Internal_EnableScrollBar(WSBState * pWState, int wSBflags, UINT wArrows)
{
    int     wOldFlags;
    int     style;
    BOOL    bRetValue = FALSE;
    BOOL    bDrawHBar = FALSE;
    BOOL    bDrawVBar = FALSE;
    HDC     hdc;
    HWND    hwnd;

    ASSERT (wSBflags != SB_CTL);

    wOldFlags = pWState->sbFlags;
    hwnd = pWState->sbHwnd;

    style = GetWindowLong(hwnd, GWL_STYLE);

    switch (wSBflags)   {
    case SB_HORZ:
    case SB_BOTH:
        if (wArrows == ESB_ENABLE_BOTH)
            pWState->sbFlags &= ~WSB_HORZ;
        else
            pWState->sbFlags |= wArrows;

        if (wOldFlags != pWState->sbFlags)
        {
            bRetValue = TRUE;

            if (TestSTYLE(pWState->style, WFHPRESENT)
                && !TestSTYLE(style, WS_MINIMIZE)
                && IsWindowVisible(hwnd))
                bDrawHBar = TRUE;
        }

        if (wSBflags == SB_HORZ)
            break;
        else
            wOldFlags = pWState->sbFlags;       //  Fall through

    case SB_VERT:
        if (wArrows == ESB_ENABLE_BOTH)
            pWState->sbFlags &= ~WSB_VERT;
        else
            pWState->sbFlags |= (wArrows<<2);

        if (wOldFlags != pWState->sbFlags)
        {
            bRetValue = TRUE;

            if (TestSTYLE(pWState->style, WFVPRESENT)
                && !TestSTYLE(style, WS_MINIMIZE)
                && IsWindowVisible(hwnd))
                bDrawVBar = TRUE;
        }
        break;
    default:
        return FALSE;
    }

    if (bDrawVBar || bDrawHBar) {
        int oldLoc = pWState->locMouse;
        int newLoc;

        if (!(hdc = GetWindowDC(hwnd)))
            return(FALSE);

        newLoc = oldLoc;
        if (bDrawHBar)  {
            FlatSB_Internal_DrawScrollBar(pWState, hdc, FALSE, FALSE);
            if (pWState->fHActive)
                newLoc = pWState->locMouse;
        }
        if (bDrawVBar) {
            pWState->locMouse = oldLoc;
            FlatSB_Internal_DrawScrollBar(pWState, hdc, TRUE, FALSE);
            if (pWState->fVActive)
                newLoc = pWState->locMouse;
        }
        pWState->locMouse = newLoc;

        ReleaseDC(hwnd, hdc);
    }

    // Keep USER scrollbar in sync for accessibility
    if (bRetValue)
        EnableScrollBar(hwnd, wSBflags, wArrows);

    return bRetValue;
}

//=-------------------------------------------------------------
// FlatSB_Internal_DrawThumb2
//=-------------------------------------------------------------

void FlatSB_Internal_DrawThumb2(WSBState * pWState, HDC hdc, BOOL fVert, UINT wDisable)
{
    int     *pLength;
    int     *pWidth;
    HWND    hwnd;
    HBRUSH  hbr;

    hwnd = pWState->sbHwnd;
    hbr = (fVert)?pWState->hbr_VSBBkg:pWState->hbr_HSBBkg;

    // Bail out if the scrollbar has an empty rect
    if ((pWState->pxTop >= pWState->pxBottom)
        || (pWState->pxLeft >= pWState->pxRight))
        return;

    pLength = (int *) &(pWState->rcSB);
    if (fVert)
        pWidth = pLength++;
    else
        pWidth = pLength + 1;

    pWidth[0] = pWState->pxLeft;
    pWidth[2] = pWState->pxRight;

    // If both scroll arrows are disabled or if there isn't enough room for
    // the thumb, just erase the whole slide area and return
    if (((wDisable & LTUPFLAG) && (wDisable & RTDNFLAG)) ||
        ((pWState->pxDownArrow - pWState->pxUpArrow) < pWState->cpxThumb))
    {
        pLength[0] = pWState->pxUpArrow;
        pLength[2] = pWState->pxDownArrow;

        FlatSB_Internal_DrawGroove(pWState, hdc, &(pWState->rcSB), fVert);
        return;
    }

    //  UI designers want a at least 1 pixel gap between arrow and thumb.
    //  Have to do this :(
    if (pWState->pxUpArrow <= pWState->pxThumbTop)
    {
        // Fill in space above Thumb
        pLength[0] = pWState->pxUpArrow;
        pLength[2] = pWState->pxThumbTop;

        FlatSB_Internal_DrawGroove(pWState, hdc, &(pWState->rcSB), fVert);
    }

    if (pWState->pxThumbBottom <= pWState->pxDownArrow)
    {
        // Fill in space below Thumb
        pLength[0] = pWState->pxThumbBottom;
        pLength[2] = pWState->pxDownArrow;

        FlatSB_Internal_DrawGroove(pWState, hdc, &(pWState->rcSB), fVert);
    }

    // Draw elevator
    pLength[0] = pWState->pxThumbTop;
    pLength[2] = pWState->pxThumbBottom;

    FlatSB_Internal_DrawElevator(pWState, hdc, &(pWState->rcSB), fVert);

    // If we're tracking a page scroll, then we've obliterated the hilite.
    // We need to correct the hiliting rectangle, and rehilite it.

    if ((pWState->cmdSB == SB_PAGEUP || pWState->cmdSB == SB_PAGEDOWN)
        && pWState->fTrackVert == fVert)
    {
        pLength = (int *) &pWState->rcTrack;

        if (fVert)
            pLength++;

        if (pWState->cmdSB == SB_PAGEUP)
            pLength[2] = pWState->pxThumbTop;
        else
            pLength[0] = pWState->pxThumbBottom;

        if (pLength[0] < pLength[2])
            InvertRect(hdc, &(pWState->rcTrack));
    }
}

//=-------------------------------------------------------------
// DrawSB2
//=-------------------------------------------------------------

void FlatSB_Internal_DrawSB2(WSBState * pWState, HDC hdc, BOOL fVert, BOOL fRedraw, int oldLoc)
{
    int     cLength;
    int     cWidth;
    int     cpxArrow;
    int     *pwX;
    int     *pwY;
    int     newLoc = pWState->locMouse;
    UINT    wDisable = FlatSB_Internal_GetSBFlags(pWState, fVert);
    HBRUSH  hbrSave;
    HWND    hwnd;
    RECT    rc, * prcSB;

    hwnd = pWState->sbHwnd;
    cLength = (pWState->pxBottom - pWState->pxTop) / 2;
    cWidth = (pWState->pxRight - pWState->pxLeft);

    if ((cLength <= 0) || (cWidth <= 0))
        return;

    cpxArrow = (fVert) ? pWState->y_VSBArrow : pWState->x_HSBArrow;

    if (cLength > cpxArrow)
        cLength = cpxArrow;
    prcSB = &(pWState->rcSB);
    pwX = (int *)prcSB;
    pwY = pwX + 1;
    if (!fVert)
        pwX = pwY--;

    pwX[0] = pWState->pxLeft;
    pwY[0] = pWState->pxTop;
    pwX[2] = pWState->pxRight;
    pwY[2] = pWState->pxBottom;

    hbrSave = SelectObject(hdc, GetSysColorBrush(COLOR_BTNTEXT));

    CopyRect(&rc, prcSB);
    if (fVert)
    {
        rc.bottom = rc.top + cLength;
        if (!fRedraw || newLoc == WSB_MOUSELOC_ARROWUP
                     || oldLoc == WSB_MOUSELOC_ARROWUP)
            FlatSB_Internal_DrawArrow(pWState, hdc, &rc, DFCS_SCROLLUP,
               ((wDisable & LTUPFLAG) ? DFCS_INACTIVE : 0));

        rc.bottom = prcSB->bottom;
        rc.top = prcSB->bottom - cLength;
        if (!fRedraw || newLoc == WSB_MOUSELOC_ARROWDN
                     || oldLoc == WSB_MOUSELOC_ARROWDN)
            FlatSB_Internal_DrawArrow(pWState, hdc, &rc, DFCS_SCROLLDOWN,
               ((wDisable & RTDNFLAG) ? DFCS_INACTIVE : 0));
    }
    else
    {
        rc.right = rc.left + cLength;
        if (!fRedraw || newLoc == WSB_MOUSELOC_ARROWLF
                     || oldLoc == WSB_MOUSELOC_ARROWLF)
            FlatSB_Internal_DrawArrow(pWState, hdc, &rc, DFCS_SCROLLLEFT,
                ((wDisable & LTUPFLAG) ? DFCS_INACTIVE : 0));

        rc.right = prcSB->right;
        rc.left = prcSB->right - cLength;
        if (!fRedraw || newLoc == WSB_MOUSELOC_ARROWRG
                     || oldLoc == WSB_MOUSELOC_ARROWRG)
            FlatSB_Internal_DrawArrow(pWState, hdc, &rc, DFCS_SCROLLRIGHT,
                ((wDisable & RTDNFLAG) ? DFCS_INACTIVE : 0));
    }

    SelectObject(hdc, hbrSave);

    if (!fRedraw)
        FlatSB_Internal_DrawThumb2(pWState, hdc, fVert, wDisable);
    else if (!fVert  || newLoc == WSB_MOUSELOC_H_THUMB
                     || oldLoc == WSB_MOUSELOC_H_THUMB)
        FlatSB_Internal_DrawThumb2(pWState, hdc, fVert, wDisable);
    else if (fVert   || newLoc == WSB_MOUSELOC_V_THUMB
                     || oldLoc == WSB_MOUSELOC_V_THUMB)
        FlatSB_Internal_DrawThumb2(pWState, hdc, fVert, wDisable);
    else
        return;
}

//=-------------------------------------------------------------
// FlatSB_Internal_CalcSBStuff2
//=-------------------------------------------------------------

void FlatSB_Internal_CalcSBStuff2(WSBState * pWState, LPRECT lprc, BOOL fVert)
{
    int     cpxThumb;    // Height of (V)scroll bar thumb.
    int     cpxArrow;    // Height of (V)scroll bar arrow.
    int     cpxSpace;    // The space in scroll bar;
    int     pxTop;
    int     pxBottom;
    int     pxLeft;
    int     pxRight;
    int     pxUpArrow;
    int     pxDownArrow;
    int     pxThumbTop;
    int     pxThumbBottom;
    int     pxMouse;
    int     locMouse;
    int     dwRange, page, relPos;
    BOOL    fSBActive;

    if (fVert) {
        pxTop    = lprc->top;
        pxBottom = lprc->bottom;
        pxLeft   = lprc->left;
        pxRight  = lprc->right;
        cpxArrow = pWState->y_VSBArrow;
        cpxThumb = pWState->y_VSBThumb;
        relPos = pWState->sbVThumbPos - pWState->sbVMinPos;
        page = pWState->sbVPage;
        dwRange = pWState->sbVMaxPos - pWState->sbVMinPos + 1;
        pxMouse = pWState->ptMouse.y;
        fSBActive = pWState->fVActive;
    } else {
        // For horiz scroll bars, "left" & "right" are "top" and "bottom",
        // and vice versa.
        pxTop    = lprc->left;
        pxBottom = lprc->right;
        pxLeft   = lprc->top;
        pxRight  = lprc->bottom;
        cpxArrow = pWState->x_HSBArrow;
        cpxThumb = pWState->x_HSBThumb;
        relPos = pWState->sbHThumbPos - pWState->sbHMinPos;
        page = pWState->sbHPage;
        dwRange = pWState->sbHMaxPos - pWState->sbHMinPos + 1;
        pxMouse = pWState->ptMouse.x;
        fSBActive = pWState->fHActive;
    }

    // For the case of short scroll bars that don't have enough
    // room to fit the full-sized up and down arrows, shorten
    // their sizes to make 'em fit

    cpxArrow = min((pxBottom - pxTop) >> 1, cpxArrow);

    pxUpArrow   = pxTop    + cpxArrow;
    pxDownArrow = pxBottom - cpxArrow;

    cpxSpace = pxDownArrow - pxUpArrow;
    if (page)
    {
        // JEFFBOG -- This is the one and only place where we should
        // see 'range'.  Elsewhere it should be 'range - page'.
        cpxThumb = max(DMultDiv(cpxSpace, page, dwRange),
                        min(cpxThumb, MINITHUMBSIZE));
    }
    cpxSpace -= cpxThumb;

    pxThumbTop = DMultDiv(relPos, cpxSpace, dwRange - (page ? page : 1)) + pxUpArrow;
    pxThumbBottom = pxThumbTop + cpxThumb;

    // Save it to local structure
    pWState->pxLeft     = pxLeft;
    pWState->pxRight    = pxRight;
    pWState->pxTop      = pxTop;
    pWState->pxBottom   = pxBottom;
    pWState->pxUpArrow  = pxUpArrow;
    pWState->pxDownArrow    = pxDownArrow;
    pWState->pxThumbTop = pxThumbTop;
    pWState->pxThumbBottom  = pxThumbBottom;
    pWState->cpxArrow   = cpxArrow;
    pWState->cpxThumb   = cpxThumb;
    pWState->cpxSpace   = cpxSpace;
    pWState->fVertSB    = fVert;

    if (pWState->fTracking) {
        return;
    } else if (!fSBActive)  {
        locMouse = WSB_MOUSELOC_OUTSIDE;
    } else if (pxMouse < pxTop) {
        locMouse = WSB_MOUSELOC_OUTSIDE;
    } else if (pxMouse < pxUpArrow) {
        locMouse = WSB_MOUSELOC_ARROWUP;
    } else if (pxMouse < pxThumbTop) {
        locMouse = WSB_MOUSELOC_V_GROOVE;
    } else if (pxMouse >= pxBottom) {
        locMouse = WSB_MOUSELOC_OUTSIDE;
    } else if (pxMouse >= pxDownArrow) {
        locMouse = WSB_MOUSELOC_ARROWDN;
    } else if (pxMouse >= pxThumbBottom) {
        locMouse = WSB_MOUSELOC_V_GROOVE;
    } else    {   //   pxThumbTop <= pxMouse < pxThumbBottom
        if (pxDownArrow - pxUpArrow <= cpxThumb)    {   //  No space for thumnb.
            locMouse = WSB_MOUSELOC_V_GROOVE;
        } else  {
            locMouse = WSB_MOUSELOC_V_THUMB;
        }
    }
    if ((!fVert) && locMouse)
        locMouse += 4;

    pWState->locMouse = locMouse;
}

//=-------------------------------------------------------------
// FlatSB_Internal_CalcSBStuff
//
// Note:
//  We won't call InitPwSB in this func.
//=-------------------------------------------------------------

void FlatSB_Internal_CalcSBStuff(WSBState * pWState, BOOL fVert)
{
    HWND    hwnd;
    RECT    rcT;
    int     style;

    if (pWState == (WSBState *)NULL)
        return;

    hwnd = pWState->sbHwnd;
    style = GetWindowLong(hwnd, GWL_STYLE);

    if (fVert)
    {
        // Only add on space if vertical scrollbar is really there.
        rcT.right = rcT.left = pWState->rcClient.right;
        if (TestSTYLE(pWState->style, WFVPRESENT))
            rcT.right += pWState->x_VSBArrow;
        rcT.top = pWState->rcClient.top;
        rcT.bottom = pWState->rcClient.bottom;
    }
    else
    {
        // Only add on space if horizontal scrollbar is really there.
        rcT.bottom = rcT.top = pWState->rcClient.bottom;
        if (TestSTYLE(pWState->style, WFHPRESENT))
            rcT.bottom += pWState->y_HSBArrow;

        rcT.left = pWState->rcClient.left;
        rcT.right = pWState->rcClient.right;
    }

    FlatSB_Internal_CalcSBStuff2(pWState, &rcT, fVert);
}

//=-------------------------------------------------------------
// FlatSB_Internal_DrawThumb
//=-------------------------------------------------------------

void FlatSB_Internal_DrawThumb(WSBState * pWState, BOOL fVert)
{
    HWND    hwnd = pWState->sbHwnd;
    HDC     hdc;
    UINT    wDisableFlags;

    hdc = (HDC) GetWindowDC(hwnd);
    FlatSB_Internal_CalcSBStuff(pWState, fVert);

    wDisableFlags = FlatSB_Internal_GetSBFlags(pWState, fVert);
    FlatSB_Internal_DrawThumb2(pWState, hdc, fVert, wDisableFlags);
    ReleaseDC(hwnd, hdc);
}

BOOL FlatSB_Internal_SBSetParms(int * pw, SCROLLINFO si, BOOL * lpfScroll, LRESULT * lplres, BOOL bOldPos)
{
    // pass the struct because we modify the struct but don't want that
    // modified version to get back to the calling app

    BOOL fChanged = FALSE;

    if (bOldPos)
        // save previous position
        *lplres = pw[SBO_POS];

    if (si.fMask & SIF_RANGE)
    {
        // if the range MAX is below the range MIN -- then treat is as a
        // zero range starting at the range MIN.
        if (si.nMax < si.nMin)
            si.nMax = si.nMin;

        if ((pw[SBO_MIN] != si.nMin) || (pw[SBO_MAX] != si.nMax))
        {
            pw[SBO_MIN] = si.nMin;
            pw[SBO_MAX] = si.nMax;

            if (!(si.fMask & SIF_PAGE))
            {
                si.fMask |= SIF_PAGE;
                si.nPage = pw[SBO_PAGE];
            }

            if (!(si.fMask & SIF_POS))
            {
                si.fMask |= SIF_POS;
                si.nPos = pw[SBO_POS];
            }

            fChanged = TRUE;
        }
    }

    if (si.fMask & SIF_PAGE)
    {
        unsigned dwMaxPage = abs(pw[SBO_MAX] - pw[SBO_MIN]) + 1;

        if (si.nPage > dwMaxPage)
            si.nPage = dwMaxPage;

        if (pw[SBO_PAGE] != (int) si.nPage)
        {
            pw[SBO_PAGE] = (int) si.nPage;

            if (!(si.fMask & SIF_POS))
            {
                si.fMask |= SIF_POS;
                si.nPos = pw[SBO_POS];
            }

            fChanged = TRUE;
        }
    }

    if (si.fMask & SIF_POS)
    {
        // Clip pos to posMin, posMax - (page - 1).
        int lMaxPos = pw[SBO_MAX] - ((pw[SBO_PAGE]) ? pw[SBO_PAGE] - 1 : 0);

        // * BOGUS -- show this to SIMONK -- the following doesn't generate *
        // * proper code so I had to use the longer form                    *
        // * si.nPos = min(max(si.nPos, pw[SBO_MIN]), lMaxPos);             *

        if (si.nPos < pw[SBO_MIN])
            si.nPos = pw[SBO_MIN];
        else if (si.nPos > lMaxPos)
            si.nPos = lMaxPos;

        if (pw[SBO_POS] != si.nPos)
        {
            pw[SBO_POS] = si.nPos;
            fChanged = TRUE;
        }
    }

    if (!(bOldPos))
        // Return the new position
        *lplres = pw[SBO_POS];

    if (si.fMask & SIF_RANGE)
    {
        if (*lpfScroll = (pw[SBO_MIN] != pw[SBO_MAX]))
            goto checkPage;
    }
    else if (si.fMask & SIF_PAGE)
checkPage:
        *lpfScroll = (pw[SBO_PAGE] <= (pw[SBO_MAX] - pw[SBO_MIN]));

    return(fChanged);
}


//=-------------------------------------------------------------
// FlatSB_Internal_SetScrollBar
//
// Note:
//     This func is called by SetScrollPos/Range/Info. We let
//     the callers take care of checking pWState.
//     Return 0 if failed.
//=-------------------------------------------------------------

LRESULT FlatSB_Internal_SetScrollBar(WSBState *pWState, int code, LPSCROLLINFO lpsi, BOOL fRedraw)
{
    BOOL    fVert;
    int     *pw;
    BOOL    fOldScroll;
    BOOL    fScroll;
    BOOL    bReturnOldPos = TRUE;
    LRESULT lres;
    int     wfScroll;
    HWND    hwnd = pWState->sbHwnd;

    ASSERT (code != SB_CTL);

    // window must be visible to redraw
    if (fRedraw)
        fRedraw = IsWindowVisible(hwnd);

    fVert = (code != SB_HORZ);
    bReturnOldPos = (lpsi->fMask == SIF_POS);

    wfScroll = (fVert) ? WS_VSCROLL : WS_HSCROLL;

    fScroll = fOldScroll = (TestSTYLE(pWState->style, wfScroll)) ? TRUE : FALSE;

    // Don't do anything if we're NOT setting the range and the scroll doesn't
    // exist.
    if (!(lpsi->fMask & SIF_RANGE) && !fOldScroll)
    {
        return(0);
    }

    pw = &(pWState->sbFlags);

    // user.h: SBO_VERT = 5, SBO_HORZ = 1;
    //  pw += (fVert) ? SBO_VERT : SBO_HORZ;
    pw += (fVert)? 5 : 1;

    // Keep USER scrollbars in sync for accessibility
    SetScrollInfo(hwnd, code, lpsi, FALSE);

    if (!FlatSB_Internal_SBSetParms(pw, *lpsi, &fScroll, &lres, bReturnOldPos))
    {
        // no change -- but if REDRAW is specified and there's a scrollbar,
        // redraw the thumb
        if (fOldScroll && fRedraw)
            goto redrawAfterSet;

        return(lres);
    }

    if (fScroll)
        pWState->style |= wfScroll;
    else
        pWState->style &= ~wfScroll;

    // Keep style bits in sync so OLEACC can read them
    SetWindowBits(hwnd, GWL_STYLE, WS_VSCROLL | WS_HSCROLL, pWState->style);

    if (lpsi->fMask & SIF_DISABLENOSCROLL)
    {
        if (fOldScroll)
        {
            pWState->style |= wfScroll;

            // Keep style bits in sync so OLEACC can read them
            SetWindowBits(hwnd, GWL_STYLE, WS_VSCROLL | WS_HSCROLL, pWState->style);

            FlatSB_Internal_EnableScrollBar(pWState, code, (fScroll) ? ESB_ENABLE_BOTH : ESB_DISABLE_BOTH);
        }
    }
    else if (fOldScroll ^ fScroll)
    {
        CCInvalidateFrame(hwnd);
        return(lres);
    }

    if (fScroll && fRedraw && (fVert ? TestSTYLE(pWState->style, WFVPRESENT) : TestSTYLE(pWState->style, WFHPRESENT)))
    {
redrawAfterSet:

        // Don't send this, since USER already sent one for us when we
        // called SetScrollBar.
        // FlatSB_Internal_NotifyWinEvent(pWState, EVENT_OBJECT_VALUECHANGE, INDEX_SCROLLBAR_SELF);

        // Bail out if the caller is trying to change a scrollbar which is
        // in the middle of tracking.  We'll hose FlatSB_Internal_TrackThumb() otherwise.

        // BUGBUG: CalcSBStuff will change locMouse!
        if (pWState->pfnSB == FlatSB_Internal_TrackThumb)
        {
            FlatSB_Internal_CalcSBStuff(pWState, fVert);
            return(lres);
        }
        FlatSB_Internal_DrawThumb(pWState, fVert);
    }

    return(lres);
}

//=-------------------------------------------------------------
//  SetScrollPos()
//=-------------------------------------------------------------

int WINAPI FlatSB_SetScrollPos(HWND hwnd, int code, int pos, BOOL fRedraw)
{
    SCROLLINFO  si;
    WSBState * pWState;

    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if (pWState == (WSBState *)NULL)    {
        return SetScrollPos(hwnd, code, pos, fRedraw);
    } else if (pWState == WSB_UNINIT_HANDLE) {
        return 0;
    } else if (hwnd != pWState->sbHwnd) {
        return 0;
    }

    si.cbSize = sizeof(si);
    si.fMask = SIF_POS;
    si.nPos = pos;
    
    return (int)FlatSB_Internal_SetScrollBar(pWState, code, &si, fRedraw);
}

//=-------------------------------------------------------------
//  SetScrollRange()
//=-------------------------------------------------------------

BOOL WINAPI FlatSB_SetScrollRange(HWND hwnd, int code, int nMin, int nMax, BOOL fRedraw)
{
    SCROLLINFO si;
    WSBState * pWState;

    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if (pWState == (WSBState *)NULL)    {
        return SetScrollRange(hwnd, code, nMin, nMax, fRedraw);
    } else if (pWState == WSB_UNINIT_HANDLE) {
        pWState = FlatSB_Internal_InitPwSB(hwnd);
        if (pWState == (WSBState *)NULL)
            return FALSE;
        else if (!SetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0,  (ULONG_PTR)pWState))  {
            DeleteObject(pWState->hbm_Bkg);
            DeleteObject(pWState->hbr_Bkg);
            LocalFree((HLOCAL)pWState);
            return FALSE;
        }
        //  In this case we always need to (re)draw the scrollbar.
        fRedraw = TRUE;
    } else if (hwnd != pWState->sbHwnd) {
        return FALSE;
    }


    //
    // Still need MAXINT check for PackRat 4.  32-bit apps don't
    // go thru this--we wrap 'em to SetScrollInfo() on the 32-bit side,
    // so DWORD precision is preserved.
    //
    if ((UINT)(nMax - nMin) > 0x7FFF)
        return FALSE;

    si.cbSize = sizeof(si);
    si.fMask = SIF_RANGE;
    si.nMin = nMin;
    si.nMax = nMax;

    FlatSB_Internal_SetScrollBar(pWState, code, &si, fRedraw);

    return(TRUE);
}


//=-------------------------------------------------------------
//  SetScrollInfo()
//
//  Note:
//      Inconsistent with 'user' code. Under no circumstance will
//      we create a new scrollbar(by allocate a new buffer).
//=-------------------------------------------------------------

int WINAPI FlatSB_SetScrollInfo(HWND hwnd, int code, LPSCROLLINFO lpsi, BOOL fRedraw)
{
    WSBState * pWState;

    //  ZDC@Oct. 10, Detect GP faults here.
    if ((LPSCROLLINFO)NULL == lpsi)
        return FALSE;

    if (lpsi->cbSize < sizeof (SCROLLINFO))
        return FALSE;

    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if (pWState == (WSBState *)NULL)    {
        return SetScrollInfo(hwnd, code, lpsi, fRedraw);
    } else if (pWState == WSB_UNINIT_HANDLE) {
        if (!(lpsi->fMask & SIF_RANGE))
            return 0;
        pWState = FlatSB_Internal_InitPwSB(hwnd);
        if (pWState == (WSBState *)NULL)
            return 0;
        else if (!SetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0,  (ULONG_PTR)pWState)) {
            DeleteObject(pWState->hbm_Bkg);
            DeleteObject(pWState->hbr_Bkg);
            LocalFree((HLOCAL)pWState);
            return 0;
        }

        //  In this case we always need to (re)draw the scrollbar.
        fRedraw = TRUE;
    } else if (hwnd != pWState->sbHwnd) {
        return 0;
    }

    //  ZDC@Oct 9, We should always return new pos. How ever, if the fMask
    //  is SIF_POS, SetScrollBar returns the old pos.
    if (lpsi->fMask == SIF_POS)
        lpsi->fMask = SIF_POS | SIF_TRACKPOS;

    return (int)FlatSB_Internal_SetScrollBar(pWState, code, lpsi, fRedraw);
}

//=-------------------------------------------------------------
// FlatSB_SetScrollProp
//     This functions shouldn't be called we we are tracking.
//=-------------------------------------------------------------

BOOL WINAPI FlatSB_SetScrollProp(HWND hwnd, UINT index, INT_PTR newValue, BOOL fRedraw)
{
    BOOL    fResize = FALSE;
    BOOL    fVert = FALSE;
    WSBState * pWState;

    GetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0, (ULONG_PTR *)&pWState);
    if (pWState == (WSBState *)NULL)
        return FALSE;
    else if (pWState == WSB_UNINIT_HANDLE)  {
        pWState = FlatSB_Internal_InitPwSB(hwnd);
        if (pWState == (WSBState *)NULL)
            return 0;
        else if (!SetWindowSubclass(hwnd, FlatSB_SubclassWndProc, 0,  (ULONG_PTR)pWState)) {
            DeleteObject(pWState->hbm_Bkg);
            DeleteObject(pWState->hbr_Bkg);
            LocalFree((HLOCAL)pWState);
            return 0;
        }

        //  In this case we don't want to (re)draw the scrollbar.
        fRedraw = FALSE;
    }

    if (pWState->fTracking)
        return FALSE;

    switch (index) {
    case WSB_PROP_CXVSCROLL:
        if ((int)newValue == pWState->metApp.cxVSBArrow)
            return TRUE;
        pWState->metApp.cxVSBArrow = (int)newValue;
        fResize = TRUE;
        break;

    case WSB_PROP_CXHSCROLL:
        if ((int)newValue == pWState->metApp.cxHSBArrow)
            return TRUE;
        pWState->metApp.cxHSBArrow = (int)newValue;
        fResize = TRUE;
        break;

    case WSB_PROP_CYVSCROLL:
        if ((int)newValue == pWState->metApp.cyVSBArrow)
            return TRUE;
        pWState->metApp.cyVSBArrow = (int)newValue;
        fResize = TRUE;
        break;

    case WSB_PROP_CYHSCROLL:
        if ((int)newValue == pWState->metApp.cyHSBArrow)
            return TRUE;
        pWState->metApp.cyHSBArrow = (int)newValue;
        fResize = TRUE;
        break;

    case WSB_PROP_CXHTHUMB:
        if ((int)newValue == pWState->metApp.cxHSBThumb)
            return TRUE;
        pWState->metApp.cxHSBThumb = (int)newValue;
        fResize = TRUE;
        break;

    case WSB_PROP_CYVTHUMB:
        if ((int)newValue == pWState->metApp.cyVSBThumb)
            return TRUE;
        pWState->metApp.cyVSBThumb = (int)newValue;
        fResize = TRUE;
        break;

    case WSB_PROP_VBKGCOLOR:
        if ((COLORREF)newValue == pWState->col_VSBBkg)
            return TRUE;
        pWState->col_VSBBkg = (COLORREF)newValue;
        fVert = TRUE;
        break;
    case WSB_PROP_HBKGCOLOR:
        if ((COLORREF)newValue == pWState->col_HSBBkg)
            return TRUE;
        pWState->col_HSBBkg = (COLORREF)newValue;
        break;

    case WSB_PROP_PALETTE:
        if ((HPALETTE)newValue == pWState->hPalette)
            return TRUE;
        pWState->hPalette = (HPALETTE)newValue;
        break;
    case WSB_PROP_VSTYLE:
        if ((int)newValue == pWState->vStyle)
            return TRUE;
        pWState->vStyle = (int)newValue;
        fVert = TRUE;
        break;
    case WSB_PROP_HSTYLE:
        if ((int)newValue == pWState->hStyle)
            return TRUE;
        pWState->hStyle = (int)newValue;
        break;
    case WSB_PROP_GUTTER:
        if ((int)newValue == pWState->sbGutter)
            return TRUE;
        pWState->sbGutter = (int)newValue;
        break;

    default:
        return FALSE;
    }

    if (fResize)    {
    // Always redraw after we change the size.
        CCInvalidateFrame(hwnd);
    } else if (fRedraw) {
        HDC hdc;
        int oldLoc = pWState->locMouse;
        int fSBActive = (fVert)?pWState->fVActive:pWState->fHActive;

        hdc = GetWindowDC(hwnd);
        FlatSB_Internal_DrawScrollBar(pWState, hdc, fVert, FALSE /* Not redraw*/);
        if (!fSBActive)
            pWState->locMouse = oldLoc;
        ReleaseDC(hwnd, hdc);
    }
    return TRUE;
}

//=-------------------------------------------------------------
//  FlatSB_Internal_DrawScrollBar()
//=-------------------------------------------------------------

void FlatSB_Internal_DrawScrollBar(WSBState * pWState, HDC hdc, BOOL fVert, BOOL fRedraw)
{
    int oldLoc = pWState->locMouse;

    FlatSB_Internal_CalcSBStuff(pWState, fVert);
    if ((!fRedraw) || oldLoc != pWState->locMouse)
        FlatSB_Internal_DrawSB2(pWState, hdc, fVert, fRedraw, oldLoc);
}

//=------------------------------------------------------------
//  FlatSB_Internal_IsSizeBox
//      It's still an incomplete mimic of SizeBoxWnd in user/winwhere.c
//=------------------------------------------------------------

BOOL FlatSB_Internal_IsSizeBox(HWND hwndStart)
{
    int style;
    HWND hwnd, hwndDesktop;
    int cxEdge, cyEdge;
    RECT rcChild, rcParent;

    ASSERT(hwndStart);
    hwnd = hwndStart;
    hwndDesktop = GetDesktopWindow();

    cxEdge = GetSystemMetrics(SM_CXEDGE);
    cyEdge = GetSystemMetrics(SM_CYEDGE);
    if (!GetWindowRect(hwnd, &rcChild))
        return FALSE;
    do  {
        style = GetWindowStyle(hwnd);
        if (TestSTYLE(style, WS_SIZEBOX))   {
            if (IsZoomed(hwnd))
                return FALSE;
            else    {
                POINT pt;

                GetClientRect(hwnd, &rcParent);

                pt.x = rcParent.right;
                pt.y = rcParent.bottom;

                ClientToScreen(hwnd, &pt);

                if (rcChild.right + cxEdge < pt.x)
                    return FALSE;
                if (rcChild.bottom + cyEdge < pt.y)
                    return FALSE;
                return TRUE;
            }
        } else  {
            hwnd = GetParent(hwnd);
        }
    }

    while ((hwnd) && (hwnd != hwndDesktop));
    return FALSE;
}