IncrementalHitTester.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Core / CSharp / System / Windows / Ink / IncrementalHitTester.cs / 1 / IncrementalHitTester.cs

                            //------------------------------------------------------------------------ 
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
//----------------------------------------------------------------------- 
//#define TRACE
 
using System; 
using System.Windows;
using System.Windows.Input; 
using System.Windows.Ink;
using System.Windows.Media;
using System.Collections.Generic;
using System.Collections.ObjectModel; 
using MS.Internal.Ink;
using MS.Utility; 
using MS.Internal; 
using System.Diagnostics;
 
using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID;

namespace System.Windows.Ink 
{
    #region IncrementalHitTester Abstract Base Class 
 
    /// 
    /// This class serves as both the base class and public interface for 
    /// incremental hit-testing implementaions.
    /// 
    public abstract class IncrementalHitTester
    { 
        #region Public API
 
        ///  
        /// Adds a point representing an incremental move of the hit-testing tool
        ///  
        /// a point that represents an incremental move of the hitting tool
        public void AddPoint(Point point)
        {
            AddPoints(new Point[1] { point }); 
        }
 
        ///  
        /// Adds an array of points representing an incremental move of the hit-testing tool
        ///  
        /// points representing an incremental move of the hitting tool
        public void AddPoints(IEnumerable points)
        {
            if (points == null) 
            {
                throw new System.ArgumentNullException("points"); 
            } 

            if (IEnumerablePointHelper.GetCount(points) == 0) 
            {
                throw new System.ArgumentException(SR.Get(SRID.EmptyArrayNotAllowedAsArgument), "points");
            }
 
            if (false == _fValid)
            { 
                throw new System.InvalidOperationException(SR.Get(SRID.EndHitTestingCalled)); 
            }
 
            System.Diagnostics.Debug.Assert(_strokes != null);

            AddPointsCore(points);
        } 

        ///  
        /// Adds a StylusPacket representing an incremental move of the hit-testing tool 
        /// 
        /// stylusPoints 
        public void AddPoints(StylusPointCollection stylusPoints)
        {
            if (stylusPoints == null)
            { 
                throw new System.ArgumentNullException("stylusPoints");
            } 
 
            if (stylusPoints.Count == 0)
            { 
                throw new System.ArgumentException(SR.Get(SRID.EmptyArrayNotAllowedAsArgument), "stylusPoints");
            }

            if (false == _fValid) 
            {
                throw new System.InvalidOperationException(SR.Get(SRID.EndHitTestingCalled)); 
            } 

            System.Diagnostics.Debug.Assert(_strokes != null); 

            Point[] points = new Point[stylusPoints.Count];
            for (int x = 0; x < stylusPoints.Count; x++)
            { 
                points[x] = (Point)stylusPoints[x];
            } 
 
            AddPointsCore(points);
        } 


        /// 
        /// Release as many resources as possible for this enumerator 
        /// 
        public void EndHitTesting() 
        { 
            if (_strokes != null)
            { 
                // Detach the event handler
                _strokes.StrokesChangedInternal -= new StrokeCollectionChangedEventHandler(OnStrokesChanged);
                _strokes = null;
                int count = _strokeInfos.Count; 
                for ( int i = 0; i < count; i++)
                { 
#if TRACE 
                    if (_strokeInfos[i].HitWeight != _strokeInfos[i].TotalWeight)
                    { 
                        System.Diagnostics.Debug.WriteLine(String.Format("IsHit: {0}; TotalWeight: {1}; HitWeight: {2}, Percent hit: {3}",
                                                            _strokeInfos[i].IsHit, _strokeInfos[i].TotalWeight, _strokeInfos[i].HitWeight, 100f * _strokeInfos[i].HitWeight / _strokeInfos[i].TotalWeight));
                    }
#endif 
                    _strokeInfos[i].Detach();
                } 
                _strokeInfos = null; 
            }
            _fValid = false; 
        }

        /// 
        /// Accessor to see if the Hit Tester is still valid 
        /// 
        public bool IsValid { get { return _fValid; } } 
        #endregion 

        #region Internal 

        /// 
        /// C-tor.
        ///  
        /// strokes to hit-test
        internal IncrementalHitTester(StrokeCollection strokes) 
        { 
            System.Diagnostics.Debug.Assert(strokes != null);
 
            // Create a StrokeInfo object for each stroke.
            _strokeInfos = new List(strokes.Count);
            for (int x = 0; x < strokes.Count; x++)
            { 
                Stroke stroke = strokes[x];
                _strokeInfos.Add(new StrokeInfo(stroke)); 
            } 

            _strokes = strokes; 

            // Attach an event handler to the strokes' changed event
            _strokes.StrokesChangedInternal += new StrokeCollectionChangedEventHandler(OnStrokesChanged);
        } 

        ///  
        /// The implementation behind AddPoint/AddPoints. 
        /// Derived classes are supposed to override this method.
        ///  
        protected abstract void AddPointsCore(IEnumerable points);


        ///  
        /// Accessor to the internal collection of StrokeInfo objects
        ///  
        internal List StrokeInfos { get { return _strokeInfos; } } 

        #endregion 

        #region Private

        ///  
        /// Event handler associated with the stroke collection.
        ///  
        /// Stroke collection that was modified 
        /// Modification that occurred
        ///  
        /// Update our _strokeInfos cache.  We get notified on StrokeCollection.StrokesChangedInternal which
        /// is raised first so we can assume we're the first delegate in the call chain
        /// 
        private void OnStrokesChanged(object sender, StrokeCollectionChangedEventArgs args) 
        {
            System.Diagnostics.Debug.Assert((_strokes != null) && (_strokeInfos != null) && (_strokes == sender)); 
 
            StrokeCollection added = args.Added;
            StrokeCollection removed = args.Removed; 

            if (added.Count > 0)
            {
                int firstIndex = _strokes.IndexOf(added[0]); 
                for (int i = 0; i < added.Count; i++)
                { 
                    _strokeInfos.Insert(firstIndex, new StrokeInfo(added[i])); 
                    firstIndex++;
                } 
            }

            if (removed.Count > 0)
            { 
                StrokeCollection localRemoved = new StrokeCollection(removed);
                //we have to assume that removed strokes can be in any order in _strokes 
                for (int i = 0; i < _strokeInfos.Count && localRemoved.Count > 0; ) 
                {
                    bool found = false; 
                    for (int j = 0; j < localRemoved.Count; j++)
                    {
                        if (localRemoved[j] == _strokeInfos[i].Stroke)
                        { 
                            _strokeInfos.RemoveAt(i);
                            localRemoved.RemoveAt(j); 
 
                            found = true;
                        } 
                    }
                    //we didn't find a removed stroke at index i in _strokeInfos, so advance i
                    if (!found)
                    { 
                        i++;
                    } 
                } 
            }
 
            //validate our cache
            if (_strokes.Count != _strokeInfos.Count)
            {
                Debug.Assert(false, "Benign assert.  IncrementalHitTester's _strokeInfos cache is out of [....], rebuilding."); 
                RebuildStrokeInfoCache();
                return; 
            } 
            for (int i = 0; i < _strokeInfos.Count; i++)
            { 
                if (_strokeInfos[i].Stroke != _strokes[i])
                {
                    Debug.Assert(false, "Benign assert.  IncrementalHitTester's _strokeInfos cache is out of [....], rebuilding.");
                    RebuildStrokeInfoCache(); 
                    return;
                } 
            } 
        }
 
        /// 
        /// IHT's can get into a state where their StrokeInfo cache is too
        /// out of [....] with the StrokeCollection to incrementally update it.
        /// When we detect this has happened, we just rebuild the entire cache. 
        /// 
        private void RebuildStrokeInfoCache() 
        { 
            List newStrokeInfos = new List(_strokes.Count);
            foreach (Stroke stroke in _strokes) 
            {
                bool found = false;
                for (int x = 0; x < _strokeInfos.Count; x++)
                { 
                    StrokeInfo strokeInfo = _strokeInfos[x];
                    if (strokeInfo != null && stroke == strokeInfo.Stroke) 
                    { 
                        newStrokeInfos.Add(strokeInfo);
                        //just set to null instead of removing and shifting 
                        //we're about to GC _strokeInfos
                        _strokeInfos[x] = null;
                        found = true;
                        break; 
                    }
                } 
                if (!found) 
                {
                    //we didn't find an existing strokeInfo 
                    newStrokeInfos.Add(new StrokeInfo(stroke));
                }
            }
 
            //detach the remaining strokeInfo's from their strokes
            for (int x = 0; x < _strokeInfos.Count; x++) 
            { 
                StrokeInfo strokeInfo = _strokeInfos[x];
 
                if (strokeInfo != null)
                {
                    strokeInfo.Detach();
                } 
            }
 
            _strokeInfos = newStrokeInfos; 

#if DEBUG 
            Debug.Assert(_strokeInfos.Count == _strokes.Count);
            for (int x = 0; x < _strokeInfos.Count; x++)
            {
                Debug.Assert(_strokeInfos[x].Stroke == _strokes[x]); 
            }
#endif 
        } 

