Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

530 lines
14 KiB

  1. /*
  2. * @doc INTERNAL
  3. *
  4. * @module magellan.cpp -- Handle magellan mouse. |
  5. *
  6. * For REC 2, Magellan mouse can roll scroll and mButtonDown drag scroll.
  7. *
  8. * Owner: <nl>
  9. * Jon Matousek - 1996
  10. *
  11. * Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
  12. */
  13. #include "_common.h"
  14. #if !defined(NOMAGELLAN)
  15. #include "_edit.h"
  16. #include "_disp.h"
  17. #include "_magelln.h"
  18. ASSERTDATA
  19. /*
  20. * CMagellan::MagellanStartMButtonScroll
  21. *
  22. * @mfunc
  23. * Called when we get an mButtonDown message. Initiates tracking
  24. * of the mouse which in turn will scroll at various speeds based
  25. * on how far the user moves the mouse from the mDownPt.
  26. *
  27. * @rdesc
  28. * TRUE if the caller should capture the mouse.
  29. *
  30. */
  31. BOOL CMagellan::MagellanStartMButtonScroll( CTxtEdit &ed, POINT mDownPt )
  32. {
  33. TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::MagellanStartMButtonScroll");
  34. RECT rc;
  35. BOOL fCapture = FALSE;
  36. CDisplay *pdp;
  37. pdp = ed._pdp;
  38. if ( pdp)
  39. {
  40. pdp->GetViewRect(rc); // skip scroll bars, etc.
  41. if ( PtInRect(&rc, mDownPt) && !_fMButtonScroll )
  42. {
  43. fCapture = TRUE;
  44. _ID_currMDownBMP = 0;
  45. _fMButtonScroll = TRUE; // Now tracking...
  46. _zMouseScrollStartPt = mDownPt;
  47. _fLastScrollWasRoll = FALSE; // Differentiate type.
  48. CheckInstallMagellanTrackTimer ( ed ); // Fire up timer...
  49. }
  50. }
  51. return fCapture;
  52. }
  53. /*
  54. * CMagellan::MagellanEndMButtonScroll
  55. *
  56. * @mfunc
  57. * Finished tracking mButtonDown magellan scroll, finish up state.
  58. *
  59. */
  60. VOID CMagellan::MagellanEndMButtonScroll( CTxtEdit &ed )
  61. {
  62. TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::MagellanEndMButtonScroll");
  63. CDisplay *pdp;
  64. _fMButtonScroll = FALSE;
  65. CheckRemoveMagellanUpdaterTimer ( ed ); // Remove timer...
  66. pdp = ed._pdp;
  67. if ( pdp )
  68. {
  69. pdp->FinishSmoothVScroll(); // So smooth scroll stops.
  70. InvertMagellanDownBMP(pdp, FALSE, NULL); // Turn it off.
  71. }
  72. if ( _MagellanMDownBMP ) // Release bitmap.
  73. {
  74. DeleteObject( _MagellanMDownBMP );
  75. _MagellanMDownBMP = NULL;
  76. _ID_currMDownBMP = 0;
  77. }
  78. }
  79. /*
  80. * CMagellan::MagellanRollScroll
  81. *
  82. * @mfunc
  83. * Handle the Magellan WM_MOUSEROLLER message. This routine has global, internal
  84. * state that allows the number of lines scrolled to increase if the user continues
  85. * to roll the wheel in rapid succession.
  86. *
  87. */
  88. VOID CMagellan::MagellanRollScroll ( CDisplay *pdp, int direction, WORD cLines,
  89. int speedNum, int speedDenom, BOOL fAdditive )
  90. {
  91. TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::MagellanRollScroll");
  92. static DWORD lastFastRollTime;
  93. static int lastDirection;
  94. static INT cFastRolls;
  95. DWORD tickCount = GetTickCount();
  96. if ( !_fMButtonScroll && pdp )
  97. {
  98. // start/continue fast
  99. if ( tickCount - lastFastRollTime < FAST_ROLL_SCROLL_TRANSITION_TICKS
  100. || ((lastDirection ^ (direction < 0 ? -1 : 1)) == 0 // or, same sign
  101. && _fLastScrollWasRoll // and in slow.
  102. && pdp->IsSmoothVScolling() ))
  103. {
  104. cFastRolls++;
  105. if ( cFastRolls > FASTER_ROLL2_COUNT ) // make faster.
  106. cLines <<= 1;
  107. else if ( cFastRolls > FASTER_ROLL1_COUNT ) // make fast
  108. cLines += 1;
  109. speedNum = cLines; // Cancel smooth
  110. // effect.
  111. lastFastRollTime = tickCount;
  112. }
  113. else
  114. {
  115. cFastRolls = 0;
  116. } // Do the scroll.
  117. pdp->SmoothVScroll( direction, cLines, speedNum, speedDenom, TRUE);
  118. _fLastScrollWasRoll = TRUE;
  119. lastDirection = (direction < 0) ? -1 : 1;
  120. }
  121. }
  122. /*
  123. * CMagellan::CheckInstallMagellanTrackTimer
  124. *
  125. * @mfunc
  126. * Install a timing task that will allow TrackUpdateMagellanMButtonDown
  127. * To be periodically called.
  128. *
  129. * @devnote
  130. * The CTxtEdit class handles all WM_TIMER dispatches, so there's glue there
  131. * to call our magellan routine.
  132. *
  133. */
  134. VOID CMagellan::CheckInstallMagellanTrackTimer ( CTxtEdit &ed )
  135. {
  136. TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::CheckInstallMagellanTrackTimer");
  137. ed.TxSetTimer(RETID_MAGELLANTRACK, cmsecScrollInterval);
  138. }
  139. /*
  140. * CMagellan::CheckRemoveMagellanUpdaterTimer
  141. *
  142. * @mfunc
  143. * Remove the timing task that dispatches to TrackUpdateMagellanMButtonDown.
  144. *
  145. */
  146. VOID CMagellan::CheckRemoveMagellanUpdaterTimer ( CTxtEdit &ed )
  147. {
  148. TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::CheckRemoveMagellanUpdaterTimer");
  149. ed.TxKillTimer(RETID_MAGELLANTRACK);
  150. }
  151. /*
  152. * CMagellan::TrackUpdateMagellanMButtonDown
  153. *
  154. * @mfunc
  155. * After mButtonDown capture, a periodic WM_TIMER calls this from OnTxTimer(). The cursor
  156. * is tracked to determine direction, speed, and in dead zone (not moving).
  157. * Movement is dispacted to CDisplay. The cursor is set to the appropriate
  158. * direction cusor, and the mButtonDown point BMP is drawn.
  159. */
  160. VOID CMagellan::TrackUpdateMagellanMButtonDown ( CTxtEdit &ed, POINT mousePt )
  161. {
  162. TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::TrackUpdateMagellanMButtonDown");
  163. RECT deadZone, rcClient;
  164. WORD wide, tall, xInset, yInset;
  165. POINT pt, center;
  166. LONG xDiff, yDiff, inflate, target;
  167. SHORT IDC_mScrollCursor, IDC_mDeadScrollCursor;
  168. BOOL fDoHScroll, fDoVScroll;
  169. BOOL fFastScroll = FALSE;
  170. CDisplay *pdp;
  171. pdp = ed._pdp;
  172. Assert ( _fMButtonScroll );
  173. Assert ( pdp );
  174. // Calc dead zone rect.
  175. deadZone.top = deadZone.bottom = _zMouseScrollStartPt.y;
  176. deadZone.left = deadZone.right = _zMouseScrollStartPt.x;
  177. inflate = pdp->LYtoDY(DEAD_ZONE_TWIPS);
  178. InflateRect(&deadZone, inflate, inflate);
  179. //
  180. // Calculate direction to scroll and what cusor to display.
  181. //
  182. // By numbering a compass like the following, we can easily calc the index into
  183. // the scrollCursors array to get the proper cursor:
  184. //
  185. // North = 1
  186. // NW = 7 NE = 4
  187. // West = 6 East = 3
  188. // SW = 8 SE = 5
  189. // South = 2
  190. //
  191. IDC_mScrollCursor = 0;
  192. IDC_mDeadScrollCursor = 0;
  193. fDoVScroll = FALSE;
  194. fDoHScroll = FALSE;
  195. if ( pdp->IsVScrollEnabled() ) // Can scroll vertically?
  196. {
  197. IDC_mDeadScrollCursor = 1;
  198. if ( mousePt.y < deadZone.top || mousePt.y > deadZone.bottom )
  199. {
  200. fDoVScroll = TRUE;
  201. IDC_mScrollCursor = ( mousePt.y < _zMouseScrollStartPt.y ) ? 1 : 2;
  202. }
  203. }
  204. // FUTURE (alexgo): allow magellan scrolling even for single line
  205. // controls with no scrollbar. For now, however, that change is too
  206. // risky, so we look explicity for a scrollbar.
  207. if( pdp->IsHScrollEnabled() && ed.TxGetScrollBars() & WS_HSCROLL ) // Can scroll horizontally?
  208. {
  209. IDC_mDeadScrollCursor |= 2;
  210. if ( mousePt.x < deadZone.left || mousePt.x > deadZone.right )
  211. {
  212. fDoHScroll = TRUE;
  213. IDC_mScrollCursor += ( mousePt.x < _zMouseScrollStartPt.x ) ? 6 : 3;
  214. }
  215. }
  216. SHORT scrollCursors[] = { // Cursor for various
  217. 0, // directions.
  218. IDC_SCROLLNORTH,
  219. IDC_SCROLLSOUTH,
  220. IDC_SCROLLEAST,
  221. IDC_SCROLLNE,
  222. IDC_SCROLLSE,
  223. IDC_SCROLLWEST,
  224. IDC_SCROLLNW,
  225. IDC_SCROLLSW
  226. };
  227. IDC_mScrollCursor = scrollCursors[IDC_mScrollCursor];
  228. SHORT mDownBMPs[] = { // mButtonDown origin BMPs.
  229. 0,
  230. IDB_1DVSCROL,
  231. IDB_1DHSCROL,
  232. IDB_2DSCROL
  233. };
  234. // BMAP-mButtonDown for UI
  235. if ( mDownBMPs[IDC_mDeadScrollCursor] != _ID_currMDownBMP )
  236. {
  237. if ( _MagellanMDownBMP ) // Undraw old BMP.
  238. {
  239. InvertMagellanDownBMP( pdp, FALSE, NULL );
  240. DeleteObject ( _MagellanMDownBMP );
  241. _MagellanMDownBMP = NULL;
  242. }
  243. // Draw new BMP.
  244. _ID_currMDownBMP = mDownBMPs[IDC_mDeadScrollCursor];
  245. _MagellanMDownBMP = LoadBitmap ( hinstRE, MAKEINTRESOURCE ( _ID_currMDownBMP ) );
  246. InvertMagellanDownBMP( pdp, TRUE, NULL );
  247. }
  248. // Moved out of dead zone?
  249. if ( fDoVScroll || fDoHScroll ) // time to scroll...
  250. {
  251. // Prepare data for
  252. // scrolling routines.
  253. ed.TxGetClientRect(&rcClient); // Get our client rect.
  254. wide = rcClient.right - rcClient.left;
  255. tall = rcClient.bottom - rcClient.top;
  256. // Calc center of rcClient.
  257. center.x = rcClient.left + (wide >> 1);
  258. center.y = rcClient.top + (tall >> 1);
  259. xInset = (wide >> 1) - 2; // Get inset to center
  260. yInset = (tall >> 1) - 2; // about rcClient.
  261. // Map origin to rcClient.
  262. xDiff = mousePt.x - _zMouseScrollStartPt.x;
  263. yDiff = mousePt.y - _zMouseScrollStartPt.y;
  264. pt.x = center.x + xDiff;
  265. pt.y = center.y + yDiff;
  266. // Determine scroll speed.
  267. target = (tall * 2) / 5; // target is 40% of screen
  268. // height. Past that, we
  269. // scroll page at a time.
  270. yDiff = abs(yDiff);
  271. if ( yDiff >= target ) // Fast scroll?
  272. {
  273. fFastScroll = TRUE;
  274. // Stop mutually exclusive
  275. pdp->CheckRemoveSmoothVScroll(); // scroll type.
  276. // Fast line scroll.
  277. if ( fDoVScroll ) // Vertically a page at a time.
  278. {
  279. pdp->VScroll( ( _zMouseScrollStartPt.y - mousePt.y < 0 ) ? SB_PAGEDOWN : SB_PAGEUP, 0 );
  280. }
  281. if ( fDoHScroll )
  282. {
  283. pt.y = center.y; // Prevent y dir scrolling.
  284. // Do x dir scroll.
  285. pdp->AutoScroll( pt, xInset, 0 );
  286. }
  287. }
  288. else // Smooth scroll.
  289. {
  290. // Start, or continue
  291. // smooth vertical scrolling.
  292. // This formula is a bit magical, but here goes. What
  293. // we want is the sub-linear part of an exponential function.
  294. // In other words, smallish distances should produce pixel
  295. // by pixel scrolling. At 40% of the screen height, however,
  296. // we should be srolling by a page at a time (tall # of pixels).
  297. //
  298. // So the formula we use is (x^2)/tall, where x is yDiff scaled
  299. // to be in units of tall (i.e. 5yDiff/2). The final 10*
  300. // multiplier is to shift all the values leftward so we can
  301. // do this in integer arithmetic.
  302. LONG num = MulDiv(10*25*yDiff/4, yDiff, tall);
  303. if( !num )
  304. {
  305. num = 1;
  306. }
  307. if ( fDoVScroll )
  308. {
  309. pdp->SmoothVScroll ( _zMouseScrollStartPt.y - mousePt.y,
  310. 0, num, 10*tall, FALSE );
  311. }
  312. // x direction scrolling?
  313. if ( fDoHScroll )
  314. {
  315. pt.y = center.y; // Prevent y dir scrolling.
  316. // Do x dir scroll.
  317. pdp->AutoScroll( pt, xInset, 0 );
  318. }
  319. }
  320. // notify through the messagefilter that we scrolled
  321. if ((ed._dwEventMask & ENM_SCROLLEVENTS) && (fDoHScroll || fDoVScroll))
  322. {
  323. MSGFILTER msgfltr;
  324. ZeroMemory(&msgfltr, sizeof(MSGFILTER));
  325. if (fDoHScroll)
  326. {
  327. msgfltr.msg = WM_HSCROLL;
  328. msgfltr.wParam = fFastScroll ?
  329. (xDiff > 0 ? SB_PAGERIGHT: SB_PAGELEFT):
  330. (xDiff > 0 ? SB_LINERIGHT: SB_LINELEFT);
  331. }
  332. else
  333. {
  334. msgfltr.msg = WM_VSCROLL;
  335. msgfltr.wParam = fFastScroll ?
  336. (yDiff > 0 ? SB_PAGEDOWN: SB_PAGEUP):
  337. (yDiff > 0 ? SB_LINEDOWN: SB_LINEUP);
  338. }
  339. msgfltr.lParam = NULL;
  340. // we don't check the result of this call --
  341. // it's not a message we received and we're not going to
  342. // process it any further
  343. ed._phost->TxNotify(EN_MSGFILTER, &msgfltr);
  344. }
  345. }
  346. else
  347. { // No scroll in dead zone.
  348. SHORT noScrollCursors[] = {
  349. 0,
  350. IDC_NOSCROLLV,
  351. IDC_NOSCROLLH,
  352. IDC_NOSCROLLVH
  353. }; // Set dead-zone cursor.
  354. IDC_mScrollCursor = noScrollCursors[IDC_mDeadScrollCursor];
  355. pdp->FinishSmoothVScroll(); // Finish up last line.
  356. }
  357. // Set magellan cursor.
  358. ed._phost->TxSetCursor(IDC_mScrollCursor ?
  359. LoadCursor(hinstRE, MAKEINTRESOURCE(IDC_mScrollCursor)) :
  360. ed._hcurArrow, FALSE);
  361. }
  362. /*
  363. * BOOL CMagellan::InvertMagellanDownBMP
  364. *
  365. * @mfunc
  366. * Magellan mouse UI requires that the mouse down point draw
  367. * and maintain a bitmap in order to help the user control scroll speed.
  368. *
  369. * @devnote
  370. * This routine is designed to be nested. It also handles WM_PAINT updates
  371. * when the repaintDC is passed in. Because there is no support for multiple
  372. * cursors in the operating system, all WM_PAINT and ScrollWindow redraws
  373. * must temporarily turn off the BMP and then redraw it. This gives the
  374. * BMAP a flicker.
  375. *
  376. * @rdesc
  377. * TRUE if the bitmap was previously drawn.
  378. */
  379. BOOL CMagellan::InvertMagellanDownBMP( CDisplay *pdp, BOOL fTurnOn, HDC repaintDC )
  380. {
  381. TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellan::InvertMagellanDownBMP");
  382. BOOL fOldState = _fMagellanBitMapOn;
  383. Assert (pdp);
  384. if ( fOldState != fTurnOn )
  385. {
  386. if ( _MagellanMDownBMP )
  387. {
  388. BITMAP bm;
  389. HDC hdcMem, screenDC;
  390. POINT ptSize, ptOrg;
  391. screenDC = (repaintDC != NULL) ? repaintDC : pdp->GetDC();
  392. if ( screenDC )
  393. {
  394. hdcMem = CreateCompatibleDC ( screenDC );
  395. if ( hdcMem )
  396. {
  397. SelectObject ( hdcMem, _MagellanMDownBMP );
  398. SetMapMode ( hdcMem, GetMapMode (screenDC) );
  399. if ( GetObjectA( _MagellanMDownBMP, sizeof(BITMAP), (LPVOID) &bm) )
  400. {
  401. ptSize.x = bm.bmWidth;
  402. ptSize.y = bm.bmHeight;
  403. DPtoLP ( screenDC, &ptSize, 1 );
  404. ptOrg.x = 0;
  405. ptOrg.y = 0;
  406. DPtoLP( hdcMem, &ptOrg, 1 );
  407. BitBlt( screenDC,
  408. _zMouseScrollStartPt.x - (ptSize.x >> 1) - 1,
  409. _zMouseScrollStartPt.y - (ptSize.y >> 1) + 1,
  410. ptSize.x, ptSize.y,
  411. hdcMem, ptOrg.x, ptOrg.y, 0x00990066 /* NOTXOR */ );
  412. _fMagellanBitMapOn = !fOldState;
  413. }
  414. DeleteDC( hdcMem );
  415. }
  416. if ( repaintDC == NULL ) pdp->ReleaseDC( screenDC );
  417. }
  418. }
  419. }
  420. return fOldState;
  421. }
  422. ////////////////////////// CMagellanBMPStateWrap class.
  423. /*
  424. * CMagellanBMPStateWrap:: CMagellanBMPStateWrap
  425. *
  426. * @mfunc
  427. * Handles the state of whether to redraw the Magellan BMP as well as
  428. * repaints due to WM_PAINT.
  429. *
  430. * @devnote
  431. * This class is akin to smart pointer wrapper class idioms, in that
  432. * no matter how a routine exits the correct state of whether the
  433. * BMP is drawn will be maintined.
  434. */
  435. CMagellanBMPStateWrap:: CMagellanBMPStateWrap(CTxtEdit &ed, HDC repaintDC)
  436. : _ed(ed), _repaintDC(repaintDC)
  437. {
  438. TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellanBMPStateWrap:: CMagellanBMPStateWrap");
  439. BOOL fRepaint;
  440. fRepaint = repaintDC != NULL && _ed.mouse._fMagellanBitMapOn != 0;
  441. _fMagellanState = fRepaint || _ed.mouse.InvertMagellanDownBMP(_ed._pdp, FALSE, NULL);
  442. _ed.mouse._fMagellanBitMapOn = FALSE;
  443. }
  444. CMagellanBMPStateWrap::~CMagellanBMPStateWrap()
  445. {
  446. TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CMagellanBMPStateWrap::~CMagellanBMPStateWrap");
  447. _ed.mouse.InvertMagellanDownBMP(_ed._pdp, _fMagellanState, _repaintDC);
  448. }
  449. #endif // !defined(NOMAGELLAN)