File: MS\Internal\Annotations\Anchoring\FixedPageProcessor.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 processor looks for FixedPage elements.  When generating
//     locators it produces a locator part identifying the FixedPage's
//     index in the parent FixedDocument.  When resolving, it looks for
//     the FixedPage in the required index.  When processing it
//     processes annotations for any FixedPage it finds.
//
//
 
using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
using System.Windows;
using System.Windows.Annotations;
using System.Windows.Annotations.Storage;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Media;
using MS.Utility;
 
namespace MS.Internal.Annotations.Anchoring
{
    /// <summary>
    ///     This processor looks for FixedPage elements.  When generating
    ///     locators it produces a locator part identifying the FixedPage's
    ///     index in the parent FixedDocument.  When resolving, it looks for
    ///     the FixedPage in the required index.  When processing it
    ///     processes annotations for any FixedPage it finds.
    /// </summary>
    internal class FixedPageProcessor : SubTreeProcessor
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        /// <summary>
        ///     Creates an instance of FixedPageProcessor.
        /// </summary>
        /// <param name="manager">the manager that owns this processor</param>
        /// <exception cref="ArgumentNullException">manager is null</exception>
        public FixedPageProcessor(LocatorManager manager) : base(manager)
        {
        }
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        #region Public Methods
 
        /// <summary>
        ///     If the node is a 'chunk' of fingerprintable text,
        ///     LocatorManager.ProcessAnnotations is called.
        /// </summary>
        /// <param name="node">node to process</param>
        /// <param name="calledProcessAnnotations">indicates the callback was called by
        /// this processor</param>
        /// <returns>
        ///     a list of AttachedAnnotations loaded during the processing of
        ///     the node; can be null if the node is not a FixedPage, or empty
        ///     if the node is FixedPage, but annotations are not loaded
        /// </returns>
        /// <exception cref="ArgumentNullException">node is null</exception>
        public override IList<IAttachedAnnotation> PreProcessNode(DependencyObject node, out bool calledProcessAnnotations)
        {
            ArgumentNullException.ThrowIfNull(node);
 
            DocumentPageView dpv = node as DocumentPageView;
 
            if (dpv != null && (dpv.DocumentPage is FixedDocumentPage || dpv.DocumentPage is FixedDocumentSequenceDocumentPage))
            {
                calledProcessAnnotations = true;
                return Manager.ProcessAnnotations(dpv);
            }
 
            calledProcessAnnotations = false;
            return null;
        }
 
        /// <summary>
        ///     Generates locators identifying 'chunks'.  If node is a chunk,
        ///     generates a locator with a single locator part containing a
        ///     fingerprint of the chunk.  Otherwise null is returned.
        /// </summary>
        /// <param name="node">the node to generate a locator for</param>
        /// <param name="continueGenerating">return flag indicating whether the search
        /// should continue (presumably because the search was not exhaustive).
        /// This processor will always return true because it is possible to locate
        /// parts of the node (if it is FixedPage or FixedPageProxy)</param>
        /// <returns>if node is a FixedPage or FixedPageProxy, a ContentLocator
        /// with  a single locator part containing the page number; null if node is not
        /// FixedPage or FixedPageProxy </returns>
        /// <exception cref="ArgumentNullException">node is null</exception>
        /// <exception cref="ArgumentException">node points to a Document Page View which
        /// doesn't contain a FixedDocumentPage</exception>
        public override ContentLocator GenerateLocator(PathNode node, out bool continueGenerating)
        {
            ArgumentNullException.ThrowIfNull(node);
 
            // Initial value
            continueGenerating = true;
 
            ContentLocator locator = null;
            DocumentPageView dpv = node.Node as DocumentPageView;
 
            int pageNb = -1;
 
            if (dpv != null)
            {
                // Only produce locator parts for FixedDocumentPages
                if (dpv.DocumentPage is FixedDocumentPage || dpv.DocumentPage is FixedDocumentSequenceDocumentPage)
                {
                    pageNb = dpv.PageNumber;
                }
            }
            else
            {
                FixedTextSelectionProcessor.FixedPageProxy fPage = node.Node as FixedTextSelectionProcessor.FixedPageProxy;
                if (fPage != null)
                {
                    pageNb = fPage.Page;
                }
            }
 
            if (pageNb >= 0)
            {
                locator = new ContentLocator();
                ContentLocatorPart locatorPart = CreateLocatorPart(pageNb);
                locator.Parts.Add(locatorPart);
            }
 
            return locator;
        }
 
