TextAdaptor.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / Controls / TextAdaptor.cs / 1305600 / TextAdaptor.cs

                            //---------------------------------------------------------------------------- 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// 
// Description: Text Object Models Text pattern provider 
// Spec for TextPattern at http://team/sites/uiauto/Shared%20Documents/TextPatternSpecM8.doc
// Spec for Text Object Model (TOM) at http://avalon/uis/TextBox%20and%20RichTextBox/Text%20Object%20Model.doc 
//
// History:
//  03/15/2004 : mmccr - created
//  09/07/2004 : vsmirnov - refactored 
//  01/20/2004 : [....] - refactored
// 
//--------------------------------------------------------------------------- 

using System;                               // Exception 
using System.Collections.Generic;           // List
using System.Collections.ObjectModel;       // ReadOnlyCollection
using System.Security;                      // SecurityCritical, ...
using System.Windows;                       // PresentationSource 
using System.Windows.Automation;            // SupportedTextSelection
using System.Windows.Automation.Peers;      // AutomationPeer 
using System.Windows.Automation.Provider;   // ITextProvider 
using System.Windows.Controls.Primitives;   // IScrollInfo
using System.Windows.Documents;             // ITextContainer 
using System.Windows.Media;                 // Visual
using MS.Internal.Documents;                // MultiPageTextView

namespace MS.Internal.Automation 
{
    ///  
    /// Represents a text provider that supports the text pattern across Text Object 
    /// Model based Text Controls.
    ///  
    internal class TextAdaptor : ITextProvider, IDisposable
    {
        //-------------------------------------------------------------------
        // 
        //  Constructors
        // 
        //------------------------------------------------------------------- 

        #region Constructors 

        /// 
        /// Constructor
        ///  
        /// Automation Peer representing element for the ui scope of the text
        /// ITextContainer 
        internal TextAdaptor(AutomationPeer textPeer, ITextContainer textContainer) 
        {
            Invariant.Assert(textContainer != null, "Invalid ITextContainer"); 
            Invariant.Assert(textPeer is TextAutomationPeer || textPeer is ContentTextAutomationPeer, "Invalid AutomationPeer");
            _textPeer = textPeer;
            _textContainer = textContainer;
            _textContainer.Changed += new TextContainerChangedEventHandler(OnTextContainerChanged); 
            if (_textContainer.TextSelection != null)
            { 
                _textContainer.TextSelection.Changed += new EventHandler(OnTextSelectionChanged); 
            }
        } 

        /// 
        /// Dispose.
        ///  
        public void Dispose()
        { 
            if (_textContainer != null && _textContainer.TextSelection != null) 
            {
                _textContainer.TextSelection.Changed -= new EventHandler(OnTextSelectionChanged); 
            }
            GC.SuppressFinalize(this);
        }
 
        #endregion Constructors
 
        //-------------------------------------------------------------------- 
        //
        //  Internal Methods 
        //
        //-------------------------------------------------------------------

        #region Internal Methods 

        ///  
        /// Retrieves the bounding rectangles for the text lines of a given range. 
        /// 
        /// Start of range to measure 
        /// End of range to measure
        /// Specifies whether the caller wants the full bounds (false) or the bounds of visible portions
        /// of the viewable line only ('true')
        /// Requests the results in screen coordinates 
        /// An array of bounding rectangles for each line or portion of a line within the client area of the text provider.
        /// No bounding rectangles will be returned for lines that are empty or scrolled out of view.  Note that even though a 
        /// bounding rectangle is returned the corresponding text may not be visible due to overlapping windows. 
        /// This will not return null, but may return an empty array.
        internal Rect[] GetBoundingRectangles(ITextPointer start, ITextPointer end, bool clipToView, bool transformToScreen) 
        {
            ITextView textView = GetUpdatedTextView();
            if (textView == null)
            { 
                return new Rect[0];
            } 
 
            // If start/end positions are not in the visible range, move them to the first/last visible positions.
            ReadOnlyCollection textSegments = textView.TextSegments; 
            if (textSegments.Count > 0)
            {
                if (!textView.Contains(start) && start.CompareTo(textSegments[0].Start) < 0)
                { 
                    start = textSegments[0].Start.CreatePointer(); ;
                } 
                if (!textView.Contains(end) && end.CompareTo(textSegments[textSegments.Count-1].End) > 0) 
                {
                    end = textSegments[textSegments.Count - 1].End.CreatePointer(); 
                }
            }
            if (!textView.Contains(start) || !textView.Contains(end))
            { 
                return new Rect[0];
            } 
 
            TextRangeAdaptor.MoveToInsertionPosition(start, LogicalDirection.Forward);
            TextRangeAdaptor.MoveToInsertionPosition(end, LogicalDirection.Backward); 

            Rect visibleRect = Rect.Empty;
            if (clipToView)
            { 
                visibleRect = GetVisibleRectangle(textView);
                // If clipping into view and visible rect is empty, return. 
                if (visibleRect.IsEmpty) 
                {
                    return new Rect[0]; 
                }
            }

            List rectangles = new List(); 
            ITextPointer position = start.CreatePointer();
            while (position.CompareTo(end) < 0) 
            { 
                TextSegment lineRange = textView.GetLineRange(position);
                if (!lineRange.IsNull) 
                {
                    // Since range is limited to just one line, GetTightBoundingGeometry will return tight bounding
                    // rectangle for given range. It will also work correctly with bidi text.
                    ITextPointer first = (lineRange.Start.CompareTo(start) <= 0) ? start : lineRange.Start; 
                    ITextPointer last = (lineRange.End.CompareTo(end) >= 0) ? end : lineRange.End;
                    Rect lineRect = Rect.Empty; 
                    Geometry geometry = textView.GetTightBoundingGeometryFromTextPositions(first, last); 
                    if (geometry != null)
                    { 
                        lineRect = geometry.Bounds;
                        if (clipToView)
                        {
                            lineRect.Intersect(visibleRect); 
                        }
                        if (!lineRect.IsEmpty) 
                        { 
                            if (transformToScreen)
                            { 
                                lineRect = new Rect(ClientToScreen(lineRect.TopLeft, textView.RenderScope), ClientToScreen(lineRect.BottomRight, textView.RenderScope));
                            }
                            rectangles.Add(lineRect);
                        } 
                    }
                } 
                if (position.MoveToLineBoundary(1) == 0) 
                {
                    position = end; 
                }
            }
            return rectangles.ToArray();
        } 

