Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Framework / System / Windows / Documents / TextSelection.cs / 1 / TextSelection.cs
//---------------------------------------------------------------------------- // // File: TextSelection.cs // // Copyright (C) Microsoft Corporation. All rights reserved. // // Description: Holds and manipulates the text selection state for TextEditor. // //--------------------------------------------------------------------------- namespace System.Windows.Documents { using MS.Internal; using System.Collections.Generic; using System.Globalization; using System.Windows.Controls.Primitives; // TextBoxBase using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using System.Threading; using System.Security; using System.Security.Permissions; using System.IO; using System.Xml; using MS.Win32; ////// The TextSelection class encapsulates selection state for the RichTextBox /// control. It has no public constructor, but is exposed via a public /// property on RichTextBox. /// public sealed class TextSelection : TextRange, ITextSelection { #region Constructors //----------------------------------------------------- // // Constructors // //----------------------------------------------------- // Contstructor. // TextSelection does not have a public constructor. It is only accessible // through TextEditor's Selection property. internal TextSelection(TextEditor textEditor) : base(textEditor.TextContainer.Start, textEditor.TextContainer.Start) { ITextSelection thisSelection = (ITextSelection)this; Invariant.Assert(textEditor.UiScope != null); // Attach the selection to its editor _textEditor = textEditor; // Initialize active pointers of the selection - anchor and moving pointers SetActivePositions(/*AnchorPosition:*/thisSelection.Start, thisSelection.End); // Activate selection in case if this control has keyboard focus already thisSelection.UpdateCaretAndHighlight(); } #endregion Constructors // ****************************************************** // ***************************************************** // ****************************************************** // // Abstract TextSelection Implementation // // ****************************************************** // ***************************************************** // ****************************************************** //----------------------------------------------------- // // ITextRange implementation // //----------------------------------------------------- #region ITextRange Implementation //...................................................... // // Selection Building // //...................................................... ////// void ITextRange.Select(ITextPointer anchorPosition, ITextPointer movingPosition) { TextRangeBase.BeginChange(this); try { TextRangeBase.Select(this, anchorPosition, movingPosition); SetActivePositions(anchorPosition, movingPosition); } finally { TextRangeBase.EndChange(this); } } ////// /// void ITextRange.SelectWord(ITextPointer position) { TextRangeBase.BeginChange(this); try { TextRangeBase.SelectWord(this, position); ITextSelection thisSelection = this; SetActivePositions(/*anchorPosition:*/thisSelection.Start, thisSelection.End); } finally { TextRangeBase.EndChange(this); } } ////// /// void ITextRange.SelectParagraph(ITextPointer position) { TextRangeBase.BeginChange(this); try { TextRangeBase.SelectParagraph(this, position); // ITextSelection thisSelection = this; SetActivePositions(/*anchorPosition:*/position, thisSelection.End); } finally { TextRangeBase.EndChange(this); } } ////// /// void ITextRange.ApplyTypingHeuristics(bool overType) { TextRangeBase.BeginChange(this); try { TextRangeBase.ApplyInitialTypingHeuristics(this); // For non-empty selection start with saving current formatting if (!this.IsEmpty && _textEditor.AcceptsRichContent) { SpringloadCurrentFormatting(); // Note: we springload formatting before overtype expansion } TextRangeBase.ApplyFinalTypingHeuristics(this, overType); } finally { TextRangeBase.EndChange(this); } } ////// /// object ITextRange.GetPropertyValue(DependencyProperty formattingProperty) { object propertyValue; if (this.IsEmpty && TextSchema.IsCharacterProperty(formattingProperty)) { // For empty selection return springloaded formatting (only character formatting properties can be springloaded) propertyValue = ((TextSelection)this).GetCurrentValue(formattingProperty); } else { // Otherwise return base implementation from a TextRange. propertyValue = TextRangeBase.GetPropertyValue(this, formattingProperty); } return propertyValue; } //...................................................... // // Plain Text Modification // //...................................................... //----------------------------------------------------- // // Overrides // //------------------------------------------------------ // Set true if a Changed event is pending. bool ITextRange._IsChanged { get { return _IsChanged; } set { // TextStore needs to know about state changes // from false to true. if (!_IsChanged && value) { if (this.TextStore != null) { this.TextStore.OnSelectionChange(); } if (this.ImmComposition != null) { this.ImmComposition.OnSelectionChange(); } } _IsChanged = value; } } ////// /// void ITextRange.NotifyChanged(bool disableScroll, bool skipEvents) { // Notify text store about selection movement. if (this.TextStore != null) { this.TextStore.OnSelectionChanged(); } // Notify ImmComposition about selection movement. if (this.ImmComposition != null) { this.ImmComposition.OnSelectionChanged(); } if (!skipEvents) { TextRangeBase.NotifyChanged(this, disableScroll); } if (!disableScroll) { // Force a synchronous layout update. If the update was big enough, background layout // kicked in and the caret won't otherwise be updated. Note this will block the thread // while layout runs. // // It's possible an application repositioned the caret // while listening to a change event just raised, but in that case the following // code should be harmless. ITextPointer movingPosition = ((ITextSelection)this).MovingPosition; if (this.TextView != null && this.TextView.IsValid && !this.TextView.Contains(movingPosition)) { movingPosition.ValidateLayout(); } // If layout wasn't valid, then there's a pending caret update // that will proceed correctly now. Otherwise the whole operation // is a nop. } // Fixup the caret. UpdateCaretState(disableScroll ? CaretScrollMethod.None : CaretScrollMethod.Simple); } //----------------------------------------------------- // // ITextRange Properties // //------------------------------------------------------ //...................................................... // // Content - rich and plain // //...................................................... ////// string ITextRange.Text { get { return TextRangeBase.GetText(this); } set { TextRangeBase.BeginChange(this); try { TextRangeBase.SetText(this, value); if (this.IsEmpty) { // We need to ensure appropriate caret visual position. ((ITextSelection)this).SetCaretToPosition(((ITextRange)this).End, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } Invariant.Assert(!this.IsTableCellRange); SetActivePositions(((ITextRange)this).Start, ((ITextRange)this).End); } finally { TextRangeBase.EndChange(this); } } } #endregion ITextRange Implementation //------------------------------------------------------ // // ITextSelection implementation // //----------------------------------------------------- #region ITextSelection Implementation void ITextSelection.UpdateCaretAndHighlight() { if (this.UiScope.IsEnabled && this.TextView != null && this.UiScope.IsKeyboardFocused) { // Update the TLS first, so that EnsureCaret is working in // the correct context. SetThreadSelection(); // Make caret visible and blinking EnsureCaret(/*isBlinkEnabled:*/true, CaretScrollMethod.None); // Highlight selection Highlight(); } else if (this.UiScope.IsEnabled && this.TextView != null && this.UiScope.IsFocused && IsRootElement(FocusManager.GetFocusScope(this.UiScope)) && (IsFocusWithinRoot() || // either UiScope root window has keyboard focus _textEditor.IsContextMenuOpen)) // or UiScope has a context menu open { // Update the TLS first, so that EnsureCaret is working in // the correct context. SetThreadSelection(); // just stop the caret from blinking EnsureCaret(/*isBlinkEnabled:*/false, CaretScrollMethod.None); // Highlight selection Highlight(); } else { // Update the TLS first, so that the caret is working in // the correct context. ClearThreadSelection(); // delete the caret DetachCaretFromVisualTree(); // Remove highlight Unhighlight(); } } ////// /// ITextPointer ITextSelection.AnchorPosition { get { Invariant.Assert(this.IsEmpty || _anchorPosition != null); Invariant.Assert(_anchorPosition == null || _anchorPosition.IsFrozen); return this.IsEmpty ? ((ITextSelection)this).Start : _anchorPosition; } } ////// The position within this selection that responds to user input. /// ITextPointer ITextSelection.MovingPosition { get { ITextSelection thisSelection = this; ITextPointer movingPosition; if (this.IsEmpty) { movingPosition = thisSelection.Start; } else { switch (_movingPositionEdge) { case MovingEdge.Start: movingPosition = thisSelection.Start; break; case MovingEdge.StartInner: movingPosition = thisSelection.TextSegments[0].End; break; case MovingEdge.EndInner: movingPosition = thisSelection.TextSegments[thisSelection.TextSegments.Count - 1].Start; break; case MovingEdge.End: movingPosition = thisSelection.End; break; case MovingEdge.None: default: Invariant.Assert(false, "MovingEdge should never be None with non-empty TextSelection!"); movingPosition = null; break; } movingPosition = movingPosition.GetFrozenPointer(_movingPositionDirection); } return movingPosition; } } ////// void ITextSelection.SetCaretToPosition(ITextPointer caretPosition, LogicalDirection direction, bool allowStopAtLineEnd, bool allowStopNearSpace) { // We need a pointer with appropriate direction, // becasue it will be used in textRangeBase.Select method for // pointer normalization. caretPosition = caretPosition.CreatePointer(direction); // Normalize the position in its logical direction - to get to text content over there. caretPosition.MoveToInsertionPosition(direction); // We need a pointer with the reverse direction to confirm // the line wrapping position. So we can ensure Bidi caret navigation. // Bidi can have the different caret position by setting the // logical direction, so we have to only set the logical direction // as the forward for the real line wrapping position. ITextPointer reversePosition = caretPosition.CreatePointer(direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward); // Check line wrapping condition if (!allowStopAtLineEnd && ((TextPointerBase.IsAtLineWrappingPosition(caretPosition, this.TextView) && TextPointerBase.IsAtLineWrappingPosition(reversePosition, this.TextView)) || TextPointerBase.IsNextToPlainLineBreak(caretPosition, LogicalDirection.Backward) || TextSchema.IsBreak(caretPosition.GetElementType(LogicalDirection.Backward)))) { // Caret is at wrapping position, and we are not allowed to stay at end of line, // so we choose forward direction to appear in the begiinning of a second line caretPosition.SetLogicalDirection(LogicalDirection.Forward); } else { if (caretPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text && caretPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text) { // This is statistically most typical case. No "smartness" needed // to choose standard Forward orientation for the caret. // NOTE: By using caretPosition's direction we solve BiDi caret orientation case: // The orietnation reflects a direction from where caret has been moved // or orientation where mouse clicked. So we will stick with appropriate // character. // Nothing to do. The caretPosition is good to go. } else if (!allowStopNearSpace) { // There are some tags around, and we are not allowed to choose a side near to space. // So we need to perform some content analysis. char[] charBuffer = new char[1]; if (caretPosition.GetPointerContext(direction) == TextPointerContext.Text && caretPosition.GetTextInRun(direction, charBuffer, 0, 1) == 1 && Char.IsWhiteSpace(charBuffer[0])) { LogicalDirection oppositeDirection = direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward; // Check formatting switch condition at this position FlowDirection initialFlowDirection = (FlowDirection)caretPosition.GetValue(FrameworkElement.FlowDirectionProperty); bool moved = caretPosition.MoveToInsertionPosition(oppositeDirection); if (moved && initialFlowDirection == (FlowDirection)caretPosition.GetValue(FrameworkElement.FlowDirectionProperty) && (caretPosition.GetPointerContext(oppositeDirection) != TextPointerContext.Text || caretPosition.GetTextInRun(oppositeDirection, charBuffer, 0, 1) != 1 || !Char.IsWhiteSpace(charBuffer[0]))) { // In the opposite direction we have a non-space // character. So we choose that direction direction = oppositeDirection; caretPosition.SetLogicalDirection(direction); } } } } // Now that orientation of a caretPosition is identified, // build an empty selection at this position TextRangeBase.BeginChange(this); try { TextRangeBase.Select(this, caretPosition, caretPosition); // Note how Select method works for the case of empty range: // It creates a single instance TextPointer normalized and oriented // in a direction taken from caretPosition: ITextSelection thisSelection = this; Invariant.Assert(thisSelection.Start.LogicalDirection == caretPosition.LogicalDirection); // orientation must be as passed Invariant.Assert(this.IsEmpty); //Invariant.Assert((object)thisSelection.Start == (object)thisSelection.End); // it must be the same instance of TextPointer //Invariant.Assert(TextPointerBase.IsAtInsertionPosition(thisSelection.Start, caretPosition.LogicalDirection)); // normalization must be done in the same diredction as orientation // Clear active positions when selection is empty SetActivePositions(null, null); } finally { TextRangeBase.EndChange(this); } } //...................................................... // // Extending via movingEnd movements // //...................................................... // Worker for ExtendToPosition, handles all ITextContainers. void ITextSelection.ExtendToPosition(ITextPointer position) { TextRangeBase.BeginChange(this); try { ITextSelection thisSelection = (ITextSelection)this; // Store initial anchor position ITextPointer anchorPosition = thisSelection.AnchorPosition; //Build new selection TextRangeBase.Select(thisSelection, anchorPosition, position); // Store active positions. SetActivePositions(anchorPosition, position); } finally { TextRangeBase.EndChange(this); } } // Worker for ExtendToNextInsertionPosition, handles all ITextContainers. bool ITextSelection.ExtendToNextInsertionPosition(LogicalDirection direction) { bool moved = false; TextRangeBase.BeginChange(this); try { ITextPointer anchorPosition = ((ITextSelection)this).AnchorPosition; ITextPointer movingPosition = ((ITextSelection)this).MovingPosition; ITextPointer newMovingPosition; if (this.IsTableCellRange) { // Both moving and anchor positions are within a single Table, in seperate // cells. Select next cell. newMovingPosition = TextRangeEditTables.GetNextTableCellRangeInsertionPosition(this, direction); } else if (movingPosition is TextPointer && TextPointerBase.IsAtRowEnd(movingPosition)) { // Moving position at at a Table row end, anchor position is outside // the Table. Select next row. newMovingPosition = TextRangeEditTables.GetNextRowEndMovingPosition(this, direction); } else if (movingPosition is TextPointer && TextRangeEditTables.MovingPositionCrossesCellBoundary(this)) { // Moving position at at a Table row start, anchor position is outside // the Table. Select next row. newMovingPosition = TextRangeEditTables.GetNextRowStartMovingPosition(this, direction); } else { // No Table logic. newMovingPosition = GetNextTextSegmentInsertionPosition(direction); } if (newMovingPosition == null && direction == LogicalDirection.Forward) { // When moving forward we cannot find next insertion position, set the end of selection after the last paragraph // (which is not an insertion position) if (movingPosition.CompareTo(movingPosition.TextContainer.End) != 0) { newMovingPosition = movingPosition.TextContainer.End; } } // Now that new movingPosition is prepared, build the new selection if (newMovingPosition != null) { moved = true; // Re-build a range for the new pair of positions TextRangeBase.Select(this, anchorPosition, newMovingPosition); // Make sure that active positions are inside of a selection // Set the moving position direction to point toward the inner content. LogicalDirection contentDirection = (anchorPosition.CompareTo(newMovingPosition) <= 0) ? LogicalDirection.Backward : LogicalDirection.Forward; newMovingPosition = newMovingPosition.GetFrozenPointer(contentDirection); SetActivePositions(anchorPosition, newMovingPosition); } } finally { TextRangeBase.EndChange(this); } return moved; } // Finds new movingPosition for the selection when it is in TextSegment state. // Returns null if there is no next insertion position in the requested direction. private ITextPointer GetNextTextSegmentInsertionPosition(LogicalDirection direction) { ITextSelection thisSelection = (ITextSelection)this; // Move one over symbol in a given direction return thisSelection.MovingPosition.GetNextInsertionPosition(direction); } bool ITextSelection.Contains(Point point) { ITextSelection thisSelection = (ITextSelection)this; if (thisSelection.IsEmpty) { return false; } if (this.TextView == null || !this.TextView.IsValid) { return false; } bool contains = false; ITextPointer position = this.TextView.GetTextPositionFromPoint(point, /*snapToText:*/false); // Did we hit any text? if (position != null && thisSelection.Contains(position)) { // If we did, make sure the range covers at least one full character. // Check both character edges. position = position.GetNextInsertionPosition(position.LogicalDirection); if (position != null && thisSelection.Contains(position)) { contains = true; } } // Point snapped to text did not hit anything, but we still have a chance // to hit selection - in inter-paragraph or end-of-paragraph areas - highlighted by selection if (!contains) { if (_caretElement != null && _caretElement.SelectionGeometry != null && _caretElement.SelectionGeometry.FillContains(point)) { contains = true; } } return contains; } #region ITextSelection Implementation //...................................................... // // Interaction with Selection host // //...................................................... // Called by TextEditor.OnDetach, when the behavior is shut down. void ITextSelection.OnDetach() { ITextSelection thisSelection = (ITextSelection)this; // Check if we need to deactivate the selection thisSelection.UpdateCaretAndHighlight(); // Delete highlight layer created for this selection (if any) if (_highlightLayer != null && thisSelection.Start.TextContainer.Highlights.GetLayer(typeof(TextSelection)) == _highlightLayer) { thisSelection.Start.TextContainer.Highlights.RemoveLayer(_highlightLayer); } _highlightLayer = null; // Detach the selection from its editor _textEditor = null; } // ITextView.Updated event listener. // Called by the TextEditor. void ITextSelection.OnTextViewUpdated() { if ((this.UiScope.IsKeyboardFocused || this.UiScope.IsFocused)) { // Use the locally defined caretElement because _caretElement can be null by // detaching CaretElement object // Stress bug#1583327 indicate that _caretElement can be set to null by // detaching. So the below code is caching the caret element instance locally. CaretElement caretElement = _caretElement; if (caretElement != null) { caretElement.OnTextViewUpdated(); } } } // Perform any cleanup necessary when removing the current UiScope // from the visual tree (eg, during a template change). void ITextSelection.DetachFromVisualTree() { DetachCaretFromVisualTree(); } // Italic command event handler. Called by TextEditor. void ITextSelection.RefreshCaret() { // Update the caret to show it as italic or normal caret. RefreshCaret(_textEditor, _textEditor.Selection); } // This is called from TextStore when the InterimSelection style of the current selection is changed. void ITextSelection.OnInterimSelectionChanged(bool interimSelection) { // When the interim selection is changed, we need to update the highlight. _highlightLayer.InternalOnSelectionChanged(); // Update the caret to show or remove the interim block caret. UpdateCaretState(CaretScrollMethod.None); } //...................................................... // // Selection Heuristics // //...................................................... // Moves the selection to the mouse cursor position. // Extends the active end if extend == true, otherwise the selection // is collapsed to a caret. void ITextSelection.SetSelectionByMouse(ITextPointer cursorPosition, Point cursorMousePoint) { ITextSelection thisSelection = (ITextSelection)this; if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0) { // Shift modifier pressed - extending selection from existing one thisSelection.ExtendSelectionByMouse(cursorPosition, /*forceWordSelection:*/false, /*forceParagraphSelection*/false); } else { // No shift modifier pressed - move selection to a cursor position MoveSelectionByMouse(cursorPosition, cursorMousePoint); } } // Extends the selection to the mouse cursor position. void ITextSelection.ExtendSelectionByMouse(ITextPointer cursorPosition, bool forceWordSelection, bool forceParagraphSelection) { ITextSelection thisSelection = (ITextSelection)this; // Check whether the cursor has been actually moved - compare with the previous position if (forceParagraphSelection || _previousCursorPosition != null && cursorPosition.CompareTo(_previousCursorPosition) == 0) { // Mouse was not actually moved. Ignore the event. return; } thisSelection.BeginChange(); try { if (!BeginMouseSelectionProcess(cursorPosition)) { return; } // Get anchor position ITextPointer anchorPosition = ((ITextSelection)this).AnchorPosition; // Identify words on selection ends TextSegment anchorWordRange; TextSegment cursorWordRange; IdentifyWordsOnSelectionEnds(anchorPosition, cursorPosition, forceWordSelection, out anchorWordRange, out cursorWordRange); // Calculate selection boundary positions ITextPointer startPosition; ITextPointer movingPosition; if (anchorWordRange.Start.CompareTo(cursorWordRange.Start) <= 0) { startPosition = anchorWordRange.Start.GetFrozenPointer(LogicalDirection.Forward); movingPosition = cursorWordRange.End.GetFrozenPointer(LogicalDirection.Backward); ; } else { startPosition = anchorWordRange.End.GetFrozenPointer(LogicalDirection.Backward); movingPosition = cursorWordRange.Start.GetFrozenPointer(LogicalDirection.Forward); ; } // Note that we use includeCellAtMovingPosition=true because we want that hit-tested table cell // be included into selection no matter whether it's empty or not. TextRangeBase.Select(this, startPosition, movingPosition, /*includeCellAtMovingPosition:*/true); SetActivePositions(anchorPosition, movingPosition); // Store previous cursor position - for the next extension event _previousCursorPosition = cursorPosition.CreatePointer(); Invariant.Assert(thisSelection.Contains(thisSelection.AnchorPosition)); } finally { thisSelection.EndChange(); } } // Part of ExtendSelectionByMouse method: // Checks whether selection has been started and initiates selection process // Usually always returns true, // returns false only as a special value indicating that we need to return without executing selection expansion code. private bool BeginMouseSelectionProcess(ITextPointer cursorPosition) { if (_previousCursorPosition == null) { // This is a beginning of mouse selection guesture. // Initialize the guesture state // initially autoword expansion of both ends is enabled _anchorWordRangeHasBeenCrossedOnce = false; _allowWordExpansionOnAnchorEnd = true; _reenterPosition = null; if (this.GetUIElementSelected() != null) { // This means that we have just received mousedown event and selected embedded element in this event. // MoveMove event is sent immediately, but we don't want to loose UIElement selection, // so we do not extend to the cursorPosition now. _previousCursorPosition = cursorPosition.CreatePointer(); return false; } } return true; } // Part of ExtendSelectionByMouse method: // Identifies words on selection ends. private void IdentifyWordsOnSelectionEnds(ITextPointer anchorPosition, ITextPointer cursorPosition, bool forceWordSelection, out TextSegment anchorWordRange, out TextSegment cursorWordRange) { if (forceWordSelection) { anchorWordRange = TextPointerBase.GetWordRange(anchorPosition); cursorWordRange = TextPointerBase.GetWordRange(cursorPosition, cursorPosition.LogicalDirection); } else { // Define whether word adjustment is allowed. Pressing Shift+Control prevents from auto-word expansion. bool disableWordExpansion = _textEditor.AutoWordSelection == false || ((Keyboard.Modifiers & ModifierKeys.Shift) != 0 && (Keyboard.Modifiers & ModifierKeys.Control) != 0); if (disableWordExpansion) { anchorWordRange = new TextSegment(anchorPosition, anchorPosition); cursorWordRange = new TextSegment(cursorPosition, cursorPosition); } else { // Autoword expansion heuristics // ----------------------------- // Word autoword heuristics: // a) After active end returned to selected area, autoword expansion on active end is disabled // b) After active end returned to the very first word, expansion on anchor word is disabled either // We do this though only if selection has crossed initial word boundary at least once. // c) After active end crosses new word, autoword expansion of active end is enabled again // Calculate a word range for anchor position anchorWordRange = TextPointerBase.GetWordRange(anchorPosition); // Check if we re-entering selection or moving outside // and set autoexpansion flags accordingly if (_previousCursorPosition != null && (anchorPosition.CompareTo(cursorPosition) < 0 && cursorPosition.CompareTo(_previousCursorPosition) < 0 || _previousCursorPosition.CompareTo(cursorPosition) < 0 && cursorPosition.CompareTo(anchorPosition) < 0)) { // Re-entering selection. // Store position of reentering _reenterPosition = cursorPosition.CreatePointer(); // When re-entering reaches initial word, disable word expansion on anchor end either if (_anchorWordRangeHasBeenCrossedOnce && anchorWordRange.Contains(cursorPosition)) { _allowWordExpansionOnAnchorEnd = false; } } else { // Extending the selection. // Check if we are crossing a boundary of last reentered word to re-enable word expansion on moving end if (_reenterPosition != null) { TextSegment lastReenteredWordRange = TextPointerBase.GetWordRange(_reenterPosition); if (!lastReenteredWordRange.Contains(cursorPosition)) { _reenterPosition = null; } } } // Identify expanded range on both ends // if (anchorWordRange.Contains(cursorPosition) || anchorWordRange.Contains(cursorPosition.GetInsertionPosition(LogicalDirection.Forward)) || anchorWordRange.Contains(cursorPosition.GetInsertionPosition(LogicalDirection.Backward))) { // Selection does not cross word boundary, so shrink selection to exact anchor/cursor positions anchorWordRange = new TextSegment(anchorPosition, anchorPosition); cursorWordRange = new TextSegment(cursorPosition, cursorPosition); } else { // Selection crosses word boundary. _anchorWordRangeHasBeenCrossedOnce = true; if (!_allowWordExpansionOnAnchorEnd || // TextPointerBase.IsAtWordBoundary(anchorPosition, /*insideWordDirection:*/LogicalDirection.Forward)) { // We collapse anchorPosition in two cases: // If we have been re-entering the initial word before - // then we treat it as an indicator that user wants exact position on anchor end // or // if selection starts exactly on word boundary - // then we should not include the following word (when selection extends backward). // // So in the both cases we collapse anchorWordRange to exact _anchorPosition anchorWordRange = new TextSegment(anchorPosition, anchorPosition); } if (TextPointerBase.IsAfterLastParagraph(cursorPosition) || TextPointerBase.IsAtWordBoundary(cursorPosition, /*insideWordDirection:*/LogicalDirection.Forward)) { cursorWordRange = new TextSegment(cursorPosition, cursorPosition); } else { if (_reenterPosition == null) { // We are not in re-entering mode; expand moving end to word boundary cursorWordRange = TextPointerBase.GetWordRange(cursorPosition, cursorPosition.LogicalDirection); } else { // We are in re-entering mode; use exact moving end position cursorWordRange = new TextSegment(cursorPosition, cursorPosition); } } } } } } //...................................................... // // Table Selection // //...................................................... ////// Extends table selection by one row in a given direction /// /// /// LogicalDirection.Forward means moving active cell one row down, /// LogicalDirection.Backward - one row up. /// bool ITextSelection.ExtendToNextTableRow(LogicalDirection direction) { TableCell anchorCell; TableCell movingCell; TableRowGroup rowGroup; int nextRowIndex; TableCell nextCell; if (!this.IsTableCellRange) { return false; } Invariant.Assert(!this.IsEmpty); Invariant.Assert(_anchorPosition != null); Invariant.Assert(_movingPositionEdge != MovingEdge.None); if (!TextRangeEditTables.IsTableCellRange((TextPointer)_anchorPosition, (TextPointer)((ITextSelection)this).MovingPosition, /*includeCellAtMovingPosition:*/false, out anchorCell, out movingCell)) { return false; } Invariant.Assert(anchorCell != null && movingCell != null); rowGroup = movingCell.Row.RowGroup; nextCell = null; if (direction == LogicalDirection.Forward) { // Move movingPosition to the following row. // Find a row in the forward direction nextRowIndex = movingCell.Row.Index + movingCell.RowSpan; if (nextRowIndex < rowGroup.Rows.Count) { nextCell = FindCellAtColumnIndex(rowGroup.Rows[nextRowIndex].Cells, movingCell.ColumnIndex); } } else { // Find preceding row containing a cell in position of movingCell's first column nextRowIndex = movingCell.Row.Index - 1; while (nextRowIndex >= 0) { nextCell = FindCellAtColumnIndex(rowGroup.Rows[nextRowIndex].Cells, movingCell.ColumnIndex); if (nextCell != null) { break; } nextRowIndex--; } } if (nextCell != null) { // ITextPointer movingPosition = nextCell.ContentEnd.CreatePointer(); movingPosition.MoveToNextInsertionPosition(LogicalDirection.Forward); TextRangeBase.Select(this, _anchorPosition, movingPosition); // Make sure that active positions are inside of a selection SetActivePositions(_anchorPosition, movingPosition); return true; } return false; } //------------------------------------------------------ // // ITextSelection Properties // //----------------------------------------------------- // True if the current seleciton is for interim character. // Korean Interim character is now invisilbe selection (no highlight) and the controls needs to // have the block caret to indicate the interim character. // This should be updated by TextStore. internal bool IsInterimSelection { get { if (this.TextStore != null) { return TextStore.IsInterimSelection; } return false; } } // IsInterimSelection wrapper for ITextSelection. bool ITextSelection.IsInterimSelection { get { return this.IsInterimSelection; } } #endregion ITextSelection Implementation // ***************************************************** // ***************************************************** // ****************************************************** // // Concrete TextSelection Implementation // // ***************************************************** // ****************************************************** // ****************************************************** //----------------------------------------------------- // // Public Methods // //------------------------------------------------------ //...................................................... // // Selection extension (moving active end) // //...................................................... ////// Potential public method - concrete equivalent of abstract method /// internal TextPointer AnchorPosition { get { // ATTENTION: This method is supposed to be a pure redirection // to corresponding ITextSelection method - to keep abstract and concrete selection behavior consistent return (TextPointer)((ITextSelection)this).AnchorPosition; } } ////// Potential public method - concrete equivalent of abstract method /// internal TextPointer MovingPosition { get { // ATTENTION: This method is supposed to be a pure redirection // to corresponding ITextSelection method - to keep abstract and concrete selection behavior consistent return (TextPointer)((ITextSelection)this).MovingPosition; } } ////// Potential public method - concrete equivalent of abstract method /// internal void SetCaretToPosition(TextPointer caretPosition, LogicalDirection direction, bool allowStopAtLineEnd, bool allowStopNearSpace) { // ATTENTION: This method is supposed to be a pure redirection // to corresponding ITextSelection method - to keep abstract and concrete selection behavior consistent ((ITextSelection)this).SetCaretToPosition(caretPosition, direction, allowStopAtLineEnd, allowStopNearSpace); } // internal bool ExtendToNextInsertionPosition(LogicalDirection direction) { // ATTENTION: This method is supposed to be a pure redirection // to corresponding ITextSelection method - to keep abstract and concrete selection behavior consistent return ((ITextSelection)this).ExtendToNextInsertionPosition(direction); } #endregion Public Methods #region Internal Methods //----------------------------------------------------- // // Internal Methods // //----------------------------------------------------- // InputLanguageChanged event handler. Called by TextEditor. internal static void OnInputLanguageChanged(CultureInfo cultureInfo) { TextEditorThreadLocalStore threadLocalStore = TextEditor._ThreadLocalStore; // Check the changed input language of the cultureInfo to draw the BiDi caret in case of BiDi language // like as Arabic or Hebrew input language. if (IsBidiInputLanguage(cultureInfo)) { threadLocalStore.Bidi = true; } else { threadLocalStore.Bidi = false; } // Update caret on focused text editor if (threadLocalStore.FocusedTextSelection != null) { ((ITextSelection)threadLocalStore.FocusedTextSelection).RefreshCaret(); } } // Returns true if the text matching a pixel position falls within // the selection. internal bool Contains(Point point) { return ((ITextSelection)this).Contains(point); } //----------------------------------------------------- // // Internal Virtual Methods - TextSelection Extensibility // //------------------------------------------------------ //...................................................... // // Formatting // //...................................................... ////// Append an object at the end of the TextRange. /// internal override void InsertEmbeddedUIElementVirtual(FrameworkElement embeddedElement) { TextRangeBase.BeginChange(this); try { base.InsertEmbeddedUIElementVirtual(embeddedElement); this.ClearSpringloadFormatting(); } finally { TextRangeBase.EndChange(this); } } ////// Applies a property value to a selection. /// In case of empty selection sets property to springloaded property set. /// internal override void ApplyPropertyToTextVirtual(DependencyProperty formattingProperty, object value, bool applyToParagraphs, PropertyValueAction propertyValueAction) { if (!TextSchema.IsParagraphProperty(formattingProperty) && !TextSchema.IsCharacterProperty(formattingProperty)) { return; // Ignore any unknown property } // Check whether we are in a situation when auto-word formatting must happen if (this.IsEmpty && TextSchema.IsCharacterProperty(formattingProperty) && !applyToParagraphs && formattingProperty != FrameworkElement.FlowDirectionProperty) // We dont want to apply flowdirection property to inlines when selection is empty. { TextSegment autoWordRange = TextRangeBase.GetAutoWord(this); if (autoWordRange.IsNull) { // This property goes to springload formatting. We should not create undo unit for it. if (_springloadFormatting == null) { _springloadFormatting = new DependencyObject(); } _springloadFormatting.SetValue(formattingProperty, value); } else { // TextRange will create undo unit with proper name new TextRange(autoWordRange.Start, autoWordRange.End).ApplyPropertyValue(formattingProperty, value); } } else { // No word to auto-format. Apply property to a selection. // TextRange will create undo unit with proper name base.ApplyPropertyToTextVirtual(formattingProperty, value, applyToParagraphs, propertyValueAction); this.ClearSpringloadFormatting(); } } internal override void ClearAllPropertiesVirtual() { // Character property - applies to text runs in selected range, or springloaded if selection is empty if (this.IsEmpty) { this.ClearSpringloadFormatting(); } else { TextRangeBase.BeginChange(this); try { base.ClearAllPropertiesVirtual(); this.ClearSpringloadFormatting(); } finally { TextRangeBase.EndChange(this); } } } //...................................................... // // Range Serialization // //...................................................... // Worker for Xml property setter; enables extensibility for TextSelection internal override void SetXmlVirtual(TextElement fragment) { TextRangeBase.BeginChange(this); try { base.SetXmlVirtual(fragment); this.ClearSpringloadFormatting(); } finally { TextRangeBase.EndChange(this); } } // Worker for Load public method; enables extensibility for TextSelection internal override void LoadVirtual(Stream stream, string dataFormat) { TextRangeBase.BeginChange(this); try { base.LoadVirtual(stream, dataFormat); this.ClearSpringloadFormatting(); } finally { TextRangeBase.EndChange(this); } } //...................................................... // // Table Editing // //...................................................... // Worker for InsertTable; enables extensibility for TextSelection internal override Table InsertTableVirtual(int rowCount, int columnCount) { using (DeclareChangeBlock()) { Table table = base.InsertTableVirtual(rowCount, columnCount); if (table != null) { TextPointer cellStart = table.RowGroups[0].Rows[0].Cells[0].ContentStart; this.SetCaretToPosition(cellStart, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } return table; } } // .............................................................. // // Springload Formatting // // .............................................................. #region Springload Formatting ////// Function used in OnApplyProperty commands for character formatting properties. /// It takes care of springload formatting (applied to empty selection). /// /// /// Property whose value is subject to be toggled. /// ////// The value of a property /// internal object GetCurrentValue(DependencyProperty formattingProperty) { ITextSelection thisSelection = this; object propertyValue = DependencyProperty.UnsetValue; if (thisSelection.Start is TextPointer) { if (_springloadFormatting != null && this.IsEmpty) { // Get springload value propertyValue = _springloadFormatting.ReadLocalValue(formattingProperty); if (propertyValue == DependencyProperty.UnsetValue) { propertyValue = this.Start.Parent.GetValue(formattingProperty); } } } // If there's no spring loaded value, read the local value. if (propertyValue == DependencyProperty.UnsetValue) { propertyValue = this.PropertyPosition.GetValue(formattingProperty); } return propertyValue; } ////// Reads a set of current formatting properties (from selection start) /// into _springloadFormatting - to apply potentially for the following input. /// internal void SpringloadCurrentFormatting() { if (((ITextSelection)this).Start is TextPointer) { TextPointer start = this.Start; Inline ancestor = start.GetNonMergeableInlineAncestor(); if (ancestor != null) { // Unless the selection is wholly contained within a Hyperlink, we don't // want to springload its character properties. if (this.End.GetNonMergeableInlineAncestor() != ancestor) { start = ancestor.ElementEnd; } } if (_springloadFormatting == null) { SpringloadCurrentFormatting(start.Parent); } } } private void SpringloadCurrentFormatting(DependencyObject parent) { // Create new bag for formatting properties _springloadFormatting = new DependencyObject(); // Check if we have an object to read from if (parent == null) { return; } DependencyProperty[] inheritableProperties = TextSchema.GetInheritableProperties(typeof(Inline)); DependencyProperty[] noninheritableProperties = TextSchema.GetNoninheritableProperties(typeof(Span)); // Walk up the tree. At each step, if the element is typographical only, // grab all non-inherited values. When we reach the top of the tree, grab all // values. DependencyObject element = parent; while (element is Inline) { TextElementEditingBehaviorAttribute att = (TextElementEditingBehaviorAttribute)Attribute.GetCustomAttribute(element.GetType(), typeof(TextElementEditingBehaviorAttribute)); if (att.IsTypographicOnly) { for (int i = 0; i < inheritableProperties.Length; i++) { if (_springloadFormatting.ReadLocalValue(inheritableProperties[i]) == DependencyProperty.UnsetValue && inheritableProperties[i] != FrameworkElement.LanguageProperty && inheritableProperties[i] != FrameworkElement.FlowDirectionProperty && System.Windows.DependencyPropertyHelper.GetValueSource(element, inheritableProperties[i]).BaseValueSource != BaseValueSource.Inherited) { object value = parent.GetValue(inheritableProperties[i]); _springloadFormatting.SetValue(inheritableProperties[i], value); } } for (int i = 0; i < noninheritableProperties.Length; i++) { if (_springloadFormatting.ReadLocalValue(noninheritableProperties[i]) == DependencyProperty.UnsetValue && noninheritableProperties[i] != TextElement.TextEffectsProperty && System.Windows.DependencyPropertyHelper.GetValueSource(element, noninheritableProperties[i]).BaseValueSource != BaseValueSource.Inherited) { object value = parent.GetValue(noninheritableProperties[i]); _springloadFormatting.SetValue(noninheritableProperties[i], value); } } } element = ((TextElement)element).Parent; } } ////// Clears springload formatting, so that the following text input /// will use formatting from a current position. /// internal void ClearSpringloadFormatting() { if (((ITextSelection)this).Start is TextPointer) { // Delete all springloaded values _springloadFormatting = null; // Update caret italic state ((ITextSelection)this).RefreshCaret(); } } ////// Applies springload formatting to a given content range. /// Clears springloadFormatting after applying it. /// internal void ApplySpringloadFormatting() { if (!(((ITextSelection)this).Start is TextPointer)) { return; } if (this.IsEmpty) { // We can't apply formatting to non-TextContainers or empty selection. return; } if (_springloadFormatting != null) { Invariant.Assert(this.Start.LogicalDirection == LogicalDirection.Backward); Invariant.Assert(this.End.LogicalDirection == LogicalDirection.Forward); LocalValueEnumerator springloadFormattingValues = _springloadFormatting.GetLocalValueEnumerator(); while (!this.IsEmpty && springloadFormattingValues.MoveNext()) { // Note: we repeatedly check for IsEmpty because the selection // may become empty as a result of normalization after formatting // (thai character sequence). LocalValueEntry propertyEntry = springloadFormattingValues.Current; Invariant.Assert(TextSchema.IsCharacterProperty(propertyEntry.Property)); base.ApplyPropertyValue(propertyEntry.Property, propertyEntry.Value); } ClearSpringloadFormatting(); } } #endregion Springload Formatting #region Caret Support // .............................................................. // // Caret Support // // .............................................................. // Shows/hides the caret and scrolls it into view if requested. // Called when the range is moved or layout is updated. internal void UpdateCaretState(CaretScrollMethod caretScrollMethod) { Invariant.Assert(caretScrollMethod != CaretScrollMethod.Unset); if (_pendingCaretNavigation) { caretScrollMethod = CaretScrollMethod.Navigation; _pendingCaretNavigation = false; } if (_caretScrollMethod == CaretScrollMethod.Unset) { _caretScrollMethod = caretScrollMethod; // Post a "Loaded" priority operation to the dispatcher queue. // Operations at Loaded priority are processed when layout and render is // done but just before items at input priority are serviced. // We want the update caret worker to run after layout is clean. if (_textEditor.TextView != null && _textEditor.TextView.IsValid) { UpdateCaretStateWorker(null); } else { Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Loaded, new DispatcherOperationCallback(UpdateCaretStateWorker), null); } } else if (caretScrollMethod != CaretScrollMethod.None) { _caretScrollMethod = caretScrollMethod; } } // Get the caret brush that is the inverted color from the system window or background color. internal static Brush GetCaretBrush(TextEditor textEditor, byte opacity) { Color backgroundColor; ITextSelection focusedTextSelection; object backgroundPropertyValue; // Get the default background from the system color or UiScope's background backgroundPropertyValue = textEditor.UiScope.GetValue(System.Windows.Controls.Panel.BackgroundProperty); if (backgroundPropertyValue != null && backgroundPropertyValue != DependencyProperty.UnsetValue && backgroundPropertyValue is SolidColorBrush) { backgroundColor = ((SolidColorBrush)backgroundPropertyValue).Color; } else { backgroundColor = SystemColors.WindowColor; } // Get the background color from current selection focusedTextSelection = textEditor.Selection; if (focusedTextSelection is TextSelection) { backgroundPropertyValue = ((TextSelection)focusedTextSelection).GetCurrentValue(TextElement.BackgroundProperty); if (backgroundPropertyValue != null && backgroundPropertyValue != DependencyProperty.UnsetValue) { if (backgroundPropertyValue is SolidColorBrush) { backgroundColor = ((SolidColorBrush)backgroundPropertyValue).Color; } } } // Invert the color to get the caret color from the system window or background color. byte r = (byte)~(backgroundColor.R); byte g = (byte)~(backgroundColor.G); byte b = (byte)~(backgroundColor.B); return new SolidColorBrush(Color.FromArgb(opacity, r, g, b)); } #endregion Caret Support #region Bidi Support //...................................................... // // BIDI Support // //...................................................... ////// Check the installed bidi input language from the current /// input keyboard list. /// ////// Critical - calls unmanaged code to get the current available keyboard list. /// TreatAsSafe - data exposed (user using bidi) is safe to release /// [SecurityCritical, SecurityTreatAsSafe] internal static bool IsBidiInputLanguageInstalled() { bool bidiInputLanguageInstalled; bidiInputLanguageInstalled = false; int keyboardListCount = (int)SafeNativeMethods.GetKeyboardLayoutList(0, null); if (keyboardListCount > 0) { int keyboardListIndex; IntPtr[] keyboardList; keyboardList = new IntPtr[keyboardListCount]; keyboardListCount = SafeNativeMethods.GetKeyboardLayoutList(keyboardListCount, keyboardList); for (keyboardListIndex = 0; (keyboardListIndex < keyboardList.Length) && (keyboardListIndex < keyboardListCount); keyboardListIndex++) { CultureInfo cultureInfo = new CultureInfo((short)keyboardList[keyboardListIndex]); if (IsBidiInputLanguage(cultureInfo)) { bidiInputLanguageInstalled = true; break; } } } return bidiInputLanguageInstalled; } #endregion Bidi Support // Forces a synchronous layout validation, up to the selection moving position. void ITextSelection.ValidateLayout() { ((ITextSelection)this).MovingPosition.ValidateLayout(); } #endregion Internal methods //----------------------------------------------------- // // Internal Properties // //------------------------------------------------------ #region Internal Properties // Caret associated with this TextSelection. internal CaretElement CaretElement { get { return _caretElement; } } // Caret associated with this TextSelection. CaretElement ITextSelection.CaretElement { get { return this.CaretElement; } } // Returns true iff there are no additional insertion positions are either // end of the selection. bool ITextSelection.CoversEntireContent { get { ITextSelection This = this; return (This.Start.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.Text && This.End.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text && This.Start.GetNextInsertionPosition(LogicalDirection.Backward) == null && This.End.GetNextInsertionPosition(LogicalDirection.Forward) == null); } } #endregion Internal Properties //------------------------------------------------------ // // Private Methods // //----------------------------------------------------- #region Private Methods // Stores this TextSelection into our TLS slot. private void SetThreadSelection() { TextEditorThreadLocalStore threadLocalStore = TextEditor._ThreadLocalStore; // Store this selection as focused one threadLocalStore.FocusedTextSelection = this; } // Removes this TextSelection from our TLS slot. private void ClearThreadSelection() { // Clear currently focused selection, if it's us if (TextEditor._ThreadLocalStore.FocusedTextSelection == this) { TextEditor._ThreadLocalStore.FocusedTextSelection = null; } } // GotKeyboardFocus event handler. Called by UpdateCaretAndHighlight. private void Highlight() { // Make sure that a highlight layer exists for drawing this selection if (_highlightLayer == null) { _highlightLayer = new TextSelectionHighlightLayer(this); } // Make selection visible if (((ITextSelection)this).Start.TextContainer.Highlights.GetLayer(typeof(TextSelection)) == null) { ((ITextSelection)this).Start.TextContainer.Highlights.AddLayer(_highlightLayer); } } // LostKeyboardFocus event handler. Called by UpdateCaretAndHighlight. private void Unhighlight() { TextSelectionHighlightLayer highlightLayer = ((ITextSelection)this).Start.TextContainer.Highlights.GetLayer(typeof(TextSelection)) as TextSelectionHighlightLayer; if (highlightLayer != null) { ((ITextSelection)this).Start.TextContainer.Highlights.RemoveLayer(highlightLayer); Invariant.Assert(((ITextSelection)this).Start.TextContainer.Highlights.GetLayer(typeof(TextSelection)) == null); } } //...................................................... // // Active Positions of Selection // //...................................................... ////// Stores normalized anchor and moving positions for the selection. /// Ensures that they are both inside of range Start/End. /// /// /// A position which must be stored as initial position for the selection. /// /// /// The "hot" or active selection edge which responds to user input. /// private void SetActivePositions(ITextPointer anchorPosition, ITextPointer movingPosition) { // The following settings are used in auto-word exapnsion. // By setting a _previousPosition we are clearing them all - // they will be re-initialized in the beginning of a selection // expansion guesture in ExtendSelectionByMouse method // Previous position is needed for selection gestures to remember // where mouse drag happed last time. Used for word autoexpansion. _previousCursorPosition = null; if (this.IsEmpty) { _anchorPosition = null; _movingPositionEdge = MovingEdge.None; return; } Invariant.Assert(anchorPosition != null); ITextSelection thisSelection = (ITextSelection)this; // Normalize and store new selection anchor position _anchorPosition = anchorPosition.GetInsertionPosition(anchorPosition.LogicalDirection); // Ensure that anchor position is within one of text segments if (_anchorPosition.CompareTo(thisSelection.Start) < 0) { _anchorPosition = thisSelection.Start.GetFrozenPointer(_anchorPosition.LogicalDirection); } else if (_anchorPosition.CompareTo(thisSelection.End) > 0) { _anchorPosition = thisSelection.End.GetFrozenPointer(_anchorPosition.LogicalDirection); } _movingPositionEdge = ConvertToMovingEdge(anchorPosition, movingPosition); _movingPositionDirection = movingPosition.LogicalDirection; } // Uses the current selection state to match an ITextPointer to one of the possible // moving position edges. private MovingEdge ConvertToMovingEdge(ITextPointer anchorPosition, ITextPointer movingPosition) { ITextSelection thisSelection = this; MovingEdge movingEdge; if (thisSelection.IsEmpty) { // Empty selections have no moving edge. movingEdge = MovingEdge.None; } else if (thisSelection.TextSegments.Count < 2) { // Simple text selections move opposite their anchor positions. movingEdge = (anchorPosition.CompareTo(movingPosition) <= 0) ? MovingEdge.End : MovingEdge.Start; } else { // Table selection. Look for an exact match. if (movingPosition.CompareTo(thisSelection.Start) == 0) { movingEdge = MovingEdge.Start; } else if (movingPosition.CompareTo(thisSelection.End) == 0) { movingEdge = MovingEdge.End; } else if (movingPosition.CompareTo(thisSelection.TextSegments[0].End) == 0) { movingEdge = MovingEdge.StartInner; } else if (movingPosition.CompareTo(thisSelection.TextSegments[thisSelection.TextSegments.Count-1].Start) == 0) { movingEdge = MovingEdge.EndInner; } else { movingEdge = (anchorPosition.CompareTo(movingPosition) <= 0) ? MovingEdge.End : MovingEdge.Start; } } return movingEdge; } //...................................................... // // Selection Building With Mouse // //...................................................... // Moves the selection to the mouse cursor position. // If the cursor is facing a UIElement, select the UIElement. // Sets new selection anchor to a given cursorPosition. private void MoveSelectionByMouse(ITextPointer cursorPosition, Point cursorMousePoint) { ITextSelection thisSelection = (ITextSelection)this; if (this.TextView == null) { return; } Invariant.Assert(this.TextView.IsValid); // We just checked RenderScope. We'll use TextView below ITextPointer movingPosition = null; if (cursorPosition.GetPointerContext(cursorPosition.LogicalDirection) == TextPointerContext.EmbeddedElement) { Rect objectEdgeRect = this.TextView.GetRectangleFromTextPosition(cursorPosition); // Check for embedded object. // If the click happend inside of it we need to select it as a whole, when content is not read-only. if (!_textEditor.IsReadOnly && ShouldSelectEmbeddedObject(cursorPosition, cursorMousePoint, objectEdgeRect)) { movingPosition = cursorPosition.GetNextContextPosition(cursorPosition.LogicalDirection); } } // Move selection to this position if (movingPosition == null) { thisSelection.SetCaretToPosition(cursorPosition, cursorPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/false); } else { thisSelection.Select(cursorPosition, movingPosition); } } // Helper for MoveSelectionByMouse private bool ShouldSelectEmbeddedObject(ITextPointer cursorPosition, Point cursorMousePoint, Rect objectEdgeRect) { // Although we now know that cursorPosition is facing an embedded object, // we still need an additional test to determine if the original mouse point // fell within the object or outside it's bouding box (which can happen when // a mouse click is snapped to the nearest content). // If the mouse point is outside the object, we don't want to select it. if (!objectEdgeRect.IsEmpty && cursorMousePoint.Y >= objectEdgeRect.Y && cursorMousePoint.Y < objectEdgeRect.Y + objectEdgeRect.Height) { // Compare X coordinates of mouse down point and object edge rect, // depending on the FlowDirection of the render scope and paragraph content. FlowDirection renderScopeFlowDirection = (FlowDirection)this.TextView.RenderScope.GetValue(Block.FlowDirectionProperty); FlowDirection paragraphFlowDirection = (FlowDirection)cursorPosition.GetValue(Block.FlowDirectionProperty); if (renderScopeFlowDirection == FlowDirection.LeftToRight) { if (paragraphFlowDirection == FlowDirection.LeftToRight && (cursorPosition.LogicalDirection == LogicalDirection.Forward && objectEdgeRect.X < cursorMousePoint.X || cursorPosition.LogicalDirection == LogicalDirection.Backward && cursorMousePoint.X < objectEdgeRect.X)) { return true; } else if (paragraphFlowDirection == FlowDirection.RightToLeft && (cursorPosition.LogicalDirection == LogicalDirection.Forward && objectEdgeRect.X > cursorMousePoint.X || cursorPosition.LogicalDirection == LogicalDirection.Backward && cursorMousePoint.X > objectEdgeRect.X)) { return true; } } else { if (paragraphFlowDirection == FlowDirection.LeftToRight && (cursorPosition.LogicalDirection == LogicalDirection.Forward && objectEdgeRect.X > cursorMousePoint.X || cursorPosition.LogicalDirection == LogicalDirection.Backward && cursorMousePoint.X > objectEdgeRect.X)) { return true; } else if (paragraphFlowDirection == FlowDirection.RightToLeft && (cursorPosition.LogicalDirection == LogicalDirection.Forward && objectEdgeRect.X < cursorMousePoint.X || cursorPosition.LogicalDirection == LogicalDirection.Backward && cursorMousePoint.X < objectEdgeRect.X)) { return true; } } } return false; } //...................................................... // // Caret Support // //...................................................... // Redraws a caret using current setting for italic - taking springload formatting into account. private static void RefreshCaret(TextEditor textEditor, ITextSelection textSelection) { object fontStylePropertyValue; bool italic; if (textSelection == null || textSelection.CaretElement == null) { return; } // NOTE: We are using GetCurrentValue to take springload formatting into account. fontStylePropertyValue = ((TextSelection)textSelection).GetCurrentValue(TextElement.FontStyleProperty); italic = (textEditor.AcceptsRichContent && fontStylePropertyValue != DependencyProperty.UnsetValue && (FontStyle)fontStylePropertyValue == FontStyles.Italic); textSelection.CaretElement.RefreshCaret(italic); } // Called after a caret navigation, to signal that the next caret // scroll-into-view should include hueristics to include following // text. internal void OnCaretNavigation() { _pendingCaretNavigation = true; } // Called after a caret navigation, to signal that the next caret // scroll-into-view should include hueristics to include following // text. void ITextSelection.OnCaretNavigation() { OnCaretNavigation(); } // Callback for UpdateCaretState worker. private object UpdateCaretStateWorker(object o) { // This can happen if selection has been detached by TextEditor.OnDetach. if (_textEditor == null) { return null; } TextEditorThreadLocalStore threadLocalStore = TextEditor._ThreadLocalStore; CaretScrollMethod caretScrollMethod = _caretScrollMethod; _caretScrollMethod = CaretScrollMethod.Unset; // Use the locally defined caretElement because _caretElement can be null by // detaching CaretElement object CaretElement caretElement = _caretElement; if (caretElement == null) { return null; } if (threadLocalStore.FocusedTextSelection == null) { // If we have multiple windows open, a non-blinking caret might be showing // in tne given TextEditor's UiScope. If the selection for that Editor is // not empty, we need to hide the caret. if (!this.IsEmpty) { caretElement.Hide(); } return null; } // When the TextView is not valid, there is nothing to do if (_textEditor.TextView == null || !_textEditor.TextView.IsValid) { // return null; } if (!this.VerifyAdornerLayerExists()) { caretElement.Hide(); } // Identify caret position // Make sure that moving position is inside of selection ITextPointer caretPosition = IdentifyCaretPosition(this); // If caret position is not valid in focusedTextSelection.TextView, we cannot update it. Return if (caretPosition.HasValidLayout) { Rect caretRectangle; bool italic = false; bool caretVisible = (this.IsEmpty && !_textEditor.IsReadOnly) || (_textEditor.IsReadOnly && _textEditor.IsReadOnlyCaretVisible); if (!this.IsInterimSelection) { caretRectangle = CalculateCaretRectangle(this, caretPosition); if (this.IsEmpty) { // Identify italic condition (including springload) - to use for caret shaping object fontStylePropertyValue = GetPropertyValue(TextElement.FontStyleProperty); italic = (_textEditor.AcceptsRichContent && fontStylePropertyValue != DependencyProperty.UnsetValue && (FontStyle)fontStylePropertyValue == FontStyles.Italic); } } else { caretRectangle = CalculateInterimCaretRectangle(this); // Caret always visible on the interim input mode. caretVisible = true; } Brush caretBrush = GetCaretBrush(_textEditor, /*opacity:1.0f*/0xff); // Calculate the scroll origin position to scroll it with the caret position. double scrollToOriginPosition = CalculateScrollToOriginPosition(_textEditor, caretPosition, caretRectangle.X); // Re-render the caret. // Get a bounding rect from the active end of selection. caretElement.Update(caretVisible, caretRectangle, caretBrush, italic, caretScrollMethod, scrollToOriginPosition); } // CaretElement.Update(...) makes a conditional call to BringIntoView() // on the text view's associated element, which makes the view invalid... if (this.TextView.IsValid && !this.TextView.RendersOwnSelection) { // Re-render selection. Need to do this to invalidate and update adorner for this caret element. caretElement.UpdateSelection(); } return null; } // Helper for UpdateCaretState -- identifies caret position from text selection. private static ITextPointer IdentifyCaretPosition(ITextSelection currentTextSelection) { ITextPointer caretPosition = currentTextSelection.MovingPosition; if (!currentTextSelection.IsEmpty) { // Even when we do not draw the blinking caret, we need this position // for scrolling caret position into view. // Special case for nonempty selection extended beyond end of line if ((caretPosition.LogicalDirection == LogicalDirection.Backward && // caretPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) || // TextPointerBase.IsAfterLastParagraph(caretPosition)) { // This means that selection has been expanded by ExtendToLineEnd/ExtendToDocumentEnd command. // TextView in this case cannot give the rect from this position; // we need to move backward to the end of content caretPosition = caretPosition.CreatePointer(); caretPosition.MoveToNextInsertionPosition(LogicalDirection.Backward); caretPosition.SetLogicalDirection(LogicalDirection.Forward); } } // TextView.GetRectangleFromTextPosition returns the end of character rect in case of Backward // logical direction at the start caret position of the docuemtn or paragraph, so we reset the // logical direction with Forward to get the right rect of caret at the start position of // document or paragraph. if (caretPosition.LogicalDirection == LogicalDirection.Backward && // caretPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart && // (caretPosition.GetNextInsertionPosition(LogicalDirection.Backward) == null || // TextPointerBase.IsNextToAnyBreak(caretPosition, LogicalDirection.Backward))) { caretPosition = caretPosition.CreatePointer(); caretPosition.SetLogicalDirection(LogicalDirection.Forward); } return caretPosition; } // Helper for UpdateCaretState -- calculates caret rectangle from text selection. // regular (non-interim) case of caret positioning private static Rect CalculateCaretRectangle(ITextSelection currentTextSelection, ITextPointer caretPosition) { Transform caretTransform; Rect caretRectangle = currentTextSelection.TextView.GetRawRectangleFromTextPosition(caretPosition, out caretTransform); if (caretRectangle.IsEmpty) { // Caret is not at an insertion position, it has no geometry // and will not be displayed. return Rect.Empty; } // Convert to local coordiantes. caretRectangle = caretTransform.TransformBounds(caretRectangle); // We will use the system defined caret width later. caretRectangle.Width = 0; if (currentTextSelection.IsEmpty) { // Calculate caret height - from current font size (ignoring a rect returned by TextView, // as it can be a rect of embedded object, which should not affect caret height) double fontSize = (double)currentTextSelection.GetPropertyValue(TextElement.FontSizeProperty); FontFamily fontFamily = (FontFamily)currentTextSelection.GetPropertyValue(TextElement.FontFamilyProperty); double caretHeight = fontFamily.LineSpacing * fontSize; if (caretHeight < caretRectangle.Height) { // Decrease the height of caret to the font height and lower the caret to keep its bottom // staying on the baseline. caretRectangle.Y += caretRectangle.Height - caretHeight; caretRectangle.Height = caretHeight; } if (!caretTransform.IsIdentity) { Point top = new Point(caretRectangle.X, caretRectangle.Y); Point bottom = new Point(caretRectangle.X, caretRectangle.Y + caretRectangle.Height); caretTransform.TryTransform(top, out top); caretTransform.TryTransform(bottom, out bottom); caretRectangle.Y += caretRectangle.Height - Math.Abs(bottom.Y - top.Y); caretRectangle.Height = Math.Abs(bottom.Y - top.Y); } } return caretRectangle; } // Helper for UpdateCaretState -- handles the korean interim caret. private static Rect CalculateInterimCaretRectangle(ITextSelection focusedTextSelection) { // // Get the current flow direction on the selection of interim. // This is for getting the right size of distance on the interim character // whatever the current flow direction is. FlowDirection flowDirection = (FlowDirection)focusedTextSelection.Start.GetValue(FrameworkElement.FlowDirectionProperty); ITextPointer nextCharacterPosition; Rect nextCharacterRectangle; Rect caretRectangle; if (flowDirection != FlowDirection.RightToLeft) { // Flow direction is Left-to-Right // Get the rectangle for both interim selection start position and the next character // position. nextCharacterPosition = focusedTextSelection.Start.CreatePointer(LogicalDirection.Forward); caretRectangle = focusedTextSelection.TextView.GetRectangleFromTextPosition(nextCharacterPosition); // Get the next character position from the start position of the interim selection // on the left to right flow direction. nextCharacterPosition.MoveToNextInsertionPosition(LogicalDirection.Forward); nextCharacterPosition.SetLogicalDirection(LogicalDirection.Backward); nextCharacterRectangle = focusedTextSelection.TextView.GetRectangleFromTextPosition(nextCharacterPosition); } else { // Flow direction is Right-to-Left // Get the rectangle for both interim selection end position and the next character // position. nextCharacterPosition = focusedTextSelection.End.CreatePointer(LogicalDirection.Backward); caretRectangle = focusedTextSelection.TextView.GetRectangleFromTextPosition(nextCharacterPosition); // Get the next character position from the end position of the interim selection // on the right to left flow direction. nextCharacterPosition.MoveToNextInsertionPosition(LogicalDirection.Backward); nextCharacterPosition.SetLogicalDirection(LogicalDirection.Forward); nextCharacterRectangle = focusedTextSelection.TextView.GetRectangleFromTextPosition(nextCharacterPosition); } // The interim next character position should be great than the current interim position. // Otherwise, we show the caret as the normal state that use the system caret width. // In case of BiDi character, the next character position can be greater than the current position. if (!caretRectangle.IsEmpty && !nextCharacterRectangle.IsEmpty && nextCharacterRectangle.Left > caretRectangle.Left) { // Get the interim caret width to show the interim block caret. caretRectangle.Width = nextCharacterRectangle.Left - caretRectangle.Left; } return caretRectangle; } // Helper for UpdateCaretStateWorker -- Calculate the scroll origin position to scroll caret // with the scroll origin position so that we can ensure of displaying caret with the wrapped word. // // There are four cases of different corrdinate by the flow direction on UiScope and Paragraph. // UiScope has two flow direction which is LeftToRightflow directioin and another is RightToLeft. // Paragraph has also two flow direction which is LeftToRightflow directioin and another is RightToLeft. // // The below is the example of how horizontal corrdinate and scroll origin value base on the different // four cases. So we have to calculate the scroll to origin position base on the case. Simply we can // get the scroll to origin value as zero if UiScope and Paragraph's flow direction is the same. // Otherwise, the scroll to origin value is the extent width value that is the max width. // // <> // Case 1. // UiScope FlowDirection: LTR(LeftToRight) // Paragraph FlowDirection: LTR(LefTToRight) // Horizontal origin: "Left" // Scroll horizontal origin: "0" // Wrapping to: "Left" // ABC ...... // XYZ| // // Case 2. // UiScope FlowDirection: LTR(LeftToRight) // Paragraph FlowDirection: RTL(RightToLeft) // Horizontal origin: "Left" // Scroll horizontal origin: "Max:Extent Width" // Wrapping to: "Right" // ......ABC // XYZ| // // Case 3. // UiScope FlowDirection: RTL(RightToLeft) // Paragraph FlowDirection: RTL(RightToLeft) // horizontal origin: "Right" // Scroll horizontal origin: "0" // Wrapping to: "Right" // ......ABC // XYZ| // // Case 4. // UiScope FlowDirection: RTL(RightToLeft) // Paragraph FlowDirection: LTR(LefTToRight) // horizontal origin: "Right" // Scroll horizontal origin: "Max:Extent Width" // Wrapping to: "Left" // ABC ...... // XYZ| private static double CalculateScrollToOriginPosition(TextEditor textEditor, ITextPointer caretPosition, double horizontalCaretPosition) { double scrollToOriginPosition = double.NaN; if (textEditor.UiScope is TextBoxBase) { double viewportWidth = ((TextBoxBase)textEditor.UiScope).ViewportWidth; double extentWidth = ((TextBoxBase)textEditor.UiScope).ExtentWidth; // Calculate the scroll to the origin position position when the horizontal scroll is available if (viewportWidth != 0 && extentWidth != 0 && viewportWidth < extentWidth) { bool needScrollToOriginPosition = false; // Check whether we need to calculate the scroll origin position to scroll it with the caret // position. If the caret position is out of the current visual viewport area, the scroll // to origin positioin will be calculated to scroll into the origin position first that // ensure of displaying the wrapped word. // // Note that horizontalCaretPosition is always relative to the viewport, not the document. if (horizontalCaretPosition < 0 || horizontalCaretPosition >= viewportWidth) { needScrollToOriginPosition = true; } if (needScrollToOriginPosition) { // Set the scroll original position as zero scrollToOriginPosition = 0; // Get the flow direction of uiScope FlowDirection uiScopeflowDirection = (FlowDirection)textEditor.UiScope.GetValue(FrameworkElement.FlowDirectionProperty); // Get the flow direction of the current paragraph and compare it with uiScope's flow direction. Block paragraphOrBlockUIContainer = (caretPosition is TextPointer) ? ((TextPointer)caretPosition).ParagraphOrBlockUIContainer : null; if (paragraphOrBlockUIContainer != null) { FlowDirection pagraphFlowDirection = paragraphOrBlockUIContainer.FlowDirection; // If the flow direction is different between uiScopoe and paragaph, // the original scroll position is the extent width value. if (uiScopeflowDirection != pagraphFlowDirection) { scrollToOriginPosition = extentWidth; } } // Adjust scroll position by current viewport offset scrollToOriginPosition -= ((TextBoxBase)textEditor.UiScope).HorizontalOffset; } } } return scrollToOriginPosition; } private CaretElement EnsureCaret(bool isBlinkEnabled, CaretScrollMethod scrollMethod) { TextEditorThreadLocalStore threadLocalStore = TextEditor._ThreadLocalStore; if (_caretElement == null) { // Create new caret _caretElement = new CaretElement(_textEditor, isBlinkEnabled); // Check the current input language to draw the BiDi caret in case of BiDi language // like as Arabic or Hebrew input language. // if (IsBidiInputLanguage(InputLanguageManager.Current.CurrentInputLanguage)) { TextEditor._ThreadLocalStore.Bidi = true; } else { TextEditor._ThreadLocalStore.Bidi = false; } } else { _caretElement.SetBlinking(isBlinkEnabled); } UpdateCaretState(scrollMethod); return _caretElement; } /// /// Walk up the tree from the RenderScope to the UiScope until we find an /// AdornerDecorator or ScrollContentPresenter. /// ///true if one is found, false otherwise private bool VerifyAdornerLayerExists() { DependencyObject element = TextView.RenderScope; while (element != _textEditor.UiScope && element != null) { if (element is AdornerDecorator || element is System.Windows.Controls.ScrollContentPresenter) { return true; } element = VisualTreeHelper.GetParent(element); } return false; } //...................................................... // // BIDI Support // //...................................................... ////// Check the input language of cultureInfo whether it is the bi-directional language or not. /// /// ////// Return true if the passed cultureInfo is the bi-directional language like as Arabic or Hebrew. /// Otherwise, return false. /// ////// Critical - calls unmanaged code to check the font signature /// TreatAsSafe - data exposed (user using bidi) is safe to release /// [SecurityCritical, SecurityTreatAsSafe] private static bool IsBidiInputLanguage(CultureInfo cultureInfo) { bool bidiInput; string fontSignature; bidiInput = false; fontSignature = new String(new Char[FONTSIGNATURE_SIZE]); // Get the font signature to know the current LCID is BiDi(Arabic, Hebrew etc.) or not. if (UnsafeNativeMethods.GetLocaleInfoW(cultureInfo.LCID, NativeMethods.LOCALE_FONTSIGNATURE, fontSignature, FONTSIGNATURE_SIZE) != 0) { // Compare fontSignature[7] with 0x0800 to detect BiDi language. if ((fontSignature[FONTSIGNATURE_BIDI_INDEX] & FONTSIGNATURE_BIDI) != 0) { bidiInput = true; } } return bidiInput; } //...................................................... // // Table Selection // //...................................................... private static TableCell FindCellAtColumnIndex(TableCellCollection cells, int columnIndex) { for (int cellIndex = 0; cellIndex < cells.Count; cellIndex++) { TableCell cell; int startColumnIndex; int endColumnIndex; cell = cells[cellIndex]; startColumnIndex = cell.ColumnIndex; endColumnIndex = startColumnIndex + cell.ColumnSpan - 1; if (startColumnIndex <= columnIndex && columnIndex <= endColumnIndex) { return cell; } } return null; } ////// Determines if the given element has any ancestor. /// /// ///private static bool IsRootElement(DependencyObject element) { return GetParentElement(element) == null; } /// /// Workaround to approximate whether or not our window is active. /// ///private bool IsFocusWithinRoot() { DependencyObject element = this.UiScope; DependencyObject parent = this.UiScope; while (parent != null) { element = parent; parent = GetParentElement(element); } if (element is UIElement && ((UIElement)element).IsKeyboardFocusWithin) { return true; } return false; } /// /// Helper method to determine an element's parent, using the umpteen methods of /// parenting. /// /// private static DependencyObject GetParentElement(DependencyObject element) { DependencyObject parent; if (element is FrameworkElement || element is FrameworkContentElement) { parent = LogicalTreeHelper.GetParent(element); if (parent == null && element is FrameworkElement) { parent = ((FrameworkElement)element).TemplatedParent; if (parent == null && element is Visual) { parent = VisualTreeHelper.GetParent(element); } } } else if (element is Visual) { parent = VisualTreeHelper.GetParent(element); } else { parent = null; } return parent; } // Removes the caret from the visual tree. private void DetachCaretFromVisualTree() { if (_caretElement != null) { _caretElement.DetachFromView(); _caretElement = null; } } #endregion Private methods //------------------------------------------------------ // // Private Properties // //----------------------------------------------------- #region Private Properties TextEditor ITextSelection.TextEditor { get { return _textEditor; } } ITextView ITextSelection.TextView { get { return _textEditor.TextView; } } private ITextView TextView { get { return ((ITextSelection)this).TextView; } } private TextStore TextStore { get { return _textEditor.TextStore; } } private ImmComposition ImmComposition { get { return _textEditor.ImmComposition; } } // UiScope associated with this TextSelection's TextEditor. private FrameworkElement UiScope { get { return _textEditor.UiScope; } } // Position from which to spring load property values. private ITextPointer PropertyPosition { get { ITextSelection This = this; ITextPointer position = null; if (!This.IsEmpty) { position = TextPointerBase.GetFollowingNonMergeableInlineContentStart(This.Start); } if (position == null) { position = This.Start; } position.Freeze(); return position; } } #endregion Private Properties //----------------------------------------------------- // // Private Types // //----------------------------------------------------- #region Private Types // The four possible positions for the selection moving position. private enum MovingEdge { Start, StartInner, EndInner, End, None }; #endregion Private Types //------------------------------------------------------ // // Private Fields // //----------------------------------------------------- #region Private Fields // Selected Content // ---------------- // TextEditor that owns this selection. private TextEditor _textEditor; // Container for highlights on selected text. private TextSelectionHighlightLayer _highlightLayer; // Springload Formatting // --------------------- // Dependency object representing a set of springloaded formatting properties private DependencyObject _springloadFormatting; // Selection autoexpansion for unit boundaries // ------------------------------------------- // A position where the last selection gesture has been initiated. // Selection gestures are: mouseDown-Move-...-Move-Up, Shift+ArrowDown-...-Down-Up. // Note that Shift+MouseDown-Move-...-Move-Up is a gesture continuation, // it does not change _anchorPosition. // Actual selection always contains this position but may be extended // to a wider range - to encompass whole units such as words, // hyperlinks, sentences, paragraphs, table cells etc. // private ITextPointer _anchorPosition; // A position to which user input is applied. // It may coinside with Start or End, but it may be also in some other // corner of rectangular table range. private MovingEdge _movingPositionEdge; // LogicalDirection for the moving position. // If the selection is empty, this value is ignored and // the direction matches this.Start. Otherwise we respect // the value, which typically points inwards towards the // content but may point outward to include an empty line. private LogicalDirection _movingPositionDirection; // Text position used on previous step of mouse dragging selection // It is used in selection dragging heuristic to identify a situation when // dragging end returned back to selected area which means that // autoword expansion must be temporary stopped - until a new word // boundary is crossed. See also _reenterPosition. private ITextPointer _previousCursorPosition; // Text position where dragged mouse re-entered a selection. This word should not be autoexapnded private ITextPointer _reenterPosition; // Flag indicating that initial word boundary has been crossed at least once doring selection expansion private bool _anchorWordRangeHasBeenCrossedOnce; // Flag allows autoword expansion for anchor end of selection private bool _allowWordExpansionOnAnchorEnd; // Font signature size as 16 private const int FONTSIGNATURE_SIZE = 16; // BIDI font signature index from GetLocaleInfo. private const int FONTSIGNATURE_BIDI_INDEX = 7; // BIDI font signature value private const int FONTSIGNATURE_BIDI = 0x0800; // Signals how the caret should be scrolled into view on the next // caret update. private CaretScrollMethod _caretScrollMethod; // If true, signals that the next caret // scroll-into-view should include hueristics to include following // text. private bool _pendingCaretNavigation; // Caret associated with this selection. private CaretElement _caretElement; #endregion Private Fields } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // // File: TextSelection.cs // // Copyright (C) Microsoft Corporation. All rights reserved. // // Description: Holds and manipulates the text selection state for TextEditor. // //--------------------------------------------------------------------------- namespace System.Windows.Documents { using MS.Internal; using System.Collections.Generic; using System.Globalization; using System.Windows.Controls.Primitives; // TextBoxBase using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using System.Threading; using System.Security; using System.Security.Permissions; using System.IO; using System.Xml; using MS.Win32; ////// The TextSelection class encapsulates selection state for the RichTextBox /// control. It has no public constructor, but is exposed via a public /// property on RichTextBox. /// public sealed class TextSelection : TextRange, ITextSelection { #region Constructors //----------------------------------------------------- // // Constructors // //----------------------------------------------------- // Contstructor. // TextSelection does not have a public constructor. It is only accessible // through TextEditor's Selection property. internal TextSelection(TextEditor textEditor) : base(textEditor.TextContainer.Start, textEditor.TextContainer.Start) { ITextSelection thisSelection = (ITextSelection)this; Invariant.Assert(textEditor.UiScope != null); // Attach the selection to its editor _textEditor = textEditor; // Initialize active pointers of the selection - anchor and moving pointers SetActivePositions(/*AnchorPosition:*/thisSelection.Start, thisSelection.End); // Activate selection in case if this control has keyboard focus already thisSelection.UpdateCaretAndHighlight(); } #endregion Constructors // ****************************************************** // ***************************************************** // ****************************************************** // // Abstract TextSelection Implementation // // ****************************************************** // ***************************************************** // ****************************************************** //----------------------------------------------------- // // ITextRange implementation // //----------------------------------------------------- #region ITextRange Implementation //...................................................... // // Selection Building // //...................................................... ////// void ITextRange.Select(ITextPointer anchorPosition, ITextPointer movingPosition) { TextRangeBase.BeginChange(this); try { TextRangeBase.Select(this, anchorPosition, movingPosition); SetActivePositions(anchorPosition, movingPosition); } finally { TextRangeBase.EndChange(this); } } ////// /// void ITextRange.SelectWord(ITextPointer position) { TextRangeBase.BeginChange(this); try { TextRangeBase.SelectWord(this, position); ITextSelection thisSelection = this; SetActivePositions(/*anchorPosition:*/thisSelection.Start, thisSelection.End); } finally { TextRangeBase.EndChange(this); } } ////// /// void ITextRange.SelectParagraph(ITextPointer position) { TextRangeBase.BeginChange(this); try { TextRangeBase.SelectParagraph(this, position); // ITextSelection thisSelection = this; SetActivePositions(/*anchorPosition:*/position, thisSelection.End); } finally { TextRangeBase.EndChange(this); } } ////// /// void ITextRange.ApplyTypingHeuristics(bool overType) { TextRangeBase.BeginChange(this); try { TextRangeBase.ApplyInitialTypingHeuristics(this); // For non-empty selection start with saving current formatting if (!this.IsEmpty && _textEditor.AcceptsRichContent) { SpringloadCurrentFormatting(); // Note: we springload formatting before overtype expansion } TextRangeBase.ApplyFinalTypingHeuristics(this, overType); } finally { TextRangeBase.EndChange(this); } } ////// /// object ITextRange.GetPropertyValue(DependencyProperty formattingProperty) { object propertyValue; if (this.IsEmpty && TextSchema.IsCharacterProperty(formattingProperty)) { // For empty selection return springloaded formatting (only character formatting properties can be springloaded) propertyValue = ((TextSelection)this).GetCurrentValue(formattingProperty); } else { // Otherwise return base implementation from a TextRange. propertyValue = TextRangeBase.GetPropertyValue(this, formattingProperty); } return propertyValue; } //...................................................... // // Plain Text Modification // //...................................................... //----------------------------------------------------- // // Overrides // //------------------------------------------------------ // Set true if a Changed event is pending. bool ITextRange._IsChanged { get { return _IsChanged; } set { // TextStore needs to know about state changes // from false to true. if (!_IsChanged && value) { if (this.TextStore != null) { this.TextStore.OnSelectionChange(); } if (this.ImmComposition != null) { this.ImmComposition.OnSelectionChange(); } } _IsChanged = value; } } ////// /// void ITextRange.NotifyChanged(bool disableScroll, bool skipEvents) { // Notify text store about selection movement. if (this.TextStore != null) { this.TextStore.OnSelectionChanged(); } // Notify ImmComposition about selection movement. if (this.ImmComposition != null) { this.ImmComposition.OnSelectionChanged(); } if (!skipEvents) { TextRangeBase.NotifyChanged(this, disableScroll); } if (!disableScroll) { // Force a synchronous layout update. If the update was big enough, background layout // kicked in and the caret won't otherwise be updated. Note this will block the thread // while layout runs. // // It's possible an application repositioned the caret // while listening to a change event just raised, but in that case the following // code should be harmless. ITextPointer movingPosition = ((ITextSelection)this).MovingPosition; if (this.TextView != null && this.TextView.IsValid && !this.TextView.Contains(movingPosition)) { movingPosition.ValidateLayout(); } // If layout wasn't valid, then there's a pending caret update // that will proceed correctly now. Otherwise the whole operation // is a nop. } // Fixup the caret. UpdateCaretState(disableScroll ? CaretScrollMethod.None : CaretScrollMethod.Simple); } //----------------------------------------------------- // // ITextRange Properties // //------------------------------------------------------ //...................................................... // // Content - rich and plain // //...................................................... ////// string ITextRange.Text { get { return TextRangeBase.GetText(this); } set { TextRangeBase.BeginChange(this); try { TextRangeBase.SetText(this, value); if (this.IsEmpty) { // We need to ensure appropriate caret visual position. ((ITextSelection)this).SetCaretToPosition(((ITextRange)this).End, LogicalDirection.Forward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } Invariant.Assert(!this.IsTableCellRange); SetActivePositions(((ITextRange)this).Start, ((ITextRange)this).End); } finally { TextRangeBase.EndChange(this); } } } #endregion ITextRange Implementation //------------------------------------------------------ // // ITextSelection implementation // //----------------------------------------------------- #region ITextSelection Implementation void ITextSelection.UpdateCaretAndHighlight() { if (this.UiScope.IsEnabled && this.TextView != null && this.UiScope.IsKeyboardFocused) { // Update the TLS first, so that EnsureCaret is working in // the correct context. SetThreadSelection(); // Make caret visible and blinking EnsureCaret(/*isBlinkEnabled:*/true, CaretScrollMethod.None); // Highlight selection Highlight(); } else if (this.UiScope.IsEnabled && this.TextView != null && this.UiScope.IsFocused && IsRootElement(FocusManager.GetFocusScope(this.UiScope)) && (IsFocusWithinRoot() || // either UiScope root window has keyboard focus _textEditor.IsContextMenuOpen)) // or UiScope has a context menu open { // Update the TLS first, so that EnsureCaret is working in // the correct context. SetThreadSelection(); // just stop the caret from blinking EnsureCaret(/*isBlinkEnabled:*/false, CaretScrollMethod.None); // Highlight selection Highlight(); } else { // Update the TLS first, so that the caret is working in // the correct context. ClearThreadSelection(); // delete the caret DetachCaretFromVisualTree(); // Remove highlight Unhighlight(); } } ////// /// ITextPointer ITextSelection.AnchorPosition { get { Invariant.Assert(this.IsEmpty || _anchorPosition != null); Invariant.Assert(_anchorPosition == null || _anchorPosition.IsFrozen); return this.IsEmpty ? ((ITextSelection)this).Start : _anchorPosition; } } ////// The position within this selection that responds to user input. /// ITextPointer ITextSelection.MovingPosition { get { ITextSelection thisSelection = this; ITextPointer movingPosition; if (this.IsEmpty) { movingPosition = thisSelection.Start; } else { switch (_movingPositionEdge) { case MovingEdge.Start: movingPosition = thisSelection.Start; break; case MovingEdge.StartInner: movingPosition = thisSelection.TextSegments[0].End; break; case MovingEdge.EndInner: movingPosition = thisSelection.TextSegments[thisSelection.TextSegments.Count - 1].Start; break; case MovingEdge.End: movingPosition = thisSelection.End; break; case MovingEdge.None: default: Invariant.Assert(false, "MovingEdge should never be None with non-empty TextSelection!"); movingPosition = null; break; } movingPosition = movingPosition.GetFrozenPointer(_movingPositionDirection); } return movingPosition; } } ////// void ITextSelection.SetCaretToPosition(ITextPointer caretPosition, LogicalDirection direction, bool allowStopAtLineEnd, bool allowStopNearSpace) { // We need a pointer with appropriate direction, // becasue it will be used in textRangeBase.Select method for // pointer normalization. caretPosition = caretPosition.CreatePointer(direction); // Normalize the position in its logical direction - to get to text content over there. caretPosition.MoveToInsertionPosition(direction); // We need a pointer with the reverse direction to confirm // the line wrapping position. So we can ensure Bidi caret navigation. // Bidi can have the different caret position by setting the // logical direction, so we have to only set the logical direction // as the forward for the real line wrapping position. ITextPointer reversePosition = caretPosition.CreatePointer(direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward); // Check line wrapping condition if (!allowStopAtLineEnd && ((TextPointerBase.IsAtLineWrappingPosition(caretPosition, this.TextView) && TextPointerBase.IsAtLineWrappingPosition(reversePosition, this.TextView)) || TextPointerBase.IsNextToPlainLineBreak(caretPosition, LogicalDirection.Backward) || TextSchema.IsBreak(caretPosition.GetElementType(LogicalDirection.Backward)))) { // Caret is at wrapping position, and we are not allowed to stay at end of line, // so we choose forward direction to appear in the begiinning of a second line caretPosition.SetLogicalDirection(LogicalDirection.Forward); } else { if (caretPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text && caretPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text) { // This is statistically most typical case. No "smartness" needed // to choose standard Forward orientation for the caret. // NOTE: By using caretPosition's direction we solve BiDi caret orientation case: // The orietnation reflects a direction from where caret has been moved // or orientation where mouse clicked. So we will stick with appropriate // character. // Nothing to do. The caretPosition is good to go. } else if (!allowStopNearSpace) { // There are some tags around, and we are not allowed to choose a side near to space. // So we need to perform some content analysis. char[] charBuffer = new char[1]; if (caretPosition.GetPointerContext(direction) == TextPointerContext.Text && caretPosition.GetTextInRun(direction, charBuffer, 0, 1) == 1 && Char.IsWhiteSpace(charBuffer[0])) { LogicalDirection oppositeDirection = direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward; // Check formatting switch condition at this position FlowDirection initialFlowDirection = (FlowDirection)caretPosition.GetValue(FrameworkElement.FlowDirectionProperty); bool moved = caretPosition.MoveToInsertionPosition(oppositeDirection); if (moved && initialFlowDirection == (FlowDirection)caretPosition.GetValue(FrameworkElement.FlowDirectionProperty) && (caretPosition.GetPointerContext(oppositeDirection) != TextPointerContext.Text || caretPosition.GetTextInRun(oppositeDirection, charBuffer, 0, 1) != 1 || !Char.IsWhiteSpace(charBuffer[0]))) { // In the opposite direction we have a non-space // character. So we choose that direction direction = oppositeDirection; caretPosition.SetLogicalDirection(direction); } } } } // Now that orientation of a caretPosition is identified, // build an empty selection at this position TextRangeBase.BeginChange(this); try { TextRangeBase.Select(this, caretPosition, caretPosition); // Note how Select method works for the case of empty range: // It creates a single instance TextPointer normalized and oriented // in a direction taken from caretPosition: ITextSelection thisSelection = this; Invariant.Assert(thisSelection.Start.LogicalDirection == caretPosition.LogicalDirection); // orientation must be as passed Invariant.Assert(this.IsEmpty); //Invariant.Assert((object)thisSelection.Start == (object)thisSelection.End); // it must be the same instance of TextPointer //Invariant.Assert(TextPointerBase.IsAtInsertionPosition(thisSelection.Start, caretPosition.LogicalDirection)); // normalization must be done in the same diredction as orientation // Clear active positions when selection is empty SetActivePositions(null, null); } finally { TextRangeBase.EndChange(this); } } //...................................................... // // Extending via movingEnd movements // //...................................................... // Worker for ExtendToPosition, handles all ITextContainers. void ITextSelection.ExtendToPosition(ITextPointer position) { TextRangeBase.BeginChange(this); try { ITextSelection thisSelection = (ITextSelection)this; // Store initial anchor position ITextPointer anchorPosition = thisSelection.AnchorPosition; //Build new selection TextRangeBase.Select(thisSelection, anchorPosition, position); // Store active positions. SetActivePositions(anchorPosition, position); } finally { TextRangeBase.EndChange(this); } } // Worker for ExtendToNextInsertionPosition, handles all ITextContainers. bool ITextSelection.ExtendToNextInsertionPosition(LogicalDirection direction) { bool moved = false; TextRangeBase.BeginChange(this); try { ITextPointer anchorPosition = ((ITextSelection)this).AnchorPosition; ITextPointer movingPosition = ((ITextSelection)this).MovingPosition; ITextPointer newMovingPosition; if (this.IsTableCellRange) { // Both moving and anchor positions are within a single Table, in seperate // cells. Select next cell. newMovingPosition = TextRangeEditTables.GetNextTableCellRangeInsertionPosition(this, direction); } else if (movingPosition is TextPointer && TextPointerBase.IsAtRowEnd(movingPosition)) { // Moving position at at a Table row end, anchor position is outside // the Table. Select next row. newMovingPosition = TextRangeEditTables.GetNextRowEndMovingPosition(this, direction); } else if (movingPosition is TextPointer && TextRangeEditTables.MovingPositionCrossesCellBoundary(this)) { // Moving position at at a Table row start, anchor position is outside // the Table. Select next row. newMovingPosition = TextRangeEditTables.GetNextRowStartMovingPosition(this, direction); } else { // No Table logic. newMovingPosition = GetNextTextSegmentInsertionPosition(direction); } if (newMovingPosition == null && direction == LogicalDirection.Forward) { // When moving forward we cannot find next insertion position, set the end of selection after the last paragraph // (which is not an insertion position) if (movingPosition.CompareTo(movingPosition.TextContainer.End) != 0) { newMovingPosition = movingPosition.TextContainer.End; } } // Now that new movingPosition is prepared, build the new selection if (newMovingPosition != null) { moved = true; // Re-build a range for the new pair of positions TextRangeBase.Select(this, anchorPosition, newMovingPosition); // Make sure that active positions are inside of a selection // Set the moving position direction to point toward the inner content. LogicalDirection contentDirection = (anchorPosition.CompareTo(newMovingPosition) <= 0) ? LogicalDirection.Backward : LogicalDirection.Forward; newMovingPosition = newMovingPosition.GetFrozenPointer(contentDirection); SetActivePositions(anchorPosition, newMovingPosition); } } finally { TextRangeBase.EndChange(this); } return moved; } // Finds new movingPosition for the selection when it is in TextSegment state. // Returns null if there is no next insertion position in the requested direction. private ITextPointer GetNextTextSegmentInsertionPosition(LogicalDirection direction) { ITextSelection thisSelection = (ITextSelection)this; // Move one over symbol in a given direction return thisSelection.MovingPosition.GetNextInsertionPosition(direction); } bool ITextSelection.Contains(Point point) { ITextSelection thisSelection = (ITextSelection)this; if (thisSelection.IsEmpty) { return false; } if (this.TextView == null || !this.TextView.IsValid) { return false; } bool contains = false; ITextPointer position = this.TextView.GetTextPositionFromPoint(point, /*snapToText:*/false); // Did we hit any text? if (position != null && thisSelection.Contains(position)) { // If we did, make sure the range covers at least one full character. // Check both character edges. position = position.GetNextInsertionPosition(position.LogicalDirection); if (position != null && thisSelection.Contains(position)) { contains = true; } } // Point snapped to text did not hit anything, but we still have a chance // to hit selection - in inter-paragraph or end-of-paragraph areas - highlighted by selection if (!contains) { if (_caretElement != null && _caretElement.SelectionGeometry != null && _caretElement.SelectionGeometry.FillContains(point)) { contains = true; } } return contains; } #region ITextSelection Implementation //...................................................... // // Interaction with Selection host // //...................................................... // Called by TextEditor.OnDetach, when the behavior is shut down. void ITextSelection.OnDetach() { ITextSelection thisSelection = (ITextSelection)this; // Check if we need to deactivate the selection thisSelection.UpdateCaretAndHighlight(); // Delete highlight layer created for this selection (if any) if (_highlightLayer != null && thisSelection.Start.TextContainer.Highlights.GetLayer(typeof(TextSelection)) == _highlightLayer) { thisSelection.Start.TextContainer.Highlights.RemoveLayer(_highlightLayer); } _highlightLayer = null; // Detach the selection from its editor _textEditor = null; } // ITextView.Updated event listener. // Called by the TextEditor. void ITextSelection.OnTextViewUpdated() { if ((this.UiScope.IsKeyboardFocused || this.UiScope.IsFocused)) { // Use the locally defined caretElement because _caretElement can be null by // detaching CaretElement object // Stress bug#1583327 indicate that _caretElement can be set to null by // detaching. So the below code is caching the caret element instance locally. CaretElement caretElement = _caretElement; if (caretElement != null) { caretElement.OnTextViewUpdated(); } } } // Perform any cleanup necessary when removing the current UiScope // from the visual tree (eg, during a template change). void ITextSelection.DetachFromVisualTree() { DetachCaretFromVisualTree(); } // Italic command event handler. Called by TextEditor. void ITextSelection.RefreshCaret() { // Update the caret to show it as italic or normal caret. RefreshCaret(_textEditor, _textEditor.Selection); } // This is called from TextStore when the InterimSelection style of the current selection is changed. void ITextSelection.OnInterimSelectionChanged(bool interimSelection) { // When the interim selection is changed, we need to update the highlight. _highlightLayer.InternalOnSelectionChanged(); // Update the caret to show or remove the interim block caret. UpdateCaretState(CaretScrollMethod.None); } //...................................................... // // Selection Heuristics // //...................................................... // Moves the selection to the mouse cursor position. // Extends the active end if extend == true, otherwise the selection // is collapsed to a caret. void ITextSelection.SetSelectionByMouse(ITextPointer cursorPosition, Point cursorMousePoint) { ITextSelection thisSelection = (ITextSelection)this; if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0) { // Shift modifier pressed - extending selection from existing one thisSelection.ExtendSelectionByMouse(cursorPosition, /*forceWordSelection:*/false, /*forceParagraphSelection*/false); } else { // No shift modifier pressed - move selection to a cursor position MoveSelectionByMouse(cursorPosition, cursorMousePoint); } } // Extends the selection to the mouse cursor position. void ITextSelection.ExtendSelectionByMouse(ITextPointer cursorPosition, bool forceWordSelection, bool forceParagraphSelection) { ITextSelection thisSelection = (ITextSelection)this; // Check whether the cursor has been actually moved - compare with the previous position if (forceParagraphSelection || _previousCursorPosition != null && cursorPosition.CompareTo(_previousCursorPosition) == 0) { // Mouse was not actually moved. Ignore the event. return; } thisSelection.BeginChange(); try { if (!BeginMouseSelectionProcess(cursorPosition)) { return; } // Get anchor position ITextPointer anchorPosition = ((ITextSelection)this).AnchorPosition; // Identify words on selection ends TextSegment anchorWordRange; TextSegment cursorWordRange; IdentifyWordsOnSelectionEnds(anchorPosition, cursorPosition, forceWordSelection, out anchorWordRange, out cursorWordRange); // Calculate selection boundary positions ITextPointer startPosition; ITextPointer movingPosition; if (anchorWordRange.Start.CompareTo(cursorWordRange.Start) <= 0) { startPosition = anchorWordRange.Start.GetFrozenPointer(LogicalDirection.Forward); movingPosition = cursorWordRange.End.GetFrozenPointer(LogicalDirection.Backward); ; } else { startPosition = anchorWordRange.End.GetFrozenPointer(LogicalDirection.Backward); movingPosition = cursorWordRange.Start.GetFrozenPointer(LogicalDirection.Forward); ; } // Note that we use includeCellAtMovingPosition=true because we want that hit-tested table cell // be included into selection no matter whether it's empty or not. TextRangeBase.Select(this, startPosition, movingPosition, /*includeCellAtMovingPosition:*/true); SetActivePositions(anchorPosition, movingPosition); // Store previous cursor position - for the next extension event _previousCursorPosition = cursorPosition.CreatePointer(); Invariant.Assert(thisSelection.Contains(thisSelection.AnchorPosition)); } finally { thisSelection.EndChange(); } } // Part of ExtendSelectionByMouse method: // Checks whether selection has been started and initiates selection process // Usually always returns true, // returns false only as a special value indicating that we need to return without executing selection expansion code. private bool BeginMouseSelectionProcess(ITextPointer cursorPosition) { if (_previousCursorPosition == null) { // This is a beginning of mouse selection guesture. // Initialize the guesture state // initially autoword expansion of both ends is enabled _anchorWordRangeHasBeenCrossedOnce = false; _allowWordExpansionOnAnchorEnd = true; _reenterPosition = null; if (this.GetUIElementSelected() != null) { // This means that we have just received mousedown event and selected embedded element in this event. // MoveMove event is sent immediately, but we don't want to loose UIElement selection, // so we do not extend to the cursorPosition now. _previousCursorPosition = cursorPosition.CreatePointer(); return false; } } return true; } // Part of ExtendSelectionByMouse method: // Identifies words on selection ends. private void IdentifyWordsOnSelectionEnds(ITextPointer anchorPosition, ITextPointer cursorPosition, bool forceWordSelection, out TextSegment anchorWordRange, out TextSegment cursorWordRange) { if (forceWordSelection) { anchorWordRange = TextPointerBase.GetWordRange(anchorPosition); cursorWordRange = TextPointerBase.GetWordRange(cursorPosition, cursorPosition.LogicalDirection); } else { // Define whether word adjustment is allowed. Pressing Shift+Control prevents from auto-word expansion. bool disableWordExpansion = _textEditor.AutoWordSelection == false || ((Keyboard.Modifiers & ModifierKeys.Shift) != 0 && (Keyboard.Modifiers & ModifierKeys.Control) != 0); if (disableWordExpansion) { anchorWordRange = new TextSegment(anchorPosition, anchorPosition); cursorWordRange = new TextSegment(cursorPosition, cursorPosition); } else { // Autoword expansion heuristics // ----------------------------- // Word autoword heuristics: // a) After active end returned to selected area, autoword expansion on active end is disabled // b) After active end returned to the very first word, expansion on anchor word is disabled either // We do this though only if selection has crossed initial word boundary at least once. // c) After active end crosses new word, autoword expansion of active end is enabled again // Calculate a word range for anchor position anchorWordRange = TextPointerBase.GetWordRange(anchorPosition); // Check if we re-entering selection or moving outside // and set autoexpansion flags accordingly if (_previousCursorPosition != null && (anchorPosition.CompareTo(cursorPosition) < 0 && cursorPosition.CompareTo(_previousCursorPosition) < 0 || _previousCursorPosition.CompareTo(cursorPosition) < 0 && cursorPosition.CompareTo(anchorPosition) < 0)) { // Re-entering selection. // Store position of reentering _reenterPosition = cursorPosition.CreatePointer(); // When re-entering reaches initial word, disable word expansion on anchor end either if (_anchorWordRangeHasBeenCrossedOnce && anchorWordRange.Contains(cursorPosition)) { _allowWordExpansionOnAnchorEnd = false; } } else { // Extending the selection. // Check if we are crossing a boundary of last reentered word to re-enable word expansion on moving end if (_reenterPosition != null) { TextSegment lastReenteredWordRange = TextPointerBase.GetWordRange(_reenterPosition); if (!lastReenteredWordRange.Contains(cursorPosition)) { _reenterPosition = null; } } } // Identify expanded range on both ends // if (anchorWordRange.Contains(cursorPosition) || anchorWordRange.Contains(cursorPosition.GetInsertionPosition(LogicalDirection.Forward)) || anchorWordRange.Contains(cursorPosition.GetInsertionPosition(LogicalDirection.Backward))) { // Selection does not cross word boundary, so shrink selection to exact anchor/cursor positions anchorWordRange = new TextSegment(anchorPosition, anchorPosition); cursorWordRange = new TextSegment(cursorPosition, cursorPosition); } else { // Selection crosses word boundary. _anchorWordRangeHasBeenCrossedOnce = true; if (!_allowWordExpansionOnAnchorEnd || // TextPointerBase.IsAtWordBoundary(anchorPosition, /*insideWordDirection:*/LogicalDirection.Forward)) { // We collapse anchorPosition in two cases: // If we have been re-entering the initial word before - // then we treat it as an indicator that user wants exact position on anchor end // or // if selection starts exactly on word boundary - // then we should not include the following word (when selection extends backward). // // So in the both cases we collapse anchorWordRange to exact _anchorPosition anchorWordRange = new TextSegment(anchorPosition, anchorPosition); } if (TextPointerBase.IsAfterLastParagraph(cursorPosition) || TextPointerBase.IsAtWordBoundary(cursorPosition, /*insideWordDirection:*/LogicalDirection.Forward)) { cursorWordRange = new TextSegment(cursorPosition, cursorPosition); } else { if (_reenterPosition == null) { // We are not in re-entering mode; expand moving end to word boundary cursorWordRange = TextPointerBase.GetWordRange(cursorPosition, cursorPosition.LogicalDirection); } else { // We are in re-entering mode; use exact moving end position cursorWordRange = new TextSegment(cursorPosition, cursorPosition); } } } } } } //...................................................... // // Table Selection // //...................................................... ////// Extends table selection by one row in a given direction /// /// /// LogicalDirection.Forward means moving active cell one row down, /// LogicalDirection.Backward - one row up. /// bool ITextSelection.ExtendToNextTableRow(LogicalDirection direction) { TableCell anchorCell; TableCell movingCell; TableRowGroup rowGroup; int nextRowIndex; TableCell nextCell; if (!this.IsTableCellRange) { return false; } Invariant.Assert(!this.IsEmpty); Invariant.Assert(_anchorPosition != null); Invariant.Assert(_movingPositionEdge != MovingEdge.None); if (!TextRangeEditTables.IsTableCellRange((TextPointer)_anchorPosition, (TextPointer)((ITextSelection)this).MovingPosition, /*includeCellAtMovingPosition:*/false, out anchorCell, out movingCell)) { return false; } Invariant.Assert(anchorCell != null && movingCell != null); rowGroup = movingCell.Row.RowGroup; nextCell = null; if (direction == LogicalDirection.Forward) { // Move movingPosition to the following row. // Find a row in the forward direction nextRowIndex = movingCell.Row.Index + movingCell.RowSpan; if (nextRowIndex < rowGroup.Rows.Count) { nextCell = FindCellAtColumnIndex(rowGroup.Rows[nextRowIndex].Cells, movingCell.ColumnIndex); } } else { // Find preceding row containing a cell in position of movingCell's first column nextRowIndex = movingCell.Row.Index - 1; while (nextRowIndex >= 0) { nextCell = FindCellAtColumnIndex(rowGroup.Rows[nextRowIndex].Cells, movingCell.ColumnIndex); if (nextCell != null) { break; } nextRowIndex--; } } if (nextCell != null) { // ITextPointer movingPosition = nextCell.ContentEnd.CreatePointer(); movingPosition.MoveToNextInsertionPosition(LogicalDirection.Forward); TextRangeBase.Select(this, _anchorPosition, movingPosition); // Make sure that active positions are inside of a selection SetActivePositions(_anchorPosition, movingPosition); return true; } return false; } //------------------------------------------------------ // // ITextSelection Properties // //----------------------------------------------------- // True if the current seleciton is for interim character. // Korean Interim character is now invisilbe selection (no highlight) and the controls needs to // have the block caret to indicate the interim character. // This should be updated by TextStore. internal bool IsInterimSelection { get { if (this.TextStore != null) { return TextStore.IsInterimSelection; } return false; } } // IsInterimSelection wrapper for ITextSelection. bool ITextSelection.IsInterimSelection { get { return this.IsInterimSelection; } } #endregion ITextSelection Implementation // ***************************************************** // ***************************************************** // ****************************************************** // // Concrete TextSelection Implementation // // ***************************************************** // ****************************************************** // ****************************************************** //----------------------------------------------------- // // Public Methods // //------------------------------------------------------ //...................................................... // // Selection extension (moving active end) // //...................................................... ////// Potential public method - concrete equivalent of abstract method /// internal TextPointer AnchorPosition { get { // ATTENTION: This method is supposed to be a pure redirection // to corresponding ITextSelection method - to keep abstract and concrete selection behavior consistent return (TextPointer)((ITextSelection)this).AnchorPosition; } } ////// Potential public method - concrete equivalent of abstract method /// internal TextPointer MovingPosition { get { // ATTENTION: This method is supposed to be a pure redirection // to corresponding ITextSelection method - to keep abstract and concrete selection behavior consistent return (TextPointer)((ITextSelection)this).MovingPosition; } } ////// Potential public method - concrete equivalent of abstract method /// internal void SetCaretToPosition(TextPointer caretPosition, LogicalDirection direction, bool allowStopAtLineEnd, bool allowStopNearSpace) { // ATTENTION: This method is supposed to be a pure redirection // to corresponding ITextSelection method - to keep abstract and concrete selection behavior consistent ((ITextSelection)this).SetCaretToPosition(caretPosition, direction, allowStopAtLineEnd, allowStopNearSpace); } // internal bool ExtendToNextInsertionPosition(LogicalDirection direction) { // ATTENTION: This method is supposed to be a pure redirection // to corresponding ITextSelection method - to keep abstract and concrete selection behavior consistent return ((ITextSelection)this).ExtendToNextInsertionPosition(direction); } #endregion Public Methods #region Internal Methods //----------------------------------------------------- // // Internal Methods // //----------------------------------------------------- // InputLanguageChanged event handler. Called by TextEditor. internal static void OnInputLanguageChanged(CultureInfo cultureInfo) { TextEditorThreadLocalStore threadLocalStore = TextEditor._ThreadLocalStore; // Check the changed input language of the cultureInfo to draw the BiDi caret in case of BiDi language // like as Arabic or Hebrew input language. if (IsBidiInputLanguage(cultureInfo)) { threadLocalStore.Bidi = true; } else { threadLocalStore.Bidi = false; } // Update caret on focused text editor if (threadLocalStore.FocusedTextSelection != null) { ((ITextSelection)threadLocalStore.FocusedTextSelection).RefreshCaret(); } } // Returns true if the text matching a pixel position falls within // the selection. internal bool Contains(Point point) { return ((ITextSelection)this).Contains(point); } //----------------------------------------------------- // // Internal Virtual Methods - TextSelection Extensibility // //------------------------------------------------------ //...................................................... // // Formatting // //...................................................... ////// Append an object at the end of the TextRange. /// internal override void InsertEmbeddedUIElementVirtual(FrameworkElement embeddedElement) { TextRangeBase.BeginChange(this); try { base.InsertEmbeddedUIElementVirtual(embeddedElement); this.ClearSpringloadFormatting(); } finally { TextRangeBase.EndChange(this); } } ////// Applies a property value to a selection. /// In case of empty selection sets property to springloaded property set. /// internal override void ApplyPropertyToTextVirtual(DependencyProperty formattingProperty, object value, bool applyToParagraphs, PropertyValueAction propertyValueAction) { if (!TextSchema.IsParagraphProperty(formattingProperty) && !TextSchema.IsCharacterProperty(formattingProperty)) { return; // Ignore any unknown property } // Check whether we are in a situation when auto-word formatting must happen if (this.IsEmpty && TextSchema.IsCharacterProperty(formattingProperty) && !applyToParagraphs && formattingProperty != FrameworkElement.FlowDirectionProperty) // We dont want to apply flowdirection property to inlines when selection is empty. { TextSegment autoWordRange = TextRangeBase.GetAutoWord(this); if (autoWordRange.IsNull) { // This property goes to springload formatting. We should not create undo unit for it. if (_springloadFormatting == null) { _springloadFormatting = new DependencyObject(); } _springloadFormatting.SetValue(formattingProperty, value); } else { // TextRange will create undo unit with proper name new TextRange(autoWordRange.Start, autoWordRange.End).ApplyPropertyValue(formattingProperty, value); } } else { // No word to auto-format. Apply property to a selection. // TextRange will create undo unit with proper name base.ApplyPropertyToTextVirtual(formattingProperty, value, applyToParagraphs, propertyValueAction); this.ClearSpringloadFormatting(); } } internal override void ClearAllPropertiesVirtual() { // Character property - applies to text runs in selected range, or springloaded if selection is empty if (this.IsEmpty) { this.ClearSpringloadFormatting(); } else { TextRangeBase.BeginChange(this); try { base.ClearAllPropertiesVirtual(); this.ClearSpringloadFormatting(); } finally { TextRangeBase.EndChange(this); } } } //...................................................... // // Range Serialization // //...................................................... // Worker for Xml property setter; enables extensibility for TextSelection internal override void SetXmlVirtual(TextElement fragment) { TextRangeBase.BeginChange(this); try { base.SetXmlVirtual(fragment); this.ClearSpringloadFormatting(); } finally { TextRangeBase.EndChange(this); } } // Worker for Load public method; enables extensibility for TextSelection internal override void LoadVirtual(Stream stream, string dataFormat) { TextRangeBase.BeginChange(this); try { base.LoadVirtual(stream, dataFormat); this.ClearSpringloadFormatting(); } finally { TextRangeBase.EndChange(this); } } //...................................................... // // Table Editing // //...................................................... // Worker for InsertTable; enables extensibility for TextSelection internal override Table InsertTableVirtual(int rowCount, int columnCount) { using (DeclareChangeBlock()) { Table table = base.InsertTableVirtual(rowCount, columnCount); if (table != null) { TextPointer cellStart = table.RowGroups[0].Rows[0].Cells[0].ContentStart; this.SetCaretToPosition(cellStart, LogicalDirection.Backward, /*allowStopAtLineEnd:*/false, /*allowStopNearSpace:*/false); } return table; } } // .............................................................. // // Springload Formatting // // .............................................................. #region Springload Formatting ////// Function used in OnApplyProperty commands for character formatting properties. /// It takes care of springload formatting (applied to empty selection). /// /// /// Property whose value is subject to be toggled. /// ////// The value of a property /// internal object GetCurrentValue(DependencyProperty formattingProperty) { ITextSelection thisSelection = this; object propertyValue = DependencyProperty.UnsetValue; if (thisSelection.Start is TextPointer) { if (_springloadFormatting != null && this.IsEmpty) { // Get springload value propertyValue = _springloadFormatting.ReadLocalValue(formattingProperty); if (propertyValue == DependencyProperty.UnsetValue) { propertyValue = this.Start.Parent.GetValue(formattingProperty); } } } // If there's no spring loaded value, read the local value. if (propertyValue == DependencyProperty.UnsetValue) { propertyValue = this.PropertyPosition.GetValue(formattingProperty); } return propertyValue; } ////// Reads a set of current formatting properties (from selection start) /// into _springloadFormatting - to apply potentially for the following input. /// internal void SpringloadCurrentFormatting() { if (((ITextSelection)this).Start is TextPointer) { TextPointer start = this.Start; Inline ancestor = start.GetNonMergeableInlineAncestor(); if (ancestor != null) { // Unless the selection is wholly contained within a Hyperlink, we don't // want to springload its character properties. if (this.End.GetNonMergeableInlineAncestor() != ancestor) { start = ancestor.ElementEnd; } } if (_springloadFormatting == null) { SpringloadCurrentFormatting(start.Parent); } } } private void SpringloadCurrentFormatting(DependencyObject parent) { // Create new bag for formatting properties _springloadFormatting = new DependencyObject(); // Check if we have an object to read from if (parent == null) { return; } DependencyProperty[] inheritableProperties = TextSchema.GetInheritableProperties(typeof(Inline)); DependencyProperty[] noninheritableProperties = TextSchema.GetNoninheritableProperties(typeof(Span)); // Walk up the tree. At each step, if the element is typographical only, // grab all non-inherited values. When we reach the top of the tree, grab all // values. DependencyObject element = parent; while (element is Inline) { TextElementEditingBehaviorAttribute att = (TextElementEditingBehaviorAttribute)Attribute.GetCustomAttribute(element.GetType(), typeof(TextElementEditingBehaviorAttribute)); if (att.IsTypographicOnly) { for (int i = 0; i < inheritableProperties.Length; i++) { if (_springloadFormatting.ReadLocalValue(inheritableProperties[i]) == DependencyProperty.UnsetValue && inheritableProperties[i] != FrameworkElement.LanguageProperty && inheritableProperties[i] != FrameworkElement.FlowDirectionProperty && System.Windows.DependencyPropertyHelper.GetValueSource(element, inheritableProperties[i]).BaseValueSource != BaseValueSource.Inherited) { object value = parent.GetValue(inheritableProperties[i]); _springloadFormatting.SetValue(inheritableProperties[i], value); } } for (int i = 0; i < noninheritableProperties.Length; i++) { if (_springloadFormatting.ReadLocalValue(noninheritableProperties[i]) == DependencyProperty.UnsetValue && noninheritableProperties[i] != TextElement.TextEffectsProperty && System.Windows.DependencyPropertyHelper.GetValueSource(element, noninheritableProperties[i]).BaseValueSource != BaseValueSource.Inherited) { object value = parent.GetValue(noninheritableProperties[i]); _springloadFormatting.SetValue(noninheritableProperties[i], value); } } } element = ((TextElement)element).Parent; } } ////// Clears springload formatting, so that the following text input /// will use formatting from a current position. /// internal void ClearSpringloadFormatting() { if (((ITextSelection)this).Start is TextPointer) { // Delete all springloaded values _springloadFormatting = null; // Update caret italic state ((ITextSelection)this).RefreshCaret(); } } ////// Applies springload formatting to a given content range. /// Clears springloadFormatting after applying it. /// internal void ApplySpringloadFormatting() { if (!(((ITextSelection)this).Start is TextPointer)) { return; } if (this.IsEmpty) { // We can't apply formatting to non-TextContainers or empty selection. return; } if (_springloadFormatting != null) { Invariant.Assert(this.Start.LogicalDirection == LogicalDirection.Backward); Invariant.Assert(this.End.LogicalDirection == LogicalDirection.Forward); LocalValueEnumerator springloadFormattingValues = _springloadFormatting.GetLocalValueEnumerator(); while (!this.IsEmpty && springloadFormattingValues.MoveNext()) { // Note: we repeatedly check for IsEmpty because the selection // may become empty as a result of normalization after formatting // (thai character sequence). LocalValueEntry propertyEntry = springloadFormattingValues.Current; Invariant.Assert(TextSchema.IsCharacterProperty(propertyEntry.Property)); base.ApplyPropertyValue(propertyEntry.Property, propertyEntry.Value); } ClearSpringloadFormatting(); } } #endregion Springload Formatting #region Caret Support // .............................................................. // // Caret Support // // .............................................................. // Shows/hides the caret and scrolls it into view if requested. // Called when the range is moved or layout is updated. internal void UpdateCaretState(CaretScrollMethod caretScrollMethod) { Invariant.Assert(caretScrollMethod != CaretScrollMethod.Unset); if (_pendingCaretNavigation) { caretScrollMethod = CaretScrollMethod.Navigation; _pendingCaretNavigation = false; } if (_caretScrollMethod == CaretScrollMethod.Unset) { _caretScrollMethod = caretScrollMethod; // Post a "Loaded" priority operation to the dispatcher queue. // Operations at Loaded priority are processed when layout and render is // done but just before items at input priority are serviced. // We want the update caret worker to run after layout is clean. if (_textEditor.TextView != null && _textEditor.TextView.IsValid) { UpdateCaretStateWorker(null); } else { Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Loaded, new DispatcherOperationCallback(UpdateCaretStateWorker), null); } } else if (caretScrollMethod != CaretScrollMethod.None) { _caretScrollMethod = caretScrollMethod; } } // Get the caret brush that is the inverted color from the system window or background color. internal static Brush GetCaretBrush(TextEditor textEditor, byte opacity) { Color backgroundColor; ITextSelection focusedTextSelection; object backgroundPropertyValue; // Get the default background from the system color or UiScope's background backgroundPropertyValue = textEditor.UiScope.GetValue(System.Windows.Controls.Panel.BackgroundProperty); if (backgroundPropertyValue != null && backgroundPropertyValue != DependencyProperty.UnsetValue && backgroundPropertyValue is SolidColorBrush) { backgroundColor = ((SolidColorBrush)backgroundPropertyValue).Color; } else { backgroundColor = SystemColors.WindowColor; } // Get the background color from current selection focusedTextSelection = textEditor.Selection; if (focusedTextSelection is TextSelection) { backgroundPropertyValue = ((TextSelection)focusedTextSelection).GetCurrentValue(TextElement.BackgroundProperty); if (backgroundPropertyValue != null && backgroundPropertyValue != DependencyProperty.UnsetValue) { if (backgroundPropertyValue is SolidColorBrush) { backgroundColor = ((SolidColorBrush)backgroundPropertyValue).Color; } } } // Invert the color to get the caret color from the system window or background color. byte r = (byte)~(backgroundColor.R); byte g = (byte)~(backgroundColor.G); byte b = (byte)~(backgroundColor.B); return new SolidColorBrush(Color.FromArgb(opacity, r, g, b)); } #endregion Caret Support #region Bidi Support //...................................................... // // BIDI Support // //...................................................... ////// Check the installed bidi input language from the current /// input keyboard list. /// ////// Critical - calls unmanaged code to get the current available keyboard list. /// TreatAsSafe - data exposed (user using bidi) is safe to release /// [SecurityCritical, SecurityTreatAsSafe] internal static bool IsBidiInputLanguageInstalled() { bool bidiInputLanguageInstalled; bidiInputLanguageInstalled = false; int keyboardListCount = (int)SafeNativeMethods.GetKeyboardLayoutList(0, null); if (keyboardListCount > 0) { int keyboardListIndex; IntPtr[] keyboardList; keyboardList = new IntPtr[keyboardListCount]; keyboardListCount = SafeNativeMethods.GetKeyboardLayoutList(keyboardListCount, keyboardList); for (keyboardListIndex = 0; (keyboardListIndex < keyboardList.Length) && (keyboardListIndex < keyboardListCount); keyboardListIndex++) { CultureInfo cultureInfo = new CultureInfo((short)keyboardList[keyboardListIndex]); if (IsBidiInputLanguage(cultureInfo)) { bidiInputLanguageInstalled = true; break; } } } return bidiInputLanguageInstalled; } #endregion Bidi Support // Forces a synchronous layout validation, up to the selection moving position. void ITextSelection.ValidateLayout() { ((ITextSelection)this).MovingPosition.ValidateLayout(); } #endregion Internal methods //----------------------------------------------------- // // Internal Properties // //------------------------------------------------------ #region Internal Properties // Caret associated with this TextSelection. internal CaretElement CaretElement { get { return _caretElement; } } // Caret associated with this TextSelection. CaretElement ITextSelection.CaretElement { get { return this.CaretElement; } } // Returns true iff there are no additional insertion positions are either // end of the selection. bool ITextSelection.CoversEntireContent { get { ITextSelection This = this; return (This.Start.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.Text && This.End.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text && This.Start.GetNextInsertionPosition(LogicalDirection.Backward) == null && This.End.GetNextInsertionPosition(LogicalDirection.Forward) == null); } } #endregion Internal Properties //------------------------------------------------------ // // Private Methods // //----------------------------------------------------- #region Private Methods // Stores this TextSelection into our TLS slot. private void SetThreadSelection() { TextEditorThreadLocalStore threadLocalStore = TextEditor._ThreadLocalStore; // Store this selection as focused one threadLocalStore.FocusedTextSelection = this; } // Removes this TextSelection from our TLS slot. private void ClearThreadSelection() { // Clear currently focused selection, if it's us if (TextEditor._ThreadLocalStore.FocusedTextSelection == this) { TextEditor._ThreadLocalStore.FocusedTextSelection = null; } } // GotKeyboardFocus event handler. Called by UpdateCaretAndHighlight. private void Highlight() { // Make sure that a highlight layer exists for drawing this selection if (_highlightLayer == null) { _highlightLayer = new TextSelectionHighlightLayer(this); } // Make selection visible if (((ITextSelection)this).Start.TextContainer.Highlights.GetLayer(typeof(TextSelection)) == null) { ((ITextSelection)this).Start.TextContainer.Highlights.AddLayer(_highlightLayer); } } // LostKeyboardFocus event handler. Called by UpdateCaretAndHighlight. private void Unhighlight() { TextSelectionHighlightLayer highlightLayer = ((ITextSelection)this).Start.TextContainer.Highlights.GetLayer(typeof(TextSelection)) as TextSelectionHighlightLayer; if (highlightLayer != null) { ((ITextSelection)this).Start.TextContainer.Highlights.RemoveLayer(highlightLayer); Invariant.Assert(((ITextSelection)this).Start.TextContainer.Highlights.GetLayer(typeof(TextSelection)) == null); } } //...................................................... // // Active Positions of Selection // //...................................................... ////// Stores normalized anchor and moving positions for the selection. /// Ensures that they are both inside of range Start/End. /// /// /// A position which must be stored as initial position for the selection. /// /// /// The "hot" or active selection edge which responds to user input. /// private void SetActivePositions(ITextPointer anchorPosition, ITextPointer movingPosition) { // The following settings are used in auto-word exapnsion. // By setting a _previousPosition we are clearing them all - // they will be re-initialized in the beginning of a selection // expansion guesture in ExtendSelectionByMouse method // Previous position is needed for selection gestures to remember // where mouse drag happed last time. Used for word autoexpansion. _previousCursorPosition = null; if (this.IsEmpty) { _anchorPosition = null; _movingPositionEdge = MovingEdge.None; return; } Invariant.Assert(anchorPosition != null); ITextSelection thisSelection = (ITextSelection)this; // Normalize and store new selection anchor position _anchorPosition = anchorPosition.GetInsertionPosition(anchorPosition.LogicalDirection); // Ensure that anchor position is within one of text segments if (_anchorPosition.CompareTo(thisSelection.Start) < 0) { _anchorPosition = thisSelection.Start.GetFrozenPointer(_anchorPosition.LogicalDirection); } else if (_anchorPosition.CompareTo(thisSelection.End) > 0) { _anchorPosition = thisSelection.End.GetFrozenPointer(_anchorPosition.LogicalDirection); } _movingPositionEdge = ConvertToMovingEdge(anchorPosition, movingPosition); _movingPositionDirection = movingPosition.LogicalDirection; } // Uses the current selection state to match an ITextPointer to one of the possible // moving position edges. private MovingEdge ConvertToMovingEdge(ITextPointer anchorPosition, ITextPointer movingPosition) { ITextSelection thisSelection = this; MovingEdge movingEdge; if (thisSelection.IsEmpty) { // Empty selections have no moving edge. movingEdge = MovingEdge.None; } else if (thisSelection.TextSegments.Count < 2) { // Simple text selections move opposite their anchor positions. movingEdge = (anchorPosition.CompareTo(movingPosition) <= 0) ? MovingEdge.End : MovingEdge.Start; } else { // Table selection. Look for an exact match. if (movingPosition.CompareTo(thisSelection.Start) == 0) { movingEdge = MovingEdge.Start; } else if (movingPosition.CompareTo(thisSelection.End) == 0) { movingEdge = MovingEdge.End; } else if (movingPosition.CompareTo(thisSelection.TextSegments[0].End) == 0) { movingEdge = MovingEdge.StartInner; } else if (movingPosition.CompareTo(thisSelection.TextSegments[thisSelection.TextSegments.Count-1].Start) == 0) { movingEdge = MovingEdge.EndInner; } else { movingEdge = (anchorPosition.CompareTo(movingPosition) <= 0) ? MovingEdge.End : MovingEdge.Start; } } return movingEdge; } //...................................................... // // Selection Building With Mouse // //...................................................... // Moves the selection to the mouse cursor position. // If the cursor is facing a UIElement, select the UIElement. // Sets new selection anchor to a given cursorPosition. private void MoveSelectionByMouse(ITextPointer cursorPosition, Point cursorMousePoint) { ITextSelection thisSelection = (ITextSelection)this; if (this.TextView == null) { return; } Invariant.Assert(this.TextView.IsValid); // We just checked RenderScope. We'll use TextView below ITextPointer movingPosition = null; if (cursorPosition.GetPointerContext(cursorPosition.LogicalDirection) == TextPointerContext.EmbeddedElement) { Rect objectEdgeRect = this.TextView.GetRectangleFromTextPosition(cursorPosition); // Check for embedded object. // If the click happend inside of it we need to select it as a whole, when content is not read-only. if (!_textEditor.IsReadOnly && ShouldSelectEmbeddedObject(cursorPosition, cursorMousePoint, objectEdgeRect)) { movingPosition = cursorPosition.GetNextContextPosition(cursorPosition.LogicalDirection); } } // Move selection to this position if (movingPosition == null) { thisSelection.SetCaretToPosition(cursorPosition, cursorPosition.LogicalDirection, /*allowStopAtLineEnd:*/true, /*allowStopNearSpace:*/false); } else { thisSelection.Select(cursorPosition, movingPosition); } } // Helper for MoveSelectionByMouse private bool ShouldSelectEmbeddedObject(ITextPointer cursorPosition, Point cursorMousePoint, Rect objectEdgeRect) { // Although we now know that cursorPosition is facing an embedded object, // we still need an additional test to determine if the original mouse point // fell within the object or outside it's bouding box (which can happen when // a mouse click is snapped to the nearest content). // If the mouse point is outside the object, we don't want to select it. if (!objectEdgeRect.IsEmpty && cursorMousePoint.Y >= objectEdgeRect.Y && cursorMousePoint.Y < objectEdgeRect.Y + objectEdgeRect.Height) { // Compare X coordinates of mouse down point and object edge rect, // depending on the FlowDirection of the render scope and paragraph content. FlowDirection renderScopeFlowDirection = (FlowDirection)this.TextView.RenderScope.GetValue(Block.FlowDirectionProperty); FlowDirection paragraphFlowDirection = (FlowDirection)cursorPosition.GetValue(Block.FlowDirectionProperty); if (renderScopeFlowDirection == FlowDirection.LeftToRight) { if (paragraphFlowDirection == FlowDirection.LeftToRight && (cursorPosition.LogicalDirection == LogicalDirection.Forward && objectEdgeRect.X < cursorMousePoint.X || cursorPosition.LogicalDirection == LogicalDirection.Backward && cursorMousePoint.X < objectEdgeRect.X)) { return true; } else if (paragraphFlowDirection == FlowDirection.RightToLeft && (cursorPosition.LogicalDirection == LogicalDirection.Forward && objectEdgeRect.X > cursorMousePoint.X || cursorPosition.LogicalDirection == LogicalDirection.Backward && cursorMousePoint.X > objectEdgeRect.X)) { return true; } } else { if (paragraphFlowDirection == FlowDirection.LeftToRight && (cursorPosition.LogicalDirection == LogicalDirection.Forward && objectEdgeRect.X > cursorMousePoint.X || cursorPosition.LogicalDirection == LogicalDirection.Backward && cursorMousePoint.X > objectEdgeRect.X)) { return true; } else if (paragraphFlowDirection == FlowDirection.RightToLeft && (cursorPosition.LogicalDirection == LogicalDirection.Forward && objectEdgeRect.X < cursorMousePoint.X || cursorPosition.LogicalDirection == LogicalDirection.Backward && cursorMousePoint.X < objectEdgeRect.X)) { return true; } } } return false; } //...................................................... // // Caret Support // //...................................................... // Redraws a caret using current setting for italic - taking springload formatting into account. private static void RefreshCaret(TextEditor textEditor, ITextSelection textSelection) { object fontStylePropertyValue; bool italic; if (textSelection == null || textSelection.CaretElement == null) { return; } // NOTE: We are using GetCurrentValue to take springload formatting into account. fontStylePropertyValue = ((TextSelection)textSelection).GetCurrentValue(TextElement.FontStyleProperty); italic = (textEditor.AcceptsRichContent && fontStylePropertyValue != DependencyProperty.UnsetValue && (FontStyle)fontStylePropertyValue == FontStyles.Italic); textSelection.CaretElement.RefreshCaret(italic); } // Called after a caret navigation, to signal that the next caret // scroll-into-view should include hueristics to include following // text. internal void OnCaretNavigation() { _pendingCaretNavigation = true; } // Called after a caret navigation, to signal that the next caret // scroll-into-view should include hueristics to include following // text. void ITextSelection.OnCaretNavigation() { OnCaretNavigation(); } // Callback for UpdateCaretState worker. private object UpdateCaretStateWorker(object o) { // This can happen if selection has been detached by TextEditor.OnDetach. if (_textEditor == null) { return null; } TextEditorThreadLocalStore threadLocalStore = TextEditor._ThreadLocalStore; CaretScrollMethod caretScrollMethod = _caretScrollMethod; _caretScrollMethod = CaretScrollMethod.Unset; // Use the locally defined caretElement because _caretElement can be null by // detaching CaretElement object CaretElement caretElement = _caretElement; if (caretElement == null) { return null; } if (threadLocalStore.FocusedTextSelection == null) { // If we have multiple windows open, a non-blinking caret might be showing // in tne given TextEditor's UiScope. If the selection for that Editor is // not empty, we need to hide the caret. if (!this.IsEmpty) { caretElement.Hide(); } return null; } // When the TextView is not valid, there is nothing to do if (_textEditor.TextView == null || !_textEditor.TextView.IsValid) { // return null; } if (!this.VerifyAdornerLayerExists()) { caretElement.Hide(); } // Identify caret position // Make sure that moving position is inside of selection ITextPointer caretPosition = IdentifyCaretPosition(this); // If caret position is not valid in focusedTextSelection.TextView, we cannot update it. Return if (caretPosition.HasValidLayout) { Rect caretRectangle; bool italic = false; bool caretVisible = (this.IsEmpty && !_textEditor.IsReadOnly) || (_textEditor.IsReadOnly && _textEditor.IsReadOnlyCaretVisible); if (!this.IsInterimSelection) { caretRectangle = CalculateCaretRectangle(this, caretPosition); if (this.IsEmpty) { // Identify italic condition (including springload) - to use for caret shaping object fontStylePropertyValue = GetPropertyValue(TextElement.FontStyleProperty); italic = (_textEditor.AcceptsRichContent && fontStylePropertyValue != DependencyProperty.UnsetValue && (FontStyle)fontStylePropertyValue == FontStyles.Italic); } } else { caretRectangle = CalculateInterimCaretRectangle(this); // Caret always visible on the interim input mode. caretVisible = true; } Brush caretBrush = GetCaretBrush(_textEditor, /*opacity:1.0f*/0xff); // Calculate the scroll origin position to scroll it with the caret position. double scrollToOriginPosition = CalculateScrollToOriginPosition(_textEditor, caretPosition, caretRectangle.X); // Re-render the caret. // Get a bounding rect from the active end of selection. caretElement.Update(caretVisible, caretRectangle, caretBrush, italic, caretScrollMethod, scrollToOriginPosition); } // CaretElement.Update(...) makes a conditional call to BringIntoView() // on the text view's associated element, which makes the view invalid... if (this.TextView.IsValid && !this.TextView.RendersOwnSelection) { // Re-render selection. Need to do this to invalidate and update adorner for this caret element. caretElement.UpdateSelection(); } return null; } // Helper for UpdateCaretState -- identifies caret position from text selection. private static ITextPointer IdentifyCaretPosition(ITextSelection currentTextSelection) { ITextPointer caretPosition = currentTextSelection.MovingPosition; if (!currentTextSelection.IsEmpty) { // Even when we do not draw the blinking caret, we need this position // for scrolling caret position into view. // Special case for nonempty selection extended beyond end of line if ((caretPosition.LogicalDirection == LogicalDirection.Backward && // caretPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) || // TextPointerBase.IsAfterLastParagraph(caretPosition)) { // This means that selection has been expanded by ExtendToLineEnd/ExtendToDocumentEnd command. // TextView in this case cannot give the rect from this position; // we need to move backward to the end of content caretPosition = caretPosition.CreatePointer(); caretPosition.MoveToNextInsertionPosition(LogicalDirection.Backward); caretPosition.SetLogicalDirection(LogicalDirection.Forward); } } // TextView.GetRectangleFromTextPosition returns the end of character rect in case of Backward // logical direction at the start caret position of the docuemtn or paragraph, so we reset the // logical direction with Forward to get the right rect of caret at the start position of // document or paragraph. if (caretPosition.LogicalDirection == LogicalDirection.Backward && // caretPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart && // (caretPosition.GetNextInsertionPosition(LogicalDirection.Backward) == null || // TextPointerBase.IsNextToAnyBreak(caretPosition, LogicalDirection.Backward))) { caretPosition = caretPosition.CreatePointer(); caretPosition.SetLogicalDirection(LogicalDirection.Forward); } return caretPosition; } // Helper for UpdateCaretState -- calculates caret rectangle from text selection. // regular (non-interim) case of caret positioning private static Rect CalculateCaretRectangle(ITextSelection currentTextSelection, ITextPointer caretPosition) { Transform caretTransform; Rect caretRectangle = currentTextSelection.TextView.GetRawRectangleFromTextPosition(caretPosition, out caretTransform); if (caretRectangle.IsEmpty) { // Caret is not at an insertion position, it has no geometry // and will not be displayed. return Rect.Empty; } // Convert to local coordiantes. caretRectangle = caretTransform.TransformBounds(caretRectangle); // We will use the system defined caret width later. caretRectangle.Width = 0; if (currentTextSelection.IsEmpty) { // Calculate caret height - from current font size (ignoring a rect returned by TextView, // as it can be a rect of embedded object, which should not affect caret height) double fontSize = (double)currentTextSelection.GetPropertyValue(TextElement.FontSizeProperty); FontFamily fontFamily = (FontFamily)currentTextSelection.GetPropertyValue(TextElement.FontFamilyProperty); double caretHeight = fontFamily.LineSpacing * fontSize; if (caretHeight < caretRectangle.Height) { // Decrease the height of caret to the font height and lower the caret to keep its bottom // staying on the baseline. caretRectangle.Y += caretRectangle.Height - caretHeight; caretRectangle.Height = caretHeight; } if (!caretTransform.IsIdentity) { Point top = new Point(caretRectangle.X, caretRectangle.Y); Point bottom = new Point(caretRectangle.X, caretRectangle.Y + caretRectangle.Height); caretTransform.TryTransform(top, out top); caretTransform.TryTransform(bottom, out bottom); caretRectangle.Y += caretRectangle.Height - Math.Abs(bottom.Y - top.Y); caretRectangle.Height = Math.Abs(bottom.Y - top.Y); } } return caretRectangle; } // Helper for UpdateCaretState -- handles the korean interim caret. private static Rect CalculateInterimCaretRectangle(ITextSelection focusedTextSelection) { // // Get the current flow direction on the selection of interim. // This is for getting the right size of distance on the interim character // whatever the current flow direction is. FlowDirection flowDirection = (FlowDirection)focusedTextSelection.Start.GetValue(FrameworkElement.FlowDirectionProperty); ITextPointer nextCharacterPosition; Rect nextCharacterRectangle; Rect caretRectangle; if (flowDirection != FlowDirection.RightToLeft) { // Flow direction is Left-to-Right // Get the rectangle for both interim selection start position and the next character // position. nextCharacterPosition = focusedTextSelection.Start.CreatePointer(LogicalDirection.Forward); caretRectangle = focusedTextSelection.TextView.GetRectangleFromTextPosition(nextCharacterPosition); // Get the next character position from the start position of the interim selection // on the left to right flow direction. nextCharacterPosition.MoveToNextInsertionPosition(LogicalDirection.Forward); nextCharacterPosition.SetLogicalDirection(LogicalDirection.Backward); nextCharacterRectangle = focusedTextSelection.TextView.GetRectangleFromTextPosition(nextCharacterPosition); } else { // Flow direction is Right-to-Left // Get the rectangle for both interim selection end position and the next character // position. nextCharacterPosition = focusedTextSelection.End.CreatePointer(LogicalDirection.Backward); caretRectangle = focusedTextSelection.TextView.GetRectangleFromTextPosition(nextCharacterPosition); // Get the next character position from the end position of the interim selection // on the right to left flow direction. nextCharacterPosition.MoveToNextInsertionPosition(LogicalDirection.Backward); nextCharacterPosition.SetLogicalDirection(LogicalDirection.Forward); nextCharacterRectangle = focusedTextSelection.TextView.GetRectangleFromTextPosition(nextCharacterPosition); } // The interim next character position should be great than the current interim position. // Otherwise, we show the caret as the normal state that use the system caret width. // In case of BiDi character, the next character position can be greater than the current position. if (!caretRectangle.IsEmpty && !nextCharacterRectangle.IsEmpty && nextCharacterRectangle.Left > caretRectangle.Left) { // Get the interim caret width to show the interim block caret. caretRectangle.Width = nextCharacterRectangle.Left - caretRectangle.Left; } return caretRectangle; } // Helper for UpdateCaretStateWorker -- Calculate the scroll origin position to scroll caret // with the scroll origin position so that we can ensure of displaying caret with the wrapped word. // // There are four cases of different corrdinate by the flow direction on UiScope and Paragraph. // UiScope has two flow direction which is LeftToRightflow directioin and another is RightToLeft. // Paragraph has also two flow direction which is LeftToRightflow directioin and another is RightToLeft. // // The below is the example of how horizontal corrdinate and scroll origin value base on the different // four cases. So we have to calculate the scroll to origin position base on the case. Simply we can // get the scroll to origin value as zero if UiScope and Paragraph's flow direction is the same. // Otherwise, the scroll to origin value is the extent width value that is the max width. // // <> // Case 1. // UiScope FlowDirection: LTR(LeftToRight) // Paragraph FlowDirection: LTR(LefTToRight) // Horizontal origin: "Left" // Scroll horizontal origin: "0" // Wrapping to: "Left" // ABC ...... // XYZ| // // Case 2. // UiScope FlowDirection: LTR(LeftToRight) // Paragraph FlowDirection: RTL(RightToLeft) // Horizontal origin: "Left" // Scroll horizontal origin: "Max:Extent Width" // Wrapping to: "Right" // ......ABC // XYZ| // // Case 3. // UiScope FlowDirection: RTL(RightToLeft) // Paragraph FlowDirection: RTL(RightToLeft) // horizontal origin: "Right" // Scroll horizontal origin: "0" // Wrapping to: "Right" // ......ABC // XYZ| // // Case 4. // UiScope FlowDirection: RTL(RightToLeft) // Paragraph FlowDirection: LTR(LefTToRight) // horizontal origin: "Right" // Scroll horizontal origin: "Max:Extent Width" // Wrapping to: "Left" // ABC ...... // XYZ| private static double CalculateScrollToOriginPosition(TextEditor textEditor, ITextPointer caretPosition, double horizontalCaretPosition) { double scrollToOriginPosition = double.NaN; if (textEditor.UiScope is TextBoxBase) { double viewportWidth = ((TextBoxBase)textEditor.UiScope).ViewportWidth; double extentWidth = ((TextBoxBase)textEditor.UiScope).ExtentWidth; // Calculate the scroll to the origin position position when the horizontal scroll is available if (viewportWidth != 0 && extentWidth != 0 && viewportWidth < extentWidth) { bool needScrollToOriginPosition = false; // Check whether we need to calculate the scroll origin position to scroll it with the caret // position. If the caret position is out of the current visual viewport area, the scroll // to origin positioin will be calculated to scroll into the origin position first that // ensure of displaying the wrapped word. // // Note that horizontalCaretPosition is always relative to the viewport, not the document. if (horizontalCaretPosition < 0 || horizontalCaretPosition >= viewportWidth) { needScrollToOriginPosition = true; } if (needScrollToOriginPosition) { // Set the scroll original position as zero scrollToOriginPosition = 0; // Get the flow direction of uiScope FlowDirection uiScopeflowDirection = (FlowDirection)textEditor.UiScope.GetValue(FrameworkElement.FlowDirectionProperty); // Get the flow direction of the current paragraph and compare it with uiScope's flow direction. Block paragraphOrBlockUIContainer = (caretPosition is TextPointer) ? ((TextPointer)caretPosition).ParagraphOrBlockUIContainer : null; if (paragraphOrBlockUIContainer != null) { FlowDirection pagraphFlowDirection = paragraphOrBlockUIContainer.FlowDirection; // If the flow direction is different between uiScopoe and paragaph, // the original scroll position is the extent width value. if (uiScopeflowDirection != pagraphFlowDirection) { scrollToOriginPosition = extentWidth; } } // Adjust scroll position by current viewport offset scrollToOriginPosition -= ((TextBoxBase)textEditor.UiScope).HorizontalOffset; } } } return scrollToOriginPosition; } private CaretElement EnsureCaret(bool isBlinkEnabled, CaretScrollMethod scrollMethod) { TextEditorThreadLocalStore threadLocalStore = TextEditor._ThreadLocalStore; if (_caretElement == null) { // Create new caret _caretElement = new CaretElement(_textEditor, isBlinkEnabled); // Check the current input language to draw the BiDi caret in case of BiDi language // like as Arabic or Hebrew input language. // if (IsBidiInputLanguage(InputLanguageManager.Current.CurrentInputLanguage)) { TextEditor._ThreadLocalStore.Bidi = true; } else { TextEditor._ThreadLocalStore.Bidi = false; } } else { _caretElement.SetBlinking(isBlinkEnabled); } UpdateCaretState(scrollMethod); return _caretElement; } /// /// Walk up the tree from the RenderScope to the UiScope until we find an /// AdornerDecorator or ScrollContentPresenter. /// ///true if one is found, false otherwise private bool VerifyAdornerLayerExists() { DependencyObject element = TextView.RenderScope; while (element != _textEditor.UiScope && element != null) { if (element is AdornerDecorator || element is System.Windows.Controls.ScrollContentPresenter) { return true; } element = VisualTreeHelper.GetParent(element); } return false; } //...................................................... // // BIDI Support // //...................................................... ////// Check the input language of cultureInfo whether it is the bi-directional language or not. /// /// ////// Return true if the passed cultureInfo is the bi-directional language like as Arabic or Hebrew. /// Otherwise, return false. /// ////// Critical - calls unmanaged code to check the font signature /// TreatAsSafe - data exposed (user using bidi) is safe to release /// [SecurityCritical, SecurityTreatAsSafe] private static bool IsBidiInputLanguage(CultureInfo cultureInfo) { bool bidiInput; string fontSignature; bidiInput = false; fontSignature = new String(new Char[FONTSIGNATURE_SIZE]); // Get the font signature to know the current LCID is BiDi(Arabic, Hebrew etc.) or not. if (UnsafeNativeMethods.GetLocaleInfoW(cultureInfo.LCID, NativeMethods.LOCALE_FONTSIGNATURE, fontSignature, FONTSIGNATURE_SIZE) != 0) { // Compare fontSignature[7] with 0x0800 to detect BiDi language. if ((fontSignature[FONTSIGNATURE_BIDI_INDEX] & FONTSIGNATURE_BIDI) != 0) { bidiInput = true; } } return bidiInput; } //...................................................... // // Table Selection // //...................................................... private static TableCell FindCellAtColumnIndex(TableCellCollection cells, int columnIndex) { for (int cellIndex = 0; cellIndex < cells.Count; cellIndex++) { TableCell cell; int startColumnIndex; int endColumnIndex; cell = cells[cellIndex]; startColumnIndex = cell.ColumnIndex; endColumnIndex = startColumnIndex + cell.ColumnSpan - 1; if (startColumnIndex <= columnIndex && columnIndex <= endColumnIndex) { return cell; } } return null; } ////// Determines if the given element has any ancestor. /// /// ///private static bool IsRootElement(DependencyObject element) { return GetParentElement(element) == null; } /// /// Workaround to approximate whether or not our window is active. /// ///private bool IsFocusWithinRoot() { DependencyObject element = this.UiScope; DependencyObject parent = this.UiScope; while (parent != null) { element = parent; parent = GetParentElement(element); } if (element is UIElement && ((UIElement)element).IsKeyboardFocusWithin) { return true; } return false; } /// /// Helper method to determine an element's parent, using the umpteen methods of /// parenting. /// /// private static DependencyObject GetParentElement(DependencyObject element) { DependencyObject parent; if (element is FrameworkElement || element is FrameworkContentElement) { parent = LogicalTreeHelper.GetParent(element); if (parent == null && element is FrameworkElement) { parent = ((FrameworkElement)element).TemplatedParent; if (parent == null && element is Visual) { parent = VisualTreeHelper.GetParent(element); } } } else if (element is Visual) { parent = VisualTreeHelper.GetParent(element); } else { parent = null; } return parent; } // Removes the caret from the visual tree. private void DetachCaretFromVisualTree() { if (_caretElement != null) { _caretElement.DetachFromView(); _caretElement = null; } } #endregion Private methods //------------------------------------------------------ // // Private Properties // //----------------------------------------------------- #region Private Properties TextEditor ITextSelection.TextEditor { get { return _textEditor; } } ITextView ITextSelection.TextView { get { return _textEditor.TextView; } } private ITextView TextView { get { return ((ITextSelection)this).TextView; } } private TextStore TextStore { get { return _textEditor.TextStore; } } private ImmComposition ImmComposition { get { return _textEditor.ImmComposition; } } // UiScope associated with this TextSelection's TextEditor. private FrameworkElement UiScope { get { return _textEditor.UiScope; } } // Position from which to spring load property values. private ITextPointer PropertyPosition { get { ITextSelection This = this; ITextPointer position = null; if (!This.IsEmpty) { position = TextPointerBase.GetFollowingNonMergeableInlineContentStart(This.Start); } if (position == null) { position = This.Start; } position.Freeze(); return position; } } #endregion Private Properties //----------------------------------------------------- // // Private Types // //----------------------------------------------------- #region Private Types // The four possible positions for the selection moving position. private enum MovingEdge { Start, StartInner, EndInner, End, None }; #endregion Private Types //------------------------------------------------------ // // Private Fields // //----------------------------------------------------- #region Private Fields // Selected Content // ---------------- // TextEditor that owns this selection. private TextEditor _textEditor; // Container for highlights on selected text. private TextSelectionHighlightLayer _highlightLayer; // Springload Formatting // --------------------- // Dependency object representing a set of springloaded formatting properties private DependencyObject _springloadFormatting; // Selection autoexpansion for unit boundaries // ------------------------------------------- // A position where the last selection gesture has been initiated. // Selection gestures are: mouseDown-Move-...-Move-Up, Shift+ArrowDown-...-Down-Up. // Note that Shift+MouseDown-Move-...-Move-Up is a gesture continuation, // it does not change _anchorPosition. // Actual selection always contains this position but may be extended // to a wider range - to encompass whole units such as words, // hyperlinks, sentences, paragraphs, table cells etc. // private ITextPointer _anchorPosition; // A position to which user input is applied. // It may coinside with Start or End, but it may be also in some other // corner of rectangular table range. private MovingEdge _movingPositionEdge; // LogicalDirection for the moving position. // If the selection is empty, this value is ignored and // the direction matches this.Start. Otherwise we respect // the value, which typically points inwards towards the // content but may point outward to include an empty line. private LogicalDirection _movingPositionDirection; // Text position used on previous step of mouse dragging selection // It is used in selection dragging heuristic to identify a situation when // dragging end returned back to selected area which means that // autoword expansion must be temporary stopped - until a new word // boundary is crossed. See also _reenterPosition. private ITextPointer _previousCursorPosition; // Text position where dragged mouse re-entered a selection. This word should not be autoexapnded private ITextPointer _reenterPosition; // Flag indicating that initial word boundary has been crossed at least once doring selection expansion private bool _anchorWordRangeHasBeenCrossedOnce; // Flag allows autoword expansion for anchor end of selection private bool _allowWordExpansionOnAnchorEnd; // Font signature size as 16 private const int FONTSIGNATURE_SIZE = 16; // BIDI font signature index from GetLocaleInfo. private const int FONTSIGNATURE_BIDI_INDEX = 7; // BIDI font signature value private const int FONTSIGNATURE_BIDI = 0x0800; // Signals how the caret should be scrolled into view on the next // caret update. private CaretScrollMethod _caretScrollMethod; // If true, signals that the next caret // scroll-into-view should include hueristics to include following // text. private bool _pendingCaretNavigation; // Caret associated with this selection. private CaretElement _caretElement; #endregion Private Fields } } // 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
- HelloMessageApril2005.cs
- SqlServices.cs
- UidPropertyAttribute.cs
- ConfigurationErrorsException.cs
- Icon.cs
- WasEndpointConfigContainer.cs
- XmlDocumentSerializer.cs
- QilInvokeLateBound.cs
- XmlDataSourceView.cs
- ControlAdapter.cs
- FileIOPermission.cs
- EntitySet.cs
- ToolboxSnapDragDropEventArgs.cs
- SecurityRuntime.cs
- ResourceDefaultValueAttribute.cs
- KeyTimeConverter.cs
- CodeNamespaceImport.cs
- WindowHideOrCloseTracker.cs
- UserValidatedEventArgs.cs
- WebPartTransformerAttribute.cs
- SystemIPInterfaceProperties.cs
- StructuralType.cs
- DefaultConfirmation.cs
- MachineKeyValidationConverter.cs
- RightNameExpirationInfoPair.cs
- CheckBoxList.cs
- ParserStack.cs
- JsonByteArrayDataContract.cs
- PrivateUnsafeNativeCompoundFileMethods.cs
- RevocationPoint.cs
- SystemColorTracker.cs
- ValueChangedEventManager.cs
- QueryReaderSettings.cs
- GridView.cs
- SR.cs
- ItemCheckEvent.cs
- SelectorItemAutomationPeer.cs
- GrammarBuilderDictation.cs
- TraceRecord.cs
- EntityCommandExecutionException.cs
- SplashScreen.cs
- StylusEventArgs.cs
- LocalFileSettingsProvider.cs
- StylusShape.cs
- DataServiceCollectionOfT.cs
- GridViewRow.cs
- ReferenceConverter.cs
- String.cs
- SchemaImporter.cs
- ParameterBinding.cs
- TimeoutException.cs
- ListBox.cs
- returneventsaver.cs
- SymDocumentType.cs
- DelegatedStream.cs
- OverrideMode.cs
- BlurEffect.cs
- WpfXamlMember.cs
- RuleSettingsCollection.cs
- WebReferencesBuildProvider.cs
- DataGridCommandEventArgs.cs
- PlainXmlSerializer.cs
- SmtpMail.cs
- LoginName.cs
- SoapUnknownHeader.cs
- EdmFunction.cs
- M3DUtil.cs
- TemplateControl.cs
- SerialStream.cs
- AuthenticationServiceManager.cs
- MDIClient.cs
- StringDictionary.cs
- CoTaskMemSafeHandle.cs
- PropertyValidationContext.cs
- ValidatorCollection.cs
- SqlDataSourceCommandEventArgs.cs
- X509RawDataKeyIdentifierClause.cs
- TreeViewEvent.cs
- ParallelDesigner.cs
- ObjectSet.cs
- ListViewAutomationPeer.cs
- AsyncOperationManager.cs
- Comparer.cs
- GeometryGroup.cs
- TraceListener.cs
- ValueProviderWrapper.cs
- ConnectionPointCookie.cs
- Setter.cs
- FixedSchema.cs
- LineGeometry.cs
- ContentElement.cs
- GlyphTypeface.cs
- HttpsChannelListener.cs
- X509ChainPolicy.cs
- PasswordPropertyTextAttribute.cs
- Win32.cs
- SessionPageStateSection.cs
- CommonProperties.cs
- DateTimeEditor.cs
- CollectionsUtil.cs