        /// <summary>
        ///     Searches the logical tree for a node matching the values of
        ///     locatorPart.  A match must be a chunk which produces the same
        ///     fingerprint as in the locator part.
        /// </summary>
        /// <param name="locatorPart">locator part to be matched, must be of the type
        /// handled by this processor</param>
        /// <param name="startNode">logical tree node to start search at</param>
        /// <param name="continueResolving">return flag indicating whether the search
        /// should continue (presumably because the search was not exhaustive). This
        /// processor will return false if the startNode is a FixedPage
        /// with a different page number than the locator part's page number.
        /// Otherwise the return value will be true.
        /// inside the FixedPage(like TextSelection) </param>
        /// <returns>returns a node that matches the locator part; null if no such
        /// node is found</returns>
        /// <exception cref="ArgumentNullException">locatorPart or startNode are
        /// null</exception>
        /// <exception cref="ArgumentException">locatorPart is of the incorrect
        /// type</exception>
        public override DependencyObject ResolveLocatorPart(ContentLocatorPart locatorPart, DependencyObject startNode, out bool continueResolving)
        {
            ArgumentNullException.ThrowIfNull(locatorPart);
            ArgumentNullException.ThrowIfNull(startNode);
 
            if (PageNumberElementName != locatorPart.PartType)
                throw new ArgumentException(SR.Format(SR.IncorrectLocatorPartType, $"{locatorPart.PartType.Namespace}:{locatorPart.PartType.Name}"), "locatorPart");
 
            // Initial value
            continueResolving = true;
 
            int pageNumber = 0;
            string pageNumberString = locatorPart.NameValuePairs[ValueAttributeName];
 
            if (pageNumberString != null)
                pageNumber = Int32.Parse(pageNumberString, NumberFormatInfo.InvariantInfo);
            else
                throw new ArgumentException(SR.Format(SR.IncorrectLocatorPartType, $"{locatorPart.PartType.Namespace}:{locatorPart.PartType.Name}"), "locatorPart");
 
 
            // Get the actual FixedPage for the page number specified in the LocatorPart.  We need
            // the actual FixedPage cause its what exists in the visual tree and what we'll use to
            // anchor the annotations to.
            FixedDocumentPage page = null;
            IDocumentPaginatorSource document = null;
            DocumentPageView dpv = null;
            if (_useLogicalTree)
            {
                document = startNode as FixedDocument;
                if (document != null)
                {
                    page = document.DocumentPaginator.GetPage(pageNumber) as FixedDocumentPage;
                }
                else
                {
                    document = startNode as FixedDocumentSequence;
                    if (document != null)
                    {
                        FixedDocumentSequenceDocumentPage sequencePage = document.DocumentPaginator.GetPage(pageNumber) as FixedDocumentSequenceDocumentPage;
                        if (sequencePage != null)
                        {
                            page = sequencePage.ChildDocumentPage as FixedDocumentPage;
                        }
                    }
                }
            }
            else
            {
                dpv = startNode as DocumentPageView;
                if (dpv != null)
                {
                    page = dpv.DocumentPage as FixedDocumentPage;
                    if (page == null)
                    {
                        FixedDocumentSequenceDocumentPage sequencePage = dpv.DocumentPage as FixedDocumentSequenceDocumentPage;
                        if (sequencePage != null)
                        {
                            page = sequencePage.ChildDocumentPage as FixedDocumentPage;
                        }
                    }
 
                    // If this was the wrong fixed page we want to stop searching this subtree
                    if (page != null && dpv.PageNumber != pageNumber)
                    {
                        continueResolving = false;
                        page = null;
                    }
                }
            }
 
            if (page != null)
            {
                return page.FixedPage;
            }
 
            return null;
        }
 
        /// <summary>
        ///     Returns a list of XmlQualifiedNames representing the
        ///     the locator parts this processor can resolve/generate.
        /// </summary>
        public override XmlQualifiedName[] GetLocatorPartTypes()
        {
            return (XmlQualifiedName[])LocatorPartTypeNames.Clone();
        }
 
        #endregion Public Methods
 
        //------------------------------------------------------
        //
        //  Public Operators
        //
        //------------------------------------------------------
        //------------------------------------------------------
        //
        //  Public Events
        //
        //------------------------------------------------------
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        #region Public Properties
 
        /// <summary>
        ///     Id used to register this processor with the LocatorManager.  Registration
        ///     is done by the framework and does not need to be repeated.  Use this
        ///     string in markup as a value for SubTreeProcessorIdProperty.
        /// </summary>
        public static readonly String Id = "FixedPage";
 
        #endregion Public Properties
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
 
        #region Internal Properties
 
        internal bool UseLogicalTree
        {
            set
            {
                _useLogicalTree = value;
            }
        }
 
        #endregion Internal Properties
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        /// <summary>
        ///     Creates an instance of the locator part type handled by this
        ///     handler that represents node.
        /// </summary>
        /// <param name="page">FixedPage for which a locator part will be created</param>
        /// <returns>
        ///     a locator part of the type handled by this handler representing
        ///     the passed in node; null is returned if the locator part cannot
        ///     be created for the node
        /// </returns>
        static internal ContentLocatorPart CreateLocatorPart(int page)
        {
            Debug.Assert(page >= 0, "page can not be negative");
 
            ContentLocatorPart part = new ContentLocatorPart(PageNumberElementName);
 
            part.NameValuePairs.Add(ValueAttributeName, page.ToString(NumberFormatInfo.InvariantInfo));
            return part;
        }
 
        #endregion Private Methods
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        // Name of the value attribute containing the hash
        private static readonly String ValueAttributeName = "Value";
 
        // Name of the locator part produced by this processor.
        private static readonly XmlQualifiedName PageNumberElementName = new XmlQualifiedName("PageNumber", AnnotationXmlConstants.Namespaces.BaseSchemaNamespace);
 
        // ContentLocatorPart types understood by this processor
        private static readonly XmlQualifiedName[] LocatorPartTypeNames = new XmlQualifiedName[] {
            PageNumberElementName
        };
 
        // Specifies whether the processor should use the logical tree to resolve locator parts.
        // If this is false (the default) only visible pages will be found.
        private bool _useLogicalTree = false;
 
        #endregion Private Fields
    }
}