        ///  
        /// Retrieves associated TextView. If TextView is not valid, tries to update its layout. 
        /// 
        internal ITextView GetUpdatedTextView() 
        {
            ITextView textView = _textContainer.TextView;
            if (textView != null)
            { 
                if (!textView.IsValid)
                { 
                    if (!textView.Validate()) 
                    {
                        textView = null; 
                    }
                    if (textView != null && !textView.IsValid)
                    {
                        textView = null; 
                    }
                } 
            } 
            return textView;
        } 

        /// 
        /// Changes text selection on the element
        ///  
        /// Start of range to select
        /// End of range to select 
        /// Automation clients as well as the internal caller of this method (a TextRangeAdapter object) are supposed 
        /// to verify whether the provider supports text selection by calling SupportsTextSelection first.
        /// The internal caller is responsible for raising an InvalidOperationException upon the Automation client' attempt 
        /// to change selection when it's not supported by the provider
        internal void Select(ITextPointer start, ITextPointer end)
        {
            // Update the selection range 
            if (_textContainer.TextSelection != null)
            { 
                _textContainer.TextSelection.Select(start, end); 
            }
        } 

        /// 
        /// This helper method is used by TextRangeAdaptor to bring the range into view
        /// through multiple nested scroll providers. 
        /// 
        internal void ScrollIntoView(ITextPointer start, ITextPointer end, bool alignToTop) 
        { 
            // Calculate the bounding rectangle for the range
            Rect rangeBounds = Rect.Empty; 
            Rect[] lineBounds = GetBoundingRectangles(start, end, false, false);
            foreach (Rect rect in lineBounds)
            {
                rangeBounds.Union(rect); 
            }
 
            ITextView textView = GetUpdatedTextView(); 
            if (textView != null && !rangeBounds.IsEmpty)
            { 
                // Find out the visible portion of the range.
                Rect visibleRect = GetVisibleRectangle(textView);
                Rect rangeVisibleBounds = Rect.Intersect(rangeBounds, visibleRect);
                if (rangeVisibleBounds == rangeBounds) 
                {
                    // The range is already in the view. It's probably not aligned as requested, 
                    // but who cares since it's entirely visible anyway. 
                    return;
                } 

                // Ensure the visibility of the range.
                // BringIntoView will do most of the magic except the very first scroll
                // in order to satisfy the requested alignment. 
                UIElement renderScope = textView.RenderScope;
                Visual visual = renderScope; 
                while (visual != null) 
                {
                    IScrollInfo isi = visual as IScrollInfo; 
                    if (isi != null)
                    {
                        // Transform the bounding rectangle into the IScrollInfo coordinates.
                        if (visual != renderScope) 
                        {
                            GeneralTransform childToParent = renderScope.TransformToAncestor(visual); 
                            rangeBounds = childToParent.TransformBounds(rangeBounds); 
                        }
 
                        if (isi.CanHorizontallyScroll)
                        {
                            isi.SetHorizontalOffset(alignToTop ? rangeBounds.Left : (rangeBounds.Right - isi.ViewportWidth));
                        } 
                        if (isi.CanVerticallyScroll)
                        { 
                            isi.SetVerticalOffset(alignToTop ? rangeBounds.Top : (rangeBounds.Bottom - isi.ViewportHeight)); 
                        }
                        break; 
                    }
                    visual = VisualTreeHelper.GetParent(visual) as Visual;
                }
 
                FrameworkElement fe = renderScope as FrameworkElement;
                if (fe != null) 
                { 
                    fe.BringIntoView(rangeVisibleBounds);
                } 
            }
            else
            {
                // If failed to retrive range bounds, try to Bring into view closes element. 
                ITextPointer pointer = alignToTop ? start.CreatePointer() : end.CreatePointer();
                pointer.MoveToElementEdge(alignToTop ? ElementEdge.AfterStart : ElementEdge.AfterEnd); 
                FrameworkContentElement element = pointer.GetAdjacentElement(LogicalDirection.Backward) as FrameworkContentElement; 
                if (element != null)
                { 
                    element.BringIntoView();
                }
            }
        } 

        #endregion Internal Methods 
 
        //--------------------------------------------------------------------
        // 
        //  Private Methods
        //
        //--------------------------------------------------------------------
 
        #region Private Methods
 
        ///  
        /// Notify about content changes.
        ///  
        private void OnTextContainerChanged(object sender, TextContainerChangedEventArgs e)
        {
            _textPeer.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextChanged);
        } 

