File: MS\Internal\Ink\EraserBehavior.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
 
//
// Description:
// ErasingBehavior for Ink
// Features:
//
 
 
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Input;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Interop;
using System.Windows.Navigation;
using System.Windows.Media;
using Swi = System.Windows.Ink;
 
namespace MS.Internal.Ink
{
    /// <summary>
    /// Eraser Behavior
    /// </summary>
    internal sealed class EraserBehavior : StylusEditingBehavior
    {
        //-------------------------------------------------------------------------------
        //
        // Constructors
        //
        //-------------------------------------------------------------------------------
 
        #region Constructors
 
        internal EraserBehavior(EditingCoordinator editingCoordinator, InkCanvas inkCanvas)
            : base(editingCoordinator, inkCanvas)
        {
        }
 
        #endregion Constructors
 
        //-------------------------------------------------------------------------------
        //
        // Protected Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Protected Methods
 
        /// <summary>
        /// Overrides SwitchToMode as the following expectations
        /// 19. From EraseByPoint To InkAndGesture
        ///     After mode change ink is being collected. On StylusUp gesture event fires. If it’s not a gesture, StrokeCollected event fires.
        /// 20. From EraseByPoint To GestureOnly
        ///     After mode change ink is being collected. On StylusUp gesture event fires. Stroke gets removed on StylusUp even if it’s not a gesture.
        /// 21. From EraseByPoint To Ink
        ///     Ink collection starts when changing the mode.
        /// 22. From EraseByPoint To EraseByStroke
        ///     After mode change StrokeErasing is performed.
        /// 23. From EraseByPoint To Select
        ///     Lasso is drawn for all packets between StylusDown and StylusUp. Strokes/elements within the lasso will be selected.
        /// 24. From EraseByPoint To None
        ///     No erasing is performed after mode change.
        /// 25. From EraseByStroke To InkAndGesture
        ///     After mode change ink is being collected. On StylusUp gesture event fires. If it’s not a gesture, StrokeCollected event fires.
        /// 26. From EraseByStroke To GestureOnly
        ///     After mode change ink is being collected. On StylusUp gesture event fires. Stroke gets removed on StylusUp even if it’s not a gesture.
        /// 27. From EraseByStroke To EraseByPoint
        ///     After mode change PointErasing is performed.
        /// 28. From EraseByStroke To Ink
        ///     Ink collection starts when changing the mode.
        /// 29. From EraseByStroke To Select
        ///     Lasso is drawn for all packets between StylusDown and StylusUp. Strokes/elements within the lasso will be selected
        /// 30. From EraseByStroke To None
        ///     No erasing is performed after mode change.
        /// </summary>
        /// <param name="mode"></param>
        protected override void OnSwitchToMode(InkCanvasEditingMode mode)
        {
            Debug.Assert(EditingCoordinator.IsInMidStroke, "SwitchToMode should only be called in a mid-stroke");
 
            switch ( mode )
            {
                case InkCanvasEditingMode.Ink:
                case InkCanvasEditingMode.InkAndGesture:
                case InkCanvasEditingMode.GestureOnly:
                    {
                        // Commit the current behavior
                        Commit(true);
 
                        // Change the mode. The dynamic renderer will be reset automatically.
                        EditingCoordinator.ChangeStylusEditingMode(this, mode);
                        break;
                    }
                case InkCanvasEditingMode.EraseByPoint:
                case InkCanvasEditingMode.EraseByStroke:
                    {
                        Debug.Assert(_cachedEraseMode != mode);
 
                        // Commit the current behavior
                        Commit(true);
 
                        // Change the mode
                        EditingCoordinator.ChangeStylusEditingMode(this, mode);
 
                        break;
                    }
                case InkCanvasEditingMode.Select:
                    {
                        // Make a copy of the current cached points.
                        StylusPointCollection cachedPoints = _stylusPoints != null ? 
                                                                _stylusPoints.Clone() : null;
 
                        // Commit the current behavior.
                        Commit(true);
 
                        // Change the Select mode
                        IStylusEditing newBehavior = EditingCoordinator.ChangeStylusEditingMode(this, mode);
 
                        if ( cachedPoints != null
                            // NOTICE-2006/04/27-WAYNEZEN,
                            // EditingCoordinator.ChangeStylusEditingMode raises external event.
                            // The user code could take any arbitrary action for instance calling InkCanvas.ReleaseMouseCapture()
                            // So there is no guarantee that we could receive the newBehavior.
                            && newBehavior != null )
                        {
                            // Now add the previous points to the lasso behavior
                            newBehavior.AddStylusPoints(cachedPoints, false/*userInitiated*/);
                        }
 
                        break;
                    }
                case InkCanvasEditingMode.None:
                    {
                        // Discard the collected ink.
                        Commit(true);
 
                        // Change to the None mode
                        EditingCoordinator.ChangeStylusEditingMode(this, mode);
                        break;
                    }
                default:
                    Debug.Assert(false, "Unknown InkCanvasEditingMode!");
                    break;
            }
        }
 
