/* * Code used to trim text segments for alternative display of sticky note anchors * * /// <summary> * /// ctor that initializes the TextSegments array by cloning and trimming the input segment Array * /// </summary> * /// <param name="segments">input segment</param> * /// <remarks>This is used to convert a TextRange into TextAnchor. * /// Input segments must be ordered and non overlapping</remarks> * internal TextAnchor(IList<TextSegment> segments) * { * if (segments == null) * return; * * ITextPointer lastPointer = null; * for (int i = 0; i < segments.Count; i++) * { * Invariant.Assert((lastPointer == null) || (lastPointer.CompareTo(segments[i].Start) <= 0), "overlapped segments found"); * TextSegment newSegment = TextAnchor.Trim(segments[i]); * if (newSegment.IsNull) * continue; * * _segments.Add(newSegment); * lastPointer = newSegment.End; * } * } */ #endregion Constructors //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods /// <summary> /// Determines if the text pointer is contained by one of the /// anchor's TextSegment.s /// </summary> /// <param name="textPointer">text pointer to test</param> internal bool Contains(ITextPointer textPointer) { if (textPointer == null) { throw new ArgumentNullException("textPointer"); } if (textPointer.TextContainer != this.Start.TextContainer) { throw new ArgumentException(SR.Get(SRID.NotInAssociatedTree, "textPointer")); } // Correct position normalization on range boundary so that // our test would not depend on what side of formatting tags // pointer is located. if (textPointer.CompareTo(this.Start) < 0) { textPointer = textPointer.GetInsertionPosition(LogicalDirection.Forward); } else if (textPointer.CompareTo(this.End) > 0) { textPointer = textPointer.GetInsertionPosition(LogicalDirection.Backward); } // Check if at least one segment contains this position. for (int i = 0; i < _segments.Count; i++) { if (_segments[i].Contains(textPointer)) { return(true); } } return(false); }
// Token: 0x0600625B RID: 25179 RVA: 0x001B9470 File Offset: 0x001B7670 private static TextSegment CreateNormalizedSegment(ITextPointer start, ITextPointer end) { if (start.CompareTo(end) == 0) { if (!TextPointerBase.IsAtInsertionPosition(start, start.LogicalDirection)) { start = start.GetInsertionPosition(start.LogicalDirection); end = start; } } else { if (!TextPointerBase.IsAtInsertionPosition(start, start.LogicalDirection)) { start = start.GetInsertionPosition(LogicalDirection.Forward); } if (!TextPointerBase.IsAtInsertionPosition(end, start.LogicalDirection)) { end = end.GetInsertionPosition(LogicalDirection.Backward); } if (start.CompareTo(end) >= 0) { if (start.LogicalDirection == LogicalDirection.Backward) { start = end.GetFrozenPointer(LogicalDirection.Backward); } end = start; } } return(new TextSegment(start, end)); }
// Token: 0x0600624A RID: 25162 RVA: 0x001B8AC4 File Offset: 0x001B6CC4 internal bool Contains(ITextPointer textPointer) { if (textPointer == null) { throw new ArgumentNullException("textPointer"); } if (textPointer.TextContainer != this.Start.TextContainer) { throw new ArgumentException(SR.Get("NotInAssociatedTree", new object[] { "textPointer" })); } if (textPointer.CompareTo(this.Start) < 0) { textPointer = textPointer.GetInsertionPosition(LogicalDirection.Forward); } else if (textPointer.CompareTo(this.End) > 0) { textPointer = textPointer.GetInsertionPosition(LogicalDirection.Backward); } for (int i = 0; i < this._segments.Count; i++) { if (this._segments[i].Contains(textPointer)) { return(true); } } return(false); }
// Token: 0x060085BD RID: 34237 RVA: 0x0024AA08 File Offset: 0x00248C08 private ITextPointer GetDropPosition(Visual target, Point point) { Invariant.Assert(target != null); Invariant.Assert(this._textEditor.TextView.IsValid); if (target != this._textEditor.TextView.RenderScope && target != null && this._textEditor.TextView.RenderScope.IsAncestorOf(target)) { GeneralTransform generalTransform = target.TransformToAncestor(this._textEditor.TextView.RenderScope); generalTransform.TryTransform(point, out point); } ITextPointer textPointer = this.TextView.GetTextPositionFromPoint(point, true); if (textPointer != null) { textPointer = textPointer.GetInsertionPosition(textPointer.LogicalDirection); if (this._textEditor.AcceptsRichContent) { TextSegment normalizedLineRange = TextEditorSelection.GetNormalizedLineRange(this.TextView, textPointer); if (!normalizedLineRange.IsNull && textPointer.CompareTo(normalizedLineRange.End) < 0 && !TextPointerBase.IsAtWordBoundary(textPointer, LogicalDirection.Forward) && this._dragSourceTextRange != null && TextPointerBase.IsAtWordBoundary(this._dragSourceTextRange.Start, LogicalDirection.Forward) && TextPointerBase.IsAtWordBoundary(this._dragSourceTextRange.End, LogicalDirection.Forward)) { TextSegment wordRange = TextPointerBase.GetWordRange(textPointer); string textInternal = TextRangeBase.GetTextInternal(wordRange.Start, wordRange.End); int offsetToPosition = wordRange.Start.GetOffsetToPosition(textPointer); textPointer = ((offsetToPosition < textInternal.Length / 2) ? wordRange.Start : wordRange.End); } } } return(textPointer); }
// Token: 0x06008E5C RID: 36444 RVA: 0x0025C04B File Offset: 0x0025A24B private void OpenSegment(ref ITextPointer segmentStart, ITextPointer cursor) { if (segmentStart == null) { segmentStart = cursor.GetInsertionPosition(LogicalDirection.Forward); } }
/// <summary> /// Creates a new segment with the specified pointers, but first /// normalizes them to make sure they are on insertion positions. /// </summary> /// <param name="start">start of the new segment</param> /// <param name="end">end of the new segment</param> private static TextSegment CreateNormalizedSegment(ITextPointer start, ITextPointer end) { // Normalize the segment if (start.CompareTo(end) == 0) { // When the range is empty we must keep it that way during normalization if (!TextPointerBase.IsAtInsertionPosition(start, start.LogicalDirection)) { start = start.GetInsertionPosition(start.LogicalDirection); end = start; } } else { if (!TextPointerBase.IsAtInsertionPosition(start, start.LogicalDirection)) { start = start.GetInsertionPosition(LogicalDirection.Forward); } if (!TextPointerBase.IsAtInsertionPosition(end, start.LogicalDirection)) { end = end.GetInsertionPosition(LogicalDirection.Backward); } // Collapse range in case of overlapped normalization result if (start.CompareTo(end) >= 0) { // The range is effectuvely empty, so collapse it to single pointer instance if (start.LogicalDirection == LogicalDirection.Backward) { // Choose a position normalized backward, start = end.GetFrozenPointer(LogicalDirection.Backward); // NOTE that otherwise we will use start position, // which is oriented and normalizd Forward } end = start; } } return(new TextSegment(start, end)); }
/// <summary> /// Calculates a TextPointer indended for dropping the text. /// </summary> /// <param name="target"></param> /// <param name="point"></param> /// <returns> /// ITextPointer intended for dropping the selected text. /// Adjusts the dropping point to a word boundary (beginning of word) /// in case if source range contains whole words. /// The position returned is oriented towards a character /// under the mouse pointer. /// </returns> private ITextPointer GetDropPosition(Visual target, Point point) { Invariant.Assert(target != null); Invariant.Assert(_textEditor.TextView.IsValid); // caller must guarantee this. // Convert point to RenderScope if (target != _textEditor.TextView.RenderScope && target != null && (_textEditor.TextView.RenderScope).IsAncestorOf(target)) { GeneralTransform transform = target.TransformToAncestor(_textEditor.TextView.RenderScope); transform.TryTransform(point, out point); } ITextPointer dropPosition = this.TextView.GetTextPositionFromPoint(point, /*snapToText:*/ true); // For rich text content we adjust drop position to word boundary if (dropPosition != null) { // Normalize drop position dropPosition = dropPosition.GetInsertionPosition(dropPosition.LogicalDirection); if (_textEditor.AcceptsRichContent) { TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(this.TextView, dropPosition); if (!lineRange.IsNull && // The drop position must be before of end of line dropPosition.CompareTo(lineRange.End) < 0 && // We check if we are not at word boundary already: !TextPointerBase.IsAtWordBoundary(dropPosition, /*insideWordDirection:*/ LogicalDirection.Forward) && // We do not do it if the source range was not on word boundaries from both ends _dragSourceTextRange != null && // TextPointerBase.IsAtWordBoundary(_dragSourceTextRange.Start, LogicalDirection.Forward) && // TextPointerBase.IsAtWordBoundary(_dragSourceTextRange.End, LogicalDirection.Forward)) { // Move to word boundary. Select closest one to a dropPosition. TextSegment wordSegment = TextPointerBase.GetWordRange(dropPosition); string wordText = TextRangeBase.GetTextInternal(wordSegment.Start, wordSegment.End); int indexInWord = wordSegment.Start.GetOffsetToPosition(dropPosition); dropPosition = (indexInWord < (wordText.Length / 2)) ? wordSegment.Start : wordSegment.End; } } } return(dropPosition); }
// Part of ExtendSelectionByMouse method: // Identifies words on selection ends. private void IdentifyWordsOnSelectionEnds(ITextPointer anchorPosition, ITextPointer cursorPosition, bool forceWordSelection, out TextSegment anchorWordRange, out TextSegment cursorWordRange) { if (forceWordSelection) { anchorWordRange = TextPointerBase.GetWordRange(anchorPosition); cursorWordRange = TextPointerBase.GetWordRange(cursorPosition, cursorPosition.LogicalDirection); } else { // Define whether word adjustment is allowed. Pressing Shift+Control prevents from auto-word expansion. bool disableWordExpansion = _textEditor.AutoWordSelection == false || ((Keyboard.Modifiers & ModifierKeys.Shift) != 0 && (Keyboard.Modifiers & ModifierKeys.Control) != 0); if (disableWordExpansion) { anchorWordRange = new TextSegment(anchorPosition, anchorPosition); cursorWordRange = new TextSegment(cursorPosition, cursorPosition); } else { // Autoword expansion heuristics // ----------------------------- // Word autoword heuristics: // a) After active end returned to selected area, autoword expansion on active end is disabled // b) After active end returned to the very first word, expansion on anchor word is disabled either // We do this though only if selection has crossed initial word boundary at least once. // c) After active end crosses new word, autoword expansion of active end is enabled again // Calculate a word range for anchor position anchorWordRange = TextPointerBase.GetWordRange(anchorPosition); // Check if we re-entering selection or moving outside // and set autoexpansion flags accordingly if (_previousCursorPosition != null && (anchorPosition.CompareTo(cursorPosition) < 0 && cursorPosition.CompareTo(_previousCursorPosition) < 0 || _previousCursorPosition.CompareTo(cursorPosition) < 0 && cursorPosition.CompareTo(anchorPosition) < 0)) { // Re-entering selection. // Store position of reentering _reenterPosition = cursorPosition.CreatePointer(); // When re-entering reaches initial word, disable word expansion on anchor end either if (_anchorWordRangeHasBeenCrossedOnce && anchorWordRange.Contains(cursorPosition)) { _allowWordExpansionOnAnchorEnd = false; } } else { // Extending the selection. // Check if we are crossing a boundary of last reentered word to re-enable word expansion on moving end if (_reenterPosition != null) { TextSegment lastReenteredWordRange = TextPointerBase.GetWordRange(_reenterPosition); if (!lastReenteredWordRange.Contains(cursorPosition)) { _reenterPosition = null; } } } // Identify expanded range on both ends // if (anchorWordRange.Contains(cursorPosition) || anchorWordRange.Contains(cursorPosition.GetInsertionPosition(LogicalDirection.Forward)) || anchorWordRange.Contains(cursorPosition.GetInsertionPosition(LogicalDirection.Backward))) { // Selection does not cross word boundary, so shrink selection to exact anchor/cursor positions anchorWordRange = new TextSegment(anchorPosition, anchorPosition); cursorWordRange = new TextSegment(cursorPosition, cursorPosition); } else { // Selection crosses word boundary. _anchorWordRangeHasBeenCrossedOnce = true; if (!_allowWordExpansionOnAnchorEnd || // TextPointerBase.IsAtWordBoundary(anchorPosition, /*insideWordDirection:*/LogicalDirection.Forward)) { // We collapse anchorPosition in two cases: // If we have been re-entering the initial word before - // then we treat it as an indicator that user wants exact position on anchor end // or // if selection starts exactly on word boundary - // then we should not include the following word (when selection extends backward). // // So in the both cases we collapse anchorWordRange to exact _anchorPosition anchorWordRange = new TextSegment(anchorPosition, anchorPosition); } if (TextPointerBase.IsAfterLastParagraph(cursorPosition) || TextPointerBase.IsAtWordBoundary(cursorPosition, /*insideWordDirection:*/LogicalDirection.Forward)) { cursorWordRange = new TextSegment(cursorPosition, cursorPosition); } else { if (_reenterPosition == null) { // We are not in re-entering mode; expand moving end to word boundary cursorWordRange = TextPointerBase.GetWordRange(cursorPosition, cursorPosition.LogicalDirection); } else { // We are in re-entering mode; use exact moving end position cursorWordRange = new TextSegment(cursorPosition, cursorPosition); } } } } } }
//...................................................... // // Active Positions of Selection // //...................................................... /// <summary> /// Stores normalized anchor and moving positions for the selection. /// Ensures that they are both inside of range Start/End. /// </summary> /// <param name="anchorPosition"> /// A position which must be stored as initial position for the selection. /// </param> /// <param name="movingPosition"> /// The "hot" or active selection edge which responds to user input. /// </param> private void SetActivePositions(ITextPointer anchorPosition, ITextPointer movingPosition) { // The following settings are used in auto-word exapnsion. // By setting a _previousPosition we are clearing them all - // they will be re-initialized in the beginning of a selection // expansion guesture in ExtendSelectionByMouse method // Previous position is needed for selection gestures to remember // where mouse drag happed last time. Used for word autoexpansion. _previousCursorPosition = null; if (this.IsEmpty) { _anchorPosition = null; _movingPositionEdge = MovingEdge.None; return; } Invariant.Assert(anchorPosition != null); ITextSelection thisSelection = (ITextSelection)this; // Normalize and store new selection anchor position _anchorPosition = anchorPosition.GetInsertionPosition(anchorPosition.LogicalDirection); // Ensure that anchor position is within one of text segments if (_anchorPosition.CompareTo(thisSelection.Start) < 0) { _anchorPosition = thisSelection.Start.GetFrozenPointer(_anchorPosition.LogicalDirection); } else if (_anchorPosition.CompareTo(thisSelection.End) > 0) { _anchorPosition = thisSelection.End.GetFrozenPointer(_anchorPosition.LogicalDirection); } _movingPositionEdge = ConvertToMovingEdge(anchorPosition, movingPosition); _movingPositionDirection = movingPosition.LogicalDirection; }
// <see cref="TextPointer.GetCharacterRect"/> 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; }
// <summary> // 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 // </summary> internal static bool IsNextToAnyBreak(ITextPointer thisPosition, LogicalDirection direction) { if (!thisPosition.IsAtInsertionPosition) { thisPosition = thisPosition.GetInsertionPosition(direction); } return (IsNextToPlainLineBreak(thisPosition, direction) || IsNextToRichBreak(thisPosition, direction, null)); }
/// <summary> /// Returns a TextSegment covering the word containing this TextPointer. /// </summary> /// <remarks> /// 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. /// </remarks> 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); }
// Opens a segment for the following portion private void OpenSegment(ref ITextPointer segmentStart, ITextPointer cursor) { if (segmentStart == null) { // Create normalized position for the segment start segmentStart = cursor.GetInsertionPosition(LogicalDirection.Forward); } }
private static ITextPointer GetNormalizedPosition(ITextRange thisRange, ITextPointer position, LogicalDirection direction) { ITextPointer normalizedPosition; if (thisRange.IgnoreTextUnitBoundaries) { normalizedPosition = position.GetFormatNormalizedPosition(direction); } else { normalizedPosition = position.GetInsertionPosition(direction); } return normalizedPosition; }
// Helper for OnSelectDownByLine. Updates the selection moving position // during select down by line. private static void AdjustMovingPositionForSelectDownByLine(TextEditor This, ITextPointer newMovingPosition, ITextPointer originalMovingPosition, double suggestedX) { int newComparedToOld = newMovingPosition.CompareTo(originalMovingPosition); // Note: we compare orientations of equal positions to handle a case // when original position was at the end of line (after its linebreak with backward orientation) // as a result of Shift+End selection; and the new position is in the beginning of the next line, // which is essentially the same position but oriented differently. // In such a case the new position is good enough to go there. // We certainly don't want to go to the end of the document in this case. if (newComparedToOld > 0 || newComparedToOld == 0 && newMovingPosition.LogicalDirection != originalMovingPosition.LogicalDirection) { // We have another line in a given direction; move to it // If the destination exactly preceeds a line break, expand to include // the line break if we haven't reached our desired suggestedX. if (TextPointerBase.IsNextToAnyBreak(newMovingPosition, LogicalDirection.Forward) || newMovingPosition.GetNextInsertionPosition(LogicalDirection.Forward) == null) { double newPositionX = GetAbsoluteXOffset(This.TextView, newMovingPosition); FlowDirection paragraphFlowDirection = GetScopingParagraphFlowDirection(newMovingPosition); FlowDirection controlFlowDirection = This.UiScope.FlowDirection; if ((paragraphFlowDirection == controlFlowDirection && newPositionX < suggestedX) || (paragraphFlowDirection != controlFlowDirection && newPositionX > suggestedX)) { newMovingPosition = newMovingPosition.GetInsertionPosition(LogicalDirection.Forward); newMovingPosition = newMovingPosition.GetNextInsertionPosition(LogicalDirection.Forward); // If we're at the last Paragraph, move to document end to include // the final paragraph break. if (newMovingPosition == null) { newMovingPosition = originalMovingPosition.TextContainer.End; } newMovingPosition = newMovingPosition.GetFrozenPointer(LogicalDirection.Backward); } } ExtendSelectionAndBringIntoView(newMovingPosition, This); } else { // Remember where we were so that we can return if a line up follows. if (This._NextLineAdvanceMovingPosition == null) { This._NextLineAdvanceMovingPosition = originalMovingPosition; This._IsNextLineAdvanceMovingPositionAtDocumentHead = false; } // No more lines in this direction. Move to end of current line. newMovingPosition = GetPositionAtLineEnd(originalMovingPosition); if (newMovingPosition.GetNextInsertionPosition(LogicalDirection.Forward) == null) { // Move to the final implicit line at end-of-doc. newMovingPosition = newMovingPosition.TextContainer.End; } ExtendSelectionAndBringIntoView(newMovingPosition, This); } }