        ///  
        /// Notify about selection changes. 
        /// 
        private void OnTextSelectionChanged(object sender, EventArgs e) 
        {
            _textPeer.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextSelectionChanged);
        }
 
        /// 
        /// Computes the bounds of the render scope area visible through all nested scroll areas. 
        ///  
        private Rect GetVisibleRectangle(ITextView textView)
        { 
            Rect visibleRect = new Rect(textView.RenderScope.RenderSize);
            Visual visual = VisualTreeHelper.GetParent(textView.RenderScope) as Visual;

            while (visual != null && visibleRect != Rect.Empty) 
            {
                if (VisualTreeHelper.GetClip(visual) != null) 
                { 
                    GeneralTransform transform = textView.RenderScope.TransformToAncestor(visual).Inverse;
                    // Safer version of transform to descendent (doing the inverse ourself), 
                    // we want the rect inside of our space. (Which is always rectangular and much nicer to work with).
                    if (transform != null)
                    {
                        Rect rectBounds = VisualTreeHelper.GetClip(visual).Bounds; 
                        rectBounds = transform.TransformBounds(rectBounds);
                        visibleRect.Intersect(rectBounds); 
                    } 
                    else
                    { 
                        // No visibility if non-invertable transform exists.
                        visibleRect = Rect.Empty;
                    }
                } 
                visual = VisualTreeHelper.GetParent(visual) as Visual;
            } 
            return visibleRect; 
        }
 
        /// 
        /// Convert a point from "client" coordinate space of a window into
        /// the coordinate space of the screen.
        ///  
        /// 
        ///     Critical: This code calls into PresentationSource to get HwndSource 
        ///     TreatAsSafe: This code is not exposing any critical information, at the same time 
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        private Point ClientToScreen(Point point, Visual visual)
        {
            PresentationSource presentationSource = PresentationSource.CriticalFromVisual(visual);
            if (presentationSource != null) 
            {
                GeneralTransform transform = visual.TransformToAncestor(presentationSource.RootVisual); 
                if (transform != null) 
                {
                    point = transform.Transform(point); 
                }
            }
            return PointUtil.ClientToScreen(point, presentationSource);
        } 

        ///  
        /// Convert a point from the coordinate space of the screen into 
        /// the "client" coordinate space of a window.
        ///  
        /// 
        ///     Critical: This code calls into PresentationSource to get HwndSource
        ///     TreatAsSafe: This code is not exposing any critical information, at the same time
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        private Point ScreenToClient(Point point, Visual visual) 
        { 
            PresentationSource presentationSource = PresentationSource.CriticalFromVisual(visual);
            point = PointUtil.ScreenToClient(point, presentationSource); 
            if (presentationSource != null)
            {
                GeneralTransform transform = visual.TransformToAncestor(presentationSource.RootVisual);
                if (transform != null) 
                {
                    transform = transform.Inverse; 
                    if (transform != null) 
                    {
                        point = transform.Transform(point); 
                    }
                }
            }
            return point; 
        }
 
        #endregion Private Methods 

        //------------------------------------------------------------------- 
        //
        //  Private Fields
        //
        //-------------------------------------------------------------------- 

        #region Private fields 
 
        private AutomationPeer _textPeer;
        private ITextContainer _textContainer; 

        #endregion Private Fields

        //------------------------------------------------------------------- 
        //
        //  ITextProvider 
        // 
        //-------------------------------------------------------------------
 
        #region ITextProvider implementation

        /// 
        /// Retrieves the current selection.  For providers that have the concept of 
        /// text selection the provider should implement this method and also return
        /// true for the SupportsTextSelection property below.  Otherwise this method 
        /// should throw an InvalidOperation exception. 
        /// For providers that support multiple disjoint selection, this should return
        /// an array of all the currently selected ranges. Providers that don't support 
        /// multiple disjoint selection should just return an array containing a single
        /// range.
        /// 
        /// The range of text that is selected, or possibly null if there is 
        /// no selection.
        ITextRangeProvider[] ITextProvider.GetSelection() 
        { 
            ITextRange selection = _textContainer.TextSelection;
            if (selection == null) 
            {
                throw new InvalidOperationException(SR.Get(SRID.TextProvider_TextSelectionNotSupported));
            }
            return new ITextRangeProvider[] { new TextRangeAdaptor(this, selection.Start, selection.End, _textPeer) }; 
        }
 
        ///  
        /// Retrieves the visible ranges of text.
        ///  
        /// The ranges of text that are visible, or possibly an empty array if there is
        /// no visible text whatsoever.  Text in the range may still be obscured by an overlapping
        /// window.  Also, portions
        /// of the range at the beginning, in the middle, or at the end may not be visible 
        /// because they are scrolled off to the side.
        /// Providers should ensure they return at most a range from the beginning of the first 
        /// line with portions visible through the end of the last line with portions visible. 
        ITextRangeProvider[] ITextProvider.GetVisibleRanges()
        { 
            ITextRangeProvider[] ranges = null;
            ITextView textView = GetUpdatedTextView();
            if (textView != null)
            { 
                List visibleTextSegments = new List();
 
                // Get visible portion of the document. 
                //
 



                if (textView is MultiPageTextView) 
                {
                    // For MultiPageTextView assume that all current pages are entirely visible. 
                    visibleTextSegments.AddRange(textView.TextSegments); 
                }
                else 
                {
                    // For all others TextViews get visible rectangle and hittest TopLeft and
                    // BottomRight points to retrieve visible range.
                    // Find out the bounds of the area visible through all nested scroll areas 
                    Rect visibleRect = GetVisibleRectangle(textView);
                    if (!visibleRect.IsEmpty) 
                    { 
                        ITextPointer visibleStart = textView.GetTextPositionFromPoint(visibleRect.TopLeft, true);
                        ITextPointer visibleEnd = textView.GetTextPositionFromPoint(visibleRect.BottomRight, true); 
                        visibleTextSegments.Add(new TextSegment(visibleStart, visibleEnd, true));
                    }
                }
 
                // Create collection of TextRangeProviders for visible ranges.
                if (visibleTextSegments.Count > 0) 
                { 
                    ranges = new ITextRangeProvider[visibleTextSegments.Count];
                    for (int i = 0; i < visibleTextSegments.Count; i++) 
                    {
                        ranges[i] = new TextRangeAdaptor(this, visibleTextSegments[i].Start, visibleTextSegments[i].End, _textPeer);
                    }
                } 
            }
            // If no text is visible in the control, return the degenerate text range 
            // (empty range) at the beginning of the document. 
            if (ranges == null)
            { 
                ranges = new ITextRangeProvider[] { new TextRangeAdaptor(this, _textContainer.Start, _textContainer.Start, _textPeer) };
            }
            return ranges;
        } 

        ///  
        /// Retrieves the range of a child object. 
        /// 
        /// The child element.  A provider should check that the 
        /// passed element is a child of the text container, and should throw an
        /// InvalidOperationException if it is not.
        /// A range that spans the child element.
        ITextRangeProvider ITextProvider.RangeFromChild(IRawElementProviderSimple childElementProvider) 
        {
            if (childElementProvider == null) 
            { 
                throw new ArgumentNullException("childElementProvider");
            } 

            // Retrieve DependencyObject from AutomationElement
            DependencyObject childElement;
            if (_textPeer is TextAutomationPeer) 
            {
                childElement = ((TextAutomationPeer)_textPeer).ElementFromProvider(childElementProvider); 
            } 
            else
            { 
                childElement = ((ContentTextAutomationPeer)_textPeer).ElementFromProvider(childElementProvider);
            }

            TextRangeAdaptor range = null; 
            if (childElement != null)
            { 
                ITextPointer rangeStart = null; 
                ITextPointer rangeEnd = null;
 
                // Retrieve start and end positions for given element.
                // If element is TextElement, retrieve its Element Start and End positions.
                // If element is UIElement hosted by UIContainer (Inlien of Block),
                // retrieve content Start and End positions of the container. 
                // Otherwise scan ITextContainer to find a range for given element.
                if (childElement is TextElement) 
                { 
                    rangeStart = ((TextElement)childElement).ElementStart;
                    rangeEnd = ((TextElement)childElement).ElementEnd; 
                }
                else
                {
                    DependencyObject parent = LogicalTreeHelper.GetParent(childElement); 
                    if (parent is InlineUIContainer || parent is BlockUIContainer)
                    { 
                        rangeStart = ((TextElement)parent).ContentStart; 
                        rangeEnd = ((TextElement)parent).ContentEnd;
                    } 
                    else
                    {
                        ITextPointer position = _textContainer.Start.CreatePointer();
                        while (position.CompareTo(_textContainer.End) < 0) 
                        {
                            TextPointerContext context = position.GetPointerContext(LogicalDirection.Forward); 
                            if (context == TextPointerContext.ElementStart) 
                            {
                                if (childElement == position.GetAdjacentElement(LogicalDirection.Forward)) 
                                {
                                    rangeStart = position.CreatePointer(LogicalDirection.Forward);
                                    position.MoveToElementEdge(ElementEdge.AfterEnd);
                                    rangeEnd = position.CreatePointer(LogicalDirection.Backward); 
                                    break;
                                } 
                            } 
                            else if (context == TextPointerContext.EmbeddedElement)
                            { 
                                if (childElement == position.GetAdjacentElement(LogicalDirection.Forward))
                                {
                                    rangeStart = position.CreatePointer(LogicalDirection.Forward);
                                    position.MoveToNextContextPosition(LogicalDirection.Forward); 
                                    rangeEnd = position.CreatePointer(LogicalDirection.Backward);
                                    break; 
                                } 
                            }
                            position.MoveToNextContextPosition(LogicalDirection.Forward); 
                        }
                    }
                }
                // Create range 
                if (rangeStart != null && rangeEnd != null)
                { 
                    range = new TextRangeAdaptor(this, rangeStart, rangeEnd, _textPeer); 
                }
            } 
            if (range == null)
            {
                throw new InvalidOperationException(SR.Get(SRID.TextProvider_InvalidChildElement));
            } 
            return range;
        } 
 
        /// 
        /// Finds the degenerate range nearest to a screen coordinate. 
        /// 
        /// The location in screen coordinates.
        /// The provider should check that the coordinates are within the client
        /// area of the provider, and should throw an InvalidOperation exception 
        /// if they are not.
        /// A degenerate range nearest the specified location. 
        ITextRangeProvider ITextProvider.RangeFromPoint(Point location) 
        {
            TextRangeAdaptor range = null; 
            ITextView textView = GetUpdatedTextView();
            if (textView != null)
            {
                // Convert the screen point to the element space coordinates. 
                location = ScreenToClient(location, textView.RenderScope);
                ITextPointer position = textView.GetTextPositionFromPoint(location, true); 
                if (position != null) 
                {
                    range = new TextRangeAdaptor(this, position, position, _textPeer); 
                }
            }
            if (range == null)
            { 
                throw new ArgumentException(SR.Get(SRID.TextProvider_InvalidPoint));
            } 
            return range; 
        }
 
        /// 
        /// A text range that encloses the main text of the document.  Some auxillary text such as
        /// headers, footnotes, or annotations may not be included.
        ///  
        ITextRangeProvider ITextProvider.DocumentRange
        { 
            get 
            {
                return new TextRangeAdaptor(this, _textContainer.Start, _textContainer.End, _textPeer); 
            }
        }

        ///  
        /// True if the text container supports text selection. If the provider returns false then
        /// it should throw InvalidOperation exceptions for ITextProvider.GetSelection and 
        /// ITextRangeProvider.Select. 
        /// 
        SupportedTextSelection ITextProvider.SupportedTextSelection 
        {
            get
            {
                return (_textContainer.TextSelection == null) ? SupportedTextSelection.None : SupportedTextSelection.Single; 
            }
        } 
 
        #endregion ITextProvider implementation
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//---------------------------------------------------------------------------- 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// 
// Description: Text Object Models Text pattern provider 
// Spec for TextPattern at http://team/sites/uiauto/Shared%20Documents/TextPatternSpecM8.doc
// Spec for Text Object Model (TOM) at http://avalon/uis/TextBox%20and%20RichTextBox/Text%20Object%20Model.doc 
//
// History:
//  03/15/2004 : mmccr - created
//  09/07/2004 : vsmirnov - refactored 
//  01/20/2004 : [....] - refactored
// 
//--------------------------------------------------------------------------- 

