#include "ctlspriv.h"
#include "limits.h"
#include "image.h"          // for CreateColorBitmap

#if defined(MAINWIN)
#include <mainwin.h>
#endif

//#define TB_DEBUG
//#define FEATURE_DEBUG     // Ctrl+Shift force-enables rare features for debugging

typedef struct {

    // standard header information for each control
    CCONTROLINFO ci;

    HDC     hdc;            // current DC
    HBITMAP hbmBuffer;      // double buffer

    LONG    lLogMin;        // Logical minimum
    LONG    lLogMax;        // Logical maximum
    LONG    lLogPos;        // Logical position

    LONG    lSelStart;      // Logical selection start
    LONG    lSelEnd;        // Logical selection end

    int     iThumbWidth;    // Width of the thumb
    int     iThumbHeight;   // Height of the thumb

    int     iSizePhys;      // Size of where thumb lives
    RECT    rc;             // track bar rect.

    RECT    rcThumb;          // Rectangle we current thumb
    DWORD   dwDragPos;      // Logical position of mouse while dragging.
    int     dwDragOffset;   // how many pixels off the center did they click

    int     nTics;          // number of ticks.
    PDWORD  pTics;          // the tick marks.

    int     ticFreq;        // the frequency of ticks

    LONG     lPageSize;      // how much to thumb up and down.
    LONG     lLineSize;      // how muhc to scroll up and down on line up/down

    HWND     hwndToolTips;

    // these should probably be word or bytes
    UINT     wDirtyFlags;
    UINT     uTipSide;   // which side should the tip be on?
    UINT     Flags;          // Flags for our window
    UINT     Cmd;            // The command we're repeating.

    HTHEME   hTheme;
    BOOL     bThumbHot;
    HIMC    hPrevImc;       // previous input context handle
    HWND        hwndBuddyLeft;
    HWND        hwndBuddyRight;

} TRACKBAR, *PTRACKBAR;

// Trackbar flags

#define TBF_NOTHUMB     0x0001  // No thumb because not wide enough.
#define TBF_SELECTION   0x0002  // a selection has been established (draw the range)

#define MIN_THUMB_HEIGHT (2 * g_cxEdge)

/*
        useful constants.
*/

#define REPEATTIME      500     // mouse auto repeat 1/2 of a second
#define TIMER_ID        1

/*
        Function Prototypes
*/

void   DoTrack(PTRACKBAR, int, DWORD);
WORD   WTrackType(PTRACKBAR, LONG);
void   TBTrackInit(PTRACKBAR, LPARAM);
void   TBTrackEnd(PTRACKBAR);
void   TBTrack(PTRACKBAR, LPARAM);
void   DrawThumb(PTRACKBAR, LPRECT, BOOL);

HBRUSH SelectColorObjects(PTRACKBAR, BOOL);
void   SetTBCaretPos(PTRACKBAR);

#define TICKHEIGHT 3
#define BORDERSIZE 2

#define ISVERT(tb) (tb->ci.style & TBS_VERT)

#define TBC_TICS        0x1
#define TBC_THUMB       0x2
#define TBC_ALL         0xF


// this is called internally when the trackbar has
// changed and we need to update the double buffer bitmap
// we only set a flag.  we do the actual draw
// during WM_PAINT.  This prevents wasted efforts drawing.
#define TBChanged(ptb, wFlags) ((ptb)->wDirtyFlags |= (wFlags))

//
// Function Prototypes
//
LPARAM CALLBACK TrackBarWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void FlushChanges(PTRACKBAR tb);

//--------------------------------------------------------------------------;
//
//  LONG MulDiv32(a,b,c)    = (a * b + c/2) / c
//
//--------------------------------------------------------------------------;

#ifdef WIN32

#define MulDiv32 MulDiv     // use KERNEL32 version (it rounds)

#else // WIN32

#define ASM66 _asm _emit 0x66 _asm
#define DB    _asm _emit

#define EAX_TO_DXAX \
    DB      0x66    \
    DB      0x0F    \
    DB      0xA4    \
    DB      0xC2    \
    DB      0x10

#pragma warning(disable:4035 4704)

static LONG MulDiv32(LONG a,LONG b,LONG c)
{
    ASM66   mov     ax,word ptr c   //  mov  eax, c
    ASM66   sar     ax,1            //  sar  eax,1
    ASM66   cwd                     //  cdq
    ASM66   mov     bx,ax           //  mov  ebx,eax
    ASM66   mov     cx,dx           //  mov  ecx,edx
    ASM66   mov     ax,word ptr a   //  mov  eax, a
    ASM66   imul    word ptr b      //  imul b
    ASM66   add     ax,bx           //  add  eax,ebx
    ASM66   adc     dx,cx           //  adc  edx,ecx
    ASM66   idiv    word ptr c      //  idiv c
    EAX_TO_DXAX

} // MulDiv32()

#pragma warning(default:4035 4704)

#endif // WIN32

//--------------------------------------------------------------------------;
//--------------------------------------------------------------------------;

//
//  convert a logical scroll-bar position to a physical pixel position
//
int TBLogToPhys(PTRACKBAR tb, DWORD dwPos)
{
    int x;
    x = tb->rc.left;
    if (tb->lLogMax == tb->lLogMin)
        return x;

    return (int)MulDiv32(dwPos - tb->lLogMin, tb->iSizePhys - 1,
                          tb->lLogMax - tb->lLogMin) + x;
}

LONG TBPhysToLog(PTRACKBAR ptb, int iPos)
{
    int min, max, x;
    min = ptb->rc.left;
    max = ptb->rc.right;
    x = ptb->rc.left;

    if (ptb->iSizePhys <= 1)
        return ptb->lLogMin;

    if (iPos <= min)
        return ptb->lLogMin;

    if (iPos >= max)
        return ptb->lLogMax;

    return MulDiv32(iPos - x, ptb->lLogMax - ptb->lLogMin,
                    ptb->iSizePhys - 1) + ptb->lLogMin;
}



#pragma code_seg(CODESEG_INIT)
/*
 * Initialize the trackbar code
 */

BOOL InitTrackBar(HINSTANCE hInstance)
{
    WNDCLASS wc;

    // See if we must register a window class
    wc.lpfnWndProc = TrackBarWndProc;
    wc.lpszClassName = s_szSTrackBarClass;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = NULL;
    wc.lpszMenuName = NULL;
    wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
    wc.hInstance = hInstance;
    wc.style = CS_GLOBALCLASS;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof(PTRACKBAR);

    if (!RegisterClass(&wc) && !GetClassInfo(hInstance, s_szSTrackBarClass, &wc))
        return FALSE;
    return TRUE;
}
#pragma code_seg()



/* 
 * To add vertical capabilities, I'm using a virtual coordinate
 * system.  the ptb->rcThumb and ptb->rc are in the virtual space (which
 * is just a horizontal trackbar).  Draw routines use PatRect and
 * TBBitBlt which switch to the real coordinate system as needed.
 *
 * The one gotcha is that the Thumb Bitmap has the pressed bitmap
 * to the real right, and the masks to the real right again for both
 * the vertical and horizontal Thumbs.  So those cases are hardcoded.
 * Do a search for ISVERT to find these dependancies.
 *                              -Chee
 */

/*
  FlipRect Function is moved to cutils.c as  other controls  were also using it.
  -Arul

*/

void TBFlipPoint(PTRACKBAR ptb, LPPOINT lppt)
{
    if (ISVERT(ptb)) {
        FlipPoint(lppt);
    }
}


/* added trackbar variable to do auto verticalization */
void PatRect(HDC hdc,int x,int y,int dx,int dy, PTRACKBAR ptb)
{
    RECT    rc;

    rc.left   = x;
    rc.top    = y;
    rc.right  = x + dx;
    rc.bottom = y + dy;

    if (ISVERT(ptb))
        FlipRect(&rc);
    ExtTextOut(hdc,0,0,ETO_OPAQUE,&rc,NULL,0,NULL);
}

#define TBInvalidateRect(hwnd, prc, bErase, ptb) VertInvalidateRect(hwnd, prc, bErase, ISVERT(ptb))
void VertInvalidateRect(HWND hwnd, LPRECT qrc, BOOL b, BOOL fVert)
{
    RECT rc;
    rc = *qrc;
    if (fVert) FlipRect(&rc);
    InvalidateRect(hwnd, &rc, b);
}

#define TBDrawEdge(hdc, prc, uType, grfFlags, ptb, hTheme, iPartId, iStateId) VertDrawEdge(hdc, prc, uType, grfFlags, ISVERT(ptb), hTheme, iPartId, iStateId)

