/src/Tool.cs
C# | 1467 lines | 1066 code | 218 blank | 183 comment | 149 complexity | 8458711e64769e4410807d52ff44c4ab MD5 | raw file
- /////////////////////////////////////////////////////////////////////////////////
- // Paint.NET //
- // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
- // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
- // See src/Resources/Files/License.txt for full licensing and attribution //
- // details. //
- // . //
- /////////////////////////////////////////////////////////////////////////////////
-
- using PaintDotNet.Base;
- using PaintDotNet.HistoryFunctions;
- using PaintDotNet.SystemLayer;
- using System;
- using System.Collections;
- using System.Diagnostics;
- using System.Drawing;
- using System.Windows.Forms;
-
- namespace PaintDotNet
- {
- /// <summary>
- /// Encapsulates the functionality for a tool that goes in the main window's toolbar
- /// and that affects the Document.
- /// A Tool should only emit a HistoryMemento when it actually modifies the canvas.
- /// So, for instance, if the user draws a line but that line doesn't fall within
- /// the canvas (like if the seleciton region excludes it), then since the user
- /// hasn't really done anything there should be no HistoryMemento emitted.
- /// </summary>
- /// <remarks>
- /// A bit about the eventing model:
- /// * Perform[Event]() methods are ALWAYS used to trigger the events. This can be called by
- /// either instance methods or outside (client) callers.
- /// * [Event]() methods are called first by Perform[Event](). This gives the base Tool class a
- /// first chance at handling the event. These methods are private and non-overridable.
- /// * On[Event]() methods are then called by [Event]() if necessary, and should be overrided
- /// as necessary by derived classes. Always call the base implementation unless the
- /// documentation says otherwise. The base implementation gives the Tool a chance to provide
- /// default, overridable behavior for an event.
- /// </remarks>
- internal class Tool
- : IDisposable,
- IHotKeyTarget
- {
- public static readonly Type DefaultToolType = typeof(Tools.PaintBrushTool);
-
- private readonly ImageResource _toolBarImage;
- private Cursor _cursor;
- private readonly ToolInfo _toolInfo;
- private int _mouseDown; // incremented for every MouseDown, decremented for every MouseUp
- private int _ignoreMouseMove; // when >0, MouseMove is ignored and then this is decremented
-
- protected Cursor HandCursor;
- protected Cursor HandCursorMouseDown;
- protected Cursor HandCursorInvalid;
- private Cursor _panOldCursor;
- private Point _lastMouseXY;
- private Point _lastPanMouseXY;
- private bool _panMode; // 'true' when the user is holding down the spacebar
- private bool _panTracking; // 'true' when panMode is true, and when the mouse is down (which is when MouseMove should do panning)
- private MoveNubRenderer _trackingNub; // when we are in pan-tracking mode, we draw this in the center of the screen
-
- private readonly DocumentWorkspace _documentWorkspace;
-
- protected bool AutoScroll = true;
- private readonly Hashtable _keysThatAreDown = new Hashtable();
- private MouseButtons _lastButton = MouseButtons.None;
- private PdnRegion _saveRegion;
- #if DEBUG
- private bool haveClearedScratch = false;
- #endif
-
- 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
-
- protected Surface ScratchSurface { get; private set; }
-
- protected Document Document
- {
- get
- {
- return DocumentWorkspace.Document;
- }
- }
-
- public DocumentWorkspace DocumentWorkspace
- {
- get
- {
- return _documentWorkspace;
- }
- }
-
- public AppWorkspace AppWorkspace
- {
- get
- {
- return DocumentWorkspace.AppWorkspace;
- }
- }
-
- protected HistoryStack HistoryStack
- {
- get
- {
- return DocumentWorkspace.History;
- }
- }
-
- protected AppEnvironment AppEnvironment
- {
- get
- {
- return _documentWorkspace.AppWorkspace.AppEnvironment;
- }
- }
-
- protected Selection Selection
- {
- get
- {
- return DocumentWorkspace.Selection;
- }
- }
-
- protected Layer ActiveLayer
- {
- get
- {
- return DocumentWorkspace.ActiveLayer;
- }
- }
-
- protected int ActiveLayerIndex
- {
- get
- {
- return DocumentWorkspace.ActiveLayerIndex;
- }
-
- set
- {
- DocumentWorkspace.ActiveLayerIndex = value;
- }
- }
-
- public void ClearSavedMemory()
- {
- _savedTiles = null;
- }
-
- public void ClearSavedRegion()
- {
- if (_saveRegion == null) return;
- _saveRegion.Dispose();
- _saveRegion = null;
- }
-
- public void RestoreRegion(PdnRegion region)
- {
- if (region == null) return;
- var activeLayer = (BitmapLayer)ActiveLayer;
- activeLayer.Surface.CopySurface(ScratchSurface, region);
- activeLayer.Invalidate(region);
- }
-
- public void RestoreSavedRegion()
- {
- if (_saveRegion == null) return;
- var activeLayer = (BitmapLayer)ActiveLayer;
- activeLayer.Surface.CopySurface(ScratchSurface, _saveRegion);
- activeLayer.Invalidate(_saveRegion);
- _saveRegion.Dispose();
- _saveRegion = null;
- }
-
- private const int SaveTileGranularity = 32;
- private BitVector2D _savedTiles;
-
- public void SaveRegion(PdnRegion saveMeRegion, Rectangle saveMeBounds)
- {
- var activeLayer = (BitmapLayer)ActiveLayer;
-
- if (_savedTiles == null)
- {
- _savedTiles = new BitVector2D(
- (activeLayer.Width + SaveTileGranularity - 1) / SaveTileGranularity,
- (activeLayer.Height + SaveTileGranularity - 1) / SaveTileGranularity);
-
- _savedTiles.Clear(false);
- }
-
- Rectangle regionBounds = saveMeRegion == null ? saveMeBounds : saveMeRegion.GetBoundsInt();
-
- Rectangle bounds = Rectangle.Union(regionBounds, saveMeBounds);
- bounds.Intersect(activeLayer.Bounds);
-
- int leftTile = bounds.Left / SaveTileGranularity;
- int topTile = bounds.Top / SaveTileGranularity;
- int rightTile = (bounds.Right - 1) / SaveTileGranularity;
- int bottomTile = (bounds.Bottom - 1) / SaveTileGranularity;
-
- for (int tileY = topTile; tileY <= bottomTile; ++tileY)
- {
- Rectangle rowAccumBounds = Rectangle.Empty;
-
- for (int tileX = leftTile; tileX <= rightTile; ++tileX)
- {
- if (!_savedTiles.Get(tileX, tileY))
- {
- var tileBounds = new Rectangle(tileX * SaveTileGranularity, tileY * SaveTileGranularity,
- SaveTileGranularity, SaveTileGranularity);
-
- tileBounds.Intersect(activeLayer.Bounds);
-
- rowAccumBounds = rowAccumBounds == Rectangle.Empty ? tileBounds : Rectangle.Union(rowAccumBounds, tileBounds);
-
- _savedTiles.Set(tileX, tileY, true);
- }
- else
- {
- if (rowAccumBounds != Rectangle.Empty)
- {
- using (Surface dst = ScratchSurface.CreateWindow(rowAccumBounds),
- src = activeLayer.Surface.CreateWindow(rowAccumBounds))
- {
- dst.CopySurface(src);
- }
-
- rowAccumBounds = Rectangle.Empty;
- }
- }
- }
-
- if (rowAccumBounds == Rectangle.Empty) continue;
- using (Surface dst = ScratchSurface.CreateWindow(rowAccumBounds),
- src = activeLayer.Surface.CreateWindow(rowAccumBounds))
- {
- dst.CopySurface(src);
- }
-
- rowAccumBounds = Rectangle.Empty;
- }
-
- if (_saveRegion != null)
- {
- _saveRegion.Dispose();
- _saveRegion = null;
- }
-
- if (saveMeRegion != null)
- {
- _saveRegion = saveMeRegion.Clone();
- }
- }
-
- private sealed class KeyTimeInfo
- {
- private readonly DateTime _keyDownTime;
- private DateTime _lastKeyPressPulse;
-
- private int Repeats { get; set; }
-
- public KeyTimeInfo()
- {
- Repeats = 0;
- _keyDownTime = DateTime.Now;
- _lastKeyPressPulse = _keyDownTime;
- }
- }
-
- /// <summary>
- /// Tells you whether the tool is "active" or not. If the tool is not active
- /// it is not safe to call any other method besides PerformActivate. All
- /// properties are safe to get values from.
- /// </summary>
- public bool Active { get; private set; }
-
- /// <summary>
- /// Returns true if the Tool has the input focus, or false if it does not.
- /// </summary>
- /// <remarks>
- /// This is used, for instanced, by the Text Tool so that it doesn't blink the
- /// cursor unless it's actually going to do something in response to your
- /// keyboard input!
- /// </remarks>
- public bool Focused
- {
- get
- {
- return DocumentWorkspace.Focused;
- }
- }
-
- public bool IsMouseDown
- {
- get
- {
- return _mouseDown > 0;
- }
- }
-
- /// <summary>
- /// Gets a flag that determines whether the Tool is deactivated while the current
- /// layer is changing, and then reactivated afterwards.
- /// </summary>
- /// <remarks>
- /// This property is queried every time the ActiveLayer property of DocumentWorkspace
- /// is changed. If false is returned, then the tool is not deactivated during the
- /// layer change and must manually maintain coherency.
- /// </remarks>
- public virtual bool DeactivateOnLayerChange
- {
- get
- {
- return true;
- }
- }
-
- /// <summary>
- /// Tells you which keys are pressed
- /// </summary>
- public Keys ModifierKeys
- {
- get
- {
- return Control.ModifierKeys;
- }
- }
-
- /// <summary>
- /// Represents the Image that is displayed in the toolbar.
- /// </summary>
- public ImageResource Image
- {
- get
- {
- return _toolBarImage;
- }
- }
-
- public event EventHandler CursorChanging;
- protected virtual void OnCursorChanging()
- {
- if (CursorChanging != null)
- {
- CursorChanging(this, EventArgs.Empty);
- }
- }
-
- public event EventHandler CursorChanged;
- protected virtual void OnCursorChanged()
- {
- if (CursorChanged != null)
- {
- CursorChanged(this, EventArgs.Empty);
- }
- }
-
- /// <summary>
- /// The Cursor that is displayed when this Tool is active and the
- /// mouse cursor is inside the document view.
- /// </summary>
- public Cursor Cursor
- {
- get
- {
- return _cursor;
- }
-
- set
- {
- OnCursorChanging();
- _cursor = value;
- OnCursorChanged();
- }
- }
-
- /// <summary>
- /// The name of the Tool. For instance, "Pencil". This name should *not* end in "Tool", e.g. "Pencil Tool"
- /// </summary>
- public string Name
- {
- get
- {
- return _toolInfo.Name;
- }
- }
-
- /// <summary>
- /// A short description of how to use the tool.
- /// </summary>
- public string HelpText
- {
- get
- {
- return _toolInfo.HelpText;
- }
- }
-
- public ToolInfo Info
- {
- get
- {
- return _toolInfo;
- }
- }
-
- public ToolBarConfigItems ToolBarConfigItems
- {
- get
- {
- return _toolInfo.ToolBarConfigItems;
- }
- }
-
- /// <summary>
- /// Specifies whether or not an inherited tool should take Ink commands
- /// </summary>
- protected virtual bool SupportsInk
- {
- get
- {
- return false;
- }
- }
-
- public char HotKey
- {
- get
- {
- return _toolInfo.HotKey;
- }
- }
-
- // Methods to send messages to this class
- public void PerformActivate()
- {
- Activate();
- }
-
- public void PerformDeactivate()
- {
- Deactivate();
- }
-
- private bool IsOverflow(MouseEventArgs e)
- {
- PointF clientPt = DocumentWorkspace.DocumentToClient(new PointF(e.X, e.Y));
- return clientPt.X < -16384 || clientPt.Y < -16384;
- }
-
- public bool IsMouseEntered
- {
- get
- {
- return _mouseEnter > 0;
- }
- }
-
- public void PerformMouseEnter()
- {
- MouseEnter();
- }
-
- private void MouseEnter()
- {
- ++_mouseEnter;
-
- if (_mouseEnter == 1)
- {
- OnMouseEnter();
- }
- }
-
- protected virtual void OnMouseEnter()
- {
- }
-
- public void PerformMouseLeave()
- {
- MouseLeave();
- }
-
- private void MouseLeave()
- {
- if (_mouseEnter == 1)
- {
- _mouseEnter = 0;
- OnMouseLeave();
- }
- else
- {
- _mouseEnter = Math.Max(0, _mouseEnter - 1);
- }
- }
-
- protected virtual void OnMouseLeave()
- {
- }
-
- public void PerformMouseMove(MouseEventArgs e)
- {
- if (IsOverflow(e))
- {
- return;
- }
-
- if (e is StylusEventArgs)
- {
- if (SupportsInk)
- {
- StylusMove(e as StylusEventArgs);
- }
-
- // if the tool does not claim ink support, discard
- }
- else
- {
- MouseMove(e);
- }
- }
-
- public void PerformMouseDown(MouseEventArgs e)
- {
- if (IsOverflow(e))
- {
- return;
- }
-
- if (e is StylusEventArgs)
- {
- if (SupportsInk)
- {
- StylusDown(e as StylusEventArgs);
- }
-
- // if the tool does not claim ink support, discard
- }
- else
- {
- if (SupportsInk)
- {
- DocumentWorkspace.Focus();
- }
-
- MouseDown(e);
- }
- }
-
- public void PerformMouseUp(MouseEventArgs e)
- {
- if (IsOverflow(e))
- {
- return;
- }
-
- if (e is StylusEventArgs)
- {
- if (SupportsInk)
- {
- StylusUp(e as StylusEventArgs);
- }
-
- // if the tool does not claim ink support, discard
- }
- else
- {
- MouseUp(e);
- }
- }
-
- public void PerformKeyPress(KeyPressEventArgs e)
- {
- KeyPress(e);
- }
-
- public void PerformKeyPress(Keys key)
- {
- KeyPress(key);
- }
-
- public void PerformKeyUp(KeyEventArgs e)
- {
- KeyUp(e);
- }
-
- public void PerformKeyDown(KeyEventArgs e)
- {
- KeyDown(e);
- }
-
- public void PerformClick()
- {
- Click();
- }
-
- public void PerformPulse()
- {
- Pulse();
- }
-
- public void PerformPaste(IDataObject data, out bool handled)
- {
- Paste(data, out handled);
- }
-
- public void PerformPasteQuery(IDataObject data, out bool canHandle)
- {
- PasteQuery(data, out canHandle);
- }
-
- private void Activate()
- {
- Debug.Assert(Active != true, "already active!");
- Active = true;
-
- HandCursor = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursor.cur"));
- HandCursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorMouseDown.cur"));
- HandCursorInvalid = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorInvalid.cur"));
-
- _panTracking = false;
- _panMode = false;
- _mouseDown = 0;
- _savedTiles = null;
- _saveRegion = null;
-
- ScratchSurface = DocumentWorkspace.BorrowScratchSurface(GetType().Name + ": Tool.Activate()");
- #if DEBUG
- haveClearedScratch = false;
- #endif
-
- Selection.Changing += SelectionChangingHandler;
- Selection.Changed += SelectionChangedHandler;
- HistoryStack.ExecutingHistoryMemento += ExecutingHistoryMemento;
- HistoryStack.ExecutedHistoryMemento += ExecutedHistoryMemento;
- HistoryStack.FinishedStepGroup += FinishedHistoryStepGroup;
-
- _trackingNub = new MoveNubRenderer(RendererList)
- {
- Visible = false,
- Size = new SizeF(10, 10),
- Shape = MoveNubShape.Compass
- };
- RendererList.Add(_trackingNub, false);
-
- OnActivate();
- }
-
- void FinishedHistoryStepGroup(object sender, EventArgs e)
- {
- OnFinishedHistoryStepGroup();
- }
-
- protected virtual void OnFinishedHistoryStepGroup()
- {
- }
-
- /// <summary>
- /// This method is called when the tool is being activated; that is, when the
- /// user has chosen to use this tool by clicking on it on a toolbar.
- /// </summary>
- protected virtual void OnActivate()
- {
- }
-
- private void Deactivate()
- {
- Debug.Assert(this.Active != false, "not active!");
-
- Active = false;
-
- Selection.Changing -= SelectionChangingHandler;
- Selection.Changed -= SelectionChangedHandler;
-
- HistoryStack.ExecutingHistoryMemento -= ExecutingHistoryMemento;
- HistoryStack.ExecutedHistoryMemento -= ExecutedHistoryMemento;
- HistoryStack.FinishedStepGroup -= FinishedHistoryStepGroup;
-
- OnDeactivate();
-
- RendererList.Remove(_trackingNub);
- _trackingNub.Dispose();
- _trackingNub = null;
-
- DocumentWorkspace.ReturnScratchSurface(ScratchSurface);
- ScratchSurface = null;
-
- if (_saveRegion != null)
- {
- _saveRegion.Dispose();
- _saveRegion = null;
- }
-
- _savedTiles = null;
-
- if (HandCursor != null)
- {
- HandCursor.Dispose();
- HandCursor = null;
- }
-
- if (HandCursorMouseDown != null)
- {
- HandCursorMouseDown.Dispose();
- HandCursorMouseDown = null;
- }
-
- if (HandCursorInvalid == null) return;
- HandCursorInvalid.Dispose();
- HandCursorInvalid = null;
- }
-
- /// <summary>
- /// This method is called when the tool is being deactivated; that is, when the
- /// user has chosen to use another tool by clicking on another tool on a
- /// toolbar.
- /// </summary>
- protected virtual void OnDeactivate()
- {
- }
-
- private void StylusDown(StylusEventArgs e)
- {
- if (!_panMode)
- {
- OnStylusDown(e);
- }
- }
-
- protected virtual void OnStylusDown(StylusEventArgs e)
- {
- }
-
- private void StylusMove(StylusEventArgs e)
- {
- if (!_panMode)
- {
- OnStylusMove(e);
- }
- }
-
- protected virtual void OnStylusMove(StylusEventArgs e)
- {
- if (_mouseDown > 0)
- {
- ScrollIfNecessary(new PointF(e.X, e.Y));
- }
- }
-
- private void StylusUp(StylusEventArgs e)
- {
- if (_panTracking)
- {
- _trackingNub.Visible = false;
- _panTracking = false;
- Cursor = HandCursor;
- }
- else
- {
- OnStylusUp(e);
- }
- }
-
- protected virtual void OnStylusUp(StylusEventArgs e)
- {
- }
-
- private void MouseMove(MouseEventArgs e)
- {
- if (_ignoreMouseMove > 0)
- {
- --_ignoreMouseMove;
- }
- else if (_panTracking && e.Button == MouseButtons.Left)
- {
- // Pan the document, using Stylus coordinates. This is done in
- // MouseMove instead of StylusMove because StylusMove is
- // asynchronous, and would not 'feel' right (pan motions would
- // stack up)
-
- var position = new Point(e.X, e.Y);
- RectangleF visibleRect = DocumentWorkspace.VisibleDocumentRectangleF;
- PointF visibleCenterPt = Utility.GetRectangleCenter(visibleRect);
- var delta = new PointF(e.X - _lastPanMouseXY.X, e.Y - _lastPanMouseXY.Y);
- PointF newScroll = DocumentWorkspace.DocumentScrollPositionF;
-
- if (delta.X != 0 || delta.Y != 0)
- {
- newScroll.X -= delta.X;
- newScroll.Y -= delta.Y;
-
- _lastPanMouseXY = new Point(e.X, e.Y);
- _lastPanMouseXY.X -= (int)Math.Truncate(delta.X);
- _lastPanMouseXY.Y -= (int)Math.Truncate(delta.Y);
-
- ++_ignoreMouseMove; // setting DocumentScrollPosition incurs a MouseMove event. ignore it prevents 'jittering' at non-integral zoom levels (like, say, 743%)
- DocumentWorkspace.DocumentScrollPositionF = newScroll;
- Update();
- }
-
- }
- else if (!_panMode)
- {
- OnMouseMove(e);
- }
-
- _lastMouseXY = new Point(e.X, e.Y);
- _lastButton = e.Button;
- }
-
- /// <summary>
- /// This method is called when the Tool is active and the mouse is moving within
- /// the document canvas area.
- /// </summary>
- /// <param name="e">Contains information about where the mouse cursor is, in document coordinates.</param>
- protected virtual void OnMouseMove(MouseEventArgs e)
- {
- if (_panMode || _mouseDown > 0)
- {
- ScrollIfNecessary(new PointF(e.X, e.Y));
- }
- }
-
- private void MouseDown(MouseEventArgs e)
- {
- ++_mouseDown;
-
- if (_panMode)
- {
- _panTracking = true;
- _lastPanMouseXY = new Point(e.X, e.Y);
-
- if (CanPan())
- {
- Cursor = HandCursorMouseDown;
- }
- }
- else
- {
- OnMouseDown(e);
- }
-
- _lastMouseXY = new Point(e.X, e.Y);
- }
-
- /// <summary>
- /// This method is called when the Tool is active and a mouse button has been
- /// pressed within the document area.
- /// </summary>
- /// <param name="e">Contains information about where the mouse cursor is, in document coordinates, and which mouse buttons were pressed.</param>
- protected virtual void OnMouseDown(MouseEventArgs e)
- {
- _lastButton = e.Button;
- }
-
- private void MouseUp(MouseEventArgs e)
- {
- --_mouseDown;
-
- if (!_panMode)
- {
- OnMouseUp(e);
- }
-
- _lastMouseXY = new Point(e.X, e.Y);
- }
-
- /// <summary>
- /// This method is called when the Tool is active and a mouse button has been
- /// released within the document area.
- /// </summary>
- /// <param name="e">Contains information about where the mouse cursor is, in document coordinates, and which mouse buttons were released.</param>
- protected virtual void OnMouseUp(MouseEventArgs e)
- {
- _lastButton = e.Button;
- }
-
- private void Click()
- {
- OnClick();
- }
-
- /// <summary>
- /// This method is called when the Tool is active and a mouse button has been
- /// clicked within the document area. If you need more specific information,
- /// such as where the mouse was clicked and which button was used, respond to
- /// the MouseDown/MouseUp events.
- /// </summary>
- protected virtual void OnClick()
- {
- }
-
- private void KeyPress(KeyPressEventArgs e)
- {
- OnKeyPress(e);
- }
-
- private static DateTime _lastToolSwitch = DateTime.MinValue;
-
- // if we are pressing 'S' to switch to the selection tools, then consecutive
- // presses of 'S' should switch to the next selection tol in the list. however,
- // if we wait awhile then pressing 'S' should go to the *first* selection
- // tool. 'awhile' is defined by this variable.
- private static readonly TimeSpan ToolSwitchReset = new TimeSpan(0, 0, 0, 2, 0);
-
- private const char DecPenSizeShortcut = '[';
- private const char DecPenSizeBy5Shortcut = (char)27; // Ctrl [ but must also test that Ctrl is down
- private const char IncPenSizeShortcut = ']';
- private const char IncPenSizeBy5Shortcut = (char)29; // Ctrl ] but must also test that Ctrl is down
- private const char SwapColorsShortcut = 'x';
- private const char SwapPrimarySecondaryChoice = 'c';
- private readonly char[] _wildShortcuts = new[] { ',', '.', '/' };
-
- // Return true if the key is handled, false if not.
- protected virtual bool OnWildShortcutKey(int ordinal)
- {
- return false;
- }
-
- /// <summary>
- /// This method is called when the tool is active and a keyboard key is pressed
- /// and released. If you respond to the keyboard key, set e.Handled to true.
- /// </summary>
- protected virtual void OnKeyPress(KeyPressEventArgs e)
- {
- if (e.Handled || !DocumentWorkspace.Focused) return;
- int wsIndex = Array.IndexOf(_wildShortcuts, e.KeyChar);
-
- if (wsIndex != -1)
- {
- e.Handled = OnWildShortcutKey(wsIndex);
- }
- else switch (e.KeyChar)
- {
- case SwapColorsShortcut:
- AppWorkspace.Widgets.ColorsForm.SwapUserColors();
- e.Handled = true;
- break;
- case SwapPrimarySecondaryChoice:
- AppWorkspace.Widgets.ColorsForm.ToggleWhichUserColor();
- e.Handled = true;
- break;
- case DecPenSizeShortcut:
- AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(-1.0f);
- e.Handled = true;
- break;
- default:
- if (e.KeyChar == DecPenSizeBy5Shortcut && (ModifierKeys & Keys.Control) != 0)
- {
- AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(-5.0f);
- e.Handled = true;
- }
- else if (e.KeyChar == IncPenSizeShortcut)
- {
- AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(+1.0f);
- e.Handled = true;
- }
- else if (e.KeyChar == IncPenSizeBy5Shortcut && (ModifierKeys & Keys.Control) != 0)
- {
- AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(+5.0f);
- e.Handled = true;
- }
- else
- {
- ToolInfo[] toolInfos = DocumentWorkspace.ToolInfos;
- Type currentToolType = DocumentWorkspace.GetToolType();
- int currentTool = 0;
-
- if (0 != (ModifierKeys & Keys.Shift))
- {
- Array.Reverse(toolInfos);
- }
-
- if (char.ToLower(HotKey) != char.ToLower(e.KeyChar) ||
- (DateTime.Now - _lastToolSwitch) > ToolSwitchReset)
- {
- // If it's been a short time since they pressed a tool switching hotkey,
- // we will start from the beginning of this list. This helps to enable two things:
- // 1) If multiple tools have the same hotkey, the user may press that hotkey
- // to cycle through them
- // 2) After a period of time, pressing the hotkey will revert to always
- // choosing the first tool in that list of tools which have the same hotkey.
- currentTool = -1;
- }
- else
- {
- for (int t = 0; t < toolInfos.Length; ++t)
- {
- if (toolInfos[t].ToolType != currentToolType) continue;
- currentTool = t;
- break;
- }
- }
-
- for (int t = 0; t < toolInfos.Length; ++t)
- {
- int newTool = (t + currentTool + 1) % toolInfos.Length;
- ToolInfo localToolInfo = toolInfos[newTool];
-
- if (localToolInfo.ToolType == DocumentWorkspace.GetToolType() &&
- localToolInfo.SkipIfActiveOnHotKey)
- {
- continue;
- }
-
- if (char.ToLower(localToolInfo.HotKey) != char.ToLower(e.KeyChar)) continue;
- if (!IsMouseDown)
- {
- AppWorkspace.Widgets.ToolsControl.SelectTool(localToolInfo.ToolType);
- }
-
- e.Handled = true;
- _lastToolSwitch = DateTime.Now;
- break;
- }
-
- // If the keypress is still not handled ...
- if (!e.Handled)
- {
- switch (e.KeyChar)
- {
- // By default, Esc/Enter clear the current selection if there is any
- case (char)13: // Enter
- case (char)27: // Escape
- if (_mouseDown == 0 && !Selection.IsEmpty)
- {
- e.Handled = true;
- DocumentWorkspace.ExecuteFunction(new DeselectFunction());
- }
-
- break;
- }
- }
- }
- break;
- }
- }
-
- private DateTime _lastKeyboardMove = DateTime.MinValue;
- private Keys _lastKey;
- private int _keyboardMoveSpeed = 1;
- private int _keyboardMoveRepeats = 0;
-
- private void KeyPress(Keys key)
- {
- OnKeyPress(key);
- }
-
- /// <summary>
- /// This method is called when the tool is active and a keyboard key is pressed
- /// and released that is not representable with a regular Unicode chararacter.
- /// An example would be the arrow keys.
- /// </summary>
- protected virtual void OnKeyPress(Keys key)
- {
- Point dir = Point.Empty;
-
- if (key != _lastKey)
- {
- _lastKeyboardMove = DateTime.MinValue;
- }
-
- _lastKey = key;
-
- switch (key)
- {
- case Keys.Left:
- --dir.X;
- break;
-
- case Keys.Right:
- ++dir.X;
- break;
-
- case Keys.Up:
- --dir.Y;
- break;
-
- case Keys.Down:
- ++dir.Y;
- break;
- }
-
- if (dir.Equals(Point.Empty)) return;
- long span = DateTime.Now.Ticks - _lastKeyboardMove.Ticks;
-
- if ((span * 4) > TimeSpan.TicksPerSecond)
- {
- _keyboardMoveRepeats = 0;
- _keyboardMoveSpeed = 1;
- }
- else
- {
- _keyboardMoveRepeats++;
-
- if (_keyboardMoveRepeats > 15 && (_keyboardMoveRepeats % 4) == 0)
- {
- _keyboardMoveSpeed++;
- }
- }
-
- _lastKeyboardMove = DateTime.Now;
-
- var offset = (int)(Math.Ceiling(DocumentWorkspace.ScaleFactor.Ratio) * _keyboardMoveSpeed);
- Cursor.Position = new Point(Cursor.Position.X + offset * dir.X, Cursor.Position.Y + offset * dir.Y);
-
- Point location = DocumentWorkspace.PointToScreen(Point.Truncate(DocumentWorkspace.DocumentToClient(PointF.Empty)));
-
- var stylusLocF = new PointF((float)Cursor.Position.X - (float)location.X, (float)Cursor.Position.Y - (float)location.Y);
- var stylusLoc = new Point(Cursor.Position.X - location.X, Cursor.Position.Y - location.Y);
-
- stylusLoc = DocumentWorkspace.ScaleFactor.UnscalePoint(stylusLoc);
- stylusLocF = DocumentWorkspace.ScaleFactor.UnscalePoint(stylusLocF);
-
- DocumentWorkspace.PerformDocumentMouseMove(new StylusEventArgs(_lastButton, 1, stylusLocF.X, stylusLocF.Y, 0, 1.0f));
- DocumentWorkspace.PerformDocumentMouseMove(new MouseEventArgs(_lastButton, 1, stylusLoc.X, stylusLoc.Y, 0));
- }
-
- private bool CanPan()
- {
- Rectangle vis = Utility.RoundRectangle(DocumentWorkspace.VisibleDocumentRectangleF);
- vis.Intersect(Document.Bounds);
-
- return vis != Document.Bounds;
- }
-
- private void KeyUp(KeyEventArgs e)
- {
- if (_panMode)
- {
- _panMode = false;
- _panTracking = false;
- _trackingNub.Visible = false;
- Cursor = _panOldCursor;
- _panOldCursor = null;
- e.Handled = true;
- }
-
- OnKeyUp(e);
- }
-
- /// <summary>
- /// This method is called when the tool is active and a keyboard key is pressed.
- /// If you respond to the keyboard key, set e.Handled to true.
- /// </summary>
- protected virtual void OnKeyUp(KeyEventArgs e)
- {
- _keysThatAreDown.Clear();
- }
-
- private void KeyDown(KeyEventArgs e)
- {
- OnKeyDown(e);
- }
-
- /// <summary>
- /// This method is called when the tool is active and a keyboard key is released
- /// Before responding, check that e.Handled is false, and if you then respond to
- /// the keyboard key, set e.Handled to true.
- /// </summary>
- protected virtual void OnKeyDown(KeyEventArgs e)
- {
- if (e.Handled) return;
- if (!_keysThatAreDown.Contains(e.KeyData))
- {
- _keysThatAreDown.Add(e.KeyData, new KeyTimeInfo());
- }
-
- if (!IsMouseDown &&
- !_panMode &&
- e.KeyCode == Keys.Space)
- {
- _panMode = true;
- _panOldCursor = Cursor;
-
- Cursor = CanPan() ? HandCursor : HandCursorInvalid;
- }
-
- // arrow keys are processed in another way
- // we get their KeyDown but no KeyUp, so they can not be handled
- // by our normal methods
- OnKeyPress(e.KeyData);
- }
-
- private void SelectionChanging()
- {
- OnSelectionChanging();
- }
-
- /// <summary>
- /// This method is called when the Tool is active and the selection area is
- /// about to be changed.
- /// </summary>
- protected virtual void OnSelectionChanging()
- {
- }
-
- private void SelectionChanged()
- {
- OnSelectionChanged();
- }
-
- /// <summary>
- /// This method is called when the Tool is active and the selection area has
- /// been changed.
- /// </summary>
- protected virtual void OnSelectionChanged()
- {
- }
-
- private void ExecutingHistoryMemento(object sender, ExecutingHistoryMementoEventArgs e)
- {
- OnExecutingHistoryMemento(e);
- }
-
- protected virtual void OnExecutingHistoryMemento(ExecutingHistoryMementoEventArgs e)
- {
- }
-
- private void ExecutedHistoryMemento(object sender, ExecutedHistoryMementoEventArgs e)
- {
- OnExecutedHistoryMemento(e);
- }
-
- protected virtual void OnExecutedHistoryMemento(ExecutedHistoryMementoEventArgs e)
- {
- }
-
- private void PasteQuery(IDataObject data, out bool canHandle)
- {
- OnPasteQuery(data, out canHandle);
- }
-
- /// <summary>
- /// This method is called when the system is querying a tool as to whether
- /// it can handle a pasted object.
- /// </summary>
- /// <param name="data">
- /// The clipboard data that was pasted by the user that should be inspected.
- /// </param>
- /// <param name="canHandle">
- /// <b>true</b> if the data can be handled by the tool, <b>false</b> if not.
- /// </param>
- /// <remarks>
- /// If you do not set canHandle to <b>true</b> then the tool will not be
- /// able to respond to the Edit menu's Paste item.
- /// </remarks>
- protected virtual void OnPasteQuery(IDataObject data, out bool canHandle)
- {
- canHandle = false;
- }
-
- private void Paste(IDataObject data, out bool handled)
- {
- OnPaste(data, out handled);
- }
-
- /// <summary>
- /// This method is called when the user invokes a paste operation. Tools get
- /// the first chance to handle this data.
- /// </summary>
- /// <param name="data">
- /// The data that was pasted by the user.
- /// </param>
- /// <param name="handled">
- /// <b>true</b> if the data was handled and pasted, <b>false</b> if not.
- /// </param>
- /// <remarks>
- /// If you do not set handled to <b>true</b> the event will be passed to the
- /// global paste handler.
- /// </remarks>
- protected virtual void OnPaste(IDataObject data, out bool handled)
- {
- handled = false;
- }
-
- private void Pulse()
- {
- OnPulse();
- }
-
- protected bool IsFormActive
- {
- get
- {
- return (ReferenceEquals(Form.ActiveForm, DocumentWorkspace.FindForm()));
- }
- }
-
- /// <summary>
- /// This method is called many times per second, called by the DocumentWorkspace.
- /// </summary>
- protected virtual void OnPulse()
- {
- if (!_panTracking || _lastButton != MouseButtons.Right) return;
- Point position = _lastMouseXY;
- RectangleF visibleRect = DocumentWorkspace.VisibleDocumentRectangleF;
- PointF visibleCenterPt = Utility.GetRectangleCenter(visibleRect);
- var delta = new PointF(position.X - visibleCenterPt.X, position.Y - visibleCenterPt.Y);
- PointF newScroll = DocumentWorkspace.DocumentScrollPositionF;
-
- _trackingNub.Visible = true;
-
- if (delta.X == 0 && delta.Y == 0) return;
- newScroll.X += delta.X;
- newScroll.Y += delta.Y;
-
- ++_ignoreMouseMove; // setting DocumentScrollPosition incurs a MouseMove event. ignore it prevents 'jittering' at non-integral zoom levels (like, say, 743%)
- UI.SuspendControlPainting(DocumentWorkspace);
- DocumentWorkspace.DocumentScrollPositionF = newScroll;
- _trackingNub.Visible = true;
- _trackingNub.Location = Utility.GetRectangleCenter(DocumentWorkspace.VisibleDocumentRectangleF);
- UI.ResumeControlPainting(DocumentWorkspace);
- DocumentWorkspace.Invalidate(true);
- Update();
- }
-
- protected bool ScrollIfNecessary(PointF position)
- {
- if (!AutoScroll || !CanPan())
- {
- return false;
- }
-
- RectangleF visible = DocumentWorkspace.VisibleDocumentRectangleF;
- PointF lastScrollPosition = DocumentWorkspace.DocumentScrollPositionF;
- PointF delta = PointF.Empty;
- PointF zoomedPoint = PointF.Empty;
-
- zoomedPoint.X = Utility.Lerp((visible.Left + visible.Right) / 2.0f, position.X, 1.02f);
- zoomedPoint.Y = Utility.Lerp((visible.Top + visible.Bottom) / 2.0f, position.Y, 1.02f);
-
- if (zoomedPoint.X < visible.Left)
- {
- delta.X = zoomedPoint.X - visible.Left;
- }
- else if (zoomedPoint.X > visible.Right)
- {
- delta.X = zoomedPoint.X - visible.Right;
- }
-
- if (zoomedPoint.Y < visible.Top)
- {
- delta.Y = zoomedPoint.Y - visible.Top;
- }
- else if (zoomedPoint.Y > visible.Bottom)
- {
- delta.Y = zoomedPoint.Y - visible.Bottom;
- }
-
- if (!delta.IsEmpty)
- {
- var newScrollPosition = new PointF(lastScrollPosition.X + delta.X, lastScrollPosition.Y + delta.Y);
- DocumentWorkspace.DocumentScrollPositionF = newScrollPosition;
- Update();
- return true;
- }
- return false;
- }
-
- private void SelectionChangingHandler(object sender, EventArgs e)
- {
- OnSelectionChanging();
- }
-
- private void SelectionChangedHandler(object sender, EventArgs e)
- {
- OnSelectionChanged();
- }
-
- protected void SetStatus(ImageResource statusIcon, string statusText)
- {
- if (statusIcon == null && statusText != null)
- {
- statusIcon = PdnResources.GetImageResource("Icons.MenuHelpHelpTopicsIcon.png");
- }
-
- DocumentWorkspace.SetStatus(statusText, statusIcon);
- }
-
- protected SurfaceBoxRendererList RendererList
- {
- get
- {
- return DocumentWorkspace.RendererList;
- }
- }
-
- protected void Update()
- {
- DocumentWorkspace.Update();
- }
-
- protected object GetStaticData()
- {
- return DocumentWorkspace.GetStaticToolData(GetType());
- }
-
- protected void SetStaticData(object data)
- {
- DocumentWorkspace.SetStaticToolData(GetType(), data);
- }
-
- // NOTE: Your constructor must be able to run successfully with a documentWorkspace
- // of null. This is sent in while the DocumentControl static constructor
- // class is building a list of ToolInfo instances, so that it may construct
- // the list without having to also construct a DocumentControl.
- public Tool(DocumentWorkspace documentWorkspace,
- ImageResource toolBarImage,
- string name,
- string helpText,
- char hotKey,
- bool skipIfActiveOnHotKey,
- ToolBarConfigItems toolBarConfigItems)
- {
- _documentWorkspace = documentWorkspace;
- _toolBarImage = toolBarImage;
- _toolInfo = new ToolInfo(name, helpText, toolBarImage, hotKey, skipIfActiveOnHotKey, toolBarConfigItems, GetType());
-
- if (_documentWorkspace != null)
- {
- _documentWorkspace.UpdateStatusBarToToolHelpText(this);
- }
- }
-
- ~Tool()
- {
- Dispose(false);
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- Debug.Assert(!Active, "Tool is still active!");
-
- if (!disposing) return;
- if (_saveRegion != null)
- {
- _saveRegion.Dispose();
- _saveRegion = null;
- }
-
- OnDisposed();
- }
-
- public event EventHandler Disposed;
- private void OnDisposed()
- {
- if (Disposed != null)
- {
- Disposed(this, EventArgs.Empty);
- }
- }
-
- public Form AssociatedForm
- {
- get
- {
- return AppWorkspace.FindForm();
- }
- }
- }
- }