using System;                               // Exception 
using System.Collections.Generic;           // List
using System.Collections.ObjectModel;       // ReadOnlyCollection
using System.Security;                      // SecurityCritical, ...
using System.Windows;                       // PresentationSource 
using System.Windows.Automation;            // SupportedTextSelection
using System.Windows.Automation.Peers;      // AutomationPeer 
using System.Windows.Automation.Provider;   // ITextProvider 
using System.Windows.Controls.Primitives;   // IScrollInfo
using System.Windows.Documents;             // ITextContainer 
using System.Windows.Media;                 // Visual
using MS.Internal.Documents;                // MultiPageTextView

namespace MS.Internal.Automation 
{
    ///  
    /// Represents a text provider that supports the text pattern across Text Object 
    /// Model based Text Controls.
    ///  
    internal class TextAdaptor : ITextProvider, IDisposable
    {
        //-------------------------------------------------------------------
        // 
        //  Constructors
        // 
        //------------------------------------------------------------------- 

        #region Constructors 

        /// 
        /// Constructor
        ///  
        /// Automation Peer representing element for the ui scope of the text
        /// ITextContainer 
        internal TextAdaptor(AutomationPeer textPeer, ITextContainer textContainer) 
        {
            Invariant.Assert(textContainer != null, "Invalid ITextContainer"); 
            Invariant.Assert(textPeer is TextAutomationPeer || textPeer is ContentTextAutomationPeer, "Invalid AutomationPeer");
            _textPeer = textPeer;
            _textContainer = textContainer;
            _textContainer.Changed += new TextContainerChangedEventHandler(OnTextContainerChanged); 
            if (_textContainer.TextSelection != null)
            { 
                _textContainer.TextSelection.Changed += new EventHandler(OnTextSelectionChanged); 
            }
        } 

