File: System\Windows\Documents\DocumentSequenceTextView.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.
 
using MS.Internal.Documents;
using MS.Internal;
using System.Collections.ObjectModel;
using System.Windows.Media;
 
//
// Description:
//      DocumentSequenceTextView implements TextView for DocumentSequence
//      to support text editing (e.g Selection).
//
 
namespace System.Windows.Documents
{
    /// <summary>
    /// DocumentSequenceTextView implements TextView for DocumentSequence
    /// to support text editing (e.g Selection).
    /// </summary>
    internal sealed class DocumentSequenceTextView : TextViewBase
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        /// <summary>
        /// Constructor.
        /// </summary>
        internal DocumentSequenceTextView(FixedDocumentSequenceDocumentPage docPage)
        {
            _docPage = docPage;
        }
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        /// <summary>
        /// Retrieves a position matching a point.
        /// </summary>
        /// <param name="point">
        /// Point in pixel coordinates to test.
        /// </param>
        /// <param name="snapToText">
        /// If true, this method must always return a positioned text position
        /// (the closest position as calculated by the control's heuristics).
        /// If false, this method should return null position, if the test
        /// point does not fall within any character bounding box.
        /// </param>
        /// <returns>
        /// A text position and its orientation matching or closest to the point.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">
        /// Throws InvalidOperationException if IsValid is false.
        /// If IsValid returns false, Validate method must be called before
        /// calling this method.
        /// </exception>
        internal override ITextPointer GetTextPositionFromPoint(Point point, bool snapToText)
        {
            DocumentsTrace.FixedDocumentSequence.TextOM.Trace($"GetTextPositionFromPoint {point}-{snapToText}");
            DocumentSequenceTextPointer tp = null;
            LogicalDirection edge = LogicalDirection.Forward;
 
            if (ChildTextView != null)
            {
                ITextPointer childOTP = ChildTextView.GetTextPositionFromPoint(point, snapToText);
                if (childOTP != null)
                {
                    tp = new DocumentSequenceTextPointer(ChildBlock, childOTP);
                    edge = childOTP.LogicalDirection;
                }
            }
 
            // When snapToText is true, ChildTextView.GetTextPositionFromPoint will guranttee to
            // return a non-null position.
            // In current code, ChildTextView can't be null.
            return tp == null ? null : DocumentSequenceTextPointer.CreatePointer(tp, edge);
        }
 
        /// <summary>
        /// Retrieves the height and offset, in pixels, of the edge of
        /// the object/character represented by position.
        /// </summary>
        /// <param name="position">
        /// Position of an object/character.
        /// </param>
        /// <param name="transform">
        /// Transform to be applied to returned rect
        /// </param>
        /// <returns>
        /// The height, in pixels, of the edge of the object/character
        /// represented by position.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">
        /// Throws InvalidOperationException if IsValid is false.
        /// If IsValid returns false, Validate method must be called before
        /// calling this method.
        /// </exception>
        /// <remarks>
        /// Rect.Width is always 0.
        ///
        /// If the document is empty, then this method returns the expected
        /// height of a character, if placed at the specified position.
        /// </remarks>
        internal override Rect GetRawRectangleFromTextPosition(ITextPointer position, out Transform transform)
        {
            DocumentsTrace.FixedDocumentSequence.TextOM.Trace($"GetRawRectangleFromTextPosition {position} {position.LogicalDirection}");
            DocumentSequenceTextPointer tp = null;
 
            // Initialize transform to identity
            transform = Transform.Identity;
 
            if (position != null)
            {
                 tp = _docPage.FixedDocumentSequence.TextContainer.VerifyPosition(position);
            }
 
            if (tp != null)
            {
                if (ChildTextView != null)
                {
                    if (ChildTextView.TextContainer == tp.ChildBlock.ChildContainer)
                    {
                        return ChildTextView.GetRawRectangleFromTextPosition(tp.ChildPointer.CreatePointer(position.LogicalDirection), out transform);
                    }
                }
            }
            return Rect.Empty;
        }
 