// VertDrawEdge is theme aware (RENDERS)
void VertDrawEdge(HDC hdc, LPRECT qrc, UINT edgeType, UINT grfFlags,
                               BOOL fVert, HTHEME hTheme, int iPartId, int iStateId)
{
    RECT temprc;
    UINT uFlags = grfFlags;

    temprc = *qrc;
    if (fVert) {
        FlipRect(&temprc);

        if (!(uFlags & BF_DIAGONAL)) {
            if (grfFlags & BF_TOP) uFlags |= BF_LEFT;
            else uFlags &= ~BF_LEFT;

            if (grfFlags & BF_LEFT) uFlags |= BF_TOP;
            else uFlags &= ~BF_TOP;

            if (grfFlags & BF_BOTTOM) uFlags |= BF_RIGHT;
            else uFlags &= ~BF_RIGHT;

            if (grfFlags & BF_RIGHT) uFlags |= BF_BOTTOM;
            else uFlags &= ~BF_BOTTOM;
        } else {
            if ((grfFlags & (BF_BOTTOM | BF_RIGHT)) == (BF_BOTTOM | BF_RIGHT)) {
                uFlags = BF_TOP | BF_LEFT;

                if (edgeType == EDGE_RAISED) {
                    edgeType = EDGE_SUNKEN;
                } else {
                    edgeType = EDGE_RAISED;
                }


                uFlags |= grfFlags & (~BF_RECT);
                uFlags ^= BF_SOFT;
            }
        }
    }

    if (hTheme)
    {
        DrawThemeBackground(hTheme, hdc, iPartId, iStateId, &temprc, 0);
    }
    else
    {
        DrawEdge(hdc, &temprc, edgeType, uFlags);
    }
}

void TBBitBlt(HDC hdc1, int x1, int y1, int w, int h,
                          HDC hdc2, int x2, int y2, DWORD rop, PTRACKBAR ptb)
{
    if (ISVERT(ptb))
        BitBlt(hdc1, y1, x1, h, w, hdc2, x2, y2, rop);
    else
        BitBlt(hdc1, x1, y1, w, h, hdc2, x2, y2, rop);
}

#define TBPatBlt(hdc1, x1, y1, w, h, rop, ptb) VertPatBlt(hdc1, x1, y1, w, h, rop, ISVERT(ptb), NULL, 0, 0)

// VertPatBlt is theme aware (RENDERS)
void VertPatBlt(HDC hdc1, int x1, int y1, int w, int h,
                          DWORD rop, BOOL fVert, HTHEME hTheme, int iPartId, int iStateId)
{
    if (hTheme)
    {
        RECT rc;
        if (fVert)
            SetRect(&rc, y1, x1, h, w);
        else
            SetRect(&rc, x1, y1, w, h);

        DrawThemeBackground(hTheme, hdc1, iPartId, iStateId, &rc, 0);
    }
    else
    {
        if (fVert)
            PatBlt(hdc1, y1, x1, h, w, rop);
        else
            PatBlt(hdc1, x1, y1, w, h, rop);
    }
}

// DrawTic is theme aware (RENDERS)
void DrawTic(PTRACKBAR ptb, int x, int y, int dir)
{
    if (dir == -1) y -= TICKHEIGHT;

    if (ptb->hTheme)
    {
        COLORREF cr = 0;
        GetThemeColor(ptb->hTheme, ISVERT(ptb) ? TKP_TICSVERT : TKP_TICS, TSS_NORMAL, TMT_COLOR, &cr);
        SetBkColor(ptb->hdc, cr);
    }
    else
    {
        SetBkColor(ptb->hdc, g_clrBtnText);
    }

    PatRect(ptb->hdc,x,y,1,TICKHEIGHT, ptb);
}

// dir = direction multiplier (drawing up or down)
// yTic = where (vertically) to draw the line of tics
void DrawTicsOneLine(PTRACKBAR ptb, int dir, int yTic)
{
    PDWORD pTics;
    int    iPos;
    int    i;

    DrawTic(ptb, ptb->rc.left, yTic, dir);             // first
    DrawTic(ptb, ptb->rc.left, yTic+ (dir * 1), dir);
    DrawTic(ptb, ptb->rc.right-1, yTic, dir);            // last
    DrawTic(ptb, ptb->rc.right-1, yTic+ (dir * 1), dir);

    // those inbetween
    pTics = ptb->pTics;
    if (ptb->ticFreq && pTics) {
        for (i = 0; i < ptb->nTics; ++i) {
            if (((i+1) % ptb->ticFreq) == 0) {
                iPos = TBLogToPhys(ptb,pTics[i]);
                DrawTic(ptb, iPos, yTic, dir);
            }
        }
    }

    // draw the selection range (triangles)

    if ((ptb->Flags & TBF_SELECTION) &&
        (ptb->lSelStart <= ptb->lSelEnd) && (ptb->lSelEnd >= ptb->lLogMin)) {

        SetBkColor(ptb->hdc, g_clrBtnText);

        iPos = TBLogToPhys(ptb,ptb->lSelStart);

        for (i = 0; i < TICKHEIGHT; i++)
            PatRect(ptb->hdc,iPos-i,yTic+(dir==1 ? i : -TICKHEIGHT),
                    1,TICKHEIGHT-i, ptb);

        iPos = TBLogToPhys(ptb,ptb->lSelEnd);

        for (i = 0; i < TICKHEIGHT; i++)
            PatRect(ptb->hdc,iPos+i,yTic+(dir==1 ? i : -TICKHEIGHT),
                    1,TICKHEIGHT-i, ptb);
    }

}

/* DrawTics() */
/* There is always a tick at the beginning and end of the bar, but you can */
/* add some more of your own with a TBM_SETTIC message.  This draws them.  */
/* They are kept in an array whose handle is a window word.  The first     */
/* element is the number of extra ticks, and then the positions.           */

void DrawTics(PTRACKBAR ptb)
{
    // do they even want this?
    if (ptb->ci.style & TBS_NOTICKS) return;

    if ((ptb->ci.style & TBS_BOTH) || !(ptb->ci.style & TBS_TOP)) {
        DrawTicsOneLine(ptb, 1, ptb->rc.bottom + 1);
    }

    if ((ptb->ci.style & (TBS_BOTH | TBS_TOP))) {
        DrawTicsOneLine(ptb, -1, ptb->rc.top - 1);
    }
}

void GetChannelRect(PTRACKBAR ptb, LPRECT lprc)
{
        int iwidth, iheight;

        if (!lprc)
            return;

        lprc->left = ptb->rc.left - ptb->iThumbWidth / 2;
        iwidth = ptb->iSizePhys + ptb->iThumbWidth - 1;
        lprc->right = lprc->left + iwidth;

        if (ptb->ci.style & TBS_ENABLESELRANGE) {
                iheight =  ptb->iThumbHeight / 4 * 3; // this is Scrollheight
        } else {
                iheight = 4;
        }

        lprc->top = (ptb->rc.top + ptb->rc.bottom - iheight) /2;
        if (!(ptb->ci.style & TBS_BOTH))
            if (ptb->ci.style & TBS_TOP) lprc->top++;
            else lprc->top--;

        lprc->bottom = lprc->top + iheight;

}

/* This draws the track bar itself */

// DrawChannel is theme aware (RENDERS)
void DrawChannel(PTRACKBAR ptb, LPRECT lprc)
{
    TBDrawEdge(ptb->hdc, lprc, EDGE_SUNKEN, BF_RECT,ptb, ptb->hTheme, ISVERT(ptb) ? TKP_TRACKVERT : TKP_TRACK, TRS_NORMAL);

    if (!ptb->hTheme)
    {
        SetBkColor(ptb->hdc, g_clrBtnHighlight);
        // Fill the center
        PatRect(ptb->hdc, lprc->left+2, lprc->top+2, (lprc->right-lprc->left)-4,
                (lprc->bottom-lprc->top)-4, ptb);


        // now highlight the selection range
        if ((ptb->Flags & TBF_SELECTION) &&
            (ptb->lSelStart <= ptb->lSelEnd) && (ptb->lSelEnd > ptb->lLogMin)) {
                int iStart, iEnd;

                iStart = TBLogToPhys(ptb,ptb->lSelStart);
                iEnd   = TBLogToPhys(ptb,ptb->lSelEnd);

                if (iStart + 2 <= iEnd) {
                        SetBkColor(ptb->hdc, g_clrHighlight);
                        PatRect(ptb->hdc, iStart+1, lprc->top+3,
                                iEnd-iStart-1, (lprc->bottom-lprc->top)-6, ptb);
                }
        }
    }
}