        #endregion 

        #region Fields

        ///  Reference to the stroke collection under test  
        private StrokeCollection _strokes;
        ///  A collection of helper objects mapped to the stroke colection 
        private List _strokeInfos; 

        private bool _fValid = true; 

        #endregion
    }
 
    #endregion
 
    #region IncrementalLassoHitTester 

    ///  
    /// IncrementalHitTester implementation for hit-testing with lasso
    /// 
    public class IncrementalLassoHitTester : IncrementalHitTester
    { 
        #region public APIs
        ///  
        /// Event 
        /// 
        public event LassoSelectionChangedEventHandler SelectionChanged; 
        #endregion
        #region C-tor and the overrides

        ///  
        /// C-tor.
        ///  
        /// strokes to hit-test 
        /// a hit-testing parameter that defines the minimal
        /// percent of nodes of a stroke to be inside the lasso to consider the stroke hit 
        internal IncrementalLassoHitTester(StrokeCollection strokes, int percentageWithinLasso)
            : base(strokes)
        {
            System.Diagnostics.Debug.Assert((percentageWithinLasso >= 0) && (percentageWithinLasso <= 100)); 
            _lasso = new SingleLoopLasso();
            _percentIntersect = percentageWithinLasso; 
        } 

        ///  
        /// The implementation behind the public methods AddPoint/AddPoints
        /// 
        /// new points to add to the lasso
        protected override void AddPointsCore(IEnumerable points) 
        {
            System.Diagnostics.Debug.Assert((points != null) && (IEnumerablePointHelper.GetCount(points)!= 0)); 
 
            // Add the new points to the lasso
            int lastPointIndex = (0 != _lasso.PointCount) ? (_lasso.PointCount - 1) : 0; 
            _lasso.AddPoints(points);

            // Do nothing if there's not enough points, or there's nobody listening
            // The points may be filtered out, so if all the points are filtered out, (lastPointIndex == (_lasso.PointCount - 1). 
            // For this case, check if the incremental lasso is disabled (i.e., points modified).
            if ((_lasso.IsEmpty) || (lastPointIndex == (_lasso.PointCount - 1) && false == _lasso.IsIncrementalLassoDirty) 
                || (SelectionChanged == null)) 
            {
                return; 
            }

            // Variables for possible HitChanged events to fire
            StrokeCollection strokesHit = null; 
            StrokeCollection strokesUnhit = null;
 
            // Create a lasso that represents the current increment 
            Lasso lassoUpdate = new Lasso();
 
            if (false == _lasso.IsIncrementalLassoDirty)
            {
                if (0 < lastPointIndex)
                { 
                    lassoUpdate.AddPoint(_lasso[0]);
                } 
 
                // Only the points the have been successfully added to _lasso will be added to
                // lassoUpdate. 
                for (; lastPointIndex < _lasso.PointCount; lastPointIndex++)
                {
                    lassoUpdate.AddPoint(_lasso[lastPointIndex]);
                } 
            }
 
            // Enumerate through the strokes and update their hit-test results 
            foreach (StrokeInfo strokeInfo in this.StrokeInfos)
            { 
                Lasso lasso;
                if (true == strokeInfo.IsDirty || true == _lasso.IsIncrementalLassoDirty)
                {
                    // If this is the first time this stroke gets hit-tested with this lasso, 
                    // or if the stroke (or its DAs) has changed since the last hit-testing,
                    // or if the lasso points have been modified, 
                    // then (re)hit-test this stroke against the entire lasso. 
                    lasso = _lasso;
                    strokeInfo.IsDirty = false; 
                }
                else
                {
                    // Otherwise, hit-test it against the lasso increment first and then only 
                    // those ink points that are in that small lasso need to be hit-tested
                    // against the big (entire) lasso. 
                    // This is supposed to be a significant piece of optimization, since 
                    // lasso increments are usually very small, they are defined by just
                    // a few points and they don't capture and/or release too many ink nodes. 
                    lasso = lassoUpdate;
                }

                // Skip those stroke which bounding box doesn't even intersects with the lasso bounds 
                double hitWeightChange = 0f;
                if (lasso.Bounds.IntersectsWith(strokeInfo.StrokeBounds)) 
                { 
                    // Get the stroke node points for the hit-testing.
                    StylusPointCollection stylusPoints = strokeInfo.StylusPoints; 

                    // Find out if the lasso update has changed the hit count of the stroke.
                    for (int i = 0; i < stylusPoints.Count; i++)
                    { 
                        // Consider only the points that become captured/released with this particular update
                        if (true == lasso.Contains((Point)stylusPoints[i])) 
                        { 
                            double weight = strokeInfo.GetPointWeight(i);
 
                            if (lasso == _lasso || _lasso.Contains((Point)stylusPoints[i]))
                            {
                                hitWeightChange += weight;
                            } 
                            else
                            { 
                                hitWeightChange -= weight; 
                            }
                        } 
                    }
                }

                // Update the stroke hit weight and check whether it has crossed the margin 
                // in either direction since the last update.
                if ((hitWeightChange != 0) || (lasso == _lasso)) 
                { 
                    strokeInfo.HitWeight = (lasso == _lasso) ? hitWeightChange : (strokeInfo.HitWeight + hitWeightChange);
                    bool isHit = DoubleUtil.GreaterThanOrClose(strokeInfo.HitWeight, strokeInfo.TotalWeight * _percentIntersect / 100f - Stroke.PercentageTolerance); 

                    if (strokeInfo.IsHit != isHit)
                    {
                        strokeInfo.IsHit = isHit; 
                        if (isHit)
                        { 
                            // The hit count became greater than the margin percentage, the stroke 
                            // needs to be reported for selection
                            if (null == strokesHit) 
                            {
                                strokesHit = new StrokeCollection();
                            }
                            strokesHit.Add(strokeInfo.Stroke); 
#if TRACE
                            System.Diagnostics.Debug.WriteLine(String.Format("Added a stroke to the hit list. Current hit count = {0}", strokesHit.Count)); 
#endif 
                        }
                        else 
                        {
                            // The hit count just became less than the margin percentage,
                            // the stroke needs to be reported for de-selection
                            if (null == strokesUnhit) 
                            {
                                strokesUnhit = new StrokeCollection(); 
                            } 
                            strokesUnhit.Add(strokeInfo.Stroke);
#if TRACE 
                            System.Diagnostics.Debug.WriteLine(String.Format("Added a stroke to the unhit list"));
#endif
                        }
                    } 
                }
            } 
 
            _lasso.IsIncrementalLassoDirty = false;
            // Raise StrokesHitChanged event if any strokes has changed thier 
            // hit status and there're the event subscribers.
            if ((null != strokesHit) || (null != strokesUnhit))
            {
                OnSelectionChanged(new LassoSelectionChangedEventArgs (strokesHit, strokesUnhit)); 
            }
        } 
 
        /// 
        /// SelectionChanged event raiser 
        /// 
        /// 
        protected void OnSelectionChanged(LassoSelectionChangedEventArgs  eventArgs)
        { 
            System.Diagnostics.Debug.Assert(eventArgs != null);
            if (SelectionChanged != null) 
            { 
                SelectionChanged(this, eventArgs);
            } 
        }


        #endregion 
        #region Fields
 
        private Lasso   _lasso; 
        private int     _percentIntersect;
 
        #endregion
    }

    #endregion 

    #region IncrementalStrokeHitTester 
 
    /// 
    /// IncrementalHitTester implementation for hit-testing with a shape, PointErasing . 
    /// 
    public class IncrementalStrokeHitTester  : IncrementalHitTester
    {
        ///  
        ///
        ///  
        public event StrokeHitEventHandler StrokeHit; 

        #region C-tor and the overrides 

        /// 
        /// C-tor
        ///  
        /// strokes to hit-test for erasing
        /// erasing shape 
        internal IncrementalStrokeHitTester(StrokeCollection strokes, StylusShape eraserShape) 
            : base(strokes)
        { 
            System.Diagnostics.Debug.Assert(eraserShape != null);

            // Create an ErasingStroke objects that implements the actual hit-testing
            _erasingStroke = new ErasingStroke(eraserShape); 
        }
 
        ///  
        /// The implementation behind the public methods AddPoint/AddPoints
        ///  
        /// a set of points representing the last increment
        /// in the moving of the erasing shape
        protected override void AddPointsCore(IEnumerable points)
        { 
            System.Diagnostics.Debug.Assert((points != null) && (IEnumerablePointHelper.GetCount(points) != 0));
            System.Diagnostics.Debug.Assert(_erasingStroke != null); 
 
            // Move the shape through the new points and build the contour of the move.
            _erasingStroke.MoveTo(points); 
            Rect erasingBounds = _erasingStroke.Bounds;
            if (erasingBounds.IsEmpty)
            {
                return; 
            }
 
            List strokeHitEventArgCollection = null; 
            // Do nothing if there's nobody listening to the events
            if (StrokeHit != null) 
            {
                List eraseAt = new List();

                // Test stroke by stroke and collect the results. 
                for (int x = 0; x < this.StrokeInfos.Count; x++)
                { 
                    StrokeInfo strokeInfo = this.StrokeInfos[x]; 

                    // Skip the stroke if its bounding box doesn't intersect with the one of the hitting shape. 
                    if ((erasingBounds.IntersectsWith(strokeInfo.StrokeBounds) == false) ||
                        (_erasingStroke.EraseTest(StrokeNodeIterator.GetIterator(strokeInfo.Stroke, strokeInfo.Stroke.DrawingAttributes), eraseAt) == false))
                    {
                        continue; 
                    }
 
                    // Create an event args to raise after done with hit-testing 
                    // We don't fire these events right away because user is expected to
                    // modify the stroke collection in her event handler, and that would 
                    // invalidate this foreach loop.
                    if (strokeHitEventArgCollection == null)
                    {
                        strokeHitEventArgCollection = new List(); 
                    }
                    strokeHitEventArgCollection.Add(new StrokeHitEventArgs(strokeInfo.Stroke, eraseAt.ToArray())); 
                    // We must clear eraseAt or it will contain invalid results for the next strokes 
                    eraseAt.Clear();
                } 
            }

            // Raise StrokeHit event if needed.
            if (strokeHitEventArgCollection != null) 
            {
                System.Diagnostics.Debug.Assert(strokeHitEventArgCollection.Count != 0); 
                for (int x = 0; x < strokeHitEventArgCollection.Count; x++) 
                {
                    StrokeHitEventArgs eventArgs = strokeHitEventArgCollection[x]; 

                    System.Diagnostics.Debug.Assert(eventArgs.HitStroke != null);
                    OnStrokeHit(eventArgs);
                } 
            }
        } 
 
        /// 
        /// Event raiser for StrokeHit 
        /// 
        protected void OnStrokeHit(StrokeHitEventArgs eventArgs)
        {
            System.Diagnostics.Debug.Assert(eventArgs != null); 
            if (StrokeHit != null)
            { 
                StrokeHit(this, eventArgs); 
            }
        } 

        #endregion

        #region Fields 

        private ErasingStroke _erasingStroke; 
 
        #endregion
    } 


    #endregion
 
    #region EventArgs and delegates
 
    ///  
    /// Declaration for LassoSelectionChanged event handler. Used in lasso-selection
    ///  
    public delegate void LassoSelectionChangedEventHandler(object sender, LassoSelectionChangedEventArgs e);


    ///  
    /// Declaration for StrokeHit event handler. Used in point-erasing
    ///  
    public delegate void StrokeHitEventHandler(object sender, StrokeHitEventArgs e); 

 
    /// 
    /// Event arguments for LassoSelectionChanged event
    /// 
    public class LassoSelectionChangedEventArgs  : EventArgs 
    {
        internal LassoSelectionChangedEventArgs(StrokeCollection selectedStrokes, StrokeCollection deselectedStrokes) 
        { 
            _selectedStrokes = selectedStrokes;
            _deselectedStrokes = deselectedStrokes; 
        }

        /// 
        /// Collection of strokes which were hit with the last increment 
        /// 
        public StrokeCollection SelectedStrokes 
        { 
            get
            { 
                if (_selectedStrokes != null)
                {
                    StrokeCollection sc = new StrokeCollection();
                    sc.Add(_selectedStrokes); 
                    return sc;
                } 
                else 
                {
                    return  new StrokeCollection(); 
                }
            }
        }
 
        /// 
        /// Collection of strokes which were unhit with the last increment 
        ///  
        public StrokeCollection DeselectedStrokes
        { 
            get
            {
                if (_deselectedStrokes != null)
                { 
                    StrokeCollection sc = new StrokeCollection();
                    sc.Add(_deselectedStrokes); 
                    return sc; 
                }
                else 
                {
                    return new StrokeCollection();
                }
            } 
        }
 
        private StrokeCollection _selectedStrokes; 
        private StrokeCollection _deselectedStrokes;
    } 

    /// 
    /// Event arguments for StrokeHit event
    ///  
    public class StrokeHitEventArgs : EventArgs
    { 
        ///  
        /// C-tor
        ///  
        internal StrokeHitEventArgs(Stroke stroke, StrokeIntersection[] hitFragments)
        {
            System.Diagnostics.Debug.Assert(stroke != null && hitFragments != null && hitFragments.Length > 0);
            _stroke = stroke; 
            _hitFragments = hitFragments;
        } 
 
        /// Stroke that was hit
        public Stroke HitStroke { get { return _stroke; } } 

        /// 
        ///
        ///  
        /// 
        public StrokeCollection GetPointEraseResults() 
        { 
            return _stroke.Erase(_hitFragments);
        } 

        private Stroke                  _stroke;
        private StrokeIntersection[]    _hitFragments;
 
    }
 
 

    #endregion 
}

namespace MS.Internal.Ink
{ 
    #region StrokeInfo
 
    ///  
    /// A helper class associated with a stroke. Used for caching the stroke's
    /// bounding box, hit-testing results, and for keeping an eye on the stroke changes 
    /// 
    internal class StrokeInfo
    {
        #region API (used by incremental hit-testers) 

        ///  
        /// StrokeInfo 
        /// 
        internal StrokeInfo(Stroke stroke) 
        {
            System.Diagnostics.Debug.Assert(stroke != null);
            _stroke = stroke;
            _bounds = stroke.GetBounds(); 

            // Start listening to the stroke events 
            _stroke.DrawingAttributesChanged += new PropertyDataChangedEventHandler(OnStrokeDrawingAttributesChanged); 
            _stroke.StylusPointsReplaced += new StylusPointsReplacedEventHandler(OnStylusPointsReplaced);
            _stroke.StylusPoints.Changed += new EventHandler(OnStylusPointsChanged); 
            _stroke.DrawingAttributesReplaced += new DrawingAttributesReplacedEventHandler(OnDrawingAttributesReplaced);
        }

        /// The stroke object associated with this helper structure 
        internal Stroke Stroke { get { return _stroke; } }
 
        /// Pre-calculated bounds of the stroke  
        internal Rect StrokeBounds { get { return _bounds; } }
 
        /// Tells whether the stroke or its drawing attributes have been modified
        /// since the last use (hit-testing)
        internal bool IsDirty
        { 
            get { return _isDirty; }
            set { _isDirty = value; } 
        } 

        /// Tells whether the stroke was found (and reported) as hit  
        internal bool IsHit
        {
            get { return _isHit; }
            set { _isHit = value; } 
        }
 
        ///  
        /// Cache teh stroke points
        ///  
        internal StylusPointCollection StylusPoints
        {
            get
            { 
                if (_stylusPoints == null)
                { 
                    if (_stroke.DrawingAttributes.FitToCurve) 
                    {
                        _stylusPoints = _stroke.GetBezierStylusPoints(); 
                    }
                    else
                    {
                        _stylusPoints = _stroke.StylusPoints; 
                    }
                } 
                return _stylusPoints; 
            }
        } 

        /// 
        /// Holds the current hit-testing result for the stroke. Represents the length of
        /// the stroke "inside" and "hit" by the lasso 
        /// 
        internal double HitWeight 
        { 
            get { return _hitWeight; }
            set 
            {
                // it is ok to clamp this off, rounding error sends it over or under by a minimal amount.
                if (DoubleUtil.GreaterThan(value, TotalWeight))
                { 
                    _hitWeight = TotalWeight;
                } 
                else if (DoubleUtil.LessThan(value, 0f)) 
                {
                    _hitWeight = 0f; 
                }
                else
                {
                    _hitWeight = value; 
                }
            } 
        } 

        ///  
        /// Get the total weight of the stroke. For this implementation, it is the total length of the stroke.
        /// 
        /// 
        internal double TotalWeight 
        {
            get 
            { 
                if (!_totalWeightCached)
                { 
                    _totalWeight= 0;
                    for (int i = 0; i < StylusPoints.Count; i++)
                    {
                        _totalWeight += this.GetPointWeight(i); 
                    }
                    _totalWeightCached = true; 
                } 
                return _totalWeight;
            } 
        }

        /// 
        /// Calculate the weight of a point. 
        /// 
        internal double GetPointWeight(int index) 
        { 
            StylusPointCollection stylusPoints = this.StylusPoints;
            DrawingAttributes da = this.Stroke.DrawingAttributes; 
            System.Diagnostics.Debug.Assert(stylusPoints != null && index >= 0 && index < stylusPoints.Count);

            double weight = 0f;
            if (index == 0) 
            {
                weight += Math.Sqrt(da.Width*da.Width + da.Height*da.Height) / 2.0f; 
            } 
            else
            { 
                Vector spine = (Point)stylusPoints[index] - (Point)stylusPoints[index - 1];
                weight += Math.Sqrt(spine.LengthSquared) / 2.0f;
            }
 
            if (index == stylusPoints.Count - 1)
            { 
                weight += Math.Sqrt(da.Width*da.Width + da.Height*da.Height) / 2.0f; 
            }
            else 
            {
                Vector spine = (Point)stylusPoints[index + 1] - (Point)stylusPoints[index];
                weight += Math.Sqrt(spine.LengthSquared) / 2.0f;
            } 

            return weight; 
        } 
        /// 
        /// A kind of disposing method 
        /// 
        internal void Detach()
        {
            if (_stroke != null) 
            {
                // Detach the event handlers 
                _stroke.DrawingAttributesChanged -= new PropertyDataChangedEventHandler(OnStrokeDrawingAttributesChanged); 
                _stroke.StylusPointsReplaced -= new StylusPointsReplacedEventHandler(OnStylusPointsReplaced);
                _stroke.StylusPoints.Changed -= new EventHandler(OnStylusPointsChanged); 
                _stroke.DrawingAttributesReplaced -= new DrawingAttributesReplacedEventHandler(OnDrawingAttributesReplaced);

                _stroke = null;
            } 
        }
 
        #endregion 

        #region Stroke event handlers (Private) 

        /// Event handler for stroke data changed events
        private void OnStylusPointsChanged(object sender, EventArgs args)
        { 
            Invalidate();
        } 
 
        /// Event handler for stroke data changed events
        private void OnStylusPointsReplaced(object sender, StylusPointsReplacedEventArgs args) 
        {
            Invalidate();
        }
 
        /// 
        /// Event handler for stroke's drawing attributes changes. 
        ///  
        /// 
        ///  
        private void OnStrokeDrawingAttributesChanged(object sender, PropertyDataChangedEventArgs args)
        {
            // Only enforce rehittesting of the whole stroke when the DrawingAttribute change may affect hittesting
            if(DrawingAttributes.IsGeometricalDaGuid(args.PropertyGuid)) 
            {
                Invalidate(); 
            } 
        }
 
        private void OnDrawingAttributesReplaced(Object sender, DrawingAttributesReplacedEventArgs args)
        {
            // If the drawing attributes change involves Width, Height, StylusTipTransform, IgnorePressure, or FitToCurve,
            // we need to invalidate 
            if (false == DrawingAttributes.GeometricallyEqual(args.NewDrawingAttributes, args.PreviousDrawingAttributes))
            { 
                Invalidate(); 
            }
        } 

        /// Implementation for the event handlers above
        private void Invalidate()
        { 
            _totalWeightCached = false;
            _stylusPoints = null; 
            _hitWeight = 0; 

            // Let the hit-tester know that it should not use incremental hit-testing 
            _isDirty = true;

            // The Stroke.GetBounds may be overriden in the 3rd party code.
            // The out-side code could throw exception. If an exception is thrown, _bounds will keep the original value. 
            // Re-compute the stroke bounds
            _bounds = _stroke.GetBounds(); 
        } 
        #endregion
 
        #region Fields

        private Stroke                      _stroke;
        private Rect                        _bounds; 
        private double                      _hitWeight = 0f;
        private bool                        _isHit = false; 
        private bool                        _isDirty = true; 
        private StylusPointCollection       _stylusPoints;   // Cache the stroke rendering points
        private double                      _totalWeight = 0f; 
        private bool                        _totalWeightCached = false;
        #endregion
    }
 
    #endregion // StrokeInfo
} 
 
// The following code is for Stroke-Erasing scenario. Currently the IncrementalStrokeHitTester
// can be used for Stroke-erasing but the following class is faster. If in the future there's a 
// perf issue with Stroke-Erasing, consider adding the following code.
//#region Commented Code for IncrementalStrokeHitTester
//#region IncrementalStrokeHitTester
 
///// 
///// IncrementalHitTester implementation for hit-testing with a shape, StrokeErasing . 
/////  
//public class IncrementalStrokeHitTester : IncrementalHitTester
//{ 
//    /// 
//    /// event
//    /// 
//    public event StrokesHitEventHandler StrokesHit; 

//    #region C-tor and the overrides 
 
//    /// 
//    /// C-tor 
//    /// 
//    /// strokes to hit-test for erasing
//    /// erasing shape
//    internal IncrementalStrokeHitTester(StrokeCollection strokes, StylusShape eraserShape) 
//        : base(strokes)
//    { 
//        System.Diagnostics.Debug.Assert(eraserShape != null); 

//        // Create an ErasingStroke objects that implements the actual hit-testing 
//        _erasingStroke = new ErasingStroke(eraserShape);
//    }

//    ///  
//    ///
//    ///  
//    ///  
//    internal protected void OnStrokesHit(StrokesHitEventArgs eventArgs)
//    { 
//        if (StrokesHit != null)
//        {
//            StrokesHit(this, eventArgs);
//        } 
//    }
 
//    ///  
//    /// The implementation behind the public methods AddPoint/AddPoints
//    ///  
//    /// a set of points representing the last increment
//    /// in the moving of the erasing shape
//    internal protected override void AddPointsCore(Point[] points)
//    { 
//        System.Diagnostics.Debug.Assert((points != null) && (points.Length != 0));
//        System.Diagnostics.Debug.Assert(_erasingStroke != null); 
 
//        // Move the shape through the new points and build the contour of the move.
//        _erasingStroke.MoveTo(points); 
//        Rect erasingBounds = _erasingStroke.Bounds;
//        if (erasingBounds.IsEmpty)
//        {
//            return; 
//        }
 
//        StrokeCollection strokesHit = null; 
//        if (StrokesHit != null)
//        { 
//            // Test stroke by stroke and collect hits.
//            foreach (StrokeInfo strokeInfo in StrokeInfos)
//            {
//                // Skip strokes that have already been reported hit or which bounds 
//                // don't intersect with the bounds of the erasing stroke.
//                if ((strokeInfo.IsHit == false) && erasingBounds.IntersectsWith(strokeInfo.StrokeBounds) 
//                    && _erasingStroke.HitTest(StrokeNodeIterator.GetIterator(strokeInfo.Stroke, strokeInfo.Overrides))) 
//                {
//                    if (strokesHit == null) 
//                    {
//                        strokesHit = new StrokeCollection();
//                    }
//                    strokesHit.Add(strokeInfo.Stroke); 
//                    strokeInfo.IsHit = true;
//                } 
//            } 
//        }
 
//        // Raise StrokesHitChanged event if any strokes have been hit and there're listeners to the event.
//        if (strokesHit != null)
//        {
//            System.Diagnostics.Debug.Assert(strokesHit.Count != 0); 
//            OnStrokesHit(new StrokesHitEventArgs(strokesHit));
//        } 
//    } 

//    #endregion 

//    #region Fields

//    private ErasingStroke _erasingStroke; 

//    #endregion 
//} 

//#endregion 

///// 
///// Declaration for StrokesHit event handler. Used in stroke-erasing
/////  
//public delegate void StrokesHitEventHandler(object sender, StrokesHitEventArgs e);
 
/////  
///// Event arguments for StrokesHit event
/////  
//public class StrokesHitEventArgs : EventArgs
//{
//    internal StrokesHitEventArgs(StrokeCollection hitStrokes)
//    { 
//        System.Diagnostics.Debug.Assert(hitStrokes != null && hitStrokes.Count > 0);
//        _hitStrokes = hitStrokes; 
//    } 

//    ///  
//    ///
//    /// 
//    public StrokeCollection HitStrokes
//    { 
//        get { return _hitStrokes; }
//    } 
 
//    private StrokeCollection _hitStrokes;
//} 

//#endregion

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------ 
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
//----------------------------------------------------------------------- 
//#define TRACE
 
using System; 
using System.Windows;
using System.Windows.Input; 
using System.Windows.Ink;
using System.Windows.Media;
using System.Collections.Generic;
using System.Collections.ObjectModel; 
using MS.Internal.Ink;
using MS.Utility; 
using MS.Internal; 
using System.Diagnostics;
 
using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID;

namespace System.Windows.Ink 
{
    #region IncrementalHitTester Abstract Base Class 
 
    /// 
    /// This class serves as both the base class and public interface for 
    /// incremental hit-testing implementaions.
    /// 
    public abstract class IncrementalHitTester
    { 
        #region Public API
 
        ///  
        /// Adds a point representing an incremental move of the hit-testing tool
        ///  
        /// a point that represents an incremental move of the hitting tool
        public void AddPoint(Point point)
        {
            AddPoints(new Point[1] { point }); 
        }
 
        ///  
        /// Adds an array of points representing an incremental move of the hit-testing tool
        ///  
        /// points representing an incremental move of the hitting tool
        public void AddPoints(IEnumerable points)
        {
            if (points == null) 
            {
                throw new System.ArgumentNullException("points"); 
            } 

            if (IEnumerablePointHelper.GetCount(points) == 0) 
            {
                throw new System.ArgumentException(SR.Get(SRID.EmptyArrayNotAllowedAsArgument), "points");
            }
 
            if (false == _fValid)
            { 
                throw new System.InvalidOperationException(SR.Get(SRID.EndHitTestingCalled)); 
            }
 
            System.Diagnostics.Debug.Assert(_strokes != null);

            AddPointsCore(points);
        } 

        ///  
        /// Adds a StylusPacket representing an incremental move of the hit-testing tool 
        /// 
        /// stylusPoints 
        public void AddPoints(StylusPointCollection stylusPoints)
        {
            if (stylusPoints == null)
            { 
                throw new System.ArgumentNullException("stylusPoints");
            } 
 
            if (stylusPoints.Count == 0)
            { 
                throw new System.ArgumentException(SR.Get(SRID.EmptyArrayNotAllowedAsArgument), "stylusPoints");
            }

            if (false == _fValid) 
            {
                throw new System.InvalidOperationException(SR.Get(SRID.EndHitTestingCalled)); 
            } 

            System.Diagnostics.Debug.Assert(_strokes != null); 

            Point[] points = new Point[stylusPoints.Count];
            for (int x = 0; x < stylusPoints.Count; x++)
            { 
                points[x] = (Point)stylusPoints[x];
            } 
 
            AddPointsCore(points);
        } 


        /// 
        /// Release as many resources as possible for this enumerator 
        /// 
        public void EndHitTesting() 
        { 
            if (_strokes != null)
            { 
                // Detach the event handler
                _strokes.StrokesChangedInternal -= new StrokeCollectionChangedEventHandler(OnStrokesChanged);
                _strokes = null;
                int count = _strokeInfos.Count; 
                for ( int i = 0; i < count; i++)
                { 
#if TRACE 
                    if (_strokeInfos[i].HitWeight != _strokeInfos[i].TotalWeight)
                    { 
                        System.Diagnostics.Debug.WriteLine(String.Format("IsHit: {0}; TotalWeight: {1}; HitWeight: {2}, Percent hit: {3}",
                                                            _strokeInfos[i].IsHit, _strokeInfos[i].TotalWeight, _strokeInfos[i].HitWeight, 100f * _strokeInfos[i].HitWeight / _strokeInfos[i].TotalWeight));
                    }
#endif 
                    _strokeInfos[i].Detach();
                } 
                _strokeInfos = null; 
            }
            _fValid = false; 
        }

        /// 
        /// Accessor to see if the Hit Tester is still valid 
        /// 
        public bool IsValid { get { return _fValid; } } 
        #endregion 

        #region Internal 

        /// 
        /// C-tor.
        ///  
        /// strokes to hit-test
        internal IncrementalHitTester(StrokeCollection strokes) 
        { 
            System.Diagnostics.Debug.Assert(strokes != null);
 
            // Create a StrokeInfo object for each stroke.
            _strokeInfos = new List(strokes.Count);
            for (int x = 0; x < strokes.Count; x++)
            { 
                Stroke stroke = strokes[x];
                _strokeInfos.Add(new StrokeInfo(stroke)); 
            } 

            _strokes = strokes; 

            // Attach an event handler to the strokes' changed event
            _strokes.StrokesChangedInternal += new StrokeCollectionChangedEventHandler(OnStrokesChanged);
        } 

        ///  
        /// The implementation behind AddPoint/AddPoints. 
        /// Derived classes are supposed to override this method.
        ///  
        protected abstract void AddPointsCore(IEnumerable points);


        ///  
        /// Accessor to the internal collection of StrokeInfo objects
        ///  
        internal List StrokeInfos { get { return _strokeInfos; } } 

        #endregion 

        #region Private

        ///  
        /// Event handler associated with the stroke collection.
        ///  
        /// Stroke collection that was modified 
        /// Modification that occurred
        ///  
        /// Update our _strokeInfos cache.  We get notified on StrokeCollection.StrokesChangedInternal which
        /// is raised first so we can assume we're the first delegate in the call chain
        /// 
        private void OnStrokesChanged(object sender, StrokeCollectionChangedEventArgs args) 
        {
            System.Diagnostics.Debug.Assert((_strokes != null) && (_strokeInfos != null) && (_strokes == sender)); 
 
            StrokeCollection added = args.Added;
            StrokeCollection removed = args.Removed; 

            if (added.Count > 0)
            {
                int firstIndex = _strokes.IndexOf(added[0]); 
                for (int i = 0; i < added.Count; i++)
                { 
                    _strokeInfos.Insert(firstIndex, new StrokeInfo(added[i])); 
                    firstIndex++;
                } 
            }

            if (removed.Count > 0)
            { 
                StrokeCollection localRemoved = new StrokeCollection(removed);
                //we have to assume that removed strokes can be in any order in _strokes 
                for (int i = 0; i < _strokeInfos.Count && localRemoved.Count > 0; ) 
                {
                    bool found = false; 
                    for (int j = 0; j < localRemoved.Count; j++)
                    {
                        if (localRemoved[j] == _strokeInfos[i].Stroke)
                        { 
                            _strokeInfos.RemoveAt(i);
                            localRemoved.RemoveAt(j); 
 
                            found = true;
                        } 
                    }
                    //we didn't find a removed stroke at index i in _strokeInfos, so advance i
                    if (!found)
                    { 
                        i++;
                    } 
                } 
            }
 
            //validate our cache
            if (_strokes.Count != _strokeInfos.Count)
            {
                Debug.Assert(false, "Benign assert.  IncrementalHitTester's _strokeInfos cache is out of [....], rebuilding."); 
                RebuildStrokeInfoCache();
                return; 
            } 
            for (int i = 0; i < _strokeInfos.Count; i++)
            { 
                if (_strokeInfos[i].Stroke != _strokes[i])
                {
                    Debug.Assert(false, "Benign assert.  IncrementalHitTester's _strokeInfos cache is out of [....], rebuilding.");
                    RebuildStrokeInfoCache(); 
                    return;
                } 
            } 
        }
 
        /// 
        /// IHT's can get into a state where their StrokeInfo cache is too
        /// out of [....] with the StrokeCollection to incrementally update it.
        /// When we detect this has happened, we just rebuild the entire cache. 
        /// 
        private void RebuildStrokeInfoCache() 
        { 
            List newStrokeInfos = new List(_strokes.Count);
            foreach (Stroke stroke in _strokes) 
            {
                bool found = false;
                for (int x = 0; x < _strokeInfos.Count; x++)
                { 
                    StrokeInfo strokeInfo = _strokeInfos[x];
                    if (strokeInfo != null && stroke == strokeInfo.Stroke) 
                    { 
                        newStrokeInfos.Add(strokeInfo);
                        //just set to null instead of removing and shifting 
                        //we're about to GC _strokeInfos
                        _strokeInfos[x] = null;
                        found = true;
                        break; 
                    }
                } 
                if (!found) 
                {
                    //we didn't find an existing strokeInfo 
                    newStrokeInfos.Add(new StrokeInfo(stroke));
                }
            }
 
            //detach the remaining strokeInfo's from their strokes
            for (int x = 0; x < _strokeInfos.Count; x++) 
            { 
                StrokeInfo strokeInfo = _strokeInfos[x];
 
                if (strokeInfo != null)
                {
                    strokeInfo.Detach();
                } 
            }
 
            _strokeInfos = newStrokeInfos; 

#if DEBUG 
            Debug.Assert(_strokeInfos.Count == _strokes.Count);
            for (int x = 0; x < _strokeInfos.Count; x++)
            {
                Debug.Assert(_strokeInfos[x].Stroke == _strokes[x]); 
            }
#endif 
        } 

