PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Tool.cs

https://bitbucket.org/tuldok89/openpdn
C# | 1467 lines | 1066 code | 218 blank | 183 comment | 149 complexity | 8458711e64769e4410807d52ff44c4ab MD5 | raw file
  1. /////////////////////////////////////////////////////////////////////////////////
  2. // Paint.NET //
  3. // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
  4. // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
  5. // See src/Resources/Files/License.txt for full licensing and attribution //
  6. // details. //
  7. // . //
  8. /////////////////////////////////////////////////////////////////////////////////
  9. using PaintDotNet.Base;
  10. using PaintDotNet.HistoryFunctions;
  11. using PaintDotNet.SystemLayer;
  12. using System;
  13. using System.Collections;
  14. using System.Diagnostics;
  15. using System.Drawing;
  16. using System.Windows.Forms;
  17. namespace PaintDotNet
  18. {
  19. /// <summary>
  20. /// Encapsulates the functionality for a tool that goes in the main window's toolbar
  21. /// and that affects the Document.
  22. /// A Tool should only emit a HistoryMemento when it actually modifies the canvas.
  23. /// So, for instance, if the user draws a line but that line doesn't fall within
  24. /// the canvas (like if the seleciton region excludes it), then since the user
  25. /// hasn't really done anything there should be no HistoryMemento emitted.
  26. /// </summary>
  27. /// <remarks>
  28. /// A bit about the eventing model:
  29. /// * Perform[Event]() methods are ALWAYS used to trigger the events. This can be called by
  30. /// either instance methods or outside (client) callers.
  31. /// * [Event]() methods are called first by Perform[Event](). This gives the base Tool class a
  32. /// first chance at handling the event. These methods are private and non-overridable.
  33. /// * On[Event]() methods are then called by [Event]() if necessary, and should be overrided
  34. /// as necessary by derived classes. Always call the base implementation unless the
  35. /// documentation says otherwise. The base implementation gives the Tool a chance to provide
  36. /// default, overridable behavior for an event.
  37. /// </remarks>
  38. internal class Tool
  39. : IDisposable,
  40. IHotKeyTarget
  41. {
  42. public static readonly Type DefaultToolType = typeof(Tools.PaintBrushTool);
  43. private readonly ImageResource _toolBarImage;
  44. private Cursor _cursor;
  45. private readonly ToolInfo _toolInfo;
  46. private int _mouseDown; // incremented for every MouseDown, decremented for every MouseUp
  47. private int _ignoreMouseMove; // when >0, MouseMove is ignored and then this is decremented
  48. protected Cursor HandCursor;
  49. protected Cursor HandCursorMouseDown;
  50. protected Cursor HandCursorInvalid;
  51. private Cursor _panOldCursor;
  52. private Point _lastMouseXY;
  53. private Point _lastPanMouseXY;
  54. private bool _panMode; // 'true' when the user is holding down the spacebar
  55. private bool _panTracking; // 'true' when panMode is true, and when the mouse is down (which is when MouseMove should do panning)
  56. private MoveNubRenderer _trackingNub; // when we are in pan-tracking mode, we draw this in the center of the screen
  57. private readonly DocumentWorkspace _documentWorkspace;
  58. protected bool AutoScroll = true;
  59. private readonly Hashtable _keysThatAreDown = new Hashtable();
  60. private MouseButtons _lastButton = MouseButtons.None;
  61. private PdnRegion _saveRegion;
  62. #if DEBUG
  63. private bool haveClearedScratch = false;
  64. #endif
  65. private int _mouseEnter; // increments on MouseEnter, decrements on MouseLeave. The MouseLeave event is ONLY raised when this value decrements to 0, and MouseEnter is ONLY raised when this value increments to 1
  66. protected Surface ScratchSurface { get; private set; }
  67. protected Document Document
  68. {
  69. get
  70. {
  71. return DocumentWorkspace.Document;
  72. }
  73. }
  74. public DocumentWorkspace DocumentWorkspace
  75. {
  76. get
  77. {
  78. return _documentWorkspace;
  79. }
  80. }
  81. public AppWorkspace AppWorkspace
  82. {
  83. get
  84. {
  85. return DocumentWorkspace.AppWorkspace;
  86. }
  87. }
  88. protected HistoryStack HistoryStack
  89. {
  90. get
  91. {
  92. return DocumentWorkspace.History;
  93. }
  94. }
  95. protected AppEnvironment AppEnvironment
  96. {
  97. get
  98. {
  99. return _documentWorkspace.AppWorkspace.AppEnvironment;
  100. }
  101. }
  102. protected Selection Selection
  103. {
  104. get
  105. {
  106. return DocumentWorkspace.Selection;
  107. }
  108. }
  109. protected Layer ActiveLayer
  110. {
  111. get
  112. {
  113. return DocumentWorkspace.ActiveLayer;
  114. }
  115. }
  116. protected int ActiveLayerIndex
  117. {
  118. get
  119. {
  120. return DocumentWorkspace.ActiveLayerIndex;
  121. }
  122. set
  123. {
  124. DocumentWorkspace.ActiveLayerIndex = value;
  125. }
  126. }
  127. public void ClearSavedMemory()
  128. {
  129. _savedTiles = null;
  130. }
  131. public void ClearSavedRegion()
  132. {
  133. if (_saveRegion == null) return;
  134. _saveRegion.Dispose();
  135. _saveRegion = null;
  136. }
  137. public void RestoreRegion(PdnRegion region)
  138. {
  139. if (region == null) return;
  140. var activeLayer = (BitmapLayer)ActiveLayer;
  141. activeLayer.Surface.CopySurface(ScratchSurface, region);
  142. activeLayer.Invalidate(region);
  143. }
  144. public void RestoreSavedRegion()
  145. {
  146. if (_saveRegion == null) return;
  147. var activeLayer = (BitmapLayer)ActiveLayer;
  148. activeLayer.Surface.CopySurface(ScratchSurface, _saveRegion);
  149. activeLayer.Invalidate(_saveRegion);
  150. _saveRegion.Dispose();
  151. _saveRegion = null;
  152. }
  153. private const int SaveTileGranularity = 32;
  154. private BitVector2D _savedTiles;
  155. public void SaveRegion(PdnRegion saveMeRegion, Rectangle saveMeBounds)
  156. {
  157. var activeLayer = (BitmapLayer)ActiveLayer;
  158. if (_savedTiles == null)
  159. {
  160. _savedTiles = new BitVector2D(
  161. (activeLayer.Width + SaveTileGranularity - 1) / SaveTileGranularity,
  162. (activeLayer.Height + SaveTileGranularity - 1) / SaveTileGranularity);
  163. _savedTiles.Clear(false);
  164. }
  165. Rectangle regionBounds = saveMeRegion == null ? saveMeBounds : saveMeRegion.GetBoundsInt();
  166. Rectangle bounds = Rectangle.Union(regionBounds, saveMeBounds);
  167. bounds.Intersect(activeLayer.Bounds);
  168. int leftTile = bounds.Left / SaveTileGranularity;
  169. int topTile = bounds.Top / SaveTileGranularity;
  170. int rightTile = (bounds.Right - 1) / SaveTileGranularity;
  171. int bottomTile = (bounds.Bottom - 1) / SaveTileGranularity;
  172. for (int tileY = topTile; tileY <= bottomTile; ++tileY)
  173. {
  174. Rectangle rowAccumBounds = Rectangle.Empty;
  175. for (int tileX = leftTile; tileX <= rightTile; ++tileX)
  176. {
  177. if (!_savedTiles.Get(tileX, tileY))
  178. {
  179. var tileBounds = new Rectangle(tileX * SaveTileGranularity, tileY * SaveTileGranularity,
  180. SaveTileGranularity, SaveTileGranularity);
  181. tileBounds.Intersect(activeLayer.Bounds);
  182. rowAccumBounds = rowAccumBounds == Rectangle.Empty ? tileBounds : Rectangle.Union(rowAccumBounds, tileBounds);
  183. _savedTiles.Set(tileX, tileY, true);
  184. }
  185. else
  186. {
  187. if (rowAccumBounds != Rectangle.Empty)
  188. {
  189. using (Surface dst = ScratchSurface.CreateWindow(rowAccumBounds),
  190. src = activeLayer.Surface.CreateWindow(rowAccumBounds))
  191. {
  192. dst.CopySurface(src);
  193. }
  194. rowAccumBounds = Rectangle.Empty;
  195. }
  196. }
  197. }
  198. if (rowAccumBounds == Rectangle.Empty) continue;
  199. using (Surface dst = ScratchSurface.CreateWindow(rowAccumBounds),
  200. src = activeLayer.Surface.CreateWindow(rowAccumBounds))
  201. {
  202. dst.CopySurface(src);
  203. }
  204. rowAccumBounds = Rectangle.Empty;
  205. }
  206. if (_saveRegion != null)
  207. {
  208. _saveRegion.Dispose();
  209. _saveRegion = null;
  210. }
  211. if (saveMeRegion != null)
  212. {
  213. _saveRegion = saveMeRegion.Clone();
  214. }
  215. }
  216. private sealed class KeyTimeInfo
  217. {
  218. private readonly DateTime _keyDownTime;
  219. private DateTime _lastKeyPressPulse;
  220. private int Repeats { get; set; }
  221. public KeyTimeInfo()
  222. {
  223. Repeats = 0;
  224. _keyDownTime = DateTime.Now;
  225. _lastKeyPressPulse = _keyDownTime;
  226. }
  227. }
  228. /// <summary>
  229. /// Tells you whether the tool is "active" or not. If the tool is not active
  230. /// it is not safe to call any other method besides PerformActivate. All
  231. /// properties are safe to get values from.
  232. /// </summary>
  233. public bool Active { get; private set; }
  234. /// <summary>
  235. /// Returns true if the Tool has the input focus, or false if it does not.
  236. /// </summary>
  237. /// <remarks>
  238. /// This is used, for instanced, by the Text Tool so that it doesn't blink the
  239. /// cursor unless it's actually going to do something in response to your
  240. /// keyboard input!
  241. /// </remarks>
  242. public bool Focused
  243. {
  244. get
  245. {
  246. return DocumentWorkspace.Focused;
  247. }
  248. }
  249. public bool IsMouseDown
  250. {
  251. get
  252. {
  253. return _mouseDown > 0;
  254. }
  255. }
  256. /// <summary>
  257. /// Gets a flag that determines whether the Tool is deactivated while the current
  258. /// layer is changing, and then reactivated afterwards.
  259. /// </summary>
  260. /// <remarks>
  261. /// This property is queried every time the ActiveLayer property of DocumentWorkspace
  262. /// is changed. If false is returned, then the tool is not deactivated during the
  263. /// layer change and must manually maintain coherency.
  264. /// </remarks>
  265. public virtual bool DeactivateOnLayerChange
  266. {
  267. get
  268. {
  269. return true;
  270. }
  271. }
  272. /// <summary>
  273. /// Tells you which keys are pressed
  274. /// </summary>
  275. public Keys ModifierKeys
  276. {
  277. get
  278. {
  279. return Control.ModifierKeys;
  280. }
  281. }
  282. /// <summary>
  283. /// Represents the Image that is displayed in the toolbar.
  284. /// </summary>
  285. public ImageResource Image
  286. {
  287. get
  288. {
  289. return _toolBarImage;
  290. }
  291. }
  292. public event EventHandler CursorChanging;
  293. protected virtual void OnCursorChanging()
  294. {
  295. if (CursorChanging != null)
  296. {
  297. CursorChanging(this, EventArgs.Empty);
  298. }
  299. }
  300. public event EventHandler CursorChanged;
  301. protected virtual void OnCursorChanged()
  302. {
  303. if (CursorChanged != null)
  304. {
  305. CursorChanged(this, EventArgs.Empty);
  306. }
  307. }
  308. /// <summary>
  309. /// The Cursor that is displayed when this Tool is active and the
  310. /// mouse cursor is inside the document view.
  311. /// </summary>
  312. public Cursor Cursor
  313. {
  314. get
  315. {
  316. return _cursor;
  317. }
  318. set
  319. {
  320. OnCursorChanging();
  321. _cursor = value;
  322. OnCursorChanged();
  323. }
  324. }
  325. /// <summary>
  326. /// The name of the Tool. For instance, "Pencil". This name should *not* end in "Tool", e.g. "Pencil Tool"
  327. /// </summary>
  328. public string Name
  329. {
  330. get
  331. {
  332. return _toolInfo.Name;
  333. }
  334. }
  335. /// <summary>
  336. /// A short description of how to use the tool.
  337. /// </summary>
  338. public string HelpText
  339. {
  340. get
  341. {
  342. return _toolInfo.HelpText;
  343. }
  344. }
  345. public ToolInfo Info
  346. {
  347. get
  348. {
  349. return _toolInfo;
  350. }
  351. }
  352. public ToolBarConfigItems ToolBarConfigItems
  353. {
  354. get
  355. {
  356. return _toolInfo.ToolBarConfigItems;
  357. }
  358. }
  359. /// <summary>
  360. /// Specifies whether or not an inherited tool should take Ink commands
  361. /// </summary>
  362. protected virtual bool SupportsInk
  363. {
  364. get
  365. {
  366. return false;
  367. }
  368. }
  369. public char HotKey
  370. {
  371. get
  372. {
  373. return _toolInfo.HotKey;
  374. }
  375. }
  376. // Methods to send messages to this class
  377. public void PerformActivate()
  378. {
  379. Activate();
  380. }
  381. public void PerformDeactivate()
  382. {
  383. Deactivate();
  384. }
  385. private bool IsOverflow(MouseEventArgs e)
  386. {
  387. PointF clientPt = DocumentWorkspace.DocumentToClient(new PointF(e.X, e.Y));
  388. return clientPt.X < -16384 || clientPt.Y < -16384;
  389. }
  390. public bool IsMouseEntered
  391. {
  392. get
  393. {
  394. return _mouseEnter > 0;
  395. }
  396. }
  397. public void PerformMouseEnter()
  398. {
  399. MouseEnter();
  400. }
  401. private void MouseEnter()
  402. {
  403. ++_mouseEnter;
  404. if (_mouseEnter == 1)
  405. {
  406. OnMouseEnter();
  407. }
  408. }
  409. protected virtual void OnMouseEnter()
  410. {
  411. }
  412. public void PerformMouseLeave()
  413. {
  414. MouseLeave();
  415. }
  416. private void MouseLeave()
  417. {
  418. if (_mouseEnter == 1)
  419. {
  420. _mouseEnter = 0;
  421. OnMouseLeave();
  422. }
  423. else
  424. {
  425. _mouseEnter = Math.Max(0, _mouseEnter - 1);
  426. }
  427. }
  428. protected virtual void OnMouseLeave()
  429. {
  430. }
  431. public void PerformMouseMove(MouseEventArgs e)
  432. {
  433. if (IsOverflow(e))
  434. {
  435. return;
  436. }
  437. if (e is StylusEventArgs)
  438. {
  439. if (SupportsInk)
  440. {
  441. StylusMove(e as StylusEventArgs);
  442. }
  443. // if the tool does not claim ink support, discard
  444. }
  445. else
  446. {
  447. MouseMove(e);
  448. }
  449. }
  450. public void PerformMouseDown(MouseEventArgs e)
  451. {
  452. if (IsOverflow(e))
  453. {
  454. return;
  455. }
  456. if (e is StylusEventArgs)
  457. {
  458. if (SupportsInk)
  459. {
  460. StylusDown(e as StylusEventArgs);
  461. }
  462. // if the tool does not claim ink support, discard
  463. }
  464. else
  465. {
  466. if (SupportsInk)
  467. {
  468. DocumentWorkspace.Focus();
  469. }
  470. MouseDown(e);
  471. }
  472. }
  473. public void PerformMouseUp(MouseEventArgs e)
  474. {
  475. if (IsOverflow(e))
  476. {
  477. return;
  478. }
  479. if (e is StylusEventArgs)
  480. {
  481. if (SupportsInk)
  482. {
  483. StylusUp(e as StylusEventArgs);
  484. }
  485. // if the tool does not claim ink support, discard
  486. }
  487. else
  488. {
  489. MouseUp(e);
  490. }
  491. }
  492. public void PerformKeyPress(KeyPressEventArgs e)
  493. {
  494. KeyPress(e);
  495. }
  496. public void PerformKeyPress(Keys key)
  497. {
  498. KeyPress(key);
  499. }
  500. public void PerformKeyUp(KeyEventArgs e)
  501. {
  502. KeyUp(e);
  503. }
  504. public void PerformKeyDown(KeyEventArgs e)
  505. {
  506. KeyDown(e);
  507. }
  508. public void PerformClick()
  509. {
  510. Click();
  511. }
  512. public void PerformPulse()
  513. {
  514. Pulse();
  515. }
  516. public void PerformPaste(IDataObject data, out bool handled)
  517. {
  518. Paste(data, out handled);
  519. }
  520. public void PerformPasteQuery(IDataObject data, out bool canHandle)
  521. {
  522. PasteQuery(data, out canHandle);
  523. }
  524. private void Activate()
  525. {
  526. Debug.Assert(Active != true, "already active!");
  527. Active = true;
  528. HandCursor = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursor.cur"));
  529. HandCursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorMouseDown.cur"));
  530. HandCursorInvalid = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorInvalid.cur"));
  531. _panTracking = false;
  532. _panMode = false;
  533. _mouseDown = 0;
  534. _savedTiles = null;
  535. _saveRegion = null;
  536. ScratchSurface = DocumentWorkspace.BorrowScratchSurface(GetType().Name + ": Tool.Activate()");
  537. #if DEBUG
  538. haveClearedScratch = false;
  539. #endif
  540. Selection.Changing += SelectionChangingHandler;
  541. Selection.Changed += SelectionChangedHandler;
  542. HistoryStack.ExecutingHistoryMemento += ExecutingHistoryMemento;
  543. HistoryStack.ExecutedHistoryMemento += ExecutedHistoryMemento;
  544. HistoryStack.FinishedStepGroup += FinishedHistoryStepGroup;
  545. _trackingNub = new MoveNubRenderer(RendererList)
  546. {
  547. Visible = false,
  548. Size = new SizeF(10, 10),
  549. Shape = MoveNubShape.Compass
  550. };
  551. RendererList.Add(_trackingNub, false);
  552. OnActivate();
  553. }
  554. void FinishedHistoryStepGroup(object sender, EventArgs e)
  555. {
  556. OnFinishedHistoryStepGroup();
  557. }
  558. protected virtual void OnFinishedHistoryStepGroup()
  559. {
  560. }
  561. /// <summary>
  562. /// This method is called when the tool is being activated; that is, when the
  563. /// user has chosen to use this tool by clicking on it on a toolbar.
  564. /// </summary>
  565. protected virtual void OnActivate()
  566. {
  567. }
  568. private void Deactivate()
  569. {
  570. Debug.Assert(this.Active != false, "not active!");
  571. Active = false;
  572. Selection.Changing -= SelectionChangingHandler;
  573. Selection.Changed -= SelectionChangedHandler;
  574. HistoryStack.ExecutingHistoryMemento -= ExecutingHistoryMemento;
  575. HistoryStack.ExecutedHistoryMemento -= ExecutedHistoryMemento;
  576. HistoryStack.FinishedStepGroup -= FinishedHistoryStepGroup;
  577. OnDeactivate();
  578. RendererList.Remove(_trackingNub);
  579. _trackingNub.Dispose();
  580. _trackingNub = null;
  581. DocumentWorkspace.ReturnScratchSurface(ScratchSurface);
  582. ScratchSurface = null;
  583. if (_saveRegion != null)
  584. {
  585. _saveRegion.Dispose();
  586. _saveRegion = null;
  587. }
  588. _savedTiles = null;
  589. if (HandCursor != null)
  590. {
  591. HandCursor.Dispose();
  592. HandCursor = null;
  593. }
  594. if (HandCursorMouseDown != null)
  595. {
  596. HandCursorMouseDown.Dispose();
  597. HandCursorMouseDown = null;
  598. }
  599. if (HandCursorInvalid == null) return;
  600. HandCursorInvalid.Dispose();
  601. HandCursorInvalid = null;
  602. }
  603. /// <summary>
  604. /// This method is called when the tool is being deactivated; that is, when the
  605. /// user has chosen to use another tool by clicking on another tool on a
  606. /// toolbar.
  607. /// </summary>
  608. protected virtual void OnDeactivate()
  609. {
  610. }
  611. private void StylusDown(StylusEventArgs e)
  612. {
  613. if (!_panMode)
  614. {
  615. OnStylusDown(e);
  616. }
  617. }
  618. protected virtual void OnStylusDown(StylusEventArgs e)
  619. {
  620. }
  621. private void StylusMove(StylusEventArgs e)
  622. {
  623. if (!_panMode)
  624. {
  625. OnStylusMove(e);
  626. }
  627. }
  628. protected virtual void OnStylusMove(StylusEventArgs e)
  629. {
  630. if (_mouseDown > 0)
  631. {
  632. ScrollIfNecessary(new PointF(e.X, e.Y));
  633. }
  634. }
  635. private void StylusUp(StylusEventArgs e)
  636. {
  637. if (_panTracking)
  638. {
  639. _trackingNub.Visible = false;
  640. _panTracking = false;
  641. Cursor = HandCursor;
  642. }
  643. else
  644. {
  645. OnStylusUp(e);
  646. }
  647. }
  648. protected virtual void OnStylusUp(StylusEventArgs e)
  649. {
  650. }
  651. private void MouseMove(MouseEventArgs e)
  652. {
  653. if (_ignoreMouseMove > 0)
  654. {
  655. --_ignoreMouseMove;
  656. }
  657. else if (_panTracking && e.Button == MouseButtons.Left)
  658. {
  659. // Pan the document, using Stylus coordinates. This is done in
  660. // MouseMove instead of StylusMove because StylusMove is
  661. // asynchronous, and would not 'feel' right (pan motions would
  662. // stack up)
  663. var position = new Point(e.X, e.Y);
  664. RectangleF visibleRect = DocumentWorkspace.VisibleDocumentRectangleF;
  665. PointF visibleCenterPt = Utility.GetRectangleCenter(visibleRect);
  666. var delta = new PointF(e.X - _lastPanMouseXY.X, e.Y - _lastPanMouseXY.Y);
  667. PointF newScroll = DocumentWorkspace.DocumentScrollPositionF;
  668. if (delta.X != 0 || delta.Y != 0)
  669. {
  670. newScroll.X -= delta.X;
  671. newScroll.Y -= delta.Y;
  672. _lastPanMouseXY = new Point(e.X, e.Y);
  673. _lastPanMouseXY.X -= (int)Math.Truncate(delta.X);
  674. _lastPanMouseXY.Y -= (int)Math.Truncate(delta.Y);
  675. ++_ignoreMouseMove; // setting DocumentScrollPosition incurs a MouseMove event. ignore it prevents 'jittering' at non-integral zoom levels (like, say, 743%)
  676. DocumentWorkspace.DocumentScrollPositionF = newScroll;
  677. Update();
  678. }
  679. }
  680. else if (!_panMode)
  681. {
  682. OnMouseMove(e);
  683. }
  684. _lastMouseXY = new Point(e.X, e.Y);
  685. _lastButton = e.Button;
  686. }
  687. /// <summary>
  688. /// This method is called when the Tool is active and the mouse is moving within
  689. /// the document canvas area.
  690. /// </summary>
  691. /// <param name="e">Contains information about where the mouse cursor is, in document coordinates.</param>
  692. protected virtual void OnMouseMove(MouseEventArgs e)
  693. {
  694. if (_panMode || _mouseDown > 0)
  695. {
  696. ScrollIfNecessary(new PointF(e.X, e.Y));
  697. }
  698. }
  699. private void MouseDown(MouseEventArgs e)
  700. {
  701. ++_mouseDown;
  702. if (_panMode)
  703. {
  704. _panTracking = true;
  705. _lastPanMouseXY = new Point(e.X, e.Y);
  706. if (CanPan())
  707. {
  708. Cursor = HandCursorMouseDown;
  709. }
  710. }
  711. else
  712. {
  713. OnMouseDown(e);
  714. }
  715. _lastMouseXY = new Point(e.X, e.Y);
  716. }
  717. /// <summary>
  718. /// This method is called when the Tool is active and a mouse button has been
  719. /// pressed within the document area.
  720. /// </summary>
  721. /// <param name="e">Contains information about where the mouse cursor is, in document coordinates, and which mouse buttons were pressed.</param>
  722. protected virtual void OnMouseDown(MouseEventArgs e)
  723. {
  724. _lastButton = e.Button;
  725. }
  726. private void MouseUp(MouseEventArgs e)
  727. {
  728. --_mouseDown;
  729. if (!_panMode)
  730. {
  731. OnMouseUp(e);
  732. }
  733. _lastMouseXY = new Point(e.X, e.Y);
  734. }
  735. /// <summary>
  736. /// This method is called when the Tool is active and a mouse button has been
  737. /// released within the document area.
  738. /// </summary>
  739. /// <param name="e">Contains information about where the mouse cursor is, in document coordinates, and which mouse buttons were released.</param>
  740. protected virtual void OnMouseUp(MouseEventArgs e)
  741. {
  742. _lastButton = e.Button;
  743. }
  744. private void Click()
  745. {
  746. OnClick();
  747. }
  748. /// <summary>
  749. /// This method is called when the Tool is active and a mouse button has been
  750. /// clicked within the document area. If you need more specific information,
  751. /// such as where the mouse was clicked and which button was used, respond to
  752. /// the MouseDown/MouseUp events.
  753. /// </summary>
  754. protected virtual void OnClick()
  755. {
  756. }
  757. private void KeyPress(KeyPressEventArgs e)
  758. {
  759. OnKeyPress(e);
  760. }
  761. private static DateTime _lastToolSwitch = DateTime.MinValue;
  762. // if we are pressing 'S' to switch to the selection tools, then consecutive
  763. // presses of 'S' should switch to the next selection tol in the list. however,
  764. // if we wait awhile then pressing 'S' should go to the *first* selection
  765. // tool. 'awhile' is defined by this variable.
  766. private static readonly TimeSpan ToolSwitchReset = new TimeSpan(0, 0, 0, 2, 0);
  767. private const char DecPenSizeShortcut = '[';
  768. private const char DecPenSizeBy5Shortcut = (char)27; // Ctrl [ but must also test that Ctrl is down
  769. private const char IncPenSizeShortcut = ']';
  770. private const char IncPenSizeBy5Shortcut = (char)29; // Ctrl ] but must also test that Ctrl is down
  771. private const char SwapColorsShortcut = 'x';
  772. private const char SwapPrimarySecondaryChoice = 'c';
  773. private readonly char[] _wildShortcuts = new[] { ',', '.', '/' };
  774. // Return true if the key is handled, false if not.
  775. protected virtual bool OnWildShortcutKey(int ordinal)
  776. {
  777. return false;
  778. }
  779. /// <summary>
  780. /// This method is called when the tool is active and a keyboard key is pressed
  781. /// and released. If you respond to the keyboard key, set e.Handled to true.
  782. /// </summary>
  783. protected virtual void OnKeyPress(KeyPressEventArgs e)
  784. {
  785. if (e.Handled || !DocumentWorkspace.Focused) return;
  786. int wsIndex = Array.IndexOf(_wildShortcuts, e.KeyChar);
  787. if (wsIndex != -1)
  788. {
  789. e.Handled = OnWildShortcutKey(wsIndex);
  790. }
  791. else switch (e.KeyChar)
  792. {
  793. case SwapColorsShortcut:
  794. AppWorkspace.Widgets.ColorsForm.SwapUserColors();
  795. e.Handled = true;
  796. break;
  797. case SwapPrimarySecondaryChoice:
  798. AppWorkspace.Widgets.ColorsForm.ToggleWhichUserColor();
  799. e.Handled = true;
  800. break;
  801. case DecPenSizeShortcut:
  802. AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(-1.0f);
  803. e.Handled = true;
  804. break;
  805. default:
  806. if (e.KeyChar == DecPenSizeBy5Shortcut && (ModifierKeys & Keys.Control) != 0)
  807. {
  808. AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(-5.0f);
  809. e.Handled = true;
  810. }
  811. else if (e.KeyChar == IncPenSizeShortcut)
  812. {
  813. AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(+1.0f);
  814. e.Handled = true;
  815. }
  816. else if (e.KeyChar == IncPenSizeBy5Shortcut && (ModifierKeys & Keys.Control) != 0)
  817. {
  818. AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(+5.0f);
  819. e.Handled = true;
  820. }
  821. else
  822. {
  823. ToolInfo[] toolInfos = DocumentWorkspace.ToolInfos;
  824. Type currentToolType = DocumentWorkspace.GetToolType();
  825. int currentTool = 0;
  826. if (0 != (ModifierKeys & Keys.Shift))
  827. {
  828. Array.Reverse(toolInfos);
  829. }
  830. if (char.ToLower(HotKey) != char.ToLower(e.KeyChar) ||
  831. (DateTime.Now - _lastToolSwitch) > ToolSwitchReset)
  832. {
  833. // If it's been a short time since they pressed a tool switching hotkey,
  834. // we will start from the beginning of this list. This helps to enable two things:
  835. // 1) If multiple tools have the same hotkey, the user may press that hotkey
  836. // to cycle through them
  837. // 2) After a period of time, pressing the hotkey will revert to always
  838. // choosing the first tool in that list of tools which have the same hotkey.
  839. currentTool = -1;
  840. }
  841. else
  842. {
  843. for (int t = 0; t < toolInfos.Length; ++t)
  844. {
  845. if (toolInfos[t].ToolType != currentToolType) continue;
  846. currentTool = t;
  847. break;
  848. }
  849. }
  850. for (int t = 0; t < toolInfos.Length; ++t)
  851. {
  852. int newTool = (t + currentTool + 1) % toolInfos.Length;
  853. ToolInfo localToolInfo = toolInfos[newTool];
  854. if (localToolInfo.ToolType == DocumentWorkspace.GetToolType() &&
  855. localToolInfo.SkipIfActiveOnHotKey)
  856. {
  857. continue;
  858. }
  859. if (char.ToLower(localToolInfo.HotKey) != char.ToLower(e.KeyChar)) continue;
  860. if (!IsMouseDown)
  861. {
  862. AppWorkspace.Widgets.ToolsControl.SelectTool(localToolInfo.ToolType);
  863. }
  864. e.Handled = true;
  865. _lastToolSwitch = DateTime.Now;
  866. break;
  867. }
  868. // If the keypress is still not handled ...
  869. if (!e.Handled)
  870. {
  871. switch (e.KeyChar)
  872. {
  873. // By default, Esc/Enter clear the current selection if there is any
  874. case (char)13: // Enter
  875. case (char)27: // Escape
  876. if (_mouseDown == 0 && !Selection.IsEmpty)
  877. {
  878. e.Handled = true;
  879. DocumentWorkspace.ExecuteFunction(new DeselectFunction());
  880. }
  881. break;
  882. }
  883. }
  884. }
  885. break;
  886. }
  887. }
  888. private DateTime _lastKeyboardMove = DateTime.MinValue;
  889. private Keys _lastKey;
  890. private int _keyboardMoveSpeed = 1;
  891. private int _keyboardMoveRepeats = 0;
  892. private void KeyPress(Keys key)
  893. {
  894. OnKeyPress(key);
  895. }
  896. /// <summary>
  897. /// This method is called when the tool is active and a keyboard key is pressed
  898. /// and released that is not representable with a regular Unicode chararacter.
  899. /// An example would be the arrow keys.
  900. /// </summary>
  901. protected virtual void OnKeyPress(Keys key)
  902. {
  903. Point dir = Point.Empty;
  904. if (key != _lastKey)
  905. {
  906. _lastKeyboardMove = DateTime.MinValue;
  907. }
  908. _lastKey = key;
  909. switch (key)
  910. {
  911. case Keys.Left:
  912. --dir.X;
  913. break;
  914. case Keys.Right:
  915. ++dir.X;
  916. break;
  917. case Keys.Up:
  918. --dir.Y;
  919. break;
  920. case Keys.Down:
  921. ++dir.Y;
  922. break;
  923. }
  924. if (dir.Equals(Point.Empty)) return;
  925. long span = DateTime.Now.Ticks - _lastKeyboardMove.Ticks;
  926. if ((span * 4) > TimeSpan.TicksPerSecond)
  927. {
  928. _keyboardMoveRepeats = 0;
  929. _keyboardMoveSpeed = 1;
  930. }
  931. else
  932. {
  933. _keyboardMoveRepeats++;
  934. if (_keyboardMoveRepeats > 15 && (_keyboardMoveRepeats % 4) == 0)
  935. {
  936. _keyboardMoveSpeed++;
  937. }
  938. }
  939. _lastKeyboardMove = DateTime.Now;
  940. var offset = (int)(Math.Ceiling(DocumentWorkspace.ScaleFactor.Ratio) * _keyboardMoveSpeed);
  941. Cursor.Position = new Point(Cursor.Position.X + offset * dir.X, Cursor.Position.Y + offset * dir.Y);
  942. Point location = DocumentWorkspace.PointToScreen(Point.Truncate(DocumentWorkspace.DocumentToClient(PointF.Empty)));
  943. var stylusLocF = new PointF((float)Cursor.Position.X - (float)location.X, (float)Cursor.Position.Y - (float)location.Y);
  944. var stylusLoc = new Point(Cursor.Position.X - location.X, Cursor.Position.Y - location.Y);
  945. stylusLoc = DocumentWorkspace.ScaleFactor.UnscalePoint(stylusLoc);
  946. stylusLocF = DocumentWorkspace.ScaleFactor.UnscalePoint(stylusLocF);
  947. DocumentWorkspace.PerformDocumentMouseMove(new StylusEventArgs(_lastButton, 1, stylusLocF.X, stylusLocF.Y, 0, 1.0f));
  948. DocumentWorkspace.PerformDocumentMouseMove(new MouseEventArgs(_lastButton, 1, stylusLoc.X, stylusLoc.Y, 0));
  949. }
  950. private bool CanPan()
  951. {
  952. Rectangle vis = Utility.RoundRectangle(DocumentWorkspace.VisibleDocumentRectangleF);
  953. vis.Intersect(Document.Bounds);
  954. return vis != Document.Bounds;
  955. }
  956. private void KeyUp(KeyEventArgs e)
  957. {
  958. if (_panMode)
  959. {
  960. _panMode = false;
  961. _panTracking = false;
  962. _trackingNub.Visible = false;
  963. Cursor = _panOldCursor;
  964. _panOldCursor = null;
  965. e.Handled = true;
  966. }
  967. OnKeyUp(e);
  968. }
  969. /// <summary>
  970. /// This method is called when the tool is active and a keyboard key is pressed.
  971. /// If you respond to the keyboard key, set e.Handled to true.
  972. /// </summary>
  973. protected virtual void OnKeyUp(KeyEventArgs e)
  974. {
  975. _keysThatAreDown.Clear();
  976. }
  977. private void KeyDown(KeyEventArgs e)
  978. {
  979. OnKeyDown(e);
  980. }
  981. /// <summary>
  982. /// This method is called when the tool is active and a keyboard key is released
  983. /// Before responding, check that e.Handled is false, and if you then respond to
  984. /// the keyboard key, set e.Handled to true.
  985. /// </summary>
  986. protected virtual void OnKeyDown(KeyEventArgs e)
  987. {
  988. if (e.Handled) return;
  989. if (!_keysThatAreDown.Contains(e.KeyData))
  990. {
  991. _keysThatAreDown.Add(e.KeyData, new KeyTimeInfo());
  992. }
  993. if (!IsMouseDown &&
  994. !_panMode &&
  995. e.KeyCode == Keys.Space)
  996. {
  997. _panMode = true;
  998. _panOldCursor = Cursor;
  999. Cursor = CanPan() ? HandCursor : HandCursorInvalid;
  1000. }
  1001. // arrow keys are processed in another way
  1002. // we get their KeyDown but no KeyUp, so they can not be handled
  1003. // by our normal methods
  1004. OnKeyPress(e.KeyData);
  1005. }
  1006. private void SelectionChanging()
  1007. {
  1008. OnSelectionChanging();
  1009. }
  1010. /// <summary>
  1011. /// This method is called when the Tool is active and the selection area is
  1012. /// about to be changed.
  1013. /// </summary>
  1014. protected virtual void OnSelectionChanging()
  1015. {
  1016. }
  1017. private void SelectionChanged()
  1018. {
  1019. OnSelectionChanged();
  1020. }
  1021. /// <summary>
  1022. /// This method is called when the Tool is active and the selection area has
  1023. /// been changed.
  1024. /// </summary>
  1025. protected virtual void OnSelectionChanged()
  1026. {
  1027. }
  1028. private void ExecutingHistoryMemento(object sender, ExecutingHistoryMementoEventArgs e)
  1029. {
  1030. OnExecutingHistoryMemento(e);
  1031. }
  1032. protected virtual void OnExecutingHistoryMemento(ExecutingHistoryMementoEventArgs e)
  1033. {
  1034. }
  1035. private void ExecutedHistoryMemento(object sender, ExecutedHistoryMementoEventArgs e)
  1036. {
  1037. OnExecutedHistoryMemento(e);
  1038. }
  1039. protected virtual void OnExecutedHistoryMemento(ExecutedHistoryMementoEventArgs e)
  1040. {
  1041. }
  1042. private void PasteQuery(IDataObject data, out bool canHandle)
  1043. {
  1044. OnPasteQuery(data, out canHandle);
  1045. }
  1046. /// <summary>
  1047. /// This method is called when the system is querying a tool as to whether
  1048. /// it can handle a pasted object.
  1049. /// </summary>
  1050. /// <param name="data">
  1051. /// The clipboard data that was pasted by the user that should be inspected.
  1052. /// </param>
  1053. /// <param name="canHandle">
  1054. /// <b>true</b> if the data can be handled by the tool, <b>false</b> if not.
  1055. /// </param>
  1056. /// <remarks>
  1057. /// If you do not set canHandle to <b>true</b> then the tool will not be
  1058. /// able to respond to the Edit menu's Paste item.
  1059. /// </remarks>
  1060. protected virtual void OnPasteQuery(IDataObject data, out bool canHandle)
  1061. {
  1062. canHandle = false;
  1063. }
  1064. private void Paste(IDataObject data, out bool handled)
  1065. {
  1066. OnPaste(data, out handled);
  1067. }
  1068. /// <summary>
  1069. /// This method is called when the user invokes a paste operation. Tools get
  1070. /// the first chance to handle this data.
  1071. /// </summary>
  1072. /// <param name="data">
  1073. /// The data that was pasted by the user.
  1074. /// </param>
  1075. /// <param name="handled">
  1076. /// <b>true</b> if the data was handled and pasted, <b>false</b> if not.
  1077. /// </param>
  1078. /// <remarks>
  1079. /// If you do not set handled to <b>true</b> the event will be passed to the
  1080. /// global paste handler.
  1081. /// </remarks>
  1082. protected virtual void OnPaste(IDataObject data, out bool handled)
  1083. {
  1084. handled = false;
  1085. }
  1086. private void Pulse()
  1087. {
  1088. OnPulse();
  1089. }
  1090. protected bool IsFormActive
  1091. {
  1092. get
  1093. {
  1094. return (ReferenceEquals(Form.ActiveForm, DocumentWorkspace.FindForm()));
  1095. }
  1096. }
  1097. /// <summary>
  1098. /// This method is called many times per second, called by the DocumentWorkspace.
  1099. /// </summary>
  1100. protected virtual void OnPulse()
  1101. {
  1102. if (!_panTracking || _lastButton != MouseButtons.Right) return;
  1103. Point position = _lastMouseXY;
  1104. RectangleF visibleRect = DocumentWorkspace.VisibleDocumentRectangleF;
  1105. PointF visibleCenterPt = Utility.GetRectangleCenter(visibleRect);
  1106. var delta = new PointF(position.X - visibleCenterPt.X, position.Y - visibleCenterPt.Y);
  1107. PointF newScroll = DocumentWorkspace.DocumentScrollPositionF;
  1108. _trackingNub.Visible = true;
  1109. if (delta.X == 0 && delta.Y == 0) return;
  1110. newScroll.X += delta.X;
  1111. newScroll.Y += delta.Y;
  1112. ++_ignoreMouseMove; // setting DocumentScrollPosition incurs a MouseMove event. ignore it prevents 'jittering' at non-integral zoom levels (like, say, 743%)
  1113. UI.SuspendControlPainting(DocumentWorkspace);
  1114. DocumentWorkspace.DocumentScrollPositionF = newScroll;
  1115. _trackingNub.Visible = true;
  1116. _trackingNub.Location = Utility.GetRectangleCenter(DocumentWorkspace.VisibleDocumentRectangleF);
  1117. UI.ResumeControlPainting(DocumentWorkspace);
  1118. DocumentWorkspace.Invalidate(true);
  1119. Update();
  1120. }
  1121. protected bool ScrollIfNecessary(PointF position)
  1122. {
  1123. if (!AutoScroll || !CanPan())
  1124. {
  1125. return false;
  1126. }
  1127. RectangleF visible = DocumentWorkspace.VisibleDocumentRectangleF;
  1128. PointF lastScrollPosition = DocumentWorkspace.DocumentScrollPositionF;
  1129. PointF delta = PointF.Empty;
  1130. PointF zoomedPoint = PointF.Empty;
  1131. zoomedPoint.X = Utility.Lerp((visible.Left + visible.Right) / 2.0f, position.X, 1.02f);
  1132. zoomedPoint.Y = Utility.Lerp((visible.Top + visible.Bottom) / 2.0f, position.Y, 1.02f);
  1133. if (zoomedPoint.X < visible.Left)
  1134. {
  1135. delta.X = zoomedPoint.X - visible.Left;
  1136. }
  1137. else if (zoomedPoint.X > visible.Right)
  1138. {
  1139. delta.X = zoomedPoint.X - visible.Right;
  1140. }
  1141. if (zoomedPoint.Y < visible.Top)
  1142. {
  1143. delta.Y = zoomedPoint.Y - visible.Top;
  1144. }
  1145. else if (zoomedPoint.Y > visible.Bottom)
  1146. {
  1147. delta.Y = zoomedPoint.Y - visible.Bottom;
  1148. }
  1149. if (!delta.IsEmpty)
  1150. {
  1151. var newScrollPosition = new PointF(lastScrollPosition.X + delta.X, lastScrollPosition.Y + delta.Y);
  1152. DocumentWorkspace.DocumentScrollPositionF = newScrollPosition;
  1153. Update();
  1154. return true;
  1155. }
  1156. return false;
  1157. }
  1158. private void SelectionChangingHandler(object sender, EventArgs e)
  1159. {
  1160. OnSelectionChanging();
  1161. }
  1162. private void SelectionChangedHandler(object sender, EventArgs e)
  1163. {
  1164. OnSelectionChanged();
  1165. }
  1166. protected void SetStatus(ImageResource statusIcon, string statusText)
  1167. {
  1168. if (statusIcon == null && statusText != null)
  1169. {
  1170. statusIcon = PdnResources.GetImageResource("Icons.MenuHelpHelpTopicsIcon.png");
  1171. }
  1172. DocumentWorkspace.SetStatus(statusText, statusIcon);
  1173. }
  1174. protected SurfaceBoxRendererList RendererList
  1175. {
  1176. get
  1177. {
  1178. return DocumentWorkspace.RendererList;
  1179. }
  1180. }
  1181. protected void Update()
  1182. {
  1183. DocumentWorkspace.Update();
  1184. }
  1185. protected object GetStaticData()
  1186. {
  1187. return DocumentWorkspace.GetStaticToolData(GetType());
  1188. }
  1189. protected void SetStaticData(object data)
  1190. {
  1191. DocumentWorkspace.SetStaticToolData(GetType(), data);
  1192. }
  1193. // NOTE: Your constructor must be able to run successfully with a documentWorkspace
  1194. // of null. This is sent in while the DocumentControl static constructor
  1195. // class is building a list of ToolInfo instances, so that it may construct
  1196. // the list without having to also construct a DocumentControl.
  1197. public Tool(DocumentWorkspace documentWorkspace,
  1198. ImageResource toolBarImage,
  1199. string name,
  1200. string helpText,
  1201. char hotKey,
  1202. bool skipIfActiveOnHotKey,
  1203. ToolBarConfigItems toolBarConfigItems)
  1204. {
  1205. _documentWorkspace = documentWorkspace;
  1206. _toolBarImage = toolBarImage;
  1207. _toolInfo = new ToolInfo(name, helpText, toolBarImage, hotKey, skipIfActiveOnHotKey, toolBarConfigItems, GetType());
  1208. if (_documentWorkspace != null)
  1209. {
  1210. _documentWorkspace.UpdateStatusBarToToolHelpText(this);
  1211. }
  1212. }
  1213. ~Tool()
  1214. {
  1215. Dispose(false);
  1216. }
  1217. public void Dispose()
  1218. {
  1219. Dispose(true);
  1220. GC.SuppressFinalize(this);
  1221. }
  1222. protected virtual void Dispose(bool disposing)
  1223. {
  1224. Debug.Assert(!Active, "Tool is still active!");
  1225. if (!disposing) return;
  1226. if (_saveRegion != null)
  1227. {
  1228. _saveRegion.Dispose();
  1229. _saveRegion = null;
  1230. }
  1231. OnDisposed();
  1232. }
  1233. public event EventHandler Disposed;
  1234. private void OnDisposed()
  1235. {
  1236. if (Disposed != null)
  1237. {
  1238. Disposed(this, EventArgs.Empty);
  1239. }
  1240. }
  1241. public Form AssociatedForm
  1242. {
  1243. get
  1244. {
  1245. return AppWorkspace.FindForm();
  1246. }
  1247. }
  1248. }
  1249. }