Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / Documents / TextPointerBase.cs / 1305600 / TextPointerBase.cs
//---------------------------------------------------------------------------- // // File: TextPointerBase.cs // // Copyright (C) Microsoft Corporation. All rights reserved. // // Description: ITextPointer helper methods. // //--------------------------------------------------------------------------- namespace System.Windows.Documents { using System; using MS.Internal; using MS.Internal.Documents; using System.Globalization; using System.Windows.Media; // Matrix using System.Windows.Controls; // TextBlock // ITextPointer helper methods. internal static class TextPointerBase { //----------------------------------------------------- // // Internal Methods // //----------------------------------------------------- #region Internal Methods ////// Returns the preceeding TextPointer of a pair. /// /// /// TextPointer to compare. /// /// /// TextPointer to compare. /// internal static ITextPointer Min(ITextPointer position1, ITextPointer position2) { return position1.CompareTo(position2) <= 0 ? position1 : position2; } ////// Returns the following TextPointer of a pair. /// /// /// TextPointer to compare. /// /// /// TextPointer to compare. /// internal static ITextPointer Max(ITextPointer position1, ITextPointer position2) { return position1.CompareTo(position2) >= 0 ? position1 : position2; } // Worker for GetText, accepts any ITextPointer. internal static string GetTextInRun(ITextPointer position, LogicalDirection direction) { char[] text; int textLength; int getTextLength; textLength = position.GetTextRunLength(direction); text = new char[textLength]; getTextLength = position.GetTextInRun(direction, text, 0, textLength); Invariant.Assert(getTextLength == textLength, "textLengths returned from GetTextRunLength and GetTextInRun are innconsistent"); return new string(text); } // Like GetText, excepts also accepts a limit parameter -- no text is returned past // this second position. // limit may be null, in which case it is ignored. internal static int GetTextWithLimit(ITextPointer thisPointer, LogicalDirection direction, char[] textBuffer, int startIndex, int count, ITextPointer limit) { int charsCopied; if (limit == null) { // No limit, just call GetText. charsCopied = thisPointer.GetTextInRun(direction, textBuffer, startIndex, count); } else if (direction == LogicalDirection.Forward && limit.CompareTo(thisPointer) <= 0) { // Limit completely blocks the read. charsCopied = 0; } else if (direction == LogicalDirection.Backward && limit.CompareTo(thisPointer) >= 0) { // Limit completely blocks the read. charsCopied = 0; } else { int maxCount; // Get an upper bound on the amount of text to copy. // Since GetText always stops on non-text boundaries, it's // ok if the count too high, it will get truncated anyways. if (direction == LogicalDirection.Forward) { maxCount = Math.Min(count, thisPointer.GetOffsetToPosition(limit)); } else { maxCount = Math.Min(count, limit.GetOffsetToPosition(thisPointer)); } maxCount = Math.Min(count, maxCount); charsCopied = thisPointer.GetTextInRun(direction, textBuffer, startIndex, maxCount); } return charsCopied; } #if UNUSED // Returns true if the pointer is at an insertion position or next to // any unicode code point. A useful performance win over // IsAtInsertionPosition when only formatting scopes are important. internal static bool IsAtFormatNormalizedPosition(ITextPointer position) { return IsAtNormalizedPosition(position, false /* respectCaretUnitBoundaries */); } #endif ////// Return true if this TextPointer is adjacent to a character. /// ////// True if this TextPointer is adjacent to a unit boundary, false otherwise. /// internal static bool IsAtInsertionPosition(ITextPointer position) { return IsAtNormalizedPosition(position, true /* respectCaretUnitBoundaries */); } // Tests if a position is between structural symbols where Run is potentially insertable, // but not present. // Positions in between AnchoredBlocks/InlineUIContainers/ParagraphEdges/SpanEdges internal static bool IsAtPotentialRunPosition(ITextPointer position) { bool isAtPotentialRunPosition = IsAtPotentialRunPosition(position, position); if (!isAtPotentialRunPosition) { // Test for positions inside // 1. empty table cell // 2. empty list item or // 3. empty flow document // They are a valid caret stop. // Editing operations are permitted at such positions since it is a potential insertion position. isAtPotentialRunPosition = IsAtPotentialParagraphPosition(position); } return isAtPotentialRunPosition; } // Tests whether this element is a Run inserted at potential run position. // It is used in decidint whether the empty element can be removed // or not. The Run that is at potential run position should not // be removed as it would cause a loss of formatting data at this // position. internal static bool IsAtPotentialRunPosition(TextElement run) { return run is Run && run.IsEmpty && IsAtPotentialRunPosition(run.ElementStart, run.ElementEnd); } // Worker implementing IsAtPotentialRunPosition(position) method. // It is used for testing whether an empty Run element is at potential run potision. // For this purpose the method is supposed to be called with // backwardPosition==run.ElementStart and forwardPosition==run.ElementEnd. private static bool IsAtPotentialRunPosition(ITextPointer backwardPosition, ITextPointer forwardPosition) { Invariant.Assert(backwardPosition.HasEqualScope(forwardPosition)); if (TextSchema.IsValidChild(/*position*/backwardPosition, /*childType*/typeof(Run))) { Type forwardType = forwardPosition.GetElementType(LogicalDirection.Forward); Type backwardType = backwardPosition.GetElementType(LogicalDirection.Backward); if (forwardType != null && backwardType != null) { TextPointerContext forwardContext = forwardPosition.GetPointerContext(LogicalDirection.Forward); TextPointerContext backwardContext = backwardPosition.GetPointerContext(LogicalDirection.Backward); if (// Test if the position inside empty Paragraph or Span backwardContext == TextPointerContext.ElementStart && forwardContext == TextPointerContext.ElementEnd || // Test if the position between opening tag and an embedded object backwardContext == TextPointerContext.ElementStart && TextSchema.IsNonFormattingInline(forwardType) && !IsAtNonMergeableInlineStart(backwardPosition) || // Test if the position between an embedded object and a closing tag forwardContext == TextPointerContext.ElementEnd && TextSchema.IsNonFormattingInline(backwardType) && !IsAtNonMergeableInlineEnd(forwardPosition) || // Test if the position between two embedded objects backwardContext == TextPointerContext.ElementEnd && forwardContext == TextPointerContext.ElementStart && TextSchema.IsNonFormattingInline(backwardType) && TextSchema.IsNonFormattingInline(forwardType) || // Test if the position is adjacent to a non-mergeable inline (Hyperlink). backwardContext == TextPointerContext.ElementEnd && typeof(Inline).IsAssignableFrom(backwardType) && !TextSchema.IsMergeableInline(backwardType) && !typeof(Run).IsAssignableFrom(forwardType) && (forwardContext != TextPointerContext.ElementEnd || !IsAtNonMergeableInlineEnd(forwardPosition)) || forwardContext == TextPointerContext.ElementStart && typeof(Inline).IsAssignableFrom(forwardType) && !TextSchema.IsMergeableInline(forwardType) && !typeof(Run).IsAssignableFrom(backwardType) && (backwardContext != TextPointerContext.ElementStart || !IsAtNonMergeableInlineStart(backwardPosition)) ) { return true; } } } return false; } // Tests if a position is between structural symbols where Paragraph is potentially insertable, // but not present. // Positions in empty list item, table cell or flow document. internal static bool IsAtPotentialParagraphPosition(ITextPointer position) { Type parentType = position.ParentType; TextPointerContext backwardContext = position.GetPointerContext(LogicalDirection.Backward); TextPointerContext forwardContext = position.GetPointerContext(LogicalDirection.Forward); if (backwardContext == TextPointerContext.ElementStart && forwardContext == TextPointerContext.ElementEnd) { return typeof(ListItem).IsAssignableFrom(parentType) || typeof(TableCell).IsAssignableFrom(parentType); } else if (backwardContext == TextPointerContext.None && forwardContext == TextPointerContext.None) { return typeof(FlowDocumentView).IsAssignableFrom(parentType) || typeof(FlowDocument).IsAssignableFrom(parentType); } return false; } // Tests if position is before the first Table element in a collection of Blocks at that level. // We treat this as a potential insertion position to allow editing operations before the table. // This property identifies such a position. internal static bool IsBeforeFirstTable(ITextPointer position) { TextPointerContext forwardContext = position.GetPointerContext(LogicalDirection.Forward); TextPointerContext backwardContext = position.GetPointerContext(LogicalDirection.Backward); return (forwardContext == TextPointerContext.ElementStart && (backwardContext == TextPointerContext.ElementStart || backwardContext == TextPointerContext.None) && typeof(Table).IsAssignableFrom(position.GetElementType(LogicalDirection.Forward))); } // Tests if position is parented by a BlockUIContainer element. // We allow caret stops around BlockUIContainer, to permit editing operations on its boundaries. internal static bool IsInBlockUIContainer(ITextPointer position) { return (typeof(BlockUIContainer).IsAssignableFrom(position.ParentType)); } internal static bool IsAtBlockUIContainerStart(ITextPointer position) { return IsInBlockUIContainer(position) && position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart; } internal static bool IsAtBlockUIContainerEnd(ITextPointer position) { return IsInBlockUIContainer(position) && position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd; } // Walks parents from this position until it hits limiting ancestor type. private static bool IsInAncestorScope(ITextPointer position, Type allowedParentType, Type limitingType) { ITextPointer navigator = position.CreatePointer(); Type parentType = navigator.ParentType; while (parentType != null && allowedParentType.IsAssignableFrom(parentType)) { if (limitingType.IsAssignableFrom(parentType)) { return true; } navigator.MoveToElementEdge(ElementEdge.BeforeStart); parentType = navigator.ParentType; } return false; } // Returns true if position is within the scope of AnchoredBlock element internal static bool IsInAnchoredBlock(ITextPointer position) { return IsInAncestorScope(position, typeof(TextElement), typeof(AnchoredBlock)); } // Returns true if position is inside the scope of Hyperlink element internal static bool IsInHyperlinkScope(ITextPointer position) { return IsInAncestorScope(position, typeof(Inline), typeof(Hyperlink)); } // If position is before the start boundary of a non-mergeable inline (Hyperlink), // this method returns a position immediately preceding its content (which is not an insertion position). // This method will skip past leading InlineUIContainers and BlockUIContainers. Otherwise returns null. internal static ITextPointer GetFollowingNonMergeableInlineContentStart(ITextPointer position) { ITextPointer navigator = position.CreatePointer(); bool moved = false; Type elementType; while (true) { BorderingElementCategory category = GetBorderingElementCategory(navigator, LogicalDirection.Forward); // If position is before formatting closing scope, skip to outside the formatting scope. if (category == BorderingElementCategory.MergeableScopingInline) { do { navigator.MoveToNextContextPosition(LogicalDirection.Forward); } while (GetBorderingElementCategory(navigator, LogicalDirection.Forward) == BorderingElementCategory.MergeableScopingInline); moved = true; } // Skip all InlineUIContainers and BlockUIContainers. elementType = navigator.GetElementType(LogicalDirection.Forward); if (elementType == typeof(InlineUIContainer) || elementType == typeof(BlockUIContainer)) { // We are next to an InlineUIContainer/BlockUIContainer. Skip the following element. navigator.MoveToNextContextPosition(LogicalDirection.Forward); navigator.MoveToElementEdge(ElementEdge.AfterEnd); } else if (navigator.ParentType == typeof(InlineUIContainer) || navigator.ParentType == typeof(BlockUIContainer)) { // We are inside an InlineUIContainer/BlockUIContainer. Skip this element. navigator.MoveToElementEdge(ElementEdge.AfterEnd); } else { break; } // Move to next insertion position if we are done skipping a string of sequential UICs to get a // valid selection start pointer. We need to make sure we land at a position that would be given // to this function had the UICs not been there. This ensures that we end up inside Runs that // follow instead of before them, e.g.|abc instead of |abc . elementType = navigator.GetElementType(LogicalDirection.Forward); if (!(elementType == typeof(InlineUIContainer)) && !(elementType == typeof(BlockUIContainer))) { navigator.MoveToNextInsertionPosition(LogicalDirection.Forward); } moved = true; } if (typeof(Inline).IsAssignableFrom(elementType) && !TextSchema.IsMergeableInline(elementType)) { // We are adjacent to a nonmergeable inline. Find its content. // Just skip over all opening contexts. do { navigator.MoveToNextContextPosition(LogicalDirection.Forward); } while (navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart); moved = true; } return moved ? navigator : null; } // Returns true if position is at the start boundary of a non-mergeable inline ancestor (hyperlink) internal static bool IsAtNonMergeableInlineStart(ITextPointer position) { return IsAtNonMergeableInlineEdge(position, LogicalDirection.Backward); } // Returns true if position is at the end boundary of a non-mergeable inline ancestor (hyperlink) internal static bool IsAtNonMergeableInlineEnd(ITextPointer position) { return IsAtNonMergeableInlineEdge(position, LogicalDirection.Forward); } // Returns true if passed position is in scope of a Hyperlink or other non-mergeable inline and // is an insertion position at the boundary of such an inline. internal static bool IsPositionAtNonMergeableInlineBoundary(ITextPointer position) { return IsAtNonMergeableInlineStart(position) || IsAtNonMergeableInlineEnd(position); } internal static bool IsAtFormatNormalizedPosition(ITextPointer position, LogicalDirection direction) { return IsAtNormalizedPosition(position, direction, false /* respectCaretUnitBoundaries */); } internal static bool IsAtInsertionPosition(ITextPointer position, LogicalDirection direction) { return IsAtNormalizedPosition(position, direction, true /* respectCaretUnitBoundaries */); } internal static bool IsAtNormalizedPosition(ITextPointer position, LogicalDirection direction, bool respectCaretUnitBoundaries) { if (!IsAtNormalizedPosition(position, respectCaretUnitBoundaries)) { return false; } // if (position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart && position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd) { return true; } // Check if there is no any formatting tags in the given direction if (TextSchema.IsFormattingType(position.GetElementType(direction))) { position = position.CreatePointer(); while (TextSchema.IsFormattingType(position.GetElementType(direction))) { position.MoveToNextContextPosition(direction); } if (IsAtNormalizedPosition(position, respectCaretUnitBoundaries)) { // So there is a possibility to move over formatting tags only // and reach some insertion position. This means // that our position was not normalized in the given direction. return false; } } return true; } //internal static int GetOffset(ITextPointer thisPosition) { return thisPosition.TextContainer.Start.GetOffsetToPosition(thisPosition); } /// /// Returns true if the position is at the word boundary /// in the given direction. /// /// ITextPointer to examine. /// /// If insideWordDirection == LogicalDirection.Forward, returns true iff the /// position is at the beginning of a word. /// /// If direction == LogicalDirection.Backward, returns true iff the /// position is at the end of a word. /// ///internal static bool IsAtWordBoundary(ITextPointer thisPosition, LogicalDirection insideWordDirection) { bool isAtWordBoundary; ITextPointer navigator = thisPosition.CreatePointer(); // Skip over any formatting. if (navigator.GetPointerContext(insideWordDirection) != TextPointerContext.Text) { navigator.MoveToInsertionPosition(insideWordDirection); } if (navigator.GetPointerContext(insideWordDirection) == TextPointerContext.Text) { // We're adjacent to text, so use the word breaker. char[] text; int position; GetWordBreakerText(thisPosition, out text, out position); isAtWordBoundary = SelectionWordBreaker.IsAtWordBoundary(text, position, insideWordDirection); } else { // If we're not adjacent to text then we always want to consider this // position a "word break" -- as far as selection is concerned. In practice, // we're most likely next to an embedded object or block boundary. isAtWordBoundary = true; } return isAtWordBoundary; } /// /// Returns a TextSegment covering the word containing this TextPointer. /// ////// If this TextPointer is between two words, the following word is returned. /// /// The return value includes trailing whitespace, if any. /// internal static TextSegment GetWordRange(ITextPointer thisPosition) { return GetWordRange(thisPosition, LogicalDirection.Forward); } ////// Returns a TextSegment covering the word containing this TextPointer. /// ////// If this TextPointer is between two words, direction specifies whether /// the preceeding or following word is returned. /// /// The return value includes trailing whitespace, if any. /// internal static TextSegment GetWordRange(ITextPointer thisPosition, LogicalDirection direction) { if (!thisPosition.IsAtInsertionPosition) { // Normalize original text pointer so it is at an insertion position. thisPosition = thisPosition.GetInsertionPosition(direction); } if (!thisPosition.IsAtInsertionPosition) { // In case there is no insertion position in the entire document, return an empty segment. // GetInsertionPosition() guarantees that navigator is moved back to original position. return new TextSegment(thisPosition, thisPosition); } // Find the next word end edge. ITextPointer navigator = thisPosition.CreatePointer(); bool moved = MoveToNextWordBoundary(navigator, direction); ITextPointer wordEnd = navigator; // Find the corresponding word start edge. ITextPointer wordStart; if (moved && IsAtWordBoundary(thisPosition, /*insideWordDirection:*/LogicalDirection.Forward)) { wordStart = thisPosition; } else { navigator = thisPosition.CreatePointer(); MoveToNextWordBoundary(navigator, direction == LogicalDirection.Backward ? LogicalDirection.Forward : LogicalDirection.Backward); wordStart = navigator; } if (direction == LogicalDirection.Backward) { // If this is a backward search, need to swap start/end pointers. navigator = wordStart; wordStart = wordEnd; wordEnd = navigator; } // Make sure that we are not crossing any block boundaries. wordStart = RestrictWithinBlock(thisPosition, wordStart, LogicalDirection.Backward); wordEnd = RestrictWithinBlock(thisPosition, wordEnd, LogicalDirection.Forward); // Make sure that positions do not cross - as in TextRangeBase.cs if (wordStart.CompareTo(wordEnd) < 0) { wordStart = wordStart.GetFrozenPointer(LogicalDirection.Backward); wordEnd = wordEnd.GetFrozenPointer(LogicalDirection.Forward); } else { wordStart = wordEnd.GetFrozenPointer(LogicalDirection.Backward); wordEnd = wordStart; } Invariant.Assert(wordStart.CompareTo(wordEnd) <= 0, "expecting wordStart <= wordEnd"); return new TextSegment(wordStart, wordEnd); } private static ITextPointer RestrictWithinBlock(ITextPointer position, ITextPointer limit, LogicalDirection direction) { Invariant.Assert(!(direction == LogicalDirection.Backward) || position.CompareTo(limit) >= 0, "for backward direction position must be >= than limit"); Invariant.Assert(!(direction == LogicalDirection.Forward) || position.CompareTo(limit) <= 0, "for forward direcion position must be <= than linit"); while (direction == LogicalDirection.Backward ? position.CompareTo(limit) > 0 : position.CompareTo(limit) < 0) { TextPointerContext context = position.GetPointerContext(direction); if (context == TextPointerContext.ElementStart || context == TextPointerContext.ElementEnd) { Type elementType = position.GetElementType(direction); if (!typeof(Inline).IsAssignableFrom(elementType)) { limit = position; break; } } else if (context == TextPointerContext.EmbeddedElement) { limit = position; break; } position = position.GetNextContextPosition(direction); } // Return normalized position - in the direction towards a center position. return limit.GetInsertionPosition(direction == LogicalDirection.Backward ? LogicalDirection.Forward : LogicalDirection.Backward); } //// Checks if there is a Environment.NewLine symbol immediately // next to the position. Used only for plain text scenarios. // RichText case will always return false. // internal static bool IsNextToPlainLineBreak(ITextPointer thisPosition, LogicalDirection direction) { char[] textBuffer = new char[2]; int actualCount = thisPosition.GetTextInRun(direction, textBuffer, /*startIndex:*/0, /*count:*/2); return (actualCount == 1 && IsCharUnicodeNewLine(textBuffer[0])) || (actualCount == 2 && ( (direction == LogicalDirection.Backward && IsCharUnicodeNewLine(textBuffer[1])) || (direction == LogicalDirection.Forward && IsCharUnicodeNewLine(textBuffer[0])) ) ); } // Following Unicode newline guideline from http://www.unicode.org/unicode/standard/reports/tr13/tr13-5.html // To the standard list requirements we added '\v' and '\f' internal static Char[] NextLineCharacters = new char[] { '\n', '\r', '\v', '\f', '\u0085' /*NEL*/, '\u2028' /*LS*/, '\u2029' /*PS*/ }; // Returns true if a specified char matches the Unicode definition of "newline". internal static bool IsCharUnicodeNewLine(char ch) { return Array.IndexOf(NextLineCharacters, ch) > -1; } ////// Returns true if the position is adjacent to a LineBreak element, /// ignoring any intermediate formatting elements. /// internal static bool IsNextToRichLineBreak(ITextPointer thisPosition, LogicalDirection direction) { return IsNextToRichBreak(thisPosition, direction, typeof(LineBreak)); } ////// Returns true if the position is adjacent to a Paragraph element, /// ignoring any intermediate formatting elements. /// internal static bool IsNextToParagraphBreak(ITextPointer thisPosition, LogicalDirection direction) { return IsNextToRichBreak(thisPosition, direction, typeof(Paragraph)); } //// Checks if there is a "paragraph break" symbol immediately // before the position. Paragraph break is either plaintext // newline character or a combination equivalent to // [close-paragraph;open-paragraph] tag combination // internal static bool IsNextToAnyBreak(ITextPointer thisPosition, LogicalDirection direction) { if (!thisPosition.IsAtInsertionPosition) { thisPosition = thisPosition.GetInsertionPosition(direction); } return (IsNextToPlainLineBreak(thisPosition, direction) || IsNextToRichBreak(thisPosition, direction, null)); } ////// Checks if line wrapping is happening at this position /// internal static bool IsAtLineWrappingPosition(ITextPointer position, ITextView textView) { Invariant.Assert(position != null, "null check: position"); if (!position.HasValidLayout) { return false; } Invariant.Assert(textView != null, "textView cannot be null because the position has valid layout"); TextSegment lineSegment = textView.GetLineRange(position); if (lineSegment.IsNull) { return false; } bool isAtLineWrappingPosition = position.LogicalDirection == LogicalDirection.Forward ? position.CompareTo(lineSegment.Start) == 0 : position.CompareTo(lineSegment.End) == 0; return isAtLineWrappingPosition; } // Position at row end (immediately before Row closing tag) is a valid stopper for a caret. // Editing operations are restricted here (e.g. typing should automatically jump // to the following character position. // This property identifies such special position. internal static bool IsAtRowEnd(ITextPointer thisPosition) { return typeof(TableRow).IsAssignableFrom(thisPosition.ParentType) && thisPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd && thisPosition.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.ElementStart; // Note that only non-empty TableRows are good for insertion positions. // Totally empty TableRow is treated as any other incomplete content - not an insertion. } // Position at document end - after the last paragraph/list/table is // considered as valid insertion point position. // It has though a special behavior for caret positioning and text insertion internal static bool IsAfterLastParagraph(ITextPointer thisPosition) { return thisPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.None && thisPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementEnd && !typeof(Inline).IsAssignableFrom(thisPosition.GetElementType(LogicalDirection.Backward)); } // Returns true if pointer is at the start of a paragraph. internal static bool IsAtParagraphOrBlockUIContainerStart(ITextPointer pointer) { // Is pointer at a potential paragraph position? if (IsAtPotentialParagraphPosition(pointer)) { return true; } // Can you find astart tag looking backwards? // Loop to skip multiple formatting opening tags, never crossing parent element boundary. while (pointer.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) { if (TextSchema.IsParagraphOrBlockUIContainer(pointer.ParentType)) { return true; } pointer = pointer.GetNextContextPosition(LogicalDirection.Backward); } return false; } // Returns a ListItem from a current pointer if it exists as a parent of current paragraph. // Otherwise returns null. internal static ListItem GetListItem(TextPointer pointer) { if (pointer.Parent is ListItem) { return (ListItem)pointer.Parent; } Block paragraphOrBlockUIContainer = pointer.ParagraphOrBlockUIContainer; return paragraphOrBlockUIContainer == null ? null : (paragraphOrBlockUIContainer.Parent as ListItem); } // Returns a ListItem if it exists and the current paragraph is the first block in it. // Otherwise returns null. // Non-null ImmediateListItem means that current paragraph has visual bullet on it, // so it must be treated as a list item from editing perspective. internal static ListItem GetImmediateListItem(TextPointer position) { if (position.Parent is ListItem) { return (ListItem)position.Parent; } Block paragraphOrBlockUIContainer = position.ParagraphOrBlockUIContainer; if (paragraphOrBlockUIContainer != null && paragraphOrBlockUIContainer.Parent is ListItem && paragraphOrBlockUIContainer.ElementStart.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) { return (ListItem)paragraphOrBlockUIContainer.Parent; } return null; } // Returns true if position is parented by a ListItem element, which is empty. // Checks specifically for element emptyness, . internal static bool IsInEmptyListItem(TextPointer position) { ListItem listItem = position.Parent as ListItem; return listItem != null && listItem.IsEmpty; } //This overload is to cover for a bug that prevents line by line navigation in Fixed documents PS#1742102 //MoveToLineBoundary returns the previous line if the first position on the next line is inside a Hyperlink tag //resulting in infinite loops, no line navigation etc. internal static int MoveToLineBoundary(ITextPointer thisPointer, ITextView textView, int count) { return MoveToLineBoundary(thisPointer, textView, count, false /* respectNonMeargeableInlineStart */); } // internal static int MoveToLineBoundary(ITextPointer thisPointer, ITextView textView, int count, bool respectNonMeargeableInlineStart) { ITextPointer position; double newSuggestedX; Invariant.Assert(!thisPointer.IsFrozen, "Can't reposition a frozen pointer!"); Invariant.Assert(textView != null, "Null TextView!"); // Did you check ITextPointer.HasValidLayout? position = textView.GetPositionAtNextLine(thisPointer, Double.NaN, count, out newSuggestedX, out count); if (!position.IsAtInsertionPosition) { if (!respectNonMeargeableInlineStart || (!IsAtNonMergeableInlineStart(position) && !IsAtNonMergeableInlineEnd(position))) { position.MoveToInsertionPosition(position.LogicalDirection); } } if (IsAtRowEnd(position)) { // We will find outselves at a row end when we have incomplete // markup like // // // // In that case the end-of-row is the entire line. thisPointer.MoveToPosition(position); thisPointer.SetLogicalDirection(position.LogicalDirection); } else { TextSegment lineRange = textView.GetLineRange(position); if (!lineRange.IsNull) { thisPointer.MoveToPosition(lineRange.Start); thisPointer.SetLogicalDirection(lineRange.Start.LogicalDirection); } else if (count > 0) { // It is possible to get a non-zero return value from ITextView.GetPositionAtNextLine // when moving into a BlockUIContainer. The container is the "next line" but does // not contain any lines itself -- GetLineRange will return null. thisPointer.MoveToPosition(position); thisPointer.SetLogicalDirection(position.LogicalDirection); } } return count; } internal static Rect GetCharacterRect(ITextPointer thisPointer, LogicalDirection direction) { return GetCharacterRect(thisPointer, direction, /*transformToUiScope*/true); } // internal static Rect GetCharacterRect(ITextPointer thisPointer, LogicalDirection direction, bool transformToUiScope) { ITextView textView = thisPointer.TextContainer.TextView; Invariant.Assert(textView != null, "Null TextView!"); // Did you check ITextPointer.HasValidLayout? Invariant.Assert(textView.RenderScope != null, "Null RenderScope"); Invariant.Assert(thisPointer.TextContainer != null, "Null TextContainer"); Invariant.Assert(thisPointer.TextContainer.Parent != null, "Null parent of TextContainer"); // Try to ask for a Rect from an insertion position. if (!thisPointer.IsAtInsertionPosition) { ITextPointer insertionPosition = thisPointer.GetInsertionPosition(direction); if (insertionPosition != null) { thisPointer = insertionPosition; } } Rect rect = textView.GetRectangleFromTextPosition(thisPointer.CreatePointer(direction)); if (transformToUiScope) { Visual templatedParent; if (thisPointer.TextContainer.Parent is FlowDocument && textView.RenderScope is FlowDocumentView) { // templatedParent = ((FlowDocumentView)textView.RenderScope).TemplatedParent as Visual; if (templatedParent == null && ((FlowDocumentView)textView.RenderScope).Parent is FrameworkElement) { templatedParent = ((FrameworkElement)((FlowDocumentView)textView.RenderScope).Parent).TemplatedParent as Visual; } } else if (thisPointer.TextContainer.Parent is Visual) { Invariant.Assert(textView.RenderScope == thisPointer.TextContainer.Parent || ((Visual)thisPointer.TextContainer.Parent).IsAncestorOf( /*descendant:*/textView.RenderScope), "Unexpected location of RenderScope within visual tree"); templatedParent = (Visual)thisPointer.TextContainer.Parent; } else { templatedParent = null; } if (templatedParent != null && templatedParent.IsAncestorOf( /*descendant:*/textView.RenderScope)) { // translate the rect from renderscope to uiscope coordinate system (from FlowDocumentView to RichTextBox) GeneralTransform transformFromRenderToUiScope = textView.RenderScope.TransformToAncestor(/*ancestor:*/templatedParent); rect = transformFromRenderToUiScope.TransformBounds(rect); } } return rect; } // Move to the closest insertion position, treating all unicode code points // as valid insertion positions. A useful performance win over // MoveToNextInsertionPosition when only formatting scopes are important. internal static bool MoveToFormatNormalizedPosition(ITextPointer thisNavigator, LogicalDirection direction) { return NormalizePosition(thisNavigator, direction, false /* respectCaretUnitBoundaries */); } /// /// Moves the navigator to a closest insertion position (caret stop position). /// The parameter direction is used in cases of ambiguity: /// when two caret positions are separated by only formatting tags, /// or when starting position is beteen end and start of two consequitive blocks /// (say, between Paragraphs). /// In such cases of ambiguity the position in a given direction is chosen. /// internal static bool MoveToInsertionPosition(ITextPointer thisNavigator, LogicalDirection direction) { return NormalizePosition(thisNavigator, direction, true /* respectCaretUnitBoundaries */); } ////// Advances this TextNavigator by a count number of characters. /// /// ITextPointer to advance. /// /// A direction in which to search a next characters. /// ////// True if the navigator is advanced, false if the end of document is /// encountered and the navigator is not repositioned. /// ////// A "character" in this context is a sequence of one or several text /// symbols: one or more Unicode code points may be a character, every /// embedded object is a character, a sequence of closing block tags /// followed by opening block tags may also be a unit. Formatting tags /// do not contribute in any unit. /// internal static bool MoveToNextInsertionPosition(ITextPointer thisNavigator, LogicalDirection direction) { Invariant.Assert(!thisNavigator.IsFrozen, "Can't reposition a frozen pointer!"); bool moved = true; int increment = direction == LogicalDirection.Forward ? +1 : -1; ITextPointer initialPosition = thisNavigator.CreatePointer(); if (!IsAtInsertionPosition(thisNavigator)) { // If the TextPointer is not currently at an insertion position, // move the TextPointer to the next insertion position in // the indicated direction, just like the MoveToInsertionPosition method. if (!MoveToInsertionPosition(thisNavigator, direction)) { // No insertion position in all content. MoveToInsertionPosition() guarantees that navigator is moved back to initial position. moved = false; goto Exit; } if ((direction == LogicalDirection.Forward && initialPosition.CompareTo(thisNavigator) < 0) || (direction == LogicalDirection.Backward && thisNavigator.CompareTo(initialPosition) < 0)) { // We have found an insertion position in requested direction. goto Exit; } } // Start with skipping character formatting tags in this direction while (TextSchema.IsFormattingType(thisNavigator.GetElementType(direction))) { thisNavigator.MoveByOffset(increment); } do { if (thisNavigator.GetPointerContext(direction) != TextPointerContext.None) { thisNavigator.MoveByOffset(increment); } else { // No insertion position in this direction; Move back thisNavigator.MoveToPosition(initialPosition); moved = false; goto Exit; } } while (!IsAtInsertionPosition(thisNavigator)); // We must leave position normalized in backward direction if (direction == LogicalDirection.Backward) { // For this we must skip character formatting tags if we have any while (TextSchema.IsFormattingType(thisNavigator.GetElementType(direction))) { thisNavigator.MoveByOffset(increment); } // However if it is block start we should back off TextPointerContext context = thisNavigator.GetPointerContext(direction); if (context == TextPointerContext.ElementStart || context == TextPointerContext.None) { increment = -increment; while (TextSchema.IsFormattingType(thisNavigator.GetElementType(LogicalDirection.Forward)) && !IsAtInsertionPosition(thisNavigator)) { thisNavigator.MoveByOffset(increment); } } } Exit: if (moved) { if (direction == LogicalDirection.Forward) { Invariant.Assert(thisNavigator.CompareTo(initialPosition) > 0, "thisNavigator is expected to be moved from initialPosition - 1"); } else { Invariant.Assert(thisNavigator.CompareTo(initialPosition) < 0, "thisNavigator is expected to be moved from initialPosition - 2"); } } else { Invariant.Assert(thisNavigator.CompareTo(initialPosition) == 0, "thisNavigator must stay at initial position"); } return moved; } ////// Moves the navigator in the given direction to a position of the next /// word boundary. /// /// ITextPointer to advance. /// /// Direction to move. /// ///// internal static bool MoveToNextWordBoundary(ITextPointer thisNavigator, LogicalDirection movingDirection) { int moveCounter = 0; Invariant.Assert(!thisNavigator.IsFrozen, "Can't reposition a frozen pointer!"); ITextPointer startPosition = thisNavigator.CreatePointer(); while (thisNavigator.MoveToNextInsertionPosition(movingDirection)) { moveCounter++; // Need to break the loop for weird case when there is no word break in text content. // When the word looks too long, consider end of textRun as a word break. // if (moveCounter > 64) // 64 was taken as a random number. Probably not big enough though... { thisNavigator.MoveToPosition(startPosition); thisNavigator.MoveToNextContextPosition(movingDirection); break; } if (IsAtWordBoundary(thisNavigator, /*insideWordDirection:*/LogicalDirection.Forward)) { // Note that we always use Forward direction for word orientation. break; } } return moveCounter > 0; } // internal static ITextPointer GetFrozenPointer(ITextPointer thisPointer, LogicalDirection logicalDirection) { ITextPointer frozenPointer; if (thisPointer.IsFrozen && thisPointer.LogicalDirection == logicalDirection) { frozenPointer = thisPointer; } else { frozenPointer = thisPointer.CreatePointer(logicalDirection); frozenPointer.Freeze(); } return frozenPointer; } /// internal static bool ValidateLayout(ITextPointer thisPointer, ITextView textView) { if (textView == null) { return false; } return textView.Validate(thisPointer); } #endregion Internal methods //------------------------------------------------------ // // Private Methods // //----------------------------------------------------- #region Private Methods // Worker for MoveToNextFormatNormalizedPosition/MoveToNextInsertionPosition. private static bool NormalizePosition(ITextPointer thisNavigator, LogicalDirection direction, bool respectCaretUnitBoundaries) { Invariant.Assert(!thisNavigator.IsFrozen, "Can't reposition a frozen pointer!"); int symbolCount = 0; int increment; LogicalDirection oppositeDirection; TextPointerContext directEnterScope; TextPointerContext oppositeEnterScope; if (direction == LogicalDirection.Forward) { increment = +1; oppositeDirection = LogicalDirection.Backward; directEnterScope = TextPointerContext.ElementStart; oppositeEnterScope = TextPointerContext.ElementEnd; } else { increment = -1; oppositeDirection = LogicalDirection.Forward; directEnterScope = TextPointerContext.ElementEnd; oppositeEnterScope = TextPointerContext.ElementStart; } // When the pointer appears in between structural tags we need to start // from sliding into the deepest possible position without // leaving any structural units. We need to do that only // if we are not at insertion position already. if (!IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries)) { // Go inside an innermost structured element (non-inline) while ( thisNavigator.GetPointerContext(direction) == directEnterScope && !typeof(Inline).IsAssignableFrom(thisNavigator.GetElementType(direction)) && !IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries)) { thisNavigator.MoveToNextContextPosition(direction); symbolCount += increment; } while ( thisNavigator.GetPointerContext(oppositeDirection) == oppositeEnterScope && !typeof(Inline).IsAssignableFrom(thisNavigator.GetElementType(oppositeDirection)) && !IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries)) { thisNavigator.MoveToNextContextPosition(oppositeDirection); symbolCount -= increment; } } // Get out of a Hyperlink, etc. inner edge. symbolCount = LeaveNonMergeableInlineBoundary(thisNavigator, direction, symbolCount); // Get out of a compound sequence if any. if (respectCaretUnitBoundaries) { while (!IsAtCaretUnitBoundary(thisNavigator)) { symbolCount += increment; thisNavigator.MoveByOffset(increment); } } // Here is the core part of this method's logic - skipping all formatting tags in the given direction. // Skip character formatting tags if they are present in this direction. // Even if an insertion position can be in the middle of this formatting sequence, // we want to skip it all and reach the farthest possible insertion position in that direction. // Such approach guarantees that repeated calls of this normalization will give the same reauls. // In case if there is an inserrtion position in the middle (say, in empty Run), // the loop moving in opposite direction below will find it if needed. while (TextSchema.IsMergeableInline(thisNavigator.GetElementType(direction))) { thisNavigator.MoveToNextContextPosition(direction); symbolCount += increment; } if (!IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries)) { // If still not at insertion point, try skipping inline tags in the opposite direction // now possibly stopping inside of empty element while (!IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries) && TextSchema.IsMergeableInline(thisNavigator.GetElementType(oppositeDirection))) { thisNavigator.MoveToNextContextPosition(oppositeDirection); symbolCount -= increment; } // If still not at insertion point, then try harder - skipping block tags // First in "preferred" direction while (!IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries) && thisNavigator.MoveToNextContextPosition(direction)) { symbolCount += increment; } // And finally in apposite direction while (!IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries) && thisNavigator.MoveToNextContextPosition(oppositeDirection)) { symbolCount -= increment; } if (!IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries)) { // When there is no insertion positions in the whole document // we return the position back to its original place. thisNavigator.MoveByOffset(-symbolCount); } } return symbolCount != 0; } // If thisNavigator is at the inner edge of a non-mergeable inline, this method // repositions it outside the scope of the non-mergeable. // Otherwise, this method does nothing. private static int LeaveNonMergeableInlineBoundary(ITextPointer thisNavigator, LogicalDirection direction, int symbolCount) { if (IsAtNonMergeableInlineStart(thisNavigator)) { if (direction == LogicalDirection.Forward && IsAtNonMergeableInlineEnd(thisNavigator)) { symbolCount += LeaveNonMergeableAncestor(thisNavigator, LogicalDirection.Forward); } else { symbolCount += LeaveNonMergeableAncestor(thisNavigator, LogicalDirection.Backward); } } else if (IsAtNonMergeableInlineEnd(thisNavigator)) { if (direction == LogicalDirection.Backward && IsAtNonMergeableInlineStart(thisNavigator)) { symbolCount += LeaveNonMergeableAncestor(thisNavigator, LogicalDirection.Backward); } else { symbolCount += LeaveNonMergeableAncestor(thisNavigator, LogicalDirection.Forward); } } return symbolCount; } // Exits the scope of a non-mergeable inline with inner edge in the direction indicated. private static int LeaveNonMergeableAncestor(ITextPointer thisNavigator, LogicalDirection direction) { int symbolCount = 0; int increment = (direction == LogicalDirection.Forward) ? +1 : -1; while (TextSchema.IsMergeableInline(thisNavigator.ParentType)) { thisNavigator.MoveToNextContextPosition(direction); symbolCount += increment; } thisNavigator.MoveToNextContextPosition(direction); symbolCount += increment; return symbolCount; } // Worker for IsAtFormatNormalizedPosition/IsAtInsertionPosition. private static bool IsAtNormalizedPosition(ITextPointer position, bool respectCaretUnitBoundaries) { if (IsPositionAtNonMergeableInlineBoundary(position)) { // The inner edge of a Hyperlink is not a valid insertion position. return false; } else if (TextSchema.IsValidChild(/*position*/position, /*childType*/typeof(string))) { return respectCaretUnitBoundaries ? IsAtCaretUnitBoundary(position) : true; } else { // Special positions outside of Run elements that allow caret stops return IsAtRowEnd(position) || IsAtPotentialRunPosition(position) || IsBeforeFirstTable(position) || IsInBlockUIContainer(position); } } // Returns true if the position is on the caret unit boundary. // Call TextView's IsAtCaretUnitBoundary if TextView is valid for this position // and it appears strictly within text run. // We consider all markup-boundary positions as caret unit boundaries. // If TextView information is not available call IsInsideCompoundSequence. private static bool IsAtCaretUnitBoundary(ITextPointer position) { bool isAtCaretUnitBoundary; TextPointerContext forwardContext = position.GetPointerContext(LogicalDirection.Forward); TextPointerContext backwardContext = position.GetPointerContext(LogicalDirection.Backward); if (backwardContext == TextPointerContext.Text && forwardContext == TextPointerContext.Text) { if (position.HasValidLayout) { // Check the insertion position with TextView's IsAtCaretUnitBoundary // that will acurately check the caret unit bounday for surrogate and international // characters isAtCaretUnitBoundary = position.IsAtCaretUnitBoundary; } else { // Check the insertion position with the internal compound sequence isAtCaretUnitBoundary = !IsInsideCompoundSequence(position); } } else { isAtCaretUnitBoundary = true; } return isAtCaretUnitBoundary; } // Returns true if the position is inside of a pair of surrogate characters // or inside of Newline sequence "\r\n". // Such position is not valid position for caret stopping or for text insertion. private static bool IsInsideCompoundSequence(ITextPointer position) { // OK, so we're surrounded by text runs (possibly empty), try getting a character // in each direction -- it's OK to position the caret if there's no characters // before or after it Char[] neighborhood = new char[2]; if (position.GetTextInRun(LogicalDirection.Backward, neighborhood, 0, 1) == 1 && position.GetTextInRun(LogicalDirection.Forward, neighborhood, 1, 1) == 1) { if (Char.IsSurrogatePair(neighborhood[0], neighborhood[1]) || neighborhood[0] == '\r' && neighborhood[1] == '\n') { return true; } // Check for combining marks. // // See Unicode 3.1, Section 3.5 (Combination), D13 and D14 for // strict definitions of "combining character" and "base character". // // The CLR source for StringInfo is also informative. // // In brief: we're looking for a character followed by a // combining mark. // UnicodeCategory category1 = Char.GetUnicodeCategory(neighborhood[1]); if (category1 == UnicodeCategory.SpacingCombiningMark || category1 == UnicodeCategory.NonSpacingMark || category1 == UnicodeCategory.EnclosingMark) { UnicodeCategory category0 = Char.GetUnicodeCategory(neighborhood[0]); if (category0 != UnicodeCategory.Control && category0 != UnicodeCategory.Format && category0 != UnicodeCategory.OtherNotAssigned) { return true; } } } return false; } // Initializes an array with the minimum necessary text to support a call to // SelectionWordBreaker.IsAtWordBoundary. // // position on exit holds the offset into the text array corresponding to // pointer's location in the document. // // Called by IsAtWordBoundary. private static void GetWordBreakerText(ITextPointer pointer, out char[] text, out int position) { char[] preceedingText = new char[SelectionWordBreaker.MinContextLength]; char[] followingText = new char[SelectionWordBreaker.MinContextLength]; int preceedingCount = 0; int followingCount = 0; int runLength; ITextPointer navigator; navigator = pointer.CreatePointer(); // Try to back up SelectionWordBreaker.MinContextLength chars, ignoring formatting. do { runLength = Math.Min(navigator.GetTextRunLength(LogicalDirection.Backward), SelectionWordBreaker.MinContextLength - preceedingCount); preceedingCount += runLength; navigator.MoveByOffset(-runLength); navigator.GetTextInRun(LogicalDirection.Forward, preceedingText, SelectionWordBreaker.MinContextLength - preceedingCount, runLength); if (preceedingCount == SelectionWordBreaker.MinContextLength) break; // Skip over any formatting. navigator.MoveToInsertionPosition(LogicalDirection.Backward); } while (navigator.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text); navigator.MoveToPosition(pointer); // Try to advance SelectionWordBreaker.MinContextLength chars, ignoring formatting. do { runLength = Math.Min(navigator.GetTextRunLength(LogicalDirection.Forward), SelectionWordBreaker.MinContextLength - followingCount); navigator.GetTextInRun(LogicalDirection.Forward, followingText, followingCount, runLength); followingCount += runLength; if (followingCount == SelectionWordBreaker.MinContextLength) break; navigator.MoveByOffset(runLength); // Skip over any formatting. navigator.MoveToInsertionPosition(LogicalDirection.Forward); } while (navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text); // Combine the preceeding and following text into a single array. text = new char[preceedingCount + followingCount]; Array.Copy(preceedingText, SelectionWordBreaker.MinContextLength - preceedingCount, text, 0, preceedingCount); Array.Copy(followingText, 0, text, preceedingCount, followingCount); position = preceedingCount; } // Worker for IsAtNonMergeableInlineStart/IsAtNonMergeableInlineEnd. private static bool IsAtNonMergeableInlineEdge(ITextPointer position, LogicalDirection direction) { BorderingElementCategory elementType = GetBorderingElementCategory(position, direction); if (elementType == BorderingElementCategory.MergeableScopingInline) { ITextPointer navigator = position.CreatePointer(); do { navigator.MoveToNextContextPosition(direction); } while ((elementType = GetBorderingElementCategory(navigator, direction)) == BorderingElementCategory.MergeableScopingInline); } return (elementType == BorderingElementCategory.NonMergeableScopingInline); } // Tests for the presence of a non-mergeable Inline bordering a position. // Helper for IsAtNonMergeableInlineEdge. private static BorderingElementCategory GetBorderingElementCategory(ITextPointer position, LogicalDirection direction) { TextPointerContext context = (direction == LogicalDirection.Forward) ? TextPointerContext.ElementEnd : TextPointerContext.ElementStart; BorderingElementCategory category; if (position.GetPointerContext(direction) != context || !typeof(Inline).IsAssignableFrom(position.ParentType)) { category = BorderingElementCategory.NotScopingInline; } else if (TextSchema.IsMergeableInline(position.ParentType)) { category = BorderingElementCategory.MergeableScopingInline; } else { category = BorderingElementCategory.NonMergeableScopingInline; } return category; } // Returns true if the position is adjacent to a LineBreak or Paragraph element, // ignoring any intermediate formatting elements. // // If lineBreakType is null, any line break element is considered valid. private static bool IsNextToRichBreak(ITextPointer thisPosition, LogicalDirection direction, Type lineBreakType) { Invariant.Assert(lineBreakType == null || lineBreakType == typeof(LineBreak) || lineBreakType == typeof(Paragraph)); bool result = false; while (true) { Type neighbor = thisPosition.GetElementType(direction); if (lineBreakType == null) { if (typeof(LineBreak).IsAssignableFrom(neighbor) || typeof(Paragraph).IsAssignableFrom(neighbor)) { result = true; break; } } else if (lineBreakType.IsAssignableFrom(neighbor)) { result = true; break; } if (!TextSchema.IsFormattingType(neighbor)) break; thisPosition = thisPosition.GetNextContextPosition(direction); } return result; } #endregion Private methods //------------------------------------------------------ // // Private Types // //------------------------------------------------------ #region Private Types // Element types for GetBorderingElementCategory. private enum BorderingElementCategory { MergeableScopingInline, NonMergeableScopingInline, NotScopingInline }; #endregion Private Types } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // // File: TextPointerBase.cs // // Copyright (C) Microsoft Corporation. All rights reserved. // // Description: ITextPointer helper methods. // //--------------------------------------------------------------------------- namespace System.Windows.Documents { using System; using MS.Internal; using MS.Internal.Documents; using System.Globalization; using System.Windows.Media; // Matrix using System.Windows.Controls; // TextBlock // ITextPointer helper methods. internal static class TextPointerBase { //----------------------------------------------------- // // Internal Methods // //----------------------------------------------------- #region Internal Methods /// /// Returns the preceeding TextPointer of a pair. /// /// /// TextPointer to compare. /// /// /// TextPointer to compare. /// internal static ITextPointer Min(ITextPointer position1, ITextPointer position2) { return position1.CompareTo(position2) <= 0 ? position1 : position2; } ////// Returns the following TextPointer of a pair. /// /// /// TextPointer to compare. /// /// /// TextPointer to compare. /// internal static ITextPointer Max(ITextPointer position1, ITextPointer position2) { return position1.CompareTo(position2) >= 0 ? position1 : position2; } // Worker for GetText, accepts any ITextPointer. internal static string GetTextInRun(ITextPointer position, LogicalDirection direction) { char[] text; int textLength; int getTextLength; textLength = position.GetTextRunLength(direction); text = new char[textLength]; getTextLength = position.GetTextInRun(direction, text, 0, textLength); Invariant.Assert(getTextLength == textLength, "textLengths returned from GetTextRunLength and GetTextInRun are innconsistent"); return new string(text); } // Like GetText, excepts also accepts a limit parameter -- no text is returned past // this second position. // limit may be null, in which case it is ignored. internal static int GetTextWithLimit(ITextPointer thisPointer, LogicalDirection direction, char[] textBuffer, int startIndex, int count, ITextPointer limit) { int charsCopied; if (limit == null) { // No limit, just call GetText. charsCopied = thisPointer.GetTextInRun(direction, textBuffer, startIndex, count); } else if (direction == LogicalDirection.Forward && limit.CompareTo(thisPointer) <= 0) { // Limit completely blocks the read. charsCopied = 0; } else if (direction == LogicalDirection.Backward && limit.CompareTo(thisPointer) >= 0) { // Limit completely blocks the read. charsCopied = 0; } else { int maxCount; // Get an upper bound on the amount of text to copy. // Since GetText always stops on non-text boundaries, it's // ok if the count too high, it will get truncated anyways. if (direction == LogicalDirection.Forward) { maxCount = Math.Min(count, thisPointer.GetOffsetToPosition(limit)); } else { maxCount = Math.Min(count, limit.GetOffsetToPosition(thisPointer)); } maxCount = Math.Min(count, maxCount); charsCopied = thisPointer.GetTextInRun(direction, textBuffer, startIndex, maxCount); } return charsCopied; } #if UNUSED // Returns true if the pointer is at an insertion position or next to // any unicode code point. A useful performance win over // IsAtInsertionPosition when only formatting scopes are important. internal static bool IsAtFormatNormalizedPosition(ITextPointer position) { return IsAtNormalizedPosition(position, false /* respectCaretUnitBoundaries */); } #endif ////// Return true if this TextPointer is adjacent to a character. /// ////// True if this TextPointer is adjacent to a unit boundary, false otherwise. /// internal static bool IsAtInsertionPosition(ITextPointer position) { return IsAtNormalizedPosition(position, true /* respectCaretUnitBoundaries */); } // Tests if a position is between structural symbols where Run is potentially insertable, // but not present. // Positions in between AnchoredBlocks/InlineUIContainers/ParagraphEdges/SpanEdges internal static bool IsAtPotentialRunPosition(ITextPointer position) { bool isAtPotentialRunPosition = IsAtPotentialRunPosition(position, position); if (!isAtPotentialRunPosition) { // Test for positions inside // 1. empty table cell // 2. empty list item or // 3. empty flow document // They are a valid caret stop. // Editing operations are permitted at such positions since it is a potential insertion position. isAtPotentialRunPosition = IsAtPotentialParagraphPosition(position); } return isAtPotentialRunPosition; } // Tests whether this element is a Run inserted at potential run position. // It is used in decidint whether the empty element can be removed // or not. The Run that is at potential run position should not // be removed as it would cause a loss of formatting data at this // position. internal static bool IsAtPotentialRunPosition(TextElement run) { return run is Run && run.IsEmpty && IsAtPotentialRunPosition(run.ElementStart, run.ElementEnd); } // Worker implementing IsAtPotentialRunPosition(position) method. // It is used for testing whether an empty Run element is at potential run potision. // For this purpose the method is supposed to be called with // backwardPosition==run.ElementStart and forwardPosition==run.ElementEnd. private static bool IsAtPotentialRunPosition(ITextPointer backwardPosition, ITextPointer forwardPosition) { Invariant.Assert(backwardPosition.HasEqualScope(forwardPosition)); if (TextSchema.IsValidChild(/*position*/backwardPosition, /*childType*/typeof(Run))) { Type forwardType = forwardPosition.GetElementType(LogicalDirection.Forward); Type backwardType = backwardPosition.GetElementType(LogicalDirection.Backward); if (forwardType != null && backwardType != null) { TextPointerContext forwardContext = forwardPosition.GetPointerContext(LogicalDirection.Forward); TextPointerContext backwardContext = backwardPosition.GetPointerContext(LogicalDirection.Backward); if (// Test if the position inside empty Paragraph or Span backwardContext == TextPointerContext.ElementStart && forwardContext == TextPointerContext.ElementEnd || // Test if the position between opening tag and an embedded object backwardContext == TextPointerContext.ElementStart && TextSchema.IsNonFormattingInline(forwardType) && !IsAtNonMergeableInlineStart(backwardPosition) || // Test if the position between an embedded object and a closing tag forwardContext == TextPointerContext.ElementEnd && TextSchema.IsNonFormattingInline(backwardType) && !IsAtNonMergeableInlineEnd(forwardPosition) || // Test if the position between two embedded objects backwardContext == TextPointerContext.ElementEnd && forwardContext == TextPointerContext.ElementStart && TextSchema.IsNonFormattingInline(backwardType) && TextSchema.IsNonFormattingInline(forwardType) || // Test if the position is adjacent to a non-mergeable inline (Hyperlink). backwardContext == TextPointerContext.ElementEnd && typeof(Inline).IsAssignableFrom(backwardType) && !TextSchema.IsMergeableInline(backwardType) && !typeof(Run).IsAssignableFrom(forwardType) && (forwardContext != TextPointerContext.ElementEnd || !IsAtNonMergeableInlineEnd(forwardPosition)) || forwardContext == TextPointerContext.ElementStart && typeof(Inline).IsAssignableFrom(forwardType) && !TextSchema.IsMergeableInline(forwardType) && !typeof(Run).IsAssignableFrom(backwardType) && (backwardContext != TextPointerContext.ElementStart || !IsAtNonMergeableInlineStart(backwardPosition)) ) { return true; } } } return false; } // Tests if a position is between structural symbols where Paragraph is potentially insertable, // but not present. // Positions in empty list item, table cell or flow document. internal static bool IsAtPotentialParagraphPosition(ITextPointer position) { Type parentType = position.ParentType; TextPointerContext backwardContext = position.GetPointerContext(LogicalDirection.Backward); TextPointerContext forwardContext = position.GetPointerContext(LogicalDirection.Forward); if (backwardContext == TextPointerContext.ElementStart && forwardContext == TextPointerContext.ElementEnd) { return typeof(ListItem).IsAssignableFrom(parentType) || typeof(TableCell).IsAssignableFrom(parentType); } else if (backwardContext == TextPointerContext.None && forwardContext == TextPointerContext.None) { return typeof(FlowDocumentView).IsAssignableFrom(parentType) || typeof(FlowDocument).IsAssignableFrom(parentType); } return false; } // Tests if position is before the first Table element in a collection of Blocks at that level. // We treat this as a potential insertion position to allow editing operations before the table. // This property identifies such a position. internal static bool IsBeforeFirstTable(ITextPointer position) { TextPointerContext forwardContext = position.GetPointerContext(LogicalDirection.Forward); TextPointerContext backwardContext = position.GetPointerContext(LogicalDirection.Backward); return (forwardContext == TextPointerContext.ElementStart && (backwardContext == TextPointerContext.ElementStart || backwardContext == TextPointerContext.None) && typeof(Table).IsAssignableFrom(position.GetElementType(LogicalDirection.Forward))); } // Tests if position is parented by a BlockUIContainer element. // We allow caret stops around BlockUIContainer, to permit editing operations on its boundaries. internal static bool IsInBlockUIContainer(ITextPointer position) { return (typeof(BlockUIContainer).IsAssignableFrom(position.ParentType)); } internal static bool IsAtBlockUIContainerStart(ITextPointer position) { return IsInBlockUIContainer(position) && position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart; } internal static bool IsAtBlockUIContainerEnd(ITextPointer position) { return IsInBlockUIContainer(position) && position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd; } // Walks parents from this position until it hits limiting ancestor type. private static bool IsInAncestorScope(ITextPointer position, Type allowedParentType, Type limitingType) { ITextPointer navigator = position.CreatePointer(); Type parentType = navigator.ParentType; while (parentType != null && allowedParentType.IsAssignableFrom(parentType)) { if (limitingType.IsAssignableFrom(parentType)) { return true; } navigator.MoveToElementEdge(ElementEdge.BeforeStart); parentType = navigator.ParentType; } return false; } // Returns true if position is within the scope of AnchoredBlock element internal static bool IsInAnchoredBlock(ITextPointer position) { return IsInAncestorScope(position, typeof(TextElement), typeof(AnchoredBlock)); } // Returns true if position is inside the scope of Hyperlink element internal static bool IsInHyperlinkScope(ITextPointer position) { return IsInAncestorScope(position, typeof(Inline), typeof(Hyperlink)); } // If position is before the start boundary of a non-mergeable inline (Hyperlink), // this method returns a position immediately preceding its content (which is not an insertion position). // This method will skip past leading InlineUIContainers and BlockUIContainers. Otherwise returns null. internal static ITextPointer GetFollowingNonMergeableInlineContentStart(ITextPointer position) { ITextPointer navigator = position.CreatePointer(); bool moved = false; Type elementType; while (true) { BorderingElementCategory category = GetBorderingElementCategory(navigator, LogicalDirection.Forward); // If position is before formatting closing scope, skip to outside the formatting scope. if (category == BorderingElementCategory.MergeableScopingInline) { do { navigator.MoveToNextContextPosition(LogicalDirection.Forward); } while (GetBorderingElementCategory(navigator, LogicalDirection.Forward) == BorderingElementCategory.MergeableScopingInline); moved = true; } // Skip all InlineUIContainers and BlockUIContainers. elementType = navigator.GetElementType(LogicalDirection.Forward); if (elementType == typeof(InlineUIContainer) || elementType == typeof(BlockUIContainer)) { // We are next to an InlineUIContainer/BlockUIContainer. Skip the following element. navigator.MoveToNextContextPosition(LogicalDirection.Forward); navigator.MoveToElementEdge(ElementEdge.AfterEnd); } else if (navigator.ParentType == typeof(InlineUIContainer) || navigator.ParentType == typeof(BlockUIContainer)) { // We are inside an InlineUIContainer/BlockUIContainer. Skip this element. navigator.MoveToElementEdge(ElementEdge.AfterEnd); } else { break; } // Move to next insertion position if we are done skipping a string of sequential UICs to get a // valid selection start pointer. We need to make sure we land at a position that would be given // to this function had the UICs not been there. This ensures that we end up inside Runs that // follow instead of before them, e.g.|abc instead of |abc . elementType = navigator.GetElementType(LogicalDirection.Forward); if (!(elementType == typeof(InlineUIContainer)) && !(elementType == typeof(BlockUIContainer))) { navigator.MoveToNextInsertionPosition(LogicalDirection.Forward); } moved = true; } if (typeof(Inline).IsAssignableFrom(elementType) && !TextSchema.IsMergeableInline(elementType)) { // We are adjacent to a nonmergeable inline. Find its content. // Just skip over all opening contexts. do { navigator.MoveToNextContextPosition(LogicalDirection.Forward); } while (navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart); moved = true; } return moved ? navigator : null; } // Returns true if position is at the start boundary of a non-mergeable inline ancestor (hyperlink) internal static bool IsAtNonMergeableInlineStart(ITextPointer position) { return IsAtNonMergeableInlineEdge(position, LogicalDirection.Backward); } // Returns true if position is at the end boundary of a non-mergeable inline ancestor (hyperlink) internal static bool IsAtNonMergeableInlineEnd(ITextPointer position) { return IsAtNonMergeableInlineEdge(position, LogicalDirection.Forward); } // Returns true if passed position is in scope of a Hyperlink or other non-mergeable inline and // is an insertion position at the boundary of such an inline. internal static bool IsPositionAtNonMergeableInlineBoundary(ITextPointer position) { return IsAtNonMergeableInlineStart(position) || IsAtNonMergeableInlineEnd(position); } internal static bool IsAtFormatNormalizedPosition(ITextPointer position, LogicalDirection direction) { return IsAtNormalizedPosition(position, direction, false /* respectCaretUnitBoundaries */); } internal static bool IsAtInsertionPosition(ITextPointer position, LogicalDirection direction) { return IsAtNormalizedPosition(position, direction, true /* respectCaretUnitBoundaries */); } internal static bool IsAtNormalizedPosition(ITextPointer position, LogicalDirection direction, bool respectCaretUnitBoundaries) { if (!IsAtNormalizedPosition(position, respectCaretUnitBoundaries)) { return false; } // if (position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart && position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd) { return true; } // Check if there is no any formatting tags in the given direction if (TextSchema.IsFormattingType(position.GetElementType(direction))) { position = position.CreatePointer(); while (TextSchema.IsFormattingType(position.GetElementType(direction))) { position.MoveToNextContextPosition(direction); } if (IsAtNormalizedPosition(position, respectCaretUnitBoundaries)) { // So there is a possibility to move over formatting tags only // and reach some insertion position. This means // that our position was not normalized in the given direction. return false; } } return true; } //internal static int GetOffset(ITextPointer thisPosition) { return thisPosition.TextContainer.Start.GetOffsetToPosition(thisPosition); } /// /// Returns true if the position is at the word boundary /// in the given direction. /// /// ITextPointer to examine. /// /// If insideWordDirection == LogicalDirection.Forward, returns true iff the /// position is at the beginning of a word. /// /// If direction == LogicalDirection.Backward, returns true iff the /// position is at the end of a word. /// ///internal static bool IsAtWordBoundary(ITextPointer thisPosition, LogicalDirection insideWordDirection) { bool isAtWordBoundary; ITextPointer navigator = thisPosition.CreatePointer(); // Skip over any formatting. if (navigator.GetPointerContext(insideWordDirection) != TextPointerContext.Text) { navigator.MoveToInsertionPosition(insideWordDirection); } if (navigator.GetPointerContext(insideWordDirection) == TextPointerContext.Text) { // We're adjacent to text, so use the word breaker. char[] text; int position; GetWordBreakerText(thisPosition, out text, out position); isAtWordBoundary = SelectionWordBreaker.IsAtWordBoundary(text, position, insideWordDirection); } else { // If we're not adjacent to text then we always want to consider this // position a "word break" -- as far as selection is concerned. In practice, // we're most likely next to an embedded object or block boundary. isAtWordBoundary = true; } return isAtWordBoundary; } /// /// Returns a TextSegment covering the word containing this TextPointer. /// ////// If this TextPointer is between two words, the following word is returned. /// /// The return value includes trailing whitespace, if any. /// internal static TextSegment GetWordRange(ITextPointer thisPosition) { return GetWordRange(thisPosition, LogicalDirection.Forward); } ////// Returns a TextSegment covering the word containing this TextPointer. /// ////// If this TextPointer is between two words, direction specifies whether /// the preceeding or following word is returned. /// /// The return value includes trailing whitespace, if any. /// internal static TextSegment GetWordRange(ITextPointer thisPosition, LogicalDirection direction) { if (!thisPosition.IsAtInsertionPosition) { // Normalize original text pointer so it is at an insertion position. thisPosition = thisPosition.GetInsertionPosition(direction); } if (!thisPosition.IsAtInsertionPosition) { // In case there is no insertion position in the entire document, return an empty segment. // GetInsertionPosition() guarantees that navigator is moved back to original position. return new TextSegment(thisPosition, thisPosition); } // Find the next word end edge. ITextPointer navigator = thisPosition.CreatePointer(); bool moved = MoveToNextWordBoundary(navigator, direction); ITextPointer wordEnd = navigator; // Find the corresponding word start edge. ITextPointer wordStart; if (moved && IsAtWordBoundary(thisPosition, /*insideWordDirection:*/LogicalDirection.Forward)) { wordStart = thisPosition; } else { navigator = thisPosition.CreatePointer(); MoveToNextWordBoundary(navigator, direction == LogicalDirection.Backward ? LogicalDirection.Forward : LogicalDirection.Backward); wordStart = navigator; } if (direction == LogicalDirection.Backward) { // If this is a backward search, need to swap start/end pointers. navigator = wordStart; wordStart = wordEnd; wordEnd = navigator; } // Make sure that we are not crossing any block boundaries. wordStart = RestrictWithinBlock(thisPosition, wordStart, LogicalDirection.Backward); wordEnd = RestrictWithinBlock(thisPosition, wordEnd, LogicalDirection.Forward); // Make sure that positions do not cross - as in TextRangeBase.cs if (wordStart.CompareTo(wordEnd) < 0) { wordStart = wordStart.GetFrozenPointer(LogicalDirection.Backward); wordEnd = wordEnd.GetFrozenPointer(LogicalDirection.Forward); } else { wordStart = wordEnd.GetFrozenPointer(LogicalDirection.Backward); wordEnd = wordStart; } Invariant.Assert(wordStart.CompareTo(wordEnd) <= 0, "expecting wordStart <= wordEnd"); return new TextSegment(wordStart, wordEnd); } private static ITextPointer RestrictWithinBlock(ITextPointer position, ITextPointer limit, LogicalDirection direction) { Invariant.Assert(!(direction == LogicalDirection.Backward) || position.CompareTo(limit) >= 0, "for backward direction position must be >= than limit"); Invariant.Assert(!(direction == LogicalDirection.Forward) || position.CompareTo(limit) <= 0, "for forward direcion position must be <= than linit"); while (direction == LogicalDirection.Backward ? position.CompareTo(limit) > 0 : position.CompareTo(limit) < 0) { TextPointerContext context = position.GetPointerContext(direction); if (context == TextPointerContext.ElementStart || context == TextPointerContext.ElementEnd) { Type elementType = position.GetElementType(direction); if (!typeof(Inline).IsAssignableFrom(elementType)) { limit = position; break; } } else if (context == TextPointerContext.EmbeddedElement) { limit = position; break; } position = position.GetNextContextPosition(direction); } // Return normalized position - in the direction towards a center position. return limit.GetInsertionPosition(direction == LogicalDirection.Backward ? LogicalDirection.Forward : LogicalDirection.Backward); } //// Checks if there is a Environment.NewLine symbol immediately // next to the position. Used only for plain text scenarios. // RichText case will always return false. // internal static bool IsNextToPlainLineBreak(ITextPointer thisPosition, LogicalDirection direction) { char[] textBuffer = new char[2]; int actualCount = thisPosition.GetTextInRun(direction, textBuffer, /*startIndex:*/0, /*count:*/2); return (actualCount == 1 && IsCharUnicodeNewLine(textBuffer[0])) || (actualCount == 2 && ( (direction == LogicalDirection.Backward && IsCharUnicodeNewLine(textBuffer[1])) || (direction == LogicalDirection.Forward && IsCharUnicodeNewLine(textBuffer[0])) ) ); } // Following Unicode newline guideline from http://www.unicode.org/unicode/standard/reports/tr13/tr13-5.html // To the standard list requirements we added '\v' and '\f' internal static Char[] NextLineCharacters = new char[] { '\n', '\r', '\v', '\f', '\u0085' /*NEL*/, '\u2028' /*LS*/, '\u2029' /*PS*/ }; // Returns true if a specified char matches the Unicode definition of "newline". internal static bool IsCharUnicodeNewLine(char ch) { return Array.IndexOf(NextLineCharacters, ch) > -1; } ////// Returns true if the position is adjacent to a LineBreak element, /// ignoring any intermediate formatting elements. /// internal static bool IsNextToRichLineBreak(ITextPointer thisPosition, LogicalDirection direction) { return IsNextToRichBreak(thisPosition, direction, typeof(LineBreak)); } ////// Returns true if the position is adjacent to a Paragraph element, /// ignoring any intermediate formatting elements. /// internal static bool IsNextToParagraphBreak(ITextPointer thisPosition, LogicalDirection direction) { return IsNextToRichBreak(thisPosition, direction, typeof(Paragraph)); } //// Checks if there is a "paragraph break" symbol immediately // before the position. Paragraph break is either plaintext // newline character or a combination equivalent to // [close-paragraph;open-paragraph] tag combination // internal static bool IsNextToAnyBreak(ITextPointer thisPosition, LogicalDirection direction) { if (!thisPosition.IsAtInsertionPosition) { thisPosition = thisPosition.GetInsertionPosition(direction); } return (IsNextToPlainLineBreak(thisPosition, direction) || IsNextToRichBreak(thisPosition, direction, null)); } ////// Checks if line wrapping is happening at this position /// internal static bool IsAtLineWrappingPosition(ITextPointer position, ITextView textView) { Invariant.Assert(position != null, "null check: position"); if (!position.HasValidLayout) { return false; } Invariant.Assert(textView != null, "textView cannot be null because the position has valid layout"); TextSegment lineSegment = textView.GetLineRange(position); if (lineSegment.IsNull) { return false; } bool isAtLineWrappingPosition = position.LogicalDirection == LogicalDirection.Forward ? position.CompareTo(lineSegment.Start) == 0 : position.CompareTo(lineSegment.End) == 0; return isAtLineWrappingPosition; } // Position at row end (immediately before Row closing tag) is a valid stopper for a caret. // Editing operations are restricted here (e.g. typing should automatically jump // to the following character position. // This property identifies such special position. internal static bool IsAtRowEnd(ITextPointer thisPosition) { return typeof(TableRow).IsAssignableFrom(thisPosition.ParentType) && thisPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd && thisPosition.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.ElementStart; // Note that only non-empty TableRows are good for insertion positions. // Totally empty TableRow is treated as any other incomplete content - not an insertion. } // Position at document end - after the last paragraph/list/table is // considered as valid insertion point position. // It has though a special behavior for caret positioning and text insertion internal static bool IsAfterLastParagraph(ITextPointer thisPosition) { return thisPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.None && thisPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementEnd && !typeof(Inline).IsAssignableFrom(thisPosition.GetElementType(LogicalDirection.Backward)); } // Returns true if pointer is at the start of a paragraph. internal static bool IsAtParagraphOrBlockUIContainerStart(ITextPointer pointer) { // Is pointer at a potential paragraph position? if (IsAtPotentialParagraphPosition(pointer)) { return true; } // Can you find astart tag looking backwards? // Loop to skip multiple formatting opening tags, never crossing parent element boundary. while (pointer.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) { if (TextSchema.IsParagraphOrBlockUIContainer(pointer.ParentType)) { return true; } pointer = pointer.GetNextContextPosition(LogicalDirection.Backward); } return false; } // Returns a ListItem from a current pointer if it exists as a parent of current paragraph. // Otherwise returns null. internal static ListItem GetListItem(TextPointer pointer) { if (pointer.Parent is ListItem) { return (ListItem)pointer.Parent; } Block paragraphOrBlockUIContainer = pointer.ParagraphOrBlockUIContainer; return paragraphOrBlockUIContainer == null ? null : (paragraphOrBlockUIContainer.Parent as ListItem); } // Returns a ListItem if it exists and the current paragraph is the first block in it. // Otherwise returns null. // Non-null ImmediateListItem means that current paragraph has visual bullet on it, // so it must be treated as a list item from editing perspective. internal static ListItem GetImmediateListItem(TextPointer position) { if (position.Parent is ListItem) { return (ListItem)position.Parent; } Block paragraphOrBlockUIContainer = position.ParagraphOrBlockUIContainer; if (paragraphOrBlockUIContainer != null && paragraphOrBlockUIContainer.Parent is ListItem && paragraphOrBlockUIContainer.ElementStart.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) { return (ListItem)paragraphOrBlockUIContainer.Parent; } return null; } // Returns true if position is parented by a ListItem element, which is empty. // Checks specifically for element emptyness, . internal static bool IsInEmptyListItem(TextPointer position) { ListItem listItem = position.Parent as ListItem; return listItem != null && listItem.IsEmpty; } //This overload is to cover for a bug that prevents line by line navigation in Fixed documents PS#1742102 //MoveToLineBoundary returns the previous line if the first position on the next line is inside a Hyperlink tag //resulting in infinite loops, no line navigation etc. internal static int MoveToLineBoundary(ITextPointer thisPointer, ITextView textView, int count) { return MoveToLineBoundary(thisPointer, textView, count, false /* respectNonMeargeableInlineStart */); } // internal static int MoveToLineBoundary(ITextPointer thisPointer, ITextView textView, int count, bool respectNonMeargeableInlineStart) { ITextPointer position; double newSuggestedX; Invariant.Assert(!thisPointer.IsFrozen, "Can't reposition a frozen pointer!"); Invariant.Assert(textView != null, "Null TextView!"); // Did you check ITextPointer.HasValidLayout? position = textView.GetPositionAtNextLine(thisPointer, Double.NaN, count, out newSuggestedX, out count); if (!position.IsAtInsertionPosition) { if (!respectNonMeargeableInlineStart || (!IsAtNonMergeableInlineStart(position) && !IsAtNonMergeableInlineEnd(position))) { position.MoveToInsertionPosition(position.LogicalDirection); } } if (IsAtRowEnd(position)) { // We will find outselves at a row end when we have incomplete // markup like // // // // In that case the end-of-row is the entire line. thisPointer.MoveToPosition(position); thisPointer.SetLogicalDirection(position.LogicalDirection); } else { TextSegment lineRange = textView.GetLineRange(position); if (!lineRange.IsNull) { thisPointer.MoveToPosition(lineRange.Start); thisPointer.SetLogicalDirection(lineRange.Start.LogicalDirection); } else if (count > 0) { // It is possible to get a non-zero return value from ITextView.GetPositionAtNextLine // when moving into a BlockUIContainer. The container is the "next line" but does // not contain any lines itself -- GetLineRange will return null. thisPointer.MoveToPosition(position); thisPointer.SetLogicalDirection(position.LogicalDirection); } } return count; } internal static Rect GetCharacterRect(ITextPointer thisPointer, LogicalDirection direction) { return GetCharacterRect(thisPointer, direction, /*transformToUiScope*/true); } // internal static Rect GetCharacterRect(ITextPointer thisPointer, LogicalDirection direction, bool transformToUiScope) { ITextView textView = thisPointer.TextContainer.TextView; Invariant.Assert(textView != null, "Null TextView!"); // Did you check ITextPointer.HasValidLayout? Invariant.Assert(textView.RenderScope != null, "Null RenderScope"); Invariant.Assert(thisPointer.TextContainer != null, "Null TextContainer"); Invariant.Assert(thisPointer.TextContainer.Parent != null, "Null parent of TextContainer"); // Try to ask for a Rect from an insertion position. if (!thisPointer.IsAtInsertionPosition) { ITextPointer insertionPosition = thisPointer.GetInsertionPosition(direction); if (insertionPosition != null) { thisPointer = insertionPosition; } } Rect rect = textView.GetRectangleFromTextPosition(thisPointer.CreatePointer(direction)); if (transformToUiScope) { Visual templatedParent; if (thisPointer.TextContainer.Parent is FlowDocument && textView.RenderScope is FlowDocumentView) { // templatedParent = ((FlowDocumentView)textView.RenderScope).TemplatedParent as Visual; if (templatedParent == null && ((FlowDocumentView)textView.RenderScope).Parent is FrameworkElement) { templatedParent = ((FrameworkElement)((FlowDocumentView)textView.RenderScope).Parent).TemplatedParent as Visual; } } else if (thisPointer.TextContainer.Parent is Visual) { Invariant.Assert(textView.RenderScope == thisPointer.TextContainer.Parent || ((Visual)thisPointer.TextContainer.Parent).IsAncestorOf( /*descendant:*/textView.RenderScope), "Unexpected location of RenderScope within visual tree"); templatedParent = (Visual)thisPointer.TextContainer.Parent; } else { templatedParent = null; } if (templatedParent != null && templatedParent.IsAncestorOf( /*descendant:*/textView.RenderScope)) { // translate the rect from renderscope to uiscope coordinate system (from FlowDocumentView to RichTextBox) GeneralTransform transformFromRenderToUiScope = textView.RenderScope.TransformToAncestor(/*ancestor:*/templatedParent); rect = transformFromRenderToUiScope.TransformBounds(rect); } } return rect; } // Move to the closest insertion position, treating all unicode code points // as valid insertion positions. A useful performance win over // MoveToNextInsertionPosition when only formatting scopes are important. internal static bool MoveToFormatNormalizedPosition(ITextPointer thisNavigator, LogicalDirection direction) { return NormalizePosition(thisNavigator, direction, false /* respectCaretUnitBoundaries */); } /// /// Moves the navigator to a closest insertion position (caret stop position). /// The parameter direction is used in cases of ambiguity: /// when two caret positions are separated by only formatting tags, /// or when starting position is beteen end and start of two consequitive blocks /// (say, between Paragraphs). /// In such cases of ambiguity the position in a given direction is chosen. /// internal static bool MoveToInsertionPosition(ITextPointer thisNavigator, LogicalDirection direction) { return NormalizePosition(thisNavigator, direction, true /* respectCaretUnitBoundaries */); } ////// Advances this TextNavigator by a count number of characters. /// /// ITextPointer to advance. /// /// A direction in which to search a next characters. /// ////// True if the navigator is advanced, false if the end of document is /// encountered and the navigator is not repositioned. /// ////// A "character" in this context is a sequence of one or several text /// symbols: one or more Unicode code points may be a character, every /// embedded object is a character, a sequence of closing block tags /// followed by opening block tags may also be a unit. Formatting tags /// do not contribute in any unit. /// internal static bool MoveToNextInsertionPosition(ITextPointer thisNavigator, LogicalDirection direction) { Invariant.Assert(!thisNavigator.IsFrozen, "Can't reposition a frozen pointer!"); bool moved = true; int increment = direction == LogicalDirection.Forward ? +1 : -1; ITextPointer initialPosition = thisNavigator.CreatePointer(); if (!IsAtInsertionPosition(thisNavigator)) { // If the TextPointer is not currently at an insertion position, // move the TextPointer to the next insertion position in // the indicated direction, just like the MoveToInsertionPosition method. if (!MoveToInsertionPosition(thisNavigator, direction)) { // No insertion position in all content. MoveToInsertionPosition() guarantees that navigator is moved back to initial position. moved = false; goto Exit; } if ((direction == LogicalDirection.Forward && initialPosition.CompareTo(thisNavigator) < 0) || (direction == LogicalDirection.Backward && thisNavigator.CompareTo(initialPosition) < 0)) { // We have found an insertion position in requested direction. goto Exit; } } // Start with skipping character formatting tags in this direction while (TextSchema.IsFormattingType(thisNavigator.GetElementType(direction))) { thisNavigator.MoveByOffset(increment); } do { if (thisNavigator.GetPointerContext(direction) != TextPointerContext.None) { thisNavigator.MoveByOffset(increment); } else { // No insertion position in this direction; Move back thisNavigator.MoveToPosition(initialPosition); moved = false; goto Exit; } } while (!IsAtInsertionPosition(thisNavigator)); // We must leave position normalized in backward direction if (direction == LogicalDirection.Backward) { // For this we must skip character formatting tags if we have any while (TextSchema.IsFormattingType(thisNavigator.GetElementType(direction))) { thisNavigator.MoveByOffset(increment); } // However if it is block start we should back off TextPointerContext context = thisNavigator.GetPointerContext(direction); if (context == TextPointerContext.ElementStart || context == TextPointerContext.None) { increment = -increment; while (TextSchema.IsFormattingType(thisNavigator.GetElementType(LogicalDirection.Forward)) && !IsAtInsertionPosition(thisNavigator)) { thisNavigator.MoveByOffset(increment); } } } Exit: if (moved) { if (direction == LogicalDirection.Forward) { Invariant.Assert(thisNavigator.CompareTo(initialPosition) > 0, "thisNavigator is expected to be moved from initialPosition - 1"); } else { Invariant.Assert(thisNavigator.CompareTo(initialPosition) < 0, "thisNavigator is expected to be moved from initialPosition - 2"); } } else { Invariant.Assert(thisNavigator.CompareTo(initialPosition) == 0, "thisNavigator must stay at initial position"); } return moved; } ////// Moves the navigator in the given direction to a position of the next /// word boundary. /// /// ITextPointer to advance. /// /// Direction to move. /// ///// internal static bool MoveToNextWordBoundary(ITextPointer thisNavigator, LogicalDirection movingDirection) { int moveCounter = 0; Invariant.Assert(!thisNavigator.IsFrozen, "Can't reposition a frozen pointer!"); ITextPointer startPosition = thisNavigator.CreatePointer(); while (thisNavigator.MoveToNextInsertionPosition(movingDirection)) { moveCounter++; // Need to break the loop for weird case when there is no word break in text content. // When the word looks too long, consider end of textRun as a word break. // if (moveCounter > 64) // 64 was taken as a random number. Probably not big enough though... { thisNavigator.MoveToPosition(startPosition); thisNavigator.MoveToNextContextPosition(movingDirection); break; } if (IsAtWordBoundary(thisNavigator, /*insideWordDirection:*/LogicalDirection.Forward)) { // Note that we always use Forward direction for word orientation. break; } } return moveCounter > 0; } // internal static ITextPointer GetFrozenPointer(ITextPointer thisPointer, LogicalDirection logicalDirection) { ITextPointer frozenPointer; if (thisPointer.IsFrozen && thisPointer.LogicalDirection == logicalDirection) { frozenPointer = thisPointer; } else { frozenPointer = thisPointer.CreatePointer(logicalDirection); frozenPointer.Freeze(); } return frozenPointer; } /// internal static bool ValidateLayout(ITextPointer thisPointer, ITextView textView) { if (textView == null) { return false; } return textView.Validate(thisPointer); } #endregion Internal methods //------------------------------------------------------ // // Private Methods // //----------------------------------------------------- #region Private Methods // Worker for MoveToNextFormatNormalizedPosition/MoveToNextInsertionPosition. private static bool NormalizePosition(ITextPointer thisNavigator, LogicalDirection direction, bool respectCaretUnitBoundaries) { Invariant.Assert(!thisNavigator.IsFrozen, "Can't reposition a frozen pointer!"); int symbolCount = 0; int increment; LogicalDirection oppositeDirection; TextPointerContext directEnterScope; TextPointerContext oppositeEnterScope; if (direction == LogicalDirection.Forward) { increment = +1; oppositeDirection = LogicalDirection.Backward; directEnterScope = TextPointerContext.ElementStart; oppositeEnterScope = TextPointerContext.ElementEnd; } else { increment = -1; oppositeDirection = LogicalDirection.Forward; directEnterScope = TextPointerContext.ElementEnd; oppositeEnterScope = TextPointerContext.ElementStart; } // When the pointer appears in between structural tags we need to start // from sliding into the deepest possible position without // leaving any structural units. We need to do that only // if we are not at insertion position already. if (!IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries)) { // Go inside an innermost structured element (non-inline) while ( thisNavigator.GetPointerContext(direction) == directEnterScope && !typeof(Inline).IsAssignableFrom(thisNavigator.GetElementType(direction)) && !IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries)) { thisNavigator.MoveToNextContextPosition(direction); symbolCount += increment; } while ( thisNavigator.GetPointerContext(oppositeDirection) == oppositeEnterScope && !typeof(Inline).IsAssignableFrom(thisNavigator.GetElementType(oppositeDirection)) && !IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries)) { thisNavigator.MoveToNextContextPosition(oppositeDirection); symbolCount -= increment; } } // Get out of a Hyperlink, etc. inner edge. symbolCount = LeaveNonMergeableInlineBoundary(thisNavigator, direction, symbolCount); // Get out of a compound sequence if any. if (respectCaretUnitBoundaries) { while (!IsAtCaretUnitBoundary(thisNavigator)) { symbolCount += increment; thisNavigator.MoveByOffset(increment); } } // Here is the core part of this method's logic - skipping all formatting tags in the given direction. // Skip character formatting tags if they are present in this direction. // Even if an insertion position can be in the middle of this formatting sequence, // we want to skip it all and reach the farthest possible insertion position in that direction. // Such approach guarantees that repeated calls of this normalization will give the same reauls. // In case if there is an inserrtion position in the middle (say, in empty Run), // the loop moving in opposite direction below will find it if needed. while (TextSchema.IsMergeableInline(thisNavigator.GetElementType(direction))) { thisNavigator.MoveToNextContextPosition(direction); symbolCount += increment; } if (!IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries)) { // If still not at insertion point, try skipping inline tags in the opposite direction // now possibly stopping inside of empty element while (!IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries) && TextSchema.IsMergeableInline(thisNavigator.GetElementType(oppositeDirection))) { thisNavigator.MoveToNextContextPosition(oppositeDirection); symbolCount -= increment; } // If still not at insertion point, then try harder - skipping block tags // First in "preferred" direction while (!IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries) && thisNavigator.MoveToNextContextPosition(direction)) { symbolCount += increment; } // And finally in apposite direction while (!IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries) && thisNavigator.MoveToNextContextPosition(oppositeDirection)) { symbolCount -= increment; } if (!IsAtNormalizedPosition(thisNavigator, respectCaretUnitBoundaries)) { // When there is no insertion positions in the whole document // we return the position back to its original place. thisNavigator.MoveByOffset(-symbolCount); } } return symbolCount != 0; } // If thisNavigator is at the inner edge of a non-mergeable inline, this method // repositions it outside the scope of the non-mergeable. // Otherwise, this method does nothing. private static int LeaveNonMergeableInlineBoundary(ITextPointer thisNavigator, LogicalDirection direction, int symbolCount) { if (IsAtNonMergeableInlineStart(thisNavigator)) { if (direction == LogicalDirection.Forward && IsAtNonMergeableInlineEnd(thisNavigator)) { symbolCount += LeaveNonMergeableAncestor(thisNavigator, LogicalDirection.Forward); } else { symbolCount += LeaveNonMergeableAncestor(thisNavigator, LogicalDirection.Backward); } } else if (IsAtNonMergeableInlineEnd(thisNavigator)) { if (direction == LogicalDirection.Backward && IsAtNonMergeableInlineStart(thisNavigator)) { symbolCount += LeaveNonMergeableAncestor(thisNavigator, LogicalDirection.Backward); } else { symbolCount += LeaveNonMergeableAncestor(thisNavigator, LogicalDirection.Forward); } } return symbolCount; } // Exits the scope of a non-mergeable inline with inner edge in the direction indicated. private static int LeaveNonMergeableAncestor(ITextPointer thisNavigator, LogicalDirection direction) { int symbolCount = 0; int increment = (direction == LogicalDirection.Forward) ? +1 : -1; while (TextSchema.IsMergeableInline(thisNavigator.ParentType)) { thisNavigator.MoveToNextContextPosition(direction); symbolCount += increment; } thisNavigator.MoveToNextContextPosition(direction); symbolCount += increment; return symbolCount; } // Worker for IsAtFormatNormalizedPosition/IsAtInsertionPosition. private static bool IsAtNormalizedPosition(ITextPointer position, bool respectCaretUnitBoundaries) { if (IsPositionAtNonMergeableInlineBoundary(position)) { // The inner edge of a Hyperlink is not a valid insertion position. return false; } else if (TextSchema.IsValidChild(/*position*/position, /*childType*/typeof(string))) { return respectCaretUnitBoundaries ? IsAtCaretUnitBoundary(position) : true; } else { // Special positions outside of Run elements that allow caret stops return IsAtRowEnd(position) || IsAtPotentialRunPosition(position) || IsBeforeFirstTable(position) || IsInBlockUIContainer(position); } } // Returns true if the position is on the caret unit boundary. // Call TextView's IsAtCaretUnitBoundary if TextView is valid for this position // and it appears strictly within text run. // We consider all markup-boundary positions as caret unit boundaries. // If TextView information is not available call IsInsideCompoundSequence. private static bool IsAtCaretUnitBoundary(ITextPointer position) { bool isAtCaretUnitBoundary; TextPointerContext forwardContext = position.GetPointerContext(LogicalDirection.Forward); TextPointerContext backwardContext = position.GetPointerContext(LogicalDirection.Backward); if (backwardContext == TextPointerContext.Text && forwardContext == TextPointerContext.Text) { if (position.HasValidLayout) { // Check the insertion position with TextView's IsAtCaretUnitBoundary // that will acurately check the caret unit bounday for surrogate and international // characters isAtCaretUnitBoundary = position.IsAtCaretUnitBoundary; } else { // Check the insertion position with the internal compound sequence isAtCaretUnitBoundary = !IsInsideCompoundSequence(position); } } else { isAtCaretUnitBoundary = true; } return isAtCaretUnitBoundary; } // Returns true if the position is inside of a pair of surrogate characters // or inside of Newline sequence "\r\n". // Such position is not valid position for caret stopping or for text insertion. private static bool IsInsideCompoundSequence(ITextPointer position) { // OK, so we're surrounded by text runs (possibly empty), try getting a character // in each direction -- it's OK to position the caret if there's no characters // before or after it Char[] neighborhood = new char[2]; if (position.GetTextInRun(LogicalDirection.Backward, neighborhood, 0, 1) == 1 && position.GetTextInRun(LogicalDirection.Forward, neighborhood, 1, 1) == 1) { if (Char.IsSurrogatePair(neighborhood[0], neighborhood[1]) || neighborhood[0] == '\r' && neighborhood[1] == '\n') { return true; } // Check for combining marks. // // See Unicode 3.1, Section 3.5 (Combination), D13 and D14 for // strict definitions of "combining character" and "base character". // // The CLR source for StringInfo is also informative. // // In brief: we're looking for a character followed by a // combining mark. // UnicodeCategory category1 = Char.GetUnicodeCategory(neighborhood[1]); if (category1 == UnicodeCategory.SpacingCombiningMark || category1 == UnicodeCategory.NonSpacingMark || category1 == UnicodeCategory.EnclosingMark) { UnicodeCategory category0 = Char.GetUnicodeCategory(neighborhood[0]); if (category0 != UnicodeCategory.Control && category0 != UnicodeCategory.Format && category0 != UnicodeCategory.OtherNotAssigned) { return true; } } } return false; } // Initializes an array with the minimum necessary text to support a call to // SelectionWordBreaker.IsAtWordBoundary. // // position on exit holds the offset into the text array corresponding to // pointer's location in the document. // // Called by IsAtWordBoundary. private static void GetWordBreakerText(ITextPointer pointer, out char[] text, out int position) { char[] preceedingText = new char[SelectionWordBreaker.MinContextLength]; char[] followingText = new char[SelectionWordBreaker.MinContextLength]; int preceedingCount = 0; int followingCount = 0; int runLength; ITextPointer navigator; navigator = pointer.CreatePointer(); // Try to back up SelectionWordBreaker.MinContextLength chars, ignoring formatting. do { runLength = Math.Min(navigator.GetTextRunLength(LogicalDirection.Backward), SelectionWordBreaker.MinContextLength - preceedingCount); preceedingCount += runLength; navigator.MoveByOffset(-runLength); navigator.GetTextInRun(LogicalDirection.Forward, preceedingText, SelectionWordBreaker.MinContextLength - preceedingCount, runLength); if (preceedingCount == SelectionWordBreaker.MinContextLength) break; // Skip over any formatting. navigator.MoveToInsertionPosition(LogicalDirection.Backward); } while (navigator.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text); navigator.MoveToPosition(pointer); // Try to advance SelectionWordBreaker.MinContextLength chars, ignoring formatting. do { runLength = Math.Min(navigator.GetTextRunLength(LogicalDirection.Forward), SelectionWordBreaker.MinContextLength - followingCount); navigator.GetTextInRun(LogicalDirection.Forward, followingText, followingCount, runLength); followingCount += runLength; if (followingCount == SelectionWordBreaker.MinContextLength) break; navigator.MoveByOffset(runLength); // Skip over any formatting. navigator.MoveToInsertionPosition(LogicalDirection.Forward); } while (navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text); // Combine the preceeding and following text into a single array. text = new char[preceedingCount + followingCount]; Array.Copy(preceedingText, SelectionWordBreaker.MinContextLength - preceedingCount, text, 0, preceedingCount); Array.Copy(followingText, 0, text, preceedingCount, followingCount); position = preceedingCount; } // Worker for IsAtNonMergeableInlineStart/IsAtNonMergeableInlineEnd. private static bool IsAtNonMergeableInlineEdge(ITextPointer position, LogicalDirection direction) { BorderingElementCategory elementType = GetBorderingElementCategory(position, direction); if (elementType == BorderingElementCategory.MergeableScopingInline) { ITextPointer navigator = position.CreatePointer(); do { navigator.MoveToNextContextPosition(direction); } while ((elementType = GetBorderingElementCategory(navigator, direction)) == BorderingElementCategory.MergeableScopingInline); } return (elementType == BorderingElementCategory.NonMergeableScopingInline); } // Tests for the presence of a non-mergeable Inline bordering a position. // Helper for IsAtNonMergeableInlineEdge. private static BorderingElementCategory GetBorderingElementCategory(ITextPointer position, LogicalDirection direction) { TextPointerContext context = (direction == LogicalDirection.Forward) ? TextPointerContext.ElementEnd : TextPointerContext.ElementStart; BorderingElementCategory category; if (position.GetPointerContext(direction) != context || !typeof(Inline).IsAssignableFrom(position.ParentType)) { category = BorderingElementCategory.NotScopingInline; } else if (TextSchema.IsMergeableInline(position.ParentType)) { category = BorderingElementCategory.MergeableScopingInline; } else { category = BorderingElementCategory.NonMergeableScopingInline; } return category; } // Returns true if the position is adjacent to a LineBreak or Paragraph element, // ignoring any intermediate formatting elements. // // If lineBreakType is null, any line break element is considered valid. private static bool IsNextToRichBreak(ITextPointer thisPosition, LogicalDirection direction, Type lineBreakType) { Invariant.Assert(lineBreakType == null || lineBreakType == typeof(LineBreak) || lineBreakType == typeof(Paragraph)); bool result = false; while (true) { Type neighbor = thisPosition.GetElementType(direction); if (lineBreakType == null) { if (typeof(LineBreak).IsAssignableFrom(neighbor) || typeof(Paragraph).IsAssignableFrom(neighbor)) { result = true; break; } } else if (lineBreakType.IsAssignableFrom(neighbor)) { result = true; break; } if (!TextSchema.IsFormattingType(neighbor)) break; thisPosition = thisPosition.GetNextContextPosition(direction); } return result; } #endregion Private methods //------------------------------------------------------ // // Private Types // //------------------------------------------------------ #region Private Types // Element types for GetBorderingElementCategory. private enum BorderingElementCategory { MergeableScopingInline, NonMergeableScopingInline, NotScopingInline }; #endregion Private Types } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- XmlAnyAttributeAttribute.cs
- ActivatedMessageQueue.cs
- SystemColorTracker.cs
- CorePropertiesFilter.cs
- CodeTypeDeclarationCollection.cs
- DurableInstanceManager.cs
- CompositeControlDesigner.cs
- DataGridViewRowHeightInfoPushedEventArgs.cs
- SyncMethodInvoker.cs
- ShaderEffect.cs
- HttpCookie.cs
- DataGridPagerStyle.cs
- HtmlInputRadioButton.cs
- DependencyPropertyValueSerializer.cs
- EventProvider.cs
- KernelTypeValidation.cs
- BitmapEffectState.cs
- SqlParameterCollection.cs
- MetadataSource.cs
- ConfigXmlCDataSection.cs
- DetailsViewInsertEventArgs.cs
- WebPartTransformer.cs
- ConfigXmlWhitespace.cs
- OleDbStruct.cs
- SetStoryboardSpeedRatio.cs
- VisualBasicSettings.cs
- SmiEventSink_Default.cs
- LinkButton.cs
- DaylightTime.cs
- XmlEncodedRawTextWriter.cs
- CombinedGeometry.cs
- ReachSerializableProperties.cs
- Atom10FormatterFactory.cs
- FacetDescription.cs
- SafeUserTokenHandle.cs
- WmlTextViewAdapter.cs
- ComponentTray.cs
- WindowPatternIdentifiers.cs
- CurrencyManager.cs
- Double.cs
- GeometryCombineModeValidation.cs
- DataServiceExpressionVisitor.cs
- OneOfTypeConst.cs
- SmtpLoginAuthenticationModule.cs
- MonthCalendar.cs
- TextSelectionHighlightLayer.cs
- AutomationIdentifierGuids.cs
- PrintDialogDesigner.cs
- SessionStateContainer.cs
- ClientRuntimeConfig.cs
- XmlHierarchicalDataSourceView.cs
- MessageCredentialType.cs
- XmlValueConverter.cs
- ToolStripOverflowButton.cs
- DocumentViewerBase.cs
- WasAdminWrapper.cs
- DecoderReplacementFallback.cs
- VirtualPathProvider.cs
- RepeaterItemEventArgs.cs
- DataGridCheckBoxColumn.cs
- EntityContainer.cs
- MimeMapping.cs
- ScrollData.cs
- ZipIOFileItemStream.cs
- PageStatePersister.cs
- Compress.cs
- _ServiceNameStore.cs
- SafeEventLogWriteHandle.cs
- EncoderParameters.cs
- ColorConverter.cs
- QilXmlWriter.cs
- TreeViewCancelEvent.cs
- SqlProfileProvider.cs
- IIS7WorkerRequest.cs
- UnicodeEncoding.cs
- TypeBrowserDialog.cs
- HighlightVisual.cs
- SoundPlayerAction.cs
- UidManager.cs
- UIntPtr.cs
- ZipPackagePart.cs
- WebCategoryAttribute.cs
- AlphaSortedEnumConverter.cs
- StateDesigner.CommentLayoutGlyph.cs
- Expression.DebuggerProxy.cs
- AuthStoreRoleProvider.cs
- TableTextElementCollectionInternal.cs
- TextServicesPropertyRanges.cs
- DbConnectionPoolOptions.cs
- SeparatorAutomationPeer.cs
- UnsafeNativeMethods.cs
- CommandConverter.cs
- BinaryCommonClasses.cs
- AnimationClock.cs
- PinnedBufferMemoryStream.cs
- XomlCompilerError.cs
- DataGridViewSortCompareEventArgs.cs
- OutputCacheProfileCollection.cs
- GeneralTransformCollection.cs
- DesignTimeHTMLTextWriter.cs