531 lines
14 KiB
C++
531 lines
14 KiB
C++
/*
|
|
* @doc INTERNAL
|
|
*
|
|
* @module magellan.cpp -- Handle magellan mouse. |
|
|
*
|
|
* For REC 2, Magellan mouse can roll scroll and mButtonDown drag scroll.
|
|
*
|
|
* Owner: <nl>
|
|
* 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)
|