File: MS\Internal\Controls\StickyNote\StickyNoteAnnotations.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: This is the partial class of the StickyNoteControl.
//              This file contains all CAF annotation related implementations.
//
//              See spec at StickyNoteControlSpec.mht
//
 
using MS.Internal;
using MS.Internal.Annotations;
using MS.Internal.Annotations.Component;
using MS.Internal.Controls;
using MS.Internal.Controls.StickyNote;
using MS.Internal.KnownBoxes;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;                           // Assert
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Annotations;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using System.Xml;
using System.Windows.Documents;
using MS.Internal.Documents;
using MS.Internal.Annotations.Anchoring; //TextSelectionHelper
using System.Windows.Controls.Primitives;   // IScrollInfo
using MS.Utility;
 
 
 
namespace MS.Internal.Controls.StickyNote
{
    // NOTICE-2004/08/18,
    // Whenever we add a new type data to this enum type, make sure to update SNCAnnotation.AllValues and SNCAnnotation.AllContents.
    // This is a collection which contains all the Xml elements being used by StickyNoteControl CAF schema.
    [System.Flags]
    internal enum XmlToken
    {
        MetaData = 0x00000001,
        Left = 0x00000004,
        Top = 0x00000008,
        XOffset = 0x00000010,
        YOffset = 0x00000020,
        Width = 0x00000080,
        Height = 0x00000100,
        IsExpanded = 0x00000200,
        Author = 0x00000400,
        Text = 0x00002000,
        Ink = 0x00008000,
        ZOrder = 0x00020000
    }
 
    // This a wrapper class which encapsulates the operations for dealing with CAF's cargo, resources and XmlElements.
    // The schema can be found in http://tabletpc/longhorn/Specs/WinFX%20StickyNoteControl%20M8.1.mht#_Toc79371211
    internal class SNCAnnotation
    {
        //-------------------------------------------------------------------------------
        //
        // Constructors
        //
        //-------------------------------------------------------------------------------
 
        #region Constructor
 
        // This is a static constructor which initializes the internal xml name table and the name space manager.
        static SNCAnnotation()
        {
            // Create our xml name dictionary.
            s_xmlTokeFullNames = new Dictionary<XmlToken, string>();
 
            // Fill in the name dictionary.
            foreach (XmlToken val in Enum.GetValues<XmlToken>())
            {
                AddXmlTokenNames(val);
            }
        }
 
        public SNCAnnotation(Annotation annotation)
        {
            Debug.Assert(annotation != null);
 
            _annotation = annotation;
            _isNewAnnotation = _annotation.Cargos.Count == 0;
 
            // Initialize the data cache collection.
            _cachedXmlElements = new Dictionary<XmlToken, object>();
        }
 
        private SNCAnnotation() { }
 
        #endregion Constructor
 
        //-------------------------------------------------------------------------------
        //
        // Public Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Public Methods
 
        /// <summary>
        /// This static method will update an Annotation object with the specified data in a StickyNoteControl
        /// </summary>
        /// <param name="token">A flag which indicates the data needs to be updated. The flag can be combinated with the any valid bits.</param>
        /// <param name="snc">A StickyNoteControl instance</param>
        /// <param name="sncAnnotation">An SNCAnnotation object which contains a CAF annotation object</param>
        public static void UpdateAnnotation(XmlToken token, StickyNoteControl snc, SNCAnnotation sncAnnotation)
        {
            AnnotationService service = null;
            bool autoFlush = false;
            try
            {
                service = AnnotationService.GetService(((IAnnotationComponent)snc).AnnotatedElement);
                if (service != null && service.Store != null)
                {
                    autoFlush = service.Store.AutoFlush;
                    // Temporarily turn off autoflush until we are done
                    // updating all the necessary values
                    service.Store.AutoFlush = false;
                }
 
                Debug.Assert((token & AllValues) != 0);
 
                // Update Ink
                if ((token & XmlToken.Ink) != 0 && snc.Content.Type == StickyNoteType.Ink)
                {
                    sncAnnotation.UpdateContent(snc, true, XmlToken.Ink);
                }
 
                // Update Text
                if ((token & XmlToken.Text) != 0 && snc.Content.Type == StickyNoteType.Text)
                {
                    sncAnnotation.UpdateContent(snc, true, XmlToken.Text);
                }
 
 
                // Update MetaData
                if ((token & NegativeAllContents) != 0)
                {
                    UpdateMetaData(token, snc, sncAnnotation);
                }
            }
            finally
            {
                if (service != null && service.Store != null)
                {
                    // If auto flush was true before, setting it to true again should cause a flush.
                    service.Store.AutoFlush = autoFlush;
                }
            }
        }
 
