File: MS\Internal\Annotations\Component\AdornerPresentationContext.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
// Description:
//      AdornerPresentationContext knows that annotation components are wrapped 
//      in an AnnotationAdorner and hosted in the AdornerLayer. Note, implementation-wise 
//      a new PresentationContext is created for every annotation component. Executing 
//      operations on a presentation context for a different annotation component 
//      (located in the same adorner layer) works, but is slower than using the 
//      presentation context stored in the annotation component.
//
 
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
 
namespace MS.Internal.Annotations.Component
{
    /// <summary>
    /// AdornerPresentationContext knows that annotation components are wrapped in an AnnotationAdorner and hosted in the AdornerLayer.
    /// Note,  implementation-wise a new PresentationContext is created for every annotation component. Executing operations on a presentation context
    /// for a different annotation component (located in the same adorner layer) works, but is slower than using the presentation context stored in the
    /// annotation component.
    /// </summary>
    internal sealed class AdornerPresentationContext : PresentationContext
    {
        #region Constructors
 
        /// <summary>
        /// Create an initialized instance of an AdornerPresentationContext. Set the presentation context of the 
        /// component wrapped in the adorner
        /// </summary>
        /// <param name="adornerLayer">AdornerLayer this presentation context is on, must not be null</param>
        /// <param name="adorner">AnnotationAdorner that wraps the annotation component.  Will be null in case of creating enclosing context</param>
        private AdornerPresentationContext(AdornerLayer adornerLayer, AnnotationAdorner adorner)
        {
            ArgumentNullException.ThrowIfNull(adornerLayer);
 
            _adornerLayer = adornerLayer;
            if (adorner != null)
            {
                if (adorner.AnnotationComponent == null)
                    throw new ArgumentNullException("annotation component");
                if (adorner.AnnotationComponent.PresentationContext != null)
                    throw new InvalidOperationException(SR.Format(SR.ComponentAlreadyInPresentationContext, adorner.AnnotationComponent));
                _annotationAdorner = adorner;
            }
        }
 
        #endregion Constructors
 
        #region Static Methods
 
        /// <summary>
        /// Host a component in an adorner layer.
        /// Wrap the component in an annotation adorner, add that to the adorner layer, create and set presentation context and invalidate to pick up styles.
        /// Note, this is called from two places: (1) component manager to host choosen annotation component, and (2) presentation context when component
        /// adds additional IAnnotationComponent.
        /// </summary>
        /// <param name="adornerLayer">Adorner layer the component is hosted in</param>
        /// <param name="component">Component that is being hosted</param>
        /// <param name="annotatedElement">element being annotated</param>
        /// <param name="reorder">if true - put the component on top and calculate its z-order</param>
        internal static void HostComponent(AdornerLayer adornerLayer, IAnnotationComponent component, UIElement annotatedElement, bool reorder)
        {
            AnnotationAdorner newAdorner = new AnnotationAdorner(component, annotatedElement);
            // Create the context for the layer and adorner, make sure the adorner's component has its context.
            newAdorner.AnnotationComponent.PresentationContext = new AdornerPresentationContext(adornerLayer, newAdorner);
 
            int level = GetComponentLevel(component);
 
            if (reorder)
            {
                component.ZOrder = GetNextZOrder(adornerLayer, level);
            }
 
            adornerLayer.Add(newAdorner, ComponentToAdorner(component.ZOrder, level));
        }
 
 
        /// <summary>
        /// Sets the Z-order level of an annotation Component type
        /// </summary>
        /// <param name="type">the component type</param>
        /// <param name="level">level - 0 means on top of all other types, bigger number means
        /// lower level</param>
        /// <remarks> ZLevel defines the Z-order disposition of this component type according to other
        /// component types in the same adorner layer. Components with lower ZLevel will be instantiated 
        /// on top of the components with Higher ZLevel.
        /// The Z-order of all the components with the same ZLevel is defined by the value of 
        /// IAnnotationComponent.ZOrder property with zero meaning the component is on top of all other
        /// inside the same level. ZOrder property can be changed by invoking
        /// BringToTop method. This will move the component to the top of its priority group. If there are other
        /// components with higher priority they will still be on top of that component. If more than
        /// one component type have the same ZLevel that means they all can stay on top of each other.
        /// Setting IAnnotationComponent.ZOrder must be invoked only by the PresentationContext
        /// when the Z-order changes. It can not be set by application in v1.</remarks>
        internal static void SetTypeZLevel(Type type, int level)
        {
            Invariant.Assert(level >= 0, "level is < 0");
            Invariant.Assert(type != null, "type is null");
 
            s_ZLevel[type] = level;
        }
 
