// 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(); } }