        /// <summary>
        /// This static method will update a StickyNoteControl object with the specified data in an Annotation
        /// </summary>
        /// <param name="token">A flag which indicates the data needs to be updated. The flag can be combinated with the any valid bits.</param>
        /// <param name="snc">A StickyNoteControl instance</param>
        /// <param name="sncAnnotation">An SNCAnnotation object which contains a CAF annotation object</param>
        public static void UpdateStickyNoteControl(XmlToken token, StickyNoteControl snc, SNCAnnotation sncAnnotation)
        {
            Invariant.Assert((token & AllValues) != 0, "No token specified.");
            Invariant.Assert(snc != null, "Sticky Note Control is null.");
            Invariant.Assert(sncAnnotation != null, "Annotation is null.");
 
            // FUTURE-2004/08/18-WAYNEZEN,
            // Updating the xml data is synchronized below which could pontentially block the UIContext
            // for updating the huge amount data like Ink.
            // We could conside to post the callbacks to the UIContext for update data in the future.
 
            XmlAttribute node;
 
            // Update Ink
            if ((token & XmlToken.Ink) != 0 && sncAnnotation.HasInkData)
            {
                sncAnnotation.UpdateContent(snc, false, XmlToken.Ink);
            }
 
            // Update Text
            if ((token & XmlToken.Text) != 0 && sncAnnotation.HasTextData)
            {
                sncAnnotation.UpdateContent(snc, false, XmlToken.Text);
            }
 
            // Update Author
            if ((token & XmlToken.Author) != 0)
            {
                int nCount = sncAnnotation._annotation.Authors.Count;
                // Get the culture specific text separator.
                string listSeparator = snc.Language.GetSpecificCulture().TextInfo.ListSeparator;
                string authors = string.Empty;
                for (int i = 0; i < nCount; i++)
                {
                    if (i != 0)
                    {
                        authors += listSeparator + sncAnnotation._annotation.Authors[i];
                    }
                    else
                    {
                        authors += sncAnnotation._annotation.Authors[i];
                    }
                }
 
                // Setting the author property will cause the UI to update
                snc.SetValue(StickyNoteControl.AuthorPropertyKey, authors);
            }
 
            // Update Height
            if ((token & XmlToken.Height) != 0)
            {
                node = (XmlAttribute)sncAnnotation.FindData(XmlToken.Height);
                if (node != null)
                {
                    double height = Convert.ToDouble(node.Value, CultureInfo.InvariantCulture);
                    snc.SetValue(FrameworkElement.HeightProperty, height);
                }
                else
                {
                    snc.ClearValue(FrameworkElement.HeightProperty);
                }
            }
 
            // Update Width
            if ((token & XmlToken.Width) != 0)
            {
                node = (XmlAttribute)sncAnnotation.FindData(XmlToken.Width);
                if (node != null)
                {
                    double width = Convert.ToDouble(node.Value, CultureInfo.InvariantCulture);
                    snc.SetValue(FrameworkElement.WidthProperty, width);
                }
                else
                {
                    snc.ClearValue(FrameworkElement.WidthProperty);
                }
            }
 
            // Update IsExpanded
            if ((token & XmlToken.IsExpanded) != 0)
            {
                node = (XmlAttribute)sncAnnotation.FindData(XmlToken.IsExpanded);
                if (node != null)
                {
                    bool expanded = Convert.ToBoolean(node.Value, CultureInfo.InvariantCulture);
                    snc.IsExpanded = expanded;
                }
                else
                {
                    snc.ClearValue(StickyNoteControl.IsExpandedProperty);
                }
            }
 
            // Update ZOrder
            if ((token & XmlToken.ZOrder) != 0)
            {
                node = (XmlAttribute)sncAnnotation.FindData(XmlToken.ZOrder);
                if (node != null)
                {
                    ((IAnnotationComponent)snc).ZOrder = Convert.ToInt32(node.Value, CultureInfo.InvariantCulture);
                }
            }
 
            // Update Position
            if ((token & PositionValues) != 0)
            {
                TranslateTransform transform = new TranslateTransform();
                if ((token & XmlToken.Left) != 0)
                {
                    node = (XmlAttribute)sncAnnotation.FindData(XmlToken.Left);
                    if (node != null)
                    {
                        double left = Convert.ToDouble(node.Value, CultureInfo.InvariantCulture);
                        // All 'left' values are persisted assuming two things:
                        //  1) the top-left corner (visually) of the StickyNote is the origin of its coordinate space
                        //  2) the positive x-axis of its parent is to the right
                        // This flag signals that we have a positive x-axis to the left and our
                        // top-right corner (visually) is our origin.  So we need to flip the
                        // value before using it.
                        if (snc.FlipBothOrigins)
                        {
                            left = -(left + snc.Width);
                        }
                        transform.X = left;
                    }
                }
 
                if ((token & XmlToken.Top) != 0)
                {
                    node = (XmlAttribute)sncAnnotation.FindData(XmlToken.Top);
                    if (node != null)
                    {
                        double top = Convert.ToDouble(node.Value, CultureInfo.InvariantCulture);
                        transform.Y = top;
                    }
                }
 
                // Now, we update the StickyNote offset
                if ((token & XmlToken.XOffset) != 0)
                {
                    node = (XmlAttribute)sncAnnotation.FindData(XmlToken.XOffset);
                    if (node != null)
                    {
                        snc.XOffset = Convert.ToDouble(node.Value, CultureInfo.InvariantCulture);
                    }
                }
 
                if ((token & XmlToken.YOffset) != 0)
                {
                    node = (XmlAttribute)sncAnnotation.FindData(XmlToken.YOffset);
                    if (node != null)
                    {
                        snc.YOffset = Convert.ToDouble(node.Value, CultureInfo.InvariantCulture);
                    }
                }
 
                // Set the adorner layer transform.
                snc.PositionTransform = transform;
            }
        }
 
 
        #endregion Public Methods
 
        //-------------------------------------------------------------------------------
        //
        // Public Fields
        //
        //-------------------------------------------------------------------------------
 
        #region Public Fields
 
        // A const field which contains all the Xml Elements which are mapped to a StickyNoteControl's properties.
        public const XmlToken AllValues = XmlToken.Left | XmlToken.Top | XmlToken.XOffset | XmlToken.YOffset | XmlToken.Width | XmlToken.Height
                                                | XmlToken.IsExpanded | XmlToken.Author
                                                | XmlToken.Text | XmlToken.Ink | XmlToken.ZOrder;
 
        // A const field which contains all the Xml Elements which are related to the StickyNotes persisted position.
        public const XmlToken PositionValues = XmlToken.Left | XmlToken.Top | XmlToken.XOffset | XmlToken.YOffset;
        // A const field which contains all the Xml Elements for initial draw of the StickyNote (this includes the offsets but not the ZOrder).
        public const XmlToken Sizes = XmlToken.Left | XmlToken.Top | XmlToken.XOffset | XmlToken.YOffset | XmlToken.Width | XmlToken.Height;
        // A const field which contains all the Xml Elements which are related to the properties of the its contents.
        public const XmlToken AllContents = XmlToken.Text | XmlToken.Ink;
        public const XmlToken NegativeAllContents = AllValues ^ XmlToken.Text ^ XmlToken.Ink;
 
        #endregion Public Fields
 
        //-------------------------------------------------------------------------------
        //
        // Public Properties
        //
        //-------------------------------------------------------------------------------
 
        #region Public Properties
 
        /// <summary>
        /// Returns whether this annotation is considered new by the StickyNoteControl.
        /// </summary>
        public bool IsNewAnnotation
        {
            get
            {
                return _isNewAnnotation;
            }
        }
 
        /// <summary>
        /// Returns whether this annotation has ink data.
        /// </summary>
        public bool HasInkData
        {
            get
            {
                return FindData(XmlToken.Ink) != null;
            }
        }
 
        /// <summary>
        /// Returns wether this annotation has text data.
        /// </summary>
        public bool HasTextData
        {
            get
            {
                return FindData(XmlToken.Text) != null;
            }
        }
 
        #endregion Public Properties
 
        //-------------------------------------------------------------------------------
        //
        // Private Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Private Methods
 
        /// <summary>
        /// This method will find a specified cargo.
        /// </summary>
        /// <param name="cargoName">A specified cargo name</param>
        /// <returns>The existing cargo or null</returns>
        private AnnotationResource FindCargo(string cargoName)
        {
            foreach (AnnotationResource cargo in _annotation.Cargos)
            {
                if (cargoName.Equals(cargo.Name))
                    return cargo;
            }
 
            return null;
        }
 
        /// <summary>
        /// Find the specified Annotation data
        /// </summary>
        /// <param name="token">A flag which is corresponding to the annotation data</param>
        /// <returns>The annotation data or null</returns>
        private object FindData(XmlToken token)
        {
            // Assume that we can't find any thing.
            object ret = null;
 
            // First, check if we have the data cached.
            if (_cachedXmlElements.ContainsKey(token))
            {
                ret = _cachedXmlElements[token];
            }
            else
            {
                // Then, we try to search the data in the current annotation.
                AnnotationResource cargo = FindCargo(GetCargoName(token));
                if (cargo != null)
                {
                    ret = SNCAnnotation.FindContent(token, cargo);
 
                    // If we found the data in annotation, we go ahead cache it.
                    if (ret != null)
                    {
                        _cachedXmlElements.Add(token, ret);
                    }
                }
            }
 