        /// <summary>
        /// the allowed Z-order values range for this level. 
        /// Used to define minimal Z-order value for types that are supposed to live above TextSelection
        /// which has a fixed Z-order value
        /// </summary>
        /// <param name="level">the Z-order level</param>
        /// <param name="min">min Z-order value for this level</param>
        /// <param name="max">max Z-order value for this level</param>
        internal static void SetZLevelRange(int level, int min, int max)
        {
            if (!s_ZRanges.ContainsKey(level))
            {
                s_ZRanges.Add(level, new ZRange(min, max));
            }
        }
 
        #endregion Static Methods
 
 
        #region Public Properties
 
        /// <summary>
        /// Returns the adorner layer which acts as a host for annotation components managed by the annotation component manager
        /// </summary>
        /// <value>UIElement for the adorner layer</value>
        public override UIElement Host { get { return _adornerLayer; } }
 
        /// <summary>
        /// Get the enclosing presentation context.
        /// </summary>
        /// <value>Enclosing PresentationContext or null if there is none</value>
        public override PresentationContext EnclosingContext
        {
            get
            {
                Visual parent = VisualTreeHelper.GetParent(_adornerLayer) as Visual;
                if (parent == null) return null;
 
                AdornerLayer parentLayer = AdornerLayer.GetAdornerLayer((UIElement)parent);
                if (parentLayer == null) return null;
 
                PresentationContext p = new AdornerPresentationContext(parentLayer, null);
 
                return p;
            }
        }
 
        #endregion Public Properties
 
        #region Public Methods
 
        /// <summary>
        /// Add the component to the adornerlayer of this presentation context.
        /// Create a new presentation context which includes the wrapped annotation adorner and the adornerlayer.
        /// Assign new presentation context into the component.
        /// </summary>
        /// <param name="component">Component to add to host</param>
        public override void AddToHost(IAnnotationComponent component)
        {
            ArgumentNullException.ThrowIfNull(component);
 
            AdornerPresentationContext.HostComponent(_adornerLayer, component, component.AnnotatedElement, false);
        }
 
        /// <summary>
        /// Remove annotation component from host;  in our case: respective annotation adorner from adornerLayer.
        /// If this presentation context does not contain the component search the adorner layer.
        /// Null out the presentation context of the component and set the local annotationAdorner to null if necessary,
        /// ask the annotation adorner to remove all visual children.
        /// </summary>
        /// <param name="component">Component to remove from host</param>
        /// <param name="reorder">if true - recalculate z-order</param>
        public override void RemoveFromHost(IAnnotationComponent component, bool reorder)
        {
            ArgumentNullException.ThrowIfNull(component);
 
            if (IsInternalComponent(component))
            {
                _annotationAdorner.AnnotationComponent.PresentationContext = null;
                _adornerLayer.Remove(_annotationAdorner);
                _annotationAdorner.RemoveChildren();
                _annotationAdorner = null;
            }
            else
            {// need to find annotation adorner in layer, remove it and do house-keeping
                AnnotationAdorner foundAdorner = this.FindAnnotationAdorner(component);
 
                if (foundAdorner == null) throw new InvalidOperationException(SR.Format(SR.ComponentNotInPresentationContext, component));
 
                _adornerLayer.Remove(foundAdorner);
                foundAdorner.RemoveChildren();
 
                // now get rid of reference from presentation context of annotation component to annotation adorner
                AdornerPresentationContext p = component.PresentationContext as AdornerPresentationContext;
 
                if (p != null) p.ResetInternalAnnotationAdorner();
 
                // finally get rid of reference from annotation component to presentation context
                component.PresentationContext = null;
            }
        }
 
