File: MS\Internal\Annotations\Component\MarkedHighlightComponent.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: Implements a Highlight component that has markers at the end
//              It also listens to TextSelection.Changed and GotFocus/LostFocus
//              events of the TextContainerParent and activates/deactivates
//
//              See spec at StickyNoteControlSpec.mht
//
 
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Annotations;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Collections;
using System.Xml;
using System.Windows.Input;
using MS.Internal.Annotations.Anchoring;
using MS.Utility;
 
 
namespace MS.Internal.Annotations.Component
{
    /// <summary>
    /// Displays a highlight anchor with markers at the end that gets
    /// activated when selected or when Focused property is set.
    /// Focused property is provided for synchronization with external components like
    /// Sticky Note
    /// </summary>
    internal sealed class MarkedHighlightComponent : Canvas, IAnnotationComponent
    {
        //------------------------------------------------------
        //
        //  ctors
        //
        //------------------------------------------------------
        #region ctors
 
        /// <summary>
        /// Creates a new component
        /// </summary>
        /// <param name="type">owning component type </param>
        /// <param name="host">Dependency object to host IsActive and IsMouseOverAnchor DepenedencyProperties
        /// if host is null - set them on MarkedHighlightComponent itself</param>
        public MarkedHighlightComponent(XmlQualifiedName type, DependencyObject host)
            : base()
        {
            ArgumentNullException.ThrowIfNull(type);
 
            _DPHost = host == null ? this : host;
            ClipToBounds = false;
 
            //create anchor highlight. The second parameter controls
            // if we want to render the entire anchor or only the Text content
            // This applies specificaly to tables, figures and floaters.
            // Currently implementation of GetTightBoundingGeometryForTextPointers does not
            // allow us to render the entire cell so we pass true (means content only).
            HighlightAnchor = new HighlightComponent(1, true, type);
            Children.Add(HighlightAnchor);
 
            //we will add the markers when the attached annotation is added
            //when we know the attachment level
            _leftMarker = null;
            _rightMarker = null;
 
            _state = 0;
            SetState();
        }
        #endregion ctors
 
 
        //------------------------------------------------------
        //
        //  IAnnotationComponent implementation
        //
        //------------------------------------------------------
        #region IAnnotationComponent implementation
 
 
        #region Public Properties
 
        /// <summary>
        /// Return a copy of the list of IAttachedAnnotations held by this component
        /// </summary>
        public IList AttachedAnnotations
        {
            get
            {
                ArrayList list = new ArrayList();
                if (_attachedAnnotation != null)
                {
                    list.Add(_attachedAnnotation);
                }
                return list;
            }
        }
 
        /// <summary>
        /// Sets and gets the context this annotation component is hosted in.
        /// </summary>
        /// <value>Context this annotation component is hosted in</value>
        public PresentationContext PresentationContext
        {
            get
            {
                return _presentationContext;
            }
 
            set
            {
                _presentationContext = value;
            }
        }
 
        /// <summary>
        /// Sets and gets the Z-order of this component. NOP -
        /// Highlight does not have Z-order
        /// </summary>
        /// <value>Context this annotation component is hosted in</value>
        public int ZOrder
        {
            get
            {
                return -1;
            }
 
            set
            {
            }
        }
 
        /// <summary>
        /// Returns the one element the annotation component is attached to.
        /// </summary>
        /// <value></value>
        public UIElement AnnotatedElement
        {
            get
            {
                return _attachedAnnotation != null ? (_attachedAnnotation.Parent as UIElement) : null;
            }
        }
 
        /// <summary>
        /// When the value is set to true - the AnnotatedElement content has changed -
        /// save the value and update geometry.
        /// The value is set to false when the geometry is synced with the content
        /// </summary>
        public bool IsDirty
        {
            get
            {
                return _isDirty;
            }
 
            set
            {
                _isDirty = value;
                if (value)
                    UpdateGeometry();
            }
        }
 
        #endregion Public Properties
 
        #region Public Methods
 
        /// <summary>
        /// There is no common transformation that can be used for all the children.
        /// Here we adjust the individual rendering transformation of each child and
        /// return the input transfor as a result
        /// </summary>
        /// <param name="transform">Transform to the AnnotatedElement.</param>
        /// <returns>Transform to the annotation component</returns>
        public GeneralTransform GetDesiredTransform(GeneralTransform transform)
        {
            //we need to recalculate RenderingTransformations of the markers
            //according to their current positions in the TextView
            //after the content reflow
            if (_attachedAnnotation == null)
                throw new InvalidOperationException(SR.InvalidAttachedAnnotation);
 
            HighlightAnchor.GetDesiredTransform(transform);
 
            return transform;
        }
 
        /// <summary>
        /// Add an attached annotation to the component. The attached anchor will be used to add
        /// a highlight to the appropriate TextContainer
        /// </summary>
        /// <param name="attachedAnnotation">The attached annotation to be added to the component</param>
        public void AddAttachedAnnotation(IAttachedAnnotation attachedAnnotation)
        {
            if (_attachedAnnotation != null)
            {
                throw new ArgumentException(SR.MoreThanOneAttachedAnnotation);
            }
 
            //fire trace event
            EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.AddAttachedMHBegin);
 
