PageRenderTime 49ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Tool.cs

https://bitbucket.org/tcz001/openpdn
C# | 1555 lines | 1151 code | 221 blank | 183 comment | 162 complexity | d1ac151a48de6e2ba056c13848d53c4e MD5 | raw file
Possible License(s): Unlicense

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

Large files files are truncated, but you can click here to view the full file