        /// <summary>
        /// Invalidate the transform for this adorner. called when adorner inside changed aspects of the transform.
        /// This might go away if InvalidateMeasure works 
        /// (unclear if Peter means this should work on the adorner or even one down on the annotation component itself)
        /// </summary>
        /// <param name="component">Component to invalidate transform for</param>
        public override void InvalidateTransform(IAnnotationComponent component)
        {
            AnnotationAdorner adorner = GetAnnotationAdorner(component);
            adorner.InvalidateTransform();
        }
 
        /// <summary>
        /// Sets a component on top of its ZLevel
        /// </summary>
        /// <param name="component">Component to change z-order of</param>
        public override void BringToFront(IAnnotationComponent component)
        {
            AnnotationAdorner adorner = GetAnnotationAdorner(component);
            int level = GetComponentLevel(component);
            int nextLevel = GetNextZOrder(_adornerLayer, level);
 
            // Only change the ZOrder if its not already on the top
            if (nextLevel != component.ZOrder + 1)
            {
                component.ZOrder = nextLevel;
                _adornerLayer.SetAdornerZOrder(adorner, ComponentToAdorner(component.ZOrder, level));
            }
        }
 
        /// <summary>
        /// Sets a component on bttom of its ZLevel
        /// </summary>
        /// <param name="component">Component to change z-order of</param>
        public override void SendToBack(IAnnotationComponent component)
        {
            AnnotationAdorner adorner = GetAnnotationAdorner(component);
            int level = GetComponentLevel(component);
 
            // Only change the ZOrder if its not already on the bottom
            if (0 != component.ZOrder)
            {
                component.ZOrder = 0;
                UpdateComponentZOrder(component);
            }
        }
 
        /// <summary>
        ///     Determines if the passed in object is equal to this object.
        ///     Two AdornerPresentationContexts will be equal if they both have the same adorner layer.
        /// </summary>
        /// <param name="o">The object to compare with.</param>
        /// <returns>True if the objects are equal. False otherwise.</returns>
        public override bool Equals(object o)
        {
            AdornerPresentationContext p = o as AdornerPresentationContext;
 
            if (p != null)
            {
                return (p._adornerLayer == this._adornerLayer);
            }
 
            return false;
        }
 
        /// <summary>
        /// overload operator for ==, to be same as Equal implementation.
        /// </summary>
        /// <param name="left">AdornerPresentationContext to compare</param>
        /// <param name="right">AdornerPresentationContext to compare</param>
        /// <returns></returns>
        public static bool operator ==(AdornerPresentationContext left, AdornerPresentationContext right)
        {
            if ((object)left == null)
                return (object)right == null;
 
            return left.Equals(right);
        }
 
        /// <summary>
        /// overload operator for !=, to go along with definition for ==
        /// </summary>
        /// <param name="c1">AdornerPresentationContext to compare</param>
        /// <param name="c2">AdornerPresentationContext to compare</param>
        /// <returns></returns>
        public static bool operator !=(AdornerPresentationContext c1, AdornerPresentationContext c2)
        {
            return !(c1 == c2);
        }
 
        /// <summary>
        ///     Delegate hash to adorner layer
        /// </summary>
        public override int GetHashCode()
        {
            return (int)this._adornerLayer.GetHashCode();
        }
 
 
        /// <summary>
        /// Updates the ZOrder of the input component and on all components with the same ZLevel 
        /// that have same or bigger Z-order as the  input component on a given adorner layer
        /// </summary>
        /// <param name="component">the component</param>
        public void UpdateComponentZOrder(IAnnotationComponent component)
        {
            Invariant.Assert(component != null, "null component");
 
            //check Z-order range for this level
            int level = GetComponentLevel(component);
            //get the component's adorner
            AnnotationAdorner adorner = FindAnnotationAdorner(component);
            if (adorner == null)
                return;
 
            //set the adorner z-order
            _adornerLayer.SetAdornerZOrder(adorner, ComponentToAdorner(component.ZOrder, level));
 
            List<AnnotationAdorner> adorners = GetTopAnnotationAdorners(level, component);
            if (adorners == null)
                return;
 
            int lastZOrder = component.ZOrder + 1;
            foreach (AnnotationAdorner topAdorner in adorners)
            {
                topAdorner.AnnotationComponent.ZOrder = lastZOrder;
                _adornerLayer.SetAdornerZOrder(topAdorner, ComponentToAdorner(lastZOrder, level));
                lastZOrder++;
            }
        }
 