        protected override void OnActivate()
        {
            base.OnActivate();
            InkCanvasEditingMode newEraseMode = EditingCoordinator.ActiveEditingMode;
            Debug.Assert(newEraseMode == InkCanvasEditingMode.EraseByPoint
                            || newEraseMode == InkCanvasEditingMode.EraseByStroke);
 
            // Check whether we have to update cursor.
            if ( _cachedEraseMode != newEraseMode )
            {
                // EraseMode is changed
                _cachedEraseMode = newEraseMode;
                EditingCoordinator.InvalidateBehaviorCursor(this);
            }
            else if ( newEraseMode == InkCanvasEditingMode.EraseByPoint )
            {
                // Invalidate the PointEraser if we don't have the cache yet.
                bool isPointEraserCursorValid = _cachedStylusShape != null;
 
                // 
                // If the cached EraserShape is different from the current EraserShape, we shoud just reset the cache.
                // The new cursor will be generated when it's needed later.
                if ( isPointEraserCursorValid 
                    && ( _cachedStylusShape.Width != InkCanvas.EraserShape.Width
                        || _cachedStylusShape.Height != InkCanvas.EraserShape.Height
                        || _cachedStylusShape.Rotation != InkCanvas.EraserShape.Rotation
                        || _cachedStylusShape.GetType() != InkCanvas.EraserShape.GetType()) )
                {
                    Debug.Assert(_cachedPointEraserCursor != null, "_cachedPointEraserCursor shouldn't be null.");
                    ResetCachedPointEraserCursor();
                    isPointEraserCursorValid = false;
                }
 
                if ( !isPointEraserCursorValid )
                {
                    // EraserShape is changed when the new mode is EraseByPoint
                    EditingCoordinator.InvalidateBehaviorCursor(this);
                }
            }
        }
 
        /// <summary>
        /// StylusInputBegin
        /// </summary> 
        /// <param name="stylusPoints">stylusPoints</param>
        /// <param name="userInitiated">true if the source eventArgs.UserInitiated flag was set to true</param>
        protected override void StylusInputBegin(StylusPointCollection stylusPoints, bool userInitiated)
        {
            //
            // get a disposable dynamic hit-tester and add event handler
            //
            _incrementalStrokeHitTester =
                                this.InkCanvas.Strokes.GetIncrementalStrokeHitTester(this.InkCanvas.EraserShape);
 
 
            if ( InkCanvasEditingMode.EraseByPoint == _cachedEraseMode )
            {
                _incrementalStrokeHitTester.StrokeHit += new StrokeHitEventHandler(OnPointEraseResultChanged);
            }
            else
            {
                //we're in stroke hit test mode
                _incrementalStrokeHitTester.StrokeHit += new StrokeHitEventHandler(OnStrokeEraseResultChanged);
            }
 
            _stylusPoints = new StylusPointCollection(stylusPoints.Description, 100);
            _stylusPoints.Add(stylusPoints);
 
            //
            // start erasing
            //
            _incrementalStrokeHitTester.AddPoints(stylusPoints);
 
            // 
            // Since InkCanvas will ignore the animated tranforms when it receives the property changes.
            // So we should update our cursor when the stylus is down if there are animated transforms applied to InkCanvas.
            if ( InkCanvasEditingMode.EraseByPoint == _cachedEraseMode )
            {
                // Call InvalidateBehaviorCursor at the end of the routine. The method will cause an external event fired.
                // So it should be invoked after we set up our states.
                EditingCoordinator.InvalidateBehaviorCursor(this);
            }
        }
 
        /// <summary>
        /// StylusInputContinue
        /// </summary>
        /// <param name="stylusPoints">stylusPoints</param>
        /// <param name="userInitiated">true if the source eventArgs.UserInitiated flag was set to true</param>
        protected override void StylusInputContinue(StylusPointCollection stylusPoints, bool userInitiated)
        {
            _stylusPoints.Add(stylusPoints);
 
            _incrementalStrokeHitTester.AddPoints(stylusPoints);
        }
 
        /// <summary>
        /// StylusInputEnd
        /// </summary>
        /// <param name="commit">commit</param>
        protected override void StylusInputEnd(bool commit)
        {
            if ( InkCanvasEditingMode.EraseByPoint == _cachedEraseMode )
            {
                _incrementalStrokeHitTester.StrokeHit-= new StrokeHitEventHandler(OnPointEraseResultChanged);
            }
            else
            {
                //we're in stroke hit test mode
                _incrementalStrokeHitTester.StrokeHit -= new StrokeHitEventHandler(OnStrokeEraseResultChanged);
            }
 
            _stylusPoints = null;
 
            // 
            // Don't forget ending the current incremental hit testing.
            _incrementalStrokeHitTester.EndHitTesting();
            _incrementalStrokeHitTester = null;
        }
 
