//----------------------------------------------------------
//
// BUGBUG: make sure this stuff really works with the DWORD
//         ranges
//
//----------------------------------------------------------

#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
    CONTROLINFO 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.


#if defined(FE_IME) || !defined(WINNT)
    HIMC    hPrevImc;       // previous input context handle
#endif


    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   NEAR PASCAL DoTrack(PTRACKBAR, int, DWORD);
WORD   NEAR PASCAL WTrackType(PTRACKBAR, LONG);
void   NEAR PASCAL TBTrackInit(PTRACKBAR, LPARAM);
void   NEAR PASCAL TBTrackEnd(PTRACKBAR);
void   NEAR PASCAL TBTrack(PTRACKBAR, LPARAM);
void   NEAR PASCAL DrawThumb(PTRACKBAR, LPRECT, BOOL);

HBRUSH NEAR PASCAL SelectColorObjects(PTRACKBAR, BOOL);
void   NEAR PASCAL 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 FAR CALLBACK TrackBarWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void NEAR PASCAL 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 NEAR PASCAL 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 NEAR PASCAL 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 FAR PASCAL InitTrackBar(HINSTANCE hInstance)
{
    WNDCLASS wc;

    // See if we must register a window class
    if (!GetClassInfo(hInstance, s_szSTrackBarClass, &wc)) {
#ifndef WIN32
        extern LRESULT CALLBACK _TrackBarWndProc(HWND, UINT, WPARAM, LPARAM);
        wc.lpfnWndProc = _TrackBarWndProc;
#else
        wc.lpfnWndProc = TrackBarWndProc;
#endif

        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);

        return RegisterClass(&wc);
    }
    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 NEAR PASCAL 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 FAR PASCAL 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) VertDrawEdge(hdc, prc, uType, grfFlags, ISVERT(ptb))
void FAR PASCAL VertDrawEdge(HDC hdc, LPRECT qrc, UINT edgeType, UINT grfFlags,
                               BOOL fVert)
{
    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;
            }
        }
    }
    DrawEdge(hdc, &temprc, edgeType, uFlags);
}

void NEAR PASCAL 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))
void FAR PASCAL VertPatBlt(HDC hdc1, int x1, int y1, int w, int h,
                          DWORD rop, BOOL fVert)
{
    if (fVert)
        PatBlt(hdc1, y1, x1, h, w, rop);
    else
        PatBlt(hdc1, x1, y1, w, h, rop);
}


void NEAR PASCAL DrawTic(PTRACKBAR ptb, int x, int y, int dir)
{
    if (dir == -1) y -= TICKHEIGHT;
    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 NEAR PASCAL 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 NEAR PASCAL 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 NEAR PASCAL 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 */

void NEAR PASCAL DrawChannel(PTRACKBAR ptb, LPRECT lprc)
{

        TBDrawEdge(ptb->hdc, lprc, EDGE_SUNKEN, BF_RECT,ptb);

        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);
                }
        }
}

void NEAR PASCAL 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;
    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);


    // 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);
            }
        }

    }

    TBDrawEdge(ptb->hdc, &rcThumb, EDGE_RAISED, uEdgeFlags, ptb);


    //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);
        // then do th right side
        OffsetRect(&rcThumb, i + 1, 0);
        TBDrawEdge(ptb->hdc, &rcThumb, EDGE_RAISED, uEdgeFlags2 , ptb);
    }
}
void NEAR PASCAL TBInvalidateAll(PTRACKBAR ptb)
{
    if (ptb) {
        TBChanged(ptb, TBC_ALL);
        InvalidateRect(ptb->ci.hwnd, NULL, FALSE);
    }
}

void NEAR PASCAL 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)
        MyNotifyWinEvent(EVENT_OBJECT_VALUECHANGE, ptb->ci.hwnd, OBJID_CLIENT, 0);
}


void NEAR PASCAL DrawFocus(PTRACKBAR ptb, HBRUSH hbrBackground)
{
    RECT rc;
    if (ptb->ci.hwnd == GetFocus()
#ifdef KEYBOARDCUES
        && !(CCGetUIState(&(ptb->ci)) & UISF_HIDEFOCUS)
#endif
        )
    {
        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 NEAR PASCAL DoAutoTics(PTRACKBAR ptb)
{
    LONG NEAR *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 NEAR *)LocalAlloc(LPTR, sizeof(DWORD) * ptb->nTics);
    else
        ptb->pTics = NULL;

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

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


void NEAR PASCAL 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 NEAR PASCAL TBNukeBuffer(PTRACKBAR ptb)
{
    if (ptb->hbmBuffer) {
        DeleteObject(ptb->hbmBuffer);
        ptb->hbmBuffer = NULL;
        TBChanged(ptb, TBC_ALL);            // Must do a full repaint
    }
}

void NEAR PASCAL 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 NEAR PASCAL 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 defined(FE_IME) || !defined(WINNT)
    if (g_fDBCSInputEnabled)
        ptb->hPrevImc = ImmAssociateContext(hwnd, 0L);
#endif

#ifdef FEATURE_DEBUG
    if (GetAsyncKeyState(VK_SHIFT) < 0 &&
        GetAsyncKeyState(VK_CONTROL) < 0)
        ptb->ci.style |= TBS_TOOLTIPS;
#endif

    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);
    }


    TBResize(ptb);

