//----------------------------------------------------- // // Constructors // //----------------------------------------------------- #region Constructors /// <summary> /// Structural Cache contructor. /// </summary> /// <param name="owner">Owner of the conent</param> /// <param name="textContainer">TextContainer representing content</param> internal StructuralCache(FlowDocument owner, TextContainer textContainer) { Invariant.Assert(owner != null); Invariant.Assert(textContainer != null); Invariant.Assert(textContainer.Parent != null); _owner = owner; _textContainer = textContainer; _backgroundFormatInfo = new BackgroundFormatInfo(this); }
// Adds a TextTreeInsertElementUndoUnit to the open parent undo unit, if any. // Called from TextContainer.InsertElement. internal static void CreateInsertElementUndoUnit(TextContainer tree, int symbolOffset, bool deep) { UndoManager undoManager; undoManager = GetOrClearUndoManager(tree); if (undoManager == null) return; undoManager.Add(new TextTreeInsertElementUndoUnit(tree, symbolOffset, deep)); }
//------------------------------------------------------ // // Internal Methods // //------------------------------------------------------ #region Internal Methods // Dumps a TextContainer. internal static void Dump(TextContainer tree) { if (tree.RootNode == null) { Debug.WriteLine("<" + tree + "/>"); } else { DumpNodeRecursive(tree.RootNode, 0); } }
// Dumps a TextContainer, xaml style. internal static void DumpFlat(TextContainer tree) { if (tree.RootNode == null) { Debug.WriteLine("<" + tree + "/>"); } else { DumpFlat(tree.RootNode); } }
//----------------------------------------------------- // // Constructors // //----------------------------------------------------- #region Constructors // Creates a TextTreeRootNode instance. internal TextTreeRootNode(TextContainer tree) { _tree = tree; #if REFCOUNT_DEAD_TEXTPOINTERS _deadPositionList = new ArrayList(0); #endif // REFCOUNT_DEAD_TEXTPOINTERS // Root node has two imaginary element edges to match TextElementNode semantics. _symbolCount = 2; // CaretUnitBoundaryCache always starts unset. _caretUnitBoundaryCacheOffset = -1; }
//------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors // Creates a new undo unit instance. internal TextTreeExtractElementUndoUnit(TextContainer tree, TextTreeTextElementNode elementNode) : base(tree, elementNode.GetSymbolOffset(tree.Generation)) { _symbolCount = elementNode.SymbolCount; _type = elementNode.TextElement.GetType(); _localValues = GetPropertyRecordArray(elementNode.TextElement); _resources = elementNode.TextElement.Resources; // Table requires additional work for storing its Columns collection if (elementNode.TextElement is Table) { _columns = TextTreeDeleteContentUndoUnit.SaveColumns((Table)elementNode.TextElement); } }
//------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors // Creates a new instance. // start/end span the content to copy into the new undo unit -- they // should always share the same scoping TextElement. internal TextTreeDeleteContentUndoUnit(TextContainer tree, TextPointer start, TextPointer end) : base(tree, start.GetSymbolOffset()) { TextTreeNode node; TextTreeNode haltNode; start.DebugAssertGeneration(); end.DebugAssertGeneration(); Invariant.Assert(start.GetScopingNode() == end.GetScopingNode(), "start/end have different scope!"); node = start.GetAdjacentNode(LogicalDirection.Forward); haltNode = end.GetAdjacentNode(LogicalDirection.Forward); // Walk the content, copying runs as we go. _content = CopyContent(node, haltNode); }
// Adds a DeleteContentUndoUnit to the open parent undo unit, if any. // Called by TextContainer.DeleteContent. internal static TextTreeDeleteContentUndoUnit CreateDeleteContentUndoUnit(TextContainer tree, TextPointer start, TextPointer end) { UndoManager undoManager; TextTreeDeleteContentUndoUnit undoUnit; if (start.CompareTo(end) == 0) return null; undoManager = GetOrClearUndoManager(tree); if (undoManager == null) return null; undoUnit = new TextTreeDeleteContentUndoUnit(tree, start, end); undoManager.Add(undoUnit); return undoUnit; }
//------------------------------------------------------ // // Internal Methods // //------------------------------------------------------ #region Internal Methods // Attaches this control to a new TextContainer. internal void InitializeTextContainer(TextContainer textContainer) { Invariant.Assert(textContainer != null); Invariant.Assert(textContainer.TextSelection == null); // Uninitialize previous TextEditor if (_textContainer != null) { Invariant.Assert(_textEditor != null); Invariant.Assert(_textEditor.TextContainer == _textContainer); Invariant.Assert(_textEditor.TextContainer.TextSelection == _textEditor.Selection); // Detach existing editor from VisualTree DetachFromVisualTree(); // Discard TextEditor - must release text container _textEditor.OnDetach(); } // Save text container _textContainer = textContainer; _textContainer.Changed += new TextContainerChangedEventHandler(OnTextContainerChanged); // Create a text editor, initialize undo manager for it, and link it to text container _textEditor = new TextEditor(_textContainer, this, true); _textEditor.Selection.Changed += new EventHandler(OnSelectionChangedInternal); // Init a default undo limit. UndoManager undoManager = UndoManager.GetUndoManager(this); if (undoManager != null) { undoManager.UndoLimit = this.UndoLimit; } // Delay raising automation events until the automation subsystem is activated by a client. // ISSUE-2005/01/23-vsmirnov - Adding an event listener to AutomationProvider apparently // causes memory leaks because TextBoxBase is never released. I comment it out for now just // to fix the build break (perf DRT failure). Need to find a right fix later. // AutomationProvider.Activated += new AutomationActivatedEventHandler(OnAutomationActivated); }
/// <summary> /// Constructor /// </summary> public TextBox() : base() { // Register static editing command handlers. // This only has an effect that first time we make the call. // We don't use the static ctor because there are cases // where another control will want to alias our properties // but doesn't need this overhead. TextEditor.RegisterCommandHandlers(typeof(TextBox), /*acceptsRichContent:*/false, /*readOnly*/false, /*registerEventListeners*/false); // Create TextContainer and TextEditor associated with it TextContainer container = new TextContainer(this, true /* plainTextOnly */); container.CollectTextChanges = true; InitializeTextContainer(container); // TextBox only accepts plain text, so change TextEditor's default to that. this.TextEditor.AcceptsRichContent = false; }
// Positions this navigator at a node/edge pair. // Node/edge are adjusted based on the current gravity. private void MoveToNode(TextContainer tree, TextTreeNode node, ElementEdge edge) { RepositionForGravity(ref node, ref edge, GetGravityInternal()); _tree = tree; SetNodeAndEdge(AdjustRefCounts(node, edge, _node, this.Edge), edge); _generation = tree.PositionGeneration; }
// Does a deep extract of all top-level TextElements between two positions. // Returns the combined symbol count of all extracted elements. // Each extracted element (and its children) are moved into a private tree. // This insures that outside references to the TextElement can still use // the TextElements freely, inserting or removing content, etc. // // Also calls AddLogicalChild on any top-level UIElements encountered. private int CutTopLevelLogicalNodes(TextTreeNode containingNode, TextPointer startPosition, TextPointer endPosition, out int charCount) { SplayTreeNode node; SplayTreeNode nextNode; SplayTreeNode stopNode; TextTreeTextElementNode elementNode; TextTreeObjectNode uiElementNode; char[] elementText; int symbolCount; TextContainer tree; TextPointer newTreeStart; DependencyObject logicalParent; object currentLogicalChild; Invariant.Assert(startPosition.GetScopingNode() == endPosition.GetScopingNode(), "startPosition/endPosition not in same sibling tree!"); node = startPosition.GetAdjacentSiblingNode(LogicalDirection.Forward); stopNode = endPosition.GetAdjacentSiblingNode(LogicalDirection.Forward); symbolCount = 0; charCount = 0; logicalParent = containingNode.GetLogicalTreeNode(); while (node != stopNode) { currentLogicalChild = null; // Get the next node now, before we extract any TextElementNodes. nextNode = node.GetNextNode(); elementNode = node as TextTreeTextElementNode; if (elementNode != null) { // Grab the IMECharCount before we modify the node. // This value depends on the node's current context. int imeCharCountInOriginalContainer = elementNode.IMECharCount; // Cut and record the matching symbols. elementText = TextTreeText.CutText(_rootNode.RootTextBlock, elementNode.GetSymbolOffset(this.Generation), elementNode.SymbolCount); // Rip the element out of its sibling tree. // textElementNode.TextElement's TextElementNode will be updated // with a deep copy of all contained nodes. We need a deep copy // to ensure the new element/tree has no TextPointer references. ExtractElementFromSiblingTree(containingNode, elementNode, true /* deep */); // Assert that the TextElement now points to a new TextElementNode, not the original one. Invariant.Assert(elementNode.TextElement.TextElementNode != elementNode); // We want to start referring to the copied node, update elementNode. elementNode = elementNode.TextElement.TextElementNode; UpdateContainerSymbolCount(containingNode, -elementNode.SymbolCount, -imeCharCountInOriginalContainer); NextGeneration(true /* deletedContent */); // Stick it in a private tree so it's safe for the outside world to play with. tree = new TextContainer(null, false /* plainTextOnly */); newTreeStart = tree.Start; tree.InsertElementToSiblingTree(newTreeStart, newTreeStart, elementNode); Invariant.Assert(elementText.Length == elementNode.SymbolCount); tree.UpdateContainerSymbolCount(elementNode.GetContainingNode(), elementNode.SymbolCount, elementNode.IMECharCount); tree.DemandCreateText(); TextTreeText.InsertText(tree.RootTextBlock, 1 /* symbolOffset */, elementText); tree.NextGeneration(false /* deletedContent */); currentLogicalChild = elementNode.TextElement; // Keep a running total of how many symbols we've removed. symbolCount += elementNode.SymbolCount; charCount += imeCharCountInOriginalContainer; } else { uiElementNode = node as TextTreeObjectNode; if (uiElementNode != null) { currentLogicalChild = uiElementNode.EmbeddedElement; } } // Remove the child from the logical tree LogicalTreeHelper.RemoveLogicalChild(logicalParent, currentLogicalChild); node = nextNode; } if (symbolCount > 0) { startPosition.SyncToTreeGeneration(); endPosition.SyncToTreeGeneration(); } return symbolCount; }
//------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors // Create a new undo unit instance. internal TextTreeUndoUnit(TextContainer tree, int symbolOffset) { _tree = tree; _symbolOffset = symbolOffset; _treeContentHashCode = _tree.GetContentHashCode(); }
//------------------------------------------------------ // // Constructors // //----------------------------------------------------- #region Constructors // Creates a new instance. internal ExtractChangeEventArgs(TextContainer textTree, TextPointer startPosition, TextTreeTextElementNode node, TextTreeTextElementNode newFirstIMEVisibleNode, TextTreeTextElementNode formerFirstIMEVisibleNode, int charCount, int childCharCount) { _textTree = textTree; _startPosition = startPosition; _symbolCount = node.SymbolCount; _charCount = charCount; _childCharCount = childCharCount; _newFirstIMEVisibleNode = newFirstIMEVisibleNode; _formerFirstIMEVisibleNode = formerFirstIMEVisibleNode; }
// ------------------------------------------------------------------ // Make sure that complex content is enabled. // ------------------------------------------------------------------ private void EnsureComplexContent(ITextContainer textContainer) { if (_complexContent == null) { if (textContainer == null) { textContainer = new TextContainer(IsContentPresenterContainer ? null : this, false /* plainTextOnly */); } _complexContent = new ComplexContent(this, textContainer, false, Text); _contentCache = null; if (CheckFlags(Flags.FormattedOnce)) { // If we've already measured at least once, hook up the TextContainer // listeners now. Invariant.Assert(!CheckFlags(Flags.PendingTextContainerEventInit)); InitializeTextContainerListeners(); // Line layout data cached up to this point will become invalid // becasue of content structure change (implicit Run added). // So we need to clear the cache - we call InvalidateMeasure for this // purpose. However, we do not want to produce a side effect // of making layout invalid as a result of touching ContentStart/ContentEnd // and other properties. For that we need to UpdateLayout when it was // dirtied by our switch. bool wasLayoutValid = this.IsMeasureValid && this.IsArrangeValid; InvalidateMeasure(); InvalidateVisual(); //ensure re-rendering too if (wasLayoutValid) { UpdateLayout(); } } else { // Otherwise, wait until our first measure. // This lets us skip the work for all content invalidation // during load, before the first measure. SetFlags(true, Flags.PendingTextContainerEventInit); } } }
/// <summary> /// FlowDocument constructor with TextContainer. /// </summary> internal FlowDocument(TextContainer textContainer) : base() { Initialize(textContainer); }
// Called by the TextPointer ctor. Initializes this instance. private void Initialize(TextContainer tree, TextTreeNode node, ElementEdge edge, LogicalDirection gravity, uint generation, bool caretUnitBoundaryCache, bool isCaretUnitBoundaryCacheValid, uint layoutGeneration) { _tree = tree; // Fixup of the target node based on gravity. // Positions always cling to a node edge that matches their gravity, // so that insert ops never affect the position. RepositionForGravity(ref node, ref edge, gravity); SetNodeAndEdge(node.IncrementReferenceCount(edge), edge); _generation = generation; this.CaretUnitBoundaryCache = caretUnitBoundaryCache; this.IsCaretUnitBoundaryCacheValid = isCaretUnitBoundaryCacheValid; _layoutGeneration = layoutGeneration; VerifyFlags(); tree.AssertTree(); AssertState(); }
//------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors // Creates a new undo unit instance. // symbolOffset should be just before the start edge of the TextElement to remove. // If deep is true, this unit will undo not only the scoping element // insert, but also all content scoped by the element. internal TextTreeInsertElementUndoUnit(TextContainer tree, int symbolOffset, bool deep) : base(tree, symbolOffset) { _deep = deep; }
//------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors // Create a new undo unit instance. // symbolOffset and symbolCount track the offset of the inserted content // and its symbol count, respectively. internal TextTreeInsertUndoUnit(TextContainer tree, int symbolOffset, int symbolCount) : base(tree, symbolOffset) { Invariant.Assert(symbolCount > 0, "Creating no-op insert undo unit!"); _symbolCount = symbolCount; }
/// <summary> /// CreateTextBlock - Creates a text block, adds as visual child, databinds properties and sets up appropriate event listener. /// </summary> private void CreateTextBlock() { _textContainer = new TextContainer(this, false /* plainTextOnly */); _textBlock = new TextBlock(); AddVisualChild(_textBlock); _textBlock.IsContentPresenterContainer = true; _textBlock.SetTextContainer(_textContainer); InitializeTextContainerListener(); }
// Creates a new TextPointer instance. internal TextPointer(TextContainer textContainer, int offset, LogicalDirection direction) { SplayTreeNode node; ElementEdge edge; if (offset < 1 || offset > textContainer.InternalSymbolCount - 1) { throw new ArgumentException(SR.Get(SRID.BadDistance)); } textContainer.GetNodeAndEdgeAtOffset(offset, out node, out edge); Initialize(textContainer, (TextTreeNode)node, edge, direction, textContainer.PositionGeneration, false, false, textContainer.LayoutGeneration); }
//------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors // Create a new undo unit instance. // symbolOffset is where property values will be set. internal TextTreePropertyUndoUnit(TextContainer tree, int symbolOffset, PropertyRecord propertyRecord) : base(tree, symbolOffset) { _propertyRecord = propertyRecord; }
/// <summary> /// Initialize FlowDocument. /// </summary> /// <param name="textContainer"></param> private void Initialize(TextContainer textContainer) { if (textContainer == null) { // Create text tree that contains content of the element. textContainer = new TextContainer(this, false /* plainTextOnly */); } // Create structural cache object _structuralCache = new StructuralCache(this, textContainer); // Get rid of the current formatter. if (_formatter != null) { _formatter.Suspend(); _formatter = null; } }
// Returns the symbol offset within the TextContainer of this Position. internal static int GetSymbolOffset(TextContainer tree, TextTreeNode node, ElementEdge edge) { int offset; switch (edge) { case ElementEdge.BeforeStart: offset = node.GetSymbolOffset(tree.Generation); break; case ElementEdge.AfterStart: offset = node.GetSymbolOffset(tree.Generation) + 1; break; case ElementEdge.BeforeEnd: offset = node.GetSymbolOffset(tree.Generation) + node.SymbolCount - 1; break; case ElementEdge.AfterEnd: offset = node.GetSymbolOffset(tree.Generation) + node.SymbolCount; break; default: Invariant.Assert(false, "Unknown value for position edge"); offset = 0; break; } return offset; }
// Demand creates a TextContainer if no tree is associated with this instance. // Otherwise returns the exisiting tree, and clears the tree's DeadPositionList. private TextContainer EnsureTextContainer() { TextContainer tree; TextPointer start; if (this.IsInTree) { tree = _textElementNode.GetTextTree(); tree.EmptyDeadPositionList(); } else { tree = new TextContainer(null, false /* plainTextOnly */); start = tree.Start; tree.BeginChange(); try { tree.InsertElementInternal(start, start, this); } finally { // No event will be raised, since we know there are no listeners yet! tree.EndChange(); } Invariant.Assert(this.IsInTree); } return tree; }
// Creates a new TextPointer instance. internal TextPointer(TextContainer tree, TextTreeNode node, ElementEdge edge, LogicalDirection direction) { Initialize(tree, node, edge, direction, tree.PositionGeneration, false, false, tree.LayoutGeneration); }
// Adds a TextTreeExtractElementUndoUnit to the open parent undo unit, if any. // Called by TextContainer.ExtractElement. internal static TextTreeExtractElementUndoUnit CreateExtractElementUndoUnit(TextContainer tree, TextTreeTextElementNode elementNode) { UndoManager undoManager; TextTreeExtractElementUndoUnit undoUnit; undoManager = GetOrClearUndoManager(tree); if (undoManager == null) return null; undoUnit = new TextTreeExtractElementUndoUnit(tree, elementNode); undoManager.Add(undoUnit); return undoUnit; }
internal static int GetTextInRun(TextContainer textContainer, int symbolOffset, TextTreeTextNode textNode, int nodeOffset, LogicalDirection direction, char[] textBuffer, int startIndex, int count) { int skipCount; int finalCount; if (textBuffer == null) { throw new ArgumentNullException("textBuffer"); } if (startIndex < 0) { throw new ArgumentException(SR.Get(SRID.NegativeValue, "startIndex")); } if (startIndex > textBuffer.Length) { throw new ArgumentException(SR.Get(SRID.StartIndexExceedsBufferSize, startIndex, textBuffer.Length)); } if (count < 0) { throw new ArgumentException(SR.Get(SRID.NegativeValue, "count")); } if (count > textBuffer.Length - startIndex) { throw new ArgumentException(SR.Get(SRID.MaxLengthExceedsBufferSize, count, textBuffer.Length, startIndex)); } Invariant.Assert(textNode != null, "textNode is expected to be non-null"); textContainer.EmptyDeadPositionList(); if (nodeOffset < 0) { skipCount = 0; } else { skipCount = (direction == LogicalDirection.Forward) ? nodeOffset : textNode.SymbolCount - nodeOffset; symbolOffset += nodeOffset; } finalCount = 0; // Loop and combine adjacent text nodes into a single run. // This isn't just a perf optimization. Because text positions // split text nodes, if we just returned a single node's text // callers would see strange side effects where position.GetTextLength() != // position.GetText() if another position is moved between the calls. while (textNode != null) { // Never return more textBuffer than the text following this position in the current text node. finalCount += Math.Min(count - finalCount, textNode.SymbolCount - skipCount); skipCount = 0; if (finalCount == count) break; textNode = ((direction == LogicalDirection.Forward) ? textNode.GetNextNode() : textNode.GetPreviousNode()) as TextTreeTextNode; } // If we're reading backwards, need to fixup symbolOffset to point into the node. if (direction == LogicalDirection.Backward) { symbolOffset -= finalCount; } if (finalCount > 0) // We may not have allocated textContainer.RootTextBlock if no text was ever inserted. { TextTreeText.ReadText(textContainer.RootTextBlock, symbolOffset, finalCount, textBuffer, startIndex); } return finalCount; }