            return ret;
        }
 
        /// <summary>
        /// Returns the AnnotationResource and the XML root for the given token from the passed in annotation.
        /// If the cargo or root do not exist they are created but not added to the annotation.  The newCargo
        /// and newRoot flags specify whether they were created.  The caller must use these to add the items
        /// to the annotation after they are done with their modifications.
        /// </summary>
        /// <param name="annotation">the current annotation</param>
        /// <param name="token">the token to be processed</param>
        /// <param name="cargo">the cargo for the token</param>
        /// <param name="root">the root XML element</param>
        /// <param name="newCargo">means a new root and new cargo was created.  the root was already added to the cargo but the cargo was not added to the annotation</param>
        /// <param name="newRoot">means a new root was created.  it has not been added to the cargo.</param>
        private static void GetCargoAndRoot(
            SNCAnnotation annotation, XmlToken token, out AnnotationResource cargo, out XmlElement root, out bool newCargo, out bool newRoot)
        {
            Invariant.Assert(annotation != null, "Annotation is null.");
            Invariant.Assert((token & (AllValues | XmlToken.MetaData)) != 0, "No token specified.");
 
            string cargoName = GetCargoName(token);
 
            newRoot = false;
            newCargo = false;
            cargo = annotation.FindCargo(cargoName);
 
            // Cargo exists
            if (cargo != null)
            {
                root = FindRootXmlElement(token, cargo);
                // Uncommon situation - cargo created without root XmlElement
                if (root == null)
                {
                    newRoot = true;
                    XmlDocument xmlDoc = new XmlDocument();
                    root = xmlDoc.CreateElement(GetXmlName(token), AnnotationXmlConstants.Namespaces.BaseSchemaNamespace);
                    // Don't add it to the cargo yet - wait until all the
                    // values are set on it
                }
            }
            else
            {
                newCargo = true;
                cargo = new AnnotationResource(cargoName);
                XmlDocument xmlDoc = new XmlDocument();
                root = xmlDoc.CreateElement(GetXmlName(token), AnnotationXmlConstants.Namespaces.BaseSchemaNamespace);
 
                // Since the cargo is new, its safe to add the root to it
                // No events will make it to the annotation yet
                cargo.Contents.Add(root);
            }
        }
 
        /// <summary>
        /// Updates the value of the specified token on the specified XmlElement.
        /// If the value is the same, no update is made.  If the new value is null
        /// the attribute is removed.
        /// </summary>
        private void UpdateAttribute(XmlElement root, XmlToken token, string value)
        {
            string name = GetXmlName(token);
 
            XmlNode oldValue = root.GetAttributeNode(name, AnnotationXmlConstants.Namespaces.BaseSchemaNamespace);
            if (oldValue == null)
            {
                if (value == null)
                    return;
                else
                    root.SetAttribute(name, AnnotationXmlConstants.Namespaces.BaseSchemaNamespace, value);
            }
            else
            {
                if (value == null)
                    root.RemoveAttribute(name, AnnotationXmlConstants.Namespaces.BaseSchemaNamespace);
                else if (oldValue.Value != value)
                    root.SetAttribute(name, AnnotationXmlConstants.Namespaces.BaseSchemaNamespace, value);
            }
        }
 
        /// <summary>
        /// This static method returns the Xml name for the specified token.
        /// </summary>
        /// <param name="token">A specified token</param>
        /// <returns>The name for the Xml node</returns>
        private static string GetXmlName(XmlToken token)
        {
            return s_xmlTokeFullNames[token];
        }
 
        /// <summary>
        /// This method is called by the static constructor to set up the Xml name dictionary.
        /// </summary>
        /// <param name="token">A specified token</param>
        private static void AddXmlTokenNames(XmlToken token)
        {
            // Conver the enum value to the string first.
            string xmlName = token.ToString();
 
            // Depending on the data, we add the proper prefix to the full name.
            switch (token)
            {
                // The element names should be qualified
                case XmlToken.MetaData:
                case XmlToken.Text:
                case XmlToken.Ink:
                    {
                        s_xmlTokeFullNames.Add(token, $"{AnnotationXmlConstants.Prefixes.BaseSchemaPrefix}:{xmlName}");
                    }
                    break;
 
                //the attribute names should be local
                case XmlToken.Left:
                case XmlToken.Top:
                case XmlToken.XOffset:
                case XmlToken.YOffset:
                case XmlToken.Width:
                case XmlToken.Height:
                case XmlToken.IsExpanded:
                case XmlToken.ZOrder:
                default:
                    {
                        s_xmlTokeFullNames.Add(token, xmlName);
                    }
                    break;
            }
        }
 
        /// <summary>
        /// Return the cargo name which hosts the specified data.
        /// </summary>
        /// <param name="token">The specified data</param>
        /// <returns>The host cargo name</returns>
        private static string GetCargoName(XmlToken token)
        {
            string cargoName;
            switch (token)
            {
                // Those tokens use *snc* prefix.
                case XmlToken.MetaData:
                case XmlToken.Left:
                case XmlToken.Top:
                case XmlToken.XOffset:
                case XmlToken.YOffset:
                case XmlToken.Width:
                case XmlToken.Height:
                case XmlToken.IsExpanded:
                case XmlToken.ZOrder:
                    {
                        cargoName = SNBConstants.MetaResourceName;
                    }
                    break;
                // Those tokens use *media* prefix.
                case XmlToken.Text:
                    {
                        cargoName = SNBConstants.TextResourceName;
                    }
                    break;
                case XmlToken.Ink:
                    {
                        cargoName = SNBConstants.InkResourceName;
                    }
                    break;
                default:
                    {
                        cargoName = string.Empty;
                        Debug.Assert(false);
                    }
                    break;
            }
            return cargoName;
        }
 
        /// <summary>
        /// The method returns the root node which contains the specified data in a cargo.
        /// </summary>
        /// <param name="token">The specified data</param>
        /// <param name="cargo">The specified cargo</param>
        /// <returns>The root node or null</returns>
        private static XmlElement FindRootXmlElement(XmlToken token, AnnotationResource cargo)
        {
            Debug.Assert(cargo != null);
 
            XmlElement element = null;
            string xmlName = string.Empty;
 
            // Get the xml name of the root node
            switch (token)
            {
                case XmlToken.Text:
                case XmlToken.Ink:
                    xmlName = GetXmlName(token);
                    break;
                case XmlToken.MetaData:
                case XmlToken.IsExpanded:
                case XmlToken.Width:
                case XmlToken.Height:
                case XmlToken.Top:
                case XmlToken.Left:
                case XmlToken.XOffset:
                case XmlToken.YOffset:
                case XmlToken.ZOrder:
                    xmlName = GetXmlName(XmlToken.MetaData);
                    break;
                default:
                    Debug.Assert(false);
                    break;
            }
 
            // Search the root in the cargo's contents.
            foreach (XmlElement node in cargo.Contents)
            {
                if (node.Name.Equals(xmlName))
                {
                    element = node;
                    break;
                }
            }
 
            return element;
        }
 
