PageRenderTime 39ms CodeModel.GetById 19ms app.highlight 16ms RepoModel.GetById 1ms app.codeStats 0ms

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