// DrawThumb is theme aware (RENDERS)
void DrawThumb(PTRACKBAR ptb, LPRECT lprc, BOOL fSelected)
{

    // iDpt direction from middle to point of thumb
    // a negative value inverts things.
    // this allows one code path..
    int iDpt = 0;
    int i = 0;  // size of point triangle
    int iYpt = 0;       // vertical location of tip;
    int iXmiddle = 0;
    int icount;  // just a loop counter
    UINT uEdgeFlags = 0;
    RECT rcThumb = *lprc;

    if (ptb->Flags & TBF_NOTHUMB ||
        ptb->ci.style & TBS_NOTHUMB)            // If no thumb, just leave.
        return;

    ASSERT(ptb->iThumbHeight >= MIN_THUMB_HEIGHT);
    ASSERT(ptb->iThumbWidth > 1);

    if (!ptb->hTheme)
    {
        // draw the rectangle part
        if (!(ptb->ci.style & TBS_BOTH))  {
            int iMiddle;
            // do -3  because wThumb is odd (triangles ya know)
            // and because draw rects draw inside the rects passed.
            // actually should be (width-1)/2-1, but this is the same...

            i = (ptb->iThumbWidth - 3) / 2;
            iMiddle = ptb->iThumbHeight / 2 + rcThumb.top;

            //draw the rectangle part
            if (ptb->ci.style & TBS_TOP) {
                iMiddle++; //correction because drawing routines
                iDpt = -1;
                rcThumb.top += (i+1);
                uEdgeFlags = BF_SOFT | BF_LEFT | BF_RIGHT | BF_BOTTOM;
            } else {
                iDpt = 1;
                rcThumb.bottom -= (i+1);
                // draw on the inside, not on the bottom and rt edge
                uEdgeFlags = BF_SOFT | BF_LEFT | BF_RIGHT | BF_TOP;
            }

            iYpt = iMiddle + (iDpt * (ptb->iThumbHeight / 2));
            iXmiddle = rcThumb.left + i;
        }  else {
            uEdgeFlags = BF_SOFT | BF_RECT;
        }

        // fill in the center
        if (fSelected || !IsWindowEnabled(ptb->ci.hwnd)) {
            HBRUSH hbrTemp;
            // draw the dithered insides;
            hbrTemp = SelectObject(ptb->hdc, g_hbrMonoDither);
            if (hbrTemp) {
                SetTextColor(ptb->hdc, g_clrBtnHighlight);
                SetBkColor(ptb->hdc, g_clrBtnFace);
                TBPatBlt(ptb->hdc, rcThumb.left +2 , rcThumb.top,
                         rcThumb.right-rcThumb.left -4, rcThumb.bottom-rcThumb.top,
                         PATCOPY,ptb);

                if (!(ptb->ci.style & TBS_BOTH)) {

                    for (icount = 1;  icount <= i;  icount++) {
                        TBPatBlt(ptb->hdc, iXmiddle-icount+1,
                             iYpt - (iDpt*icount),
                             icount*2, 1, PATCOPY, ptb);
                    }
                }
                SelectObject(ptb->hdc, hbrTemp);
            }

        } else {


            SetBkColor(ptb->hdc, g_clrBtnFace);
            PatRect(ptb->hdc, rcThumb.left+2, rcThumb.top,
                    rcThumb.right-rcThumb.left-4, rcThumb.bottom-rcThumb.top, ptb);

            if (!(ptb->ci.style & TBS_BOTH)) {
                for (icount = 1; icount <= i; icount++) {
                    PatRect(ptb->hdc, iXmiddle-icount+1,
                            iYpt - (iDpt*icount),
                            icount*2, 1, ptb);
                }
            }

        }
    }

    if (ptb->hTheme)
    {
        int iPartId;

        // States in overriding order
        int iStateId = TUS_NORMAL;

        if (ISVERT(ptb))
        {
            if (ptb->ci.style & TBS_BOTH)
            {
                iPartId = TKP_THUMBVERT;
            }
            else if (ptb->ci.style & TBS_LEFT)
            {
                iPartId = TKP_THUMBLEFT;
            }
            else
            {
                iPartId = TKP_THUMBRIGHT;
            }
        }
        else
        {
            if (ptb->ci.style & TBS_BOTH)
            {
                iPartId = TKP_THUMB;
            }
            else if (ptb->ci.style & TBS_TOP)
            {
                iPartId = TKP_THUMBTOP;
            }
            else
            {
                iPartId = TKP_THUMBBOTTOM;
            }
        }
#ifdef DEBUG
        if (!IsThemePartDefined(ptb->hTheme, iPartId, 0))
            DebugMsg(DM_WARNING, TEXT("WARNING: Trackbar_Drawthumb: Theme Part not defined: %d\n"), iPartId);
#endif

        if (ptb->ci.hwnd == GetFocus() && !(CCGetUIState(&(ptb->ci)) & UISF_HIDEFOCUS))
            iStateId = TUS_FOCUSED;

        if (ptb->bThumbHot)
            iStateId = TUS_HOT;

        if (fSelected)
            iStateId = TUS_PRESSED;

        if (ptb->ci.style & WS_DISABLED)
            iStateId = TUS_DISABLED;

        // Thumb and ThumbVert parts share the same enum values
        TBDrawEdge(ptb->hdc, &rcThumb, EDGE_RAISED, uEdgeFlags, ptb, ptb->hTheme, iPartId, iStateId); 
    }
    else
    {
        TBDrawEdge(ptb->hdc, &rcThumb, EDGE_RAISED, uEdgeFlags, ptb, NULL, 0, 0);
    }

    if (!ptb->hTheme)
    {
        //now draw the point
        if (!(ptb->ci.style & TBS_BOTH)) {
            UINT uEdgeFlags2;

            // uEdgeFlags is now used to switch between top and bottom.
            // we'll or it in with the diagonal and left/right flags below
            if (ptb->ci.style & TBS_TOP) {
                rcThumb.bottom = rcThumb.top + 1;
                rcThumb.top = rcThumb.bottom - (i + 2);
                uEdgeFlags = BF_TOP | BF_RIGHT | BF_DIAGONAL | BF_SOFT;
                uEdgeFlags2 = BF_BOTTOM | BF_RIGHT | BF_DIAGONAL;
            } else {
                rcThumb.top = rcThumb.bottom - 1;
                rcThumb.bottom = rcThumb.top + (i + 2);

                uEdgeFlags = BF_TOP | BF_LEFT | BF_DIAGONAL | BF_SOFT;
                uEdgeFlags2 = BF_BOTTOM | BF_LEFT | BF_DIAGONAL;
            }

            rcThumb.right = rcThumb.left + (i + 2);
            // do the left side first
            TBDrawEdge(ptb->hdc, &rcThumb, EDGE_RAISED, uEdgeFlags , ptb, NULL, 0, 0);
            // then do th right side
            OffsetRect(&rcThumb, i + 1, 0);
            TBDrawEdge(ptb->hdc, &rcThumb, EDGE_RAISED, uEdgeFlags2 , ptb, NULL, 0, 0);
        }
    }
}
void TBInvalidateAll(PTRACKBAR ptb)
{
    if (ptb) {
        TBChanged(ptb, TBC_ALL);
        InvalidateRect(ptb->ci.hwnd, NULL, FALSE);
    }
}

void MoveThumb(PTRACKBAR ptb, LONG lPos)
{
    long    lOld = ptb->lLogPos;

    TBInvalidateRect(ptb->ci.hwnd, &ptb->rcThumb, FALSE,ptb);

    ptb->lLogPos  = BOUND(lPos,ptb->lLogMin,ptb->lLogMax);
    ptb->rcThumb.left   = TBLogToPhys(ptb, ptb->lLogPos) - ptb->iThumbWidth / 2;
    ptb->rcThumb.right  = ptb->rcThumb.left + ptb->iThumbWidth;

    TBInvalidateRect(ptb->ci.hwnd, &ptb->rcThumb, FALSE,ptb);
    TBChanged(ptb, TBC_THUMB);
    UpdateWindow(ptb->ci.hwnd);

    if (lOld != ptb->lLogPos)
        NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, ptb->ci.hwnd, OBJID_CLIENT, 0);
}


void DrawFocus(PTRACKBAR ptb, HBRUSH hbrBackground)
{
    RECT rc;
    if (ptb->ci.hwnd == GetFocus() && 
        !(CCGetUIState(&(ptb->ci)) & UISF_HIDEFOCUS))
    {
        SetBkColor(ptb->hdc, g_clrBtnHighlight);
        GetClientRect(ptb->ci.hwnd, &rc);

        // Successive calls to DrawFocusRect will invert it thereby erasing it.
        // To avoid this, whenever we process WM_PAINT, we erase the focus rect ourselves
        // before we draw it below.
        if (hbrBackground)
            FrameRect(ptb->hdc, &rc, hbrBackground);

        DrawFocusRect(ptb->hdc, &rc);
    }
}