        /// <summary>
        /// Get eraser cursor.
        /// </summary>
        /// <returns></returns>
        protected override Cursor GetCurrentCursor()
        {
            if ( InkCanvasEditingMode.EraseByPoint == _cachedEraseMode )
            {
                if ( _cachedPointEraserCursor == null )
                {
                    _cachedStylusShape = InkCanvas.EraserShape;
 
                    // 
                    // The eraser cursor should respect the InkCanvas' Transform properties as the pen tip.
                    Matrix xf = GetElementTransformMatrix();
                    if ( !xf.IsIdentity )
                    {
                        // Zero the offsets if the element's transform in invertable. 
                        // Otherwise fallback the matrix to the identity.
                        if ( xf.HasInverse )
                        {
                            xf.OffsetX = 0;
                            xf.OffsetY = 0;
                        }
                        else
                        {
                            xf = Matrix.Identity;
                        }
                    }
                    DpiScale dpi = this.InkCanvas.GetDpi();
                    _cachedPointEraserCursor = PenCursorManager.GetPointEraserCursor(_cachedStylusShape, xf, dpi.DpiScaleX, dpi.DpiScaleY);
                }
 
                return _cachedPointEraserCursor;
            }
            else
            {
                return PenCursorManager.GetStrokeEraserCursor();
            }
        }
 
        /// <summary>
        /// ApplyTransformToCursor
        /// </summary>
        protected override void OnTransformChanged()
        {
            // Reset the cached point eraser cursor.
            ResetCachedPointEraserCursor();
        }
 
        #endregion Protected Methods
 
        //-------------------------------------------------------------------------------
        //
        // Private Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Private Methods
 
        /// <summary>
        /// Reset the cached point eraser cursor.
        /// </summary>
        private void ResetCachedPointEraserCursor()
        {
            _cachedPointEraserCursor = null;
            _cachedStylusShape = null;
        }
 
        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnStrokeEraseResultChanged(object sender, StrokeHitEventArgs e)
        {
            Debug.Assert(null != e.HitStroke);
 
            bool fSucceeded = false;
 
            // The below code calls out StrokeErasing or StrokeErased event.
            // The out-side code could throw exception. 
            // We use try/finally block to protect our status.
            try
            {
                InkCanvasStrokeErasingEventArgs args = new InkCanvasStrokeErasingEventArgs(e.HitStroke);
                this.InkCanvas.RaiseStrokeErasing(args);
 
                if ( !args.Cancel )
                {
                    // Erase only if the event wasn't cancelled
                    InkCanvas.Strokes.Remove(e.HitStroke);
                    this.InkCanvas.RaiseInkErased();
                }
 
                fSucceeded = true;
            }
            finally
            {
                if ( !fSucceeded )
                {
                    // Abort the editing.
                    Commit(false);
                }
            }
        }
 
        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnPointEraseResultChanged(object sender, StrokeHitEventArgs e)
        {
            Debug.Assert(null != e.HitStroke, "e.HitStroke cannot be null");
 
            bool fSucceeded = false;
 
            // The below code might call out StrokeErasing or StrokeErased event.
            // The out-side code could throw exception. 
            // We use try/finally block to protect our status.
            try
            {
                InkCanvasStrokeErasingEventArgs args = new InkCanvasStrokeErasingEventArgs(e.HitStroke);
                this.InkCanvas.RaiseStrokeErasing(args);
 
                if ( !args.Cancel )
                {
                    // Erase only if the event wasn't cancelled
                    StrokeCollection eraseResult = e.GetPointEraseResults();
                    Debug.Assert(eraseResult != null, "eraseResult cannot be null");
 
                    StrokeCollection strokesToReplace = new StrokeCollection();
                    strokesToReplace.Add(e.HitStroke);
 
                    try
                    {
                        // replace or remove the stroke
                        if (eraseResult.Count > 0)
                        {
                            this.InkCanvas.Strokes.Replace(strokesToReplace, eraseResult);
                        }
                        else
                        {
                            this.InkCanvas.Strokes.Remove(strokesToReplace);
                        }
                    }
                    catch (ArgumentException ex)
                    {
                        //this can happen if someone sits in an event handler 
                        //for StrokeErasing and removes the stroke.  
                        //this to harden against failure here.
                        if (!ex.Data.Contains("System.Windows.Ink.StrokeCollection"))
                        {
                            //System.Windows.Ink.StrokeCollection didn't throw this, 
                            //we need to just throw the original exception
                            throw;
                        }
                    }
 
 
                    //raise ink erased
                    this.InkCanvas.RaiseInkErased();
                }
 
                fSucceeded = true;
            }
            finally
            {
                if ( !fSucceeded )
                {
                    // Abort the editing.
                    Commit(false);
                }
            }
        }
 
        #endregion Private Methods
 
        //-------------------------------------------------------------------------------
        //
        // Private Fields
        //
        //-------------------------------------------------------------------------------
 
        #region Private Fields
 
        private InkCanvasEditingMode            _cachedEraseMode;
        private IncrementalStrokeHitTester      _incrementalStrokeHitTester;
        private Cursor                          _cachedPointEraserCursor;
        private StylusShape                     _cachedStylusShape;
        private StylusPointCollection           _stylusPoints = null;
 
        #endregion Private Fields
    }
}