        /// 
        /// Dispose.
        ///  
        public void Dispose()
        { 
            if (_textContainer != null && _textContainer.TextSelection != null) 
            {
                _textContainer.TextSelection.Changed -= new EventHandler(OnTextSelectionChanged); 
            }
            GC.SuppressFinalize(this);
        }
 
        #endregion Constructors
 
        //-------------------------------------------------------------------- 
        //
        //  Internal Methods 
        //
        //-------------------------------------------------------------------

        #region Internal Methods 

        ///  
        /// Retrieves the bounding rectangles for the text lines of a given range. 
        /// 
        /// Start of range to measure 
        /// End of range to measure
        /// Specifies whether the caller wants the full bounds (false) or the bounds of visible portions
        /// of the viewable line only ('true')
        /// Requests the results in screen coordinates 
        /// An array of bounding rectangles for each line or portion of a line within the client area of the text provider.
        /// No bounding rectangles will be returned for lines that are empty or scrolled out of view.  Note that even though a 
        /// bounding rectangle is returned the corresponding text may not be visible due to overlapping windows. 
        /// This will not return null, but may return an empty array.
        internal Rect[] GetBoundingRectangles(ITextPointer start, ITextPointer end, bool clipToView, bool transformToScreen) 
        {
            ITextView textView = GetUpdatedTextView();
            if (textView == null)
            { 
                return new Rect[0];
            } 
 
            // If start/end positions are not in the visible range, move them to the first/last visible positions.
            ReadOnlyCollection textSegments = textView.TextSegments; 
            if (textSegments.Count > 0)
            {
                if (!textView.Contains(start) && start.CompareTo(textSegments[0].Start) < 0)
                { 
                    start = textSegments[0].Start.CreatePointer(); ;
                } 
                if (!textView.Contains(end) && end.CompareTo(textSegments[textSegments.Count-1].End) > 0) 
                {
                    end = textSegments[textSegments.Count - 1].End.CreatePointer(); 
                }
            }
            if (!textView.Contains(start) || !textView.Contains(end))
            { 
                return new Rect[0];
            } 
 
            TextRangeAdaptor.MoveToInsertionPosition(start, LogicalDirection.Forward);
            TextRangeAdaptor.MoveToInsertionPosition(end, LogicalDirection.Backward); 

            Rect visibleRect = Rect.Empty;
            if (clipToView)
            { 
                visibleRect = GetVisibleRectangle(textView);
                // If clipping into view and visible rect is empty, return. 
                if (visibleRect.IsEmpty) 
                {
                    return new Rect[0]; 
                }
            }

            List rectangles = new List(); 
            ITextPointer position = start.CreatePointer();
            while (position.CompareTo(end) < 0) 
            { 
                TextSegment lineRange = textView.GetLineRange(position);
                if (!lineRange.IsNull) 
                {
                    // Since range is limited to just one line, GetTightBoundingGeometry will return tight bounding
                    // rectangle for given range. It will also work correctly with bidi text.
                    ITextPointer first = (lineRange.Start.CompareTo(start) <= 0) ? start : lineRange.Start; 
                    ITextPointer last = (lineRange.End.CompareTo(end) >= 0) ? end : lineRange.End;
                    Rect lineRect = Rect.Empty; 
                    Geometry geometry = textView.GetTightBoundingGeometryFromTextPositions(first, last); 
                    if (geometry != null)
                    { 
                        lineRect = geometry.Bounds;
                        if (clipToView)
                        {
                            lineRect.Intersect(visibleRect); 
                        }
                        if (!lineRect.IsEmpty) 
                        { 
                            if (transformToScreen)
                            { 
                                lineRect = new Rect(ClientToScreen(lineRect.TopLeft, textView.RenderScope), ClientToScreen(lineRect.BottomRight, textView.RenderScope));
                            }
                            rectangles.Add(lineRect);
                        } 
                    }
                } 
                if (position.MoveToLineBoundary(1) == 0) 
                {
                    position = end; 
                }
            }
            return rectangles.ToArray();
        } 