        #endregion 

        #region Fields

        ///  Reference to the stroke collection under test  
        private StrokeCollection _strokes;
        ///  A collection of helper objects mapped to the stroke colection 
        private List _strokeInfos; 

        private bool _fValid = true; 

        #endregion
    }
 
    #endregion
 
    #region IncrementalLassoHitTester 

    ///  
    /// IncrementalHitTester implementation for hit-testing with lasso
    /// 
    public class IncrementalLassoHitTester : IncrementalHitTester
    { 
        #region public APIs
        ///  
        /// Event 
        /// 
        public event LassoSelectionChangedEventHandler SelectionChanged; 
        #endregion
        #region C-tor and the overrides

        ///  
        /// C-tor.
        ///  
        /// strokes to hit-test 
        /// a hit-testing parameter that defines the minimal
        /// percent of nodes of a stroke to be inside the lasso to consider the stroke hit 
        internal IncrementalLassoHitTester(StrokeCollection strokes, int percentageWithinLasso)
            : base(strokes)
        {
            System.Diagnostics.Debug.Assert((percentageWithinLasso >= 0) && (percentageWithinLasso <= 100)); 
            _lasso = new SingleLoopLasso();
            _percentIntersect = percentageWithinLasso; 
        } 

        ///  
        /// The implementation behind the public methods AddPoint/AddPoints
        /// 
        /// new points to add to the lasso
        protected override void AddPointsCore(IEnumerable points) 
        {
            System.Diagnostics.Debug.Assert((points != null) && (IEnumerablePointHelper.GetCount(points)!= 0)); 
 
            // Add the new points to the lasso
            int lastPointIndex = (0 != _lasso.PointCount) ? (_lasso.PointCount - 1) : 0; 
            _lasso.AddPoints(points);

            // Do nothing if there's not enough points, or there's nobody listening
            // The points may be filtered out, so if all the points are filtered out, (lastPointIndex == (_lasso.PointCount - 1). 
            // For this case, check if the incremental lasso is disabled (i.e., points modified).
            if ((_lasso.IsEmpty) || (lastPointIndex == (_lasso.PointCount - 1) && false == _lasso.IsIncrementalLassoDirty) 
                || (SelectionChanged == null)) 
            {
                return; 
            }

            // Variables for possible HitChanged events to fire
            StrokeCollection strokesHit = null; 
            StrokeCollection strokesUnhit = null;
 
            // Create a lasso that represents the current increment 
            Lasso lassoUpdate = new Lasso();
 
            if (false == _lasso.IsIncrementalLassoDirty)
            {
                if (0 < lastPointIndex)
                { 
                    lassoUpdate.AddPoint(_lasso[0]);
                } 
 
                // Only the points the have been successfully added to _lasso will be added to
                // lassoUpdate. 
                for (; lastPointIndex < _lasso.PointCount; lastPointIndex++)
                {
                    lassoUpdate.AddPoint(_lasso[lastPointIndex]);
                } 
            }
 
            // Enumerate through the strokes and update their hit-test results 
            foreach (StrokeInfo strokeInfo in this.StrokeInfos)
            { 
                Lasso lasso;
                if (true == strokeInfo.IsDirty || true == _lasso.IsIncrementalLassoDirty)
                {
                    // If this is the first time this stroke gets hit-tested with this lasso, 
                    // or if the stroke (or its DAs) has changed since the last hit-testing,
                    // or if the lasso points have been modified, 
                    // then (re)hit-test this stroke against the entire lasso. 
                    lasso = _lasso;
                    strokeInfo.IsDirty = false; 
                }
                else
                {
                    // Otherwise, hit-test it against the lasso increment first and then only 
                    // those ink points that are in that small lasso need to be hit-tested
                    // against the big (entire) lasso. 
                    // This is supposed to be a significant piece of optimization, since 
                    // lasso increments are usually very small, they are defined by just
                    // a few points and they don't capture and/or release too many ink nodes. 
                    lasso = lassoUpdate;
                }

                // Skip those stroke which bounding box doesn't even intersects with the lasso bounds 
                double hitWeightChange = 0f;
                if (lasso.Bounds.IntersectsWith(strokeInfo.StrokeBounds)) 
                { 
                    // Get the stroke node points for the hit-testing.
                    StylusPointCollection stylusPoints = strokeInfo.StylusPoints; 

                    // Find out if the lasso update has changed the hit count of the stroke.
                    for (int i = 0; i < stylusPoints.Count; i++)
                    { 
                        // Consider only the points that become captured/released with this particular update
                        if (true == lasso.Contains((Point)stylusPoints[i])) 
                        { 
                            double weight = strokeInfo.GetPointWeight(i);
 
                            if (lasso == _lasso || _lasso.Contains((Point)stylusPoints[i]))
                            {
                                hitWeightChange += weight;
                            } 
                            else
                            { 
                                hitWeightChange -= weight; 
                            }
                        } 
                    }
                }

                // Update the stroke hit weight and check whether it has crossed the margin 
                // in either direction since the last update.
                if ((hitWeightChange != 0) || (lasso == _lasso)) 
                { 
                    strokeInfo.HitWeight = (lasso == _lasso) ? hitWeightChange : (strokeInfo.HitWeight + hitWeightChange);
                    bool isHit = DoubleUtil.GreaterThanOrClose(strokeInfo.HitWeight, strokeInfo.TotalWeight * _percentIntersect / 100f - Stroke.PercentageTolerance); 

                    if (strokeInfo.IsHit != isHit)
                    {
                        strokeInfo.IsHit = isHit; 
                        if (isHit)
                        { 
                            // The hit count became greater than the margin percentage, the stroke 
                            // needs to be reported for selection
                            if (null == strokesHit) 
                            {
                                strokesHit = new StrokeCollection();
                            }
                            strokesHit.Add(strokeInfo.Stroke); 
#if TRACE
                            System.Diagnostics.Debug.WriteLine(String.Format("Added a stroke to the hit list. Current hit count = {0}", strokesHit.Count)); 
#endif 
                        }
                        else 
                        {
                            // The hit count just became less than the margin percentage,
                            // the stroke needs to be reported for de-selection
                            if (null == strokesUnhit) 
                            {
                                strokesUnhit = new StrokeCollection(); 
                            } 
                            strokesUnhit.Add(strokeInfo.Stroke);
#if TRACE 
                            System.Diagnostics.Debug.WriteLine(String.Format("Added a stroke to the unhit list"));
#endif
                        }
                    } 
                }
            } 
 
            _lasso.IsIncrementalLassoDirty = false;
            // Raise StrokesHitChanged event if any strokes has changed thier 
            // hit status and there're the event subscribers.
            if ((null != strokesHit) || (null != strokesUnhit))
            {
                OnSelectionChanged(new LassoSelectionChangedEventArgs (strokesHit, strokesUnhit)); 
            }
        } 
 
        /// 
        /// SelectionChanged event raiser 
        /// 
        /// 
        protected void OnSelectionChanged(LassoSelectionChangedEventArgs  eventArgs)
        { 
            System.Diagnostics.Debug.Assert(eventArgs != null);
            if (SelectionChanged != null) 
            { 
                SelectionChanged(this, eventArgs);
            } 
        }


        #endregion 
        #region Fields
 
        private Lasso   _lasso; 
        private int     _percentIntersect;
 
        #endregion
    }

    #endregion 

    #region IncrementalStrokeHitTester 
 
    /// 
    /// IncrementalHitTester implementation for hit-testing with a shape, PointErasing . 
    /// 
    public class IncrementalStrokeHitTester  : IncrementalHitTester
    {
        ///  
        ///
        ///  
        public event StrokeHitEventHandler StrokeHit; 

        #region C-tor and the overrides 

        /// 
        /// C-tor
        ///  
        /// strokes to hit-test for erasing
        /// erasing shape 
        internal IncrementalStrokeHitTester(StrokeCollection strokes, StylusShape eraserShape) 
            : base(strokes)
        { 
            System.Diagnostics.Debug.Assert(eraserShape != null);

            // Create an ErasingStroke objects that implements the actual hit-testing
            _erasingStroke = new ErasingStroke(eraserShape); 
        }
 
