diff --git a/SharpTreeView/FlatListTreeNode.cs b/SharpTreeView/FlatListTreeNode.cs index 86a982c7a..809e81742 100644 --- a/SharpTreeView/FlatListTreeNode.cs +++ b/SharpTreeView/FlatListTreeNode.cs @@ -2,6 +2,7 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Collections.Generic; using System.Diagnostics; namespace ICSharpCode.TreeView @@ -32,6 +33,21 @@ namespace ICSharpCode.TreeView return node != null ? node.height : 0; } + SharpTreeNode GetListRoot() + { + SharpTreeNode node = this; + while (node.listParent != null) + node = node.listParent; + return node; + } + + #region Debugging + [Conditional("DEBUG")] + void CheckRootInvariants() + { + GetListRoot().CheckInvariants(); + } + [Conditional("DEBUG")] void CheckInvariants() { @@ -44,13 +60,26 @@ namespace ICSharpCode.TreeView if (right != null) right.CheckInvariants(); } - SharpTreeNode GetListRoot() + [Conditional("DEBUG")] + static void DumpTree(SharpTreeNode node) { - SharpTreeNode node = this; - while (node.listParent != null) - node = node.listParent; - return node; + node.GetListRoot().DumpTree(); + } + + [Conditional("DEBUG")] + void DumpTree() + { + Debug.Indent(); + if (left != null) + left.DumpTree(); + Debug.Unindent(); + Debug.WriteLine("{0}, totalListLength={1}, height={2}, Balance={3}, isVisible={4}", ToString(), totalListLength, height, Balance, isVisible); + Debug.Indent(); + if (right != null) + right.DumpTree(); + Debug.Unindent(); } + #endregion #region GetNodeByVisibleIndex / GetVisibleIndexForNode internal static SharpTreeNode GetNodeByVisibleIndex(SharpTreeNode root, int index) @@ -189,6 +218,28 @@ namespace ICSharpCode.TreeView newTop.right = Rebalance(this); return newTop; } + + static void RebalanceUntilRoot(SharpTreeNode pos) + { + while (pos.listParent != null) { + if (pos == pos.listParent.left) { + pos = pos.listParent.left = Rebalance(pos); + } else { + Debug.Assert(pos == pos.listParent.right); + pos = pos.listParent.right = Rebalance(pos); + } + pos = pos.listParent; + } + SharpTreeNode newRoot = Rebalance(pos); + if (newRoot != pos && pos.treeFlattener != null) { + Debug.Assert(newRoot.treeFlattener == null); + newRoot.treeFlattener = pos.treeFlattener; + pos.treeFlattener = null; + newRoot.treeFlattener.root = newRoot; + } + Debug.Assert(newRoot.listParent == null); + newRoot.CheckInvariants(); + } #endregion #region Insertion @@ -210,47 +261,116 @@ namespace ICSharpCode.TreeView } RebalanceUntilRoot(pos); } + #endregion - static void RebalanceUntilRoot(SharpTreeNode pos) + #region Removal + void RemoveNodes(SharpTreeNode start, SharpTreeNode end) { - while (pos.listParent != null) { - if (pos == pos.listParent.left) { - pos = pos.listParent.left = Rebalance(pos); - } else { - Debug.Assert(pos == pos.listParent.right); - pos = pos.listParent.right = Rebalance(pos); + // Removes all nodes from start to end (inclusive) + // All removed nodes will be reorganized in a separate tree, do not delete + // regions that don't belong together in the tree model! + + List removedSubtrees = new List(); + SharpTreeNode oldPos; + SharpTreeNode pos = start; + do { + // recalculate the endAncestors every time, because the tree might have been rebalanced + HashSet endAncestors = new HashSet(); + for (SharpTreeNode tmp = end; tmp != null; tmp = tmp.listParent) + endAncestors.Add(tmp); + + removedSubtrees.Add(pos); + if (!endAncestors.Contains(pos)) { + // we can remove pos' right subtree in a single step: + if (pos.right != null) { + removedSubtrees.Add(pos.right); + pos.right.listParent = null; + pos.right = null; + } } - pos = pos.listParent; - } - SharpTreeNode newRoot = Rebalance(pos); - if (newRoot != pos && pos.treeFlattener != null) { - Debug.Assert(newRoot.treeFlattener == null); - newRoot.treeFlattener = pos.treeFlattener; - pos.treeFlattener = null; - newRoot.treeFlattener.root = newRoot; - } - Debug.Assert(newRoot.listParent == null); - newRoot.CheckInvariants(); + DeleteNode(pos); // this will also rebalance out the deletion of the right subtree + + oldPos = pos; + pos = pos.Successor(); + } while (oldPos != end); + + // merge back together the removed subtrees: + } - [Conditional("DEBUG")] - static void DumpTree(SharpTreeNode node) + SharpTreeNode Successor() { - node.GetListRoot().DumpTree(); + SharpTreeNode node = this; + SharpTreeNode oldNode; + do { + oldNode = node; + node = node.listParent; + // loop while we are on the way up from the right part + } while (node != null && node.right == oldNode); + return node; } - [Conditional("DEBUG")] - void DumpTree() + static void DeleteNode(SharpTreeNode node) { - Debug.Indent(); - if (left != null) - left.DumpTree(); - Debug.Unindent(); - Debug.WriteLine("{0}, totalListLength={1}, height={2}, Balance={3}, isVisible={4}", ToString(), totalListLength, height, Balance, isVisible); - Debug.Indent(); - if (right != null) - right.DumpTree(); - Debug.Unindent(); + SharpTreeNode balancingNode; + if (node.left == null) { + balancingNode = node.listParent; + node.ReplaceWith(node.right); + node.right = null; + } else if (node.right == null) { + balancingNode = node.listParent; + node.ReplaceWith(node.left); + node.left = null; + } else { + SharpTreeNode tmp = node.right; + while (tmp.left != null) + tmp = tmp.left; + // First replace tmp with tmp.right + balancingNode = tmp.listParent; + tmp.ReplaceWith(tmp.right); + tmp.right = null; + Debug.Assert(tmp.left == null); + Debug.Assert(tmp.listParent == null); + // Now move node's children to tmp: + tmp.left = node.left; node.left = null; + tmp.right = node.right; node.right = null; + if (tmp.left != null) tmp.left.listParent = tmp; + if (tmp.right != null) tmp.right.listParent = tmp; + // Then replace node with tmp + node.ReplaceWith(tmp); + if (balancingNode == node) + balancingNode = tmp; + } + Debug.Assert(node.listParent == null); + Debug.Assert(node.left == null); + Debug.Assert(node.right == null); + if (balancingNode != null) + RebalanceUntilRoot(balancingNode); + } + + void ReplaceWith(SharpTreeNode node) + { + if (listParent != null) { + if (listParent.left == this) { + listParent.left = node; + } else { + Debug.Assert(listParent.right == this); + listParent.right = node; + } + if (node != null) + node.listParent = listParent; + listParent = null; + } else { + // this was a root node + Debug.Assert(node != null); // cannot delete the only node in the tree + node.listParent = null; + if (treeFlattener != null) { + Debug.Assert(node.treeFlattener == null); + node.treeFlattener = this.treeFlattener; + this.treeFlattener = null; + node.treeFlattener.root = node; + } + } } #endregion } diff --git a/SharpTreeView/SharpTreeNode.cs b/SharpTreeView/SharpTreeNode.cs index 1c2a5ed75..266fb011c 100644 --- a/SharpTreeView/SharpTreeNode.cs +++ b/SharpTreeView/SharpTreeNode.cs @@ -42,7 +42,7 @@ namespace ICSharpCode.TreeView // Validate our invariants: if (updateFlattener) - GetListRoot().CheckInvariants(); + CheckRootInvariants(); // Tell the flattener about the removed nodes: if (removedNodes != null) { @@ -154,9 +154,28 @@ namespace ICSharpCode.TreeView { if (e.OldItems != null) { foreach (SharpTreeNode node in e.OldItems) { - throw new NotImplementedException(); Debug.Assert(node.modelParent == this); node.modelParent = null; + Debug.WriteLine("Removing {0} from {1}", node, this); + SharpTreeNode removeEnd = node; + while (removeEnd.modelChildren != null && removeEnd.modelChildren.Count > 0) + removeEnd = removeEnd.modelChildren.Last(); + + List removedNodes = null; + int visibleIndexOfRemoval = 0; + if (node.isVisible) { + visibleIndexOfRemoval = GetVisibleIndexForNode(node); + removedNodes = node.VisibleDescendantsAndSelf().ToList(); + } + + RemoveNodes(node, removeEnd); + + if (removedNodes != null) { + var flattener = GetListRoot().treeFlattener; + if (flattener != null) { + flattener.NodesRemoved(visibleIndexOfRemoval, removedNodes); + } + } } } if (e.NewItems != null) { @@ -165,6 +184,7 @@ namespace ICSharpCode.TreeView insertionPos = null; else insertionPos = modelChildren[e.NewStartingIndex - 1]; + foreach (SharpTreeNode node in e.NewItems) { Debug.Assert(node.modelParent == null); node.modelParent = this; diff --git a/SharpTreeView/SharpTreeNodeCollection.cs b/SharpTreeView/SharpTreeNodeCollection.cs index 2d7620216..f598a0f2b 100644 --- a/SharpTreeView/SharpTreeNodeCollection.cs +++ b/SharpTreeView/SharpTreeNodeCollection.cs @@ -93,6 +93,19 @@ namespace ICSharpCode.TreeView OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, node, index)); } + public void InsertRange(int index, IEnumerable nodes) + { + if (nodes == null) + throw new ArgumentNullException("nodes"); + ThrowOnReentrancy(); + List newNodes = nodes.ToList(); + foreach (SharpTreeNode node in newNodes) { + ThrowIfValueIsNullOrHasParent(node); + } + list.InsertRange(index, newNodes); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newNodes, index)); + } + public void RemoveAt(int index) { ThrowOnReentrancy(); @@ -101,6 +114,14 @@ namespace ICSharpCode.TreeView OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItem, index)); } + public void RemoveRange(int index, int count) + { + ThrowOnReentrancy(); + var oldItems = list.GetRange(index, count); + list.RemoveRange(index, count); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, index)); + } + public void Add(SharpTreeNode node) { ThrowOnReentrancy(); @@ -109,6 +130,11 @@ namespace ICSharpCode.TreeView OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, node, list.Count - 1)); } + public void AddRange(IEnumerable nodes) + { + InsertRange(this.Count, nodes); + } + public void Clear() { ThrowOnReentrancy(); @@ -147,5 +173,30 @@ namespace ICSharpCode.TreeView { return list.GetEnumerator(); } + + public void BindToObservableCollection(ObservableCollection collection) where T : SharpTreeNode + { + Clear(); + AddRange(collection); + collection.CollectionChanged += delegate(object sender, NotifyCollectionChangedEventArgs e) { + switch (e.Action) { + case NotifyCollectionChangedAction.Add: + InsertRange(e.NewStartingIndex, e.NewItems.Cast()); + break; + case NotifyCollectionChangedAction.Remove: + RemoveRange(e.OldStartingIndex, e.OldItems.Count); + break; + case NotifyCollectionChangedAction.Replace: + case NotifyCollectionChangedAction.Move: + throw new NotImplementedException(); + case NotifyCollectionChangedAction.Reset: + Clear(); + AddRange(collection); + break; + default: + throw new NotSupportedException("Invalid value for NotifyCollectionChangedAction"); + } + }; + } } }