//----------------------------------------------------------------------------- // File: flexscrollbar.cpp // // Desc: Implements CFlexScrollBar (derived from CFlexWnd), a scroll bar // control similar to a Windows scroll bar. // // Copyright (C) 1999-2000 Microsoft Corporation. All Rights Reserved. //----------------------------------------------------------------------------- #include "common.hpp" CFlexScrollBar::CFlexScrollBar() : m_nMin(0), m_nMax(0), m_nPage(25), m_nPos(25), m_bVert(TRUE), m_hWndNotify(NULL), m_bValid(FALSE), m_bCapture(FALSE), m_bDragging(FALSE), m_code(SB_ENDSCROLL), m_rgbBk(RGB(0,0,0)), m_rgbFill(RGB(0,0,255)), m_rgbLine(RGB(0,255,255)) { } CFlexScrollBar::~CFlexScrollBar() { } CFlexScrollBar *CreateFlexScrollBar(FLEXSCROLLBARCREATESTRUCT *pcs) { CFlexScrollBar *psb = new CFlexScrollBar; if (psb && psb->Create(pcs)) return psb; delete psb; return NULL; } BOOL CFlexScrollBar::Create(FLEXSCROLLBARCREATESTRUCT *pcs) { if (this == NULL) return FALSE; if (pcs == NULL) return FALSE; if (pcs->dwSize != sizeof(FLEXSCROLLBARCREATESTRUCT)) return FALSE; if (pcs->min > pcs->max) return FALSE; int range = pcs->max - pcs->min; if (pcs->page > range) return FALSE; m_bVert = ( pcs->dwFlags & FSBF_VERT ) == FSBF_VERT; SetValues(pcs->min, pcs->max, pcs->page, pcs->pos); m_hWndNotify = pcs->hWndNotify ? pcs->hWndNotify : pcs->hWndParent; if (!CFlexWnd::Create(pcs->hWndParent, pcs->rect, pcs->bVisible)) return FALSE; Calc(); // TODO: make sure that creation sends no notifications. // all initial notifications should be sent here. return TRUE; } int CFlexScrollBar::GetLineAdjust() { return 1; } int CFlexScrollBar::GetPageAdjust() { return m_nPage > 1 ? m_nPage - 1 : 1; } void CFlexScrollBar::AdjustPos(int adj, BOOL bForceCalc) { int old = m_nPos; m_nPos += adj; if (m_nPos < m_nMin) m_nPos = m_nMin; if (m_nPos > m_nMax - m_nPage) m_nPos = m_nMax - m_nPage; if (m_nPos == old && !bForceCalc) return; if (Calc()) Invalidate(); } BOOL CFlexScrollBar::FailCalc(BOOL bOldValid) { m_bValid = FALSE; if (bOldValid) Invalidate(); return m_bValid; } BOOL CFlexScrollBar::Calc() { BOOL bOldValid = m_bValid; #define FAIL return FailCalc(bOldValid) if (!m_hWnd) FAIL; SRECT zero; m_rectLineUp = zero; m_rectPageUp = zero; m_rectTrack = zero; m_rectThumb = zero; m_rectPageDown = zero; m_rectLineDown = zero; SPOINT size = GetClientSize(); int ord = m_bVert ? 1 : 0; int nord = m_bVert ? 0 : 1; int length = size.a[ord]; int width = size.a[nord]; int arrowlen = width; if (width < 1 || length < 2) FAIL; int tracklen = length - arrowlen * 2; int trackofs = arrowlen; BOOL bOverlappingArrows = tracklen < -1; int overlap = !bOverlappingArrows ? 0 : -tracklen; SRECT up, down, track, thumb, temp; if (overlap > 1) { int mid = length / 2; up.lr.a[nord] = width; up.lr.a[ord] = mid; down.ul.a[ord] = mid; down.lr.a[ord] = length; down.lr.a[nord] = width; m_rectLineUp = up; m_rectLineDown = down; return m_bValid = TRUE; } up.lr.a[nord] = width; up.lr.a[ord] = arrowlen; down.lr.a[nord] = width; down.ul.a[ord] = length - arrowlen; down.lr.a[ord] = length; m_rectLineUp = up; m_rectLineDown = down; int tmin = up.lr.a[ord]; int tmax = down.ul.a[ord]; int trange = tmax - tmin; int range = m_nMax - m_nMin; assert(trange > 0); if (!(range > 0) || !(trange > 0)) return m_bValid = TRUE; track.ul.a[ord] = tmin; track.lr.a[nord] = width; track.lr.a[ord] = tmax; m_rectTrack = track; const int minthumblen = 3; int thumblen = MulDiv(m_nPage, trange, range); if (thumblen < minthumblen) thumblen = minthumblen; int thumbrange = trange - thumblen /*+ 1*/; int pagerange = range - m_nPage; if (!(pagerange > 1) || !(thumbrange > 1)) return m_bValid = TRUE; int logpos = m_bDragging ? m_nPreDragPos : m_nPos; int thumbpos = MulDiv(logpos - m_nMin, thumbrange, pagerange) + tmin; if (m_bDragging) { SPOINT rp = m_point, rs = m_startpoint; int rdelta = rp.a[ord] - rs.a[ord]; thumbpos += rdelta; if (thumbpos < tmin) thumbpos = tmin; if (thumbpos > tmax - thumblen) thumbpos = tmax - thumblen; m_nThumbPos = MulDiv(thumbpos - tmin, pagerange, thumbrange) + m_nMin; if (m_nThumbPos < m_nMin) m_nThumbPos = m_nMin; if (m_nThumbPos > m_nMax - m_nPage) m_nThumbPos = m_nMax - m_nPage; } thumb.ul.a[ord] = thumbpos; thumb.lr.a[nord] = width; thumb.lr.a[ord] = thumbpos + thumblen; m_rectThumb = thumb; temp = track; temp.lr.a[ord] = thumb.ul.a[ord]; if (temp.lr.a[ord] > temp.ul.a[ord]) m_rectPageUp = temp; temp = track; temp.ul.a[ord] = thumb.lr.a[ord]; if (temp.lr.a[ord] > temp.ul.a[ord]) m_rectPageDown = temp; return m_bValid = TRUE; #undef FAIL } void CFlexScrollBar::SetValues(int min, int max, int page, int pos) { m_nMin = min < max ? min : max; m_nMax = max > min ? max : min; m_nPage = page; AdjustPos(pos - m_nPos, TRUE); } static BOOL UseRect(const RECT &rect) { if (rect.left >= rect.right || rect.bottom <= rect.top) return FALSE; return TRUE; } static void Rectangle(HDC hDC, RECT rect) { if (!UseRect(rect)) return; Rectangle(hDC, rect.left, rect.top, rect.right, rect.bottom); } void CFlexScrollBar::OnPaint(HDC hDC) { HDC hBDC = NULL, hODC = NULL; CBitmap *pbm = NULL; if (!InRenderMode()) { hODC = hDC; pbm = CBitmap::Create(GetClientSize(), RGB(0,0,0), hDC); if (pbm != NULL) { hBDC = pbm->BeginPaintInto(); if (hBDC != NULL) { hDC = hBDC; } } } InternalPaint(hDC); if (!InRenderMode()) { if (pbm != NULL) { if (hBDC != NULL) { pbm->EndPaintInto(hBDC); pbm->Draw(hODC); } delete pbm; } } } void CFlexScrollBar::InternalPaint(HDC hDC) { HGDIOBJ hPen, hOldPen, hBrush, hOldBrush; hPen = (HGDIOBJ)CreatePen(PS_SOLID, 1, m_rgbBk); if (hPen != NULL) { hOldPen = SelectObject(hDC, hPen), hOldBrush = SelectObject(hDC, GetStockObject(BLACK_BRUSH)); Rectangle(hDC, m_rectPageUp); Rectangle(hDC, m_rectPageDown); Rectangle(hDC, m_rectLineUp); Rectangle(hDC, m_rectLineDown); SelectObject(hDC, hOldPen); DeleteObject(hPen); hBrush = (HGDIOBJ)CreateSolidBrush(m_rgbFill); if (hBrush != NULL) { SelectObject(hDC, (HGDIOBJ)hBrush); hPen = (HGDIOBJ)CreatePen(PS_SOLID, 1, m_rgbFill); if (hPen != NULL) { SelectObject(hDC, hPen); Rectangle(hDC, m_rectThumb); SelectObject(hDC, hOldPen); DeleteObject(hPen); } hPen = (HGDIOBJ)CreatePen(PS_SOLID, 1, m_rgbLine); if (hPen != NULL) { SelectObject(hDC, hPen); // draw the two arrows for this scrollbar for (int i = 0; i < 2; i++) DrawArrow(hDC, i ? m_rectLineUp : m_rectLineDown, m_bVert, i); #if 0 // draw the two arrows for this scrollbar for (int i = 0; i < 2; i++) { const RECT &rect = i == 0 ? m_rectLineUp : m_rectLineDown; SRECT srect = rect; srect.right--; srect.bottom--; int ord = m_bVert ? 1 : 0; int nord = m_bVert ? 0 : 1; SPOINT p(i ? srect.lr : srect.ul), b(i ? srect.ul : srect.lr); b.a[ord] += 2 * i - 1; SPOINT t = p; t.a[nord] = (p.a[nord] + b.a[nord]) / 2; SPOINT u; u.a[ord] = b.a[ord]; u.a[nord] = p.a[nord]; POINT poly[] = { {t.x, t.y}, {u.x, u.y}, {b.x, b.y} }; Polygon(hDC, poly, 3); } #endif SelectObject(hDC, hOldPen); DeleteObject(hPen); } SelectObject(hDC, hOldBrush); DeleteObject(hBrush); } } } BOOL InRect(const RECT &rect, POINT point) { return UseRect(rect) && PtInRect(&rect, point); } LRESULT CFlexScrollBar::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_SIZE: Calc(); Invalidate(); return 0; // make sure flexwnd doesn't do ANYTHING with our mouse messages case WM_MOUSEMOVE: case WM_LBUTTONUP: case WM_LBUTTONDOWN: case WM_RBUTTONUP: case WM_RBUTTONDOWN: case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: { POINT point = {int(signed short(LOWORD(lParam))), int(signed short(HIWORD(lParam)))}; m_point = point; m_code = HitTest(point); } case WM_TIMER: case WM_CAPTURECHANGED: break; default: return CFlexWnd::WndProc(hWnd, msg, wParam, lParam); } switch (msg) { case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: if (m_code == SB_ENDSCROLL) goto endscroll; if (m_code == SB_THUMBTRACK) m_bDragging = TRUE; else SetTimer(m_hWnd, 1, 500, NULL); m_startcode = m_code; m_startpoint = m_point; m_nPreDragPos = m_nPos; m_bCapture = TRUE; SetCapture(); if (!m_bDragging) Notify(m_code); break; case WM_LBUTTONUP: case WM_MOUSEMOVE: if (!m_bDragging) break; if (Calc()) { Invalidate(); // Force repaint the updated scrollbar position. If we don't do this, // the WM_PAINT message will be pre-empted by the WM_FLEXVSCROLL messages. // Sometimes this happens during the entire duration of draggin the scroll // bar. The result is that the scroll bar does not get updated when // dragging. SendMessage(m_hWnd, WM_PAINT, 0, 0); } Notify(m_startcode); break; case WM_TIMER: if (m_bCapture) switch (wParam) { case 1: KillTimer(m_hWnd, 1); SetTimer(m_hWnd, 2, 50, NULL); case 2: if (m_bDragging) break; if (m_code == m_startcode) Notify(m_code); break; } break; } switch (msg) { case WM_LBUTTONUP: case WM_CAPTURECHANGED: endscroll: if (m_bCapture) { m_bCapture = FALSE; KillTimer(m_hWnd, 1); KillTimer(m_hWnd, 2); ReleaseCapture(); if (m_bDragging) Notify(SB_THUMBPOSITION); BOOL bWasDragging = m_bDragging; m_bDragging = FALSE; if (bWasDragging) { if (Calc()) Invalidate(); } Notify(SB_ENDSCROLL); } break; } return 0; } void CFlexScrollBar::Notify(int code) { if (!m_hWndNotify) return; SendMessage(m_hWndNotify, m_bVert ? WM_FLEXVSCROLL : WM_FLEXHSCROLL, (WPARAM)code, (LPARAM)(LPVOID)this); } int CFlexScrollBar::HitTest(POINT point) { if (InRect(m_rectLineUp, point)) return SB_LINEUP; else if (InRect(m_rectLineDown, point)) return SB_LINEDOWN; else if (InRect(m_rectThumb, point)) return SB_THUMBTRACK; else if (InRect(m_rectPageUp, point)) return SB_PAGEUP; else if (InRect(m_rectPageDown, point)) return SB_PAGEDOWN; else return SB_ENDSCROLL; } void CFlexScrollBar::SetColors(COLORREF bk, COLORREF fill, COLORREF line) { m_rgbBk = bk; m_rgbFill = fill; m_rgbLine = line; Invalidate(); }