windows-nt/Source/XPSP1/NT/windows/richedit/re30/magellan.cpp
2020-09-26 16:20:57 +08:00

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)