#ifdef FEATURE_DEBUG
    if (GetAsyncKeyState(VK_SHIFT) < 0 &&
        GetAsyncKeyState(VK_CONTROL) < 0 )
    {
        HWND hwnd =
            CreateWindowEx(WS_EX_STATICEDGE, TEXT("static"), TEXT("left"), WS_CHILD | WS_VISIBLE, 0, 0, 30, 20, GetParent(ptb->ci.hwnd), NULL, HINST_THISDLL, NULL);
        HWND hwnd2 =
            CreateWindowEx(WS_EX_STATICEDGE, TEXT("static"), TEXT("right"), WS_CHILD |WS_VISIBLE, 0, 0, 50, 20, GetParent(ptb->ci.hwnd), NULL, HINST_THISDLL, NULL);

        SendMessage(ptb->ci.hwnd, TBM_SETBUDDY, TRUE, (LPARAM)hwnd);
        SendMessage(ptb->ci.hwnd, TBM_SETBUDDY, FALSE, (LPARAM)hwnd2);
    }
#endif


    return 0;
}

void NEAR PASCAL 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;
}

LPARAM FAR 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;
        }

        switch (uMsg) {

        // 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 defined(FE_IME) || !defined(WINNT)
                if (g_fDBCSInputEnabled)
                    ImmAssociateContext(hwnd, ptb->hPrevImc);
#endif
                if ((ptb->ci.style & TBS_TOOLTIPS) && IsWindow(ptb->hwndToolTips)) {
                    DestroyWindow (ptb->hwndToolTips);
                }

                TBNukeBuffer(ptb);

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

                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;
            }
            TBChanged(ptb, 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->Cmd != (UINT)-1) && (!(ptb->ci.style & WS_DISABLED)))
                TBTrack(ptb, lParam);
            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);
#ifdef KEYBOARDCUES
                    //notify of navigation key usage
                    CCNotifyNavigationKeyUsage(&(ptb->ci), UISF_HIDEFOCUS);
#endif
                    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;

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

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

            goto DoDefault;
        }
#endif
        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;

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

                if (g_bRunOnNT || g_bRunOnMemphis)
                    iWheelDelta = (int)(short)HIWORD(wParam);
                else
                    iWheelDelta = (int)wParam;

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

                if (g_bRunOnNT || g_bRunOnMemphis)
                {
                    if (wParam & (MK_SHIFT | MK_CONTROL))
                        goto DoDefault;
                }
                else
                {
                    if (GetKeyState(VK_SHIFT) < 0 || GetKeyState(VK_CONTROL) < 0)
                        goto DoDefault;
                }

                if (SHRT_MIN <= ptb->lLogPos && ptb->lLogPos <= SHRT_MAX) {
                    lPos = ptb->lLogPos + cDetants;
                    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 NEAR PASCAL 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;

    }

    // BUGBUG:  for now, send both in vertical mode
    // 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 NEAR PASCAL 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 NEAR PASCAL 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 NEAR PASCAL 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 NEAR PASCAL 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);
    }
}




void NEAR PASCAL FlushChanges(PTRACKBAR ptb)
{
    HBRUSH hbr;
    NMCUSTOMDRAW nmcd;

#ifdef WIN32
    hbr = FORWARD_WM_CTLCOLORSTATIC(ptb->ci.hwndParent, ptb->hdc, ptb->ci.hwnd, SendMessage);
#else
    hbr = FORWARD_WM_CTLCOLOR(ptb->ci.hwndParent, ptb->hdc, ptb->ci.hwnd, CTLCOLOR_STATIC, SendMessage);
#endif

    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;
        }
        if (fClear)
            FillRect(ptb->hdc, &rc, hbr);
    }

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

#ifdef KEYBOARDCUES
#if 0
    // BUGBUG: Custom draw stuff for UISTATE (stephstm)
    if (CCGetUIState(&(ptb->ci), KC_TBD))
        nmcd.uItemState |= CDIS_SHOWKEYBOARDCUES;
#endif
#endif
    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

}