/SharpTreeView/FlatListTreeNode.cs

http://github.com/icsharpcode/ILSpy · C# · 414 lines · 317 code · 31 blank · 66 comment · 131 complexity · 03531c30cf84d77221747bdf865c21c1 MD5 · raw file

  1. // Copyright (c) 2020 AlphaSierraPapa for the SharpDevelop Team
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Diagnostics;
  21. namespace ICSharpCode.TreeView
  22. {
  23. // This part of SharpTreeNode controls the 'flat list' data structure, which emulates
  24. // a big flat list containing the whole tree; allowing access by visible index.
  25. partial class SharpTreeNode
  26. {
  27. /// <summary>The parent in the flat list</summary>
  28. internal SharpTreeNode listParent;
  29. /// <summary>Left/right nodes in the flat list</summary>
  30. SharpTreeNode left, right;
  31. internal TreeFlattener treeFlattener;
  32. /// <summary>Subtree height in the flat list tree</summary>
  33. byte height = 1;
  34. /// <summary>Length in the flat list, including children (children within the flat list). -1 = invalidated</summary>
  35. int totalListLength = -1;
  36. int Balance {
  37. get { return Height(right) - Height(left); }
  38. }
  39. static int Height(SharpTreeNode node)
  40. {
  41. return node != null ? node.height : 0;
  42. }
  43. internal SharpTreeNode GetListRoot()
  44. {
  45. SharpTreeNode node = this;
  46. while (node.listParent != null)
  47. node = node.listParent;
  48. return node;
  49. }
  50. #region Debugging
  51. [Conditional("DEBUG")]
  52. void CheckRootInvariants()
  53. {
  54. GetListRoot().CheckInvariants();
  55. }
  56. [Conditional("DATACONSISTENCYCHECK")]
  57. void CheckInvariants()
  58. {
  59. Debug.Assert(left == null || left.listParent == this);
  60. Debug.Assert(right == null || right.listParent == this);
  61. Debug.Assert(height == 1 + Math.Max(Height(left), Height(right)));
  62. Debug.Assert(Math.Abs(this.Balance) <= 1);
  63. Debug.Assert(totalListLength == -1 || totalListLength == (left != null ? left.totalListLength : 0) + (isVisible ? 1 : 0) + (right != null ? right.totalListLength : 0));
  64. if (left != null) left.CheckInvariants();
  65. if (right != null) right.CheckInvariants();
  66. }
  67. [Conditional("DEBUG")]
  68. static void DumpTree(SharpTreeNode node)
  69. {
  70. node.GetListRoot().DumpTree();
  71. }
  72. [Conditional("DEBUG")]
  73. void DumpTree()
  74. {
  75. Debug.Indent();
  76. if (left != null)
  77. left.DumpTree();
  78. Debug.Unindent();
  79. Debug.WriteLine("{0}, totalListLength={1}, height={2}, Balance={3}, isVisible={4}", ToString(), totalListLength, height, Balance, isVisible);
  80. Debug.Indent();
  81. if (right != null)
  82. right.DumpTree();
  83. Debug.Unindent();
  84. }
  85. #endregion
  86. #region GetNodeByVisibleIndex / GetVisibleIndexForNode
  87. internal static SharpTreeNode GetNodeByVisibleIndex(SharpTreeNode root, int index)
  88. {
  89. root.GetTotalListLength(); // ensure all list lengths are calculated
  90. Debug.Assert(index >= 0);
  91. Debug.Assert(index < root.totalListLength);
  92. SharpTreeNode node = root;
  93. while (true) {
  94. if (node.left != null && index < node.left.totalListLength) {
  95. node = node.left;
  96. } else {
  97. if (node.left != null) {
  98. index -= node.left.totalListLength;
  99. }
  100. if (node.isVisible) {
  101. if (index == 0)
  102. return node;
  103. index--;
  104. }
  105. node = node.right;
  106. }
  107. }
  108. }
  109. internal static int GetVisibleIndexForNode(SharpTreeNode node)
  110. {
  111. int index = node.left != null ? node.left.GetTotalListLength() : 0;
  112. while (node.listParent != null) {
  113. if (node == node.listParent.right) {
  114. if (node.listParent.left != null)
  115. index += node.listParent.left.GetTotalListLength();
  116. if (node.listParent.isVisible)
  117. index++;
  118. }
  119. node = node.listParent;
  120. }
  121. return index;
  122. }
  123. #endregion
  124. #region Balancing
  125. /// <summary>
  126. /// Balances the subtree rooted in <paramref name="node"/> and recomputes the 'height' field.
  127. /// This method assumes that the children of this node are already balanced and have an up-to-date 'height' value.
  128. /// </summary>
  129. /// <returns>The new root node</returns>
  130. static SharpTreeNode Rebalance(SharpTreeNode node)
  131. {
  132. Debug.Assert(node.left == null || Math.Abs(node.left.Balance) <= 1);
  133. Debug.Assert(node.right == null || Math.Abs(node.right.Balance) <= 1);
  134. // Keep looping until it's balanced. Not sure if this is stricly required; this is based on
  135. // the Rope code where node merging made this necessary.
  136. while (Math.Abs(node.Balance) > 1) {
  137. // AVL balancing
  138. // note: because we don't care about the identity of concat nodes, this works a little different than usual
  139. // tree rotations: in our implementation, the "this" node will stay at the top, only its children are rearranged
  140. if (node.Balance > 1) {
  141. if (node.right.Balance < 0) {
  142. node.right = node.right.RotateRight();
  143. }
  144. node = node.RotateLeft();
  145. // If 'node' was unbalanced by more than 2, we've shifted some of the inbalance to the left node; so rebalance that.
  146. node.left = Rebalance(node.left);
  147. } else if (node.Balance < -1) {
  148. if (node.left.Balance > 0) {
  149. node.left = node.left.RotateLeft();
  150. }
  151. node = node.RotateRight();
  152. // If 'node' was unbalanced by more than 2, we've shifted some of the inbalance to the right node; so rebalance that.
  153. node.right = Rebalance(node.right);
  154. }
  155. }
  156. Debug.Assert(Math.Abs(node.Balance) <= 1);
  157. node.height = (byte)(1 + Math.Max(Height(node.left), Height(node.right)));
  158. node.totalListLength = -1; // mark for recalculation
  159. // since balancing checks the whole tree up to the root, the whole path will get marked as invalid
  160. return node;
  161. }
  162. internal int GetTotalListLength()
  163. {
  164. if (totalListLength >= 0)
  165. return totalListLength;
  166. int length = (isVisible ? 1 : 0);
  167. if (left != null) {
  168. length += left.GetTotalListLength();
  169. }
  170. if (right != null) {
  171. length += right.GetTotalListLength();
  172. }
  173. return totalListLength = length;
  174. }
  175. SharpTreeNode RotateLeft()
  176. {
  177. /* Rotate tree to the left
  178. *
  179. * this right
  180. * / \ / \
  181. * A right ===> this C
  182. * / \ / \
  183. * B C A B
  184. */
  185. SharpTreeNode b = right.left;
  186. SharpTreeNode newTop = right;
  187. if (b != null) b.listParent = this;
  188. this.right = b;
  189. newTop.left = this;
  190. newTop.listParent = this.listParent;
  191. this.listParent = newTop;
  192. // rebalance the 'this' node - this is necessary in some bulk insertion cases:
  193. newTop.left = Rebalance(this);
  194. return newTop;
  195. }
  196. SharpTreeNode RotateRight()
  197. {
  198. /* Rotate tree to the right
  199. *
  200. * this left
  201. * / \ / \
  202. * left C ===> A this
  203. * / \ / \
  204. * A B B C
  205. */
  206. SharpTreeNode b = left.right;
  207. SharpTreeNode newTop = left;
  208. if (b != null) b.listParent = this;
  209. this.left = b;
  210. newTop.right = this;
  211. newTop.listParent = this.listParent;
  212. this.listParent = newTop;
  213. newTop.right = Rebalance(this);
  214. return newTop;
  215. }
  216. static void RebalanceUntilRoot(SharpTreeNode pos)
  217. {
  218. while (pos.listParent != null) {
  219. if (pos == pos.listParent.left) {
  220. pos = pos.listParent.left = Rebalance(pos);
  221. } else {
  222. Debug.Assert(pos == pos.listParent.right);
  223. pos = pos.listParent.right = Rebalance(pos);
  224. }
  225. pos = pos.listParent;
  226. }
  227. SharpTreeNode newRoot = Rebalance(pos);
  228. if (newRoot != pos && pos.treeFlattener != null) {
  229. Debug.Assert(newRoot.treeFlattener == null);
  230. newRoot.treeFlattener = pos.treeFlattener;
  231. pos.treeFlattener = null;
  232. newRoot.treeFlattener.root = newRoot;
  233. }
  234. Debug.Assert(newRoot.listParent == null);
  235. newRoot.CheckInvariants();
  236. }
  237. #endregion
  238. #region Insertion
  239. static void InsertNodeAfter(SharpTreeNode pos, SharpTreeNode newNode)
  240. {
  241. // newNode might be the model root of a whole subtree, so go to the list root of that subtree:
  242. newNode = newNode.GetListRoot();
  243. if (pos.right == null) {
  244. pos.right = newNode;
  245. newNode.listParent = pos;
  246. } else {
  247. // insert before pos.right's leftmost:
  248. pos = pos.right;
  249. while (pos.left != null)
  250. pos = pos.left;
  251. Debug.Assert(pos.left == null);
  252. pos.left = newNode;
  253. newNode.listParent = pos;
  254. }
  255. RebalanceUntilRoot(pos);
  256. }
  257. #endregion
  258. #region Removal
  259. void RemoveNodes(SharpTreeNode start, SharpTreeNode end)
  260. {
  261. // Removes all nodes from start to end (inclusive)
  262. // All removed nodes will be reorganized in a separate tree, do not delete
  263. // regions that don't belong together in the tree model!
  264. List<SharpTreeNode> removedSubtrees = new List<SharpTreeNode>();
  265. SharpTreeNode oldPos;
  266. SharpTreeNode pos = start;
  267. do {
  268. // recalculate the endAncestors every time, because the tree might have been rebalanced
  269. HashSet<SharpTreeNode> endAncestors = new HashSet<SharpTreeNode>();
  270. for (SharpTreeNode tmp = end; tmp != null; tmp = tmp.listParent)
  271. endAncestors.Add(tmp);
  272. removedSubtrees.Add(pos);
  273. if (!endAncestors.Contains(pos)) {
  274. // we can remove pos' right subtree in a single step:
  275. if (pos.right != null) {
  276. removedSubtrees.Add(pos.right);
  277. pos.right.listParent = null;
  278. pos.right = null;
  279. }
  280. }
  281. SharpTreeNode succ = pos.Successor();
  282. DeleteNode(pos); // this will also rebalance out the deletion of the right subtree
  283. oldPos = pos;
  284. pos = succ;
  285. } while (oldPos != end);
  286. // merge back together the removed subtrees:
  287. SharpTreeNode removed = removedSubtrees[0];
  288. for (int i = 1; i < removedSubtrees.Count; i++) {
  289. removed = ConcatTrees(removed, removedSubtrees[i]);
  290. }
  291. }
  292. static SharpTreeNode ConcatTrees(SharpTreeNode first, SharpTreeNode second)
  293. {
  294. SharpTreeNode tmp = first;
  295. while (tmp.right != null)
  296. tmp = tmp.right;
  297. InsertNodeAfter(tmp, second);
  298. return tmp.GetListRoot();
  299. }
  300. SharpTreeNode Successor()
  301. {
  302. if (right != null) {
  303. SharpTreeNode node = right;
  304. while (node.left != null)
  305. node = node.left;
  306. return node;
  307. } else {
  308. SharpTreeNode node = this;
  309. SharpTreeNode oldNode;
  310. do {
  311. oldNode = node;
  312. node = node.listParent;
  313. // loop while we are on the way up from the right part
  314. } while (node != null && node.right == oldNode);
  315. return node;
  316. }
  317. }
  318. static void DeleteNode(SharpTreeNode node)
  319. {
  320. SharpTreeNode balancingNode;
  321. if (node.left == null) {
  322. balancingNode = node.listParent;
  323. node.ReplaceWith(node.right);
  324. node.right = null;
  325. } else if (node.right == null) {
  326. balancingNode = node.listParent;
  327. node.ReplaceWith(node.left);
  328. node.left = null;
  329. } else {
  330. SharpTreeNode tmp = node.right;
  331. while (tmp.left != null)
  332. tmp = tmp.left;
  333. // First replace tmp with tmp.right
  334. balancingNode = tmp.listParent;
  335. tmp.ReplaceWith(tmp.right);
  336. tmp.right = null;
  337. Debug.Assert(tmp.left == null);
  338. Debug.Assert(tmp.listParent == null);
  339. // Now move node's children to tmp:
  340. tmp.left = node.left; node.left = null;
  341. tmp.right = node.right; node.right = null;
  342. if (tmp.left != null) tmp.left.listParent = tmp;
  343. if (tmp.right != null) tmp.right.listParent = tmp;
  344. // Then replace node with tmp
  345. node.ReplaceWith(tmp);
  346. if (balancingNode == node)
  347. balancingNode = tmp;
  348. }
  349. Debug.Assert(node.listParent == null);
  350. Debug.Assert(node.left == null);
  351. Debug.Assert(node.right == null);
  352. node.height = 1;
  353. node.totalListLength = -1;
  354. if (balancingNode != null)
  355. RebalanceUntilRoot(balancingNode);
  356. }
  357. void ReplaceWith(SharpTreeNode node)
  358. {
  359. if (listParent != null) {
  360. if (listParent.left == this) {
  361. listParent.left = node;
  362. } else {
  363. Debug.Assert(listParent.right == this);
  364. listParent.right = node;
  365. }
  366. if (node != null)
  367. node.listParent = listParent;
  368. listParent = null;
  369. } else {
  370. // this was a root node
  371. Debug.Assert(node != null); // cannot delete the only node in the tree
  372. node.listParent = null;
  373. if (treeFlattener != null) {
  374. Debug.Assert(node.treeFlattener == null);
  375. node.treeFlattener = this.treeFlattener;
  376. this.treeFlattener = null;
  377. node.treeFlattener.root = node;
  378. }
  379. }
  380. }
  381. #endregion
  382. }
  383. }