        ///  
        /// The implementation behind the public methods AddPoint/AddPoints
        ///  
        /// a set of points representing the last increment
        /// in the moving of the erasing shape
        protected override void AddPointsCore(IEnumerable points)
        { 
            System.Diagnostics.Debug.Assert((points != null) && (IEnumerablePointHelper.GetCount(points) != 0));
            System.Diagnostics.Debug.Assert(_erasingStroke != null); 
 
            // Move the shape through the new points and build the contour of the move.
            _erasingStroke.MoveTo(points); 
            Rect erasingBounds = _erasingStroke.Bounds;
            if (erasingBounds.IsEmpty)
            {
                return; 
            }
 
            List strokeHitEventArgCollection = null; 
            // Do nothing if there's nobody listening to the events
            if (StrokeHit != null) 
            {
                List eraseAt = new List();

                // Test stroke by stroke and collect the results. 
                for (int x = 0; x < this.StrokeInfos.Count; x++)
                { 
                    StrokeInfo strokeInfo = this.StrokeInfos[x]; 

                    // Skip the stroke if its bounding box doesn't intersect with the one of the hitting shape. 
                    if ((erasingBounds.IntersectsWith(strokeInfo.StrokeBounds) == false) ||
                        (_erasingStroke.EraseTest(StrokeNodeIterator.GetIterator(strokeInfo.Stroke, strokeInfo.Stroke.DrawingAttributes), eraseAt) == false))
                    {
                        continue; 
                    }
 
                    // Create an event args to raise after done with hit-testing 
                    // We don't fire these events right away because user is expected to
                    // modify the stroke collection in her event handler, and that would 
                    // invalidate this foreach loop.
                    if (strokeHitEventArgCollection == null)
                    {
                        strokeHitEventArgCollection = new List(); 
                    }
                    strokeHitEventArgCollection.Add(new StrokeHitEventArgs(strokeInfo.Stroke, eraseAt.ToArray())); 
                    // We must clear eraseAt or it will contain invalid results for the next strokes 
                    eraseAt.Clear();
                } 
            }

            // Raise StrokeHit event if needed.
            if (strokeHitEventArgCollection != null) 
            {
                System.Diagnostics.Debug.Assert(strokeHitEventArgCollection.Count != 0); 
                for (int x = 0; x < strokeHitEventArgCollection.Count; x++) 
                {
                    StrokeHitEventArgs eventArgs = strokeHitEventArgCollection[x]; 

                    System.Diagnostics.Debug.Assert(eventArgs.HitStroke != null);
                    OnStrokeHit(eventArgs);
                } 
            }
        } 
 
        /// 
        /// Event raiser for StrokeHit 
        /// 
        protected void OnStrokeHit(StrokeHitEventArgs eventArgs)
        {
            System.Diagnostics.Debug.Assert(eventArgs != null); 
            if (StrokeHit != null)
            { 
                StrokeHit(this, eventArgs); 
            }
        } 

        #endregion

        #region Fields 

        private ErasingStroke _erasingStroke; 
 
        #endregion
    } 


    #endregion
 
    #region EventArgs and delegates
 
    ///  
    /// Declaration for LassoSelectionChanged event handler. Used in lasso-selection
    ///  
    public delegate void LassoSelectionChangedEventHandler(object sender, LassoSelectionChangedEventArgs e);


    ///  
    /// Declaration for StrokeHit event handler. Used in point-erasing
    ///  
    public delegate void StrokeHitEventHandler(object sender, StrokeHitEventArgs e); 

 
    /// 
    /// Event arguments for LassoSelectionChanged event
    /// 
    public class LassoSelectionChangedEventArgs  : EventArgs 
    {
        internal LassoSelectionChangedEventArgs(StrokeCollection selectedStrokes, StrokeCollection deselectedStrokes) 
        { 
            _selectedStrokes = selectedStrokes;
            _deselectedStrokes = deselectedStrokes; 
        }

        /// 
        /// Collection of strokes which were hit with the last increment 
        /// 
        public StrokeCollection SelectedStrokes 
        { 
            get
            { 
                if (_selectedStrokes != null)
                {
                    StrokeCollection sc = new StrokeCollection();
                    sc.Add(_selectedStrokes); 
                    return sc;
                } 
                else 
                {
                    return  new StrokeCollection(); 
                }
            }
        }
 
        /// 
        /// Collection of strokes which were unhit with the last increment 
        ///  
        public StrokeCollection DeselectedStrokes
        { 
            get
            {
                if (_deselectedStrokes != null)
                { 
                    StrokeCollection sc = new StrokeCollection();
                    sc.Add(_deselectedStrokes); 
                    return sc; 
                }
                else 
                {
                    return new StrokeCollection();
                }
            } 
        }
 
        private StrokeCollection _selectedStrokes; 
        private StrokeCollection _deselectedStrokes;
    } 

    /// 
    /// Event arguments for StrokeHit event
    ///  
    public class StrokeHitEventArgs : EventArgs
    { 
        ///  
        /// C-tor
        ///  
        internal StrokeHitEventArgs(Stroke stroke, StrokeIntersection[] hitFragments)
        {
            System.Diagnostics.Debug.Assert(stroke != null && hitFragments != null && hitFragments.Length > 0);
            _stroke = stroke; 
            _hitFragments = hitFragments;
        } 
 
        /// Stroke that was hit
        public Stroke HitStroke { get { return _stroke; } } 

        /// 
        ///
        ///  
        /// 
        public StrokeCollection GetPointEraseResults() 
        { 
            return _stroke.Erase(_hitFragments);
        } 

        private Stroke                  _stroke;
        private StrokeIntersection[]    _hitFragments;
 
    }
 
 

    #endregion 
}

namespace MS.Internal.Ink
{ 
    #region StrokeInfo
 
    ///  
    /// A helper class associated with a stroke. Used for caching the stroke's
    /// bounding box, hit-testing results, and for keeping an eye on the stroke changes 
    /// 
    internal class StrokeInfo
    {
        #region API (used by incremental hit-testers) 

        ///  
        /// StrokeInfo 
        /// 
        internal StrokeInfo(Stroke stroke) 
        {
            System.Diagnostics.Debug.Assert(stroke != null);
            _stroke = stroke;
            _bounds = stroke.GetBounds(); 

            // Start listening to the stroke events 
            _stroke.DrawingAttributesChanged += new PropertyDataChangedEventHandler(OnStrokeDrawingAttributesChanged); 
            _stroke.StylusPointsReplaced += new StylusPointsReplacedEventHandler(OnStylusPointsReplaced);
            _stroke.StylusPoints.Changed += new EventHandler(OnStylusPointsChanged); 
            _stroke.DrawingAttributesReplaced += new DrawingAttributesReplacedEventHandler(OnDrawingAttributesReplaced);
        }

        /// The stroke object associated with this helper structure 
        internal Stroke Stroke { get { return _stroke; } }
 
        /// Pre-calculated bounds of the stroke  
        internal Rect StrokeBounds { get { return _bounds; } }
 
        /// Tells whether the stroke or its drawing attributes have been modified
        /// since the last use (hit-testing)
        internal bool IsDirty
        { 
            get { return _isDirty; }
            set { _isDirty = value; } 
        } 

        /// Tells whether the stroke was found (and reported) as hit  
        internal bool IsHit
        {
            get { return _isHit; }
            set { _isHit = value; } 
        }
 
        ///  
        /// Cache teh stroke points
        ///  
        internal StylusPointCollection StylusPoints
        {
            get
            { 
                if (_stylusPoints == null)
                { 
                    if (_stroke.DrawingAttributes.FitToCurve) 
                    {
                        _stylusPoints = _stroke.GetBezierStylusPoints(); 
                    }
                    else
                    {
                        _stylusPoints = _stroke.StylusPoints; 
                    }
                } 
                return _stylusPoints; 
            }
        } 

        /// 
        /// Holds the current hit-testing result for the stroke. Represents the length of
        /// the stroke "inside" and "hit" by the lasso 
        /// 
        internal double HitWeight 
        { 
            get { return _hitWeight; }
            set 
            {
                // it is ok to clamp this off, rounding error sends it over or under by a minimal amount.
                if (DoubleUtil.GreaterThan(value, TotalWeight))
                { 
                    _hitWeight = TotalWeight;
                } 
                else if (DoubleUtil.LessThan(value, 0f)) 
                {
                    _hitWeight = 0f; 
                }
                else
                {
                    _hitWeight = value; 
                }
            } 
        } 

        ///  
        /// Get the total weight of the stroke. For this implementation, it is the total length of the stroke.
        /// 
        /// 
        internal double TotalWeight 
        {
            get 
            { 
                if (!_totalWeightCached)
                { 
                    _totalWeight= 0;
                    for (int i = 0; i < StylusPoints.Count; i++)
                    {
                        _totalWeight += this.GetPointWeight(i); 
                    }
                    _totalWeightCached = true; 
                } 
                return _totalWeight;
            } 
        }

        /// 
        /// Calculate the weight of a point. 
        /// 
        internal double GetPointWeight(int index) 
        { 
            StylusPointCollection stylusPoints = this.StylusPoints;
            DrawingAttributes da = this.Stroke.DrawingAttributes; 
            System.Diagnostics.Debug.Assert(stylusPoints != null && index >= 0 && index < stylusPoints.Count);

            double weight = 0f;
            if (index == 0) 
            {
                weight += Math.Sqrt(da.Width*da.Width + da.Height*da.Height) / 2.0f; 
            } 
            else
            { 
                Vector spine = (Point)stylusPoints[index] - (Point)stylusPoints[index - 1];
                weight += Math.Sqrt(spine.LengthSquared) / 2.0f;
            }
 
            if (index == stylusPoints.Count - 1)
            { 
                weight += Math.Sqrt(da.Width*da.Width + da.Height*da.Height) / 2.0f; 
            }
            else 
            {
                Vector spine = (Point)stylusPoints[index + 1] - (Point)stylusPoints[index];
                weight += Math.Sqrt(spine.LengthSquared) / 2.0f;
            } 

            return weight; 
        } 
        /// 
        /// A kind of disposing method 
        /// 
        internal void Detach()
        {
            if (_stroke != null) 
            {
                // Detach the event handlers 
                _stroke.DrawingAttributesChanged -= new PropertyDataChangedEventHandler(OnStrokeDrawingAttributesChanged); 
                _stroke.StylusPointsReplaced -= new StylusPointsReplacedEventHandler(OnStylusPointsReplaced);
                _stroke.StylusPoints.Changed -= new EventHandler(OnStylusPointsChanged); 
                _stroke.DrawingAttributesReplaced -= new DrawingAttributesReplacedEventHandler(OnDrawingAttributesReplaced);

                _stroke = null;
            } 
        }
 
        #endregion 

        #region Stroke event handlers (Private) 

        /// Event handler for stroke data changed events
        private void OnStylusPointsChanged(object sender, EventArgs args)
        { 
            Invalidate();
        } 
 
        /// Event handler for stroke data changed events
        private void OnStylusPointsReplaced(object sender, StylusPointsReplacedEventArgs args) 
        {
            Invalidate();
        }
 
        /// 
        /// Event handler for stroke's drawing attributes changes. 
        ///  
        /// 
        ///  
        private void OnStrokeDrawingAttributesChanged(object sender, PropertyDataChangedEventArgs args)
        {
            // Only enforce rehittesting of the whole stroke when the DrawingAttribute change may affect hittesting
            if(DrawingAttributes.IsGeometricalDaGuid(args.PropertyGuid)) 
            {
                Invalidate(); 
            } 
        }
 
        private void OnDrawingAttributesReplaced(Object sender, DrawingAttributesReplacedEventArgs args)
        {
            // If the drawing attributes change involves Width, Height, StylusTipTransform, IgnorePressure, or FitToCurve,
            // we need to invalidate 
            if (false == DrawingAttributes.GeometricallyEqual(args.NewDrawingAttributes, args.PreviousDrawingAttributes))
            { 
                Invalidate(); 
            }
        } 

        /// Implementation for the event handlers above
        private void Invalidate()
        { 
            _totalWeightCached = false;
            _stylusPoints = null; 
            _hitWeight = 0; 

            // Let the hit-tester know that it should not use incremental hit-testing 
            _isDirty = true;

            // The Stroke.GetBounds may be overriden in the 3rd party code.
            // The out-side code could throw exception. If an exception is thrown, _bounds will keep the original value. 
            // Re-compute the stroke bounds
            _bounds = _stroke.GetBounds(); 
        } 
        #endregion
 
        #region Fields

        private Stroke                      _stroke;
        private Rect                        _bounds; 
        private double                      _hitWeight = 0f;
        private bool                        _isHit = false; 
        private bool                        _isDirty = true; 
        private StylusPointCollection       _stylusPoints;   // Cache the stroke rendering points
        private double                      _totalWeight = 0f; 
        private bool                        _totalWeightCached = false;
        #endregion
    }
 
    #endregion // StrokeInfo
} 
 
// The following code is for Stroke-Erasing scenario. Currently the IncrementalStrokeHitTester
// can be used for Stroke-erasing but the following class is faster. If in the future there's a 
// perf issue with Stroke-Erasing, consider adding the following code.
//#region Commented Code for IncrementalStrokeHitTester
//#region IncrementalStrokeHitTester
 
///// 
///// IncrementalHitTester implementation for hit-testing with a shape, StrokeErasing . 
/////  
//public class IncrementalStrokeHitTester : IncrementalHitTester
//{ 
//    /// 
//    /// event
//    /// 
//    public event StrokesHitEventHandler StrokesHit; 

//    #region C-tor and the overrides 
 
//    /// 
//    /// C-tor 
//    /// 
//    /// strokes to hit-test for erasing
//    /// erasing shape
//    internal IncrementalStrokeHitTester(StrokeCollection strokes, StylusShape eraserShape) 
//        : base(strokes)
//    { 
//        System.Diagnostics.Debug.Assert(eraserShape != null); 

//        // Create an ErasingStroke objects that implements the actual hit-testing 
//        _erasingStroke = new ErasingStroke(eraserShape);
//    }

//    ///  
//    ///
//    ///  
//    ///  
//    internal protected void OnStrokesHit(StrokesHitEventArgs eventArgs)
//    { 
//        if (StrokesHit != null)
//        {
//            StrokesHit(this, eventArgs);
//        } 
//    }
 
//    ///  
//    /// The implementation behind the public methods AddPoint/AddPoints
//    ///  
//    /// a set of points representing the last increment
//    /// in the moving of the erasing shape
//    internal protected override void AddPointsCore(Point[] points)
//    { 
//        System.Diagnostics.Debug.Assert((points != null) && (points.Length != 0));
//        System.Diagnostics.Debug.Assert(_erasingStroke != null); 
 
//        // Move the shape through the new points and build the contour of the move.
//        _erasingStroke.MoveTo(points); 
//        Rect erasingBounds = _erasingStroke.Bounds;
//        if (erasingBounds.IsEmpty)
//        {
//            return; 
//        }
 
//        StrokeCollection strokesHit = null; 
//        if (StrokesHit != null)
//        { 
//            // Test stroke by stroke and collect hits.
//            foreach (StrokeInfo strokeInfo in StrokeInfos)
//            {
//                // Skip strokes that have already been reported hit or which bounds 
//                // don't intersect with the bounds of the erasing stroke.
//                if ((strokeInfo.IsHit == false) && erasingBounds.IntersectsWith(strokeInfo.StrokeBounds) 
//                    && _erasingStroke.HitTest(StrokeNodeIterator.GetIterator(strokeInfo.Stroke, strokeInfo.Overrides))) 
//                {
//                    if (strokesHit == null) 
//                    {
//                        strokesHit = new StrokeCollection();
//                    }
//                    strokesHit.Add(strokeInfo.Stroke); 
//                    strokeInfo.IsHit = true;
//                } 
//            } 
//        }
 
//        // Raise StrokesHitChanged event if any strokes have been hit and there're listeners to the event.
//        if (strokesHit != null)
//        {
//            System.Diagnostics.Debug.Assert(strokesHit.Count != 0); 
//            OnStrokesHit(new StrokesHitEventArgs(strokesHit));
//        } 
//    } 

//    #endregion 

//    #region Fields

//    private ErasingStroke _erasingStroke; 

//    #endregion 
//} 

//#endregion 

///// 
///// Declaration for StrokesHit event handler. Used in stroke-erasing
/////  
//public delegate void StrokesHitEventHandler(object sender, StrokesHitEventArgs e);
 
/////  
///// Event arguments for StrokesHit event
/////  
//public class StrokesHitEventArgs : EventArgs
//{
//    internal StrokesHitEventArgs(StrokeCollection hitStrokes)
//    { 
//        System.Diagnostics.Debug.Assert(hitStrokes != null && hitStrokes.Count > 0);
//        _hitStrokes = hitStrokes; 
//    } 

//    ///  
//    ///
//    /// 
//    public StrokeCollection HitStrokes
//    { 
//        get { return _hitStrokes; }
//    } 
 
//    private StrokeCollection _hitStrokes;
//} 

//#endregion

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK