// InsertElement worker. Adds a TextElement to the tree. // If element is already in a tree, we remove it, do a deep copy of its content, // and insert that too. internal void InsertElementInternal(TextPointer startPosition, TextPointer endPosition, TextElement element) { TextTreeTextElementNode elementNode; int symbolOffset; int childSymbolCount; TextPointer startEdgePosition; TextPointer endEdgePosition; char[] elementText; ExtractChangeEventArgs extractChangeEventArgs; DependencyObject parentLogicalNode; bool newElementNode; int deltaCharCount; Invariant.Assert(!this.PlainTextOnly); Invariant.Assert(startPosition.TextContainer == this); Invariant.Assert(endPosition.TextContainer == this); DemandCreateText(); startPosition.SyncToTreeGeneration(); endPosition.SyncToTreeGeneration(); bool scopesExistingContent = startPosition.CompareTo(endPosition) != 0; BeforeAddChange(); // Remove element from any previous tree. // When called from a public method we already checked all the // illegal cases in CanInsertElementInternal. if (element.TextElementNode != null) { // This element is already in a tree. Remove it! bool sameTextContainer = (this == element.TextContainer); if (!sameTextContainer) { // This is a cross-tree extract. // We need to start a change block now, so that we can // raise a Changing event inside ExtractElementInternal // before raising the LogicalTree events below. // We'll make an EndChange call to wrap up below. element.TextContainer.BeginChange(); } bool exceptionThrown = true; try { // ExtractElementInternal will raise LogicalTree events which // could raise exceptions from external code. elementText = element.TextContainer.ExtractElementInternal(element, true /* deep */, out extractChangeEventArgs); exceptionThrown = false; } finally { if (exceptionThrown && !sameTextContainer) { // If an exception is thrown, make sure we close the // change block we opened above before unwinding. element.TextContainer.EndChange(); } } elementNode = element.TextElementNode; deltaCharCount = extractChangeEventArgs.ChildIMECharCount; if (sameTextContainer) { // Re-[....] the TextPointers in case we just extracted from this tree. startPosition.SyncToTreeGeneration(); endPosition.SyncToTreeGeneration(); // We must add the extract change now, before we move on to the insert. // (When !sameTextContainer we want to delay the notification in the extract // tree until the insert tree is in an accessible state, ie at the end of this method.) extractChangeEventArgs.AddChange(); // Don't re-raise the change below. extractChangeEventArgs = null; } newElementNode = false; } else { // Allocate a node in the tree to hold the element. elementText = null; elementNode = new TextTreeTextElementNode(); deltaCharCount = 0; newElementNode = true; extractChangeEventArgs = null; } parentLogicalNode = startPosition.GetLogicalTreeNode(); // Invalidate any TextElementCollection that depends on the parent. // Make sure we do that before raising any public events. TextElementCollectionHelper.MarkDirty(parentLogicalNode); // Link the TextElement to the TextElementNode. if (newElementNode) { elementNode.TextElement = element; element.TextElementNode = (TextTreeTextElementNode)elementNode; } // If the new element will become the parent of an old element, // the old element may become a firstIMEVisibleSibling. TextTreeTextElementNode newFirstIMEVisibleNode = null; int newFirstIMEVisibleNodeCharDelta = 0; if (scopesExistingContent) { newFirstIMEVisibleNode = startPosition.GetAdjacentTextElementNodeSibling(LogicalDirection.Forward); if (newFirstIMEVisibleNode != null) { newFirstIMEVisibleNodeCharDelta = -newFirstIMEVisibleNode.IMELeftEdgeCharCount; newFirstIMEVisibleNode.IMECharCount += newFirstIMEVisibleNodeCharDelta; } } // Attach the element node. childSymbolCount = InsertElementToSiblingTree(startPosition, endPosition, elementNode); // Add the edge char count to our delta. We couldn't get this before // because it depends on the position of the element in the tree. deltaCharCount += elementNode.IMELeftEdgeCharCount; TextTreeTextElementNode formerFirstIMEVisibleNode = null; int formerFirstIMEVisibleNodeCharDelta = 0; if (element.IsFirstIMEVisibleSibling && !scopesExistingContent) { formerFirstIMEVisibleNode = (TextTreeTextElementNode)elementNode.GetNextNode(); if (formerFirstIMEVisibleNode != null) { // The following node was the former first ime visible sibling. // It just moved, and gains an edge character. formerFirstIMEVisibleNodeCharDelta = formerFirstIMEVisibleNode.IMELeftEdgeCharCount; formerFirstIMEVisibleNode.IMECharCount += formerFirstIMEVisibleNodeCharDelta; } } // Ancester nodes gain the two edge symbols. UpdateContainerSymbolCount(elementNode.GetContainingNode(), /* symbolCount */ elementText == null ? 2 : elementText.Length, deltaCharCount + formerFirstIMEVisibleNodeCharDelta + newFirstIMEVisibleNodeCharDelta); symbolOffset = elementNode.GetSymbolOffset(this.Generation); if (newElementNode) { // Insert text to account for the element edges. TextTreeText.InsertElementEdges(_rootNode.RootTextBlock, symbolOffset, childSymbolCount); } else { // element already has an existing child, just copy over the corresponding text. TextTreeText.InsertText(_rootNode.RootTextBlock, symbolOffset, elementText); } NextGeneration(false /* deletedContent */); // Handle undo. TextTreeUndo.CreateInsertElementUndoUnit(this, symbolOffset, elementText != null /* deep */); // If we extracted the TextElement from another tree, raise that event now. // We can't raise this event any earlier, because prior to now _this_ tree // is in an invalid state and this tree could be referenced by a listener // to changes on the other tree. if (extractChangeEventArgs != null) { // Announce the extract from the old tree. // NB: we already Removed the element from the original logical tree with LogicalTreeHelper, // and did a BeginChange above. extractChangeEventArgs.AddChange(); extractChangeEventArgs.TextContainer.EndChange(); } // Raise the public event for the insert into this tree. // During document load we won't have listeners and we can save // an allocation on every insert. This can easily save 1000's of allocations during boot. if (this.HasListeners) { // startEdgePosition = new TextPointer(this, elementNode, ElementEdge.BeforeStart); if (childSymbolCount == 0 || elementText != null) { AddChange(startEdgePosition, elementText == null ? 2 : elementText.Length, deltaCharCount, PrecursorTextChangeType.ContentAdded); } else { endEdgePosition = new TextPointer(this, elementNode, ElementEdge.BeforeEnd); AddChange(startEdgePosition, endEdgePosition, elementNode.SymbolCount, elementNode.IMELeftEdgeCharCount, elementNode.IMECharCount - elementNode.IMELeftEdgeCharCount, PrecursorTextChangeType.ElementAdded, null, false); } if (formerFirstIMEVisibleNodeCharDelta != 0) { RaiseEventForFormerFirstIMEVisibleNode(formerFirstIMEVisibleNode); } if (newFirstIMEVisibleNodeCharDelta != 0) { RaiseEventForNewFirstIMEVisibleNode(newFirstIMEVisibleNode); } } // Insert the element into a Framework logical tree element.BeforeLogicalTreeChange(); try { LogicalTreeHelper.AddLogicalChild(parentLogicalNode, element); } finally { element.AfterLogicalTreeChange(); } // Reparent all children. // We only need to do this if we created a new element node. if (newElementNode) { ReparentLogicalChildren(elementNode, elementNode.TextElement, parentLogicalNode /* oldParent */); } // Notify the TextElement of a content change if it was moved to parent new content. This // can happen when Runs get merged. if (scopesExistingContent) { element.OnTextUpdated(); } }
// ExtractElement worker. Removes a TextElement from the tree. // // If deep is true, also removes any content covered by the element. // In this case element.TextTreeElementNode will be replaced with a // deep copy of all contained nodes. Since this is a copy, it can // be safely inserted into a new tree -- no positions reference it. // // deep is true when this method is called during a cross-tree insert // (that is, when a TextElement is extracted from one tree and inserted // into another via a call to InsertElement). // // If deep is true, returns the raw text corresponding to element and // its contained content. Otherwise returns null. // // If deep is true, extractChangeEventArgs will be non-null on exit, // containing all the information needed to raise a matching TextChanged // event. Otherwise, extractChangeEventArgs will be null on exit. private char[] ExtractElementInternal(TextElement element, bool deep, out ExtractChangeEventArgs extractChangeEventArgs) { TextTreeTextElementNode elementNode; SplayTreeNode containingNode; TextPointer startPosition; TextPointer endPosition; bool empty; int symbolOffset; char[] elementText; TextTreeUndoUnit undoUnit; SplayTreeNode firstContainedChildNode; SplayTreeNode lastContainedChildNode; DependencyObject oldLogicalParent; BeforeAddChange(); firstContainedChildNode = null; lastContainedChildNode = null; extractChangeEventArgs = null; elementText = null; elementNode = element.TextElementNode; containingNode = elementNode.GetContainingNode(); empty = (elementNode.ContainedNode == null); startPosition = new TextPointer(this, elementNode, ElementEdge.BeforeStart, LogicalDirection.Backward); // We only need the end position if this element originally spanned any content. endPosition = null; if (!empty) { endPosition = new TextPointer(this, elementNode, ElementEdge.AfterEnd, LogicalDirection.Backward); } symbolOffset = elementNode.GetSymbolOffset(this.Generation); // Remember the old parent oldLogicalParent = ((TextTreeNode)containingNode).GetLogicalTreeNode(); // Invalidate any TextElementCollection that depends on the parent. // Make sure we do that before raising any public events. TextElementCollectionHelper.MarkDirty(oldLogicalParent); // Remove the element from the logical tree. // NB: we do this even for a deep extract, because we can't wait -- // during a deep extract/move to new tree, the property system must be // notified before the element moves into its new tree. element.BeforeLogicalTreeChange(); try { LogicalTreeHelper.RemoveLogicalChild(oldLogicalParent, element); } finally { element.AfterLogicalTreeChange(); } // Handle undo. if (deep && !empty) { undoUnit = TextTreeUndo.CreateDeleteContentUndoUnit(this, startPosition, endPosition); } else { undoUnit = TextTreeUndo.CreateExtractElementUndoUnit(this, elementNode); } // Save the first/last contained node now -- after the ExtractElementFromSiblingTree // call it will be too late to find them. if (!deep && !empty) { firstContainedChildNode = elementNode.GetFirstContainedNode(); lastContainedChildNode = elementNode.GetLastContainedNode(); } // Record all the IME related char state before the extract. int imeCharCount = elementNode.IMECharCount; int imeLeftEdgeCharCount = elementNode.IMELeftEdgeCharCount; TextTreeTextElementNode nextNode = (empty && element.IsFirstIMEVisibleSibling) ? (TextTreeTextElementNode)elementNode.GetNextNode() : null; int nextNodeCharDelta = 0; if (nextNode != null) { // The following node is the new first ime visible sibling. // It just moved, and loses an edge character. nextNodeCharDelta = -nextNode.IMELeftEdgeCharCount; nextNode.IMECharCount += nextNodeCharDelta; } // Rip the element out of its sibling tree. // If this is a deep extract element's TextElementNode will be updated // with a deep copy of all contained nodes. ExtractElementFromSiblingTree(containingNode, elementNode, deep); // The first contained node of the extracted node may no longer // be a first sibling after the parent extract. If that's the case, // update its char count. int containedNodeCharDelta = 0; TextTreeTextElementNode firstContainedElementNode = firstContainedChildNode as TextTreeTextElementNode; if (firstContainedElementNode != null) { containedNodeCharDelta = firstContainedElementNode.IMELeftEdgeCharCount; firstContainedElementNode.IMECharCount += containedNodeCharDelta; } if (!deep) { // Unlink the TextElement from the TextElementNode. element.TextElementNode = null; // Pull out the edge symbols from the text store. TextTreeText.RemoveElementEdges(_rootNode.RootTextBlock, symbolOffset, elementNode.SymbolCount); } else { // We leave element.TextElement alone, since for a deep extract we've already // stored a copy of the original nodes there that we'll use in a following insert. // Cut and return the matching symbols. elementText = TextTreeText.CutText(_rootNode.RootTextBlock, symbolOffset, elementNode.SymbolCount); } // Ancestor nodes lose either the whole node or two element edge symbols, depending // on whether or not this is a deep extract. if (deep) { UpdateContainerSymbolCount(containingNode, -elementNode.SymbolCount, -imeCharCount + nextNodeCharDelta + containedNodeCharDelta); } else { UpdateContainerSymbolCount(containingNode, /* symbolCount */ -2, /* charCount */ -imeLeftEdgeCharCount + nextNodeCharDelta + containedNodeCharDelta); } NextGeneration(true /* deletedContent */); if (undoUnit != null) { undoUnit.SetTreeHashCode(); } // Raise the public event. if (deep) { extractChangeEventArgs = new ExtractChangeEventArgs(this, startPosition, elementNode, nextNodeCharDelta == 0 ? null : nextNode, containedNodeCharDelta == 0 ? null : firstContainedElementNode, imeCharCount, imeCharCount - imeLeftEdgeCharCount); } else if (empty) { AddChange(startPosition, /* symbolCount */ 2, /* charCount */ imeCharCount, PrecursorTextChangeType.ContentRemoved); } else { AddChange(startPosition, endPosition, elementNode.SymbolCount, imeLeftEdgeCharCount, imeCharCount - imeLeftEdgeCharCount, PrecursorTextChangeType.ElementExtracted, null, false); } // Raise events for nodes that just gained or lost an IME char due // to changes in their surroundings. if (extractChangeEventArgs == null) { if (nextNodeCharDelta != 0) { RaiseEventForNewFirstIMEVisibleNode(nextNode); } if (containedNodeCharDelta != 0) { RaiseEventForFormerFirstIMEVisibleNode(firstContainedElementNode); } } if (!deep && !empty) { ReparentLogicalChildren(firstContainedChildNode, lastContainedChildNode, oldLogicalParent /* new parent */, element /* old parent */); } // // Remove char count for logical break, since the element is leaving the tree. // For more data refer to comments of dev10 bug 703174. // if (null != element.TextElementNode) { element.TextElementNode.IMECharCount -= imeLeftEdgeCharCount; } return elementText; }