Tool.cs :  » GUI » Paint.net » PaintDotNet » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » GUI » Paint.net 
Paint.net » PaintDotNet » Tool.cs
/////////////////////////////////////////////////////////////////////////////////
// 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.HistoryFunctions;
using PaintDotNet.SystemLayer;
using System;
using System.Collections;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
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 ImageResource toolBarImage;
        private Cursor cursor;
        private ToolInfo toolInfo;
        private int mouseDown = 0; // incremented for every MouseDown, decremented for every MouseUp
        private int ignoreMouseMove = 0; // 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 = false; // 'true' when the user is holding down the spacebar
        private bool panTracking = false; // 'true' when panMode is true, and when the mouse is down (which is when MouseMove should do panning)
        private MoveNubRenderer trackingNub = null; // when we are in pan-tracking mode, we draw this in the center of the screen

        private DocumentWorkspace documentWorkspace;

        private bool active = false;
        protected bool autoScroll = true;
        private Hashtable keysThatAreDown = new Hashtable();
        private MouseButtons lastButton = MouseButtons.None;
        private Surface scratchSurface;
        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
            {
#if DEBUG
                if (!haveClearedScratch)
                {
                    scratchSurface.Clear(ColorBgra.FromBgra(64, 128, 192, 128));
                    haveClearedScratch = true;
                }
#endif

                return scratchSurface;
            }
        }

        protected Document Document
        {
            get
            {
                return DocumentWorkspace.Document;
            }
        }

        public DocumentWorkspace DocumentWorkspace
        {
            get
            {
                return this.documentWorkspace;
            }
        }

        public AppWorkspace AppWorkspace
        {
            get
            {
                return DocumentWorkspace.AppWorkspace;
            }
        }

        protected HistoryStack HistoryStack
        {
            get
            {
                return DocumentWorkspace.History;
            }
        }

        protected AppEnvironment AppEnvironment
        {
            get
            {
                return this.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()
        {
            this.savedTiles = null;
        }

        public void ClearSavedRegion()
        {
            if (this.saveRegion != null)
            {
                this.saveRegion.Dispose();
                this.saveRegion = null;
            }
        }

        public void RestoreRegion(PdnRegion region)
        {
            if (region != null)
            {
                BitmapLayer activeLayer = (BitmapLayer)ActiveLayer;
                activeLayer.Surface.CopySurface(this.ScratchSurface, region);
                activeLayer.Invalidate(region);
            }
        }

        public void RestoreSavedRegion()
        {
            if (this.saveRegion != null)
            {
                BitmapLayer activeLayer = (BitmapLayer)ActiveLayer;
                activeLayer.Surface.CopySurface(this.ScratchSurface, this.saveRegion);
                activeLayer.Invalidate(this.saveRegion);
                this.saveRegion.Dispose();
                this.saveRegion = null;
            }
        }

        private const int saveTileGranularity = 32;
        private BitVector2D savedTiles;

        public void SaveRegion(PdnRegion saveMeRegion, Rectangle saveMeBounds)
        {
            BitmapLayer activeLayer = (BitmapLayer)ActiveLayer;

            if (savedTiles == null)
            {
                savedTiles = new BitVector2D(
                    (activeLayer.Width + saveTileGranularity - 1) / saveTileGranularity,
                    (activeLayer.Height + saveTileGranularity - 1) / saveTileGranularity);

                savedTiles.Clear(false);
            }

            Rectangle regionBounds;
            if (saveMeRegion == null)
            {
                regionBounds = saveMeBounds;
            }
            else
            {
                regionBounds = 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))
                    {
                        Rectangle tileBounds = new Rectangle(tileX * saveTileGranularity, tileY * saveTileGranularity,
                            saveTileGranularity, saveTileGranularity);

                        tileBounds.Intersect(activeLayer.Bounds);

                        if (rowAccumBounds == Rectangle.Empty)
                        {
                            rowAccumBounds = tileBounds;
                        }
                        else
                        {
                            rowAccumBounds = 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)
                {
                    using (Surface dst = ScratchSurface.CreateWindow(rowAccumBounds),
                                   src = activeLayer.Surface.CreateWindow(rowAccumBounds))
                    {
                        dst.CopySurface(src);
                    }

                    rowAccumBounds = Rectangle.Empty;
                }
            }

            if (this.saveRegion != null)
            {
                this.saveRegion.Dispose();
                this.saveRegion = null;
            }

            if (saveMeRegion != null)
            {
                this.saveRegion = saveMeRegion.Clone();
            }
        }

        private sealed class KeyTimeInfo
        {
            public DateTime KeyDownTime;
            public DateTime LastKeyPressPulse;
            private int repeats = 0;

            public int Repeats 
            {
                get 
                {
                    return repeats;
                }

                set 
                {
                    repeats = value;
                }
            }

            public KeyTimeInfo()
            {
                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
            {
                return this.active;
            }
        }

        /// <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 this.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 this.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 this.cursor;
            }

            set
            {
                OnCursorChanging();
                this.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 this.toolInfo.Name;
            }
        }

        /// <summary>
        /// A short description of how to use the tool.
        /// </summary>
        public string HelpText
        {
            get
            {
                return this.toolInfo.HelpText;
            }
        }

        public ToolInfo Info
        {
            get
            {
                return this.toolInfo;
            }
        }

        public ToolBarConfigItems ToolBarConfigItems
        {
            get
            {
                return this.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 this.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 this.mouseEnter > 0;
            }
        }

        public void PerformMouseEnter()
        {
            MouseEnter();
        }

        private void MouseEnter()
        {
            ++this.mouseEnter;

            if (this.mouseEnter == 1)
            {
                OnMouseEnter();
            }
        }

        protected virtual void OnMouseEnter()
        {
        }

        public void PerformMouseLeave()
        {
            MouseLeave();
        }

        private void MouseLeave()
        {
            if (this.mouseEnter == 1)
            {
                this.mouseEnter = 0;
                OnMouseLeave();
            }
            else
            {
                this.mouseEnter = Math.Max(0, this.mouseEnter - 1);
            }
        }

        protected virtual void OnMouseLeave()
        {
        }

        public void PerformMouseMove(MouseEventArgs e)
        {
            if (IsOverflow(e)) 
            {
                return;
            }

            if (e is StylusEventArgs)
            {
                if (this.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 (this.SupportsInk) 
                {
                    StylusDown(e as StylusEventArgs);
                }

                // if the tool does not claim ink support, discard
            }
            else
            {
                if (this.SupportsInk) 
                {
                    DocumentWorkspace.Focus();
                } 

                MouseDown(e);
            }
        }

        public void PerformMouseUp(MouseEventArgs e)
        {
            if (IsOverflow(e)) 
            {
                return;
            }

            if (e is StylusEventArgs) 
            {
                if (this.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(this.active != true, "already active!");
            this.active = true;

            this.handCursor = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursor.cur"));
            this.handCursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorMouseDown.cur"));
            this.handCursorInvalid = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorInvalid.cur"));

            this.panTracking = false;
            this.panMode = false;
            this.mouseDown = 0;
            this.savedTiles = null;
            this.saveRegion = null;

            this.scratchSurface = DocumentWorkspace.BorrowScratchSurface(this.GetType().Name + ": Tool.Activate()");
#if DEBUG
            this.haveClearedScratch = false;
#endif

            Selection.Changing += new EventHandler(SelectionChangingHandler);
            Selection.Changed += new EventHandler(SelectionChangedHandler);
            HistoryStack.ExecutingHistoryMemento += new ExecutingHistoryMementoEventHandler(ExecutingHistoryMemento);
            HistoryStack.ExecutedHistoryMemento += new ExecutedHistoryMementoEventHandler(ExecutedHistoryMemento);
            HistoryStack.FinishedStepGroup += new EventHandler(FinishedHistoryStepGroup);

            this.trackingNub = new MoveNubRenderer(this.RendererList);
            this.trackingNub.Visible = false;
            this.trackingNub.Size = new SizeF(10, 10);
            this.trackingNub.Shape = MoveNubShape.Compass;
            this.RendererList.Add(this.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!");

            this.active = false;

            Selection.Changing -= new EventHandler(SelectionChangingHandler);
            Selection.Changed -= new EventHandler(SelectionChangedHandler);

            HistoryStack.ExecutingHistoryMemento -= new ExecutingHistoryMementoEventHandler(ExecutingHistoryMemento);
            HistoryStack.ExecutedHistoryMemento -= new ExecutedHistoryMementoEventHandler(ExecutedHistoryMemento);
            HistoryStack.FinishedStepGroup -= new EventHandler(FinishedHistoryStepGroup);

            OnDeactivate();

            this.RendererList.Remove(this.trackingNub);
            this.trackingNub.Dispose();
            this.trackingNub = null;

            DocumentWorkspace.ReturnScratchSurface(this.scratchSurface);
            this.scratchSurface = null;

            if (this.saveRegion != null)
            {
                this.saveRegion.Dispose();
                this.saveRegion = null;
            }

            this.savedTiles = null;

            if (this.handCursor != null)
            {
                this.handCursor.Dispose();
                this.handCursor = null;
            }

            if (this.handCursorMouseDown != null)
            {
                this.handCursorMouseDown.Dispose();
                this.handCursorMouseDown = null;
            }

            if (this.handCursorInvalid != null)
            {
                this.handCursorInvalid.Dispose();
                this.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 (!this.panMode)
            {
                OnStylusDown(e);
            }
        }

        protected virtual void OnStylusDown(StylusEventArgs e)
        {
        }

        private void StylusMove(StylusEventArgs e)
        {
            if (!this.panMode)
            {
                OnStylusMove(e);
            }
        }

        protected virtual void OnStylusMove(StylusEventArgs e)
        {
            if (this.mouseDown > 0)
            {
                ScrollIfNecessary(new PointF(e.X, e.Y));
            }
        }

        private void StylusUp(StylusEventArgs e)
        {
            if (this.panTracking)
            {
                this.trackingNub.Visible = false;
                this.panTracking = false;
                this.Cursor = this.handCursor;
            }
            else
            {
                OnStylusUp(e);
            }
        }

        protected virtual void OnStylusUp(StylusEventArgs e)
        {
        }

        private void MouseMove(MouseEventArgs e)
        {
            if (this.ignoreMouseMove > 0)
            {
                --this.ignoreMouseMove;
            }
            else if (this.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)

                Point position = new Point(e.X, e.Y);
                RectangleF visibleRect = DocumentWorkspace.VisibleDocumentRectangleF;
                PointF visibleCenterPt = Utility.GetRectangleCenter(visibleRect);
                PointF 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);

                    ++this.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 (!this.panMode)
            {
                OnMouseMove(e);
            }

            this.lastMouseXY = new Point(e.X, e.Y);
            this.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 (this.panMode || this.mouseDown > 0)
            {
                ScrollIfNecessary(new PointF(e.X, e.Y));
            }
        }

        private void MouseDown(MouseEventArgs e)
        {
            ++this.mouseDown;
            
            if (this.panMode)
            {
                this.panTracking = true;
                this.lastPanMouseXY = new Point(e.X, e.Y);

                if (this.CanPan())
                {
                    this.Cursor = this.handCursorMouseDown;
                }
            }
            else
            {
                OnMouseDown(e);
            }

            this.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)
        {
            this.lastButton = e.Button;
        }

        private void MouseUp(MouseEventArgs e)
        {
            --this.mouseDown;

            if (!this.panMode)
            {
                OnMouseUp(e);
            }

            this.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)
        {
            this.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 char[] wildShortcuts = new char[] { ',', '.', '/' };

        // 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)
            {
                int wsIndex = Array.IndexOf<char>(wildShortcuts, e.KeyChar);

                if (wsIndex != -1)
                {
                    e.Handled = OnWildShortcutKey(wsIndex);
                }
                else if (e.KeyChar == swapColorsShortcut)
                {
                    AppWorkspace.Widgets.ColorsForm.SwapUserColors();
                    e.Handled = true;
                }
                else if (e.KeyChar == swapPrimarySecondaryChoice)
                {
                    AppWorkspace.Widgets.ColorsForm.ToggleWhichUserColor();
                    e.Handled = true;
                }
                else if (e.KeyChar == decPenSizeShortcut)
                {
                    AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(-1.0f);
                    e.Handled = true;
                }
                else 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(this.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)
                            {
                                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))
                        {
                            if (!this.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 (this.mouseDown == 0 && !Selection.IsEmpty)
                                {
                                    e.Handled = true;
                                    DocumentWorkspace.ExecuteFunction(new DeselectFunction());
                                }

                                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)) 
            {
                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;
                
                int offset = (int)(Math.Ceiling(DocumentWorkspace.ScaleFactor.Ratio) * (double)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)));

                PointF stylusLocF = new PointF((float)Cursor.Position.X - (float)location.X, (float)Cursor.Position.Y - (float)location.Y);
                Point 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);

            if (vis == Document.Bounds)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        private void KeyUp(KeyEventArgs e)
        {
            if (this.panMode)
            {
                this.panMode = false;
                this.panTracking = false;
                this.trackingNub.Visible = false;
                this.Cursor = this.panOldCursor;
                this.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)
            {
                if (!keysThatAreDown.Contains(e.KeyData))
                {
                    keysThatAreDown.Add(e.KeyData, new KeyTimeInfo());
                }

                if (!this.IsMouseDown && 
                    !this.panMode && 
                    e.KeyCode == Keys.Space)
                {
                    this.panMode = true;
                    this.panOldCursor = this.Cursor;

                    if (CanPan())
                    {
                        this.Cursor = this.handCursor;
                    }
                    else
                    {
                        this.Cursor = this.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 (object.ReferenceEquals(Form.ActiveForm, DocumentWorkspace.FindForm()));
            }
        }

        /// <summary>
        /// This method is called many times per second, called by the DocumentWorkspace.
        /// </summary>
        protected virtual void OnPulse()
        {
            if (this.panTracking && this.lastButton == MouseButtons.Right)
            {
                Point position = this.lastMouseXY;
                RectangleF visibleRect = DocumentWorkspace.VisibleDocumentRectangleF;
                PointF visibleCenterPt = Utility.GetRectangleCenter(visibleRect);
                PointF delta = new PointF(position.X - visibleCenterPt.X, position.Y - visibleCenterPt.Y);
                PointF newScroll = DocumentWorkspace.DocumentScrollPositionF;

                this.trackingNub.Visible = true;

                if (delta.X != 0 || delta.Y != 0)
                {
                    newScroll.X += delta.X;
                    newScroll.Y += delta.Y;

                    ++this.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;
                    this.trackingNub.Visible = true;
                    this.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) 
            {
                PointF newScrollPosition = new PointF(lastScrollPosition.X + delta.X, lastScrollPosition.Y + delta.Y);
                DocumentWorkspace.DocumentScrollPositionF = newScrollPosition;
                Update();
                return true;
            }
            else
            {
                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 this.DocumentWorkspace.RendererList;
            }
        }

        protected void Update()
        {
            DocumentWorkspace.Update();
        }

        protected object GetStaticData()
        {
            return DocumentWorkspace.GetStaticToolData(this.GetType());
        }

        protected void SetStaticData(object data)
        {
            DocumentWorkspace.SetStaticToolData(this.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)
        {
            this.documentWorkspace = documentWorkspace;
            this.toolBarImage = toolBarImage;
            this.toolInfo = new ToolInfo(name, helpText, toolBarImage, hotKey, skipIfActiveOnHotKey, toolBarConfigItems, this.GetType());

            if (this.documentWorkspace != null)
            {
                this.documentWorkspace.UpdateStatusBarToToolHelpText(this);
            }
        }

        ~Tool()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            Debug.Assert(!this.active, "Tool is still active!");

            if (disposing)
            {
                if (this.saveRegion != null)
                {
                    this.saveRegion.Dispose();
                    this.saveRegion = null;
                }

                OnDisposed();
            }
        }

        public event EventHandler Disposed;
        private void OnDisposed()
        {
            if (Disposed != null)
            {
                Disposed(this, EventArgs.Empty);
            }
        }

        public Form AssociatedForm
        {
            get
            {
                return AppWorkspace.FindForm();
            }
        }
    }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.