Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Framework / System / Windows / Documents / TextEditorSelection.cs / 1 / TextEditorSelection.cs
//---------------------------------------------------------------------------- // // File: TextEditorSelection.cs // // Copyright (C) Microsoft Corporation. All rights reserved. // // Description: A component of TextEditor supporting selection and navigation // //--------------------------------------------------------------------------- namespace System.Windows.Documents { using MS.Internal; using System.Globalization; using System.Threading; using System.ComponentModel; using System.Text; using System.Collections; // ArrayList using System.Runtime.InteropServices; using System.Security; // SecurityCritical attribute. using System.Windows.Threading; using System.Windows.Input; using System.Windows.Controls; // ScrollChangedEventArgs using System.Windows.Controls.Primitives; // CharacterCasing, TextBoxBase using System.Windows.Media; using System.Windows.Markup; using MS.Utility; using MS.Win32; using MS.Internal.Documents; using MS.Internal.Commands; // CommandHelpers ////// Text editing service for controls. /// internal static class TextEditorSelection { //----------------------------------------------------- // // Class Internal Methods // //----------------------------------------------------- #region Class Internal Methods // Registers all text editing command handlers for a given control type ////// Critical - critical - calls a method that performs a link demand. CommandBinding ctor. /// TreatAsSafe - command bindings have link demands to prevent "hijacking" of built-in avalon commands. /// however registering our own internal commands is safe. /// [SecurityCritical, SecurityTreatAsSafe] internal static void _RegisterClassHandlers(Type controlType, bool registerEventListeners) { // Shared handlers used multiple times below. ExecutedRoutedEventHandler nyiCommandHandler = new ExecutedRoutedEventHandler(OnNYICommand); CanExecuteRoutedEventHandler queryStatusCaretNavigationHandler = new CanExecuteRoutedEventHandler(OnQueryStatusCaretNavigation); CanExecuteRoutedEventHandler queryStatusKeyboardSelectionHandler = new CanExecuteRoutedEventHandler(OnQueryStatusKeyboardSelection); // Standard Commands: Select All // ----------------------------- CommandHelpers.RegisterCommandHandler(controlType, ApplicationCommands.SelectAll, Key.A, ModifierKeys.Control, new ExecutedRoutedEventHandler(OnSelectAll), null); // Editing Commands : Caret Navigation // ----------------------------------- CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveRightByCharacter, new ExecutedRoutedEventHandler(OnMoveRightByCharacter), queryStatusCaretNavigationHandler, SRID.KeyMoveRightByCharacter, SRID.KeyMoveRightByCharacterDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveLeftByCharacter, new ExecutedRoutedEventHandler(OnMoveLeftByCharacter), queryStatusCaretNavigationHandler, SRID.KeyMoveLeftByCharacter, SRID.KeyMoveLeftByCharacterDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveRightByWord, new ExecutedRoutedEventHandler(OnMoveRightByWord), queryStatusCaretNavigationHandler, SRID.KeyMoveRightByWord, SRID.KeyMoveRightByWordDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveLeftByWord, new ExecutedRoutedEventHandler(OnMoveLeftByWord), queryStatusCaretNavigationHandler, SRID.KeyMoveLeftByWord, SRID.KeyMoveLeftByWordDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveDownByLine, new ExecutedRoutedEventHandler(OnMoveDownByLine), queryStatusCaretNavigationHandler, SRID.KeyMoveDownByLine, SRID.KeyMoveDownByLineDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveUpByLine, new ExecutedRoutedEventHandler(OnMoveUpByLine), queryStatusCaretNavigationHandler, SRID.KeyMoveUpByLine, SRID.KeyMoveUpByLineDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveDownByParagraph, new ExecutedRoutedEventHandler(OnMoveDownByParagraph), queryStatusCaretNavigationHandler, SRID.KeyMoveDownByParagraph, SRID.KeyMoveDownByParagraphDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveUpByParagraph, new ExecutedRoutedEventHandler(OnMoveUpByParagraph), queryStatusCaretNavigationHandler, SRID.KeyMoveUpByParagraph, SRID.KeyMoveUpByParagraphDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveDownByPage, new ExecutedRoutedEventHandler(OnMoveDownByPage), queryStatusCaretNavigationHandler, SRID.KeyMoveDownByPage, SRID.KeyMoveDownByPageDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveUpByPage, new ExecutedRoutedEventHandler(OnMoveUpByPage), queryStatusCaretNavigationHandler, SRID.KeyMoveUpByPage, SRID.KeyMoveUpByPageDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToLineStart, new ExecutedRoutedEventHandler(OnMoveToLineStart), queryStatusCaretNavigationHandler, SRID.KeyMoveToLineStart, SRID.KeyMoveToLineStartDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToLineEnd, new ExecutedRoutedEventHandler(OnMoveToLineEnd), queryStatusCaretNavigationHandler, SRID.KeyMoveToLineEnd, SRID.KeyMoveToLineEndDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToColumnStart, nyiCommandHandler, queryStatusCaretNavigationHandler, SRID.KeyMoveToColumnStart, SRID.KeyMoveToColumnStartDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToColumnEnd, nyiCommandHandler, queryStatusCaretNavigationHandler, SRID.KeyMoveToColumnEnd, SRID.KeyMoveToColumnEndDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToWindowTop, nyiCommandHandler, queryStatusCaretNavigationHandler, SRID.KeyMoveToWindowTop, SRID.KeyMoveToWindowTopDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToWindowBottom, nyiCommandHandler, queryStatusCaretNavigationHandler, SRID.KeyMoveToWindowBottom, SRID.KeyMoveToWindowBottomDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToDocumentStart, new ExecutedRoutedEventHandler(OnMoveToDocumentStart), queryStatusCaretNavigationHandler, SRID.KeyMoveToDocumentStart, SRID.KeyMoveToDocumentStartDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToDocumentEnd, new ExecutedRoutedEventHandler(OnMoveToDocumentEnd), queryStatusCaretNavigationHandler, SRID.KeyMoveToDocumentEnd, SRID.KeyMoveToDocumentEndDisplayString); // Editing Commands: Selection Building // ------------------------------------ CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectRightByCharacter, new ExecutedRoutedEventHandler(OnSelectRightByCharacter), queryStatusKeyboardSelectionHandler, SRID.KeySelectRightByCharacter, SRID.KeySelectRightByCharacterDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectLeftByCharacter, new ExecutedRoutedEventHandler(OnSelectLeftByCharacter), queryStatusKeyboardSelectionHandler, SRID.KeySelectLeftByCharacter, SRID.KeySelectLeftByCharacterDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectRightByWord, new ExecutedRoutedEventHandler(OnSelectRightByWord), queryStatusKeyboardSelectionHandler, SRID.KeySelectRightByWord, SRID.KeySelectRightByWordDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectLeftByWord, new ExecutedRoutedEventHandler(OnSelectLeftByWord), queryStatusKeyboardSelectionHandler, SRID.KeySelectLeftByWord, SRID.KeySelectLeftByWordDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectDownByLine, new ExecutedRoutedEventHandler(OnSelectDownByLine), queryStatusKeyboardSelectionHandler, SRID.KeySelectDownByLine, SRID.KeySelectDownByLineDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectUpByLine, new ExecutedRoutedEventHandler(OnSelectUpByLine), queryStatusKeyboardSelectionHandler, SRID.KeySelectUpByLine, SRID.KeySelectUpByLineDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectDownByParagraph, new ExecutedRoutedEventHandler(OnSelectDownByParagraph), queryStatusKeyboardSelectionHandler, SRID.KeySelectDownByParagraph, SRID.KeySelectDownByParagraphDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectUpByParagraph, new ExecutedRoutedEventHandler(OnSelectUpByParagraph), queryStatusKeyboardSelectionHandler, SRID.KeySelectUpByParagraph, SRID.KeySelectUpByParagraphDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectDownByPage, new ExecutedRoutedEventHandler(OnSelectDownByPage), queryStatusKeyboardSelectionHandler, SRID.KeySelectDownByPage, SRID.KeySelectDownByPageDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectUpByPage, new ExecutedRoutedEventHandler(OnSelectUpByPage), queryStatusKeyboardSelectionHandler, SRID.KeySelectUpByPage, SRID.KeySelectUpByPageDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToLineStart, new ExecutedRoutedEventHandler(OnSelectToLineStart), queryStatusKeyboardSelectionHandler, SRID.KeySelectToLineStart, SRID.KeySelectToLineStartDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToLineEnd, new ExecutedRoutedEventHandler(OnSelectToLineEnd), queryStatusKeyboardSelectionHandler, SRID.KeySelectToLineEnd, SRID.KeySelectToLineEndDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToColumnStart, nyiCommandHandler, queryStatusKeyboardSelectionHandler, SRID.KeySelectToColumnStart, SRID.KeySelectToColumnStartDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToColumnEnd, nyiCommandHandler, queryStatusKeyboardSelectionHandler, SRID.KeySelectToColumnEnd, SRID.KeySelectToColumnEndDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToWindowTop, nyiCommandHandler, queryStatusKeyboardSelectionHandler, SRID.KeySelectToWindowTop, SRID.KeySelectToWindowTopDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToWindowBottom, nyiCommandHandler, queryStatusKeyboardSelectionHandler, SRID.KeySelectToWindowBottom, SRID.KeySelectToWindowBottomDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToDocumentStart, new ExecutedRoutedEventHandler(OnSelectToDocumentStart), queryStatusKeyboardSelectionHandler, SRID.KeySelectToDocumentStart, SRID.KeySelectToDocumentStartDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToDocumentEnd, new ExecutedRoutedEventHandler(OnSelectToDocumentEnd), queryStatusKeyboardSelectionHandler, SRID.KeySelectToDocumentEnd, SRID.KeySelectToDocumentEndDisplayString); } ////// Clears the suggestedX variable of passed TextEditor. /// /// TextEditor internal static void _ClearSuggestedX(TextEditor This) { // Discard stored horizontal position. // By setting to NaN, we indicate that the first following vertical movement // must define suggestedX from the current moving position. This._suggestedX = Double.NaN; This._NextLineAdvanceMovingPosition = null; } // Returns a normalized line range from TextView for a given position. // Note: In current contract, line range returned by TextView.GetLineRange() is not guaranteed to be normalized. // This helper does appropriate correction and returns a normalized line range. internal static TextSegment GetNormalizedLineRange(ITextView textView, ITextPointer position) { TextSegment lineRange = textView.GetLineRange(position); if (lineRange.IsNull) { if (!typeof(BlockUIContainer).IsAssignableFrom(position.ParentType)) { return lineRange; } ITextPointer lineStart = position.CreatePointer(LogicalDirection.Forward); lineStart.MoveToElementEdge(ElementEdge.AfterStart); ITextPointer lineEnd = position.CreatePointer(LogicalDirection.Backward); lineEnd.MoveToElementEdge(ElementEdge.BeforeEnd); lineRange = new TextSegment(lineStart, lineEnd); return lineRange; } // Normalize line range ITextRange textRange = new TextRange(lineRange.Start, lineRange.End); return new TextSegment(textRange.Start, textRange.End); } // Returns true if a textview is potentially paginated. internal static bool IsPaginated(ITextView textview) { return !(textview is TextBoxView); } #endregion Class Internal Methods //------------------------------------------------------ // // Private Methods // //----------------------------------------------------- #region Private Methods ////// private static void OnSelectAll(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock(true /* disableScroll */)) { This.Selection.Select(This.TextContainer.Start, This.TextContainer.End); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } // ................................................................ // // Editing Commands: Caret Navigation // // ................................................................ ////// MoveRightByCharacter command event handler. /// private static void OnMoveRightByCharacter(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Backward : LogicalDirection.Forward; MoveToCharacterLogicalDirection(This, movementDirection, /*extend:*/false); } ////// MoveLeftByCharacter command event handler. /// private static void OnMoveLeftByCharacter(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Forward : LogicalDirection.Backward; MoveToCharacterLogicalDirection(This, movementDirection, /*extend:*/false); } ////// private static void OnMoveRightByWord(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } // Navigate word to the logical forward. LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Backward : LogicalDirection.Forward; NavigateWordLogicalDirection(This, movementDirection); } ////// private static void OnMoveLeftByWord(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } // Navigate word to the logical backward. LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Forward : LogicalDirection.Backward; NavigateWordLogicalDirection(This, movementDirection); } private static void OnMoveDownByLine(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // We need a non-dirty layout to walk lines. if (!This.Selection.End.ValidateLayout()) { return; } using (This.Selection.DeclareChangeBlock()) { if (!This.Selection.IsEmpty) { // If the selection is non-empty, collapse it. // Collapsing must happen to selection END - not to its moving position. // It is Word behavior for setting a cratet on moving down from nonempty selection. // When Selection.End is moving position we must adjust it // for LineEnd condition - choose inner position within a line. ITextPointer position = TextEditorSelection.GetEndInner(This); This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); TextEditorSelection._ClearSuggestedX(This); // So that when we will request suggestedX below it will take it from the new moving position } Invariant.Assert(This.Selection.IsEmpty); // When the caret is at RowEnd position, we start by moving it into the last cell of this row. AdjustCaretAtTableRowEnd(This); ITextPointer originalMovingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out originalMovingPosition); // Continue only if we have a moving position with valid layout if (originalMovingPosition == null) { return; } // Extend the selection edge. double newSuggestedX; int linesMoved; ITextPointer newMovingPosition = This.TextView.GetPositionAtNextLine(This.Selection.MovingPosition, suggestedX, +1, out newSuggestedX, out linesMoved); Invariant.Assert(newMovingPosition != null); if (linesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); // Move insertion point to next or previous line This.Selection.SetCaretToPosition(newMovingPosition, newMovingPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } else { if (TextPointerBase.IsInAnchoredBlock(originalMovingPosition)) { // TextView treats AnchoredBlock elements as hard structural boundaries. // As a result GetPositionAtNextLine() does not work from the first/last line within an AnchoredBlock. // If line move wasn't successful because our moving position is at the end of an AnchoredBlock, // move insertion point so that it crosses AnchoredBlock boundary. // If there is no next position after the AnchoredBlock, move to current line end. ITextPointer lineEndPosition = GetPositionAtLineEnd(originalMovingPosition); ITextPointer nextPosition = lineEndPosition.GetNextInsertionPosition(LogicalDirection.Forward); This.Selection.SetCaretToPosition(nextPosition != null ? nextPosition : lineEndPosition, originalMovingPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } else if (IsPaginated(This.TextView)) { // If line move wasn't successful because it is not in the view, bring the next line into view. This.TextView.BringLineIntoViewCompleted += new BringLineIntoViewCompletedEventHandler(HandleMoveByLineCompleted); This.TextView.BringLineIntoViewAsync(newMovingPosition, newSuggestedX, +1, This); } } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } private static void OnMoveUpByLine(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // We need a non-dirty layout to walk lines. if (!This.Selection.Start.ValidateLayout()) { return; } using (This.Selection.DeclareChangeBlock()) { if (!This.Selection.IsEmpty) { // If the selection is non-empty, collapse it. // Collapsing must happen to selection START - not to its moving position. // It is Word behavior for setting a cratet on moving down from nonempty selection. ITextPointer position = TextEditorSelection.GetStartInner(This); This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); TextEditorSelection._ClearSuggestedX(This); // So that when we will request suggestedX below it will take it from the new moving position } Invariant.Assert(This.Selection.IsEmpty); // When the caret is at RowEnd position, we start by moving it into the last cell of this row. AdjustCaretAtTableRowEnd(This); ITextPointer originalMovingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out originalMovingPosition); // Continue only if we have a moving position with valid layout if (originalMovingPosition == null) { return; } // Extend the selection edge. double newSuggestedX; int linesMoved; ITextPointer newMovingPosition = This.TextView.GetPositionAtNextLine(This.Selection.MovingPosition, suggestedX, -1, out newSuggestedX, out linesMoved); Invariant.Assert(newMovingPosition != null); if (linesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); // Move insertion point to next or previous line // This.Selection.SetCaretToPosition(newMovingPosition, newMovingPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } else { if (TextPointerBase.IsInAnchoredBlock(originalMovingPosition)) { // TextView treats AnchoredBlock elements as hard structural boundaries. // As a result GetPositionAtNextLine() does not work from the first/last line within an AnchoredBlock. // If line move wasn't successful because our moving position is at the end of an AnchoredBlock, // move insertion point to a position before the AnchoredBlock boundary. // If there is no previous position before the AnchoredBlock, move to current line start. ITextPointer lineStartPosition = GetPositionAtLineStart(originalMovingPosition); ITextPointer previousPosition = lineStartPosition.GetNextInsertionPosition(LogicalDirection.Backward); This.Selection.SetCaretToPosition(previousPosition != null ? previousPosition : lineStartPosition, originalMovingPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } else if (IsPaginated(This.TextView)) { // If line move wasn't successful because it is not in the view, bring the previous line into view. This.TextView.BringLineIntoViewCompleted += new BringLineIntoViewCompletedEventHandler(HandleMoveByLineCompleted); This.TextView.BringLineIntoViewAsync(newMovingPosition, newSuggestedX, -1, This); } } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } private static void OnMoveDownByParagraph(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); // Move/extend selection in requested direction if (!This.Selection.IsEmpty) { // If the selection is non-empty and ends on a word boundary, collapse it to that boundary. // Collapsing must happen to selection END - not to its moving position. // It is Word behavior for setting a cratet on moving down from nonempty selection. // When Selection.End is moving position we must adjust it // for LineEnd condition - choose inner position within a line. ITextPointer position = TextEditorSelection.GetEndInner(This); This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } ITextPointer movingPointer = This.Selection.MovingPosition.CreatePointer(); ITextRange paragraphRange = new TextRange(movingPointer, movingPointer); paragraphRange.SelectParagraph(movingPointer); movingPointer.MoveToPosition(paragraphRange.End); if (movingPointer.MoveToNextInsertionPosition(LogicalDirection.Forward)) { // Next paragraph found. Set selection to its start paragraphRange.SelectParagraph(movingPointer); This.Selection.SetCaretToPosition(paragraphRange.Start, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } else { // Next paragraph does not exist. Set selectionn to the end of current paragraph This.Selection.SetCaretToPosition(paragraphRange.End, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } } } private static void OnMoveUpByParagraph(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); // Move/extend selection in requested direction if (!This.Selection.IsEmpty) { // If the selection is non-empty and ends on a word boundary, collapse it to that boundary. // Collapsing must happen to selection START - not to its moving position. // It is Word behavior for setting a cratet on moving down from nonempty selection. ITextPointer position = TextEditorSelection.GetStartInner(This); This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } ITextPointer movingPointer = This.Selection.MovingPosition.CreatePointer(); ITextRange paragraphRange = new TextRange(movingPointer, movingPointer); paragraphRange.SelectParagraph(movingPointer); if (This.Selection.Start.CompareTo(paragraphRange.Start) > 0) { // We are in the middle of a paragraph. Move to its start This.Selection.SetCaretToPosition(paragraphRange.Start, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } else { movingPointer.MoveToPosition(paragraphRange.Start); if (movingPointer.MoveToNextInsertionPosition(LogicalDirection.Backward)) { // Previous paragraph found. Set selection to its start paragraphRange.SelectParagraph(movingPointer); This.Selection.SetCaretToPosition(paragraphRange.Start, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } } } } private static void OnMoveDownByPage(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // We need a non-dirty layout to walk pages. if (!This.Selection.End.ValidateLayout()) { return; } using (This.Selection.DeclareChangeBlock()) { if (!This.Selection.IsEmpty) { // If the selection is non-empty and ends on a word boundary, collapse it to that boundary. // Collapsing must happen to selection END - not to its moving position. // It is Word behavior for setting a cratet on moving down from nonempty selection. // When Selection.End is moving position we must adjust it // for LineEnd condition - choose inner position within a line. ITextPointer position = TextEditorSelection.GetEndInner(This); This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } ITextPointer movingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out movingPosition); // Continue only if we have a moving position with valid layout if (movingPosition == null) { return; } ITextPointer targetPosition; double newSuggestedX; double pageHeight = (double)This.UiScope.GetValue(TextEditor.PageHeightProperty); // Presence of page height property on TextEditor instructs us to use simple bottomless version of // pagination (TextBox/RichTextBox). // Otherwise, when page height = 0, we use TextView implementation of GetPositionAtNextPage(). // if (pageHeight == 0) { if (IsPaginated(This.TextView)) { int pagesMoved; Point newSuggestedOffset; // Get suggested Y for moving position double suggestedY = GetSuggestedYFromPosition(This, movingPosition); targetPosition = This.TextView.GetPositionAtNextPage(movingPosition, new Point(GetViewportXOffset(This.TextView, suggestedX), suggestedY), +1, out newSuggestedOffset, out pagesMoved); newSuggestedX = newSuggestedOffset.X; Invariant.Assert(targetPosition != null); if (pagesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); // If shift key isn't down, collapse the range. This.Selection.SetCaretToPosition(targetPosition, targetPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/false); } else if (IsPaginated(This.TextView)) { // If page move wasn't successful because it is not in the view, bring the next page into view. This.TextView.BringPageIntoViewCompleted += new BringPageIntoViewCompletedEventHandler(HandleMoveByPageCompleted); This.TextView.BringPageIntoViewAsync(targetPosition, newSuggestedOffset, +1, This); } } } else { // Calculate target position - at a specified distance from current movingPosition Rect targetRect = This.TextView.GetRectangleFromTextPosition(movingPosition); Point targetPoint = new Point(GetViewportXOffset(This.TextView, suggestedX), targetRect.Top + pageHeight); targetPosition = This.TextView.GetTextPositionFromPoint(targetPoint, /*snapToText:*/true); if (targetPosition == null) { return; } // Check if the new position really moving forward; // otherwise force it to the very end of container. if (targetPosition.CompareTo(movingPosition) <= 0) { targetPosition = This.TextContainer.End; TextEditorSelection._ClearSuggestedX(This); } // Fire a page up/down command on the renderScope, so any ScrollViewer will pick it up ScrollBar.PageDownCommand.Execute(null, This.TextView.RenderScope); // We need the page down to happen before the caret moves. Force a layout update // so the command queue gets processed. This.TextView.RenderScope.UpdateLayout(); // If shift key isn't down, collapse the range. This.Selection.SetCaretToPosition(targetPosition, targetPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/false); } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } private static void OnMoveUpByPage(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // We need a non-dirty layout to walk pages. if (!This.Selection.Start.ValidateLayout()) { return; } using (This.Selection.DeclareChangeBlock()) { if (!This.Selection.IsEmpty) { // If the selection is non-empty and ends on a word boundary, collapse it to that boundary. // Collapsing must happen to selection START - not to its moving position. // It is Word behavior for setting a cratet on moving down from nonempty selection. ITextPointer position = TextEditorSelection.GetStartInner(This); This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } ITextPointer movingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out movingPosition); // Continue only if we have a moving position with valid layout if (movingPosition == null) { return; } ITextPointer targetPosition; double newSuggestedX; double pageHeight = (double)This.UiScope.GetValue(TextEditor.PageHeightProperty); // Presence of page height property on TextEditor instructs us to use simple bottomless version of // pagination (TextBox/RichTextBox). // Otherwise, when page height = 0, we use TextView implementation of GetPositionAtNextPage(). if (pageHeight == 0) { if (IsPaginated(This.TextView)) { int pagesMoved; Point newSuggestedOffset; // Get suggested Y for moving position double suggestedY = GetSuggestedYFromPosition(This, movingPosition); targetPosition = This.TextView.GetPositionAtNextPage(movingPosition, new Point(GetViewportXOffset(This.TextView, suggestedX), suggestedY), -1, out newSuggestedOffset, out pagesMoved); newSuggestedX = newSuggestedOffset.X; Invariant.Assert(targetPosition != null); if (pagesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); // If shift key isn't down, collapse the range. This.Selection.SetCaretToPosition(targetPosition, targetPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/false); } else if (IsPaginated(This.TextView)) { // If page move wasn't successful because it is not in the view, bring the next page into view. This.TextView.BringPageIntoViewCompleted += new BringPageIntoViewCompletedEventHandler(HandleMoveByPageCompleted); This.TextView.BringPageIntoViewAsync(targetPosition, newSuggestedOffset, -1, This); } } } else { // Calculate target position - at a specified distance from current movingPosition Rect targetRect = This.TextView.GetRectangleFromTextPosition(movingPosition); Point targetPoint = new Point(GetViewportXOffset(This.TextView, suggestedX), targetRect.Bottom - pageHeight); targetPosition = This.TextView.GetTextPositionFromPoint(targetPoint, /*snapToText:*/true); if (targetPosition == null) { return; } // Check if the new position really moving forward; // otherwise force it to the very end of container. if (targetPosition.CompareTo(movingPosition) >= 0) { targetPosition = This.TextContainer.Start; TextEditorSelection._ClearSuggestedX(This); } // Fire a page up/down command on the renderScope, so any ScrollViewer will pick it up ScrollBar.PageUpCommand.Execute(null, This.TextView.RenderScope); // We need the page down to happen before the caret moves. Force a layout update // so the command queue gets processed. This.TextView.RenderScope.UpdateLayout(); // If shift key isn't down, collapse the range. This.Selection.SetCaretToPosition(targetPosition, targetPosition.LogicalDirection, /*allowStopAtLineEnd:*/ true, /*allowStopNearSpace:*/false); } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnMoveToLineStart(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // When getting start position, we need to take care of end-of-line case // and adjust start position according to its orientation. ITextPointer startPositionInner = TextEditorSelection.GetStartInner(This); // We need a non-dirty layout to walk pages. if (!startPositionInner.ValidateLayout()) { return; } // Standard behavior, move to begin/end of line. TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, startPositionInner); if (lineRange.IsNull) { return; } using (This.Selection.DeclareChangeBlock()) { // Note caret direction here: must be forward to keep caret on the same line // // Create caret position normalized forward - towards the very first character of the line ITextPointer caretPosition = lineRange.Start.GetFrozenPointer(LogicalDirection.Forward); // Set caret to beginning of a line This.Selection.SetCaretToPosition(caretPosition, LogicalDirection.Forward, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnMoveToLineEnd(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // When getting end position, we need to take care of end-of-line case // and adjust end position according to its orientation. ITextPointer endPositionInner = TextEditorSelection.GetEndInner(This); // We need a non-dirty layout to walk pages. if (!endPositionInner.ValidateLayout()) { return; } TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, endPositionInner); if (lineRange.IsNull) { return; } using (This.Selection.DeclareChangeBlock()) { // Note caret direction here: must be backward to keep caret on the same line when it is wrapped by flow. // Orientation must be Backward when the line is wrapped by flow, otherwise - normal Forward LogicalDirection orientation = TextPointerBase.IsNextToPlainLineBreak(lineRange.End, LogicalDirection.Backward) ? LogicalDirection.Forward : LogicalDirection.Backward; // Create caret position normalized the same way as orientation ITextPointer caretPosition = lineRange.End.GetFrozenPointer(orientation); // Set caret to the end of line This.Selection.SetCaretToPosition(caretPosition, orientation, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnMoveToDocumentStart(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { This.Selection.SetCaretToPosition(This.TextContainer.Start, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnMoveToDocumentEnd(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Orientation is standard - forward, because text cannot wrap by flow at this position This.Selection.SetCaretToPosition(This.TextContainer.End, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } // ................................................................ // // Editing Commands: Selection Building // // ................................................................ ////// SelectRightByCharacter command event handler. /// private static void OnSelectRightByCharacter(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Backward : LogicalDirection.Forward; MoveToCharacterLogicalDirection(This, movementDirection, /*extend:*/true); } ////// SelectLeftByCharacter command event handler. /// private static void OnSelectLeftByCharacter(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Forward : LogicalDirection.Backward; MoveToCharacterLogicalDirection(This, movementDirection, /*extend:*/true); } ////// private static void OnSelectRightByWord(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } // Extend word to the logical forward. LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Backward : LogicalDirection.Forward; ExtendWordLogicalDirection(This, movementDirection); } ////// private static void OnSelectLeftByWord(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } // Extend word to the logical backward. LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Forward : LogicalDirection.Backward; ExtendWordLogicalDirection(This, movementDirection); } private static void OnSelectDownByLine(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } // We need a non-dirty layout to walk lines. TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { if (This.Selection.ExtendToNextTableRow(LogicalDirection.Forward)) { // This is table selection case. Vertical extension. // Table selection has been successfully extended in vertical direction. // Nothing more to do. } else { ITextPointer originalMovingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out originalMovingPosition); // Continue only if we have a moving position with valid layout if (originalMovingPosition == null) { return; } if (This._NextLineAdvanceMovingPosition != null && This._IsNextLineAdvanceMovingPositionAtDocumentHead) { // Moving position is at the beginning of text container // as a result of previous Shift+Up extension; // so now we need to return to a positing within the current line ExtendSelectionAndBringIntoView(This._NextLineAdvanceMovingPosition, This); This._NextLineAdvanceMovingPosition = null; } else { // When the moving position is at RowEnd position, we start by moving it into the last cell of this row. ITextPointer newMovingPosition = AdjustPositionAtTableRowEnd(originalMovingPosition); // Find a position in the next line double newSuggestedX; int linesMoved; newMovingPosition = This.TextView.GetPositionAtNextLine(newMovingPosition, suggestedX, +1, out newSuggestedX, out linesMoved); Invariant.Assert(newMovingPosition != null); if (linesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); // Shift key is down - Extend the selection edge AdjustMovingPositionForSelectDownByLine(This, newMovingPosition, originalMovingPosition, newSuggestedX); } else { if (TextPointerBase.IsInAnchoredBlock(originalMovingPosition)) { // TextView treats AnchoredBlock elements as hard structural boundaries. // As a result GetPositionAtNextLine() does not work from the first/last line within an AnchoredBlock. // If line move wasn't successful because our moving position is at the end of an AnchoredBlock, // expand selection so that it crosses AnchoredBlock boundary. // If there is no next position after the AnchoredBlock, expand to current line end. ITextPointer lineEndPosition = GetPositionAtLineEnd(originalMovingPosition); ITextPointer nextPosition = lineEndPosition.GetNextInsertionPosition(LogicalDirection.Forward); // Extend selection and bring new position into view if needed (for paginated viewers) ExtendSelectionAndBringIntoView(nextPosition != null ? nextPosition : lineEndPosition, This); } else if (IsPaginated(This.TextView)) { // If line move wasn't successful because it is not in the view, bring the next line into view. This.TextView.BringLineIntoViewCompleted += new BringLineIntoViewCompletedEventHandler(HandleSelectByLineCompleted); This.TextView.BringLineIntoViewAsync(newMovingPosition, newSuggestedX, +1, 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. ExtendSelectionAndBringIntoView(GetPositionAtLineEnd(newMovingPosition), This); } } } } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } // 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); } } private static void OnSelectUpByLine(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } // We need a non-dirty layout to walk lines. TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { if (This.Selection.ExtendToNextTableRow(LogicalDirection.Backward)) { // This is table selection case. Vertical extension. // Table selection has been successfully extended in vertical direction. // Nothing more to do. } else { ITextPointer originalMovingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out originalMovingPosition); // Continue only if we have a moving position with valid layout if (originalMovingPosition == null) { return; } if (This._NextLineAdvanceMovingPosition != null && !This._IsNextLineAdvanceMovingPositionAtDocumentHead) { // Moving position is at the end of text container // as a result of previous Shift+Down extension; // so now we need to return to a positing within the current line ExtendSelectionAndBringIntoView(This._NextLineAdvanceMovingPosition, This); This._NextLineAdvanceMovingPosition = null; } else { // When the moving position is at RowEnd position, we start by moving it into the last cell of this row. ITextPointer newMovingPosition = AdjustPositionAtTableRowEnd(originalMovingPosition); // Extend the selection edge. double newSuggestedX; int linesMoved; newMovingPosition = This.TextView.GetPositionAtNextLine(newMovingPosition, suggestedX, -1, out newSuggestedX, out linesMoved); Invariant.Assert(newMovingPosition != null); if (linesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); int newComparedToOld = newMovingPosition.CompareTo(originalMovingPosition); // Shift key is down - Extend the selection edge if (newComparedToOld < 0 || newComparedToOld == 0 && newMovingPosition.LogicalDirection != originalMovingPosition.LogicalDirection) { // 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. // We have another line in a given direction; move to it ExtendSelectionAndBringIntoView(newMovingPosition, This); } else { // No more lines in this direction. Move to current line start. ExtendSelectionAndBringIntoView(GetPositionAtLineStart(originalMovingPosition), This); } } else { if (TextPointerBase.IsInAnchoredBlock(originalMovingPosition)) { // TextView treats AnchoredBlock elements as hard structural boundaries. // As a result GetPositionAtNextLine() does not work from the first/last line within an AnchoredBlock. // If line move wasn't successful because our moving position is at the start of an AnchoredBlock, // expand selection so that it crosses AnchoredBlock boundary. // If there is no previous position before the AnchoredBlock, expand to current line start. ITextPointer lineStartPosition = GetPositionAtLineStart(originalMovingPosition); ITextPointer previousPosition = lineStartPosition.GetNextInsertionPosition(LogicalDirection.Backward); // Extend selection and bring new position into view if needed (for paginated viewers) ExtendSelectionAndBringIntoView(previousPosition != null ? previousPosition : lineStartPosition, This); } else if (IsPaginated(This.TextView)) { // If line move wasn't successful because it is not in the view, bring the previous line into view. This.TextView.BringLineIntoViewCompleted += new BringLineIntoViewCompletedEventHandler(HandleSelectByLineCompleted); This.TextView.BringLineIntoViewAsync(newMovingPosition, newSuggestedX, -1, This); } else { // Remember where we were so that we can return if a line down follows. if (This._NextLineAdvanceMovingPosition == null) { This._NextLineAdvanceMovingPosition = originalMovingPosition; This._IsNextLineAdvanceMovingPositionAtDocumentHead = true; } // No more lines in this direction. Move to start of current line. ExtendSelectionAndBringIntoView(GetPositionAtLineStart(newMovingPosition), This); } } } } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } private static void OnSelectDownByParagraph(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); ITextPointer movingPointer = This.Selection.MovingPosition.CreatePointer(); ITextRange paragraphRange = new TextRange(movingPointer, movingPointer); paragraphRange.SelectParagraph(movingPointer); movingPointer.MoveToPosition(paragraphRange.End); if (movingPointer.MoveToNextInsertionPosition(LogicalDirection.Forward)) { // Next paragraph found. Set selection to its start paragraphRange.SelectParagraph(movingPointer); ExtendSelectionAndBringIntoView(paragraphRange.Start, This); } else { // Next paragraph does not exist. Set selectionn to the end of current paragraph ExtendSelectionAndBringIntoView(paragraphRange.End, This); } } } private static void OnSelectUpByParagraph(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); ITextPointer movingPointer = This.Selection.MovingPosition.CreatePointer(); ITextRange paragraphRange = new TextRange(movingPointer, movingPointer); paragraphRange.SelectParagraph(movingPointer); if (movingPointer.CompareTo(paragraphRange.Start) > 0) { // We are in the middle of a paragraph. Move to its start ExtendSelectionAndBringIntoView(paragraphRange.Start, This); } else { movingPointer.MoveToPosition(paragraphRange.Start); if (movingPointer.MoveToNextInsertionPosition(LogicalDirection.Backward)) { // Previous paragraph found. Set selection to its start paragraphRange.SelectParagraph(movingPointer); ExtendSelectionAndBringIntoView(paragraphRange.Start, This); } } } } private static void OnSelectDownByPage(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); ITextPointer movingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out movingPosition); // Continue only if we have a moving position with valid layout. if (movingPosition == null) { return; } using (This.Selection.DeclareChangeBlock()) { ITextPointer targetPosition; double newSuggestedX; double pageHeight = (double)This.UiScope.GetValue(TextEditor.PageHeightProperty); // Presence of page height property on TextEditor instructs us to use simple bottomless version of // pagination (TextBox/RichTextBox). // Otherwise, when page height = 0, we use TextView implementation of GetPositionAtNextPage(). if (pageHeight == 0) { if (IsPaginated(This.TextView)) { int pagesMoved; Point newSuggestedOffset; // Get suggested Y for moving position double suggestedY = GetSuggestedYFromPosition(This, movingPosition); targetPosition = This.TextView.GetPositionAtNextPage(movingPosition, new Point(GetViewportXOffset(This.TextView, suggestedX), suggestedY), +1, out newSuggestedOffset, out pagesMoved); newSuggestedX = newSuggestedOffset.X; Invariant.Assert(targetPosition != null); if (pagesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); ExtendSelectionAndBringIntoView(targetPosition, This); } else if (IsPaginated(This.TextView)) { // If page move wasn't successful because it is not in the view, bring the next page into view. This.TextView.BringPageIntoViewCompleted += new BringPageIntoViewCompletedEventHandler(HandleSelectByPageCompleted); This.TextView.BringPageIntoViewAsync(targetPosition, newSuggestedOffset, +1, This); } else { // No more pages in this direction. Move to container end. ExtendSelectionAndBringIntoView(targetPosition.TextContainer.End, This); } } } else { // Calculate target position - at a specified distance from current movingPosition Rect targetRect = This.TextView.GetRectangleFromTextPosition(movingPosition); Point targetPoint = new Point(GetViewportXOffset(This.TextView, suggestedX), targetRect.Top + pageHeight); targetPosition = This.TextView.GetTextPositionFromPoint(targetPoint, /*snapToText:*/true); if (targetPosition == null) { return; } // Check if the new position really moving forward; // otherwise force it to the very end of container. if (targetPosition.CompareTo(movingPosition) <= 0) { targetPosition = This.TextContainer.End; } // Extend the selection edge. ExtendSelectionAndBringIntoView(targetPosition, This); // Fire a page up/down command on the renderScope, so any ScrollViewer will pick it up ScrollBar.PageDownCommand.Execute(null, This.TextView.RenderScope); } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } private static void OnSelectUpByPage(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); ITextPointer movingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out movingPosition); // Continue only if we have a moving position with valid layout. if (movingPosition == null) { return; } using (This.Selection.DeclareChangeBlock()) { ITextPointer targetPosition; double newSuggestedX; double pageHeight = (double)This.UiScope.GetValue(TextEditor.PageHeightProperty); // Presence of page height property on TextEditor instructs us to use simple bottomless version of // pagination (TextBox/RichTextBox). // Otherwise, when page height = 0, we use TextView implementation of GetPositionAtNextPage(). if (pageHeight == 0) { if (IsPaginated(This.TextView)) { int pagesMoved; Point newSuggestedOffset; // Get suggested Y for moving position double suggestedY = GetSuggestedYFromPosition(This, movingPosition); targetPosition = This.TextView.GetPositionAtNextPage(movingPosition, new Point(GetViewportXOffset(This.TextView, suggestedX), suggestedY), -1, out newSuggestedOffset, out pagesMoved); newSuggestedX = newSuggestedOffset.X; Invariant.Assert(targetPosition != null); if (pagesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); ExtendSelectionAndBringIntoView(targetPosition, This); } else if (IsPaginated(This.TextView)) { // If page move wasn't successful because it is not in the view, bring the next page into view. This.TextView.BringPageIntoViewCompleted += new BringPageIntoViewCompletedEventHandler(HandleSelectByPageCompleted); This.TextView.BringPageIntoViewAsync(targetPosition, newSuggestedOffset, -1, This); } else { // No more pages in this direction. Move to container start. ExtendSelectionAndBringIntoView(targetPosition.TextContainer.Start, This); } } } else { // Calculate target position - at a specified distance from current movingPosition Rect targetRect = This.TextView.GetRectangleFromTextPosition(movingPosition); Point targetPoint = new Point(GetViewportXOffset(This.TextView, suggestedX), targetRect.Bottom - pageHeight); targetPosition = This.TextView.GetTextPositionFromPoint(targetPoint, /*snapToText:*/true); if (targetPosition == null) { return; } // Check if the new position really moving forward; // otherwise force it to the very end of container. if (targetPosition.CompareTo(movingPosition) >= 0) { targetPosition = This.TextContainer.Start; } // Extend the selection edge. ExtendSelectionAndBringIntoView(targetPosition, This); // Fire a page up/down command on the renderScope, so any ScrollViewer will pick it up ScrollBar.PageUpCommand.Execute(null, This.TextView.RenderScope); } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnSelectToLineStart(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // When getting moving position, we need to take care of end-of-line case // and adjust moving position according to its orientation. ITextPointer movingPositionInner = TextEditorSelection.GetMovingPositionInner(This); // We need a non-dirty layout to walk pages. if (!movingPositionInner.ValidateLayout()) { return; } TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, movingPositionInner); if (lineRange.IsNull) { return; } using (This.Selection.DeclareChangeBlock()) { // Extend the selection from the active end (oriented forward) ExtendSelectionAndBringIntoView(lineRange.Start.CreatePointer(LogicalDirection.Forward), This); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnSelectToLineEnd(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // When getting moving position, we need to take care of end-of-line case // and adjust moving position according to its orientation. ITextPointer movingPositionInner = TextEditorSelection.GetMovingPositionInner(This); // We need a non-dirty layout to walk pages. if (!movingPositionInner.ValidateLayout()) { return; } TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, movingPositionInner); if (lineRange.IsNull) { return; } // The selection end is on the other side of a line break, don't move it. if (lineRange.End.CompareTo(This.Selection.End) < 0) { return; } using (This.Selection.DeclareChangeBlock()) { ITextPointer destination = lineRange.End; if (TextPointerBase.IsNextToPlainLineBreak(destination, LogicalDirection.Forward) || TextPointerBase.IsNextToRichLineBreak(destination, LogicalDirection.Forward)) { // Extend to include any following line break if the anchor position lies at the start of a line. if (This.Selection.AnchorPosition.ValidateLayout()) { TextSegment anchorLineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, This.Selection.AnchorPosition); if (!lineRange.IsNull && anchorLineRange.Start.CompareTo(This.Selection.AnchorPosition) == 0) { destination = destination.GetNextInsertionPosition(LogicalDirection.Forward); } } } else if (TextPointerBase.IsNextToParagraphBreak(destination, LogicalDirection.Forward) && TextPointerBase.IsNextToParagraphBreak(This.Selection.AnchorPosition, LogicalDirection.Backward)) { // Extend to include any following Paragraph break if the anchor position lies at the start of a Paragraph. ITextPointer newDestination = destination.GetNextInsertionPosition(LogicalDirection.Forward); if (newDestination == null) { // We are at the end of container - extend to include position after last paragraph destination = destination.TextContainer.End; } else { destination = newDestination; } } // Set orientation towards line content destination = destination.GetFrozenPointer(LogicalDirection.Backward); // Extend the selection from the active end. ExtendSelectionAndBringIntoView(destination, This); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnSelectToDocumentStart(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { ExtendSelectionAndBringIntoView(This.TextContainer.Start, This); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnSelectToDocumentEnd(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { ExtendSelectionAndBringIntoView(This.TextContainer.End, This); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// Handler for ITextView.BringLineIntoViewCompleted event. /// private static void HandleMoveByLineCompleted(object sender, BringLineIntoViewCompletedEventArgs e) { Invariant.Assert(sender is ITextView); ((ITextView)sender).BringLineIntoViewCompleted -= new BringLineIntoViewCompletedEventHandler(HandleMoveByLineCompleted); if (e != null && !e.Cancelled && e.Error == null) { TextEditor This = e.UserState as TextEditor; if (This == null || !This._IsEnabled) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, e.NewSuggestedX); // Move insertion point to next or previous line This.Selection.SetCaretToPosition(e.NewPosition, e.NewPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } } } ////// Handler for ITextView.BringPageIntoViewCompleted event. /// private static void HandleMoveByPageCompleted(object sender, BringPageIntoViewCompletedEventArgs e) { Invariant.Assert(sender is ITextView); ((ITextView)sender).BringPageIntoViewCompleted -= new BringPageIntoViewCompletedEventHandler(HandleMoveByPageCompleted); if (e != null && !e.Cancelled && e.Error == null) { TextEditor This = e.UserState as TextEditor; if (This == null || !This._IsEnabled) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, e.NewSuggestedOffset.X); // Move insertion point to next or previous page This.Selection.SetCaretToPosition(e.NewPosition, e.NewPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } } } ////// Handler for ITextView.BringLineIntoViewCompleted event. /// private static void HandleSelectByLineCompleted(object sender, BringLineIntoViewCompletedEventArgs e) { TextEditor This; Invariant.Assert(sender is ITextView); ((ITextView)sender).BringLineIntoViewCompleted -= new BringLineIntoViewCompletedEventHandler(HandleSelectByLineCompleted); if (e != null && !e.Cancelled && e.Error == null) { This = e.UserState as TextEditor; if (This == null || !This._IsEnabled) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, e.NewSuggestedX); int newComparedToOld = e.NewPosition.CompareTo(e.Position); if (e.Count < 0) // Moving up if count < 0, moving down otherwise { // Shift key is down - Extend the selection edge if (newComparedToOld < 0 || newComparedToOld == 0 && e.NewPosition.LogicalDirection != e.Position.LogicalDirection) { // 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. // We have another line in a given direction; move to it ExtendSelectionAndBringIntoView(e.NewPosition, This); } else { // Remember where we were so that we can return if a line down follows. if (This._NextLineAdvanceMovingPosition == null) { This._NextLineAdvanceMovingPosition = e.Position; This._IsNextLineAdvanceMovingPositionAtDocumentHead = true; } // No more lines in this direction. Move to start of current line. ExtendSelectionAndBringIntoView(GetPositionAtLineStart(e.NewPosition), This); } } else { AdjustMovingPositionForSelectDownByLine(This, e.NewPosition, e.Position, e.NewSuggestedX); } } } } ////// Handler for ITextView.BringPageIntoViewCompleted event. /// private static void HandleSelectByPageCompleted(object sender, BringPageIntoViewCompletedEventArgs e) { TextEditor This; Invariant.Assert(sender is ITextView); ((ITextView)sender).BringPageIntoViewCompleted -= new BringPageIntoViewCompletedEventHandler(HandleSelectByPageCompleted); if (e != null && !e.Cancelled && e.Error == null) { This = e.UserState as TextEditor; if (This == null || !This._IsEnabled) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, e.NewSuggestedOffset.X); int newComparedToOld = e.NewPosition.CompareTo(e.Position); if (e.Count < 0) // Moving up if count < 0, moving down otherwise { // Shift key is down - Extend the selection edge if (newComparedToOld < 0) { // We have another page in a given direction; move to it ExtendSelectionAndBringIntoView(e.NewPosition, This); } else { // No more pages in this direction. Move to container start. ExtendSelectionAndBringIntoView(e.NewPosition.TextContainer.Start, This); } } else { // Shift key is down - Extend the selection edge if (newComparedToOld > 0) { // We have another page in a given direction; move to it ExtendSelectionAndBringIntoView(e.NewPosition, This); } else { // No more pages in this direction. Move to container end. ExtendSelectionAndBringIntoView(e.NewPosition.TextContainer.End, This); } } } } } // ---------------------------------------------------------- // // Misceleneous Commands // // ---------------------------------------------------------- #region Misceleneous Commands ////// Keyboard selection commands QueryStatus handler /// private static void OnQueryStatusKeyboardSelection(object target, CanExecuteRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled) { return; } args.CanExecute = true; } ////// Caret navigation commands QueryStatus handler /// private static void OnQueryStatusCaretNavigation(object target, CanExecuteRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled) { return; } // We want to disable caret navigation for readonly content, when caret is not visible. (Caret is visible in DocumentViewer case) if (This.IsReadOnly && !This.IsReadOnlyCaretVisible) { return; } args.CanExecute = true; } ////// Placeholder for commands which are not yet implemented /// /// /// private static void OnNYICommand(object source, ExecutedRoutedEventArgs args) { } #endregion Misceleneous Commands // --------------------------------------------------------- // // Misceleneous Private Methods // // ---------------------------------------------------------- #region Misceleneous Private Methods // Helper for clearing springload formatting private static void ClearSpringloadFormatting(TextEditor This) { if (This.Selection is TextSelection) { ((TextSelection)This.Selection).ClearSpringloadFormatting(); } } ////// Return true if the flow direction is RightToLeft. /// private static bool IsFlowDirectionRightToLeftThenTopToBottom(TextEditor textEditor) { Invariant.Assert(textEditor != null); ITextPointer position = textEditor.Selection.MovingPosition.CreatePointer(); // Skip any scoping formatting elements. while (TextSchema.IsFormattingType(position.ParentType)) { position.MoveToElementEdge(ElementEdge.AfterEnd); } FlowDirection flowDirection = (FlowDirection)position.GetValue(FlowDocument.FlowDirectionProperty); return (flowDirection == FlowDirection.RightToLeft); } ////// Move or extend to character according to the logical direction. /// private static void MoveToCharacterLogicalDirection(TextEditor textEditor, LogicalDirection direction, bool extend) { Invariant.Assert(textEditor != null); TextEditorTyping._FlushPendingInputItems(textEditor); // Move selection in requested direction using (textEditor.Selection.DeclareChangeBlock()) { if (extend) { // Extend the selection edge. Shift key should be pressed in this case. textEditor.Selection.ExtendToNextInsertionPosition(direction); // Bring selection moving position into view BringIntoView(textEditor.Selection.MovingPosition, textEditor); } else { // Note that when the selection is non-empty we just collapse it, i.e. use one of its ends ITextPointer movingEnd = (direction == LogicalDirection.Forward ? textEditor.Selection.End : textEditor.Selection.Start); if (textEditor.Selection.IsEmpty) { movingEnd = movingEnd.GetNextInsertionPosition(direction); } if (movingEnd != null) { // Identify an orientation toward content as a character just passed by this move LogicalDirection contentDirection = direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward; // Set caret next to a text character just passed movingEnd = movingEnd.GetInsertionPosition(contentDirection); // By disallowing to stop near spaces we suppress our "next-to-space" formatting heuristics // which would consider space character as formatting insignificant. // This means that when we pass a space by keyboard we will respect its formatting, // while when we click next to a space we always ignore space's formatting // and prefer neighboring non-space character formatting instead. textEditor.Selection.SetCaretToPosition(movingEnd, contentDirection, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } } // Remember to scroll the text if we're outside the viewport. textEditor.Selection.OnCaretNavigation(); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(textEditor); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(textEditor); // Clear springload formatting ClearSpringloadFormatting(textEditor); } } ////// Navigate word according to the logical direction. /// private static void NavigateWordLogicalDirection(TextEditor textEditor, LogicalDirection direction) { Invariant.Assert(textEditor != null); TextEditorTyping._FlushPendingInputItems(textEditor); using (textEditor.Selection.DeclareChangeBlock()) { // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(textEditor); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(textEditor); // Clear springload formatting ClearSpringloadFormatting(textEditor); if (direction == LogicalDirection.Forward) { // Move/extend selection in requested direction if (!textEditor.Selection.IsEmpty && TextPointerBase.IsAtWordBoundary(textEditor.Selection.End, LogicalDirection.Forward)) { // If the selection is non-empty and ends on a word boundary, collapse it to that boundary. textEditor.Selection.SetCaretToPosition(textEditor.Selection.End, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); // Note that we are using a "smart" version of SetCaretToPosition // to choose caret orientation on formattinng switches appropriately. } else { ITextPointer wordBoundary = textEditor.Selection.End.CreatePointer(); TextPointerBase.MoveToNextWordBoundary(wordBoundary, LogicalDirection.Forward); textEditor.Selection.SetCaretToPosition(wordBoundary, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); // Note that we are using a "smart" version of SetCaretToPosition // to choose caret orientation on formattinng switches appropriately. } } else { // Move/extend selection in requested direction if (!textEditor.Selection.IsEmpty && TextPointerBase.IsAtWordBoundary(textEditor.Selection.Start, LogicalDirection.Forward)) { // If the selection is non-empty and starts at word boundary, collapse it to that boundary. textEditor.Selection.SetCaretToPosition(textEditor.Selection.Start, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); // Note that we are using a "smart" version of SetCaretToPosition // to choose caret orientation on formattinng switches appropriately. } else { ITextPointer wordBoundary = textEditor.Selection.Start.CreatePointer(); TextPointerBase.MoveToNextWordBoundary(wordBoundary, LogicalDirection.Backward); textEditor.Selection.SetCaretToPosition(wordBoundary, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); // Note that we are using a "smart" version of SetCaretToPosition // to choose caret orientation on formattinng switches appropriately. } } // Remember to scroll the text if we're outside the viewport. textEditor.Selection.OnCaretNavigation(); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(textEditor); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(textEditor); // Clear springload formatting ClearSpringloadFormatting(textEditor); } } ////// Extend word according to the logical direction. /// private static void ExtendWordLogicalDirection(TextEditor textEditor, LogicalDirection direction) { Invariant.Assert(textEditor != null); TextEditorTyping._FlushPendingInputItems(textEditor); using (textEditor.Selection.DeclareChangeBlock()) { // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(textEditor); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(textEditor); // Clear springload formatting ClearSpringloadFormatting(textEditor); // Move/extend selection in requested direction ITextPointer wordBoundary = textEditor.Selection.MovingPosition.CreatePointer(); TextPointerBase.MoveToNextWordBoundary(wordBoundary, direction); wordBoundary.SetLogicalDirection(direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward); // Extend selection and bring new position into view if needed (for paginated viewers) ExtendSelectionAndBringIntoView(wordBoundary, textEditor); // Remember to scroll the text if we're outside the viewport. textEditor.Selection.OnCaretNavigation(); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(textEditor); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(textEditor); // Clear springload formatting ClearSpringloadFormatting(textEditor); } } ////// Returns a moving position and the suggestedX value for it. /// /// TextEditor /// Inner moving position if it has valid layout, otherwise null. ///Returns suggestedX when moving position has valid layout, Double.NaN otherwise. private static Double GetSuggestedX(TextEditor This, out ITextPointer innerMovingPosition) { // When getting moving position, we need to take care of end-of-line case // and adjust moving position according to its orientation. innerMovingPosition = TextEditorSelection.GetMovingPositionInner(This); // We need a non-dirty layout to walk pages. if (!innerMovingPosition.ValidateLayout()) { innerMovingPosition = null; return Double.NaN; // This value is not supposed to be used by a caller. } if (Double.IsNaN(This._suggestedX)) { This._suggestedX = GetAbsoluteXOffset(This.TextView, innerMovingPosition); // If the original moving position is on the other side of a line break, // add in the pixel width of the line break visualization. // Note this logic implicitly only modifies suggested x when the // selection is non-empty. if (This.Selection.MovingPosition.CompareTo(innerMovingPosition) > 0) { double breakWidth = (double)innerMovingPosition.GetValue(TextElement.FontSizeProperty) * CaretElement.c_endOfParaMagicMultiplier; FlowDirection paragraphFlowDirection = GetScopingParagraphFlowDirection(innerMovingPosition); FlowDirection controlFlowDirection = This.UiScope.FlowDirection; if (paragraphFlowDirection != controlFlowDirection) { // Adjust for X-axis flip on Paragraphs with non-default flow direction. breakWidth = -breakWidth; } This._suggestedX += breakWidth; } } return This._suggestedX; } ////// Returns a suggested Y-value for a given position. /// /// TextEditor /// Position for which suggested Y is needed ///If position is null, returns Double.NaN. This method will not find the moving position. It is meant to be /// used after the moving position has been calculated with GetSuggestedX private static Double GetSuggestedYFromPosition(TextEditor This, ITextPointer position) { double suggestedY = Double.NaN; if (position != null) { suggestedY = This.TextView.GetRectangleFromTextPosition(position).Y; } return suggestedY; } ////// Update suggestedX value if it has changed due to selection moving position crossing a page or column boundary. /// /// TextEditor /// New suggestedX value private static void UpdateSuggestedXOnColumnOrPageBoundary(TextEditor This, double newSuggestedX) { if (This._suggestedX != newSuggestedX) { This._suggestedX = newSuggestedX; } } ////// Returns a position belonging to currently selected line. /// On a line boundary it depends on the movingPosition orientation. /// When the position is at the end of Selection and oriented backward /// we consider it belonging to a previous line. /// When it is oriented forward in the same location, /// it belongs to the next line. /// This method is supposed to perform appropriate correction - /// returns a position which is inside (inner) of the desired visual line. /// private static ITextPointer GetMovingPositionInner(TextEditor This) { ITextPointer movingPosition = This.Selection.MovingPosition; if (!(movingPosition is DocumentSequenceTextPointer || movingPosition is FixedTextPointer) && movingPosition.LogicalDirection == LogicalDirection.Backward && This.Selection.Start.CompareTo(movingPosition) < 0 && TextPointerBase.IsNextToAnyBreak(movingPosition, LogicalDirection.Backward)) { movingPosition = movingPosition.GetNextInsertionPosition(LogicalDirection.Backward); // When the end of selection was after the linebreak of empty line in plainn text // we need to change its orientation to not put it at the end of preceding line. if (TextPointerBase.IsNextToPlainLineBreak(movingPosition, LogicalDirection.Backward)) { movingPosition = movingPosition.GetFrozenPointer(LogicalDirection.Forward); } } else if (TextPointerBase.IsAfterLastParagraph(movingPosition)) { movingPosition = movingPosition.GetInsertionPosition(movingPosition.LogicalDirection); } return movingPosition; } // Returns a position in the beginning of a selection which // belongs to visually selected line. // Its is Start position of a selection, only its orientation // must be Forward when selection is non-empty. private static ITextPointer GetStartInner(TextEditor This) { return This.Selection.IsEmpty ? This.Selection.Start : This.Selection.Start.GetFrozenPointer(LogicalDirection.Forward); } // Returns a position in the end of a selection, // which belongs to visually selected line. // It may be different from Selection.End when // selection ends immediately after line break // and oriented backward, i.e. belongs visually // to previous line. private static ITextPointer GetEndInner(TextEditor This) { ITextPointer end = This.Selection.End; if (end.CompareTo(This.Selection.MovingPosition) == 0) { end = GetMovingPositionInner(This); } return end; } // Returns a position at the start of current line of movingPosition. private static ITextPointer GetPositionAtLineStart(ITextPointer position) { TextSegment lineRange = position.TextContainer.TextView.GetLineRange(position); // When the moving position passed to this method is at hard structual boundaries such as AnchoredBlock or TableCell, // TextView returns a null line range. return lineRange.IsNull ? position : lineRange.Start; } // Returns a position at the end of current line of movingPosition. private static ITextPointer GetPositionAtLineEnd(ITextPointer position) { TextSegment lineRange = position.TextContainer.TextView.GetLineRange(position); return lineRange.IsNull ? position : lineRange.End; } // Helper to extend selection edge to passed position and bring it into view for paginated viewers. private static void ExtendSelectionAndBringIntoView(ITextPointer position, TextEditor textEditor) { textEditor.Selection.ExtendToPosition(position); BringIntoView(position, textEditor); } private static void BringIntoView(ITextPointer position, TextEditor textEditor) { double pageHeight = (double)textEditor.UiScope.GetValue(TextEditor.PageHeightProperty); if (pageHeight == 0 && // Check for paginated viewer case textEditor.TextView != null && textEditor.TextView.IsValid && !textEditor.TextView.Contains(position) && IsPaginated(textEditor.TextView)) { // This will bring the position into view when it is in another page for paginated viewers. textEditor.TextView.BringPositionIntoViewAsync(position, textEditor); } } // If the caret is currently at a Table row end, move it inside // the last cell. // This is useful when trying to navigate by line, because ITextView // cannot handle the special end of row position. private static void AdjustCaretAtTableRowEnd(TextEditor This) { if (This.Selection.IsEmpty && TextPointerBase.IsAtRowEnd(This.Selection.Start)) { ITextPointer position = This.Selection.Start.GetNextInsertionPosition(LogicalDirection.Backward); if (position != null) { This.Selection.SetCaretToPosition(position, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } } } // If a position is currently at a Table row end, move it inside // the last cell. // This is useful when trying to navigate by line, because ITextView // cannot handle the special end of row position. private static ITextPointer AdjustPositionAtTableRowEnd(ITextPointer position) { if (TextPointerBase.IsAtRowEnd(position)) { ITextPointer cellEnd = position.GetNextInsertionPosition(LogicalDirection.Backward); if (cellEnd != null) { position = cellEnd; } } return position; } // Returns the FlowDirection of the closest scoping Block. private static FlowDirection GetScopingParagraphFlowDirection(ITextPointer position) { ITextPointer navigator = position.CreatePointer(); while (typeof(Inline).IsAssignableFrom(navigator.ParentType)) { navigator.MoveToElementEdge(ElementEdge.BeforeStart); } return (FlowDirection)navigator.GetValue(FrameworkElement.FlowDirectionProperty); } // Returns the x offset, relative to the left edge of the document // of an ITextPointer. // // This is distinct from ITextView.GetRectFromPosition, which returns // a point relative to the viewport, which may be offset by a scrollviewer. private static double GetAbsoluteXOffset(ITextView textview, ITextPointer position) { double x = textview.GetRectangleFromTextPosition(position).X; // if (textview is TextBoxView) // Extra strict....this could be removed in the future. { IScrollInfo scrollInfo = textview as IScrollInfo; if (scrollInfo != null) { x += scrollInfo.HorizontalOffset; } } return x; } // Returns the x offset, relative to the viewport, of an x position // in document coordinates. private static double GetViewportXOffset(ITextView textview, double suggestedX) { // if (textview is TextBoxView) // Extra strict....this could be removed in the future. { IScrollInfo scrollInfo = textview as IScrollInfo; if (scrollInfo != null) { suggestedX -= scrollInfo.HorizontalOffset; } } return suggestedX; } #endregion Misceleneous Private Methods #endregion Private methods } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // // File: TextEditorSelection.cs // // Copyright (C) Microsoft Corporation. All rights reserved. // // Description: A component of TextEditor supporting selection and navigation // //--------------------------------------------------------------------------- namespace System.Windows.Documents { using MS.Internal; using System.Globalization; using System.Threading; using System.ComponentModel; using System.Text; using System.Collections; // ArrayList using System.Runtime.InteropServices; using System.Security; // SecurityCritical attribute. using System.Windows.Threading; using System.Windows.Input; using System.Windows.Controls; // ScrollChangedEventArgs using System.Windows.Controls.Primitives; // CharacterCasing, TextBoxBase using System.Windows.Media; using System.Windows.Markup; using MS.Utility; using MS.Win32; using MS.Internal.Documents; using MS.Internal.Commands; // CommandHelpers ////// Text editing service for controls. /// internal static class TextEditorSelection { //----------------------------------------------------- // // Class Internal Methods // //----------------------------------------------------- #region Class Internal Methods // Registers all text editing command handlers for a given control type ////// Critical - critical - calls a method that performs a link demand. CommandBinding ctor. /// TreatAsSafe - command bindings have link demands to prevent "hijacking" of built-in avalon commands. /// however registering our own internal commands is safe. /// [SecurityCritical, SecurityTreatAsSafe] internal static void _RegisterClassHandlers(Type controlType, bool registerEventListeners) { // Shared handlers used multiple times below. ExecutedRoutedEventHandler nyiCommandHandler = new ExecutedRoutedEventHandler(OnNYICommand); CanExecuteRoutedEventHandler queryStatusCaretNavigationHandler = new CanExecuteRoutedEventHandler(OnQueryStatusCaretNavigation); CanExecuteRoutedEventHandler queryStatusKeyboardSelectionHandler = new CanExecuteRoutedEventHandler(OnQueryStatusKeyboardSelection); // Standard Commands: Select All // ----------------------------- CommandHelpers.RegisterCommandHandler(controlType, ApplicationCommands.SelectAll, Key.A, ModifierKeys.Control, new ExecutedRoutedEventHandler(OnSelectAll), null); // Editing Commands : Caret Navigation // ----------------------------------- CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveRightByCharacter, new ExecutedRoutedEventHandler(OnMoveRightByCharacter), queryStatusCaretNavigationHandler, SRID.KeyMoveRightByCharacter, SRID.KeyMoveRightByCharacterDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveLeftByCharacter, new ExecutedRoutedEventHandler(OnMoveLeftByCharacter), queryStatusCaretNavigationHandler, SRID.KeyMoveLeftByCharacter, SRID.KeyMoveLeftByCharacterDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveRightByWord, new ExecutedRoutedEventHandler(OnMoveRightByWord), queryStatusCaretNavigationHandler, SRID.KeyMoveRightByWord, SRID.KeyMoveRightByWordDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveLeftByWord, new ExecutedRoutedEventHandler(OnMoveLeftByWord), queryStatusCaretNavigationHandler, SRID.KeyMoveLeftByWord, SRID.KeyMoveLeftByWordDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveDownByLine, new ExecutedRoutedEventHandler(OnMoveDownByLine), queryStatusCaretNavigationHandler, SRID.KeyMoveDownByLine, SRID.KeyMoveDownByLineDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveUpByLine, new ExecutedRoutedEventHandler(OnMoveUpByLine), queryStatusCaretNavigationHandler, SRID.KeyMoveUpByLine, SRID.KeyMoveUpByLineDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveDownByParagraph, new ExecutedRoutedEventHandler(OnMoveDownByParagraph), queryStatusCaretNavigationHandler, SRID.KeyMoveDownByParagraph, SRID.KeyMoveDownByParagraphDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveUpByParagraph, new ExecutedRoutedEventHandler(OnMoveUpByParagraph), queryStatusCaretNavigationHandler, SRID.KeyMoveUpByParagraph, SRID.KeyMoveUpByParagraphDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveDownByPage, new ExecutedRoutedEventHandler(OnMoveDownByPage), queryStatusCaretNavigationHandler, SRID.KeyMoveDownByPage, SRID.KeyMoveDownByPageDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveUpByPage, new ExecutedRoutedEventHandler(OnMoveUpByPage), queryStatusCaretNavigationHandler, SRID.KeyMoveUpByPage, SRID.KeyMoveUpByPageDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToLineStart, new ExecutedRoutedEventHandler(OnMoveToLineStart), queryStatusCaretNavigationHandler, SRID.KeyMoveToLineStart, SRID.KeyMoveToLineStartDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToLineEnd, new ExecutedRoutedEventHandler(OnMoveToLineEnd), queryStatusCaretNavigationHandler, SRID.KeyMoveToLineEnd, SRID.KeyMoveToLineEndDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToColumnStart, nyiCommandHandler, queryStatusCaretNavigationHandler, SRID.KeyMoveToColumnStart, SRID.KeyMoveToColumnStartDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToColumnEnd, nyiCommandHandler, queryStatusCaretNavigationHandler, SRID.KeyMoveToColumnEnd, SRID.KeyMoveToColumnEndDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToWindowTop, nyiCommandHandler, queryStatusCaretNavigationHandler, SRID.KeyMoveToWindowTop, SRID.KeyMoveToWindowTopDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToWindowBottom, nyiCommandHandler, queryStatusCaretNavigationHandler, SRID.KeyMoveToWindowBottom, SRID.KeyMoveToWindowBottomDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToDocumentStart, new ExecutedRoutedEventHandler(OnMoveToDocumentStart), queryStatusCaretNavigationHandler, SRID.KeyMoveToDocumentStart, SRID.KeyMoveToDocumentStartDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.MoveToDocumentEnd, new ExecutedRoutedEventHandler(OnMoveToDocumentEnd), queryStatusCaretNavigationHandler, SRID.KeyMoveToDocumentEnd, SRID.KeyMoveToDocumentEndDisplayString); // Editing Commands: Selection Building // ------------------------------------ CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectRightByCharacter, new ExecutedRoutedEventHandler(OnSelectRightByCharacter), queryStatusKeyboardSelectionHandler, SRID.KeySelectRightByCharacter, SRID.KeySelectRightByCharacterDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectLeftByCharacter, new ExecutedRoutedEventHandler(OnSelectLeftByCharacter), queryStatusKeyboardSelectionHandler, SRID.KeySelectLeftByCharacter, SRID.KeySelectLeftByCharacterDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectRightByWord, new ExecutedRoutedEventHandler(OnSelectRightByWord), queryStatusKeyboardSelectionHandler, SRID.KeySelectRightByWord, SRID.KeySelectRightByWordDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectLeftByWord, new ExecutedRoutedEventHandler(OnSelectLeftByWord), queryStatusKeyboardSelectionHandler, SRID.KeySelectLeftByWord, SRID.KeySelectLeftByWordDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectDownByLine, new ExecutedRoutedEventHandler(OnSelectDownByLine), queryStatusKeyboardSelectionHandler, SRID.KeySelectDownByLine, SRID.KeySelectDownByLineDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectUpByLine, new ExecutedRoutedEventHandler(OnSelectUpByLine), queryStatusKeyboardSelectionHandler, SRID.KeySelectUpByLine, SRID.KeySelectUpByLineDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectDownByParagraph, new ExecutedRoutedEventHandler(OnSelectDownByParagraph), queryStatusKeyboardSelectionHandler, SRID.KeySelectDownByParagraph, SRID.KeySelectDownByParagraphDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectUpByParagraph, new ExecutedRoutedEventHandler(OnSelectUpByParagraph), queryStatusKeyboardSelectionHandler, SRID.KeySelectUpByParagraph, SRID.KeySelectUpByParagraphDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectDownByPage, new ExecutedRoutedEventHandler(OnSelectDownByPage), queryStatusKeyboardSelectionHandler, SRID.KeySelectDownByPage, SRID.KeySelectDownByPageDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectUpByPage, new ExecutedRoutedEventHandler(OnSelectUpByPage), queryStatusKeyboardSelectionHandler, SRID.KeySelectUpByPage, SRID.KeySelectUpByPageDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToLineStart, new ExecutedRoutedEventHandler(OnSelectToLineStart), queryStatusKeyboardSelectionHandler, SRID.KeySelectToLineStart, SRID.KeySelectToLineStartDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToLineEnd, new ExecutedRoutedEventHandler(OnSelectToLineEnd), queryStatusKeyboardSelectionHandler, SRID.KeySelectToLineEnd, SRID.KeySelectToLineEndDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToColumnStart, nyiCommandHandler, queryStatusKeyboardSelectionHandler, SRID.KeySelectToColumnStart, SRID.KeySelectToColumnStartDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToColumnEnd, nyiCommandHandler, queryStatusKeyboardSelectionHandler, SRID.KeySelectToColumnEnd, SRID.KeySelectToColumnEndDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToWindowTop, nyiCommandHandler, queryStatusKeyboardSelectionHandler, SRID.KeySelectToWindowTop, SRID.KeySelectToWindowTopDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToWindowBottom, nyiCommandHandler, queryStatusKeyboardSelectionHandler, SRID.KeySelectToWindowBottom, SRID.KeySelectToWindowBottomDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToDocumentStart, new ExecutedRoutedEventHandler(OnSelectToDocumentStart), queryStatusKeyboardSelectionHandler, SRID.KeySelectToDocumentStart, SRID.KeySelectToDocumentStartDisplayString); CommandHelpers.RegisterCommandHandler(controlType, EditingCommands.SelectToDocumentEnd, new ExecutedRoutedEventHandler(OnSelectToDocumentEnd), queryStatusKeyboardSelectionHandler, SRID.KeySelectToDocumentEnd, SRID.KeySelectToDocumentEndDisplayString); } ////// Clears the suggestedX variable of passed TextEditor. /// /// TextEditor internal static void _ClearSuggestedX(TextEditor This) { // Discard stored horizontal position. // By setting to NaN, we indicate that the first following vertical movement // must define suggestedX from the current moving position. This._suggestedX = Double.NaN; This._NextLineAdvanceMovingPosition = null; } // Returns a normalized line range from TextView for a given position. // Note: In current contract, line range returned by TextView.GetLineRange() is not guaranteed to be normalized. // This helper does appropriate correction and returns a normalized line range. internal static TextSegment GetNormalizedLineRange(ITextView textView, ITextPointer position) { TextSegment lineRange = textView.GetLineRange(position); if (lineRange.IsNull) { if (!typeof(BlockUIContainer).IsAssignableFrom(position.ParentType)) { return lineRange; } ITextPointer lineStart = position.CreatePointer(LogicalDirection.Forward); lineStart.MoveToElementEdge(ElementEdge.AfterStart); ITextPointer lineEnd = position.CreatePointer(LogicalDirection.Backward); lineEnd.MoveToElementEdge(ElementEdge.BeforeEnd); lineRange = new TextSegment(lineStart, lineEnd); return lineRange; } // Normalize line range ITextRange textRange = new TextRange(lineRange.Start, lineRange.End); return new TextSegment(textRange.Start, textRange.End); } // Returns true if a textview is potentially paginated. internal static bool IsPaginated(ITextView textview) { return !(textview is TextBoxView); } #endregion Class Internal Methods //------------------------------------------------------ // // Private Methods // //----------------------------------------------------- #region Private Methods ////// private static void OnSelectAll(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock(true /* disableScroll */)) { This.Selection.Select(This.TextContainer.Start, This.TextContainer.End); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } // ................................................................ // // Editing Commands: Caret Navigation // // ................................................................ ////// MoveRightByCharacter command event handler. /// private static void OnMoveRightByCharacter(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Backward : LogicalDirection.Forward; MoveToCharacterLogicalDirection(This, movementDirection, /*extend:*/false); } ////// MoveLeftByCharacter command event handler. /// private static void OnMoveLeftByCharacter(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Forward : LogicalDirection.Backward; MoveToCharacterLogicalDirection(This, movementDirection, /*extend:*/false); } ////// private static void OnMoveRightByWord(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } // Navigate word to the logical forward. LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Backward : LogicalDirection.Forward; NavigateWordLogicalDirection(This, movementDirection); } ////// private static void OnMoveLeftByWord(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } // Navigate word to the logical backward. LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Forward : LogicalDirection.Backward; NavigateWordLogicalDirection(This, movementDirection); } private static void OnMoveDownByLine(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // We need a non-dirty layout to walk lines. if (!This.Selection.End.ValidateLayout()) { return; } using (This.Selection.DeclareChangeBlock()) { if (!This.Selection.IsEmpty) { // If the selection is non-empty, collapse it. // Collapsing must happen to selection END - not to its moving position. // It is Word behavior for setting a cratet on moving down from nonempty selection. // When Selection.End is moving position we must adjust it // for LineEnd condition - choose inner position within a line. ITextPointer position = TextEditorSelection.GetEndInner(This); This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); TextEditorSelection._ClearSuggestedX(This); // So that when we will request suggestedX below it will take it from the new moving position } Invariant.Assert(This.Selection.IsEmpty); // When the caret is at RowEnd position, we start by moving it into the last cell of this row. AdjustCaretAtTableRowEnd(This); ITextPointer originalMovingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out originalMovingPosition); // Continue only if we have a moving position with valid layout if (originalMovingPosition == null) { return; } // Extend the selection edge. double newSuggestedX; int linesMoved; ITextPointer newMovingPosition = This.TextView.GetPositionAtNextLine(This.Selection.MovingPosition, suggestedX, +1, out newSuggestedX, out linesMoved); Invariant.Assert(newMovingPosition != null); if (linesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); // Move insertion point to next or previous line This.Selection.SetCaretToPosition(newMovingPosition, newMovingPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } else { if (TextPointerBase.IsInAnchoredBlock(originalMovingPosition)) { // TextView treats AnchoredBlock elements as hard structural boundaries. // As a result GetPositionAtNextLine() does not work from the first/last line within an AnchoredBlock. // If line move wasn't successful because our moving position is at the end of an AnchoredBlock, // move insertion point so that it crosses AnchoredBlock boundary. // If there is no next position after the AnchoredBlock, move to current line end. ITextPointer lineEndPosition = GetPositionAtLineEnd(originalMovingPosition); ITextPointer nextPosition = lineEndPosition.GetNextInsertionPosition(LogicalDirection.Forward); This.Selection.SetCaretToPosition(nextPosition != null ? nextPosition : lineEndPosition, originalMovingPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } else if (IsPaginated(This.TextView)) { // If line move wasn't successful because it is not in the view, bring the next line into view. This.TextView.BringLineIntoViewCompleted += new BringLineIntoViewCompletedEventHandler(HandleMoveByLineCompleted); This.TextView.BringLineIntoViewAsync(newMovingPosition, newSuggestedX, +1, This); } } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } private static void OnMoveUpByLine(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // We need a non-dirty layout to walk lines. if (!This.Selection.Start.ValidateLayout()) { return; } using (This.Selection.DeclareChangeBlock()) { if (!This.Selection.IsEmpty) { // If the selection is non-empty, collapse it. // Collapsing must happen to selection START - not to its moving position. // It is Word behavior for setting a cratet on moving down from nonempty selection. ITextPointer position = TextEditorSelection.GetStartInner(This); This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); TextEditorSelection._ClearSuggestedX(This); // So that when we will request suggestedX below it will take it from the new moving position } Invariant.Assert(This.Selection.IsEmpty); // When the caret is at RowEnd position, we start by moving it into the last cell of this row. AdjustCaretAtTableRowEnd(This); ITextPointer originalMovingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out originalMovingPosition); // Continue only if we have a moving position with valid layout if (originalMovingPosition == null) { return; } // Extend the selection edge. double newSuggestedX; int linesMoved; ITextPointer newMovingPosition = This.TextView.GetPositionAtNextLine(This.Selection.MovingPosition, suggestedX, -1, out newSuggestedX, out linesMoved); Invariant.Assert(newMovingPosition != null); if (linesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); // Move insertion point to next or previous line // This.Selection.SetCaretToPosition(newMovingPosition, newMovingPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } else { if (TextPointerBase.IsInAnchoredBlock(originalMovingPosition)) { // TextView treats AnchoredBlock elements as hard structural boundaries. // As a result GetPositionAtNextLine() does not work from the first/last line within an AnchoredBlock. // If line move wasn't successful because our moving position is at the end of an AnchoredBlock, // move insertion point to a position before the AnchoredBlock boundary. // If there is no previous position before the AnchoredBlock, move to current line start. ITextPointer lineStartPosition = GetPositionAtLineStart(originalMovingPosition); ITextPointer previousPosition = lineStartPosition.GetNextInsertionPosition(LogicalDirection.Backward); This.Selection.SetCaretToPosition(previousPosition != null ? previousPosition : lineStartPosition, originalMovingPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } else if (IsPaginated(This.TextView)) { // If line move wasn't successful because it is not in the view, bring the previous line into view. This.TextView.BringLineIntoViewCompleted += new BringLineIntoViewCompletedEventHandler(HandleMoveByLineCompleted); This.TextView.BringLineIntoViewAsync(newMovingPosition, newSuggestedX, -1, This); } } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } private static void OnMoveDownByParagraph(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); // Move/extend selection in requested direction if (!This.Selection.IsEmpty) { // If the selection is non-empty and ends on a word boundary, collapse it to that boundary. // Collapsing must happen to selection END - not to its moving position. // It is Word behavior for setting a cratet on moving down from nonempty selection. // When Selection.End is moving position we must adjust it // for LineEnd condition - choose inner position within a line. ITextPointer position = TextEditorSelection.GetEndInner(This); This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } ITextPointer movingPointer = This.Selection.MovingPosition.CreatePointer(); ITextRange paragraphRange = new TextRange(movingPointer, movingPointer); paragraphRange.SelectParagraph(movingPointer); movingPointer.MoveToPosition(paragraphRange.End); if (movingPointer.MoveToNextInsertionPosition(LogicalDirection.Forward)) { // Next paragraph found. Set selection to its start paragraphRange.SelectParagraph(movingPointer); This.Selection.SetCaretToPosition(paragraphRange.Start, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } else { // Next paragraph does not exist. Set selectionn to the end of current paragraph This.Selection.SetCaretToPosition(paragraphRange.End, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } } } private static void OnMoveUpByParagraph(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); // Move/extend selection in requested direction if (!This.Selection.IsEmpty) { // If the selection is non-empty and ends on a word boundary, collapse it to that boundary. // Collapsing must happen to selection START - not to its moving position. // It is Word behavior for setting a cratet on moving down from nonempty selection. ITextPointer position = TextEditorSelection.GetStartInner(This); This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } ITextPointer movingPointer = This.Selection.MovingPosition.CreatePointer(); ITextRange paragraphRange = new TextRange(movingPointer, movingPointer); paragraphRange.SelectParagraph(movingPointer); if (This.Selection.Start.CompareTo(paragraphRange.Start) > 0) { // We are in the middle of a paragraph. Move to its start This.Selection.SetCaretToPosition(paragraphRange.Start, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } else { movingPointer.MoveToPosition(paragraphRange.Start); if (movingPointer.MoveToNextInsertionPosition(LogicalDirection.Backward)) { // Previous paragraph found. Set selection to its start paragraphRange.SelectParagraph(movingPointer); This.Selection.SetCaretToPosition(paragraphRange.Start, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } } } } private static void OnMoveDownByPage(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // We need a non-dirty layout to walk pages. if (!This.Selection.End.ValidateLayout()) { return; } using (This.Selection.DeclareChangeBlock()) { if (!This.Selection.IsEmpty) { // If the selection is non-empty and ends on a word boundary, collapse it to that boundary. // Collapsing must happen to selection END - not to its moving position. // It is Word behavior for setting a cratet on moving down from nonempty selection. // When Selection.End is moving position we must adjust it // for LineEnd condition - choose inner position within a line. ITextPointer position = TextEditorSelection.GetEndInner(This); This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } ITextPointer movingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out movingPosition); // Continue only if we have a moving position with valid layout if (movingPosition == null) { return; } ITextPointer targetPosition; double newSuggestedX; double pageHeight = (double)This.UiScope.GetValue(TextEditor.PageHeightProperty); // Presence of page height property on TextEditor instructs us to use simple bottomless version of // pagination (TextBox/RichTextBox). // Otherwise, when page height = 0, we use TextView implementation of GetPositionAtNextPage(). // if (pageHeight == 0) { if (IsPaginated(This.TextView)) { int pagesMoved; Point newSuggestedOffset; // Get suggested Y for moving position double suggestedY = GetSuggestedYFromPosition(This, movingPosition); targetPosition = This.TextView.GetPositionAtNextPage(movingPosition, new Point(GetViewportXOffset(This.TextView, suggestedX), suggestedY), +1, out newSuggestedOffset, out pagesMoved); newSuggestedX = newSuggestedOffset.X; Invariant.Assert(targetPosition != null); if (pagesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); // If shift key isn't down, collapse the range. This.Selection.SetCaretToPosition(targetPosition, targetPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/false); } else if (IsPaginated(This.TextView)) { // If page move wasn't successful because it is not in the view, bring the next page into view. This.TextView.BringPageIntoViewCompleted += new BringPageIntoViewCompletedEventHandler(HandleMoveByPageCompleted); This.TextView.BringPageIntoViewAsync(targetPosition, newSuggestedOffset, +1, This); } } } else { // Calculate target position - at a specified distance from current movingPosition Rect targetRect = This.TextView.GetRectangleFromTextPosition(movingPosition); Point targetPoint = new Point(GetViewportXOffset(This.TextView, suggestedX), targetRect.Top + pageHeight); targetPosition = This.TextView.GetTextPositionFromPoint(targetPoint, /*snapToText:*/true); if (targetPosition == null) { return; } // Check if the new position really moving forward; // otherwise force it to the very end of container. if (targetPosition.CompareTo(movingPosition) <= 0) { targetPosition = This.TextContainer.End; TextEditorSelection._ClearSuggestedX(This); } // Fire a page up/down command on the renderScope, so any ScrollViewer will pick it up ScrollBar.PageDownCommand.Execute(null, This.TextView.RenderScope); // We need the page down to happen before the caret moves. Force a layout update // so the command queue gets processed. This.TextView.RenderScope.UpdateLayout(); // If shift key isn't down, collapse the range. This.Selection.SetCaretToPosition(targetPosition, targetPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/false); } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } private static void OnMoveUpByPage(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // We need a non-dirty layout to walk pages. if (!This.Selection.Start.ValidateLayout()) { return; } using (This.Selection.DeclareChangeBlock()) { if (!This.Selection.IsEmpty) { // If the selection is non-empty and ends on a word boundary, collapse it to that boundary. // Collapsing must happen to selection START - not to its moving position. // It is Word behavior for setting a cratet on moving down from nonempty selection. ITextPointer position = TextEditorSelection.GetStartInner(This); This.Selection.SetCaretToPosition(position, position.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } ITextPointer movingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out movingPosition); // Continue only if we have a moving position with valid layout if (movingPosition == null) { return; } ITextPointer targetPosition; double newSuggestedX; double pageHeight = (double)This.UiScope.GetValue(TextEditor.PageHeightProperty); // Presence of page height property on TextEditor instructs us to use simple bottomless version of // pagination (TextBox/RichTextBox). // Otherwise, when page height = 0, we use TextView implementation of GetPositionAtNextPage(). if (pageHeight == 0) { if (IsPaginated(This.TextView)) { int pagesMoved; Point newSuggestedOffset; // Get suggested Y for moving position double suggestedY = GetSuggestedYFromPosition(This, movingPosition); targetPosition = This.TextView.GetPositionAtNextPage(movingPosition, new Point(GetViewportXOffset(This.TextView, suggestedX), suggestedY), -1, out newSuggestedOffset, out pagesMoved); newSuggestedX = newSuggestedOffset.X; Invariant.Assert(targetPosition != null); if (pagesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); // If shift key isn't down, collapse the range. This.Selection.SetCaretToPosition(targetPosition, targetPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/false); } else if (IsPaginated(This.TextView)) { // If page move wasn't successful because it is not in the view, bring the next page into view. This.TextView.BringPageIntoViewCompleted += new BringPageIntoViewCompletedEventHandler(HandleMoveByPageCompleted); This.TextView.BringPageIntoViewAsync(targetPosition, newSuggestedOffset, -1, This); } } } else { // Calculate target position - at a specified distance from current movingPosition Rect targetRect = This.TextView.GetRectangleFromTextPosition(movingPosition); Point targetPoint = new Point(GetViewportXOffset(This.TextView, suggestedX), targetRect.Bottom - pageHeight); targetPosition = This.TextView.GetTextPositionFromPoint(targetPoint, /*snapToText:*/true); if (targetPosition == null) { return; } // Check if the new position really moving forward; // otherwise force it to the very end of container. if (targetPosition.CompareTo(movingPosition) >= 0) { targetPosition = This.TextContainer.Start; TextEditorSelection._ClearSuggestedX(This); } // Fire a page up/down command on the renderScope, so any ScrollViewer will pick it up ScrollBar.PageUpCommand.Execute(null, This.TextView.RenderScope); // We need the page down to happen before the caret moves. Force a layout update // so the command queue gets processed. This.TextView.RenderScope.UpdateLayout(); // If shift key isn't down, collapse the range. This.Selection.SetCaretToPosition(targetPosition, targetPosition.LogicalDirection, /*allowStopAtLineEnd:*/ true, /*allowStopNearSpace:*/false); } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnMoveToLineStart(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // When getting start position, we need to take care of end-of-line case // and adjust start position according to its orientation. ITextPointer startPositionInner = TextEditorSelection.GetStartInner(This); // We need a non-dirty layout to walk pages. if (!startPositionInner.ValidateLayout()) { return; } // Standard behavior, move to begin/end of line. TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, startPositionInner); if (lineRange.IsNull) { return; } using (This.Selection.DeclareChangeBlock()) { // Note caret direction here: must be forward to keep caret on the same line // // Create caret position normalized forward - towards the very first character of the line ITextPointer caretPosition = lineRange.Start.GetFrozenPointer(LogicalDirection.Forward); // Set caret to beginning of a line This.Selection.SetCaretToPosition(caretPosition, LogicalDirection.Forward, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnMoveToLineEnd(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // When getting end position, we need to take care of end-of-line case // and adjust end position according to its orientation. ITextPointer endPositionInner = TextEditorSelection.GetEndInner(This); // We need a non-dirty layout to walk pages. if (!endPositionInner.ValidateLayout()) { return; } TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, endPositionInner); if (lineRange.IsNull) { return; } using (This.Selection.DeclareChangeBlock()) { // Note caret direction here: must be backward to keep caret on the same line when it is wrapped by flow. // Orientation must be Backward when the line is wrapped by flow, otherwise - normal Forward LogicalDirection orientation = TextPointerBase.IsNextToPlainLineBreak(lineRange.End, LogicalDirection.Backward) ? LogicalDirection.Forward : LogicalDirection.Backward; // Create caret position normalized the same way as orientation ITextPointer caretPosition = lineRange.End.GetFrozenPointer(orientation); // Set caret to the end of line This.Selection.SetCaretToPosition(caretPosition, orientation, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnMoveToDocumentStart(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { This.Selection.SetCaretToPosition(This.TextContainer.Start, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnMoveToDocumentEnd(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Orientation is standard - forward, because text cannot wrap by flow at this position This.Selection.SetCaretToPosition(This.TextContainer.End, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } // ................................................................ // // Editing Commands: Selection Building // // ................................................................ ////// SelectRightByCharacter command event handler. /// private static void OnSelectRightByCharacter(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Backward : LogicalDirection.Forward; MoveToCharacterLogicalDirection(This, movementDirection, /*extend:*/true); } ////// SelectLeftByCharacter command event handler. /// private static void OnSelectLeftByCharacter(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Forward : LogicalDirection.Backward; MoveToCharacterLogicalDirection(This, movementDirection, /*extend:*/true); } ////// private static void OnSelectRightByWord(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } // Extend word to the logical forward. LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Backward : LogicalDirection.Forward; ExtendWordLogicalDirection(This, movementDirection); } ////// private static void OnSelectLeftByWord(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } // Extend word to the logical backward. LogicalDirection movementDirection = IsFlowDirectionRightToLeftThenTopToBottom(This) ? LogicalDirection.Forward : LogicalDirection.Backward; ExtendWordLogicalDirection(This, movementDirection); } private static void OnSelectDownByLine(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } // We need a non-dirty layout to walk lines. TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { if (This.Selection.ExtendToNextTableRow(LogicalDirection.Forward)) { // This is table selection case. Vertical extension. // Table selection has been successfully extended in vertical direction. // Nothing more to do. } else { ITextPointer originalMovingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out originalMovingPosition); // Continue only if we have a moving position with valid layout if (originalMovingPosition == null) { return; } if (This._NextLineAdvanceMovingPosition != null && This._IsNextLineAdvanceMovingPositionAtDocumentHead) { // Moving position is at the beginning of text container // as a result of previous Shift+Up extension; // so now we need to return to a positing within the current line ExtendSelectionAndBringIntoView(This._NextLineAdvanceMovingPosition, This); This._NextLineAdvanceMovingPosition = null; } else { // When the moving position is at RowEnd position, we start by moving it into the last cell of this row. ITextPointer newMovingPosition = AdjustPositionAtTableRowEnd(originalMovingPosition); // Find a position in the next line double newSuggestedX; int linesMoved; newMovingPosition = This.TextView.GetPositionAtNextLine(newMovingPosition, suggestedX, +1, out newSuggestedX, out linesMoved); Invariant.Assert(newMovingPosition != null); if (linesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); // Shift key is down - Extend the selection edge AdjustMovingPositionForSelectDownByLine(This, newMovingPosition, originalMovingPosition, newSuggestedX); } else { if (TextPointerBase.IsInAnchoredBlock(originalMovingPosition)) { // TextView treats AnchoredBlock elements as hard structural boundaries. // As a result GetPositionAtNextLine() does not work from the first/last line within an AnchoredBlock. // If line move wasn't successful because our moving position is at the end of an AnchoredBlock, // expand selection so that it crosses AnchoredBlock boundary. // If there is no next position after the AnchoredBlock, expand to current line end. ITextPointer lineEndPosition = GetPositionAtLineEnd(originalMovingPosition); ITextPointer nextPosition = lineEndPosition.GetNextInsertionPosition(LogicalDirection.Forward); // Extend selection and bring new position into view if needed (for paginated viewers) ExtendSelectionAndBringIntoView(nextPosition != null ? nextPosition : lineEndPosition, This); } else if (IsPaginated(This.TextView)) { // If line move wasn't successful because it is not in the view, bring the next line into view. This.TextView.BringLineIntoViewCompleted += new BringLineIntoViewCompletedEventHandler(HandleSelectByLineCompleted); This.TextView.BringLineIntoViewAsync(newMovingPosition, newSuggestedX, +1, 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. ExtendSelectionAndBringIntoView(GetPositionAtLineEnd(newMovingPosition), This); } } } } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } // 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); } } private static void OnSelectUpByLine(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } // We need a non-dirty layout to walk lines. TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { if (This.Selection.ExtendToNextTableRow(LogicalDirection.Backward)) { // This is table selection case. Vertical extension. // Table selection has been successfully extended in vertical direction. // Nothing more to do. } else { ITextPointer originalMovingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out originalMovingPosition); // Continue only if we have a moving position with valid layout if (originalMovingPosition == null) { return; } if (This._NextLineAdvanceMovingPosition != null && !This._IsNextLineAdvanceMovingPositionAtDocumentHead) { // Moving position is at the end of text container // as a result of previous Shift+Down extension; // so now we need to return to a positing within the current line ExtendSelectionAndBringIntoView(This._NextLineAdvanceMovingPosition, This); This._NextLineAdvanceMovingPosition = null; } else { // When the moving position is at RowEnd position, we start by moving it into the last cell of this row. ITextPointer newMovingPosition = AdjustPositionAtTableRowEnd(originalMovingPosition); // Extend the selection edge. double newSuggestedX; int linesMoved; newMovingPosition = This.TextView.GetPositionAtNextLine(newMovingPosition, suggestedX, -1, out newSuggestedX, out linesMoved); Invariant.Assert(newMovingPosition != null); if (linesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); int newComparedToOld = newMovingPosition.CompareTo(originalMovingPosition); // Shift key is down - Extend the selection edge if (newComparedToOld < 0 || newComparedToOld == 0 && newMovingPosition.LogicalDirection != originalMovingPosition.LogicalDirection) { // 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. // We have another line in a given direction; move to it ExtendSelectionAndBringIntoView(newMovingPosition, This); } else { // No more lines in this direction. Move to current line start. ExtendSelectionAndBringIntoView(GetPositionAtLineStart(originalMovingPosition), This); } } else { if (TextPointerBase.IsInAnchoredBlock(originalMovingPosition)) { // TextView treats AnchoredBlock elements as hard structural boundaries. // As a result GetPositionAtNextLine() does not work from the first/last line within an AnchoredBlock. // If line move wasn't successful because our moving position is at the start of an AnchoredBlock, // expand selection so that it crosses AnchoredBlock boundary. // If there is no previous position before the AnchoredBlock, expand to current line start. ITextPointer lineStartPosition = GetPositionAtLineStart(originalMovingPosition); ITextPointer previousPosition = lineStartPosition.GetNextInsertionPosition(LogicalDirection.Backward); // Extend selection and bring new position into view if needed (for paginated viewers) ExtendSelectionAndBringIntoView(previousPosition != null ? previousPosition : lineStartPosition, This); } else if (IsPaginated(This.TextView)) { // If line move wasn't successful because it is not in the view, bring the previous line into view. This.TextView.BringLineIntoViewCompleted += new BringLineIntoViewCompletedEventHandler(HandleSelectByLineCompleted); This.TextView.BringLineIntoViewAsync(newMovingPosition, newSuggestedX, -1, This); } else { // Remember where we were so that we can return if a line down follows. if (This._NextLineAdvanceMovingPosition == null) { This._NextLineAdvanceMovingPosition = originalMovingPosition; This._IsNextLineAdvanceMovingPositionAtDocumentHead = true; } // No more lines in this direction. Move to start of current line. ExtendSelectionAndBringIntoView(GetPositionAtLineStart(newMovingPosition), This); } } } } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } private static void OnSelectDownByParagraph(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); ITextPointer movingPointer = This.Selection.MovingPosition.CreatePointer(); ITextRange paragraphRange = new TextRange(movingPointer, movingPointer); paragraphRange.SelectParagraph(movingPointer); movingPointer.MoveToPosition(paragraphRange.End); if (movingPointer.MoveToNextInsertionPosition(LogicalDirection.Forward)) { // Next paragraph found. Set selection to its start paragraphRange.SelectParagraph(movingPointer); ExtendSelectionAndBringIntoView(paragraphRange.Start, This); } else { // Next paragraph does not exist. Set selectionn to the end of current paragraph ExtendSelectionAndBringIntoView(paragraphRange.End, This); } } } private static void OnSelectUpByParagraph(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); ITextPointer movingPointer = This.Selection.MovingPosition.CreatePointer(); ITextRange paragraphRange = new TextRange(movingPointer, movingPointer); paragraphRange.SelectParagraph(movingPointer); if (movingPointer.CompareTo(paragraphRange.Start) > 0) { // We are in the middle of a paragraph. Move to its start ExtendSelectionAndBringIntoView(paragraphRange.Start, This); } else { movingPointer.MoveToPosition(paragraphRange.Start); if (movingPointer.MoveToNextInsertionPosition(LogicalDirection.Backward)) { // Previous paragraph found. Set selection to its start paragraphRange.SelectParagraph(movingPointer); ExtendSelectionAndBringIntoView(paragraphRange.Start, This); } } } } private static void OnSelectDownByPage(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); ITextPointer movingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out movingPosition); // Continue only if we have a moving position with valid layout. if (movingPosition == null) { return; } using (This.Selection.DeclareChangeBlock()) { ITextPointer targetPosition; double newSuggestedX; double pageHeight = (double)This.UiScope.GetValue(TextEditor.PageHeightProperty); // Presence of page height property on TextEditor instructs us to use simple bottomless version of // pagination (TextBox/RichTextBox). // Otherwise, when page height = 0, we use TextView implementation of GetPositionAtNextPage(). if (pageHeight == 0) { if (IsPaginated(This.TextView)) { int pagesMoved; Point newSuggestedOffset; // Get suggested Y for moving position double suggestedY = GetSuggestedYFromPosition(This, movingPosition); targetPosition = This.TextView.GetPositionAtNextPage(movingPosition, new Point(GetViewportXOffset(This.TextView, suggestedX), suggestedY), +1, out newSuggestedOffset, out pagesMoved); newSuggestedX = newSuggestedOffset.X; Invariant.Assert(targetPosition != null); if (pagesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); ExtendSelectionAndBringIntoView(targetPosition, This); } else if (IsPaginated(This.TextView)) { // If page move wasn't successful because it is not in the view, bring the next page into view. This.TextView.BringPageIntoViewCompleted += new BringPageIntoViewCompletedEventHandler(HandleSelectByPageCompleted); This.TextView.BringPageIntoViewAsync(targetPosition, newSuggestedOffset, +1, This); } else { // No more pages in this direction. Move to container end. ExtendSelectionAndBringIntoView(targetPosition.TextContainer.End, This); } } } else { // Calculate target position - at a specified distance from current movingPosition Rect targetRect = This.TextView.GetRectangleFromTextPosition(movingPosition); Point targetPoint = new Point(GetViewportXOffset(This.TextView, suggestedX), targetRect.Top + pageHeight); targetPosition = This.TextView.GetTextPositionFromPoint(targetPoint, /*snapToText:*/true); if (targetPosition == null) { return; } // Check if the new position really moving forward; // otherwise force it to the very end of container. if (targetPosition.CompareTo(movingPosition) <= 0) { targetPosition = This.TextContainer.End; } // Extend the selection edge. ExtendSelectionAndBringIntoView(targetPosition, This); // Fire a page up/down command on the renderScope, so any ScrollViewer will pick it up ScrollBar.PageDownCommand.Execute(null, This.TextView.RenderScope); } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } private static void OnSelectUpByPage(object sender, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(sender); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); ITextPointer movingPosition; double suggestedX = TextEditorSelection.GetSuggestedX(This, out movingPosition); // Continue only if we have a moving position with valid layout. if (movingPosition == null) { return; } using (This.Selection.DeclareChangeBlock()) { ITextPointer targetPosition; double newSuggestedX; double pageHeight = (double)This.UiScope.GetValue(TextEditor.PageHeightProperty); // Presence of page height property on TextEditor instructs us to use simple bottomless version of // pagination (TextBox/RichTextBox). // Otherwise, when page height = 0, we use TextView implementation of GetPositionAtNextPage(). if (pageHeight == 0) { if (IsPaginated(This.TextView)) { int pagesMoved; Point newSuggestedOffset; // Get suggested Y for moving position double suggestedY = GetSuggestedYFromPosition(This, movingPosition); targetPosition = This.TextView.GetPositionAtNextPage(movingPosition, new Point(GetViewportXOffset(This.TextView, suggestedX), suggestedY), -1, out newSuggestedOffset, out pagesMoved); newSuggestedX = newSuggestedOffset.X; Invariant.Assert(targetPosition != null); if (pagesMoved != 0) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, newSuggestedX); ExtendSelectionAndBringIntoView(targetPosition, This); } else if (IsPaginated(This.TextView)) { // If page move wasn't successful because it is not in the view, bring the next page into view. This.TextView.BringPageIntoViewCompleted += new BringPageIntoViewCompletedEventHandler(HandleSelectByPageCompleted); This.TextView.BringPageIntoViewAsync(targetPosition, newSuggestedOffset, -1, This); } else { // No more pages in this direction. Move to container start. ExtendSelectionAndBringIntoView(targetPosition.TextContainer.Start, This); } } } else { // Calculate target position - at a specified distance from current movingPosition Rect targetRect = This.TextView.GetRectangleFromTextPosition(movingPosition); Point targetPoint = new Point(GetViewportXOffset(This.TextView, suggestedX), targetRect.Bottom - pageHeight); targetPosition = This.TextView.GetTextPositionFromPoint(targetPoint, /*snapToText:*/true); if (targetPosition == null) { return; } // Check if the new position really moving forward; // otherwise force it to the very end of container. if (targetPosition.CompareTo(movingPosition) >= 0) { targetPosition = This.TextContainer.Start; } // Extend the selection edge. ExtendSelectionAndBringIntoView(targetPosition, This); // Fire a page up/down command on the renderScope, so any ScrollViewer will pick it up ScrollBar.PageUpCommand.Execute(null, This.TextView.RenderScope); } // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnSelectToLineStart(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // When getting moving position, we need to take care of end-of-line case // and adjust moving position according to its orientation. ITextPointer movingPositionInner = TextEditorSelection.GetMovingPositionInner(This); // We need a non-dirty layout to walk pages. if (!movingPositionInner.ValidateLayout()) { return; } TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, movingPositionInner); if (lineRange.IsNull) { return; } using (This.Selection.DeclareChangeBlock()) { // Extend the selection from the active end (oriented forward) ExtendSelectionAndBringIntoView(lineRange.Start.CreatePointer(LogicalDirection.Forward), This); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnSelectToLineEnd(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); // When getting moving position, we need to take care of end-of-line case // and adjust moving position according to its orientation. ITextPointer movingPositionInner = TextEditorSelection.GetMovingPositionInner(This); // We need a non-dirty layout to walk pages. if (!movingPositionInner.ValidateLayout()) { return; } TextSegment lineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, movingPositionInner); if (lineRange.IsNull) { return; } // The selection end is on the other side of a line break, don't move it. if (lineRange.End.CompareTo(This.Selection.End) < 0) { return; } using (This.Selection.DeclareChangeBlock()) { ITextPointer destination = lineRange.End; if (TextPointerBase.IsNextToPlainLineBreak(destination, LogicalDirection.Forward) || TextPointerBase.IsNextToRichLineBreak(destination, LogicalDirection.Forward)) { // Extend to include any following line break if the anchor position lies at the start of a line. if (This.Selection.AnchorPosition.ValidateLayout()) { TextSegment anchorLineRange = TextEditorSelection.GetNormalizedLineRange(This.TextView, This.Selection.AnchorPosition); if (!lineRange.IsNull && anchorLineRange.Start.CompareTo(This.Selection.AnchorPosition) == 0) { destination = destination.GetNextInsertionPosition(LogicalDirection.Forward); } } } else if (TextPointerBase.IsNextToParagraphBreak(destination, LogicalDirection.Forward) && TextPointerBase.IsNextToParagraphBreak(This.Selection.AnchorPosition, LogicalDirection.Backward)) { // Extend to include any following Paragraph break if the anchor position lies at the start of a Paragraph. ITextPointer newDestination = destination.GetNextInsertionPosition(LogicalDirection.Forward); if (newDestination == null) { // We are at the end of container - extend to include position after last paragraph destination = destination.TextContainer.End; } else { destination = newDestination; } } // Set orientation towards line content destination = destination.GetFrozenPointer(LogicalDirection.Backward); // Extend the selection from the active end. ExtendSelectionAndBringIntoView(destination, This); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnSelectToDocumentStart(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { ExtendSelectionAndBringIntoView(This.TextContainer.Start, This); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// private static void OnSelectToDocumentEnd(object target, ExecutedRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled || !This._IsSourceInScope(args.Source)) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { ExtendSelectionAndBringIntoView(This.TextContainer.End, This); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(This); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(This); // Clear springload formatting ClearSpringloadFormatting(This); } } ////// Handler for ITextView.BringLineIntoViewCompleted event. /// private static void HandleMoveByLineCompleted(object sender, BringLineIntoViewCompletedEventArgs e) { Invariant.Assert(sender is ITextView); ((ITextView)sender).BringLineIntoViewCompleted -= new BringLineIntoViewCompletedEventHandler(HandleMoveByLineCompleted); if (e != null && !e.Cancelled && e.Error == null) { TextEditor This = e.UserState as TextEditor; if (This == null || !This._IsEnabled) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, e.NewSuggestedX); // Move insertion point to next or previous line This.Selection.SetCaretToPosition(e.NewPosition, e.NewPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } } } ////// Handler for ITextView.BringPageIntoViewCompleted event. /// private static void HandleMoveByPageCompleted(object sender, BringPageIntoViewCompletedEventArgs e) { Invariant.Assert(sender is ITextView); ((ITextView)sender).BringPageIntoViewCompleted -= new BringPageIntoViewCompletedEventHandler(HandleMoveByPageCompleted); if (e != null && !e.Cancelled && e.Error == null) { TextEditor This = e.UserState as TextEditor; if (This == null || !This._IsEnabled) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, e.NewSuggestedOffset.X); // Move insertion point to next or previous page This.Selection.SetCaretToPosition(e.NewPosition, e.NewPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/true); } } } ////// Handler for ITextView.BringLineIntoViewCompleted event. /// private static void HandleSelectByLineCompleted(object sender, BringLineIntoViewCompletedEventArgs e) { TextEditor This; Invariant.Assert(sender is ITextView); ((ITextView)sender).BringLineIntoViewCompleted -= new BringLineIntoViewCompletedEventHandler(HandleSelectByLineCompleted); if (e != null && !e.Cancelled && e.Error == null) { This = e.UserState as TextEditor; if (This == null || !This._IsEnabled) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, e.NewSuggestedX); int newComparedToOld = e.NewPosition.CompareTo(e.Position); if (e.Count < 0) // Moving up if count < 0, moving down otherwise { // Shift key is down - Extend the selection edge if (newComparedToOld < 0 || newComparedToOld == 0 && e.NewPosition.LogicalDirection != e.Position.LogicalDirection) { // 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. // We have another line in a given direction; move to it ExtendSelectionAndBringIntoView(e.NewPosition, This); } else { // Remember where we were so that we can return if a line down follows. if (This._NextLineAdvanceMovingPosition == null) { This._NextLineAdvanceMovingPosition = e.Position; This._IsNextLineAdvanceMovingPositionAtDocumentHead = true; } // No more lines in this direction. Move to start of current line. ExtendSelectionAndBringIntoView(GetPositionAtLineStart(e.NewPosition), This); } } else { AdjustMovingPositionForSelectDownByLine(This, e.NewPosition, e.Position, e.NewSuggestedX); } } } } ////// Handler for ITextView.BringPageIntoViewCompleted event. /// private static void HandleSelectByPageCompleted(object sender, BringPageIntoViewCompletedEventArgs e) { TextEditor This; Invariant.Assert(sender is ITextView); ((ITextView)sender).BringPageIntoViewCompleted -= new BringPageIntoViewCompletedEventHandler(HandleSelectByPageCompleted); if (e != null && !e.Cancelled && e.Error == null) { This = e.UserState as TextEditor; if (This == null || !This._IsEnabled) { return; } TextEditorTyping._FlushPendingInputItems(This); using (This.Selection.DeclareChangeBlock()) { // Update suggestedX TextEditorSelection.UpdateSuggestedXOnColumnOrPageBoundary(This, e.NewSuggestedOffset.X); int newComparedToOld = e.NewPosition.CompareTo(e.Position); if (e.Count < 0) // Moving up if count < 0, moving down otherwise { // Shift key is down - Extend the selection edge if (newComparedToOld < 0) { // We have another page in a given direction; move to it ExtendSelectionAndBringIntoView(e.NewPosition, This); } else { // No more pages in this direction. Move to container start. ExtendSelectionAndBringIntoView(e.NewPosition.TextContainer.Start, This); } } else { // Shift key is down - Extend the selection edge if (newComparedToOld > 0) { // We have another page in a given direction; move to it ExtendSelectionAndBringIntoView(e.NewPosition, This); } else { // No more pages in this direction. Move to container end. ExtendSelectionAndBringIntoView(e.NewPosition.TextContainer.End, This); } } } } } // ---------------------------------------------------------- // // Misceleneous Commands // // ---------------------------------------------------------- #region Misceleneous Commands ////// Keyboard selection commands QueryStatus handler /// private static void OnQueryStatusKeyboardSelection(object target, CanExecuteRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled) { return; } args.CanExecute = true; } ////// Caret navigation commands QueryStatus handler /// private static void OnQueryStatusCaretNavigation(object target, CanExecuteRoutedEventArgs args) { TextEditor This = TextEditor._GetTextEditor(target); if (This == null || !This._IsEnabled) { return; } // We want to disable caret navigation for readonly content, when caret is not visible. (Caret is visible in DocumentViewer case) if (This.IsReadOnly && !This.IsReadOnlyCaretVisible) { return; } args.CanExecute = true; } ////// Placeholder for commands which are not yet implemented /// /// /// private static void OnNYICommand(object source, ExecutedRoutedEventArgs args) { } #endregion Misceleneous Commands // --------------------------------------------------------- // // Misceleneous Private Methods // // ---------------------------------------------------------- #region Misceleneous Private Methods // Helper for clearing springload formatting private static void ClearSpringloadFormatting(TextEditor This) { if (This.Selection is TextSelection) { ((TextSelection)This.Selection).ClearSpringloadFormatting(); } } ////// Return true if the flow direction is RightToLeft. /// private static bool IsFlowDirectionRightToLeftThenTopToBottom(TextEditor textEditor) { Invariant.Assert(textEditor != null); ITextPointer position = textEditor.Selection.MovingPosition.CreatePointer(); // Skip any scoping formatting elements. while (TextSchema.IsFormattingType(position.ParentType)) { position.MoveToElementEdge(ElementEdge.AfterEnd); } FlowDirection flowDirection = (FlowDirection)position.GetValue(FlowDocument.FlowDirectionProperty); return (flowDirection == FlowDirection.RightToLeft); } ////// Move or extend to character according to the logical direction. /// private static void MoveToCharacterLogicalDirection(TextEditor textEditor, LogicalDirection direction, bool extend) { Invariant.Assert(textEditor != null); TextEditorTyping._FlushPendingInputItems(textEditor); // Move selection in requested direction using (textEditor.Selection.DeclareChangeBlock()) { if (extend) { // Extend the selection edge. Shift key should be pressed in this case. textEditor.Selection.ExtendToNextInsertionPosition(direction); // Bring selection moving position into view BringIntoView(textEditor.Selection.MovingPosition, textEditor); } else { // Note that when the selection is non-empty we just collapse it, i.e. use one of its ends ITextPointer movingEnd = (direction == LogicalDirection.Forward ? textEditor.Selection.End : textEditor.Selection.Start); if (textEditor.Selection.IsEmpty) { movingEnd = movingEnd.GetNextInsertionPosition(direction); } if (movingEnd != null) { // Identify an orientation toward content as a character just passed by this move LogicalDirection contentDirection = direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward; // Set caret next to a text character just passed movingEnd = movingEnd.GetInsertionPosition(contentDirection); // By disallowing to stop near spaces we suppress our "next-to-space" formatting heuristics // which would consider space character as formatting insignificant. // This means that when we pass a space by keyboard we will respect its formatting, // while when we click next to a space we always ignore space's formatting // and prefer neighboring non-space character formatting instead. textEditor.Selection.SetCaretToPosition(movingEnd, contentDirection, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } } // Remember to scroll the text if we're outside the viewport. textEditor.Selection.OnCaretNavigation(); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(textEditor); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(textEditor); // Clear springload formatting ClearSpringloadFormatting(textEditor); } } ////// Navigate word according to the logical direction. /// private static void NavigateWordLogicalDirection(TextEditor textEditor, LogicalDirection direction) { Invariant.Assert(textEditor != null); TextEditorTyping._FlushPendingInputItems(textEditor); using (textEditor.Selection.DeclareChangeBlock()) { // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(textEditor); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(textEditor); // Clear springload formatting ClearSpringloadFormatting(textEditor); if (direction == LogicalDirection.Forward) { // Move/extend selection in requested direction if (!textEditor.Selection.IsEmpty && TextPointerBase.IsAtWordBoundary(textEditor.Selection.End, LogicalDirection.Forward)) { // If the selection is non-empty and ends on a word boundary, collapse it to that boundary. textEditor.Selection.SetCaretToPosition(textEditor.Selection.End, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); // Note that we are using a "smart" version of SetCaretToPosition // to choose caret orientation on formattinng switches appropriately. } else { ITextPointer wordBoundary = textEditor.Selection.End.CreatePointer(); TextPointerBase.MoveToNextWordBoundary(wordBoundary, LogicalDirection.Forward); textEditor.Selection.SetCaretToPosition(wordBoundary, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); // Note that we are using a "smart" version of SetCaretToPosition // to choose caret orientation on formattinng switches appropriately. } } else { // Move/extend selection in requested direction if (!textEditor.Selection.IsEmpty && TextPointerBase.IsAtWordBoundary(textEditor.Selection.Start, LogicalDirection.Forward)) { // If the selection is non-empty and starts at word boundary, collapse it to that boundary. textEditor.Selection.SetCaretToPosition(textEditor.Selection.Start, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); // Note that we are using a "smart" version of SetCaretToPosition // to choose caret orientation on formattinng switches appropriately. } else { ITextPointer wordBoundary = textEditor.Selection.Start.CreatePointer(); TextPointerBase.MoveToNextWordBoundary(wordBoundary, LogicalDirection.Backward); textEditor.Selection.SetCaretToPosition(wordBoundary, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); // Note that we are using a "smart" version of SetCaretToPosition // to choose caret orientation on formattinng switches appropriately. } } // Remember to scroll the text if we're outside the viewport. textEditor.Selection.OnCaretNavigation(); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(textEditor); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(textEditor); // Clear springload formatting ClearSpringloadFormatting(textEditor); } } ////// Extend word according to the logical direction. /// private static void ExtendWordLogicalDirection(TextEditor textEditor, LogicalDirection direction) { Invariant.Assert(textEditor != null); TextEditorTyping._FlushPendingInputItems(textEditor); using (textEditor.Selection.DeclareChangeBlock()) { // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(textEditor); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(textEditor); // Clear springload formatting ClearSpringloadFormatting(textEditor); // Move/extend selection in requested direction ITextPointer wordBoundary = textEditor.Selection.MovingPosition.CreatePointer(); TextPointerBase.MoveToNextWordBoundary(wordBoundary, direction); wordBoundary.SetLogicalDirection(direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward); // Extend selection and bring new position into view if needed (for paginated viewers) ExtendSelectionAndBringIntoView(wordBoundary, textEditor); // Remember to scroll the text if we're outside the viewport. textEditor.Selection.OnCaretNavigation(); // Forget previously suggested horizontal position TextEditorSelection._ClearSuggestedX(textEditor); // Discard typing undo unit merging TextEditorTyping._BreakTypingSequence(textEditor); // Clear springload formatting ClearSpringloadFormatting(textEditor); } } ////// Returns a moving position and the suggestedX value for it. /// /// TextEditor /// Inner moving position if it has valid layout, otherwise null. ///Returns suggestedX when moving position has valid layout, Double.NaN otherwise. private static Double GetSuggestedX(TextEditor This, out ITextPointer innerMovingPosition) { // When getting moving position, we need to take care of end-of-line case // and adjust moving position according to its orientation. innerMovingPosition = TextEditorSelection.GetMovingPositionInner(This); // We need a non-dirty layout to walk pages. if (!innerMovingPosition.ValidateLayout()) { innerMovingPosition = null; return Double.NaN; // This value is not supposed to be used by a caller. } if (Double.IsNaN(This._suggestedX)) { This._suggestedX = GetAbsoluteXOffset(This.TextView, innerMovingPosition); // If the original moving position is on the other side of a line break, // add in the pixel width of the line break visualization. // Note this logic implicitly only modifies suggested x when the // selection is non-empty. if (This.Selection.MovingPosition.CompareTo(innerMovingPosition) > 0) { double breakWidth = (double)innerMovingPosition.GetValue(TextElement.FontSizeProperty) * CaretElement.c_endOfParaMagicMultiplier; FlowDirection paragraphFlowDirection = GetScopingParagraphFlowDirection(innerMovingPosition); FlowDirection controlFlowDirection = This.UiScope.FlowDirection; if (paragraphFlowDirection != controlFlowDirection) { // Adjust for X-axis flip on Paragraphs with non-default flow direction. breakWidth = -breakWidth; } This._suggestedX += breakWidth; } } return This._suggestedX; } ////// Returns a suggested Y-value for a given position. /// /// TextEditor /// Position for which suggested Y is needed ///If position is null, returns Double.NaN. This method will not find the moving position. It is meant to be /// used after the moving position has been calculated with GetSuggestedX private static Double GetSuggestedYFromPosition(TextEditor This, ITextPointer position) { double suggestedY = Double.NaN; if (position != null) { suggestedY = This.TextView.GetRectangleFromTextPosition(position).Y; } return suggestedY; } ////// Update suggestedX value if it has changed due to selection moving position crossing a page or column boundary. /// /// TextEditor /// New suggestedX value private static void UpdateSuggestedXOnColumnOrPageBoundary(TextEditor This, double newSuggestedX) { if (This._suggestedX != newSuggestedX) { This._suggestedX = newSuggestedX; } } ////// Returns a position belonging to currently selected line. /// On a line boundary it depends on the movingPosition orientation. /// When the position is at the end of Selection and oriented backward /// we consider it belonging to a previous line. /// When it is oriented forward in the same location, /// it belongs to the next line. /// This method is supposed to perform appropriate correction - /// returns a position which is inside (inner) of the desired visual line. /// private static ITextPointer GetMovingPositionInner(TextEditor This) { ITextPointer movingPosition = This.Selection.MovingPosition; if (!(movingPosition is DocumentSequenceTextPointer || movingPosition is FixedTextPointer) && movingPosition.LogicalDirection == LogicalDirection.Backward && This.Selection.Start.CompareTo(movingPosition) < 0 && TextPointerBase.IsNextToAnyBreak(movingPosition, LogicalDirection.Backward)) { movingPosition = movingPosition.GetNextInsertionPosition(LogicalDirection.Backward); // When the end of selection was after the linebreak of empty line in plainn text // we need to change its orientation to not put it at the end of preceding line. if (TextPointerBase.IsNextToPlainLineBreak(movingPosition, LogicalDirection.Backward)) { movingPosition = movingPosition.GetFrozenPointer(LogicalDirection.Forward); } } else if (TextPointerBase.IsAfterLastParagraph(movingPosition)) { movingPosition = movingPosition.GetInsertionPosition(movingPosition.LogicalDirection); } return movingPosition; } // Returns a position in the beginning of a selection which // belongs to visually selected line. // Its is Start position of a selection, only its orientation // must be Forward when selection is non-empty. private static ITextPointer GetStartInner(TextEditor This) { return This.Selection.IsEmpty ? This.Selection.Start : This.Selection.Start.GetFrozenPointer(LogicalDirection.Forward); } // Returns a position in the end of a selection, // which belongs to visually selected line. // It may be different from Selection.End when // selection ends immediately after line break // and oriented backward, i.e. belongs visually // to previous line. private static ITextPointer GetEndInner(TextEditor This) { ITextPointer end = This.Selection.End; if (end.CompareTo(This.Selection.MovingPosition) == 0) { end = GetMovingPositionInner(This); } return end; } // Returns a position at the start of current line of movingPosition. private static ITextPointer GetPositionAtLineStart(ITextPointer position) { TextSegment lineRange = position.TextContainer.TextView.GetLineRange(position); // When the moving position passed to this method is at hard structual boundaries such as AnchoredBlock or TableCell, // TextView returns a null line range. return lineRange.IsNull ? position : lineRange.Start; } // Returns a position at the end of current line of movingPosition. private static ITextPointer GetPositionAtLineEnd(ITextPointer position) { TextSegment lineRange = position.TextContainer.TextView.GetLineRange(position); return lineRange.IsNull ? position : lineRange.End; } // Helper to extend selection edge to passed position and bring it into view for paginated viewers. private static void ExtendSelectionAndBringIntoView(ITextPointer position, TextEditor textEditor) { textEditor.Selection.ExtendToPosition(position); BringIntoView(position, textEditor); } private static void BringIntoView(ITextPointer position, TextEditor textEditor) { double pageHeight = (double)textEditor.UiScope.GetValue(TextEditor.PageHeightProperty); if (pageHeight == 0 && // Check for paginated viewer case textEditor.TextView != null && textEditor.TextView.IsValid && !textEditor.TextView.Contains(position) && IsPaginated(textEditor.TextView)) { // This will bring the position into view when it is in another page for paginated viewers. textEditor.TextView.BringPositionIntoViewAsync(position, textEditor); } } // If the caret is currently at a Table row end, move it inside // the last cell. // This is useful when trying to navigate by line, because ITextView // cannot handle the special end of row position. private static void AdjustCaretAtTableRowEnd(TextEditor This) { if (This.Selection.IsEmpty && TextPointerBase.IsAtRowEnd(This.Selection.Start)) { ITextPointer position = This.Selection.Start.GetNextInsertionPosition(LogicalDirection.Backward); if (position != null) { This.Selection.SetCaretToPosition(position, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } } } // If a position is currently at a Table row end, move it inside // the last cell. // This is useful when trying to navigate by line, because ITextView // cannot handle the special end of row position. private static ITextPointer AdjustPositionAtTableRowEnd(ITextPointer position) { if (TextPointerBase.IsAtRowEnd(position)) { ITextPointer cellEnd = position.GetNextInsertionPosition(LogicalDirection.Backward); if (cellEnd != null) { position = cellEnd; } } return position; } // Returns the FlowDirection of the closest scoping Block. private static FlowDirection GetScopingParagraphFlowDirection(ITextPointer position) { ITextPointer navigator = position.CreatePointer(); while (typeof(Inline).IsAssignableFrom(navigator.ParentType)) { navigator.MoveToElementEdge(ElementEdge.BeforeStart); } return (FlowDirection)navigator.GetValue(FrameworkElement.FlowDirectionProperty); } // Returns the x offset, relative to the left edge of the document // of an ITextPointer. // // This is distinct from ITextView.GetRectFromPosition, which returns // a point relative to the viewport, which may be offset by a scrollviewer. private static double GetAbsoluteXOffset(ITextView textview, ITextPointer position) { double x = textview.GetRectangleFromTextPosition(position).X; // if (textview is TextBoxView) // Extra strict....this could be removed in the future. { IScrollInfo scrollInfo = textview as IScrollInfo; if (scrollInfo != null) { x += scrollInfo.HorizontalOffset; } } return x; } // Returns the x offset, relative to the viewport, of an x position // in document coordinates. private static double GetViewportXOffset(ITextView textview, double suggestedX) { // if (textview is TextBoxView) // Extra strict....this could be removed in the future. { IScrollInfo scrollInfo = textview as IScrollInfo; if (scrollInfo != null) { suggestedX -= scrollInfo.HorizontalOffset; } } return suggestedX; } #endregion Misceleneous Private Methods #endregion Private methods } } // 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
- SelectedDatesCollection.cs
- RepeatBehaviorConverter.cs
- TypeReference.cs
- GradientStop.cs
- Matrix.cs
- Range.cs
- ConvertEvent.cs
- _PooledStream.cs
- HandlerFactoryCache.cs
- VisualStyleElement.cs
- TemplateBuilder.cs
- RTLAwareMessageBox.cs
- WebConfigurationHostFileChange.cs
- Help.cs
- ComponentResourceKeyConverter.cs
- EventsTab.cs
- basevalidator.cs
- Solver.cs
- OdbcEnvironmentHandle.cs
- BasicHttpMessageSecurity.cs
- SqlDataSourceConfigureFilterForm.cs
- EncoderFallback.cs
- MULTI_QI.cs
- ToolBar.cs
- ContentOperations.cs
- DesignerLoader.cs
- DocumentGridContextMenu.cs
- ProviderCollection.cs
- AnimationException.cs
- DecoderFallbackWithFailureFlag.cs
- HttpHandlerAction.cs
- SystemEvents.cs
- SymDocumentType.cs
- TraceEventCache.cs
- SQLBinaryStorage.cs
- BamlLocalizableResourceKey.cs
- InputProcessorProfiles.cs
- Quaternion.cs
- Delay.cs
- TextSearch.cs
- WebPartEditVerb.cs
- CatalogZone.cs
- ColumnReorderedEventArgs.cs
- DataGridHeaderBorder.cs
- ScrollChrome.cs
- WebPartConnectionsCloseVerb.cs
- ClientSideProviderDescription.cs
- AssociatedControlConverter.cs
- AsyncOperation.cs
- ValidateNames.cs
- WeakReadOnlyCollection.cs
- PlaceHolder.cs
- HttpException.cs
- DisableDpiAwarenessAttribute.cs
- LeaseManager.cs
- IpcChannel.cs
- DocumentViewerAutomationPeer.cs
- odbcmetadatacollectionnames.cs
- SiteMapSection.cs
- Thumb.cs
- AspNetPartialTrustHelpers.cs
- SelectionEditingBehavior.cs
- LocalValueEnumerator.cs
- ScalarType.cs
- HttpWriter.cs
- SpStreamWrapper.cs
- Selector.cs
- ActivityStateRecord.cs
- StylusButtonCollection.cs
- FloatAverageAggregationOperator.cs
- DetailsViewUpdateEventArgs.cs
- IndicCharClassifier.cs
- RegexMatch.cs
- WeakHashtable.cs
- ReadOnlyDictionary.cs
- HttpChannelBindingToken.cs
- ToolStripContentPanelRenderEventArgs.cs
- XmlSerializerFactory.cs
- BufferedWebEventProvider.cs
- InputMethod.cs
- PrincipalPermission.cs
- Span.cs
- BasicViewGenerator.cs
- ViewCellSlot.cs
- RealProxy.cs
- ComplexPropertyEntry.cs
- DesignerActionVerbItem.cs
- ErrorRuntimeConfig.cs
- WebControl.cs
- DBSchemaTable.cs
- ControlType.cs
- DataTableMappingCollection.cs
- DataControlImageButton.cs
- BitmapDecoder.cs
- FormsAuthenticationCredentials.cs
- CompensationParticipant.cs
- HostUtils.cs
- Parser.cs
- InvalidComObjectException.cs
- ToolStripDropDown.cs