            //save the attached annotation
            _attachedAnnotation = attachedAnnotation;
 
            //create anchor markers
            if ((attachedAnnotation.AttachmentLevel & AttachmentLevel.StartPortion) != 0)
                _leftMarker = CreateMarker(GetMarkerGeometry());
            if ((attachedAnnotation.AttachmentLevel & AttachmentLevel.EndPortion) != 0)
                _rightMarker = CreateMarker(GetMarkerGeometry());
 
            //create highlight, markers and register for TextSelection.Changed and other events
            RegisterAnchor();
 
            //fire trace event
            EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.AddAttachedMHEnd);
        }
 
        /// <summary>
        /// Remove an attached annotation from the component
        /// </summary>
        /// <param name="attachedAnnotation">The attached annotation to be removed from the component</param>
        public void RemoveAttachedAnnotation(IAttachedAnnotation attachedAnnotation)
        {
            ArgumentNullException.ThrowIfNull(attachedAnnotation);
 
            if (attachedAnnotation != _attachedAnnotation)
            {
                throw new ArgumentException(SR.InvalidAttachedAnnotation, "attachedAnnotation");
            }
 
            //fire trace event
            EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.RemoveAttachedMHBegin);
 
            //unregister markers from the adorner layer aND LISTENERS
            CleanUpAnchor();
 
            _attachedAnnotation = null;
 
            //fire trace event
            EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.RemoveAttachedMHEnd);
        }
 
        /// <summary>
        /// Modify an attached annotation that is held by the component
        /// </summary>
        /// <param name="attachedAnnotation">The attached annotation after modification</param>
        /// <param name="previousAttachedAnchor">The attached anchor previously associated with the attached annotation.</param>
        /// <param name="previousAttachmentLevel">The previous attachment level of the attached annotation.</param>
        public void ModifyAttachedAnnotation(IAttachedAnnotation attachedAnnotation, object previousAttachedAnchor, AttachmentLevel previousAttachmentLevel)
        {
            throw new NotSupportedException(SR.NotSupported);
        }
 
        #endregion Public Methods
 
        #endregion IAnnotationComponent implementation
 
        //------------------------------------------------------
        //
        //  Additional Public Properties
        //
        //------------------------------------------------------
        #region Additional Public Properties
 
        /// <summary>
        /// Used to sync with another component focus (like StickyNote)
        /// </summary>
        public bool Focused
        {
            set
            {
                byte oldState = _state;
 
                if (value)
                    _state |= FocusFlag;
                else
                    _state &= FocusFlagComplement;
 
                if ((_state == 0) != (oldState == 0))
                {
                    SetState();
                }
            }
        }
 
        /// <summary>
        /// Anchor brackets color
        /// </summary>
        public Brush MarkerBrush
        {
            set
            {
                SetValue(MarkedHighlightComponent.MarkerBrushProperty, value);
            }
        }
 
        //those properies are exposed to allow external components like StickyNote to synchronize their color
        // with MarkedHighlight colors
        public static DependencyProperty MarkerBrushProperty = DependencyProperty.Register("MarkerBrushProperty", typeof(Brush), typeof(MarkedHighlightComponent));
 
        /// <summary>
        /// Anchor brackets thickness
        /// </summary>
        public double StrokeThickness
        {
            set
            {
                SetValue(MarkedHighlightComponent.StrokeThicknessProperty, value);
            }
        }
 
        //A DP for StrokeThickness to bind marker thickness to.
        public static DependencyProperty StrokeThicknessProperty = DependencyProperty.Register("StrokeThicknessProperty", typeof(double), typeof(MarkedHighlightComponent));
 
        #endregion Additional Public Properties
 
        #region Inrernal Methods
 
        //------------------------------------------------------
        //
        //  Inrernal Methods
        //
        //------------------------------------------------------
        /// <summary>
        /// Set the TabIndex for the hosting element (StickyNote or other)
        /// </summary>
        /// <param name="index">tab index to be set</param>
        internal void SetTabIndex(int index)
        {
            if (_DPHost != null)
                KeyboardNavigation.SetTabIndex(_DPHost, index);
        }
        #endregion Internal Methods
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
        #region Private methods
 
        /// <summary>
        /// Marker transformation is calculated based on anchor position
        /// </summary>
        /// <param name="marker">marker Path element</param>
        /// <param name="anchor">marker anchor</param>
        /// <param name="baseAnchor">if anchor is the end anchor, pass the start one too so we can check for
        /// relative transform. If anchor is the start anchor baseAnchor is null</param>
        /// <param name="xScaleFactor">x-scale parameter of the ScaleTransform multiplier. if -1 theis will get the right bracket</param>
        private void SetMarkerTransform(Path marker, ITextPointer anchor, ITextPointer baseAnchor, int xScaleFactor)
        {
            //check if this marker is visible at all
            if (marker == null)
                return;
 
            Debug.Assert(anchor != null, "undefined anchor");
            Debug.Assert(marker.Data != null || marker.Data == Geometry.Empty, "undefined geometry");
 
            //marker.Data must be a GeometryGroup with 3 children
            GeometryGroup geometry = marker.Data as GeometryGroup;
            Debug.Assert(geometry != null, "unexpected geometry type");
            Debug.Assert(geometry.Children.Count == 3, "unexpected geometry children count");
 
 
            Rect markerRect = TextSelectionHelper.GetAnchorRectangle(anchor);
 
            if (markerRect == Rect.Empty)
                return;
 
            //transform of the body of the bracket
            double desiredBodyHeight = markerRect.Height - MarkerVerticalSpace - _bottomTailHeight - _topTailHeight;
            double scale = 0.0;
            double yScaleFactor = 0;
            if (desiredBodyHeight > 0)
            {
                scale = desiredBodyHeight / _bodyHeight;
                yScaleFactor = 1;
            }
            ScaleTransform bodyScale = new ScaleTransform(1, scale);
            TranslateTransform bodyOffset = new TranslateTransform(markerRect.X, markerRect.Y + _topTailHeight + MarkerVerticalSpace);
            TransformGroup bodyTransform = new TransformGroup();
            bodyTransform.Children.Add(bodyScale);
            bodyTransform.Children.Add(bodyOffset);
 
            //scale of the tails of the bracket
            ScaleTransform tailScale = new ScaleTransform(xScaleFactor, yScaleFactor);
 
            //bottom tail offset
            TranslateTransform bottomOffset = new TranslateTransform(markerRect.X, markerRect.Bottom - _bottomTailHeight);
            //top tail offset
            TranslateTransform topOffset = new TranslateTransform(markerRect.X, markerRect.Top + MarkerVerticalSpace);
 
            //bottom tail transform
            TransformGroup bottomTailTransform = new TransformGroup();
            bottomTailTransform.Children.Add(tailScale);
            bottomTailTransform.Children.Add(bottomOffset);
 
            //top tail transform
            TransformGroup topTailTransform = new TransformGroup();
            topTailTransform.Children.Add(tailScale);
            topTailTransform.Children.Add(topOffset);
 
            if (geometry.Children[0] != null)
                geometry.Children[0].Transform = topTailTransform;
 
            if (geometry.Children[1] != null)
                geometry.Children[1].Transform = bodyTransform;
            if (geometry.Children[2] != null)
                geometry.Children[2].Transform = bottomTailTransform;
 
            //add transform to base anchor if needed
            if (baseAnchor != null)
            {
                ITextView startView = TextSelectionHelper.GetDocumentPageTextView(baseAnchor);
                ITextView endView = TextSelectionHelper.GetDocumentPageTextView(anchor);
                if (startView != endView && startView.RenderScope != null && endView.RenderScope != null)
                {
                    geometry.Transform = (Transform)endView.RenderScope.TransformToVisual(startView.RenderScope);
                }
            }
        }
 
        /// <summary>
        /// Handles selected/unselected changes
        /// </summary>
        /// <param name="selected">true - selected/false - unselected</param>
        private void SetSelected(bool selected)
        {
            Debug.Assert(_uiParent != null, "No selection container");
            byte oldState = _state;
 
            if (selected && _uiParent.IsFocused)
                _state |= SelectedFlag;
            else
                _state &= SelectedFlagComplement;
 
            if ((_state == 0) != (oldState == 0))
            {
                SetState();
            }
        }
 
        /// <summary>
        /// Removes markers
        /// </summary>
        private void RemoveHighlightMarkers()
        {
            if (_leftMarker != null)
                Children.Remove(_leftMarker);
            if (_rightMarker != null)
                Children.Remove(_rightMarker);
            _leftMarker = null;
            _rightMarker = null;
        }
 
        /// <summary>
        /// Creates a HighlightComponent + 2 Path elements that mark the ends of the highlight.
        /// Add everything to the Children.
        /// Register for TextSelection.Changed and container parent GotFocus/LostFocus events
        /// </summary>
        private void RegisterAnchor()
        {
            //register for caret events
            TextAnchor anchor = _attachedAnnotation.AttachedAnchor as TextAnchor;
            if (anchor == null)
            {
                throw new ArgumentException(SR.InvalidAttachedAnchor);
            }
 
            ITextContainer textContainer = anchor.Start.TextContainer;
            Debug.Assert(textContainer != null, "TextAnchor does not belong to a TextContainer");
 
            HighlightAnchor.AddAttachedAnnotation(_attachedAnnotation);
 
            //this will set  correct transformations on the markers
            UpdateGeometry();
 
            //host in the appropriate adorner layer
            AdornerLayer layer = AdornerLayer.GetAdornerLayer(AnnotatedElement); // note, GetAdornerLayer requires UIElement
            if (layer == null) throw new InvalidOperationException(SR.Format(SR.NoPresentationContextForGivenElement, AnnotatedElement));
 
            AdornerPresentationContext.HostComponent(layer, this, AnnotatedElement, false);
 
            //register for selection changed
            _selection = textContainer.TextSelection as ITextRange;
            if (_selection != null)
            {
                //find the container's parent as well so we can listen to mouseMove events
                _uiParent = PathNode.GetParent(textContainer.Parent) as UIElement;
 
                RegisterComponent();
 
                //register for container's parent GotFocus/LostFocus
                if (_uiParent != null)
                {
                    _uiParent.GotKeyboardFocus += new KeyboardFocusChangedEventHandler(OnContainerGotFocus);
                    _uiParent.LostKeyboardFocus += new KeyboardFocusChangedEventHandler(OnContainerLostFocus);
                    //check if it is currently selected
                    if (HighlightAnchor.IsSelected(_selection))
                        SetSelected(true);
                }
            }
        }
 
        /// <summary>
        /// Unregisters the markers, listeners etc
        /// </summary>
        private void CleanUpAnchor()
        {
            //unregister for selection changed
            if (_selection != null)
            {
                UnregisterComponent();
 
                //unregister for container focus events
                if (_uiParent != null)
                {
                    _uiParent.GotKeyboardFocus -= new KeyboardFocusChangedEventHandler(OnContainerGotFocus);
                    _uiParent.LostKeyboardFocus -= new KeyboardFocusChangedEventHandler(OnContainerLostFocus);
                }
            }
 
            //remove from host
            _presentationContext.RemoveFromHost(this, false);
 
            //clear highlight if any
            if (HighlightAnchor != null)
            {
                HighlightAnchor.RemoveAttachedAnnotation(_attachedAnnotation);
                Children.Remove(HighlightAnchor);
                HighlightAnchor = null;
                //remove the Markers as well
                RemoveHighlightMarkers();
            }
            _attachedAnnotation = null;
        }
 
        /// <summary>
        /// If active/inactive state changes - change colors and Invalidate visuals
        /// </summary>
        private void SetState()
        {
            if (_state == 0)
            {
                if (_highlightAnchor != null)
                    _highlightAnchor.Activate(false);
                MarkerBrush = new SolidColorBrush(DefaultMarkerColor);
                StrokeThickness = MarkerStrokeThickness;
                _DPHost.SetValue(StickyNoteControl.IsActiveProperty, false);
            }
            else
            {
                if (_highlightAnchor != null)
                    _highlightAnchor.Activate(true);
                MarkerBrush = new SolidColorBrush(DefaultActiveMarkerColor);
                StrokeThickness = ActiveMarkerStrokeThickness;
                _DPHost.SetValue(StickyNoteControl.IsActiveProperty, true);
            }
        }
 
        /// <summary>
        /// Creates one marker and wraps it up in an adorner
        /// </summary>
        /// <param name="geometry">marker geometry</param>
        /// <returns>The MarkerComponent</returns>
        private Path CreateMarker(Geometry geometry)
        {
            Path marker = new Path();
            marker.Data = geometry;
 
            //set activation binding
            Binding markerStroke = new Binding("MarkerBrushProperty");
            markerStroke.Source = this;
            marker.SetBinding(Path.StrokeProperty, markerStroke);
            Binding markerStrokeThickness = new Binding("StrokeThicknessProperty");
            markerStrokeThickness.Source = this;
            marker.SetBinding(Path.StrokeThicknessProperty, markerStrokeThickness);
            marker.StrokeEndLineCap = PenLineCap.Round;
            marker.StrokeStartLineCap = PenLineCap.Round;
            Children.Add(marker);
 
            return marker;
        }
 
        /// <summary>
        /// Register a MarkedHighlightComponent with the TextSelection. In order to handle
        /// highlights z-order we must have only one EventHandler
        /// for each TextSelection so we keep all the state objects along with the EventHandler
        /// in one entry of a Hashtable where the key is the selection
        /// </summary>
        private void RegisterComponent()
        {
            ComponentsRegister componentsRegister = (ComponentsRegister)_documentHandlers[_selection];
            if (componentsRegister == null)
            {
                //create a new selection entry
                componentsRegister = new ComponentsRegister(new EventHandler(OnSelectionChanged), new MouseEventHandler(OnMouseMove));
                _documentHandlers.Add(_selection, componentsRegister);
                _selection.Changed += componentsRegister.SelectionHandler;
 
                //register with the container's mousemove event
                if (_uiParent != null)
                {
                    _uiParent.MouseMove += componentsRegister.MouseMoveHandler;
                }
            }
 
            componentsRegister.Add(this);
        }
 
        /// <summary>
        /// Unregister a MarkedHighlightComponent with the Changed event of the _selection.
        /// If this is the last MarkedHighlightComponent remove the entry in the
        /// Hashtable and EventHandler.
        /// </summary>
        private void UnregisterComponent()
        {
            ComponentsRegister componentsRegister = (ComponentsRegister)_documentHandlers[_selection];
            Debug.Assert(componentsRegister != null, "The selection is not registered");
            componentsRegister.Remove(this);
            if (componentsRegister.Components.Count == 0)
            {
                _documentHandlers.Remove(_selection);
                _selection.Changed -= componentsRegister.SelectionHandler;
                if (_uiParent != null)
                {
                    _uiParent.MouseMove -= componentsRegister.MouseMoveHandler;
                }
            }
        }
 
        /// <summary>
        /// Updates the geometry of the component - this includes
        /// setting IsDirty flag of the highlight to true, and recalculating marker
        /// transformations
        /// </summary>
        private void UpdateGeometry()
        {
            if ((HighlightAnchor == null) || !(HighlightAnchor is IHighlightRange))
            {
                throw new Exception(SR.UndefinedHighlightAnchor);
            }
 
            TextAnchor anchor = ((IHighlightRange)HighlightAnchor).Range;
            Debug.Assert(anchor != null, "wrong attachedAnchor");
 
            ITextPointer start = anchor.Start.CreatePointer(LogicalDirection.Forward);
            ITextPointer end = anchor.End.CreatePointer(LogicalDirection.Backward);
 
            FlowDirection startFlowDirection = GetTextFlowDirection(start);
            FlowDirection endFlowDirection = GetTextFlowDirection(end);
 
            SetMarkerTransform(_leftMarker, start, null, startFlowDirection == FlowDirection.LeftToRight ? 1 : -1);
            SetMarkerTransform(_rightMarker, end, start, endFlowDirection == FlowDirection.LeftToRight ? -1 : 1);
 
            // Highlight anchor might have changed too - mark it Dirty.
            HighlightAnchor.IsDirty = true;
 
            IsDirty = false;
        }
 
        /// <summary>
        /// Loads a Path element that will be used as a marker at the end of SN anchor.
        /// The element is loaded from a embeded resource
        /// </summary>
        /// <returns>the Geometry</returns>
        private Geometry GetMarkerGeometry()
        {
            // Load the GeometryGroup from a resource - not working yet
            //The GeometryGroup must contain 3 geometries one for top part which is not transformed
            // one fo r the middle part which will strech with the font size and one for the bottom part which is not
            //transforemd either. Each geometry must have non-zero height.
            /*IResourceLoader loader = new ResourceLoader() as IResourceLoader;
            System.Windows.Shapes.Path path = loader.LoadResource(SNBConstants.c_MarkerPathResource) as System.Windows.Shapes.Path;
             */
 
            GeometryGroup markerGeometry = new GeometryGroup();
            markerGeometry.Children.Add(new LineGeometry(new Point(0, 1), new Point(1, 0)));
            markerGeometry.Children.Add(new LineGeometry(new Point(0, 0), new Point(0, 50)));
            markerGeometry.Children.Add(new LineGeometry(new Point(0, 0), new Point(1, 1)));
            //set pixel height of geometry
            _bodyHeight = markerGeometry.Children[1].Bounds.Height;
            _topTailHeight = markerGeometry.Children[0].Bounds.Height;
            _bottomTailHeight = markerGeometry.Children[2].Bounds.Height;
 
            return markerGeometry;
        }
 
        /// <summary>
        /// Checks if the passed position is over the text range and sets the
        /// IsMouseOver state accordingly
        /// </summary>
        /// <param name="position">the position</param>
        private void CheckPosition(ITextPointer position)
        {
            IHighlightRange range = _highlightAnchor;
            bool newState = range.Range.Contains(position);
            bool currentState = (bool)_DPHost.GetValue(StickyNoteControl.IsMouseOverAnchorProperty);
 
            if (newState != currentState)
            {
                _DPHost.SetValue(StickyNoteControl.IsMouseOverAnchorPropertyKey, newState);
            }
        }
        /// <summary>
        /// We need to track TextContainer parent GotFocus
        /// in order to activate the SN that are currently spanned by the selection
        /// </summary>
        /// <param name="sender">TextContainer parent (not used)</param>
        /// <param name="args">not used</param>
        private void OnContainerGotFocus(Object sender, KeyboardFocusChangedEventArgs args)
        {
            //check if we need to activate the SN again
            if ((HighlightAnchor != null) && HighlightAnchor.IsSelected(_selection))
                SetSelected(true);
        }
 
        /// <summary>
        /// We need to track TextContainer parent LostFocus
        /// events so we can deactivate all SN in this container
        /// </summary>
        /// <param name="sender">TextContainer parent (not used)</param>
        /// <param name="args">not used</param>
        private void OnContainerLostFocus(object sender, KeyboardFocusChangedEventArgs args)
        {
            //deactivate SN
            SetSelected(false);
        }
 
        #endregion Private methods
 
        //------------------------------------------------------
        //
        //  Private Properies
        //
        //------------------------------------------------------
        #region Private Properies
 
        /// <summary>
        /// The highlight component
        /// </summary>
        internal HighlightComponent HighlightAnchor
        {
            get
            {
                return _highlightAnchor;
            }
 
            set
            {
                _highlightAnchor = value;
                if (_highlightAnchor != null)
                {
                    //set the default colors
                    _highlightAnchor.DefaultBackground = DefaultAnchorBackground;
                    _highlightAnchor.DefaultActiveBackground = DefaultActiveAnchorBackground;
                }
            }
        }
 
        #endregion Private Properies
 
        //------------------------------------------------------
        //
        //  Static Methods
        //
        //------------------------------------------------------
        #region Static Methods
 
        /// <summary>
        /// Determine the flow direction of the character referred to by the TextPointer.
        /// If the context of the pointer is not text, we use the FlowDirection of the
        /// containing element.
        /// </summary>
        /// <param name="pointer">pointer to get flow direction for</param>
        /// <returns>positive value means LeftToRight, negative value means RightToLeft</returns>
        private static FlowDirection GetTextFlowDirection(ITextPointer pointer)
        {
            Invariant.Assert(pointer != null, "Null pointer passed.");
            Invariant.Assert(pointer.IsAtInsertionPosition, "Pointer is not an insertion position");
 
            int sign = 0;
            FlowDirection flowDirection;
 
            LogicalDirection direction = pointer.LogicalDirection;
 
            TextPointerContext currentContext = pointer.GetPointerContext(direction);
            if ((currentContext == TextPointerContext.ElementEnd || currentContext == TextPointerContext.ElementStart) &&
                !TextSchema.IsFormattingType(pointer.ParentType))
            {
                // If the current pointer (with its current direction) is at the start or end of a paragraph,
                // the next insertion position will be in a separate paragraph.  We can't determine direction
                // based on the pointer rects in that case so we use the direction of the Paragraph.
                flowDirection = (FlowDirection)pointer.GetValue(FlowDirectionProperty);
            }
            else
            {
                // We get the rects before and after the character following the current
                // pointer and determine that character's flow direction based on the x-values
                // of the rects.  Because we use direction when requesting a rect, we should
                // always get rects that are on the same line (except for the case above)
 
                // Forward gravity for leading edge
                Rect current = TextSelectionHelper.GetAnchorRectangle(pointer);
 
                // Get insertion position after the current pointer
                ITextPointer nextPointer = pointer.GetNextInsertionPosition(direction);
 
                // There may be no more insertion positions in this document
                if (nextPointer != null)
                {
                    // Backward gravity for trailing edge
                    nextPointer = nextPointer.CreatePointer(direction == LogicalDirection.Backward ? LogicalDirection.Forward : LogicalDirection.Backward);
 
                    // Special case - for pointers at the end of a paragraph
                    // Actually the pointer is the last insertion position in the paragraph and the next insertion position is the first
                    // in the next paragraph.  We handle this by detecting that the two pointers only have markup between them.  If the
                    // markup was only formatting, there would also be content (because insertion position would move past a character).
                    if (direction == LogicalDirection.Forward)
                    {
                        if (currentContext == TextPointerContext.ElementEnd && nextPointer.GetPointerContext(nextPointer.LogicalDirection) == TextPointerContext.ElementStart)
                        {
                            return (FlowDirection)pointer.GetValue(FlowDirectionProperty);
                        }
                    }
                    else
                    {
                        if (currentContext == TextPointerContext.ElementStart && nextPointer.GetPointerContext(nextPointer.LogicalDirection) == TextPointerContext.ElementEnd)
                        {
                            return (FlowDirection)pointer.GetValue(FlowDirectionProperty);
                        }
                    }
 
 
                    Rect next = TextSelectionHelper.GetAnchorRectangle(nextPointer);
 
                    // Calculate the difference in x-coordinate between the two rects
                    if (next != Rect.Empty && current != Rect.Empty)
                    {
                        sign = Math.Sign(next.Left - current.Left);
 
                        // If we are looking at the character before the current pointer,
                        // we swap the difference since "next" was actually before "current"
                        if (direction == LogicalDirection.Backward)
                        {
                            sign = -(sign);
                        }
                    }
                }
 
                // If the rects were at the same x-coordinate or we couldn't get rects
                // at all, we simply use the FlowDirection at that pointer.
                if (sign == 0)
                {
                    flowDirection = (FlowDirection)pointer.GetValue(FlowDirectionProperty);
                }
                else
                {
                    // Positive is left to right, negative is right to left
                    flowDirection = (sign > 0 ? FlowDirection.LeftToRight : FlowDirection.RightToLeft);
                }
            }
 
            return flowDirection;
        }
 
        /// <summary>
        /// We need to track selection changes in order to activate/deactivate
        /// MarkedHighlightComponent when its anchor is selected/unselected. In fact we register only one
        /// handler for all anchors on the same TextContainer because we need to process them together
        /// (see comments for _selectionStateHandlers Dictionary)
        /// </summary>
        /// <param name="sender">text selection</param>
        /// <param name="args">selection arguments</param>
        private static void OnSelectionChanged(object sender, EventArgs args)
        {
            ITextRange selection = sender as ITextRange;
            Debug.Assert(selection != null, "Unexpected sender of Changed event");
 
            //get all the anchors
            ComponentsRegister componentsRegister = (ComponentsRegister)_documentHandlers[selection];
            //Debug.Assert(stateHandler != null, "The selection is not registered");
            if (componentsRegister == null)
            {
                return;
            }
            List<MarkedHighlightComponent> components = componentsRegister.Components;
            Debug.Assert(components != null, "No SN registered for this selection");
 
            //get the state and deactivate. We do not want to activate now -
            //the activation should happen after all deactivation is done in order to
            //make sure that overlaping parts of active and unactive anchor remain active.
            bool[] active = new bool[components.Count];
            for (int i = 0; i < components.Count; i++)
            {
                Debug.Assert(components[i].HighlightAnchor != null, "Missing highlight anchor component");
                active[i] = components[i].HighlightAnchor.IsSelected(selection);
                if (!active[i])
                    components[i].SetSelected(false);
            }
 
            //now activate
            for (int i = 0; i < components.Count; i++)
            {
                if (active[i])
                    components[i].SetSelected(true);
            }
        }
 
        /// <summary>
        /// MouseMove event handler. Used to check if the mouse is over the textrange
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="args">mouse event params</param>
        private static void OnMouseMove(object sender, MouseEventArgs args)
        {
            Debug.Assert(sender != null, "undefined sender");
 
            //the sender must provide ITextView service in order to have this feature working
            IServiceProvider service = sender as IServiceProvider;
            if (service == null)
                return;
 
            ITextView textView = (ITextView)service.GetService(typeof(ITextView));
            if (textView == null || !textView.IsValid)
                return;
 
            //get mouse point
            Point currentPosition = Mouse.PrimaryDevice.GetPosition(textView.RenderScope);
            //now convert it to a text position
            ITextPointer pos = textView.GetTextPositionFromPoint(currentPosition, false);
 
            if (pos != null)
                CheckAllHighlightRanges(pos);
        }
 
        /// <summary>
        /// Scans through annotation components, registered with the TextContainer
        /// of this text pointer and checks if the mouse is over any of them
        /// </summary>
        /// <param name="pos">the text pointer to be checked</param>
        private static void CheckAllHighlightRanges(ITextPointer pos)
        {
            Debug.Assert(pos != null, "null text pointer");
 
            ITextContainer container = pos.TextContainer;
            ITextRange selection = container.TextSelection as ITextRange;
            if (selection == null)
                return;
 
            //now get the components registered with this selection
            ComponentsRegister registry = (ComponentsRegister)_documentHandlers[selection];
 
            if (registry == null)
                return;
 
            List<MarkedHighlightComponent> components = registry.Components;
            Debug.Assert((components != null) && (components.Count > 0), "invalid component registry");
 
            for (int i = 0; i < components.Count; i++)
            {
                components[i].CheckPosition(pos);
            }
        }
 
        #endregion Static Methods
 
        //-------------------------------------------------------------------------------
        //
        // Private Classes
        //
        //-------------------------------------------------------------------------------
 
        #region Private Classes
 
        /// <summary>
        /// We need this to store the EventHandler along with the MarkedHighlightComponent array.
        /// We need to have the EventHandler so we can erase the same one no metter which
        /// MarkedHighlightComponent will be removed last
        /// </summary>
        private class ComponentsRegister
        {
            public ComponentsRegister(EventHandler selectionHandler, MouseEventHandler mouseMoveHandler)
            {
                Debug.Assert(selectionHandler != null, "SelectionHandler handler can not be null");
                Debug.Assert(mouseMoveHandler != null, "MouseMoveHandler handler can not be null");
                _components = new List<MarkedHighlightComponent>();
                _selectionHandler = selectionHandler;
                _mouseMoveHandler = mouseMoveHandler;
            }
 
            /// <summary>
            /// Adds the component to the list, sorted by the start point of the attachedAnchor.
            /// Sets its TabIndex and updates the TabIndexes of all components after this one.
            /// </summary>
            /// <param name="component">Component to be added</param>
            public void Add(MarkedHighlightComponent component)
            {
                Debug.Assert(component != null, "component is null");
                Debug.Assert(_components != null, "_components are null");
 
                //if this is the first component set KeyboardNavigation mode of the host
                if (_components.Count == 0)
                {
                    UIElement host = component.PresentationContext.Host;
                    if (host != null)
                    {
                        KeyboardNavigation.SetTabNavigation(host, KeyboardNavigationMode.Local);
                        KeyboardNavigation.SetControlTabNavigation(host, KeyboardNavigationMode.Local);
                    }
                }
 
                int i = 0;
                for (; i < _components.Count; i++)
                {
                    if (Compare(_components[i], component) > 0)
                        break;
                }
 
                _components.Insert(i, component);
 
                //update TabStopIndexes
                for (; i < _components.Count; i++)
                    _components[i].SetTabIndex(i);
            }
 
            /// <summary>
            /// Removes the component from the list and updates TabIndexes of the
            /// components after removed one.
            /// </summary>
            /// <param name="component">component to be removed</param>
            public void Remove(MarkedHighlightComponent component)
            {
                Debug.Assert(component != null, "component is null");
                Debug.Assert(_components != null, "_components are null");
 
                int ind = 0;
                for (; ind < _components.Count; ind++)
                {
                    if (_components[ind] == component)
                        break;
                }
 
                if (ind < _components.Count)
                {
                    _components.RemoveAt(ind);
 
                    //update TabIndexes
                    for (; ind < _components.Count; ind++)
                        _components[ind].SetTabIndex(ind);
                }
            }
 
            public List<MarkedHighlightComponent> Components
            {
                get
                {
                    return _components;
                }
            }
 
            public EventHandler SelectionHandler
            {
                get
                {
                    return _selectionHandler;
                }
            }
 
            public MouseEventHandler MouseMoveHandler
            {
                get
                {
                    return _mouseMoveHandler;
                }
            }
 
            /// <summary>
            /// Compares the anchors of two AnnotationComponents that have a ITextRange
            /// as attachedAnchor.
            /// </summary>
            /// <param name="first">first component</param>
            /// <param name="second">second component</param>
            /// <returns> less than 0 - first is before the second; == 0 - equal attached anchors; > 0 first is after the second</returns>
            /// <remarks>The comparison is based on the comparison of the Start TPs of the two AttachedAnchors
            /// If Start TPs are equal, the end TPs are compared. The result will be 0 only if
            /// both = Start and End TPs are equal. </remarks>
            private int Compare(IAnnotationComponent first, IAnnotationComponent second)
            {
                Debug.Assert(first != null, "first component is null");
                Debug.Assert((first.AttachedAnnotations != null) && (first.AttachedAnnotations.Count > 0), "first AttachedAnchor is null");
                Debug.Assert(second != null, "second component is null");
                Debug.Assert((second.AttachedAnnotations != null) && (second.AttachedAnnotations.Count > 0), "second AttachedAnchor is null");
 
                TextAnchor firstAnchor = ((IAttachedAnnotation)first.AttachedAnnotations[0]).FullyAttachedAnchor as TextAnchor;
                TextAnchor secondAnchor = ((IAttachedAnnotation)second.AttachedAnnotations[0]).FullyAttachedAnchor as TextAnchor;
 
                Debug.Assert(firstAnchor != null, " first TextAnchor is null");
                Debug.Assert(secondAnchor != null, " second TextAnchor is null");
 
                int res = firstAnchor.Start.CompareTo(secondAnchor.Start);
                if (res == 0)
                    res = firstAnchor.End.CompareTo(secondAnchor.End);
 
                return res;
            }
 
            private List<MarkedHighlightComponent> _components;
            private EventHandler _selectionHandler;
            private MouseEventHandler _mouseMoveHandler;
        }
 
        #endregion Private Classes
 
        //------------------------------------------------------
        //
        //  Static Fields
        //
        //------------------------------------------------------
        #region Static fields
 
        //Colors of the MarkedHighlightComponent anchor
        internal static Color DefaultAnchorBackground = (Color)ColorConverter.ConvertFromString("#3380FF80");
        internal static Color DefaultMarkerColor = (Color)ColorConverter.ConvertFromString("#FF008000");
        internal static Color DefaultActiveAnchorBackground = (Color)ColorConverter.ConvertFromString("#3300FF00");
        internal static Color DefaultActiveMarkerColor = (Color)ColorConverter.ConvertFromString("#FF008000");
        internal static double MarkerStrokeThickness = 1;
        internal static double ActiveMarkerStrokeThickness = 2;
        internal static double MarkerVerticalSpace = 2;
 
        //We need to process all the anchors registered for the same selection
        // together, so we can ensure that we first deactivate every thing that needs to be
        // deactivated and then activate the segments to be activated. Otherwise it is possible
        // to live some of the overlaping segments drown as inactive even if they are active. This is used as
        // well for hadling MoseMove event in order to set IsMouseOverAnchor property
        // key is a TextSelection (which is unique per document, the value is of type ComponentRegister which contains an array of
        // MarkedHighlightComponent objects registered with this TextSelection along with the EventHandlers for the
        // TextSelection.Changed and document.MouseMove events
        private static Hashtable _documentHandlers = new Hashtable();
 
        #endregion Static fields
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
        #region Private fields
 
        private byte _state;    //active or inactive
        private HighlightComponent _highlightAnchor = null;     // the HighlightComponent which visualizes the anchor
        private double _bodyHeight;                             // the height of the middle part of the anchor
        private double _bottomTailHeight;                       // the height of the bottom part of the bracket
        private double _topTailHeight;                          // the height of the top part of the bracket
        private Path _leftMarker;                               //The path element to visualize left bracket
        private Path _rightMarker;                              //The path element to visualize right bracket
        private DependencyObject _DPHost;                       //The DO that hosts IsActive and IsMouseOverAnchor DPs
        //defines the meaning of the bits in the _state member
        private const byte FocusFlag = 0x1;
        private const byte FocusFlagComplement = 0x7E;
        private const byte SelectedFlag = 0x2;
        private const byte SelectedFlagComplement = 0x7D;
 
        private IAttachedAnnotation _attachedAnnotation;
        private PresentationContext _presentationContext;
        private bool _isDirty = true; //shows if the annotation is in sync with the content of the AnnotatedElement
 
 
        //event sources
        private ITextRange _selection;          // we need to listen to selection.Changed event
        private UIElement _uiParent = null;     // the TextContainer parent. We need to handle GotFocus/LostFocus events
 
        #endregion Private fields
    }
}