        /// <summary>
        /// Find the specified data in a cargo.
        /// </summary>
        /// <param name="token">The specified data</param>
        /// <param name="cargo">The cargo which we are searhing in</param>
        /// <returns>The data object or null</returns>
        private static object FindContent(XmlToken token, AnnotationResource cargo)
        {
            object content = null;
            XmlElement root = SNCAnnotation.FindRootXmlElement(token, cargo);
 
            // If we found the root node, we should use XPath to query the node which contains the corresponding data.
            // The StickyNoteControl's xml schema can be found
            // in http://tabletpc/longhorn/Specs/WinFX%20StickyNoteControl%20M8.1.mht#_Toc79371211
            if (root != null)
            {
                switch (token)
                {
                    case XmlToken.Text:
                    case XmlToken.Ink:
                        return root;
                    case XmlToken.IsExpanded:
                    case XmlToken.ZOrder:
                    case XmlToken.Top:
                    case XmlToken.Left:
                    case XmlToken.XOffset:
                    case XmlToken.YOffset:
                    case XmlToken.Width:
                    case XmlToken.Height:
                        return root.GetAttributeNode(GetXmlName(token), AnnotationXmlConstants.Namespaces.BaseSchemaNamespace);
                    default:
                        Debug.Assert(false);
                        break;
                }
            }
 
            return content;
        }
 
        // Update ink data from/to SNC.
        private void UpdateContent(StickyNoteControl snc, bool updateAnnotation, XmlToken token)
        {
            Invariant.Assert(snc != null, "Sticky Note Control is null.");
            Invariant.Assert((token & AllContents) != 0, "No token specified.");
 
            StickyNoteContentControl contentControl = snc.Content;
 
            // Template hasn't been applied yet.  Once it has the content control will then be setup.
            if (contentControl == null)
            {
                return;
            }
 
            // Check whether the annotation data matches the content control.
            if ((token == XmlToken.Ink && contentControl.Type != StickyNoteType.Ink)
                || (token == XmlToken.Text && contentControl.Type != StickyNoteType.Text))
            {
                Debug.Assert(false, "The annotation data does match with the current content control in StickyNote");
                return;
            }
 
            XmlElement root = null;
 
            if (updateAnnotation)
            {
                // Update annotation from SNC
 
                AnnotationResource cargo = null;
                bool newRoot = false;
                bool newCargo = false;
 
                // Check if the text is empty.
                if (!contentControl.IsEmpty)
                {
                    GetCargoAndRoot(this, token, out cargo, out root, out newCargo, out newRoot);
                    contentControl.Save(root);
                }
                else
                {
                    string cargoName = GetCargoName(token);
                    cargo = FindCargo(cargoName);
                    if (cargo != null)
                    {
                        _annotation.Cargos.Remove(cargo);
                        _cachedXmlElements.Remove(token);
                    }
                }
                if (newRoot)
                {
                    Invariant.Assert(root != null, "XmlElement should have been created.");
                    Invariant.Assert(cargo != null, "Cargo should have been retrieved.");
                    cargo.Contents.Add(root);
                }
                if (newCargo)
                {
                    Invariant.Assert(cargo != null, "Cargo should have been created.");
                    _annotation.Cargos.Add(cargo);
                }
            }
            else
            {
                // Update SNC from annotation
 
                // Check if we have the text data in the xml store.
                XmlElement node = (XmlElement)FindData(token);
                if (node != null)
                {
                    contentControl.Load(node);
                }
                else
                {
                    if (!contentControl.IsEmpty)
                    {
                        contentControl.Clear();
                    }
                }
            }
        }
 
        /// <summary>
        /// Update the metadata tokens specified in token.
        /// </summary>
        private static void UpdateMetaData(XmlToken token, StickyNoteControl snc, SNCAnnotation sncAnnotation)
        {
            bool newCargo, newRoot;
            AnnotationResource cargo;
            XmlElement root;
 
            GetCargoAndRoot(sncAnnotation, XmlToken.MetaData, out cargo, out root, out newCargo, out newRoot);
 
            // Update Expanded
            if ((token & XmlToken.IsExpanded) != 0)
            {
                bool expanded = snc.IsExpanded;
                sncAnnotation.UpdateAttribute(root, XmlToken.IsExpanded, expanded.ToString(CultureInfo.InvariantCulture));
            }
 
            // Update Height
            if ((token & XmlToken.Height) != 0)
            {
                Debug.Assert(snc.IsExpanded);
                double height = (double)snc.GetValue(FrameworkElement.HeightProperty);
                sncAnnotation.UpdateAttribute(root, XmlToken.Height, height.ToString(CultureInfo.InvariantCulture));
            }
 
            // Update Width
            if ((token & XmlToken.Width) != 0)
            {
                Debug.Assert(snc.IsExpanded);
                double width = (double)snc.GetValue(FrameworkElement.WidthProperty);
                sncAnnotation.UpdateAttribute(root, XmlToken.Width, width.ToString(CultureInfo.InvariantCulture));
            }
 
            // Update Left
            if ((token & XmlToken.Left) != 0)
            {
                double left = snc.PositionTransform.X;
                // All 'left' values are persisted assuming two things:
                //  1) the top-left corner (visually) of the StickyNote is the origin of its coordinate space
                //  2) the positive x-axis of its parent is to the right
                // This flag signals that we have a positive x-axis to the left and our
                // top-right corner (visually) is our origin.  So we need to flip the
                // value before persisting it.
                if (snc.FlipBothOrigins)
                {
                    left = -(left + snc.Width);
                }
                sncAnnotation.UpdateAttribute(root, XmlToken.Left, left.ToString(CultureInfo.InvariantCulture));
            }
 
            // Update Top
            if ((token & XmlToken.Top) != 0)
            {
                sncAnnotation.UpdateAttribute(root, XmlToken.Top, snc.PositionTransform.Y.ToString(CultureInfo.InvariantCulture));
            }
 
            // Update XOffset
            if ((token & XmlToken.XOffset) != 0)
            {
                sncAnnotation.UpdateAttribute(root, XmlToken.XOffset, snc.XOffset.ToString(CultureInfo.InvariantCulture));
            }
 
            // Update YOffset
            if ((token & XmlToken.YOffset) != 0)
            {
                sncAnnotation.UpdateAttribute(root, XmlToken.YOffset, snc.YOffset.ToString(CultureInfo.InvariantCulture));
            }
 
            // Update ZOrder
            if ((token & XmlToken.ZOrder) != 0)
            {
                sncAnnotation.UpdateAttribute(root, XmlToken.ZOrder, ((IAnnotationComponent)snc).ZOrder.ToString(CultureInfo.InvariantCulture));
            }
 
            if (newRoot)
            {
                cargo.Contents.Add(root);
            }
            if (newCargo)
            {
                sncAnnotation._annotation.Cargos.Add(cargo);
            }
        }
 