void DoAutoTics(PTRACKBAR ptb)
{
    LONG *pl;
    LONG l;

    if (!(ptb->ci.style & TBS_AUTOTICKS))
        return;

    if (ptb->pTics)
        LocalFree((HLOCAL)ptb->pTics);

    ptb->nTics = (int)(ptb->lLogMax - ptb->lLogMin - 1);

    if (ptb->nTics > 0)
        ptb->pTics = (DWORD *)LocalAlloc(LPTR, sizeof(DWORD) * ptb->nTics);
    else
        ptb->pTics = NULL;

    if (!ptb->pTics) {
        ptb->nTics = 0;
        return;
    }

    for (pl = (LONG *)ptb->pTics, l = ptb->lLogMin + 1; l < ptb->lLogMax; l++)
        *pl++ = l;
}


void ValidateThumbHeight(PTRACKBAR ptb)
{
    if (ptb->iThumbHeight < MIN_THUMB_HEIGHT)
        ptb->iThumbHeight = MIN_THUMB_HEIGHT;

    ptb->iThumbWidth = ptb->iThumbHeight / 2;
    ptb->iThumbWidth |= 0x01;  // make sure it's odd at at least 3

    if (ptb->ci.style & TBS_ENABLESELRANGE) {
        if (ptb->ci.style & TBS_FIXEDLENGTH) {
            // half of 9/10
            ptb->iThumbWidth = (ptb->iThumbHeight * 9) / 20;
            ptb->iThumbWidth |= 0x01;
        } else {
            ptb->iThumbHeight += (ptb->iThumbWidth * 2) / 9;
        }
    }
}

void TBPositionBuddies(PTRACKBAR ptb)
{
    POINT pt;
    HWND hwndParent;
    RECT rcBuddy;
    RECT rcClient;
    RECT rcChannel;

    int yMid;

    GetChannelRect(ptb, &rcChannel);
    yMid = (rcChannel.top + rcChannel.bottom) / 2;

    GetClientRect(ptb->ci.hwnd, &rcClient);
    if (ISVERT(ptb))
        FlipRect(&rcClient);


    if (ptb->hwndBuddyLeft) {
        GetClientRect(ptb->hwndBuddyLeft, &rcBuddy);
        if (ISVERT(ptb))
            FlipRect(&rcBuddy);

        pt.y = yMid - ((RECTHEIGHT(rcBuddy))/2);
        pt.x = rcClient.left - RECTWIDTH(rcBuddy) - g_cxEdge;

        // x and y are now in trackbar's coordinates.
        // convert them to the parent of the buddy's coordinates
        hwndParent = GetParent(ptb->hwndBuddyLeft);
        TBFlipPoint(ptb, &pt);
        MapWindowPoints(ptb->ci.hwnd, hwndParent, &pt, 1);
        SetWindowPos(ptb->hwndBuddyLeft, NULL, pt.x, pt.y, 0, 0, SWP_NOSIZE |SWP_NOZORDER | SWP_NOACTIVATE);
    }

    if (ptb->hwndBuddyRight) {
        GetClientRect(ptb->hwndBuddyRight, &rcBuddy);
        if (ISVERT(ptb))
            FlipRect(&rcBuddy);

        pt.y = yMid - ((RECTHEIGHT(rcBuddy))/2);
        pt.x = rcClient.right + g_cxEdge;

        // x and y are now in trackbar's coordinates.
        // convert them to the parent of the buddy's coordinates
        hwndParent = GetParent(ptb->hwndBuddyRight);
        TBFlipPoint(ptb, &pt);
        MapWindowPoints(ptb->ci.hwnd, hwndParent, &pt, 1);
        SetWindowPos(ptb->hwndBuddyRight, NULL, pt.x, pt.y, 0, 0, SWP_NOSIZE |SWP_NOZORDER | SWP_NOACTIVATE);
    }

}

void TBNukeBuffer(PTRACKBAR ptb)
{
    if (ptb->hbmBuffer) {
        DeleteObject(ptb->hbmBuffer);
        ptb->hbmBuffer = NULL;
        TBChanged(ptb, TBC_ALL);            // Must do a full repaint
    }
}

void TBResize(PTRACKBAR ptb)
{
    GetClientRect(ptb->ci.hwnd, &ptb->rc);

    if (ISVERT(ptb))
        FlipRect(&ptb->rc);


    if (!(ptb->ci.style & TBS_FIXEDLENGTH)) {
        ptb->iThumbHeight = (g_cyHScroll * 4) / 3;

        ValidateThumbHeight(ptb);
        if ((ptb->iThumbHeight > MIN_THUMB_HEIGHT) && (ptb->rc.bottom < (int)ptb->iThumbHeight)) {
            ptb->iThumbHeight = ptb->rc.bottom - 3*g_cyEdge; // top, bottom, and tic
            if (ptb->ci.style & TBS_ENABLESELRANGE)
                ptb->iThumbHeight = (ptb->iThumbHeight * 3 / 4);
            ValidateThumbHeight(ptb);
        }
    } else {
        ValidateThumbHeight(ptb);
    }


    if (ptb->ci.style & (TBS_BOTH | TBS_TOP) && !(ptb->ci.style & TBS_NOTICKS))
        ptb->rc.top += TICKHEIGHT + BORDERSIZE + 3;
    ptb->rc.top   += BORDERSIZE;
    ptb->rc.bottom  = ptb->rc.top + ptb->iThumbHeight;
    ptb->rc.left   += (ptb->iThumbWidth + BORDERSIZE);
    ptb->rc.right  -= (ptb->iThumbWidth + BORDERSIZE);

    ptb->rcThumb.top = ptb->rc.top;
    ptb->rcThumb.bottom = ptb->rc.bottom;

    // Figure out how much room we have to move the thumb in
    ptb->iSizePhys = ptb->rc.right - ptb->rc.left;

    // Elevator isn't there if there's no room.
    if (ptb->iSizePhys == 0) {
        // Lost our thumb.
        ptb->Flags |= TBF_NOTHUMB;
        ptb->iSizePhys = 1;
    } else {
        // Ah. We have a thumb.
        ptb->Flags &= ~TBF_NOTHUMB;
    }

    TBNukeBuffer(ptb);

    MoveThumb(ptb, ptb->lLogPos);
    TBInvalidateAll(ptb);

    TBPositionBuddies(ptb);
}

LRESULT TrackOnCreate(HWND hwnd, LPCREATESTRUCT lpCreate)
{
    PTRACKBAR       ptb;

#ifdef MAINWIN
    DWORD exStyle = WS_EX_MW_UNMANAGED_WINDOW;
#else
    DWORD exStyle = 0;
#endif

    InitDitherBrush();
    InitGlobalColors();

    // Get us our window structure.
    ptb = (PTRACKBAR)LocalAlloc(LPTR, sizeof(TRACKBAR));
    if (!ptb)
        return -1;

    SetWindowPtr(hwnd, 0, ptb);
    CIInitialize(&ptb->ci, hwnd, lpCreate);

    ptb->Cmd = (UINT)-1;
    ptb->lLogMax = 100;
    ptb->ticFreq = 1;
    // ptb->hbmBuffer = 0;
    ptb->lPageSize = -1;
    ptb->lLineSize = 1;
    // initial size;
    ptb->iThumbHeight = (g_cyHScroll * 4) / 3;
    if (g_fDBCSInputEnabled)
        ptb->hPrevImc = ImmAssociateContext(hwnd, 0L);

    if (ISVERT(ptb)) 
    {
        if (ptb->ci.style & TBS_TOP) 
        {
            ptb->uTipSide = TBTS_RIGHT;
        } 
        else 
        {
            ptb->uTipSide = TBTS_LEFT;
        }
    } 
    else 
    {
        if (ptb->ci.style & TBS_TOP) 
        {
            ptb->uTipSide = TBTS_BOTTOM;
        } 
        else 
        {
            ptb->uTipSide = TBTS_TOP;
        }
    }

    if (ptb->ci.style & TBS_TOOLTIPS) 
    {
        ptb->hwndToolTips = CreateWindowEx(exStyle, 
                                              c_szSToolTipsClass, TEXT(""),
                                              WS_POPUP,
                                              CW_USEDEFAULT, CW_USEDEFAULT,
                                              CW_USEDEFAULT, CW_USEDEFAULT,
                                              ptb->ci.hwnd, NULL, HINST_THISDLL,
                                              NULL);
        if (ptb->hwndToolTips)
        {
            TOOLINFO ti;
            // don't bother setting the rect because we'll do it below
            // in FlushToolTipsMgr;
            ti.cbSize = sizeof(ti);
            ti.uFlags = TTF_TRACK | TTF_IDISHWND | TTF_CENTERTIP;
            ti.hwnd = ptb->ci.hwnd;
            ti.uId = (UINT_PTR)ptb->ci.hwnd;
            ti.lpszText = LPSTR_TEXTCALLBACK;
            ti.rect.left = ti.rect.top = ti.rect.bottom = ti.rect.right = 0; // update this on size
            SendMessage(ptb->hwndToolTips, TTM_ADDTOOL, 0,
                        (LPARAM)(LPTOOLINFO)&ti);
        } 
        else
            ptb->ci.style &= ~(TBS_TOOLTIPS);
    }

    // Initialize themes. No themese for owner drawn tab controls
    ptb->hTheme = OpenThemeData(ptb->ci.hwnd, L"TrackBar");
    ptb->bThumbHot = FALSE;

    TBResize(ptb);

    return 0;
}