        /// <summary>
        /// <see cref="TextViewBase.GetTightBoundingGeometryFromTextPositions"/>
        /// </summary>
        internal override Geometry GetTightBoundingGeometryFromTextPositions(ITextPointer startPosition, ITextPointer endPosition)
        {
            if (startPosition != null && endPosition != null && ChildTextView != null)
            {
                DocumentSequenceTextPointer startTp = null;
                DocumentSequenceTextPointer endTp = null;
 
                startTp = _docPage.FixedDocumentSequence.TextContainer.VerifyPosition(startPosition);
                endTp = _docPage.FixedDocumentSequence.TextContainer.VerifyPosition(endPosition);
 
                if (startTp != null && endTp != null)
                {
                    return ChildTextView.GetTightBoundingGeometryFromTextPositions(startTp.ChildPointer, endTp.ChildPointer);
                }
            }
 
            return (new PathGeometry());;
        }
 
        /// <summary>
        /// Retrieves an oriented text position matching position advanced by
        /// a number of lines from its initial position.
        /// </summary>
        /// <param name="position">
        /// Initial text position of an object/character.
        /// </param>
        /// <param name="suggestedX">
        /// The suggested X offset, in pixels, of text position on the destination
        /// line. If suggestedX is set to Double.NaN it will be ignored, otherwise
        /// the method will try to find a position on the destination line closest
        /// to suggestedX.
        /// </param>
        /// <param name="count">
        /// Number of lines to advance. Negative means move backwards.
        /// </param>
        /// <param name="newSuggestedX">
        /// newSuggestedX is the offset at the position moved (useful when moving
        /// between columns or pages).
        /// </param>
        /// <param name="linesMoved">
        /// linesMoved indicates the number of lines moved, which may be less
        /// than count if there is no more content.
        /// </param>
        /// <returns>
        /// A TextPointer and its orientation matching suggestedX on the
        /// destination line.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">
        /// Throws InvalidOperationException if IsValid is false.
        /// If IsValid returns false, Validate method must be called before
        /// calling this method.
        /// </exception>
        internal override ITextPointer GetPositionAtNextLine(ITextPointer position, double suggestedX, int count, out double newSuggestedX, out int linesMoved)
        {
            newSuggestedX = suggestedX;
            linesMoved = count;
 
            DocumentsTrace.FixedDocumentSequence.TextOM.Trace($"GetPositionAtNextLine {position} {position.LogicalDirection} {suggestedX} {count} ");
            DocumentSequenceTextPointer newTp  = null;
            LogicalDirection newEdge = LogicalDirection.Forward;
            DocumentSequenceTextPointer tp = null;
            if (position != null)
            {
                tp = _docPage.FixedDocumentSequence.TextContainer.VerifyPosition(position);
            }
 
            // Note we do not handle cross page navigation
            if (tp != null)
            {
                if (ChildTextView != null)
                {
                    if (ChildTextView.TextContainer == tp.ChildBlock.ChildContainer)
                    {
                        ITextPointer childOTP = ChildTextView.GetPositionAtNextLine(tp.ChildPointer.CreatePointer(position.LogicalDirection), suggestedX, count, out newSuggestedX, out linesMoved);
                        if (childOTP != null)
                        {
                            newTp = new DocumentSequenceTextPointer(ChildBlock, childOTP);
                            newEdge = childOTP.LogicalDirection;
                        }
                    }
                }
            }
            return DocumentSequenceTextPointer.CreatePointer(newTp, newEdge);
        }
 