        #endregion Private Methods
 
        //-------------------------------------------------------------------------------
        //
        // Private Fields
        //
        //-------------------------------------------------------------------------------
 
        #region Private Fields
 
        private static Dictionary<XmlToken, string> s_xmlTokeFullNames;     // A dictionary for the names of the xml elements
 
        private Dictionary<XmlToken, object> _cachedXmlElements;   // A dictionary for caching the data object
        private Annotation _annotation;
        private readonly bool _isNewAnnotation;
 
        #endregion Private Fields
    }
}
 
namespace System.Windows.Controls
{
    public partial class StickyNoteControl
    {
        //-------------------------------------------------------------------------------
        //
        // IAnnotationComponent Interface
        //
        //-------------------------------------------------------------------------------
 
        #region IAnnotationComponent Members
        /// <summary>
        /// Adds an attached annotations to this StickyNoteControl
        /// </summary>
        /// <param name="attachedAnnotation">An IAttachedAnnotation instance</param>
        void IAnnotationComponent.AddAttachedAnnotation(IAttachedAnnotation attachedAnnotation)
        {
            ArgumentNullException.ThrowIfNull(attachedAnnotation);
 
            if (_attachedAnnotation == null)
            {
                //fire trace event
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.AddAttachedSNBegin);
 
                SetAnnotation(attachedAnnotation);
 
                //fire trace event
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.AddAttachedSNEnd);
            }
            else
            {
                throw new InvalidOperationException(SR.AddAnnotationsNotImplemented);
            }
        }
 
        /// <summary>
        /// Removes an attached annotations from this StickyNoteControl.
        /// </summary>
        /// <param name="attachedAnnotation">An IAttachedAnnotation instance</param>
        void IAnnotationComponent.RemoveAttachedAnnotation(IAttachedAnnotation attachedAnnotation)
        {
            ArgumentNullException.ThrowIfNull(attachedAnnotation);
 
            if (attachedAnnotation == _attachedAnnotation)
            {
                //fire trace event
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.RemoveAttachedSNBegin);
 
                GiveUpFocus();
 
                ClearAnnotation();
 
                //fire trace event
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.RemoveAttachedSNEnd);
            }
            else
            {
                throw new ArgumentException(SR.InvalidValueSpecified, "attachedAnnotation");
            }
        }
 
        /// <summary>
        /// Called when an AttachedAnnotation's attached anchor changes.
        /// </summary>
        /// <param name="attachedAnnotation">The attached annotation after modification</param>
        /// <param name="previousAttachedAnchor">The attached anchor previously associated with the attached annotation.</param>
        /// <param name="previousAttachmentLevel">The previous attachment level of the attached annotation.</param>
        void IAnnotationComponent.ModifyAttachedAnnotation(IAttachedAnnotation attachedAnnotation, object previousAttachedAnchor, AttachmentLevel previousAttachmentLevel)
        {
            throw new NotSupportedException(SR.NotSupported);
        }
 
        /// <summary>
        /// Gets the attached annotations this component is representing.
        /// </summary>
        /// <returns>list of IAttachedAnnotation instances this component is representing</returns>
        IList IAnnotationComponent.AttachedAnnotations
        {
            get
            {
                ArrayList annotations = new ArrayList(1);
 
                if (_attachedAnnotation != null)
                {
                    annotations.Add(_attachedAnnotation);
                }
 
                return annotations;
            }
        }
 
        /// <summary>
        /// Compute the transform for the icon.
        /// also hide or show the expanded component.
        /// </summary>
        /// <param name="transform"></param>
        /// <returns></returns>
        GeneralTransform IAnnotationComponent.GetDesiredTransform(GeneralTransform transform)
        {
            if (_attachedAnnotation != null)
            {
                // If we are expanded and set to flow from RightToLeft and we are in a viewer using a DocumentPageHost
                // we need to mirror ourselves.  This is work around an issue with DocumentViewerBase which mirrors its
                // contents (because its generated always as LeftToRight).  We are anchored to that content so the mirror
                // gets applied to as as well.  This is our attempt to cancel out that mirror.
                if (this.IsExpanded
                    && this.FlowDirection == FlowDirection.RightToLeft
                    && _attachedAnnotation.Parent is DocumentPageHost)
                {
                    _selfMirroring = true;
                }
                else
                {
                    _selfMirroring = false;
                }
 
                Point anchor = _attachedAnnotation.AnchorPoint;
 
                if (double.IsInfinity(anchor.X) || double.IsInfinity(anchor.Y))
                {
                    throw new InvalidOperationException(SR.InvalidAnchorPosition);
                }
 
                if ((double.IsNaN(anchor.X)) || (double.IsNaN(anchor.Y)))
                    return null;
 
                GeneralTransformGroup transformations = new GeneralTransformGroup();
 
                // We should be in normal Right-To-Left mode, but because of special cases
                // in DocumentPageView, we've been mirrored.  We detect this and re-mirror
                // ourselves.  We also need to do special handling of move/resize operations.
                if (_selfMirroring)
                {
                    // This is the mirroring transform that will get the StickyNote to lay out
                    // as if it were in Right-To-Left mode
                    transformations.Children.Add(new MatrixTransform(-1.0, 0.0, 0.0, 1.0, this.Width, 0.0));
                }
 
                transformations.Children.Add(new TranslateTransform(anchor.X, anchor.Y));
 
                TranslateTransform offsetTransform = new TranslateTransform(0, 0);
                if (IsExpanded == true)
                {
                    offsetTransform = PositionTransform.Clone();
 
                    // Reset delta values
                    _deltaX = _deltaY = 0;
 
                    //if we are in any kind of page viewer we might need to bring SN on the page
                    Rect rectPage = PageBounds;
                    Rect rectStickyNote = StickyNoteBounds;
 
                    // Get the current offsets
                    double offsetX, offsetY;
                    GetOffsets(rectPage, rectStickyNote, out offsetX, out offsetY);
 
                    // If the current offsets are greater than the cached the values,
                    // we will make sure that stickynote sticks on the cached values.
                    if (DoubleUtil.GreaterThan(Math.Abs(offsetX), Math.Abs(_offsetX)))
                    {
                        // Whatever the offset - don't move to the right more than
                        double offset = _offsetX - offsetX;
                        if (DoubleUtil.LessThan(offset, 0)) // if we are moving to the left, don't go beyond the edge
                        {
                            offset = Math.Max(offset, -(rectStickyNote.Left - rectPage.Left));
                        }
                        offsetTransform.X += offset;
                        _deltaX = offset;
                    }
 
                    if (DoubleUtil.GreaterThan(Math.Abs(offsetY), Math.Abs(_offsetY)))
                    {
                        double offset = _offsetY - offsetY;
                        if (DoubleUtil.LessThan(offset, 0)) // if we are moving to the top, don't go beyond the edge
                        {
                            offset = Math.Max(offset, -(rectStickyNote.Top - rectPage.Top));
                        }
                        offsetTransform.Y += offset;
                        _deltaY = offset;
                    }
                }
 
                if (offsetTransform != null)
                    transformations.Children.Add(offsetTransform);
                if (transform != null)
                    transformations.Children.Add(transform);                
                return transformations;
            }
 
            return null;
        }
 
        /// <summary>
        /// Return the attached annotation parent as the annotated element
        /// </summary>
        /// <value></value>
        UIElement IAnnotationComponent.AnnotatedElement
        {
            get
            {
                return _attachedAnnotation != null ? _attachedAnnotation.Parent as UIElement : null;
            }
        }
 
        /// <summary>
        /// Get/Set the PresentationContext
        /// </summary>
        /// <value></value>
        PresentationContext IAnnotationComponent.PresentationContext
        {
            get
            {
                return _presentationContext;
            }
            set
            {
                _presentationContext = value;
            }
        }
 
        /// <summary>
        /// Sets and gets the Z-order of this component. NOP -
        /// Highlight does not have Z-order
        /// </summary>
        /// <value>Context this annotation component is hosted in</value>
        int IAnnotationComponent.ZOrder
        {
            get
            {
                return _zOrder;
            }
 
            set
            {
                _zOrder = value;
                UpdateAnnotationWithSNC(XmlToken.ZOrder);
            }
        }
 
        /// <summary>
        /// Notifies the component that the AnnotatedElement content has changed
        /// </summary>
        bool IAnnotationComponent.IsDirty
        {
            get
            {
                if (_anchor != null)
                    return _anchor.IsDirty;
                return false;
            }
            set
            {
                if (_anchor != null)
                    _anchor.IsDirty = value;
                if (value)
                    InvalidateVisual();
            }
        }
 
        #endregion // IAnnotationComponent Members
 
        #region Public Fields
 
        /// <summary>
        /// The Xml type name which is used by the Annotation to instantiate a Text StickyNoteControl
        /// </summary>
        public static readonly XmlQualifiedName TextSchemaName = new XmlQualifiedName("TextStickyNote", AnnotationXmlConstants.Namespaces.BaseSchemaNamespace);
 
        /// <summary>
        /// The Xml type name which is used by the Annotation to instantiate an Ink StickyNoteControl
        /// </summary>
        public static readonly XmlQualifiedName InkSchemaName = new XmlQualifiedName("InkStickyNote", AnnotationXmlConstants.Namespaces.BaseSchemaNamespace);
 
        #endregion Public Fields
 
        //-------------------------------------------------------------------------------
        //
        // Internal Properties
        //
        //-------------------------------------------------------------------------------
 
        #region Internal Properties
 
        // The property is the accessor of the variable of _positionTransform which is used by
        // IAnnotationComponent.GetDesiredTransform method. The annotation adorner layer will use the transform to
        // position the StickyNoteControl
        internal TranslateTransform PositionTransform
        {
            get
            {
                return _positionTransform;
            }
            set
            {
                Invariant.Assert(value != null, "PositionTransform cannot be null.");
                _positionTransform = value;
 
                InvalidateTransform();
            }
        }
 
        /// <summary>
        /// Gets/Sets the cached X offset when StikcyNote is cross the page boundary
        /// </summary>
        internal double XOffset
        {
            get
            {
                return _offsetX;
            }
            set
            {
                _offsetX = value;
            }
        }
 
        /// <summary>
        /// Gets/Sets the cached Y offset when StikcyNote is cross the page boundary
        /// </summary>
        internal double YOffset
        {
            get
            {
                return _offsetY;
            }
            set
            {
                _offsetY = value;
            }
        }
 
        /// <summary>
        /// This flag signals that we are truly in Right-To-Left mode.  This means the positive
        /// x-axis points to the left and our top-right corner (visually) is our origin.  We use
        /// this flag to determine whether we should flip the value for 'left' before storing it.
        /// Flipping it allows it to be persisted in a way that can be used by the StickyNote in
        /// non Right-To-Left mode or in the self-mirroring case.
        /// </summary>
        internal bool FlipBothOrigins
        {
            get
            {
                return (this.IsExpanded && this.FlowDirection == FlowDirection.RightToLeft &&
                    _attachedAnnotation != null && _attachedAnnotation.Parent is DocumentPageHost);
            }
        }
 
        #endregion Internal Properties
 
        //-------------------------------------------------------------------------------
        //
        // Private Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Private Methods
 
        // A registered handler for a bubble listening to the annotation author update event.
        //     obj  -   The sender of the event
        //     args -   event arguement
        private void OnAuthorUpdated(object obj, AnnotationAuthorChangedEventArgs args)
        {
            Debug.Assert(_attachedAnnotation != null && _attachedAnnotation.Annotation == args.Annotation);
 
            if (!InternalLocker.IsLocked(LockHelper.LockFlag.AnnotationChanged))
            {
                UpdateSNCWithAnnotation(XmlToken.Author);
                IsDirty = true;
            }
        }
 
        // A registered handler for a bubble listening to the annotation store update event.
        //     obj  -   The sender of the event
        //     args -   event arguement
        private void OnAnnotationUpdated(object obj, AnnotationResourceChangedEventArgs args)
        {
            Debug.Assert(_attachedAnnotation != null && _attachedAnnotation.Annotation == args.Annotation);
 
            if (!InternalLocker.IsLocked(LockHelper.LockFlag.AnnotationChanged))
            {
                SNCAnnotation sncAnnotation = new SNCAnnotation(args.Annotation);
                _sncAnnotation = sncAnnotation;
                UpdateSNCWithAnnotation(SNCAnnotation.AllValues);
                IsDirty = true;
            }
        }
 
        /// <summary>
        /// The method sets an instance of the IAttachedAnnotation to the StickyNoteControl.
        /// It will be called by IAnnotationComponent.AddAttachedAnnotation.
        /// </summary>
        /// <param name="attachedAnnotation">The instance of the IAttachedAnnotation</param>
        private void SetAnnotation(IAttachedAnnotation attachedAnnotation)
        {
            SNCAnnotation sncAnnotation = new SNCAnnotation(attachedAnnotation.Annotation);
 
            // Retrieve the data type. Then set the StickyNote to correct type.
            // If we have empty data, we won't change the current StickyNote type.
            bool hasInkData = sncAnnotation.HasInkData;
            bool hasTextData = sncAnnotation.HasTextData;
            if (hasInkData && hasTextData)
            {
                throw new ArgumentException(SR.InvalidStickyNoteAnnotation, "attachedAnnotation");
            }
            else if (hasInkData)
            {
                _stickyNoteType = StickyNoteType.Ink;
            }
            else if (hasTextData)
            {
                _stickyNoteType = StickyNoteType.Text;
            }
 
            // If we already created a Content control, make sure it matches our new type or
            // gets recreated to match.
            if (Content != null)
            {
                EnsureStickyNoteType();
            }
 
            //create cargo if it is a new Annotation so it is not considered as new next time
            if (sncAnnotation.IsNewAnnotation)
            {
                AnnotationResource cargo = new AnnotationResource(SNBConstants.MetaResourceName);
                attachedAnnotation.Annotation.Cargos.Add(cargo);
            }
 
            // Set the internal variables
            _attachedAnnotation = attachedAnnotation;
            _attachedAnnotation.Annotation.CargoChanged += new AnnotationResourceChangedEventHandler(OnAnnotationUpdated);
            _attachedAnnotation.Annotation.AuthorChanged += new AnnotationAuthorChangedEventHandler(OnAuthorUpdated);
            _sncAnnotation = sncAnnotation;
            _anchor.AddAttachedAnnotation(attachedAnnotation);
 
            // Update all value
            UpdateSNCWithAnnotation(SNCAnnotation.AllValues);
 
            // The internal data is just sync'ed to the store. So, reset the dirty to false.
            IsDirty = false;
 
            //now check if the SN must be seen
            if ((_attachedAnnotation.AttachmentLevel & AttachmentLevel.StartPortion) == 0)
            {
                //we do not need to show the StickyNote
                SetValue(UIElement.VisibilityProperty, Visibility.Collapsed);
            }
            else
            {
                //if it is seen we need to take care about bringing into view when needed
                RequestBringIntoView += new RequestBringIntoViewEventHandler(OnRequestBringIntoView);
            }
        }
 
        /// <summary>
        /// Clear the internal variables and events which are related to the annotation.
        /// The method will be called by IAnnotationComponent.RemoveAttachedAnnotation
        /// </summary>
        private void ClearAnnotation()
        {
            _attachedAnnotation.Annotation.CargoChanged -= new AnnotationResourceChangedEventHandler(OnAnnotationUpdated);
            _attachedAnnotation.Annotation.AuthorChanged -= new AnnotationAuthorChangedEventHandler(OnAuthorUpdated);
            _anchor.RemoveAttachedAnnotation(_attachedAnnotation);
            _sncAnnotation = null;
 
            _attachedAnnotation = null;
            RequestBringIntoView -= new RequestBringIntoViewEventHandler(OnRequestBringIntoView);
        }
 
        /// <summary>
        /// Brings the SN into view. When navigate through the keyboard navigation the focus
        /// can get to a SN that is not currently  into view.
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">arguments</param>
        /// <remarks> Since the AdornerLayer does not have scrolling abilities we need to scroll
        /// AnnotatedElement. In order to get out SN into view we must calculate the corresponding area of
        /// the AnnotatedElement.</remarks>
        private void OnRequestBringIntoView(Object sender, RequestBringIntoViewEventArgs e)
        {
            Debug.Assert(((IAnnotationComponent)this).AnnotatedElement != null, "undefined annotated element");
            FrameworkElement target = ((IAnnotationComponent)this).AnnotatedElement as FrameworkElement;
 
            DocumentPageHost host = target as DocumentPageHost;
            if (host != null)
            {
                target = host.PageVisual as FrameworkElement;
            }
 
            if (target == null)
            {
                //we have nothing to do here
                return;
            }
 
            //if target is IScrollInfo - check if we are within the viewport
            IScrollInfo scrollInfo = target as IScrollInfo;
            if (scrollInfo != null)
            {
                Rect bounds = StickyNoteBounds;
                Rect viewport = new Rect(0, 0, scrollInfo.ViewportWidth, scrollInfo.ViewportHeight);
                if (bounds.IntersectsWith(viewport))
                    return;
            }
 
            //get adorned element
            Transform adornerTransform = (Transform)TransformToVisual(target);
            Debug.Assert(adornerTransform != null, "transform to AnnotatedElement is null");
 
            //get SN sizes
            Rect rect = new Rect(0, 0, Width, Height);
            rect.Transform(adornerTransform.Value);
 
            // Schedule BringIntoView, rather than making direct call.
            // Otherwise in some cases this can cause nested RequestBringIntoView call to be issued.
            // In scenario when document is inside scroll viewer and annotation needs to be 
            // brought to view the call to BringIntoView won't work because of nested events.
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, new DispatcherOperationCallback(DispatchBringIntoView), new object[] { target, rect });
        }
 
        /// <summary>
        /// Schedules BringIntoView call on <see cref="IAnnotationComponent.AnnotatedElement"/> from <see cref="OnRequestBringIntoView"/>
        /// </summary>
        /// <param name="arg">object array of size 2. param[0] is target to bring into view, param[1] is target Rect</param>
        /// <returns>null</returns>
        private object DispatchBringIntoView(object arg)
        {
            object[] args = (object[])arg;
            FrameworkElement target = (FrameworkElement)(args[0]);
            Rect rect = (Rect)(args[1]);
            target.BringIntoView(rect);
            return null;
        }
 
        /// <summary>
        /// This method will update this StickyNoteControl data based on the internal annotation variable.
        /// </summary>
        /// <param name="tokens">The data need to be updated</param>
        private void UpdateSNCWithAnnotation(XmlToken tokens)
        {
            if (_sncAnnotation != null)
            {
                //fire trace event
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.UpdateSNCWithAnnotationBegin);
 
                // Now, we are going to update this StickyNoteControl. We will get notified for the data being changed.
                // we don't want to update our internal annotation because for the data changes which are coming
                // from the annotation itself. So, we lock our internal locker.
                using (LockHelper.AutoLocker locker = new LockHelper.AutoLocker(InternalLocker, LockHelper.LockFlag.DataChanged))
                {
                    SNCAnnotation.UpdateStickyNoteControl(tokens, this, _sncAnnotation);
                }
 
                //fire trace event
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.UpdateSNCWithAnnotationEnd);
            }
        }
 
        /// <summary>
        /// This method will update this internal annotation based on this StickyNoteControl.
        /// </summary>
        /// <param name="tokens"></param>
        private void UpdateAnnotationWithSNC(XmlToken tokens)
        {
            // Check if we have an annotation attached.
            // Also we don't want to update the annotation when the data changes are actually caused by UpdateSNCWithAnnotation.
            if (_sncAnnotation != null &&
                !InternalLocker.IsLocked(LockHelper.LockFlag.DataChanged))
            {
                //fire trace event
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.UpdateAnnotationWithSNCBegin);
 
                // Now, we are going to update the annotation. Since we will get notified from the annotation store,
                // we don't want to update our internal annotation if the change has been made by this instance.
                // Here we lock the internal locker.
                using (LockHelper.AutoLocker locker = new LockHelper.AutoLocker(InternalLocker, LockHelper.LockFlag.AnnotationChanged))
                {
                    // Now, update the attached annotation.
                    SNCAnnotation.UpdateAnnotation(tokens, this, _sncAnnotation);
                }
                //fire trace event
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnnotation, EventTrace.Event.UpdateAnnotationWithSNCEnd);
            }
        }
 
        /// <summary>
        /// The method will update the X and Y offsets when StickyNote is cross the host page boundary.
        /// This method should only be called from OnDragDelta.
        /// </summary>
        private void UpdateOffsets()
        {
            // If we don't have annotation, don't even borther.
            if (_attachedAnnotation != null)
            {
                Rect rectPage = PageBounds;
                Rect rectStickyNote = StickyNoteBounds;
 
                // Make sure that we have the valid bounds.
                if (!rectPage.IsEmpty && !rectStickyNote.IsEmpty)
                {
                    // StickyNote should never disappear from the host page.
                    Invariant.Assert(DoubleUtil.GreaterThan(rectStickyNote.Right, rectPage.Left), "Note's right is off left of page.");
                    Invariant.Assert(DoubleUtil.LessThan(rectStickyNote.Left, rectPage.Right), "Note's left is off right of page.");
                    Invariant.Assert(DoubleUtil.GreaterThan(rectStickyNote.Bottom, rectPage.Top), "Note's bottom is off top of page.");
                    Invariant.Assert(DoubleUtil.LessThan(rectStickyNote.Top, rectPage.Bottom), "Note's top is off bottom of page.");
 
                    double offsetX, offsetY;
                    // Get the current offsets.
                    GetOffsets(rectPage, rectStickyNote, out offsetX, out offsetY);
 
                    // If the offsets have been changed, we should update the cached values.
                    if (!DoubleUtil.AreClose(XOffset, offsetX))
                    {
                        XOffset = offsetX;
                    }
 
                    if (!DoubleUtil.AreClose(YOffset, offsetY))
                    {
                        YOffset = offsetY;
                    }
                }
            }
        }
 
        /// <summary>
        /// A method which calculates the X and Y offsets between StickyNote and Page bounds.
        /// </summary>
        /// <param name="rectPage">The page bounds</param>
        /// <param name="rectStickyNote">The StickyNote bounds</param>
        /// <param name="offsetX">
        /// X Offset.
        /// 0 means that StickyNote is completely inside page on X dimension.
        /// A negative value means that StickyNote is partially beyond page left.
        /// A positive value means that StickyNote is partially beyond page right.
        /// </param>
        /// <param name="offsetY">
        /// Y Offset.
        /// 0 means that StickyNote is completely inside page on Y dimension.
        /// A negative value means that StickyNote is partially beyond page top.
        /// A positive value means that StickyNote is partially beyond page bottom.
        /// </param>
        private static void GetOffsets(Rect rectPage, Rect rectStickyNote, out double offsetX, out double offsetY)
        {
            offsetX = 0;
            if (DoubleUtil.LessThan(rectStickyNote.Left, rectPage.Left))
            {
                // StickyNote is beyond the left boundary
                offsetX = rectStickyNote.Left - rectPage.Left;
            }
            else if (DoubleUtil.GreaterThan(rectStickyNote.Right, rectPage.Right))
            {
                // StickyNote is beyond the right boundary
                offsetX = rectStickyNote.Right - rectPage.Right;
            }
 
 
            offsetY = 0;
            if (DoubleUtil.LessThan(rectStickyNote.Top, rectPage.Top))
            {
                // StickyNote is beyond the top boundary
                offsetY = rectStickyNote.Top - rectPage.Top;
            }
            else if (DoubleUtil.GreaterThan(rectStickyNote.Bottom, rectPage.Bottom))
            {
                // StickyNote is beyond the bottom boundary
                offsetY = rectStickyNote.Bottom - rectPage.Bottom;
            }
        }
 
        private Rect StickyNoteBounds
        {
            get
            {
                Debug.Assert(_attachedAnnotation != null, "This property should never be acccessed from outside of CAF");
 
                Rect ret = Rect.Empty;
                Point anchor = _attachedAnnotation.AnchorPoint;
 
                if (!(double.IsNaN(anchor.X)) && !(double.IsNaN(anchor.Y)) && PositionTransform != null)
                {
                    ret = new Rect(anchor.X + PositionTransform.X + _deltaX, anchor.Y + PositionTransform.Y + _deltaY, Width, Height);
                }
 
                return ret;
            }
        }
 
        private Rect PageBounds
        {
            get
            {
                Rect pageBounds = Rect.Empty;
 
                IAnnotationComponent component = (IAnnotationComponent)this;
 
                // If the annotated element is a scroll info, we should use the
                // full size of the scrollable content - ExtendWidth/ExtentHeight.
                IScrollInfo scrollInfo = component.AnnotatedElement as IScrollInfo;
                if (scrollInfo != null)
                {
                    pageBounds = new Rect(-scrollInfo.HorizontalOffset, -scrollInfo.VerticalOffset, scrollInfo.ExtentWidth, scrollInfo.ExtentHeight);
                }
                else
                {
                    UIElement parent = component.AnnotatedElement;
 
                    if (parent != null)
                    {
                        Size pageSize = parent.RenderSize;
                        pageBounds = new Rect(0, 0, pageSize.Width, pageSize.Height);
                    }
                }
 
                return pageBounds;
            }
        }
 
        #endregion // Private Methods
 
        //-------------------------------------------------------------------------------
        //
        // Private Fields
        //
        //-------------------------------------------------------------------------------
 
        #region Private Fields
 
        /// <summary>
        /// the presentation context this sticky note is in
        /// </summary>
        PresentationContext _presentationContext;
 
        /// <summary>
        /// Offset from anchor point to sticky note icon
        /// </summary>
        TranslateTransform _positionTransform = new TranslateTransform(0, 0);
 
        // A reference of the current attached annotation instance.
        private IAttachedAnnotation _attachedAnnotation;
        private SNCAnnotation _sncAnnotation;
 
        // The cached horizontal and vertical portions of the StickyNote the user
        // put off the page boundary.  This is used to attempt to reproduce the same
        // portions when the page has been reflowed.
        // 0 means that StickyNote is completely inside page on X or Y dimension.
        // A negative value means that StickyNote is partially beyond page top/left.
        // A positive value means that StickyNote is partially beyond page bottom/right.
        // These are updated everytime the user moves the StickyNote and are persisted.
        private double _offsetX;
        private double _offsetY;
 
        // The distances the StickyNote has been moved at layout time in order to
        // reproduce the same portion off the page as the user previously created.
        // 0 means we had to do no adjusting, the note is where the use put it.
        // A negative value means we had to move the StickyNote towards the origin.
        // A positive value menas we had to move the StickyNote away from the origin.
        // These are updated on every layout pass and are not persisted.
        private double _deltaX;
        private double _deltaY;
 
        //Component Z-order
        private int _zOrder;
 
        // This flag signals that we should be in normal Right-To-Left mode, but because
        // of special cases in DocumentPageView, we've been mirrored (back to Left-To-Right mode).
        // Therefore we need to re-mirror ourselves and special handling of move/resize operations.
        private bool _selfMirroring = false;
 
        #endregion // Private Fields
    }
}