/* * @doc INTERNAL * * @module magellan.cpp -- Handle magellan mouse. | * * Magellan mouse can roll scroll and mButtonDown drag scroll. * * History: * Jon Matousek - 1996 * 4/1/2000 KeithCu - Cleanup, coding convention, support for textflows * * Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved. */ #include "_common.h" #if !defined(NOMAGELLAN) #include "_edit.h" #include "_disp.h" #include "_magelln.h" ASSERTDATA const SHORT scrollCursors[] = { // Cursor for various 0, // directions. IDC_SCROLLNORTH, IDC_SCROLLSOUTH, IDC_SCROLLEAST, IDC_SCROLLNE, IDC_SCROLLSE, IDC_SCROLLWEST, IDC_SCROLLNW, IDC_SCROLLSW }; const SHORT mDownBMPs[] = { // mButtonDown origin BMPs. 0, IDB_1DVSCROL, IDB_1DHSCROL, IDB_2DSCROL }; const SHORT noScrollCursors[] = { 0, IDC_NOSCROLLV, IDC_NOSCROLLH, IDC_NOSCROLLVH }; //Convert the compass from logical to visual const BYTE mapCursorTflowSW[] = { 0, 3, 6, 2, 5, 8, 1, 4, 7 }; const BYTE mapCursorTflowWN[] = { 0, 2, 1, 6, 8, 7, 3, 5, 4 }; const BYTE mapCursorTflowNE[] = { 0, 6, 3, 1, 7, 4, 2, 8, 5 }; BOOL CMagellan::ContinueMButtonScroll(CTxtEdit *ped, INT x, INT y) { POINTUV ptuv; POINT ptxy = {x,y}; ped->_pdp->PointuvFromPoint(ptuv, ptxy); return (_ptStart.u == ptuv.u && _ptStart.v == ptuv.v); } /* * CMagellan::MagellanStartMButtonScroll * * @mfunc * Called when we get an mButtonDown message. Initiates tracking * of the mouse which in turn will scroll at various speeds based * on how far the user moves the mouse from the mDownPt. * * @rdesc * TRUE if the caller should capture the mouse. * */ BOOL CMagellan::MagellanStartMButtonScroll(CTxtEdit &ed, POINT ptxy) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::MagellanStartMButtonScroll"); RECTUV rc; BOOL fCapture = FALSE; CDisplay *pdp = ed._pdp; POINTUV pt; pdp->PointuvFromPoint(pt, ptxy); pdp->GetViewRect(rc); // skip scroll bars, etc. if (PtInRect(&rc, pt) && !_fMButtonScroll) { fCapture = TRUE; _ID_currMDownBMP = 0; _fMButtonScroll = TRUE; // Now tracking... _ptStart = pt; _fLastScrollWasRoll = FALSE; // Differentiate type. CheckInstallMagellanTrackTimer(ed); // Fire up timer... } return fCapture; } /* * CMagellan::MagellanEndMButtonScroll * * @mfunc * Finished tracking mButtonDown magellan scroll, finish up state. * */ VOID CMagellan::MagellanEndMButtonScroll(CTxtEdit &ed) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::MagellanEndMButtonScroll"); CDisplay *pdp = ed._pdp; _fMButtonScroll = FALSE; CheckRemoveMagellanUpdaterTimer(ed); // Remove timer... pdp->FinishSmoothVScroll(); // So smooth scroll stops. InvertMagellanDownBMP(pdp, FALSE, NULL); // Turn it off. if (_MagellanMDownBMP) // Release bitmap. { DeleteObject(_MagellanMDownBMP); _MagellanMDownBMP = NULL; _ID_currMDownBMP = 0; } } /* * CMagellan::MagellanRollScroll * * @mfunc * Handle the Magellan WM_MOUSEROLLER message. This routine has global, internal * state that allows the number of lines scrolled to increase if the user continues * to roll the wheel in rapid succession. * */ VOID CMagellan::MagellanRollScroll (CDisplay *pdp, int direction, WORD cLines, int speedNum, int speedDenom, BOOL fAdditive) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::MagellanRollScroll"); static DWORD lastFastRollTime; static int lastDirection; static INT cFastRolls; DWORD tickCount = GetTickCount(); if (!_fMButtonScroll) { // start/continue fast if (tickCount - lastFastRollTime < FAST_ROLL_SCROLL_TRANSITION_TICKS || ((lastDirection ^ (direction < 0 ? -1 : 1)) == 0 // or, same sign && _fLastScrollWasRoll // and in slow. && pdp->IsSmoothVScolling())) { cFastRolls++; if (cFastRolls > FASTER_ROLL2_COUNT) // make faster. cLines <<= 1; else if (cFastRolls > FASTER_ROLL1_COUNT) // make fast cLines += 1; speedNum = cLines; // Cancel smooth // effect. lastFastRollTime = tickCount; } else cFastRolls = 0; pdp->SmoothVScroll(direction, cLines, speedNum, speedDenom, TRUE); _fLastScrollWasRoll = TRUE; lastDirection = (direction < 0) ? -1 : 1; } } /* * CMagellan::CheckInstallMagellanTrackTimer * * @mfunc * Install a timing task that will allow TrackUpdateMagellanMButtonDown * To be periodically called. * * @devnote * The CTxtEdit class handles all WM_TIMER dispatches, so there's glue there * to call our magellan routine. * */ VOID CMagellan::CheckInstallMagellanTrackTimer (CTxtEdit &ed) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::CheckInstallMagellanTrackTimer"); ed.TxSetTimer(RETID_MAGELLANTRACK, cmsecScrollInterval); } /* * CMagellan::CheckRemoveMagellanUpdaterTimer * * @mfunc * Remove the timing task that dispatches to TrackUpdateMagellanMButtonDown. * */ VOID CMagellan::CheckRemoveMagellanUpdaterTimer (CTxtEdit &ed) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::CheckRemoveMagellanUpdaterTimer"); ed.TxKillTimer(RETID_MAGELLANTRACK); } /* * CMagellan::TrackUpdateMagellanMButtonDown * * @mfunc * After mButtonDown capture, a periodic WM_TIMER calls this from OnTxTimer(). The cursor * is tracked to determine direction, speed, and in dead zone (not moving). * Movement is dispacted to CDisplay. The cursor is set to the appropriate * direction cusor, and the mButtonDown point BMP is drawn. */ VOID CMagellan::TrackUpdateMagellanMButtonDown (CTxtEdit &ed, POINT ptxy) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::TrackUpdateMagellanMButtonDown"); RECTUV deadZone; POINTUV pt; LONG inflate; SHORT IDC_mScrollCursor = 0, IDC_mDeadScrollCursor = 0; BOOL fDoUScroll = FALSE, fDoVScroll = FALSE; CDisplay *pdp = ed._pdp; pdp->PointuvFromPoint(pt, ptxy); Assert (_fMButtonScroll); deadZone.top = deadZone.bottom = _ptStart.v; deadZone.left = deadZone.right = _ptStart.u; inflate = pdp->LYtoDY(DEAD_ZONE_TWIPS); InflateRect(&deadZone, inflate, inflate); // // Calculate direction to scroll and what cusor to display. // // By numbering a compass like the following, we can easily calc the index into // the scrollCursors array to get the proper cursor: // // North = 1 // NW = 7 NE = 4 // West = 6 East = 3 // SW = 8 SE = 5 // South = 2 // if (pdp->IsVScrollEnabled()) // Can scroll vertically? { IDC_mDeadScrollCursor = 1; if (pt.v < deadZone.top || pt.v > deadZone.bottom) { fDoVScroll = TRUE; IDC_mScrollCursor = (pt.v < _ptStart.v) ? 1 : 2; } } // FUTURE (alexgo): allow magellan scrolling when no scrollbar? if(pdp->IsUScrollEnabled() && ed.TxGetScrollBars() & WS_HSCROLL) // Can scroll horizontally? { IDC_mDeadScrollCursor |= 2; if (pt.u < deadZone.left || pt.u > deadZone.right) { fDoUScroll = TRUE; IDC_mScrollCursor += (pt.u < _ptStart.u) ? 6 : 3; } } //Convert cursors from logical to visual switch(pdp->GetTflow()) { case tflowSW: IDC_mScrollCursor = mapCursorTflowSW[IDC_mScrollCursor]; break; case tflowWN: IDC_mScrollCursor = mapCursorTflowWN[IDC_mScrollCursor]; break; case tflowNE: IDC_mScrollCursor = mapCursorTflowNE[IDC_mScrollCursor]; break; } if (IsUVerticalTflow(pdp->GetTflow())) { if (IDC_mDeadScrollCursor == 2) IDC_mDeadScrollCursor = 1; else if (IDC_mDeadScrollCursor == 1) IDC_mDeadScrollCursor = 2; } //Review (keithcu) A little goofy... IDC_mScrollCursor = scrollCursors[IDC_mScrollCursor]; if (mDownBMPs[IDC_mDeadScrollCursor] != _ID_currMDownBMP) { if (_MagellanMDownBMP) // Undraw old BMP. { InvertMagellanDownBMP(pdp, FALSE, NULL); DeleteObject (_MagellanMDownBMP); _MagellanMDownBMP = NULL; } // Draw new BMP. _ID_currMDownBMP = mDownBMPs[IDC_mDeadScrollCursor]; _MagellanMDownBMP = LoadBitmap (hinstRE, MAKEINTRESOURCE (_ID_currMDownBMP)); InvertMagellanDownBMP(pdp, TRUE, NULL); } // Moved out of dead zone? if (fDoVScroll || fDoUScroll) // time to scroll... { RECTUV rcClient; POINTUV ptCenter, ptAuto; ed.TxGetClientRect(&rcClient); // Get our client rect. LONG dupClient = rcClient.right - rcClient.left; LONG dvpClient = rcClient.bottom - rcClient.top; ptCenter.u = rcClient.left + (dupClient >> 1); ptCenter.v = rcClient.top + (dvpClient >> 1); LONG uInset = (dupClient >> 1) - 2; // Get inset to center // Map origin to rcClient. LONG dup = pt.u - _ptStart.u; LONG dvp = pt.v - _ptStart.v; ptAuto.u = ptCenter.u + dup; ptAuto.v = ptCenter.v + dvp; // This formula is a bit magical, but here goes. What // we want is the sub-linear part of an exponential function. // In other words, smallish distances should produce pixel // by pixel scrolling. // // So the formula we use is (x^2) / dvpClient, where x is dvpClient scaled // to be in units of dvpClient (i.e. 5yDiff/2). The final 10* // multiplier is to shift all the values leftward so we can // do this in integer arithmetic. LONG num = MulDiv(10 * 25 * dvp, dvp, dvpClient * 4); if(!num) num = 1; if (fDoVScroll) pdp->SmoothVScroll(_ptStart.v - pt.v, 0, num, 10 * dvpClient, FALSE); if (fDoUScroll) // u direction scrolling? { ptAuto.v = ptCenter.v; // Prevent v dir scrolling. // Do u dir scroll. pdp->AutoScroll(ptAuto, uInset, 0); } // notify through the messagefilter that we scrolled if ((ed._dwEventMask & ENM_SCROLLEVENTS) && (fDoUScroll || fDoVScroll)) { MSGFILTER msgfltr; ZeroMemory(&msgfltr, sizeof(MSGFILTER)); if (fDoUScroll) { msgfltr.msg = WM_HSCROLL; msgfltr.wParam = (dup > 0 ? SB_LINERIGHT : SB_LINELEFT); ed._phost->TxNotify(EN_MSGFILTER, &msgfltr); } if (fDoVScroll) { msgfltr.msg = WM_VSCROLL; msgfltr.wParam = (dvp > 0 ? SB_LINEDOWN : SB_LINEUP); ed._phost->TxNotify(EN_MSGFILTER, &msgfltr); } } } else // No scroll in dead zone. { IDC_mScrollCursor = noScrollCursors[IDC_mDeadScrollCursor]; pdp->FinishSmoothVScroll(); // Finish up last line. } ed._phost->TxSetCursor(IDC_mScrollCursor ? LoadCursor(hinstRE, MAKEINTRESOURCE(IDC_mScrollCursor)) : ed._hcurArrow, FALSE); } /* * BOOL CMagellan::InvertMagellanDownBMP * * @mfunc * Magellan mouse UI requires that the mouse down point draw * and maintain a bitmap in order to help the user control scroll speed. * * @devnote * This routine is designed to be nested. It also handles WM_PAINT updates * when the repaintDC is passed in. Because there is no support for multiple * cursors in the operating system, all WM_PAINT and ScrollWindow redraws * must temporarily turn off the BMP and then redraw it. This gives the * BMAP a flicker. * * @rdesc * TRUE if the bitmap was previously drawn. */ BOOL CMagellan::InvertMagellanDownBMP(CDisplay *pdp, BOOL fTurnOn, HDC repaintDC) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::InvertMagellanDownBMP"); BOOL fOldState = _fMagellanBitMapOn; Assert (pdp); if (fOldState != fTurnOn) { if (_MagellanMDownBMP) { BITMAP bm; HDC hdcMem, screenDC; POINT ptSize, ptOrg; screenDC = (repaintDC != NULL) ? repaintDC : pdp->GetDC(); if (screenDC) { hdcMem = CreateCompatibleDC(screenDC); if (hdcMem) { SelectObject(hdcMem, _MagellanMDownBMP); if (W32->GetObject(_MagellanMDownBMP, sizeof(BITMAP), &bm)) { ptSize.x = bm.bmWidth; ptSize.y = bm.bmHeight; DPtoLP(screenDC, &ptSize, 1); ptOrg.x = 0; ptOrg.y = 0; DPtoLP(hdcMem, &ptOrg, 1); POINT ptBitmap; pdp->PointFromPointuv(ptBitmap, _ptStart); BitBlt(screenDC, ptBitmap.x - (ptSize.x >> 1) - 1, ptBitmap.y - (ptSize.y >> 1) + 1, ptSize.x, ptSize.y, hdcMem, ptOrg.x, ptOrg.y, 0x00990066 /* NOTXOR */); _fMagellanBitMapOn = !fOldState; } DeleteDC(hdcMem); } if (repaintDC == NULL) pdp->ReleaseDC(screenDC); } } } return fOldState; } ////////////////////////// CMagellanBMPStateWrap class. /* * CMagellanBMPStateWrap:: CMagellanBMPStateWrap * * @mfunc * Handles the state of whether to redraw the Magellan BMP as well as * repaints due to WM_PAINT. * * @devnote * This class is akin to smart pointer wrapper class idioms, in that * no matter how a routine exits the correct state of whether the * BMP is drawn will be maintined. */ CMagellanBMPStateWrap:: CMagellanBMPStateWrap(CTxtEdit &ed, HDC repaintDC) : _ed(ed), _repaintDC(repaintDC) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellanBMPStateWrap:: CMagellanBMPStateWrap"); BOOL fRepaint; fRepaint = repaintDC != NULL && _ed.mouse._fMagellanBitMapOn != 0; _fMagellanState = fRepaint || _ed.mouse.InvertMagellanDownBMP(_ed._pdp, FALSE, NULL); _ed.mouse._fMagellanBitMapOn = FALSE; } CMagellanBMPStateWrap::~CMagellanBMPStateWrap() { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellanBMPStateWrap::~CMagellanBMPStateWrap"); _ed.mouse.InvertMagellanDownBMP(_ed._pdp, _fMagellanState, _repaintDC); } #endif // !defined(NOMAGELLAN)