MarkedHighlightComponent.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / MS / Internal / Annotations / Component / MarkedHighlightComponent.cs / 1305600 / MarkedHighlightComponent.cs

                            //---------------------------------------------------------------------------- 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// 
// 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 http://tabletpc/longhorn/Specs/StickyNoteControlSpec.mht
//
// History: 
//  05/26/2005 - ssimova created
// 
//--------------------------------------------------------------------------- 

using System; 
using System.ComponentModel;
using System.Windows.Threading;
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.Text;
using System.Collections;
using System.Collections.Generic;
using System.Xml; 
using System.Windows.Input;
using System.Diagnostics;                           // Assert 
using MS.Internal.Annotations.Anchoring; 
using MS.Utility;
 

namespace MS.Internal.Annotations.Component
{
    ///  
    /// 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
    ///  
    internal sealed class MarkedHighlightComponent : Canvas, IAnnotationComponent
    {
        //-----------------------------------------------------
        // 
        //  ctors
        // 
        //----------------------------------------------------- 
        #region ctors
 
        /// 
        /// Creates a new component
        /// 
        /// owning component type  
        /// Dependency object to host IsActive and IsMouseOverAnchor DepenedencyProperties
        /// if host is null - set them on MarkedHighlightComponent itself 
        public MarkedHighlightComponent(XmlQualifiedName type, DependencyObject host) 
            : base()
        { 
            if (type == null)
            {
                throw new ArgumentNullException("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 

        /// 
        /// Return a copy of the list of IAttachedAnnotations held by this component
        ///  
        public IList AttachedAnnotations
        { 
            get 
            {
                ArrayList list = new ArrayList(); 
                if (_attachedAnnotation != null)
                {
                    list.Add(_attachedAnnotation);
                } 
                return list;
            } 
 
        }
 
        /// 
        /// Sets and gets the context this annotation component is hosted in.
        /// 
        /// Context this annotation component is hosted in 
        public PresentationContext PresentationContext
        { 
            get 
            {
                return _presentationContext; 
            }

            set
            { 
                _presentationContext = value;
 
            } 
        }
 
        /// 
        /// Sets and gets the Z-order of this component. NOP -
        /// Highlight does not have Z-order
        ///  
        /// Context this annotation component is hosted in
        public int ZOrder 
        { 
            get
            { 
                return -1;
            }

            set 
            {
            } 
        } 

        ///  
        /// Returns the one element the annotation component is attached to.
        /// 
        /// 
        public UIElement AnnotatedElement 
        {
            get 
            { 
                return _attachedAnnotation != null ? (_attachedAnnotation.Parent as UIElement) : null;
            } 
        }

        /// 
        /// 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 
        ///  
        public bool IsDirty
        { 
            get
            {
                return _isDirty;
            } 

            set 
            { 
                _isDirty = value;
                if (value) 
                    UpdateGeometry();
            }
        }
 
        #endregion Public Properties
 
        #region Public Methods 

        ///  
        /// 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
        ///  
        /// Transform to the AnnotatedElement.
        /// Transform to the annotation component 
        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.Get(SRID.InvalidAttachedAnnotation)); 

            HighlightAnchor.GetDesiredTransform(transform); 
 
            return transform;
        } 

        /// 
        /// Add an attached annotation to the component. The attached anchor will be used to add
        /// a highlight to the appropriate TextContainer 
        /// 
        /// The attached annotation to be added to the component 
        public void AddAttachedAnnotation(IAttachedAnnotation attachedAnnotation) 
        {
            if (_attachedAnnotation != null) 
            {
                throw new ArgumentException(SR.Get(SRID.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);
 
        }
 
        ///  
        /// Remove an attached annotation from the component
        ///  
        /// The attached annotation to be removed from the component
        public void RemoveAttachedAnnotation(IAttachedAnnotation attachedAnnotation)
        {
            if (attachedAnnotation == null) 
            {
                throw new ArgumentNullException("attachedAnnotation"); 
            } 

            if (attachedAnnotation != _attachedAnnotation) 
            {
                throw new ArgumentException(SR.Get(SRID.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);
 
        } 

        ///  
        /// Modify an attached annotation that is held by the component
        /// 
        /// The attached annotation after modification
        /// The attached anchor previously associated with the attached annotation. 
        /// The previous attachment level of the attached annotation.
        public void ModifyAttachedAnnotation(IAttachedAnnotation attachedAnnotation, object previousAttachedAnchor, AttachmentLevel previousAttachmentLevel) 
        { 
            throw new NotSupportedException(SR.Get(SRID.NotSupported));
        } 

        #endregion Public Methods

        #endregion IAnnotationComponent implementation 

        //------------------------------------------------------ 
        // 
        //  Additional Public Properties
        // 
        //------------------------------------------------------
        #region Additional Public Properties

        ///  
        /// Used to [....] with another component focus (like StickyNote)
        ///  
        public bool Focused 
        {
            set 
            {
                byte oldState = _state;

                if (value) 
                    _state |= FocusFlag;
                else 
                    _state &= FocusFlagComplement; 

                if ((_state == 0) != (oldState == 0)) 
                {
                    SetState();
                }
            } 
        }
 
        ///  
        /// Anchor brackets color
        ///  
        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));

        ///  
        /// Anchor brackets thickness
        ///  
        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 
        // 
        //------------------------------------------------------
        ///  
        /// Set the TabIndex for the hosting element (StickyNote or other)
        /// 
        /// tab index to be set
        internal void SetTabIndex(int index) 
        {
            if (_DPHost != null) 
                KeyboardNavigation.SetTabIndex(_DPHost, index); 

        } 
        #endregion Internal Methods

        //-----------------------------------------------------
        // 
        //  Private Methods
        // 
        //----------------------------------------------------- 
        #region Private methods
 
        /// 
        /// Marker transformation is calculated based on anchor position
        /// 
        /// marker Path element 
        /// marker anchor
        /// 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 
        /// x-scale parameter of the ScaleTransform multiplier. if -1 theis will get the right bracket
        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);
                } 
            } 
        }
 