        #endregion Public Methods
 
        #region Private Methods
 
        /// <summary>
        /// Reset the annotation adorner to null.  This is needed for reset of found adorner in method RemoveFromHost
        /// </summary>
        private void ResetInternalAnnotationAdorner()
        {
            _annotationAdorner = null;
        }
 
        /// <summary>
        /// Return true if the given annotation component is the one this presentation context is on.
        /// </summary>
        /// <param name="component">The component that might be referred to by this presentation context</param>
        /// <returns>True if the component is internal</returns>
        private bool IsInternalComponent(IAnnotationComponent component)
        {
            return _annotationAdorner != null && component == _annotationAdorner.AnnotationComponent;
        }
 
        /// <summary>
        /// Return the annotation adorner for the given annotation component.
        /// will not look at local annotation adorner, will always iterate through annotation adorners of adorner layer.
        /// Return null if none can be found.
        /// </summary>
        /// <param name="component">The component that is wrapped by an annotation adorner</param>
        /// <returns>The annotation adorner that wraps the component in the adorner layer associated with this presentation context</returns>
        private AnnotationAdorner FindAnnotationAdorner(IAnnotationComponent component)
        {
            if (_adornerLayer == null) return null;
 
            foreach (Adorner adorner in _adornerLayer.GetAdorners(component.AnnotatedElement))
            {
                AnnotationAdorner annotationAdorner = adorner as AnnotationAdorner;
 
                if (annotationAdorner != null && annotationAdorner.AnnotationComponent == component) return annotationAdorner;
            }
 
            return null;
        }
 
        /// <summary>
        /// Finds all AnnotationAddorners from particular Z-level that have the same or bigger z-order as the component
        /// </summary>
        /// <param name="level">the ZLevel of interest</param>
        /// <param name="component">the component</param>
        /// <returns>the AnnotationAdorner children</returns>
        private List<AnnotationAdorner> GetTopAnnotationAdorners(int level, IAnnotationComponent component)
        {
            List<AnnotationAdorner> res = new List<AnnotationAdorner>();
 
            int count = VisualTreeHelper.GetChildrenCount(_adornerLayer);
            if (count == 0)
                return res;
 
            for (int i = 0; i < count; i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(_adornerLayer, i);
                AnnotationAdorner adorner = child as AnnotationAdorner;
                if (adorner != null)
                {
                    IAnnotationComponent childComponent = adorner.AnnotationComponent;
                    if ((childComponent != component) &&
                       (GetComponentLevel(childComponent) == level) &&
                       (childComponent.ZOrder >= component.ZOrder))
                    {
                        AddAdorner(res, adorner);
                    }
                }
            }
 
            return res;
        }
 
        /// <summary>
        /// Inserts an adorner after the last adorner with ZOrder less or equal of the input one
        /// </summary>
        /// <param name="adorners">adorners list</param>
        /// <param name="adorner">the new adorner</param>
        /// <remarks>In most cases the AnnotationAdorners are already orderd so we expect that the new one
        /// will be added at the end of the list. That is why we start scaning from the end.</remarks>
        private void AddAdorner(List<AnnotationAdorner> adorners, AnnotationAdorner adorner)
        {
            Debug.Assert((adorners != null) && (adorner != null), "null adorners list or adorner");
 
            int index = 0;
            if (adorners.Count > 0)
            {
                for (index = adorners.Count; index > 0; index--)
                {
                    if (adorners[index - 1].AnnotationComponent.ZOrder <= adorner.AnnotationComponent.ZOrder)
                        break;
                }
            }
 
            adorners.Insert(index, adorner);
        }
 
