#include "ctlspriv.h" #include "limits.h" #include "image.h" // for CreateColorBitmap //#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 // //--------------------------------------------------------------------------; #define MulDiv32 MulDiv // use KERNEL32 version (it rounds) //--------------------------------------------------------------------------; //--------------------------------------------------------------------------; // // 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); return (RegisterClass(&wc) || (GetLastError() == ERROR_CLASS_ALREADY_EXISTS)); } #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 * 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); } } #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; DWORD exStyle = 0; 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) StringCchPrintf(lpttt->szText, ARRAYSIZE(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 }