Browse Source

Implemented FlatListTreeNode.

pull/10/head
Daniel Grunwald 14 years ago
parent
commit
aa50a0c3cb
  1. 157
      SharpTreeView/FlatListTreeNode.cs
  2. 10
      SharpTreeView/SharpTreeNode.cs
  3. 1
      SharpTreeView/SharpTreeView.cs
  4. 24
      SharpTreeView/TreeFlattener.cs

157
SharpTreeView/FlatListTreeNode.cs

@ -11,12 +11,14 @@ namespace ICSharpCode.TreeView @@ -11,12 +11,14 @@ namespace ICSharpCode.TreeView
partial class SharpTreeNode
{
/// <summary>The parent in the flat list</summary>
SharpTreeNode listParent;
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;
byte height = 1;
/// <summary>Length in the flat list, including children (children within the flat list).</summary>
internal int totalListLength = 1;
@ -37,6 +39,7 @@ namespace ICSharpCode.TreeView @@ -37,6 +39,7 @@ namespace ICSharpCode.TreeView
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 == (left != null ? left.totalListLength : 0) + (isVisible ? 1 : 0) + (right != null ? right.totalListLength : 0));
if (left != null) left.CheckInvariants();
if (right != null) right.CheckInvariants();
}
@ -79,5 +82,155 @@ namespace ICSharpCode.TreeView @@ -79,5 +82,155 @@ namespace ICSharpCode.TreeView
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)
{
// 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.right.RecalculateAugmentedData();
}
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.left.RecalculateAugmentedData();
}
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);
}
}
node.RecalculateAugmentedData();
return node;
}
void RecalculateAugmentedData()
{
Debug.Assert(Math.Abs(this.Balance) <= 1);
this.height = (byte)(1 + Math.Max(Height(this.left), Height(this.right)));
this.totalListLength = (isVisible ? 1 : 0)
+ (left != null ? left.totalListLength : 0)
+ (right != null ? right.totalListLength : 0);
}
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;
RecalculateAugmentedData();
return this.listParent = 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;
RecalculateAugmentedData();
return this.listParent = newTop;
}
#endregion
#region Insertion
static void InsertNodeAfter(SharpTreeNode pos, SharpTreeNode newNode)
{
Debug.Assert(newNode.listParent == null);
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);
}
static void RebalanceUntilRoot(SharpTreeNode pos)
{
while (pos.listParent != null) {
if (pos == pos.listParent.left) {
pos.listParent.left = Rebalance(pos);
} else {
Debug.Assert(pos == pos.listParent.right);
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;
}
}
[Conditional("DEBUG")]
static void DumpTree(SharpTreeNode node)
{
while (node.listParent != null)
node = node.listParent;
node.DumpTree();
}
[Conditional("DEBUG")]
void DumpTree()
{
Debug.Indent();
if (left != null)
left.DumpTree();
Debug.Unindent();
Debug.WriteLine("{0}, totalListLength={1}, height={2}", ToString(), totalListLength, height);
Debug.Indent();
if (right != null)
right.DumpTree();
Debug.Unindent();
}
#endregion
}
}

10
SharpTreeView/SharpTreeNode.cs

@ -102,15 +102,25 @@ namespace ICSharpCode.TreeView @@ -102,15 +102,25 @@ namespace ICSharpCode.TreeView
{
if (e.OldItems != null) {
foreach (SharpTreeNode node in e.OldItems) {
throw new NotImplementedException();
Debug.Assert(node.modelParent == this);
node.modelParent = null;
}
}
if (e.NewItems != null) {
SharpTreeNode insertionPos;
if (e.NewStartingIndex == 0)
insertionPos = this;
else
insertionPos = modelChildren[e.NewStartingIndex - 1];
foreach (SharpTreeNode node in e.NewItems) {
Debug.Assert(node.modelParent == null);
node.modelParent = this;
Debug.WriteLine("Inserting {0} after {1}", node.ToString(), insertionPos.ToString());
InsertNodeAfter(insertionPos, node);
insertionPos = node;
}
DumpTree(this);
}
RaisePropertyChanged("ShowExpander");

1
SharpTreeView/SharpTreeView.cs

@ -131,7 +131,6 @@ namespace ICSharpCode.TreeView @@ -131,7 +131,6 @@ namespace ICSharpCode.TreeView
}
flattener = new TreeFlattener(Root, ShowRoot);
this.ItemsSource = flattener;
flattener.Start();
}
}

24
SharpTreeView/TreeFlattener.cs

@ -5,6 +5,7 @@ using System; @@ -5,6 +5,7 @@ using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
@ -12,33 +13,36 @@ namespace ICSharpCode.TreeView @@ -12,33 +13,36 @@ namespace ICSharpCode.TreeView
{
sealed class TreeFlattener : IList, INotifyCollectionChanged
{
readonly SharpTreeNode root;
/// <summary>
/// The root node of the flat list tree.
/// Tjis is not necessarily the root of the model!
/// </summary>
internal SharpTreeNode root;
readonly bool includeRoot;
readonly object syncRoot = new object();
public TreeFlattener(SharpTreeNode root, bool includeRoot)
public TreeFlattener(SharpTreeNode modelRoot, bool includeRoot)
{
this.root = root;
this.root = modelRoot;
while (root.listParent != null)
root = root.listParent;
root.treeFlattener = this;
this.includeRoot = includeRoot;
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void Start()
{
}
public void Stop()
{
Debug.Assert(root.treeFlattener == this);
root.treeFlattener = null;
}
public object this[int index] {
get {
if (index < 0 || index >= this.Count)
throw new ArgumentOutOfRangeException();
return SharpTreeNode.GetNodeByVisibleIndex(root, includeRoot ? index : index - 1);
return SharpTreeNode.GetNodeByVisibleIndex(root, includeRoot ? index : index + 1);
}
set {
throw new NotSupportedException();

Loading…
Cancel
Save