void TrackOnNotify(PTRACKBAR ptb, LPNMHDR lpnm)
{
    if (lpnm->hwndFrom == ptb->hwndToolTips) 
    {
        switch (lpnm->code) 
        {
        case TTN_NEEDTEXT:
#define lpttt ((LPTOOLTIPTEXT)lpnm)
            wsprintf(lpttt->szText, TEXT("%d"), ptb->lLogPos);

        default:
            SendNotifyEx(ptb->ci.hwndParent, (HWND)-1,
                         lpnm->code, lpnm, ptb->ci.bUnicode);
            break;
        }
    }
}

HWND TBSetBuddy(PTRACKBAR ptb, BOOL fLeft, HWND hwndBuddy)
{
    HWND hwndOldBuddy;

    if (fLeft) 
    {
        hwndOldBuddy = ptb->hwndBuddyLeft;
        ptb->hwndBuddyLeft = hwndBuddy;
    } 
    else 
    {
        hwndOldBuddy = ptb->hwndBuddyRight;
        ptb->hwndBuddyRight = hwndBuddy;
    }

    TBResize(ptb);

    return hwndOldBuddy;
}

// Theme helper
void TBRedrawThumb(PTRACKBAR ptb)
{
    // Update display
    TBInvalidateRect(ptb->ci.hwnd, &ptb->rcThumb, FALSE, ptb);
    TBChanged(ptb, TBC_THUMB); 
    UpdateWindow(ptb->ci.hwnd);
}