        /// 
        /// Handles selected/unselected changes
        /// 
        /// true - selected/false - unselected 
        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();
            } 
        }

        /// 
        /// Removes markers 
        /// 
        private void RemoveHighlightMarkers() 
        { 
            if (_leftMarker != null)
                Children.Remove(_leftMarker); 
            if (_rightMarker != null)
                Children.Remove(_rightMarker);
            _leftMarker = null;
            _rightMarker = null; 
        }
 
        ///  
        /// 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
        /// 
        private void RegisterAnchor()
        { 
            //register for caret events
            TextAnchor anchor = _attachedAnnotation.AttachedAnchor as TextAnchor; 
            if (anchor == null) 
            {
                throw new ArgumentException(SR.Get(SRID.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.Get(SRID.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); 
                } 
            }
 
        }

        /// 
        /// Unregisters the markers, listeners etc 
        /// 
        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; 

        } 

        /// 
        /// If active/inactive state changes - change colors and Invalidate visuals
        ///  
        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);
            } 
        }
 
        ///  
        /// Creates one marker and wraps it up in an adorner
        ///  
        /// marker geometry
        /// The MarkerComponent
        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;

        } 

        ///  
        /// 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
        /// 
        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);

        } 

        ///  
        /// Unregister a MarkedHighlightComponent with the Changed event of the _selection. 
        /// If this is the last MarkedHighlightComponent remove the entry in the
        /// Hashtable and EventHandler. 
        /// 
        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; 
                }
            } 
        } 

        ///  
        /// Updates the geometry of the component - this includes
        /// setting IsDirty flag of the highlight to true, and recalculating marker
        /// transformations
        ///  
        private void UpdateGeometry()
        { 
            if ((HighlightAnchor == null) || !(HighlightAnchor is IHighlightRange)) 
            {
                throw new Exception(SR.Get(SRID.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; 
        }

        /// 
        /// 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
        ///  
        /// the Geometry 
        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; 

        }

        ///  
        /// Checks if the passed position is over the text range and sets the
        /// IsMouseOver state accordingly 
        ///  
        /// the position
        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);
            } 
        }
        /// 
        /// We need to track TextContainer parent GotFocus
        /// in order to activate the SN that are currently spanned by the selection 
        /// 
        /// TextContainer parent (not used) 
        /// not used 
        private void OnContainerGotFocus(Object sender, KeyboardFocusChangedEventArgs args)
        { 
            //check if we need to activate the SN again
            if ((HighlightAnchor != null) && HighlightAnchor.IsSelected(_selection))
                SetSelected(true);
        } 

        ///  
        /// We need to track TextContainer parent LostFocus 
        /// events so we can deactivate all SN in this container
        ///  
        /// TextContainer parent (not used)
        /// not used
        private void OnContainerLostFocus(object sender, KeyboardFocusChangedEventArgs args)
        { 
            //deactivate SN
            SetSelected(false); 
        } 

        #endregion Private methods 

        //-----------------------------------------------------
        //
        //  Private Properies 
        //
        //------------------------------------------------------ 
        #region Private Properies 

        ///  
        /// The highlight component
        /// 
        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

        ///  
        /// 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. 
        /// 
        /// pointer to get flow direction for 
        /// positive value means LeftToRight, negative value means RightToLeft
        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;
        } 

        /// 
        /// 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) 
        ///  
        /// text selection
        /// selection arguments 
        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 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);
            } 
        } 

        ///  
        /// MouseMove event handler. Used to check if the mouse is over the textrange
        /// 
        /// sender
        /// mouse event params 
        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); 
        } 

        ///  
        /// Scans through annotation components, registered with the TextContainer
        /// of this text pointer and checks if the mouse is over any of them
        /// 
        /// the text pointer to be checked 
        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 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

        /// 
        /// 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 
        ///  
        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();
                _selectionHandler = selectionHandler; 
                _mouseMoveHandler = mouseMoveHandler; 
            }
 
            /// 
            /// 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.
            ///  
            /// Component to be added
            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); 
 
            }
 
            /// 
            /// Removes the component from the list and updates TabIndexes of the
            /// components after removed one.
            ///  
            /// component to be removed
            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 Components
            {
                get
                { 
                    return _components;
                } 
            } 

            public EventHandler SelectionHandler 
            {
                get
                {
                    return _selectionHandler; 
                }
            } 
 
            public MouseEventHandler MouseMoveHandler
            { 
                get
                {
                    return _mouseMoveHandler;
                } 
            }
 
            ///  
            /// Compares the anchors of two AnnotationComponents that have a ITextRange
            /// as attachedAnchor. 
            /// 
            /// first component
            /// second component
            ///  less than 0 - first is before the second; == 0 - equal attached anchors; > 0 first is after the second 
            /// 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.  
            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 _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 [....] 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 

    } 
} 

 


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

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