        /// <summary>
        /// Determines if a position is located between two caret units.
        /// </summary>
        /// <param name="position">
        /// Position to test.
        /// </param>
        /// <returns>
        /// Returns true if the specified position precedes or follows
        /// the first or last code point of a caret unit, respectively.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">
        /// Throws InvalidOperationException if IsValid is false.
        /// If IsValid returns false, Validate method must be called before
        /// calling this method.
        /// </exception>
        /// <remarks>
        /// In the context of this method, "caret unit" refers to a group
        /// of one or more Unicode code points that map to a single rendered
        /// glyph.
        /// </remarks>
        internal override bool IsAtCaretUnitBoundary(ITextPointer position)
        {
            Invariant.Assert(position != null);
            ArgumentNullException.ThrowIfNull(position);
 
            //Verify the position and propagate the call to the child text view
            Invariant.Assert(ChildTextView != null);
            DocumentSequenceTextPointer ftp = this.DocumentSequenceTextContainer.VerifyPosition(position);
 
            return this.ChildTextView.IsAtCaretUnitBoundary(ftp.ChildPointer);
        }
 
        /// <summary>
        /// Finds the next position at the edge of a caret unit in
        /// specified direction.
        /// </summary>
        /// <param name="position">
        /// Initial text position of an object/character.
        /// </param>
        /// <param name="direction">
        /// If Forward, this method returns the "caret unit" position following
        /// the initial position.
        /// If Backward, this method returns the caret unit" position preceding
        /// the initial position.
        /// </param>
        /// <returns>
        /// The next caret unit break position in specified direction.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">
        /// Throws InvalidOperationException if IsValid is false.
        /// If IsValid returns false, Validate method must be called before
        /// calling this method.
        /// </exception>
        /// <remarks>
        /// In the context of this method, "caret unit" refers to a group of one
        /// or more Unicode code points that map to a single rendered glyph.
        ///
        /// If position is located between two caret units, this method returns
        /// a new position located at the opposite edge of the caret unit in
        /// the indicated direction.
        /// If position is located within a group of Unicode code points that map
        /// to a single caret unit, this method returns a new position at
        /// the indicated edge of the containing caret unit.
        /// If position is located at the beginning of end of content -- there is
        /// no content in the indicated direction -- then this method returns
        /// a position located at the same location as initial position.
        /// </remarks>
        internal override ITextPointer GetNextCaretUnitPosition(ITextPointer position, LogicalDirection direction)
        {
            Invariant.Assert(position != null);
            ArgumentNullException.ThrowIfNull(position);
 
            //Verify the position and propagate the call to the child text view
            Invariant.Assert(ChildTextView != null);
            DocumentSequenceTextPointer ftp = this.DocumentSequenceTextContainer.VerifyPosition(position);
 
            return this.ChildTextView.GetNextCaretUnitPosition(ftp.ChildPointer, direction);
        }
 
        /// <summary>
        /// Finds the previous position at the edge of a caret after backspacing.
        /// </summary>
        /// <param name="position">
        /// Initial text position of an object/character.
        /// </param>
        /// <returns>
        /// The previous caret unit break position after backspacing.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">
        /// Throws InvalidOperationException if IsValid is false.
        /// If IsValid returns false, Validate method must be called before
        /// calling this method.
        /// </exception>
        internal override ITextPointer GetBackspaceCaretUnitPosition(ITextPointer position)
        {
            Invariant.Assert(position != null);
            ArgumentNullException.ThrowIfNull(position);
 
            //Verify the position and propagate the call to the child text view
            Invariant.Assert(ChildTextView != null);
            DocumentSequenceTextPointer ftp = this.DocumentSequenceTextContainer.VerifyPosition(position);
 
            return this.ChildTextView.GetBackspaceCaretUnitPosition(ftp.ChildPointer);
        }
 
