/SharpTreeView/FlatListTreeNode.cs
http://github.com/icsharpcode/ILSpy · C# · 414 lines · 317 code · 31 blank · 66 comment · 131 complexity · 03531c30cf84d77221747bdf865c21c1 MD5 · raw file
- // Copyright (c) 2020 AlphaSierraPapa for the SharpDevelop Team
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this
- // software and associated documentation files (the "Software"), to deal in the Software
- // without restriction, including without limitation the rights to use, copy, modify, merge,
- // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
- // to whom the Software is furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in all copies or
- // substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
- // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
- // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
- // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- // DEALINGS IN THE SOFTWARE.
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- namespace ICSharpCode.TreeView
- {
- // This part of SharpTreeNode controls the 'flat list' data structure, which emulates
- // a big flat list containing the whole tree; allowing access by visible index.
- partial class SharpTreeNode
- {
- /// <summary>The parent in the flat list</summary>
- internal SharpTreeNode listParent;
- /// <summary>Left/right nodes in the flat list</summary>
- SharpTreeNode left, right;
-
- internal TreeFlattener treeFlattener;
-
- /// <summary>Subtree height in the flat list tree</summary>
- byte height = 1;
-
- /// <summary>Length in the flat list, including children (children within the flat list). -1 = invalidated</summary>
- int totalListLength = -1;
-
- int Balance {
- get { return Height(right) - Height(left); }
- }
-
- static int Height(SharpTreeNode node)
- {
- return node != null ? node.height : 0;
- }
-
- internal SharpTreeNode GetListRoot()
- {
- SharpTreeNode node = this;
- while (node.listParent != null)
- node = node.listParent;
- return node;
- }
-
- #region Debugging
- [Conditional("DEBUG")]
- void CheckRootInvariants()
- {
- GetListRoot().CheckInvariants();
- }
-
- [Conditional("DATACONSISTENCYCHECK")]
- void CheckInvariants()
- {
- Debug.Assert(left == null || left.listParent == this);
- Debug.Assert(right == null || right.listParent == this);
- Debug.Assert(height == 1 + Math.Max(Height(left), Height(right)));
- Debug.Assert(Math.Abs(this.Balance) <= 1);
- Debug.Assert(totalListLength == -1 || totalListLength == (left != null ? left.totalListLength : 0) + (isVisible ? 1 : 0) + (right != null ? right.totalListLength : 0));
- if (left != null) left.CheckInvariants();
- if (right != null) right.CheckInvariants();
- }
-
- [Conditional("DEBUG")]
- static void DumpTree(SharpTreeNode 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)
- {
- root.GetTotalListLength(); // ensure all list lengths are calculated
- Debug.Assert(index >= 0);
- Debug.Assert(index < root.totalListLength);
- SharpTreeNode node = root;
- while (true) {
- if (node.left != null && index < node.left.totalListLength) {
- node = node.left;
- } else {
- if (node.left != null) {
- index -= node.left.totalListLength;
- }
- if (node.isVisible) {
- if (index == 0)
- return node;
- index--;
- }
- node = node.right;
- }
- }
- }
-
- internal static int GetVisibleIndexForNode(SharpTreeNode node)
- {
- int index = node.left != null ? node.left.GetTotalListLength() : 0;
- while (node.listParent != null) {
- if (node == node.listParent.right) {
- if (node.listParent.left != null)
- index += node.listParent.left.GetTotalListLength();
- if (node.listParent.isVisible)
- index++;
- }
- node = node.listParent;
- }
- return index;
- }
- #endregion
-
- #region Balancing
- /// <summary>
- /// Balances the subtree rooted in <paramref name="node"/> and recomputes the 'height' field.
- /// This method assumes that the children of this node are already balanced and have an up-to-date 'height' value.
- /// </summary>
- /// <returns>The new root node</returns>
- static SharpTreeNode Rebalance(SharpTreeNode node)
- {
- Debug.Assert(node.left == null || Math.Abs(node.left.Balance) <= 1);
- Debug.Assert(node.right == null || Math.Abs(node.right.Balance) <= 1);
- // Keep looping until it's balanced. Not sure if this is stricly required; this is based on
- // the Rope code where node merging made this necessary.
- while (Math.Abs(node.Balance) > 1) {
- // AVL balancing
- // note: because we don't care about the identity of concat nodes, this works a little different than usual
- // tree rotations: in our implementation, the "this" node will stay at the top, only its children are rearranged
- if (node.Balance > 1) {
- if (node.right.Balance < 0) {
- node.right = node.right.RotateRight();
- }
- node = node.RotateLeft();
- // If 'node' was unbalanced by more than 2, we've shifted some of the inbalance to the left node; so rebalance that.
- node.left = Rebalance(node.left);
- } else if (node.Balance < -1) {
- if (node.left.Balance > 0) {
- node.left = node.left.RotateLeft();
- }
- node = node.RotateRight();
- // If 'node' was unbalanced by more than 2, we've shifted some of the inbalance to the right node; so rebalance that.
- node.right = Rebalance(node.right);
- }
- }
- Debug.Assert(Math.Abs(node.Balance) <= 1);
- node.height = (byte)(1 + Math.Max(Height(node.left), Height(node.right)));
- node.totalListLength = -1; // mark for recalculation
- // since balancing checks the whole tree up to the root, the whole path will get marked as invalid
- return node;
- }
-
- internal int GetTotalListLength()
- {
- if (totalListLength >= 0)
- return totalListLength;
- int length = (isVisible ? 1 : 0);
- if (left != null) {
- length += left.GetTotalListLength();
- }
- if (right != null) {
- length += right.GetTotalListLength();
- }
- return totalListLength = length;
- }
-
- SharpTreeNode RotateLeft()
- {
- /* Rotate tree to the left
- *
- * this right
- * / \ / \
- * A right ===> this C
- * / \ / \
- * B C A B
- */
- SharpTreeNode b = right.left;
- SharpTreeNode newTop = right;
-
- if (b != null) b.listParent = this;
- this.right = b;
- newTop.left = this;
- newTop.listParent = this.listParent;
- this.listParent = newTop;
- // rebalance the 'this' node - this is necessary in some bulk insertion cases:
- newTop.left = Rebalance(this);
- return newTop;
- }
-
- SharpTreeNode RotateRight()
- {
- /* Rotate tree to the right
- *
- * this left
- * / \ / \
- * left C ===> A this
- * / \ / \
- * A B B C
- */
- SharpTreeNode b = left.right;
- SharpTreeNode newTop = left;
-
- if (b != null) b.listParent = this;
- this.left = b;
- newTop.right = this;
- newTop.listParent = this.listParent;
- this.listParent = newTop;
- 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
- static void InsertNodeAfter(SharpTreeNode pos, SharpTreeNode newNode)
- {
- // newNode might be the model root of a whole subtree, so go to the list root of that subtree:
- newNode = newNode.GetListRoot();
- if (pos.right == null) {
- pos.right = newNode;
- newNode.listParent = pos;
- } else {
- // insert before pos.right's leftmost:
- pos = pos.right;
- while (pos.left != null)
- pos = pos.left;
- Debug.Assert(pos.left == null);
- pos.left = newNode;
- newNode.listParent = pos;
- }
- RebalanceUntilRoot(pos);
- }
- #endregion
-
- #region Removal
- void RemoveNodes(SharpTreeNode start, SharpTreeNode end)
- {
- // 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<SharpTreeNode> removedSubtrees = new List<SharpTreeNode>();
- SharpTreeNode oldPos;
- SharpTreeNode pos = start;
- do {
- // recalculate the endAncestors every time, because the tree might have been rebalanced
- HashSet<SharpTreeNode> endAncestors = new HashSet<SharpTreeNode>();
- 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;
- }
- }
- SharpTreeNode succ = pos.Successor();
- DeleteNode(pos); // this will also rebalance out the deletion of the right subtree
-
- oldPos = pos;
- pos = succ;
- } while (oldPos != end);
-
- // merge back together the removed subtrees:
- SharpTreeNode removed = removedSubtrees[0];
- for (int i = 1; i < removedSubtrees.Count; i++) {
- removed = ConcatTrees(removed, removedSubtrees[i]);
- }
- }
-
- static SharpTreeNode ConcatTrees(SharpTreeNode first, SharpTreeNode second)
- {
- SharpTreeNode tmp = first;
- while (tmp.right != null)
- tmp = tmp.right;
- InsertNodeAfter(tmp, second);
- return tmp.GetListRoot();
- }
-
- SharpTreeNode Successor()
- {
- if (right != null) {
- SharpTreeNode node = right;
- while (node.left != null)
- node = node.left;
- return node;
- } else {
- 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;
- }
- }
-
- static void DeleteNode(SharpTreeNode node)
- {
- 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);
- node.height = 1;
- node.totalListLength = -1;
- 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
- }
- }