        /// <summary>
        /// Gets the next free Z-order value for the components in this level
        /// </summary>
        /// <param name="adornerLayer">adorner layer</param>
        /// <param name="level">Z-level</param>
        /// <returns>next free Z-order value</returns>
        private static int GetNextZOrder(AdornerLayer adornerLayer, int level)
        {
            Invariant.Assert(adornerLayer != null, "null adornerLayer");
 
            int res = 0;
 
            int count = VisualTreeHelper.GetChildrenCount(adornerLayer);
            if (count == 0)
                return res;
 
            for (int i = 0; i < count; i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(adornerLayer, i);
                AnnotationAdorner adorner = child as AnnotationAdorner;
                if (adorner != null)
                {
                    if ((GetComponentLevel(adorner.AnnotationComponent) == level) &&
                        (adorner.AnnotationComponent.ZOrder >= res))
                    {
                        res = adorner.AnnotationComponent.ZOrder + 1;
                    }
                }
            }
 
            return res;
        }
 
        /// <summary>
        /// Finds the correct AdornerLayer where a component lives
        /// </summary>
        /// <param name="component">the component</param>
        /// <returns></returns>
        private AnnotationAdorner GetAnnotationAdorner(IAnnotationComponent component)
        {
            ArgumentNullException.ThrowIfNull(component);
 
            //find the adornerLayer
            AnnotationAdorner adorner = _annotationAdorner;
            if (!this.IsInternalComponent(component))
            {
                adorner = this.FindAnnotationAdorner(component);
 
                if (adorner == null) throw new InvalidOperationException(SR.Format(SR.ComponentNotInPresentationContext, component));
            }
 
            return adorner;
        }
 
        /// <summary>
        /// Returns the component ZLevel
        /// </summary>
        /// <param name="component">component</param>
        /// <returns>ZLevel</returns>
        private static int GetComponentLevel(IAnnotationComponent component)
        {
            Type type = component.GetType();
 
            return s_ZLevel.TryGetValue(type, out int value) ? value : 0;
        }
 
        /// <summary>
        /// Converts the component z-order to the Adorner z-order. The adorner z-order
        /// is shifted by the minimal value for this level. Also there is a restriction 
        /// about the maximal possible value for this level too
        /// </summary>
        /// <param name="zOrder">component z-order</param>
        /// <param name="level">component z-level</param>
        /// <returns></returns>
        private static int ComponentToAdorner(int zOrder, int level)
        {
            int res = zOrder;
 
            if (s_ZRanges.TryGetValue(level, out ZRange range))
            {
                //adjust the Z-order (shift it with the minimal value for this range)
                //that way the component does need to know the range for its type that is 
                // set by the application. It always sets the z-order as it starts from 0
                res += range.Min;
 
                if (res < range.Min)
                    res = range.Min;
 
                if (res > range.Max)
                    res = range.Max;
            }
 
            return res;
        }
 
        #endregion Private Methods
 
        #region Private Fields
 
        /// <summary>
        /// The annotation adorner which wraps the annotation component this presentation context is optimized for.
        /// Can be null.
        /// </summary>
        private AnnotationAdorner _annotationAdorner = null;
 
        /// <summary>
        /// The adornerLayer which contains the annotation component.  Basically what the presentation hides.
        /// </summary>
        private readonly AdornerLayer _adornerLayer;
 
        /// <summary>
        /// The dictionary holds the priority level for each Component type as defined by the application
        /// </summary>
        private static readonly Dictionary<Type, int> s_ZLevel = new();
 
        /// <summary>
        /// The ZRanges for the ZLevels. 
        /// </summary>
        private static readonly Dictionary<int, ZRange> s_ZRanges = new();
 
        #endregion Private Fields
 
        #region Private classes
 
        /// <summary>
        /// This is to control the relationships with TextSelection which lives in the same
        /// AdornerLayer. Will be removed when more flexible Z-ordering mechanism is available
        /// </summary>
        private readonly struct ZRange
        {
            public ZRange(int min, int max)
            {
                // Swap values if needed
                if (min > max)
                {
                    int num = min;
                    min = max;
                    max = num;
                }
 
                Min = min;
                Max = max;
            }
 
            public int Min { get; }
            public int Max { get; }
 
        }
 
        #endregion Private classes
    }
}