        ///  
        /// Retrieves associated TextView. If TextView is not valid, tries to update its layout. 
        /// 
        internal ITextView GetUpdatedTextView() 
        {
            ITextView textView = _textContainer.TextView;
            if (textView != null)
            { 
                if (!textView.IsValid)
                { 
                    if (!textView.Validate()) 
                    {
                        textView = null; 
                    }
                    if (textView != null && !textView.IsValid)
                    {
                        textView = null; 
                    }
                } 
            } 
            return textView;
        } 

        /// 
        /// Changes text selection on the element
        ///  
        /// Start of range to select
        /// End of range to select 
        /// Automation clients as well as the internal caller of this method (a TextRangeAdapter object) are supposed 
        /// to verify whether the provider supports text selection by calling SupportsTextSelection first.
        /// The internal caller is responsible for raising an InvalidOperationException upon the Automation client' attempt 
        /// to change selection when it's not supported by the provider
        internal void Select(ITextPointer start, ITextPointer end)
        {
            // Update the selection range 
            if (_textContainer.TextSelection != null)
            { 
                _textContainer.TextSelection.Select(start, end); 
            }
        } 

        /// 
        /// This helper method is used by TextRangeAdaptor to bring the range into view
        /// through multiple nested scroll providers. 
        /// 
        internal void ScrollIntoView(ITextPointer start, ITextPointer end, bool alignToTop) 
        { 
            // Calculate the bounding rectangle for the range
            Rect rangeBounds = Rect.Empty; 
            Rect[] lineBounds = GetBoundingRectangles(start, end, false, false);
            foreach (Rect rect in lineBounds)
            {
                rangeBounds.Union(rect); 
            }
 
            ITextView textView = GetUpdatedTextView(); 
            if (textView != null && !rangeBounds.IsEmpty)
            { 
                // Find out the visible portion of the range.
                Rect visibleRect = GetVisibleRectangle(textView);
                Rect rangeVisibleBounds = Rect.Intersect(rangeBounds, visibleRect);
                if (rangeVisibleBounds == rangeBounds) 
                {
                    // The range is already in the view. It's probably not aligned as requested, 
                    // but who cares since it's entirely visible anyway. 
                    return;
                } 

                // Ensure the visibility of the range.
                // BringIntoView will do most of the magic except the very first scroll
                // in order to satisfy the requested alignment. 
                UIElement renderScope = textView.RenderScope;
                Visual visual = renderScope; 
                while (visual != null) 
                {
                    IScrollInfo isi = visual as IScrollInfo; 
                    if (isi != null)
                    {
                        // Transform the bounding rectangle into the IScrollInfo coordinates.
                        if (visual != renderScope) 
                        {
                            GeneralTransform childToParent = renderScope.TransformToAncestor(visual); 
                            rangeBounds = childToParent.TransformBounds(rangeBounds); 
                        }
 
                        if (isi.CanHorizontallyScroll)
                        {
                            isi.SetHorizontalOffset(alignToTop ? rangeBounds.Left : (rangeBounds.Right - isi.ViewportWidth));
                        } 
                        if (isi.CanVerticallyScroll)
                        { 
                            isi.SetVerticalOffset(alignToTop ? rangeBounds.Top : (rangeBounds.Bottom - isi.ViewportHeight)); 
                        }
                        break; 
                    }
                    visual = VisualTreeHelper.GetParent(visual) as Visual;
                }
 
                FrameworkElement fe = renderScope as FrameworkElement;
                if (fe != null) 
                { 
                    fe.BringIntoView(rangeVisibleBounds);
                } 
            }
            else
            {
                // If failed to retrive range bounds, try to Bring into view closes element. 
                ITextPointer pointer = alignToTop ? start.CreatePointer() : end.CreatePointer();
                pointer.MoveToElementEdge(alignToTop ? ElementEdge.AfterStart : ElementEdge.AfterEnd); 
                FrameworkContentElement element = pointer.GetAdjacentElement(LogicalDirection.Backward) as FrameworkContentElement; 
                if (element != null)
                { 
                    element.BringIntoView();
                }
            }
        } 

        #endregion Internal Methods 
 
        //--------------------------------------------------------------------
        // 
        //  Private Methods
        //
        //--------------------------------------------------------------------
 
        #region Private Methods
 
        ///  
        /// Notify about content changes.
        ///  
        private void OnTextContainerChanged(object sender, TextContainerChangedEventArgs e)
        {
            _textPeer.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextChanged);
        } 

        ///  
        /// Notify about selection changes. 
        /// 
        private void OnTextSelectionChanged(object sender, EventArgs e) 
        {
            _textPeer.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextSelectionChanged);
        }
 
        /// 
        /// Computes the bounds of the render scope area visible through all nested scroll areas. 
        ///  
        private Rect GetVisibleRectangle(ITextView textView)
        { 
            Rect visibleRect = new Rect(textView.RenderScope.RenderSize);
            Visual visual = VisualTreeHelper.GetParent(textView.RenderScope) as Visual;

            while (visual != null && visibleRect != Rect.Empty) 
            {
                if (VisualTreeHelper.GetClip(visual) != null) 
                { 
                    GeneralTransform transform = textView.RenderScope.TransformToAncestor(visual).Inverse;
                    // Safer version of transform to descendent (doing the inverse ourself), 
                    // we want the rect inside of our space. (Which is always rectangular and much nicer to work with).
                    if (transform != null)
                    {
                        Rect rectBounds = VisualTreeHelper.GetClip(visual).Bounds; 
                        rectBounds = transform.TransformBounds(rectBounds);
                        visibleRect.Intersect(rectBounds); 
                    } 
                    else
                    { 
                        // No visibility if non-invertable transform exists.
                        visibleRect = Rect.Empty;
                    }
                } 
                visual = VisualTreeHelper.GetParent(visual) as Visual;
            } 
            return visibleRect; 
        }
 
        /// 
        /// Convert a point from "client" coordinate space of a window into
        /// the coordinate space of the screen.
        ///  
        /// 
        ///     Critical: This code calls into PresentationSource to get HwndSource 
        ///     TreatAsSafe: This code is not exposing any critical information, at the same time 
        /// 
        [SecurityCritical, SecurityTreatAsSafe] 
        private Point ClientToScreen(Point point, Visual visual)
        {
            PresentationSource presentationSource = PresentationSource.CriticalFromVisual(visual);
            if (presentationSource != null) 
            {
                GeneralTransform transform = visual.TransformToAncestor(presentationSource.RootVisual); 
                if (transform != null) 
                {
                    point = transform.Transform(point); 
                }
            }
            return PointUtil.ClientToScreen(point, presentationSource);
        } 

        ///  
        /// Convert a point from the coordinate space of the screen into 
        /// the "client" coordinate space of a window.
        ///  
        /// 
        ///     Critical: This code calls into PresentationSource to get HwndSource
        ///     TreatAsSafe: This code is not exposing any critical information, at the same time
        ///  
        [SecurityCritical, SecurityTreatAsSafe]
        private Point ScreenToClient(Point point, Visual visual) 
        { 
            PresentationSource presentationSource = PresentationSource.CriticalFromVisual(visual);
            point = PointUtil.ScreenToClient(point, presentationSource); 
            if (presentationSource != null)
            {
                GeneralTransform transform = visual.TransformToAncestor(presentationSource.RootVisual);
                if (transform != null) 
                {
                    transform = transform.Inverse; 
                    if (transform != null) 
                    {
                        point = transform.Transform(point); 
                    }
                }
            }
            return point; 
        }
 
        #endregion Private Methods 

        //------------------------------------------------------------------- 
        //
        //  Private Fields
        //
        //-------------------------------------------------------------------- 

        #region Private fields 
 
        private AutomationPeer _textPeer;
        private ITextContainer _textContainer; 

        #endregion Private Fields

        //------------------------------------------------------------------- 
        //
        //  ITextProvider 
        // 
        //-------------------------------------------------------------------
 
        #region ITextProvider implementation

        /// 
        /// Retrieves the current selection.  For providers that have the concept of 
        /// text selection the provider should implement this method and also return
        /// true for the SupportsTextSelection property below.  Otherwise this method 
        /// should throw an InvalidOperation exception. 
        /// For providers that support multiple disjoint selection, this should return
        /// an array of all the currently selected ranges. Providers that don't support 
        /// multiple disjoint selection should just return an array containing a single
        /// range.
        /// 
        /// The range of text that is selected, or possibly null if there is 
        /// no selection.
        ITextRangeProvider[] ITextProvider.GetSelection() 
        { 
            ITextRange selection = _textContainer.TextSelection;
            if (selection == null) 
            {
                throw new InvalidOperationException(SR.Get(SRID.TextProvider_TextSelectionNotSupported));
            }
            return new ITextRangeProvider[] { new TextRangeAdaptor(this, selection.Start, selection.End, _textPeer) }; 
        }
 
        ///  
        /// Retrieves the visible ranges of text.
        ///  
        /// The ranges of text that are visible, or possibly an empty array if there is
        /// no visible text whatsoever.  Text in the range may still be obscured by an overlapping
        /// window.  Also, portions
        /// of the range at the beginning, in the middle, or at the end may not be visible 
        /// because they are scrolled off to the side.
        /// Providers should ensure they return at most a range from the beginning of the first 
        /// line with portions visible through the end of the last line with portions visible. 
        ITextRangeProvider[] ITextProvider.GetVisibleRanges()
        { 
            ITextRangeProvider[] ranges = null;
            ITextView textView = GetUpdatedTextView();
            if (textView != null)
            { 
                List visibleTextSegments = new List();
 
                // Get visible portion of the document. 
                //
 



                if (textView is MultiPageTextView) 
                {
                    // For MultiPageTextView assume that all current pages are entirely visible. 
                    visibleTextSegments.AddRange(textView.TextSegments); 
                }
                else 
                {
                    // For all others TextViews get visible rectangle and hittest TopLeft and
                    // BottomRight points to retrieve visible range.
                    // Find out the bounds of the area visible through all nested scroll areas 
                    Rect visibleRect = GetVisibleRectangle(textView);
                    if (!visibleRect.IsEmpty) 
                    { 
                        ITextPointer visibleStart = textView.GetTextPositionFromPoint(visibleRect.TopLeft, true);
                        ITextPointer visibleEnd = textView.GetTextPositionFromPoint(visibleRect.BottomRight, true); 
                        visibleTextSegments.Add(new TextSegment(visibleStart, visibleEnd, true));
                    }
                }
 
                // Create collection of TextRangeProviders for visible ranges.
                if (visibleTextSegments.Count > 0) 
                { 
                    ranges = new ITextRangeProvider[visibleTextSegments.Count];
                    for (int i = 0; i < visibleTextSegments.Count; i++) 
                    {
                        ranges[i] = new TextRangeAdaptor(this, visibleTextSegments[i].Start, visibleTextSegments[i].End, _textPeer);
                    }
                } 
            }
            // If no text is visible in the control, return the degenerate text range 
            // (empty range) at the beginning of the document. 
            if (ranges == null)
            { 
                ranges = new ITextRangeProvider[] { new TextRangeAdaptor(this, _textContainer.Start, _textContainer.Start, _textPeer) };
            }
            return ranges;
        } 

        ///  
        /// Retrieves the range of a child object. 
        /// 
        /// The child element.  A provider should check that the 
        /// passed element is a child of the text container, and should throw an
        /// InvalidOperationException if it is not.
        /// A range that spans the child element.
        ITextRangeProvider ITextProvider.RangeFromChild(IRawElementProviderSimple childElementProvider) 
        {
            if (childElementProvider == null) 
            { 
                throw new ArgumentNullException("childElementProvider");
            } 

            // Retrieve DependencyObject from AutomationElement
            DependencyObject childElement;
            if (_textPeer is TextAutomationPeer) 
            {
                childElement = ((TextAutomationPeer)_textPeer).ElementFromProvider(childElementProvider); 
            } 
            else
            { 
                childElement = ((ContentTextAutomationPeer)_textPeer).ElementFromProvider(childElementProvider);
            }

            TextRangeAdaptor range = null; 
            if (childElement != null)
            { 
                ITextPointer rangeStart = null; 
                ITextPointer rangeEnd = null;
 
                // Retrieve start and end positions for given element.
                // If element is TextElement, retrieve its Element Start and End positions.
                // If element is UIElement hosted by UIContainer (Inlien of Block),
                // retrieve content Start and End positions of the container. 
                // Otherwise scan ITextContainer to find a range for given element.
                if (childElement is TextElement) 
                { 
                    rangeStart = ((TextElement)childElement).ElementStart;
                    rangeEnd = ((TextElement)childElement).ElementEnd; 
                }
                else
                {
                    DependencyObject parent = LogicalTreeHelper.GetParent(childElement); 
                    if (parent is InlineUIContainer || parent is BlockUIContainer)
                    { 
                        rangeStart = ((TextElement)parent).ContentStart; 
                        rangeEnd = ((TextElement)parent).ContentEnd;
                    } 
                    else
                    {
                        ITextPointer position = _textContainer.Start.CreatePointer();
                        while (position.CompareTo(_textContainer.End) < 0) 
                        {
                            TextPointerContext context = position.GetPointerContext(LogicalDirection.Forward); 
                            if (context == TextPointerContext.ElementStart) 
                            {
                                if (childElement == position.GetAdjacentElement(LogicalDirection.Forward)) 
                                {
                                    rangeStart = position.CreatePointer(LogicalDirection.Forward);
                                    position.MoveToElementEdge(ElementEdge.AfterEnd);
                                    rangeEnd = position.CreatePointer(LogicalDirection.Backward); 
                                    break;
                                } 
                            } 
                            else if (context == TextPointerContext.EmbeddedElement)
                            { 
                                if (childElement == position.GetAdjacentElement(LogicalDirection.Forward))
                                {
                                    rangeStart = position.CreatePointer(LogicalDirection.Forward);
                                    position.MoveToNextContextPosition(LogicalDirection.Forward); 
                                    rangeEnd = position.CreatePointer(LogicalDirection.Backward);
                                    break; 
                                } 
                            }
                            position.MoveToNextContextPosition(LogicalDirection.Forward); 
                        }
                    }
                }
                // Create range 
                if (rangeStart != null && rangeEnd != null)
                { 
                    range = new TextRangeAdaptor(this, rangeStart, rangeEnd, _textPeer); 
                }
            } 
            if (range == null)
            {
                throw new InvalidOperationException(SR.Get(SRID.TextProvider_InvalidChildElement));
            } 
            return range;
        } 
 
        /// 
        /// Finds the degenerate range nearest to a screen coordinate. 
        /// 
        /// The location in screen coordinates.
        /// The provider should check that the coordinates are within the client
        /// area of the provider, and should throw an InvalidOperation exception 
        /// if they are not.
        /// A degenerate range nearest the specified location. 
        ITextRangeProvider ITextProvider.RangeFromPoint(Point location) 
        {
            TextRangeAdaptor range = null; 
            ITextView textView = GetUpdatedTextView();
            if (textView != null)
            {
                // Convert the screen point to the element space coordinates. 
                location = ScreenToClient(location, textView.RenderScope);
                ITextPointer position = textView.GetTextPositionFromPoint(location, true); 
                if (position != null) 
                {
                    range = new TextRangeAdaptor(this, position, position, _textPeer); 
                }
            }
            if (range == null)
            { 
                throw new ArgumentException(SR.Get(SRID.TextProvider_InvalidPoint));
            } 
            return range; 
        }
 
        /// 
        /// A text range that encloses the main text of the document.  Some auxillary text such as
        /// headers, footnotes, or annotations may not be included.
        ///  
        ITextRangeProvider ITextProvider.DocumentRange
        { 
            get 
            {
                return new TextRangeAdaptor(this, _textContainer.Start, _textContainer.End, _textPeer); 
            }
        }

        ///  
        /// True if the text container supports text selection. If the provider returns false then
        /// it should throw InvalidOperation exceptions for ITextProvider.GetSelection and 
        /// ITextRangeProvider.Select. 
        /// 
        SupportedTextSelection ITextProvider.SupportedTextSelection 
        {
            get
            {
                return (_textContainer.TextSelection == null) ? SupportedTextSelection.None : SupportedTextSelection.Single; 
            }
        } 
 
        #endregion ITextProvider implementation
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK