/* * @doc INTERNAL * * @module magellan.cpp -- Handle magellan mouse. | * * For REC 2, Magellan mouse can roll scroll and mButtonDown drag scroll. * * Owner: * Jon Matousek - 1996 * * 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 /* * 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 mDownPt ) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::MagellanStartMButtonScroll"); RECT rc; BOOL fCapture = FALSE; CDisplay *pdp; pdp = ed._pdp; if ( pdp) { pdp->GetViewRect(rc); // skip scroll bars, etc. if ( PtInRect(&rc, mDownPt) && !_fMButtonScroll ) { fCapture = TRUE; _ID_currMDownBMP = 0; _fMButtonScroll = TRUE; // Now tracking... _zMouseScrollStartPt = mDownPt; _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; _fMButtonScroll = FALSE; CheckRemoveMagellanUpdaterTimer ( ed ); // Remove timer... pdp = ed._pdp; if ( pdp ) { 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 && pdp ) { // 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; } // Do the scroll. 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 mousePt ) { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::TrackUpdateMagellanMButtonDown"); RECT deadZone, rcClient; WORD wide, tall, xInset, yInset; POINT pt, center; LONG xDiff, yDiff, inflate, target; SHORT IDC_mScrollCursor, IDC_mDeadScrollCursor; BOOL fDoHScroll, fDoVScroll; BOOL fFastScroll = FALSE; CDisplay *pdp; pdp = ed._pdp; Assert ( _fMButtonScroll ); Assert ( pdp ); // Calc dead zone rect. deadZone.top = deadZone.bottom = _zMouseScrollStartPt.y; deadZone.left = deadZone.right = _zMouseScrollStartPt.x; 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 // IDC_mScrollCursor = 0; IDC_mDeadScrollCursor = 0; fDoVScroll = FALSE; fDoHScroll = FALSE; if ( pdp->IsVScrollEnabled() ) // Can scroll vertically? { IDC_mDeadScrollCursor = 1; if ( mousePt.y < deadZone.top || mousePt.y > deadZone.bottom ) { fDoVScroll = TRUE; IDC_mScrollCursor = ( mousePt.y < _zMouseScrollStartPt.y ) ? 1 : 2; } } // FUTURE (alexgo): allow magellan scrolling even for single line // controls with no scrollbar. For now, however, that change is too // risky, so we look explicity for a scrollbar. if( pdp->IsHScrollEnabled() && ed.TxGetScrollBars() & WS_HSCROLL ) // Can scroll horizontally? { IDC_mDeadScrollCursor |= 2; if ( mousePt.x < deadZone.left || mousePt.x > deadZone.right ) { fDoHScroll = TRUE; IDC_mScrollCursor += ( mousePt.x < _zMouseScrollStartPt.x ) ? 6 : 3; } } SHORT scrollCursors[] = { // Cursor for various 0, // directions. IDC_SCROLLNORTH, IDC_SCROLLSOUTH, IDC_SCROLLEAST, IDC_SCROLLNE, IDC_SCROLLSE, IDC_SCROLLWEST, IDC_SCROLLNW, IDC_SCROLLSW }; IDC_mScrollCursor = scrollCursors[IDC_mScrollCursor]; SHORT mDownBMPs[] = { // mButtonDown origin BMPs. 0, IDB_1DVSCROL, IDB_1DHSCROL, IDB_2DSCROL }; // BMAP-mButtonDown for UI 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 || fDoHScroll ) // time to scroll... { // Prepare data for // scrolling routines. ed.TxGetClientRect(&rcClient); // Get our client rect. wide = rcClient.right - rcClient.left; tall = rcClient.bottom - rcClient.top; // Calc center of rcClient. center.x = rcClient.left + (wide >> 1); center.y = rcClient.top + (tall >> 1); xInset = (wide >> 1) - 2; // Get inset to center yInset = (tall >> 1) - 2; // about rcClient. // Map origin to rcClient. xDiff = mousePt.x - _zMouseScrollStartPt.x; yDiff = mousePt.y - _zMouseScrollStartPt.y; pt.x = center.x + xDiff; pt.y = center.y + yDiff; // Determine scroll speed. target = (tall * 2) / 5; // target is 40% of screen // height. Past that, we // scroll page at a time. yDiff = abs(yDiff); if ( yDiff >= target ) // Fast scroll? { fFastScroll = TRUE; // Stop mutually exclusive pdp->CheckRemoveSmoothVScroll(); // scroll type. // Fast line scroll. if ( fDoVScroll ) // Vertically a page at a time. { pdp->VScroll( ( _zMouseScrollStartPt.y - mousePt.y < 0 ) ? SB_PAGEDOWN : SB_PAGEUP, 0 ); } if ( fDoHScroll ) { pt.y = center.y; // Prevent y dir scrolling. // Do x dir scroll. pdp->AutoScroll( pt, xInset, 0 ); } } else // Smooth scroll. { // Start, or continue // smooth vertical scrolling. // 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. At 40% of the screen height, however, // we should be srolling by a page at a time (tall # of pixels). // // So the formula we use is (x^2)/tall, where x is yDiff scaled // to be in units of tall (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*yDiff/4, yDiff, tall); if( !num ) { num = 1; } if ( fDoVScroll ) { pdp->SmoothVScroll ( _zMouseScrollStartPt.y - mousePt.y, 0, num, 10*tall, FALSE ); } // x direction scrolling? if ( fDoHScroll ) { pt.y = center.y; // Prevent y dir scrolling. // Do x dir scroll. pdp->AutoScroll( pt, xInset, 0 ); } } // notify through the messagefilter that we scrolled if ((ed._dwEventMask & ENM_SCROLLEVENTS) && (fDoHScroll || fDoVScroll)) { MSGFILTER msgfltr; ZeroMemory(&msgfltr, sizeof(MSGFILTER)); if (fDoHScroll) { msgfltr.msg = WM_HSCROLL; msgfltr.wParam = fFastScroll ? (xDiff > 0 ? SB_PAGERIGHT: SB_PAGELEFT): (xDiff > 0 ? SB_LINERIGHT: SB_LINELEFT); } else { msgfltr.msg = WM_VSCROLL; msgfltr.wParam = fFastScroll ? (yDiff > 0 ? SB_PAGEDOWN: SB_PAGEUP): (yDiff > 0 ? SB_LINEDOWN: SB_LINEUP); } msgfltr.lParam = NULL; // we don't check the result of this call -- // it's not a message we received and we're not going to // process it any further ed._phost->TxNotify(EN_MSGFILTER, &msgfltr); } } else { // No scroll in dead zone. SHORT noScrollCursors[] = { 0, IDC_NOSCROLLV, IDC_NOSCROLLH, IDC_NOSCROLLVH }; // Set dead-zone cursor. IDC_mScrollCursor = noScrollCursors[IDC_mDeadScrollCursor]; pdp->FinishSmoothVScroll(); // Finish up last line. } // Set magellan cursor. 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 ); SetMapMode ( hdcMem, GetMapMode (screenDC) ); if ( GetObjectA( _MagellanMDownBMP, sizeof(BITMAP), (LPVOID) &bm) ) { ptSize.x = bm.bmWidth; ptSize.y = bm.bmHeight; DPtoLP ( screenDC, &ptSize, 1 ); ptOrg.x = 0; ptOrg.y = 0; DPtoLP( hdcMem, &ptOrg, 1 ); BitBlt( screenDC, _zMouseScrollStartPt.x - (ptSize.x >> 1) - 1, _zMouseScrollStartPt.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)