        /// <summary>
        /// Returns a TextSegment that spans the line on which position is located.
        /// </summary>
        /// <param name="position">
        /// Any oriented text position on the line.
        /// </param>
        /// <returns>
        /// TextSegment that spans the line on which position is located.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">
        /// Throws InvalidOperationException if IsValid is false.
        /// If IsValid returns false, Validate method must be called before
        /// calling this method.
        /// </exception>
        internal override TextSegment GetLineRange(ITextPointer position)
        {
            DocumentsTrace.FixedDocumentSequence.TextOM.Trace($"GetLineRange {position} {position.LogicalDirection}");
            DocumentSequenceTextPointer tpStart = null;
            DocumentSequenceTextPointer tpEnd   = null;
            DocumentSequenceTextPointer tpLine = null;
 
            if (position != null)
            {
                if (ChildTextView != null)
                {
                    tpLine = _docPage.FixedDocumentSequence.TextContainer.VerifyPosition(position);
 
                    if (ChildTextView.TextContainer == tpLine.ChildBlock.ChildContainer)
                    {
                        TextSegment childTR = ChildTextView.GetLineRange(tpLine.ChildPointer.CreatePointer(position.LogicalDirection));
                        if (!childTR.IsNull)
                        {
                            tpStart = new DocumentSequenceTextPointer(ChildBlock, childTR.Start);
                            tpEnd = new DocumentSequenceTextPointer(ChildBlock, childTR.End);
                            return new TextSegment(tpStart, tpEnd, true);
                        }
                    }
                }
            }
            return TextSegment.Null;
        }
 
        /// <summary>
        /// Provides a collection of glyph properties corresponding to runs
        /// of Unicode code points.
        /// </summary>
        /// <param name="start">
        /// A position preceding the first code point to examine.
        /// </param>
        /// <param name="end">
        /// A position following the last code point to examine.
        /// </param>
        /// <returns>
        /// A collection of glyph property runs.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">
        /// Throws InvalidOperationException if IsValid is false.
        /// If IsValid returns false, Validate method must be called before
        /// calling this method.
        /// </exception>
        /// <remarks>
        /// A "glyph" in this context is the lowest level rendered representation
        /// of text.  Each entry in the output array describes a constant run of
        /// properties on the glyphs corresponding to a range of Unicode code points.
        /// With this array, it's possible to enumerate the glpyh properties for
        /// each code point in the specified text run.
        /// </remarks>
        internal override  ReadOnlyCollection<GlyphRun> GetGlyphRuns(ITextPointer start, ITextPointer end)
        {
            throw new NotImplementedException();
        }
 
        /// <summary>
        /// Determines whenever TextView contains specified position.
        /// </summary>
        /// <param name="position">
        /// A position to test.
        /// </param>
        /// <returns>
        /// True if TextView contains specified text position.
        /// Otherwise returns false.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">
        /// Throws InvalidOperationException if IsValid is false.
        /// If IsValid returns false, Validate method must be called before
        /// calling this method.
        /// </exception>
        internal override bool Contains(ITextPointer position)
        {
            DocumentsTrace.FixedDocumentSequence.TextOM.Trace($"Contains {position} {position.LogicalDirection}");
            DocumentSequenceTextPointer tp = null;
            if (position != null)
            {
                tp = _docPage.FixedDocumentSequence.TextContainer.VerifyPosition(position);
            }
 
            // Note we do not handle cross page navigation
            if (tp != null)
            {
                if (ChildTextView != null)
                {
                    if (ChildTextView.TextContainer == tp.ChildBlock.ChildContainer)
                    {
                        return ChildTextView.Contains(tp.ChildPointer.CreatePointer(position.LogicalDirection));
                    }
                }
            }
            return false;
        }
 
        /// <summary>
        /// Makes sure that TextView is in a clean layout state and it is
        /// possible to retrieve layout related data.
        /// </summary>
        /// <remarks>
        /// If IsValid returns false, it is required to call this method
        /// before calling any other method on TextView.
        /// Validate method might be very expensive, because it may lead
        /// to full layout update.
        /// </remarks>
        internal override bool Validate()
        {
            if (ChildTextView != null)
            {
                ChildTextView.Validate();
            }
 
            return ((ITextView)this).IsValid;
        }
 
        /// <see cref="ITextView.Validate(Point)"/>
        internal override bool Validate(Point point)
        {
            if (ChildTextView != null)
            {
                ChildTextView.Validate(point);
            }
 
            return ((ITextView)this).IsValid;
        }
 
        #endregion Internal Methods
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
 
        #region Internal Properties
 
        /// <summary>
        /// </summary>
        internal override UIElement RenderScope
        {
            get
            {
                Visual visual = _docPage.Visual;
 
                while (visual != null && !(visual is UIElement))
                {
                    visual = VisualTreeHelper.GetParent(visual) as Visual;
                }
 
                return visual as UIElement;
            }
        }
 
        /// <summary>
        /// TextContainer that stores content.
        /// </summary>
        internal override ITextContainer TextContainer
        {
            get
            {
                return this._docPage.FixedDocumentSequence.TextContainer;
            }
        }
 
        /// <summary>
        /// Determines whenever layout is in clean state and it is possible
        /// to retrieve layout related data.
        /// </summary>
        internal override bool IsValid
        {
            get
            {
                if (ChildTextView != null)
                {
                    return ChildTextView.IsValid;
                }
                return true;
            }
        }
 
        /// <summary>
        /// <see cref="ITextView.RendersOwnSelection"/>
        /// </summary>
        internal override bool RendersOwnSelection
        {
            get
            {
                return true;
            }
        }
 
 
        /// <summary>
        /// Collection of TextSegments representing content of the TextView.
        /// </summary>
        internal override ReadOnlyCollection<TextSegment> TextSegments
        {
            get
            {
                if (_textSegments == null)
                {
                    ReadOnlyCollection<TextSegment> childSegments = ChildTextView.TextSegments;
                    if (childSegments != null)
                    {
                        List<TextSegment> parentSegments = new List<TextSegment>(childSegments.Count);
                        foreach (TextSegment segment in childSegments)
                        {
                            DocumentSequenceTextPointer ptpStart, ptpEnd;
                            ptpStart = this._docPage.FixedDocumentSequence.TextContainer.MapChildPositionToParent(segment.Start);
                            ptpEnd = this._docPage.FixedDocumentSequence.TextContainer.MapChildPositionToParent(segment.End);
                            parentSegments.Add(new TextSegment(ptpStart, ptpEnd,true));
                        }
                        _textSegments = new ReadOnlyCollection<TextSegment>(parentSegments);
                    }
                }
                return _textSegments;
            }
        }
        #endregion Internal Properties
 
        //------------------------------------------------------
        //
        //  Private Properties
        //
        //------------------------------------------------------
 
        #region Private Properties
        private ITextView ChildTextView
        {
            get
            {
                if (_childTextView == null)
                {
                    IServiceProvider isp = _docPage.ChildDocumentPage as IServiceProvider;
                    if (isp != null)
                    {
                        _childTextView = (ITextView)isp.GetService(typeof(ITextView));
                    }
                }
                return _childTextView;
            }
        }
 
 
        private ChildDocumentBlock ChildBlock
        {
            get
            {
                if (_childBlock == null)
                {
                    _childBlock = _docPage.FixedDocumentSequence.TextContainer.FindChildBlock(_docPage.ChildDocumentReference);
                }
                return _childBlock;
            }
        }
 
        private DocumentSequenceTextContainer DocumentSequenceTextContainer
        {
            get
            {
                return _docPage.FixedDocumentSequence.TextContainer;
            }
        }
 
        #endregion Private Properties
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        // The FixedDocumentSequenceDocumentPage for this TextView
        private readonly FixedDocumentSequenceDocumentPage _docPage;
        private ITextView _childTextView;
        private ReadOnlyCollection<TextSegment> _textSegments;
        private ChildDocumentBlock _childBlock;
    }
}