|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// Description: a high-level tool for editing text based FrameworkElements.
//
using MS.Internal;
using System.Collections.Generic;
using System.Windows.Threading;
using System.Globalization;
using System.Xml;
using System.IO;
using System.Windows.Markup; // Parser
#pragma warning disable 1634, 1691 // suppressing PreSharp warnings
namespace System.Windows.Documents
{
/// <summary>
/// TextRange is a high-level tool for editing text based FrameworkElements
/// such as Text, TextFlow, or RichTextBox.
/// </summary>
public class TextRange : ITextRange
{
#region Constructors
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
/// <summary>
/// Creates a new TextRange instance.
/// </summary>
/// <param name="position1">
/// TextPointer specifying the static end of the new TextRange.
/// </param>
/// <param name="position2">
/// TextPointer specifying the dynamic end of the new TextRange.
/// </param>
/// <exception cref="System.ArgumentException">
/// Throws an ArgumentException if position1 and position2
/// are not positioned within the same document, or if either
/// TextPointer is null.
/// </exception>
/// <remarks>
/// position1 represents the origin of the TextRange, as opposed
/// to position2, which represents the UI active end of a TextRange.
/// The distinction is important in applying selection heuristics to
/// the exact placement of a new TextRange.
///
/// Note that the parameters to this method will not always match
/// the Start and End properties of the new TextRange. The exact
/// positioning of a new TextRange is subject to hueristics specific
/// to document types -- hueristics that match text selection behavior.
/// </remarks>
public TextRange(TextPointer position1, TextPointer position2) :
this((ITextPointer)position1, (ITextPointer)position2)
{
}
internal TextRange(ITextPointer position1, ITextPointer position2)
: this(position1, position2, false /* ignoreTextUnitBoundaries */)
{
}
/// <summary>
/// Creates a new TextRange instance.
/// </summary>
/// <param name="position1">
/// </param>
/// TextPointer specifying the static end of the new TextRange.
/// <param name="position2">
/// TextPointer specifying the dynamic end of the new TextRange.
/// </param>
/// <param name="useRestrictiveXamlXmlReader">
/// Boolean flag. False by default, set to true to disable external xaml loading in specific scenarios like StickyNotes annotation loading
/// </param>
internal TextRange(TextPointer position1, TextPointer position2, bool useRestrictiveXamlXmlReader) :
this((ITextPointer)position1, (ITextPointer)position2)
{
_useRestrictiveXamlXmlReader = useRestrictiveXamlXmlReader;
}
// ignoreTextUnitBoundaries - true if normalization should ignore text
// normalization (surrogates, combining marks, etc).
// Used for fine-grained control by IMEs.
internal TextRange(ITextPointer position1, ITextPointer position2, bool ignoreTextUnitBoundaries)
{
ArgumentNullException.ThrowIfNull(position1);
ArgumentNullException.ThrowIfNull(position2);
SetFlags(ignoreTextUnitBoundaries, Flags.IgnoreTextUnitBoundaries);
ValidationHelper.VerifyPosition(position1.TextContainer, position1, "position1");
ValidationHelper.VerifyPosition(position1.TextContainer, position2, "position2");
TextRangeBase.Select(this, position1, position2);
}
#endregion Constructors
// *****************************************************
// *****************************************************
// *****************************************************
//
// Abstract TextRange Implementation
//
// *****************************************************
// *****************************************************
// *****************************************************
//------------------------------------------------------
//
// ITextRange implementation
//
//------------------------------------------------------
#region ITextRange Implementation
//......................................................
//
// Selection Building
//
//......................................................
/// <summary>
/// <see cref="System.Windows.Documents.TextRange.Contains"/>
/// </summary>
bool ITextRange.Contains(ITextPointer position)
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
return TextRangeBase.Contains(this, position);
}
/// <summary>
/// <see cref="System.Windows.Documents.TextRange.Select"/>
/// </summary>
void ITextRange.Select(ITextPointer position1, ITextPointer position2)
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
TextRangeBase.Select(this, position1, position2);
}
/// <summary>
/// <see cref="System.Windows.Documents.TextRange.SelectWord"/>
/// </summary>
void ITextRange.SelectWord(ITextPointer position)
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
TextRangeBase.SelectWord(this, position);
}
/// <summary>
/// <see cref="System.Windows.Documents.TextRange.SelectParagraph"/>
/// </summary>
void ITextRange.SelectParagraph(ITextPointer position)
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
TextRangeBase.SelectParagraph(this, position);
}
/// <summary>
/// <see cref="System.Windows.Documents.ITextRange.ApplyTypingHeuristics"/>
/// </summary>
void ITextRange.ApplyTypingHeuristics(bool overType)
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
TextRangeBase.ApplyTypingHeuristics(this, overType);
}
object ITextRange.GetPropertyValue(DependencyProperty formattingProperty)
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
return TextRangeBase.GetPropertyValue(this, formattingProperty);
}
UIElement ITextRange.GetUIElementSelected()
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
return TextRangeBase.GetUIElementSelected(this);
}
//......................................................
//
// Range Content Serialization
//
//......................................................
bool ITextRange.CanSave(string dataFormat)
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
return TextRangeBase.CanSave(this, dataFormat);
}
void ITextRange.Save(Stream stream, string dataFormat)
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
TextRangeBase.Save(this, stream, dataFormat, false);
}
void ITextRange.Save(Stream stream, string dataFormat, bool preserveTextElements)
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
TextRangeBase.Save(this, stream, dataFormat, preserveTextElements);
}
//......................................................
//
// Change Notifications
//
//......................................................
/// <summary>
/// <see cref="ITextRange.BeginChange"/>
/// </summary>
void ITextRange.BeginChange()
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
TextRangeBase.BeginChange(this);
}
/// <summary>
/// <see cref="ITextRange.BeginChangeNoUndo"/>
/// </summary>
void ITextRange.BeginChangeNoUndo()
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
TextRangeBase.BeginChangeNoUndo(this);
}
/// <summary>
/// <see cref="ITextRange.EndChange()"/>
/// </summary>
void ITextRange.EndChange()
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
TextRangeBase.EndChange(this, false /* disableScroll */, false /* skipEvents */);
}
/// <summary>
/// <see cref="ITextRange.EndChange(bool,bool)"/>
/// </summary>
void ITextRange.EndChange(bool disableScroll, bool skipEvents)
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
TextRangeBase.EndChange(this, disableScroll, skipEvents);
}
/// <summary>
/// <see cref="ITextRange.DeclareChangeBlock()"/>
/// </summary>
IDisposable ITextRange.DeclareChangeBlock()
{
return new ChangeBlock(this, false /* disableScroll */);
}
/// <summary>
/// <see cref="ITextRange.DeclareChangeBlock(bool)"/>
/// </summary>
IDisposable ITextRange.DeclareChangeBlock(bool disableScroll)
{
return new ChangeBlock(this, disableScroll);
}
/// <summary>
/// <see cref="ITextRange.NotifyChanged"/>
/// </summary>
void ITextRange.NotifyChanged(bool disableScroll, bool skipEvents)
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
TextRangeBase.NotifyChanged(this, disableScroll);
}
//------------------------------------------------------
//
// ITextRange Properties
//
//------------------------------------------------------
/// <summary>
/// Set in the ctor. If true, normalization will ignore text normalization
/// (surrogates, combining marks, etc).
/// Used for fine-grained control by IMEs.
/// <see cref="ITextRange.Start"/>
/// </summary>
bool ITextRange.IgnoreTextUnitBoundaries
{
get
{
return CheckFlags(Flags.IgnoreTextUnitBoundaries);
}
}
//......................................................
//
// Boundary Positions
//
//......................................................
/// <summary>
/// <see cref="ITextRange.Start"/>
/// </summary>
ITextPointer ITextRange.Start
{
get
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
return TextRangeBase.GetStart(this);
}
}
/// <summary>
/// <see cref="ITextRange.End"/>
/// </summary>
ITextPointer ITextRange.End
{
get
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
return TextRangeBase.GetEnd(this);
}
}
/// <summary>
/// <see cref="ITextRange.IsEmpty"/>
/// </summary>
bool ITextRange.IsEmpty
{
get
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
return TextRangeBase.GetIsEmpty(this);
}
}
/// <summary>
/// <see cref="ITextRange.TextSegments"/>
/// </summary>
List<TextSegment> ITextRange.TextSegments
{
get
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
return TextRangeBase.GetTextSegments(this);
}
}
//......................................................
//
// Content - rich and plain
//
//......................................................
/// <summary>
/// <see cref="ITextRange.HasConcreteTextContainer"/>
/// </summary>
bool ITextRange.HasConcreteTextContainer
{
get
{
Invariant.Assert(_textSegments != null, "_textSegments must not be null");
Invariant.Assert(_textSegments.Count > 0, "_textSegments.Count must be > 0");
return _textSegments[0].Start is TextPointer;
}
}
/// <summary>
/// <see cref="ITextRange.Text"/>
/// </summary>
string ITextRange.Text
{
get
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
return TextRangeBase.GetText(this);
}
set
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
TextRangeBase.SetText(this, value);
}
}
/// <summary>
/// <see cref="ITextRange.Xml"/>
/// </summary>
string ITextRange.Xml
{
get
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
return TextRangeBase.GetXml(this);
}
}
/// <summary>
/// Ref count of open change blocks -- incremented/decremented
/// around BeginChange/EndChange calls.
/// <see cref="ITextRange.ChangeBlockLevel"/>
/// </summary>
int ITextRange.ChangeBlockLevel
{
get
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
return TextRangeBase.GetChangeBlockLevel(this);
}
}
//......................................................
//
// Table Selection Properties
//
//......................................................
/// <summary>
/// <see cref="ITextRange.IsTableCellRange"/>
/// </summary>
bool ITextRange.IsTableCellRange
{
get
{
// ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken
// DO NOT ANY CODE IN THIS METHOD!
return TextRangeBase.GetIsTableCellRange(this);
}
}
//------------------------------------------------------
//
// ITextRange Events
//
//------------------------------------------------------
/// <summary>
/// <see cref="ITextRange.Changed"/>
/// </summary>
event EventHandler ITextRange.Changed
{
add
{
this.Changed += value;
}
remove
{
this.Changed -= value;
}
}
/// <summary>
/// <see cref="ITextRange.FireChanged"/>
/// </summary>
void ITextRange.FireChanged()
{
if (this.Changed != null)
{
this.Changed(this, EventArgs.Empty);
}
}
//------------------------------------------------------
//
// ITextRange Private Fields Accessors
//
//------------------------------------------------------
/// <summary>
/// Defines a state of this text range
/// <see cref="ITextRange._IsTableCellRange"/>
/// </summary>
bool ITextRange._IsTableCellRange
{
get
{
return CheckFlags(Flags.IsTableCellRange);
}
set
{
SetFlags(value, Flags.IsTableCellRange);
}
}
/// <summary>
/// A collection of TextSegments. Contains at least one segment.
/// <see cref="ITextRange._TextSegments"/>
/// </summary>
List<TextSegment> ITextRange._TextSegments
{
get
{
return _textSegments;
}
set
{
_textSegments = value;
}
}
/// <summary>
/// Count of nested move sequences.
/// <see cref="ITextRange._ChangeBlockLevel"/>
/// </summary>
int ITextRange._ChangeBlockLevel
{
get
{
return _changeBlockLevel;
}
set
{
_changeBlockLevel = value;
}
}
/// <summary>
/// <see cref="ITextRange._ChangeBlockUndoRecord"/>
/// </summary>
ChangeBlockUndoRecord ITextRange._ChangeBlockUndoRecord
{
get
{
return _changeBlockUndoRecord;
}
set
{
_changeBlockUndoRecord = value;
}
}
/// <summary>
/// Set true if a Changed event is pending.
/// <see cref="ITextRange._IsChanged"/>
/// </summary>
bool ITextRange._IsChanged
{
get
{
return _IsChanged;
}
set
{
_IsChanged = value;
}
}
/// <summary>
/// ContentGeneration counter storage implementation
/// <see cref="ITextRange._ContentGeneration"/>
/// </summary>
uint ITextRange._ContentGeneration
{
get
{
return _ContentGeneration;
}
set
{
_ContentGeneration = value;
}
}
#endregion ITextRange Implementation
// *****************************************************
// *****************************************************
// *****************************************************
//
// Concrete TextRange Implementation
//
// *****************************************************
// *****************************************************
// *****************************************************
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
#region Public Methods
//......................................................
//
// Selection Building
//
//......................................................
/// <summary>
/// Determines if a TextPointer is within this TextRange.
/// </summary>
/// <param name="textPointer">
/// The TextPointer to test.
/// </param>
/// <exception cref="System.ArgumentException">
/// Throws an ArgumentException if textPointer
/// are not positioned within the same document.
/// </exception>
/// <returns>
/// Returns true if textPosition is contained within this TextRange, false
/// otherwise.
/// </returns>
/// <remarks>
/// Note TextRanges may be disjoint (for instance, when a column in a
/// Table is selected), so calling this method is not always equivalent
/// to testing for inclusion against the raw Start and End properties.
///
/// If textPointer is located at the TextRange Start or End position,
/// it is contained by the TextRange.
/// </remarks>
// This is behavior we want to eventually support:
//
// If textPointer is exactly at the edge of this TextRange (ie,
// textPointer == this.Start or textPointer == this.End),
// textPointer.LogicalDirection is used to determine whether or not
// the TextPointer is contained. If the LogicalDirection points to
// content inside the TextRange, this method returns true.
// Because of this rule, an empty TextRange will never contain
// any TextPointer.
public bool Contains(TextPointer textPointer)
{
return ((ITextRange)this).Contains(textPointer);
}
/// <summary>
/// Repositions this TextRange to cover specified content.
/// </summary>
/// <param name="position1">
/// TextPointer specifying the static end of the new TextRange.
/// </param>
/// <param name="position2">
/// TextPointer specifying the dynamic end of the new TextRange.
/// </param>
/// <exception cref="System.ArgumentException">
/// Throws an ArgumentException if position1 and position2
/// are not positioned within the same document, or if either
/// TextPointer is null.
/// </exception>
/// <remarks>
/// position1 represents the origin of the TextRange, as opposed
/// to position2, which represents the UI active end of a TextRange.
/// The distinction is important in applying selection heuristics to
/// the exact placement of the TextRange.
///
/// Note that the parameters to this method will not always match
/// the Start and End properties of the repositioned TextRange. The
/// exact positioning of a TextRange is subject to hueristics
/// specific to document types -- hueristics that match text selection
/// behavior.
/// </remarks>
public void Select(TextPointer position1, TextPointer position2)
{
((ITextRange)this).Select(position1, position2);
}
/// <summary>
/// Selects the word containing a TextPointer.
/// </summary>
/// <param name="textPointer">
/// A TextPointer containing a word to select.
/// </param>
/// <exception cref="System.ArgumentException">
/// Throws an ArgumentException if textPointer is not positioned within the
/// same document.
/// </exception>
internal void SelectWord(TextPointer textPointer)
{
((ITextRange)this).SelectWord(textPointer);
}
/// <summary>
/// Selects a paragraph around the given position.
/// </summary>
/// <param name="position">
/// A position identifying a paragraph to select.
/// </param>
internal void SelectParagraph(ITextPointer position)
{
((ITextRange)this).SelectParagraph(position);
}
//......................................................
//
// Plain Text Modification
//
//......................................................
//......................................................
//
// Rich Text Formatting
//
//......................................................
/// <summary>
/// Applies a formatting property to this TextRange.
/// </summary>
/// <param name="formattingProperty">
/// Property to apply.
/// </param>
/// <param name="value">
/// Specifies a value for the property.
/// </param>
/// <remarks>
/// This method applies the specificed property directly to the
/// TextRange's content through the application of Inline elements.
/// </remarks>
public void ApplyPropertyValue(DependencyProperty formattingProperty, object value)
{
this.ApplyPropertyValue(formattingProperty, value, /*applyToParagraphs*/false, PropertyValueAction.SetValue);
}
/// <summary>
/// Applies a formatting property to this TextRange.
/// </summary>
/// <param name="formattingProperty">
/// Property to apply.
/// </param>
/// <param name="value">
/// Specifies a value for the property.
/// </param>
/// <param name="applyToParagraphs">
/// This parameter is used to resolve the ambiguity for overlapping inherited properties
/// that apply to both inline and paragraph elements.
/// </param>
/// <remarks>
/// This method applies the specificed property directly to the
/// TextRange's content through the application of Inline elements.
/// </remarks>
internal void ApplyPropertyValue(DependencyProperty formattingProperty, object value, bool applyToParagraphs)
{
this.ApplyPropertyValue(formattingProperty, value, applyToParagraphs, PropertyValueAction.SetValue);
}
/// <summary>
/// Applies a formatting property to this TextRange.
/// </summary>
/// <param name="formattingProperty">
/// Property to apply.
/// </param>
/// <param name="value">
/// Specifies a value for the property.
/// </param>
/// <param name="applyToParagraphs">
/// This parameter is used to resolve the ambiguity for overlapping inherited properties
/// that apply to both inline and paragraph elements.
/// </param>
/// <param name="propertyValueAction">
/// Specifies how to apply the given value - use it for setting,
/// for increasing or for decreasing existing values.
/// This parameter must have PropertyValueAction.SetValue for all properties that
/// cannot be incremented or decremented by their type.
/// </param>
internal void ApplyPropertyValue(DependencyProperty formattingProperty, object value, bool applyToParagraphs, PropertyValueAction propertyValueAction)
{
Invariant.Assert(this.HasConcreteTextContainer, "Can't apply property to non-TextContainer range!");
ArgumentNullException.ThrowIfNull(formattingProperty);
if (!TextSchema.IsCharacterProperty(formattingProperty) &&
!TextSchema.IsParagraphProperty(formattingProperty))
{
#pragma warning suppress 6506 // formattingProperty is obviously not null
throw new ArgumentException(SR.Format(SR.TextEditorPropertyIsNotApplicableForTextFormatting, formattingProperty.Name));
}
// Convert property value from a string to object if needed
if ((value is string) && formattingProperty.PropertyType != typeof(string))
{
System.ComponentModel.TypeConverter typeConverter = System.ComponentModel.TypeDescriptor.GetConverter(formattingProperty.PropertyType);
Invariant.Assert(typeConverter != null);
value = typeConverter.ConvertFromString((string)value);
}
// Check if the value is appropriate for the property
if (!formattingProperty.IsValidValue(value) &&
!(formattingProperty.PropertyType == typeof(Thickness) && (value is Thickness)))
{
// We exclude checking thcickness values because we have special treatment for negative values
// in TextRangeEdit.SetParagraphProperty - negative values mean: "leave the value as is".
throw new ArgumentException(SR.Format(SR.TextEditorTypeOfParameterIsNotAppropriateForFormattingProperty, value == null ? "null" : value.GetType().Name, formattingProperty.Name), "value");
}
// Check propertyValueAction validity
if (propertyValueAction != PropertyValueAction.SetValue &&
propertyValueAction != PropertyValueAction.IncreaseByAbsoluteValue &&
propertyValueAction != PropertyValueAction.DecreaseByAbsoluteValue &&
propertyValueAction != PropertyValueAction.IncreaseByPercentageValue &&
propertyValueAction != PropertyValueAction.DecreaseByPercentageValue)
{
throw new ArgumentException(SR.TextRange_InvalidParameterValue, "propertyValueAction");
}
// Check if propertyValueAction is applicable to this property
if (propertyValueAction != PropertyValueAction.SetValue &&
!TextSchema.IsPropertyIncremental(formattingProperty))
{
throw new ArgumentException(SR.Format(SR.TextRange_PropertyCannotBeIncrementedOrDecremented, formattingProperty.Name), "propertyValueAction");
}
ApplyPropertyToTextVirtual(formattingProperty, value, applyToParagraphs, propertyValueAction);
}
/// <summary>
/// Removes all Inline formatting properties from this range.
/// Affects only Inline elements: splits the on range borders
/// and deletes all Inlines inside the range.
/// Properties set on Paragraphs and other enclosing Block elements
/// remain intact.
/// </summary>
public void ClearAllProperties()
{
Invariant.Assert(this.HasConcreteTextContainer, "Can't clear properties in non-TextContainer range");
ClearAllPropertiesVirtual();
}
/// <summary>
/// Gets the value of the given formatting property on this range.
/// </summary>
/// <param name="formattingProperty">
/// Property value to get.
/// </param>
/// <returns>
/// Value of the requested property.
/// </returns>
public object GetPropertyValue(DependencyProperty formattingProperty)
{
ArgumentNullException.ThrowIfNull(formattingProperty);
if (!TextSchema.IsCharacterProperty(formattingProperty) &&
!TextSchema.IsParagraphProperty(formattingProperty))
{
#pragma warning suppress 6506 // formattingProperty is obviously not null
throw new ArgumentException(SR.Format(SR.TextEditorPropertyIsNotApplicableForTextFormatting, formattingProperty.Name));
}
// Redirect to virtual implementation - to allow extensiblity on TextSelection level
return ((ITextRange)this).GetPropertyValue(formattingProperty);
}
/// <summary>
/// Returns a UIElement if it is selected by this range as its
/// only content. If there is no UIElement in the range or
/// if there is any other printable content (charaters, other
/// UIElements, structural boundaries crossed), the method returns
/// null.
/// </summary>
/// <returns></returns>
internal UIElement GetUIElementSelected()
{
return ((ITextRange)this).GetUIElementSelected();
}
//......................................................
//
// Data Conversion Support
//
//......................................................
/// <summary>
/// Detects whether the content of a range can be converted
/// to a requested format.
/// </summary>
/// <param name="dataFormat">
/// A string indicatinng a requested format.
/// </param>
/// <returns>
/// True if the given format is supported; false otherwise.
/// </returns>
public bool CanSave(string dataFormat)
{
return ((ITextRange)this).CanSave(dataFormat);
}
/// <summary>
/// Detects whether the content of a range can be converted
/// from a requested format.
/// </summary>
/// <param name="dataFormat">
/// A string indicatinng a requested format.
/// </param>
/// <returns>
/// True if the given format is supported; false otherwise.
/// </returns>
public bool CanLoad(string dataFormat)
{
return TextRangeBase.CanLoad(this, dataFormat);
}
/// <summary>
/// Writes the contents of the range into a stream
/// in a requested format.
/// </summary>
/// <param name="stream">
/// Writeable Stream - a destination for the serialized content.
/// Must be empty on entry. Will contain a data converted from the range
/// in a requested format.
/// After saving the stream remains opened.
/// The stream position after the saving operation
/// is undefined.
/// </param>
/// <param name="dataFormat">
/// A string denoting one of supported data conversions:
/// DataFormats.Text, DataFormats.Xaml, DataFormats.XamlPackage,
/// DataFormats.Rtf.
/// </param>
/// <remarks>
/// When dataFormat requested is not supported
/// the method will throw an exception.
/// To detect whether the given format is supported
/// call CanSave method.
/// </remarks>
public void Save(Stream stream, string dataFormat)
{
((ITextRange)this).Save(stream, dataFormat);
}
/// <summary>
/// Writes the contents of the range into a stream
/// in a requested format.
/// </summary>
/// <param name="stream">
/// Writeable Stream - a destination for the serialized content.
/// Must be empty on entry. Will contain a data converted from the range
/// in a requested format.
/// After saving the stream remains opened.
/// The stream position after the saving operation
/// is undefined.
/// </param>
/// <param name="dataFormat">
/// A string denoting one of supported data conversions:
/// DataFormats.Text, DataFormats.Xaml, DataFormats.XamlPackage,
/// DataFormats.Rtf.
/// </param>
/// <param name="preserveTextElements">
/// If TRUE, TextElements are saved as-is. If FALSE, they are upcast
/// to their base type. Non-complex custom properties are also saved if this parameter
/// is true. This parameter is only used for DataFormats.Xaml and
/// DataFormats.XamlPackage and is ignored by other formats.
/// </param>
/// <remarks>
/// When dataFormat requested is not supported
/// the method will throw an exception.
/// To detect whether the given format is supported
/// call CanSave method.
/// </remarks>
public void Save(Stream stream, string dataFormat, bool preserveTextElements)
{
((ITextRange)this).Save(stream, dataFormat, preserveTextElements);
}
/// <summary>
/// Reads the contents of the range from the stream
/// in a requested format.
/// </summary>
/// <param name="stream">
/// Readable Stream - a source for the serialized content.
/// Expected to contain data in the specified dataFormat.
/// The content will be read from the beginning of the stream
/// if the stream is Seekable (CanSeek=true),
/// otherwize the content will be read from the current
/// position of the stream.
/// After loading the stream remains opened.
/// The stream position after the loading operation
/// is undefined.
/// </param>
/// <param name="dataFormat">
/// A string denoting one of supported data conversions:
/// DataFormats.Text, DataFormats.Xaml, DataFormats.XamlPackage,
/// DataFormats.Rtf
/// </param>
/// <remarks>
/// When dataFormat requested is not supported
/// the method will throw an exception.
/// To detect whether the given format is supported
/// call CanLoad method.
/// </remarks>
public void Load(Stream stream, string dataFormat)
{
LoadVirtual(stream, dataFormat);
}
//......................................................
//
// Image support
//
//......................................................
// Implements smart insertion logic for embedded elements:
// when inserted into an empty paragraph uses BlockUIContainer wrapper,
// otherwise uses InlineUIContainer wrapper.
internal void InsertEmbeddedUIElement(FrameworkElement embeddedElement)
{
Invariant.Assert(embeddedElement != null);
this.InsertEmbeddedUIElementVirtual(embeddedElement);
}
// Inserts an image maintaining its size within a hard-coded predefined limit,
// and keeping its aspect ratio. Uses a smart insertion logic - same
// as in InsertEmbeddedUIElement method.
// This method is called from Lexicon to insert the image.
internal void InsertImage(System.Windows.Controls.Image image)
{
// The following code may change some of the properties of passed Image.
// So, we should really assert here that the image element is not parented by another tree
// and all caller code should obey that contract.
// However, for the time being this is an internal helper and only copy/paste code invokes this.
// Note that this same comment applies to InsertEmbeddedUIElement() as well.
System.Windows.Media.Imaging.BitmapSource bitmapSource = (System.Windows.Media.Imaging.BitmapSource)image.Source;
Invariant.Assert(bitmapSource != null);
const double MaxImageHeight = 300.0;
if (double.IsNaN(image.Height))
{
// Define image dimenstions maintaining its native aspect ratio,
// but not bigger than a predefined maximum.
if (bitmapSource.PixelHeight < MaxImageHeight)
{
image.Height = bitmapSource.PixelHeight;
}
else
{
image.Height = MaxImageHeight;
}
}
if (double.IsNaN(image.Width))
{
// Define image dimenstions maintaining its native aspect ratio,
// but not bigger than a predefined maximum.
if (bitmapSource.PixelHeight < MaxImageHeight)
{
image.Width = bitmapSource.PixelWidth;
}
else
{
image.Width = (MaxImageHeight / bitmapSource.PixelHeight) * bitmapSource.PixelWidth;
}
}
this.InsertEmbeddedUIElement(image);
}
//......................................................
//
// Range Serialization
//
//......................................................
// Worker for Xml property setter; enables extensibility for TextSelection
internal virtual void SetXmlVirtual(TextElement fragment)
{
if (!this.IsTableCellRange)
{
TextRangeSerialization.PasteXml(this, fragment);
}
}
// Worker for Load public method; enables extensibility for TextSelection
internal virtual void LoadVirtual(Stream stream, string dataFormat)
{
TextRangeBase.Load(this, stream, dataFormat);
}
//......................................................
//
// Table Editing
//
//......................................................
/// <summary>
/// Inserts a table with a given number of rows and columns.
/// </summary>
/// <param name="rowCount">
/// A number of rows generated in a table.
/// </param>
/// <param name="columnCount">
/// A number of columns generated in each row of a table
/// </param>
/// <returns>
/// Table element innserted.
/// </returns>
internal Table InsertTable(int rowCount, int columnCount)
{
Invariant.Assert(this.HasConcreteTextContainer, "InsertTable: TextRange must belong to non-abstract TextContainer");
return InsertTableVirtual(rowCount, columnCount);
}
/// <summary>
/// Inserts several table rows before or after the selection depending on
/// sign of rowCount parameter.
/// </summary>
/// <param name="rowCount">
/// Absolute value of a parameter specifies number of rows inserted,
/// the sign indicates before (negative) or after (positive) the range
/// new rows must be inserted.
/// </param>
/// <returns>
/// TextRange spanning all insereted rows.
/// </returns>
/// <remarks>
/// Candidate for public method - when Table editing exposed
/// </remarks>
internal TextRange InsertRows(int rowCount)
{
Invariant.Assert(this.HasConcreteTextContainer, "InsertRows: TextRange must belong to non-abstract TextContainer");
return InsertRowsVirtual(rowCount);
}
/// <summary>
/// Deletes rows identified by this range.
/// </summary>
/// <returns>
/// True if row range was selected and successfully deleted.
/// False if a source range did not contain rows; no actions done in this case.
/// </returns>
/// <remarks>
/// Candidate for public method - when Table editing exposed
/// </remarks>
internal bool DeleteRows()
{
Invariant.Assert(this.HasConcreteTextContainer, "DeleteRows: TextRange must belong to non-abstract TextContainer");
return DeleteRowsVirtual();
}
/// <summary>
/// Inserts several table columns before or after the selection depending on
/// sign of rowCount parameter.
/// </summary>
/// <param name="columnCount">
/// Absolute value of a parameter specifies number of columns inserted,
/// the sign indicates before (negative) or after (positive) the range
/// new columns must be inserted.
/// </param>
/// <returns>
/// TextRange spanning all insereted columns.
/// </returns>
/// <remarks>
/// Candidate for public method - when Table editing exposed
/// </remarks>
internal TextRange InsertColumns(int columnCount)
{
Invariant.Assert(this.HasConcreteTextContainer, "InsertColumns: TextRange must belong to non-abstract TextContainer");
return InsertColumnsVirtual(columnCount);
}
/// <summary>
/// Deletes columns identified by this range.
/// </summary>
/// <returns>
/// True if cell range was selected and columns were successfully deleted.
/// False if a source range did not contain cells; no actions done in this case.
/// </returns>
/// <remarks>
/// Candidate for public method - when Table editing exposed
/// </remarks>
internal bool DeleteColumns()
{
Invariant.Assert(this.HasConcreteTextContainer, "DeleteColumns: TextRange must belong to non-abstract TextContainer");
return DeleteColumnsVirtual();
}
/// <summary>
/// Merges all cells in a given range into one cell.
/// </summary>
/// <returns>
/// TextRange containing the resulting merged cell.
/// </returns>
/// <remarks>
/// Candidate for public method - when Table editing exposed
/// </remarks>
internal TextRange MergeCells()
{
Invariant.Assert(this.HasConcreteTextContainer, "MergeCells: TextRange must belong to non-abstract TextContainer");
return MergeCellsVirtual();
}
/// <summary>
/// Splits a merged cell in vertical and in horizontal directions
/// </summary>
/// <param name="splitCountHorizontal">
/// Number of cells created to the right of the current cell.
/// Must be less than current cell's ColumnSpan property value.
/// </param>
/// <param name="splitCountVertical">
/// Number of cells created below the current cell.
/// Must be less than current cell's RowSpan property value.
/// </param>
/// <returns>
/// Table range spanning initial cell and all split cells to the left and below it.
/// </returns>
/// <remarks>
/// Candidate for public method - when Table editing exposed
/// </remarks>
internal TextRange SplitCell(int splitCountHorizontal, int splitCountVertical)
{
Invariant.Assert(this.HasConcreteTextContainer, "SplitCells: TextRange must belong to non-abstract TextContainer");
return SplitCellVirtual(splitCountHorizontal, splitCountVertical);
}
#endregion Public Methods
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
//......................................................
//
// Boundary Positions
//
//......................................................
/// <summary>
/// TextPointer preceding all content.
/// </summary>
/// <remarks>
/// The TextPointer returned always has its IsFrozen property set true.
/// </remarks>
public TextPointer Start
{
get
{
return (TextPointer)((ITextRange)this).Start;
}
}
/// <summary>
/// TextPointer following all content.
/// </summary>
/// <remarks>
/// The TextPointer returned always has its IsFrozen property set true.
/// </remarks>
public TextPointer End
{
get
{
return (TextPointer)((ITextRange)this).End;
}
}
/// <summary>
/// Returns true if this TextRange spans no content.
/// </summary>
public bool IsEmpty
{
get
{
return ((ITextRange)this).IsEmpty;
}
}
//......................................................
//
// Content - rich and plain
//
//......................................................
internal bool HasConcreteTextContainer
{
get
{
return ((ITextRange)this).HasConcreteTextContainer;
}
}
internal FrameworkElement ContainingFrameworkElement
{
get
{
if (this.HasConcreteTextContainer)
{
return ((TextPointer)this.Start).ContainingFrameworkElement;
}
else
{
return null;
}
}
}
/// <summary>
/// Get and set the text spanned by this text range.
/// New line characters and paragraph breaks are
/// considered as equivalent from plain text perspective,
/// so all kinds of breaks are converted into new lines
/// on get, and converted into paragraph breaks
/// on set (if back-end store allows that, or
/// remain new line characters otherwise).
/// </summary>
/// <remarks>
/// The selected content is collapsed before setting text.
/// Collapse assumes mering all block elements crossed by
/// this range - from the two neighboring block the preceding
/// one survives.
/// Character formatting elements are not merged.
/// They are eliminated only if they become empty.
/// </remarks>
public string Text
{
get
{
return ((ITextRange)this).Text;
}
set
{
((ITextRange)this).Text = value;
}
}
/// <summary>
/// Returns the serialized content of this TextRange, in xml format.
/// </summary>
internal string Xml
{
get
{
return ((ITextRange)this).Xml;
}
set
{
// Note that setter for this property is not in ITextRange
// so we use virtual mechanism for extensibility in TextSelection
TextRangeBase.BeginChange(this);
try
{
// Parse the fragment into a separate subtree
object xamlObject = XamlReader.Load(new XmlTextReader(new System.IO.StringReader(value)), _useRestrictiveXamlXmlReader);
TextElement fragment = xamlObject as TextElement;
if (fragment != null)
{
this.SetXmlVirtual(fragment);
}
}
finally
{
TextRangeBase.EndChange(this);
}
}
}
//......................................................
//
// Table Selection Properties
//
//......................................................
internal bool IsTableCellRange
{
get
{
return ((ITextRange)this).IsTableCellRange;
}
}
#endregion Public Properties
#region Public Events
//------------------------------------------------------
//
// Public Events
//
//------------------------------------------------------
/// <summary>
/// The Changed event is fired when the range is repositioned
/// to cover a new span of text.
///
/// The EventHandler delegate is called with this TextRange
/// as the sender, and EventArgs.Empty as the argument.
/// </summary>
public event EventHandler Changed;
#endregion Public Events
#region Internal methods
//......................................................
//
// Change Notifications
//
//......................................................
/// <summary>
/// Begins a change block.
/// </summary>
/// <remarks>
/// This method, along with EndChange or DeclareChangeBlock, provides
/// an optional means of controlling the timing of events fired off this
/// TextRange and its underlying document.
///
/// Events are fired whenever a TextRange is moved, or its content
/// modified. Normally, this happens immediately, before the instigating
/// method (Select, set_Text, etc.) returns. This can cause trouble if
/// all event listeners are not coordinated -- in particular because
/// reentrant edits are possible a caller has no guarantees about the
/// state of the TextRange or document after making any state changes.
///
/// Calling BeginChange declares a "change block", a scope during which
/// all state changes are recorded but no events are raised. Only when
/// a matching EndChange call is made will events be raised.
///
/// The pattern becomes:
///
/// range.BeginChange();
/// try // Use a try/finally to ensure the EndChange is always called.
/// {
/// .. // Reposition the range, or modify the content.
/// }
/// finally
/// {
/// range.EndChange(); // events are raised.
/// }
///
/// Callers must take care to always match every call to BeginChange
/// with a matching EndChange, or else the TextRange will stop raising
/// events. The DeclareChangeBlock method provides a handy usage pattern
/// for C# developers taking advantage of the "using" statement.
///
/// Begin/EndChange calls are reference counted and may be nested. Only
/// when the outermost EndChange call is made will events be raised.
/// </remarks>
internal void BeginChange()
{
((ITextRange)this).BeginChange();
}
/// <summary>
/// Closes a change block.
/// </summary>
/// <remarks>
/// <see cref="TextRange.BeginChange"/>
/// Each call the BeginChange must be followed by a call to this method,
/// which raises public events for any editing operations performed
/// within the block.
/// </remarks>
internal void EndChange()
{
((ITextRange)this).EndChange();
}
/// <summary>
/// Begins a change block.
/// </summary>
/// <remarks>
/// This method is an alternative to the BeginChange/EndChange usage
/// pattern.
///
/// Calling this method is equivalent to calling the BeginChange method,
/// except additionally an IDisposable is returned. Disposing the
/// object is equivalent to calling EndChange.
///
/// This method is intended for C# users taking advantage of the "using"
/// statement. Instead of writing
///
/// range.BeginChange();
/// try // Use a try/finally to ensure the EndChange is always called.
/// {
/// .. // Reposition the range, or modify the content.
/// }
/// finally
/// {
/// range.EndChange(); // events are raised.
/// }
///
/// a more concise (and exactly equivalent)
///
/// using (new range.DeclareChangeBlock())
/// {
/// .. // Reposition the range, or modify the content.
/// } // Events are raised the Dispose takes place.
///
/// is possible.
/// </remarks>
internal IDisposable DeclareChangeBlock()
{
return ((ITextRange)this).DeclareChangeBlock();
}
/// <summary>
/// Begins a change block.
/// </summary>
/// <remarks>
/// When disableScroll == true, the caret will not automatically scroll into view.
/// </remarks>
internal IDisposable DeclareChangeBlock(bool disableScroll)
{
return ((ITextRange)this).DeclareChangeBlock(disableScroll);
}
// Set true if a Changed event is pending.
// This method only intended for use by derived classes
// (but may not be declared "protected" without public
// exposure).
internal bool _IsChanged
{
get
{
return CheckFlags(Flags.IsChanged);
}
set
{
SetFlags(value, Flags.IsChanged);
}
}
#endregion Internal methods
//------------------------------------------------------
//
// Internal Virtual Methods - TextSelection Extensibility
//
//------------------------------------------------------
#region Internal Virtual Methods
//......................................................
//
// Formatting
//
//......................................................
// Worker for AppendEmbeddedElement; enabled extensibility for TextSelection
internal virtual void InsertEmbeddedUIElementVirtual(FrameworkElement embeddedElement)
{
Invariant.Assert(this.HasConcreteTextContainer, "Can't insert embedded object to non-TextContainer range!");
Invariant.Assert(embeddedElement != null);
TextRangeBase.BeginChange(this);
try
{
// Delete existing selected content
this.Text = String.Empty;
// Calling EnsureInsertionPosition has the effect of inserting a paragraph
// before insert the embedded UIElement at BlockUIContainer or InlineUIConatiner.
TextPointer startPosition = TextRangeEditTables.EnsureInsertionPosition(this.Start);
// Choose what wrapper to use - BlockUIContainer or InlineUIContainer -
// depending on the current paragraph emptiness
Paragraph paragraph = startPosition.Paragraph;
if (paragraph != null)
{
if (Paragraph.HasNoTextContent(paragraph))
{
// Use BlockUIContainer as a replacement of the current paragraph
BlockUIContainer blockUIContainer = new BlockUIContainer(embeddedElement);
// Translate embedded element's horizontal alignment property to the BlockUIContainer's text alignment
blockUIContainer.TextAlignment = TextRangeEdit.GetTextAlignmentFromHorizontalAlignment(embeddedElement.HorizontalAlignment);
// Replace paragraph with BlockUIContainer
paragraph.SiblingBlocks.InsertAfter(paragraph, blockUIContainer);
paragraph.SiblingBlocks.Remove(paragraph);
this.Select(blockUIContainer.ContentStart, blockUIContainer.ContentEnd);
}
else
{
// Use InlineUIContainer
InlineUIContainer inlineUIContainer = new InlineUIContainer(embeddedElement);
TextPointer insertionPosition = TextRangeEdit.SplitFormattingElements(this.Start, /*keepEmptyFormatting:*/false);
insertionPosition.InsertTextElement(inlineUIContainer);
this.Select(inlineUIContainer.ElementStart, inlineUIContainer.ElementEnd);
}
}
}
finally
{
TextRangeBase.EndChange(this);
}
}
// Worker for ApplyProperty; enables extensibility for TextSelection
internal virtual void ApplyPropertyToTextVirtual(DependencyProperty formattingProperty, object value, bool applyToParagraphs, PropertyValueAction propertyValueAction)
{
TextRangeBase.BeginChange(this);
try
{
for (int i = 0; i < _textSegments.Count; i++)
{
TextSegment textSegment = _textSegments[i];
if (formattingProperty == FrameworkElement.FlowDirectionProperty)
{
// FlowDirection is an overlapping inheritable property that needs special handling.
// We apply it as a paragraph property when:
// 1. applyToParagraphs = true or
// 2. range is empty or
// 3. range crossed paragraph boundary
// Otherwise, apply as inline property.
if (applyToParagraphs || this.IsEmpty || TextRangeBase.IsParagraphBoundaryCrossed(this))
{
TextRangeEdit.SetParagraphProperty((TextPointer)textSegment.Start, (TextPointer)textSegment.End, formattingProperty, value, propertyValueAction);
}
else
{
TextRangeEdit.SetInlineProperty((TextPointer)textSegment.Start, (TextPointer)textSegment.End, formattingProperty, value, propertyValueAction);
}
}
else if (TextSchema.IsCharacterProperty(formattingProperty))
{
TextRangeEdit.SetInlineProperty((TextPointer)textSegment.Start, (TextPointer)textSegment.End, formattingProperty, value, propertyValueAction);
}
else if (TextSchema.IsParagraphProperty(formattingProperty))
{
// We must check for paragraph properties after character ones,
// to account for overlapping inheritable properties.
// Thinkness properties (Margin, Padding, BorderThickness) have special treatment
// in SetParagraphProperty method: it swaps Left and Right values for paragraphs
// with RightToLeft flow direction. So we need to set them appropriatly -
// depending on the FlowDirection of the first paragraph.
if (formattingProperty.PropertyType == typeof(Thickness) &&
(FlowDirection)textSegment.Start.GetValue(Paragraph.FlowDirectionProperty) == FlowDirection.RightToLeft)
{
value = new Thickness(
((Thickness)value).Right, ((Thickness)value).Top, ((Thickness)value).Left, ((Thickness)value).Bottom);
}
TextRangeEdit.SetParagraphProperty((TextPointer)textSegment.Start, (TextPointer)textSegment.End, formattingProperty, value, propertyValueAction);
}
}
}
finally
{
TextRangeBase.EndChange(this);
}
}
// Worker for ClearAllProperties method; enables extensibility for TextSelection
internal virtual void ClearAllPropertiesVirtual()
{
TextRangeBase.BeginChange(this);
try
{
// Clear all inline formattings
TextRangeEdit.CharacterResetFormatting((TextPointer)this.Start, (TextPointer)this.End);
}
finally
{
TextRangeBase.EndChange(this);
}
}
//------------------------------------------------------
//
// Table Editing
//
//------------------------------------------------------
// Worker for InsertTable; enables extensibility for TextSelection
internal virtual Table InsertTableVirtual(int rowCount, int columnCount)
{
TextRangeBase.BeginChange(this);
try
{
return TextRangeEditTables.InsertTable((TextPointer)this.End, rowCount, columnCount);
}
finally
{
TextRangeBase.EndChange(this);
}
}
// Worker for InsertRows; enables extensibility for TextSelection
internal virtual TextRange InsertRowsVirtual(int rowCount)
{
TextRangeBase.BeginChange(this);
try
{
return TextRangeEditTables.InsertRows(this, rowCount);
}
finally
{
TextRangeBase.EndChange(this);
}
}
// Worker for DeleteRows; enables extensibility for TextSelection
internal virtual bool DeleteRowsVirtual()
{
TextRangeBase.BeginChange(this);
try
{
return TextRangeEditTables.DeleteRows(this);
}
finally
{
TextRangeBase.EndChange(this);
}
}
// Worker for InsertColumns; enables extensibility for TextSelection
internal virtual TextRange InsertColumnsVirtual(int columnCount)
{
TextRangeBase.BeginChange(this);
try
{
return TextRangeEditTables.InsertColumns(this, columnCount);
}
finally
{
TextRangeBase.EndChange(this);
}
}
// Worker for DeleteColumns; enables extensibility for TextSelection
internal virtual bool DeleteColumnsVirtual()
{
TextRangeBase.BeginChange(this);
try
{
return TextRangeEditTables.DeleteColumns(this);
}
finally
{
TextRangeBase.EndChange(this);
}
}
// Worker for MergeCells; enables extensibility for TextSelection
internal virtual TextRange MergeCellsVirtual()
{
TextRangeBase.BeginChange(this);
try
{
return TextRangeEditTables.MergeCells(this);
}
finally
{
TextRangeBase.EndChange(this);
}
}
// Worker for SplitCells; enables extensibility for TextSelection
internal virtual TextRange SplitCellVirtual(int splitCountHorizontal, int splitCountVertical)
{
TextRangeBase.BeginChange(this);
try
{
return TextRangeEditTables.SplitCell(this, splitCountHorizontal, splitCountVertical);
}
finally
{
TextRangeBase.EndChange(this);
}
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
internal int ChangeBlockLevel
{
get
{
return _changeBlockLevel;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
// Sets boolean state.
private void SetFlags(bool value, Flags flags)
{
_flags = value ? (_flags | flags) : (_flags & (~flags));
}
// Reads boolean state.
private bool CheckFlags(Flags flags)
{
return ((_flags & flags) == flags);
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Types
//
//------------------------------------------------------
#region Private Types
private class ChangeBlock : IDisposable
{
internal ChangeBlock(ITextRange range, bool disableScroll)
{
_range = range;
_disableScroll = disableScroll;
_range.BeginChange();
}
void IDisposable.Dispose()
{
_range.EndChange(_disableScroll, false /* skipEvents */);
GC.SuppressFinalize(this);
}
private readonly ITextRange _range;
private readonly bool _disableScroll;
}
// Booleans for the _flags field.
[System.Flags]
private enum Flags
{
// True if normalization should ignore text normalization (surrogates, combining marks, etc).
// Used for fine-grained control by IMEs.
IgnoreTextUnitBoundaries = 0x1,
// True if a Changed event is pending.
IsChanged = 0x2,
// True if this range covers a TableCell.
IsTableCellRange = 0x4,
}
#endregion Private Types
#region Private Fields
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
// A collection of TextSegments. Contains at least one segment.
private List<TextSegment> _textSegments;
// Count of nested move sequences.
private int _changeBlockLevel;
// Undo unit associated with the current change block, if any.
private ChangeBlockUndoRecord _changeBlockUndoRecord;
// Generation id associated with this range. Remembers the state of TextContainer
// at the moment of the last range building/normalization
private uint _ContentGeneration;
// Boolean flags, set with Flags enum.
private Flags _flags;
// Boolean flag, set to true via constructor when you want to use the RestrictiveXamlXmlReader
private bool _useRestrictiveXamlXmlReader;
#endregion Private Fields
}
}
|