// TrackBarWndProc is theme aware
LPARAM CALLBACK TrackBarWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
        PTRACKBAR       ptb;
        PAINTSTRUCT     ps;
        HLOCAL          h;

        ptb = GetWindowPtr(hwnd, 0);
        if (!ptb) {
            if (uMsg == WM_CREATE)
                return TrackOnCreate(hwnd, (LPCREATESTRUCT)lParam);

            goto DoDefault;
        }

        // Track hot state for themes
        if ((uMsg >= WM_MOUSEFIRST) && (uMsg <= WM_MOUSELAST))
        {
            TRACKMOUSEEVENT tme;

            tme.cbSize = sizeof(tme);
            tme.hwndTrack = hwnd;
            tme.dwFlags = TME_LEAVE;

            TrackMouseEvent(&tme);
        }

        switch (uMsg) {

        case WM_MOUSELEAVE:
            if (ptb->hTheme)
            {
                // Make sure thumb hot is turned off
                if (ptb->bThumbHot)
                {
                    ptb->bThumbHot = FALSE;
                    TBRedrawThumb(ptb);
                }
            }
            break;

        // If color depth changes, the old buffer is no longer any good
        case WM_DISPLAYCHANGE:
            TBNukeBuffer(ptb);
            break;

        case WM_WININICHANGE:

            InitGlobalMetrics(wParam);
            // fall through to WM_SIZE

        case WM_SIZE:
            TBResize(ptb);
            break;

        case WM_SYSCOLORCHANGE:
            InitGlobalColors();
            TBInvalidateAll(ptb);
            break;

        case WM_NOTIFYFORMAT:
            return CIHandleNotifyFormat(&ptb->ci,lParam);

        case WM_NOTIFY:
            TrackOnNotify(ptb, (LPNMHDR)lParam);
            break;

        case WM_DESTROY:
            TerminateDitherBrush();
            if (ptb) 
            {
                if (g_fDBCSInputEnabled)
                    ImmAssociateContext(hwnd, ptb->hPrevImc);

                if ((ptb->ci.style & TBS_TOOLTIPS) && IsWindow(ptb->hwndToolTips)) 
                {
                    DestroyWindow (ptb->hwndToolTips);
                }

                TBNukeBuffer(ptb);

                if (ptb->pTics)
                    LocalFree((HLOCAL)ptb->pTics);

                // Close theme
                if (ptb->hTheme)
                    CloseThemeData(ptb->hTheme);

                LocalFree((HLOCAL)ptb);
                SetWindowPtr(hwnd, 0, 0);

            }
            break;

        case WM_KILLFOCUS:
            // Reset wheel scroll amount
            gcWheelDelta = 0;
            // fall-through

        case WM_SETFOCUS:
            ASSERT(gcWheelDelta == 0);
            if (ptb)
                TBInvalidateAll(ptb);
            break;

        case WM_ENABLE:
            if (wParam) {
                ptb->ci.style &= ~WS_DISABLED;
            } else {
                ptb->ci.style |= WS_DISABLED;
            }
            // Redraw all if themes are enabled since more is configurable
            TBChanged(ptb, (ptb->hTheme) ? TBC_ALL : TBC_THUMB);
            InvalidateRect(hwnd, NULL, FALSE);
            break;

        case WM_PRINTCLIENT:
        case WM_PAINT: {
            RECT rc;
            HBITMAP hbmOld;
            HDC hdc;

            hdc = wParam ?  (HDC)wParam : BeginPaint(hwnd, &ps);

            //DebugMsg(DM_TRACE, "NumTics = %d", SendMessage(ptb->ci.hwnd, TBM_GETNUMTICS, 0, 0));

            //ptb->hdc = GetDC(NULL);
            ptb->hdc = CreateCompatibleDC(hdc);
            if (!ptb->hbmBuffer) {
                GetClientRect(hwnd, &rc);
                ptb->hbmBuffer = CreateColorBitmap(rc.right, rc.bottom);
            }

            hbmOld = SelectObject(ptb->hdc, ptb->hbmBuffer);
            FlushChanges(ptb);

            //only copy the area that's changable.. ie the clip box
            switch(GetClipBox(hdc, &rc)) {
                case NULLREGION:
                case ERROR:
                    GetClientRect(ptb->ci.hwnd, &rc);
            }
            BitBlt(hdc, rc.left, rc.top,
                     rc.right - rc.left, rc.bottom - rc.top,
                     ptb->hdc, rc.left, rc.top, SRCCOPY);

#ifdef TB_DEBUG
            {
                HDC hdcScreen;
                RECT rcClient;
                hdcScreen = GetDC(NULL);
                GetClientRect(ptb->ci.hwnd, &rcClient);
                BitBlt(hdcScreen, 0, 0, rcClient.right, rcClient.bottom, ptb->hdc, 0,0, SRCCOPY);
                ReleaseDC(NULL, hdcScreen);
            }
#endif

            SelectObject(ptb->hdc, hbmOld);
            DeleteDC(ptb->hdc);
            //ReleaseDC(NULL, ptb->hdc);
            if (wParam == 0)
                EndPaint(hwnd, &ps);

            ptb->hdc = NULL;
            break;
        }

        case WM_GETDLGCODE:
            return DLGC_WANTARROWS;

        case WM_LBUTTONDOWN:
            /* Give ourselves focus */
            if (!(ptb->ci.style & WS_DISABLED)) {
                SetFocus(hwnd); // REVIEW: we may not want to do this
                TBTrackInit(ptb, lParam);
            }
            break;

        case WM_LBUTTONUP:
            // We're through doing whatever we were doing with the
            // button down.
            if (!(ptb->ci.style & WS_DISABLED)) {
                TBTrackEnd(ptb);
                if (GetCapture() == hwnd)
                    CCReleaseCapture(&ptb->ci);
            }
            break;

        case WM_TIMER:
            // The only way we get a timer message is if we're
            // autotracking.
            lParam = GetMessagePosClient(ptb->ci.hwnd, NULL);
            // fall through to WM_MOUSEMOVE

        case WM_MOUSEMOVE:

            // We only care that the mouse is moving if we're
            // tracking the bloody thing.
            if (!(ptb->ci.style & WS_DISABLED))
            {
                if ((ptb->Cmd != (UINT)-1))
                    TBTrack(ptb, lParam);
                else
                {
                    // No user actions, track hot state if theme
                    if (ptb->hTheme)
                    {
                        // Check if mouse is currently over thumb
                        if (WTrackType(ptb, (LONG)lParam) == TB_THUMBTRACK)
                        {
                            if (!ptb->bThumbHot)
                            {
                                // Hot bit not set, set now and invalidate
                                ptb->bThumbHot = TRUE;

                                // Update display
                                TBRedrawThumb(ptb);
                            }
                        }
                        else
                        {
                            // Mouse not over thumb
                            if (ptb->bThumbHot)
                            {
                                ptb->bThumbHot = FALSE;

                                // Update display
                                TBRedrawThumb(ptb);
                            }
                        }
                    }
                }
            }
            break;

        case WM_CAPTURECHANGED:
            // someone is stealing the capture from us
            TBTrackEnd(ptb);
            break;

        case WM_KEYUP:
            if (!(ptb->ci.style & WS_DISABLED)) {
                // If key was any of the keyboard accelerators, send end
                // track message when user up clicks on keyboard
                switch (wParam) {
                case VK_HOME:
                case VK_END:
                case VK_PRIOR:
                case VK_NEXT:
                case VK_LEFT:
                case VK_UP:
                case VK_RIGHT:
                case VK_DOWN:
                    DoTrack(ptb, TB_ENDTRACK, 0);
                    break;
                default:
                    break;
                }
            }
            break;

        case WM_KEYDOWN:
            if (!(ptb->ci.style & WS_DISABLED)) {

                // Swap the left and right arrow key if the control is mirrored.
                wParam = RTLSwapLeftRightArrows(&ptb->ci, wParam);

                // If TBS_DOWNISLEFT, then swap left/right or up/down
                // depending on whether we are vertical or horizontal.
                // Some horizontal trackbars (e.g.) prefer that
                // UpArrow=TB_PAGEDOWN.
                if (ptb->ci.style & TBS_DOWNISLEFT) {
                    if (ISVERT(ptb)) {
                        wParam = CCSwapKeys(wParam, VK_LEFT, VK_RIGHT);
                    } else {
                        wParam = CCSwapKeys(wParam, VK_UP, VK_DOWN);
                        wParam = CCSwapKeys(wParam, VK_PRIOR, VK_NEXT);
                    }
                }

                switch (wParam) {
                case VK_HOME:
                    wParam = TB_TOP;
                    goto KeyTrack;

                case VK_END:
                    wParam = TB_BOTTOM;
                    goto KeyTrack;

                case VK_PRIOR:
                    wParam = TB_PAGEUP;
                    goto KeyTrack;

                case VK_NEXT:
                    wParam = TB_PAGEDOWN;
                    goto KeyTrack;

                case VK_LEFT:
                case VK_UP:
                    wParam = TB_LINEUP;
                    goto KeyTrack;

                case VK_RIGHT:
                case VK_DOWN:
                    wParam = TB_LINEDOWN;
                KeyTrack:
                    DoTrack(ptb, (int) wParam, 0);

                    //notify of navigation key usage
                    CCNotifyNavigationKeyUsage(&(ptb->ci), UISF_HIDEFOCUS);

                    break;

                default:
                    break;
                }
            }
            break;

        case WM_MBUTTONDOWN:
            SetFocus(hwnd);
            break;

        case WM_STYLECHANGED:
            if (wParam == GWL_STYLE) {
                ptb->ci.style = ((LPSTYLESTRUCT)lParam)->styleNew;
                TBResize(ptb);
            }
            break;

        case WM_UPDATEUISTATE:
        {
            DWORD dwUIStateMask = MAKEWPARAM(0xFFFF, UISF_HIDEFOCUS);

            if (CCOnUIState(&(ptb->ci), WM_UPDATEUISTATE, wParam & dwUIStateMask, lParam))
                InvalidateRect(hwnd, NULL, TRUE);

            goto DoDefault;
        }
        case TBM_GETPOS:
            return ptb->lLogPos;

        case TBM_GETSELSTART:
            return ptb->lSelStart;

        case TBM_GETSELEND:
            return ptb->lSelEnd;

        case TBM_GETRANGEMIN:
            return ptb->lLogMin;

        case TBM_GETRANGEMAX:
            return ptb->lLogMax;

        case TBM_GETPTICS:
            return (LRESULT)ptb->pTics;

        case TBM_CLEARSEL:
            ptb->Flags &= ~TBF_SELECTION;
            ptb->lSelStart = -1;
            ptb->lSelEnd   = -1;
            goto RedrawTB;

        case TBM_CLEARTICS:
            if (ptb->pTics)
                LocalFree((HLOCAL)ptb->pTics);

            ptb->pTics = NULL;
            ptb->nTics = 0;
            goto RedrawTB;

        case TBM_GETTIC:

            if (ptb->pTics == NULL || (int)wParam >= ptb->nTics)
                return -1L;

            return ptb->pTics[wParam];

        case TBM_GETTICPOS:

            if (ptb->pTics == NULL || (int)wParam >= ptb->nTics)
                return -1L;

            return TBLogToPhys(ptb,ptb->pTics[wParam]);

        case TBM_GETNUMTICS:
            if (ptb->ci.style & TBS_NOTICKS)
                return 0;

            if (ptb->ticFreq) {
                // first and last +
                return 2 + (ptb->nTics / ptb->ticFreq);
            }

            // if there's no ticFreq, then we fall down here.
            // 2 for the first and last tics that we always draw
            // when NOTICS isn't set.
            return 2;


        case TBM_SETTIC:
            /* not a valid position */
            if (((LONG)lParam) < ptb->lLogMin || ((LONG)lParam) > ptb->lLogMax)
                break;

            h = CCLocalReAlloc(ptb->pTics,
                                 sizeof(DWORD) * (ptb->nTics + 1));
            if (!h)
                return (LONG)FALSE;
            
            ptb->pTics = (PDWORD)h;
            ptb->pTics[ptb->nTics++] = (DWORD)lParam;

            TBInvalidateAll(ptb);
            return (LONG)TRUE;

        case TBM_SETTICFREQ:
            ptb->ticFreq = (int) wParam;
            DoAutoTics(ptb);
            goto RedrawTB;

        case TBM_SETPOS:
            /* Only redraw if it will physically move */
            if (wParam && TBLogToPhys(ptb, (DWORD) lParam) !=
                TBLogToPhys(ptb, ptb->lLogPos))
                MoveThumb(ptb, (DWORD) lParam);
            else
                ptb->lLogPos = BOUND((LONG)lParam,ptb->lLogMin,ptb->lLogMax);
            break;

        case TBM_SETSEL:

            if (!(ptb->ci.style & TBS_ENABLESELRANGE)) break;
            ptb->Flags |= TBF_SELECTION;

            if (((LONG)(SHORT)LOWORD(lParam)) < ptb->lLogMin)
                ptb->lSelStart = ptb->lLogMin;
            else
                ptb->lSelStart = (LONG)(SHORT)LOWORD(lParam);

            if (((LONG)(SHORT)HIWORD(lParam)) > ptb->lLogMax)
                ptb->lSelEnd = ptb->lLogMax;
            else
                ptb->lSelEnd   = (LONG)(SHORT)HIWORD(lParam);

            if (ptb->lSelEnd < ptb->lSelStart)
                ptb->lSelEnd = ptb->lSelStart;
            goto RedrawTB;

        case TBM_SETSELSTART:

            if (!(ptb->ci.style & TBS_ENABLESELRANGE)) break;
            ptb->Flags |= TBF_SELECTION;
            if (lParam < ptb->lLogMin)
                ptb->lSelStart = ptb->lLogMin;
            else
                ptb->lSelStart = (LONG) lParam;
            if (ptb->lSelEnd < ptb->lSelStart || ptb->lSelEnd == -1)
                ptb->lSelEnd = ptb->lSelStart;
            goto RedrawTB;

        case TBM_SETSELEND:

            if (!(ptb->ci.style & TBS_ENABLESELRANGE)) break;
            ptb->Flags |= TBF_SELECTION;
            if (lParam > ptb->lLogMax)
                ptb->lSelEnd = ptb->lLogMax;
            else
                ptb->lSelEnd = (LONG) lParam;
            if (ptb->lSelStart > ptb->lSelEnd || ptb->lSelStart == -1)
                ptb->lSelStart = ptb->lSelEnd;
            goto RedrawTB;

        case TBM_SETRANGE:

            ptb->lLogMin = (LONG)(SHORT)LOWORD(lParam);
            ptb->lLogMax = (LONG)(SHORT)HIWORD(lParam);
            if (ptb->lSelStart < ptb->lLogMin)
                ptb->lSelStart = ptb->lLogMin;
            if (ptb->lSelEnd > ptb->lLogMax)
                ptb->lSelEnd = ptb->lLogMax;
            DoAutoTics(ptb);
            goto RedrawTB;

        case TBM_SETRANGEMIN:
            ptb->lLogMin = (LONG)lParam;
            if (ptb->lSelStart < ptb->lLogMin)
                ptb->lSelStart = ptb->lLogMin;
            DoAutoTics(ptb);
            goto RedrawTB;

        case TBM_SETRANGEMAX:
            ptb->lLogMax = (LONG)lParam;
            if (ptb->lSelEnd > ptb->lLogMax)
                ptb->lSelEnd = ptb->lLogMax;
            DoAutoTics(ptb);

RedrawTB:
            ptb->lLogPos = BOUND(ptb->lLogPos, ptb->lLogMin,ptb->lLogMax);
            TBChanged(ptb, TBC_ALL);
            /* Only redraw if flag says so */
            if (wParam) {
                InvalidateRect(hwnd, NULL, FALSE);
                MoveThumb(ptb, ptb->lLogPos);
            }
            break;

        case TBM_SETTHUMBLENGTH:
            if (ptb->ci.style & TBS_FIXEDLENGTH) {
                ptb->iThumbHeight = (UINT)wParam;
                TBResize(ptb);
            }
            break;

        case TBM_GETTHUMBLENGTH:
            return ptb->iThumbHeight;

        case TBM_SETPAGESIZE: {
            LONG lOldPage = ptb->lPageSize == -1 ? (ptb->lLogMax - ptb->lLogMin)/5 : ptb->lPageSize;
            ptb->lPageSize = (LONG)lParam;
            return lOldPage;
        }

        case TBM_GETPAGESIZE:
            return ptb->lPageSize == -1 ? (ptb->lLogMax - ptb->lLogMin)/5 : ptb->lPageSize;

        case TBM_SETLINESIZE:  {
            LONG lOldLine = ptb->lLineSize;
            ptb->lLineSize = (LONG)lParam;
            return lOldLine;
        }

        case TBM_GETLINESIZE:
            return ptb->lLineSize;

        case TBM_GETTHUMBRECT:
            if (lParam) {
                *((LPRECT)lParam) = ptb->rcThumb;
                if (ISVERT(ptb)) FlipRect((LPRECT)lParam);
            }
            break;

        case TBM_GETTOOLTIPS:
            return (LRESULT)ptb->hwndToolTips;

        case TBM_SETTOOLTIPS:
            ptb->hwndToolTips = (HWND)wParam;
            break;

        case TBM_SETTIPSIDE:
        {
            UINT uOldSide = ptb->uTipSide;
            
            ptb->uTipSide = (UINT) wParam;
            return uOldSide;
        }

        case TBM_GETCHANNELRECT:
            GetChannelRect(ptb, (LPRECT)lParam);
            break;

        case TBM_SETBUDDY:
            return (LRESULT)TBSetBuddy(ptb, (BOOL)wParam, (HWND)lParam);

        case TBM_GETBUDDY:
            return (LRESULT)(wParam ? ptb->hwndBuddyLeft : ptb->hwndBuddyRight);

        case WM_GETOBJECT:
            if( lParam == OBJID_QUERYCLASSNAMEIDX )
                return MSAA_CLASSNAMEIDX_TRACKBAR;
            goto DoDefault;

        case WM_THEMECHANGED:
            if (ptb->hTheme)
                CloseThemeData(ptb->hTheme);

            ptb->hTheme = OpenThemeData(ptb->ci.hwnd, L"TrackBar");

            TBInvalidateAll(ptb);
            break;

        default:
            if (uMsg == g_msgMSWheel) 
            {
                int   cDetants;
                long  lPos;
                ULONG ulPos;
                int   iWheelDelta = (int)(short)HIWORD(wParam);

                // Update count of scroll amount
                gcWheelDelta -= iWheelDelta;
                cDetants = gcWheelDelta / WHEEL_DELTA;
                if (cDetants != 0) 
                {
                    gcWheelDelta %= WHEEL_DELTA;
                }

                if (wParam & (MK_SHIFT | MK_CONTROL))
                    goto DoDefault;

                if (SHRT_MIN <= ptb->lLogPos && ptb->lLogPos <= SHRT_MAX) 
                {
                    // Update position based on the logical unit length of the trackbar
                    // The larger the spread, the more logical units traversed
                    int cMult = (ptb->lLogMax - ptb->lLogMin) / 50;
                    if (cMult == 0)
                        cMult = 1;

                    lPos = ptb->lLogPos + (cDetants * cMult);
                    lPos = BOUND(lPos, ptb->lLogMin, ptb->lLogMax);
                    ulPos = BOUND(lPos, SHRT_MIN, SHRT_MAX);
                    if ((long) ulPos != ptb->lLogPos) 
                    {
                        MoveThumb(ptb, (long) ulPos);
                        DoTrack(ptb, TB_THUMBPOSITION, ulPos);
                    }
                }

                return TRUE;
            }
            else
            {
                LRESULT lres;
                if (CCWndProc(&ptb->ci, uMsg, wParam, lParam, &lres))
                    return lres;
            }

DoDefault:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }

    return 0L;
}

/* DoTrack() */

void DoTrack(PTRACKBAR ptb, int cmd, DWORD dwPos)
{
    LONG dpos;
    switch(cmd) {
        case TB_LINEDOWN:
            dpos = ptb->lLineSize;
            goto DMoveThumb;

        case TB_LINEUP:
            dpos = -ptb->lLineSize;
            goto DMoveThumb;

        case TB_PAGEUP:
        case TB_PAGEDOWN:
            if (ptb->lPageSize == -1) {
                dpos = (ptb->lLogMax - ptb->lLogMin) / 5;
                if (!dpos)
                    dpos = 1;
            } else {
                dpos = ptb->lPageSize;
            }

            if (cmd == TB_PAGEUP)
                dpos *= -1;

DMoveThumb: // move delta
            MoveThumb(ptb, ptb->lLogPos + dpos);
            break;

        case TB_BOTTOM:
            dpos = ptb->lLogMax; // the BOUND will take care of this;
            goto ABSMoveThumb;

        case TB_TOP:
            dpos = ptb->lLogMin; // the BOUND will take care of this;

ABSMoveThumb: // move absolute
            MoveThumb(ptb, dpos);
            break;

        default:  // do nothing
            break;

    }

    // note: we only send back a WORD worth of the position.
    if (ISVERT(ptb)) {
        FORWARD_WM_VSCROLL(ptb->ci.hwndParent, ptb->ci.hwnd, cmd, LOWORD(dwPos), SendMessage);
    } else
        FORWARD_WM_HSCROLL(ptb->ci.hwndParent, ptb->ci.hwnd, cmd, LOWORD(dwPos), SendMessage);
}

/* WTrackType() */

WORD WTrackType(PTRACKBAR ptb, LONG lParam)
{
    POINT pt;

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

    if (ptb->Flags & TBF_NOTHUMB ||
        ptb->ci.style & TBS_NOTHUMB)            // If no thumb, just leave.
        return 0;

    if (ISVERT(ptb)) {
        // put point in virtual coordinates
        int temp;
        temp = pt.x;
        pt.x = pt.y;
        pt.y = temp;
    }

    if (PtInRect(&ptb->rcThumb, pt))
        return TB_THUMBTRACK;

    if (!PtInRect(&ptb->rc, pt))
        return 0;

    if (pt.x >= ptb->rcThumb.left)
        return TB_PAGEDOWN;
    else
        return TB_PAGEUP;
}

/* TBTrackInit() */

void TBTrackInit(PTRACKBAR ptb, LPARAM lParam)
{
        WORD wCmd;

        if (ptb->Flags & TBF_NOTHUMB ||
            ptb->ci.style & TBS_NOTHUMB)         // No thumb:  just leave.
            return;

        wCmd = WTrackType(ptb, (LONG) lParam);
        if (!wCmd)
            return;

        SetCapture(ptb->ci.hwnd);

        ptb->Cmd = wCmd;
        ptb->dwDragPos = (DWORD)-1;

        // Set up for auto-track (if needed).
        if (wCmd != TB_THUMBTRACK) {
                // Set our timer up
                SetTimer(ptb->ci.hwnd, TIMER_ID, REPEATTIME, NULL);
        } else {
            int xPos;
            // thumb tracking...

            // store the offset between the cursor's position and the center of the thumb
            xPos = TBLogToPhys(ptb, ptb->lLogPos);
            ptb->dwDragOffset = (ISVERT(ptb) ? HIWORD(lParam) : LOWORD(lParam)) - xPos;

            if (ptb->hwndToolTips) {
                TOOLINFO ti;
                // don't bother setting the rect because we'll do it below
                // in FlushToolTipsMgr;
                ti.cbSize = sizeof(ti);
                ti.uFlags = TTF_TRACK | TTF_CENTERTIP;
                ti.hwnd = ptb->ci.hwnd;
                ti.uId = (UINT_PTR)ptb->ci.hwnd;
                SendMessage(ptb->hwndToolTips, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&ti);
            }
        }

        TBTrack(ptb, lParam);
}

/* EndTrack() */

void TBTrackEnd(PTRACKBAR ptb)
{
        // Decide how we're ending this thing.
        if (ptb->Cmd == TB_THUMBTRACK) {

            if (ptb->hwndToolTips)
                SendMessage(ptb->hwndToolTips, TTM_TRACKACTIVATE, (WPARAM)FALSE, 0);

            DoTrack(ptb, TB_THUMBPOSITION, ptb->dwDragPos);

        }

        KillTimer(ptb->ci.hwnd, TIMER_ID);

        // Always send TB_ENDTRACK message if there's some sort of command tracking.
        if (ptb->Cmd != (UINT)-1) {
            DoTrack(ptb, TB_ENDTRACK, 0);

            // Nothing going on.
            ptb->Cmd = (UINT)-1;
        }

        MoveThumb(ptb, ptb->lLogPos);
}

#define TBTS_RIGHTLEFT   1   // low bit means it's on the right or left

void TBTrack(PTRACKBAR ptb, LPARAM lParam)
{
    DWORD dwPos;
    WORD pos;


    // See if we're tracking the thumb
    if (ptb->Cmd == TB_THUMBTRACK) {


        pos = (ISVERT(ptb)) ? HIWORD(lParam) : LOWORD(lParam);
        pos -= (WORD) ptb->dwDragOffset;
        dwPos = TBPhysToLog(ptb, (int)(SHORT)pos);

        // Tentative position changed -- notify the guy.
        if (dwPos != ptb->dwDragPos) {
            ptb->dwDragPos = dwPos;
            MoveThumb(ptb, dwPos);
            DoTrack(ptb, TB_THUMBTRACK, dwPos);
        }

        if (ptb->hwndToolTips) {
            RECT rc;
            POINT pt;
            int iPixel;
            UINT uTipSide = ptb->uTipSide;

            // find the center of the window
            GetClientRect(ptb->ci.hwnd, &rc);
            pt.x = rc.right / 2;
            pt.y = rc.bottom / 2;

            //find the position of the thumb
            iPixel = TBLogToPhys(ptb, dwPos);
            if (ISVERT(ptb)) {
                pt.y = iPixel;
                uTipSide |= TBTS_RIGHTLEFT;
            } else {
                pt.x = iPixel;
                uTipSide &= ~TBTS_RIGHTLEFT;
            }
            
            // move it out to the requested side
            switch (uTipSide) {

            case TBTS_TOP:
                pt.y = -1;
                break;

            case TBTS_LEFT:
                pt.x = -1;
                break;

            case TBTS_BOTTOM:
                pt.y = rc.bottom + 1;
                break;

            case TBTS_RIGHT:
                pt.x = rc.right + 1;
                break;
            }

            // map it to screen coordinates
            MapWindowPoints(ptb->ci.hwnd, HWND_DESKTOP, &pt, 1);

            SendMessage(ptb->hwndToolTips, TTM_TRACKPOSITION, 0, MAKELONG(pt.x, pt.y));
        }

    }
    else {
        if (ptb->Cmd != WTrackType(ptb, (LONG) lParam))
            return;

        DoTrack(ptb, ptb->Cmd, 0);
    }
}


// FlushChanges is theme aware (RENDERS)
void FlushChanges(PTRACKBAR ptb)
{
    HBRUSH hbr;
    NMCUSTOMDRAW nmcd;

    hbr = FORWARD_WM_CTLCOLORSTATIC(ptb->ci.hwndParent, ptb->hdc, ptb->ci.hwnd, SendMessage);

    if (hbr) 
    {
        RECT rc;
        BOOL fClear = FALSE;

        if ( ptb->wDirtyFlags == TBC_ALL ) 
        {
            GetClientRect(ptb->ci.hwnd, &rc);
            fClear = TRUE;
        } 
        else if (ptb->wDirtyFlags & TBC_THUMB) 
        {
            rc = ptb->rc;
            rc.left = 0;
            rc.right += ptb->iThumbWidth;
            if (ISVERT(ptb))
                FlipRect(&rc);
            fClear = TRUE;
        }

        // Background fill
        if (fClear)
        {
            FillRect(ptb->hdc, &rc, hbr);
        }
    }

    nmcd.hdc = ptb->hdc;
    if (ptb->ci.hwnd == GetFocus())
        nmcd.uItemState = CDIS_FOCUS;
    else
        nmcd.uItemState = 0;

    nmcd.lItemlParam = 0;
    ptb->ci.dwCustom = CICustomDrawNotify(&ptb->ci, CDDS_PREPAINT, &nmcd);

    // for skip default, no other flags make sense..  only allow that one
    if (!(ptb->ci.dwCustom == CDRF_SKIPDEFAULT)) 
    {
        DWORD dwRet = 0;
        // do the actual drawing

        if (nmcd.uItemState & CDIS_FOCUS)
        {
            DrawFocus(ptb, hbr);
        }

        nmcd.uItemState = 0;
        if (ptb->wDirtyFlags & TBC_TICS) 
        {

            nmcd.dwItemSpec = TBCD_TICS;
            dwRet = CICustomDrawNotify(&ptb->ci, CDDS_ITEMPREPAINT, &nmcd);

            if (!(dwRet == CDRF_SKIPDEFAULT)) 
            {
                DrawTics(ptb);

                if (dwRet & CDRF_NOTIFYPOSTPAINT) 
                {
                    nmcd.dwItemSpec = TBCD_TICS;
                    CICustomDrawNotify(&ptb->ci, CDDS_ITEMPOSTPAINT, &nmcd);
                }
            }
        }

        if (ptb->wDirtyFlags & TBC_THUMB) 
        {


            // the channel
            GetChannelRect(ptb, &nmcd.rc);
            if (ISVERT(ptb))
                FlipRect(&nmcd.rc);
            nmcd.dwItemSpec = TBCD_CHANNEL;
            dwRet = CICustomDrawNotify(&ptb->ci, CDDS_ITEMPREPAINT, &nmcd);

            if (!(dwRet == CDRF_SKIPDEFAULT)) 
            {

                // flip it back from the last notify
                if (ISVERT(ptb))
                    FlipRect(&nmcd.rc);

                // the actual drawing
                DrawChannel(ptb, &nmcd.rc);

                if (dwRet & CDRF_NOTIFYPOSTPAINT) 
                {

                    if (ISVERT(ptb))
                        FlipRect(&nmcd.rc);
                    nmcd.dwItemSpec = TBCD_CHANNEL;
                    CICustomDrawNotify(&ptb->ci, CDDS_ITEMPOSTPAINT, &nmcd);
                }
            }


            // the thumb
            nmcd.rc = ptb->rcThumb;
            if (ptb->Cmd == TB_THUMBTRACK) 
            {
                nmcd.uItemState = CDIS_SELECTED;
            }

            if (ISVERT(ptb))
                FlipRect(&nmcd.rc);
            nmcd.dwItemSpec = TBCD_THUMB;
            dwRet = CICustomDrawNotify(&ptb->ci, CDDS_ITEMPREPAINT, &nmcd);

            if (!(dwRet == CDRF_SKIPDEFAULT))
            {

                if (ISVERT(ptb))
                    FlipRect(&nmcd.rc);

                // the actual drawing
                DrawThumb(ptb, &nmcd.rc, nmcd.uItemState & CDIS_SELECTED);

                if (dwRet & CDRF_NOTIFYPOSTPAINT) 
                {
                    if (ISVERT(ptb))
                        FlipRect(&nmcd.rc);
                    nmcd.dwItemSpec = TBCD_THUMB;
                    CICustomDrawNotify(&ptb->ci, CDDS_ITEMPOSTPAINT, &nmcd);
                }
            }

        }
        ptb->wDirtyFlags = 0;

        // notify parent afterwards if they want us to
        if (ptb->ci.dwCustom & CDRF_NOTIFYPOSTPAINT)
        {
            CICustomDrawNotify(&ptb->ci, CDDS_POSTPAINT, &nmcd);
        }
    }

#ifdef TB_DEBUG
    DebugMsg(DM_TRACE, TEXT("DrawDone"));
    {
        HDC hdcScreen;
        RECT rcClient;
        hdcScreen = GetDC(NULL);
        GetClientRect(ptb->ci.hwnd, &rcClient);
        BitBlt(hdcScreen, 200, 0, 200 + rcClient.right, rcClient.bottom, ptb->hdc, 0,0, SRCCOPY);
        ReleaseDC(NULL, hdcScreen);
    }
#endif

}
