Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Framework / MS / Internal / Annotations / Component / MarkedHighlightComponent.cs / 1 / 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.NormalTraceEvent(EventTraceGuidId.ADDATTACHEDMHGUID, EventType.StartEvent); //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.NormalTraceEvent(EventTraceGuidId.ADDATTACHEDMHGUID, EventType.EndEvent); } ////// 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.NormalTraceEvent(EventTraceGuidId.REMOVEATTACHEDMHGUID, EventType.StartEvent); //unregister markers from the adorner layer aND LISTENERS CleanUpAnchor(); _attachedAnnotation = null; //fire trace event EventTrace.NormalTraceEvent(EventTraceGuidId.REMOVEATTACHEDMHGUID, EventType.EndEvent); } ////// 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; } Listcomponents = 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; Listcomponents = 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 ListComponents { 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. //---------------------------------------------------------------------------- // // // 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.NormalTraceEvent(EventTraceGuidId.ADDATTACHEDMHGUID, EventType.StartEvent); //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.NormalTraceEvent(EventTraceGuidId.ADDATTACHEDMHGUID, EventType.EndEvent); } ////// 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.NormalTraceEvent(EventTraceGuidId.REMOVEATTACHEDMHGUID, EventType.StartEvent); //unregister markers from the adorner layer aND LISTENERS CleanUpAnchor(); _attachedAnnotation = null; //fire trace event EventTrace.NormalTraceEvent(EventTraceGuidId.REMOVEATTACHEDMHGUID, EventType.EndEvent); } ////// 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; } Listcomponents = 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; Listcomponents = 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 ListComponents { 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

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- StylusButton.cs
- PeerTransportBindingElement.cs
- FilteredAttributeCollection.cs
- XmlUtf8RawTextWriter.cs
- FlowLayoutSettings.cs
- GeometryHitTestParameters.cs
- RawAppCommandInputReport.cs
- ChannelDemuxer.cs
- DataRowChangeEvent.cs
- GlyphRunDrawing.cs
- MatcherBuilder.cs
- SqlNodeAnnotation.cs
- DataKeyCollection.cs
- DataBinder.cs
- SessionStateUtil.cs
- Cursors.cs
- FormattedTextSymbols.cs
- RawKeyboardInputReport.cs
- UnionCodeGroup.cs
- RangeBaseAutomationPeer.cs
- DataSourceCache.cs
- GenericTypeParameterBuilder.cs
- IteratorFilter.cs
- LocatorBase.cs
- versioninfo.cs
- CatalogPartChrome.cs
- DeclarativeCatalogPart.cs
- EnvelopedSignatureTransform.cs
- PropertyGridView.cs
- ICspAsymmetricAlgorithm.cs
- Font.cs
- CalloutQueueItem.cs
- SafeIUnknown.cs
- ADRoleFactory.cs
- StorageTypeMapping.cs
- ContextActivityUtils.cs
- SqlClientFactory.cs
- WindowsImpersonationContext.cs
- FilteredAttributeCollection.cs
- SuppressIldasmAttribute.cs
- SecurityImpersonationBehavior.cs
- XsltArgumentList.cs
- DrawingContextDrawingContextWalker.cs
- NullableConverter.cs
- Rect.cs
- MimeTypeAttribute.cs
- InvalidPropValue.cs
- SafeTokenHandle.cs
- CheckBoxAutomationPeer.cs
- AmbiguousMatchException.cs
- StoreAnnotationsMap.cs
- DataGridRowsPresenter.cs
- ZoneIdentityPermission.cs
- JsonReader.cs
- SetMemberBinder.cs
- HelpInfo.cs
- ProtocolState.cs
- SimplePropertyEntry.cs
- SessionSymmetricTransportSecurityProtocolFactory.cs
- PermissionRequestEvidence.cs
- DoubleAnimationUsingPath.cs
- ListContractAdapter.cs
- PropertyDescriptor.cs
- PersonalizablePropertyEntry.cs
- NativeRightsManagementAPIsStructures.cs
- ThicknessAnimationBase.cs
- SqlConnectionPoolProviderInfo.cs
- XmlILModule.cs
- TimelineGroup.cs
- RelationshipDetailsRow.cs
- MappingMetadataHelper.cs
- InputLanguage.cs
- AnimationClockResource.cs
- PropertyOrder.cs
- ShutDownListener.cs
- ActionMessageFilter.cs
- PlanCompilerUtil.cs
- InitializationEventAttribute.cs
- ServiceTimeoutsBehavior.cs
- ItemsControlAutomationPeer.cs
- compensatingcollection.cs
- EndPoint.cs
- ListItemCollection.cs
- CollectionContainer.cs
- IssuedTokenClientCredential.cs
- ApplicationSecurityInfo.cs
- FileCodeGroup.cs
- TraceProvider.cs
- DataGridViewCellErrorTextNeededEventArgs.cs
- HwndHost.cs
- DataContractSerializerElement.cs
- UniqueIdentifierService.cs
- InnerItemCollectionView.cs
- SharedUtils.cs
- SkipStoryboardToFill.cs
- SqlUDTStorage.cs
- CompareValidator.cs
- DataControlPagerLinkButton.cs
- ElementNotEnabledException.cs
- AnimatedTypeHelpers.cs