void ReplaceWith(FileNodeViewModel 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; } } }
static void RebalanceUntilRoot(FileNodeViewModel 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; } FileNodeViewModel 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(); }
internal static FileNodeViewModel GetNodeByVisibleIndex(FileNodeViewModel root, int index) { root.GetTotalListLength(); // ensure all list lengths are calculated Debug.Assert(index >= 0); Debug.Assert(index < root.totalListLength); FileNodeViewModel 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; } } }
FileNodeViewModel RotateLeft() { /* Rotate tree to the left * * this right * / \ / \ * A right ===> this C * / \ / \ * B C A B */ FileNodeViewModel b = right.left; FileNodeViewModel 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); }
/*private bool isHidden; * private bool isSelected; * private bool isExpanded; * private bool isEditing;*/ /*private BitField16 flags = new BitField16 { * //[0] = true, * }; * #pragma warning disable 0649, IDE1006 * private bool isVisible { * get => flags[0]; * set => flags[0] = value; * } * private bool isHidden { * get => flags[1]; * set => flags[1] = value; * } * private bool isSelected { * get => flags[2]; * set => flags[2] = value; * } * private bool isExpanded { * get => flags[3]; * set => flags[3] = value; * } * private bool isEditing { * get => flags[4]; * set => flags[4] = value; * } * private bool isEventsHooked { * get => flags[5]; * set => flags[5] = value; * } #pragma warning restore 0649, IDE1006*/ //private bool canExpandRecursively = true; //private bool lazyLoading; //private bool? isChecked; private void UpdateIsVisible(bool parentIsVisible, bool updateFlattener) { bool newIsVisible = parentIsVisible && !isHidden; if (isVisible != newIsVisible) { isVisible = newIsVisible; // invalidate the augmented data FileNodeViewModel node = this; while (node != null && node.totalListLength >= 0) { node.totalListLength = -1; node = node.listParent; } // Remember the removed nodes: List <FileNodeViewModel> removedNodes = null; if (updateFlattener && !newIsVisible) { removedNodes = VisibleDescendantsAndSelf().ToList(); } // also update the model children: UpdateChildIsVisible(false); // Validate our invariants: if (updateFlattener) { CheckRootInvariants(); } // Tell the flattener about the removed nodes: if (removedNodes != null) { var flattener = GetListRoot().treeFlattener; if (flattener != null) { flattener.NodesRemoved(GetVisibleIndexForNode(this), removedNodes); foreach (var n in removedNodes) { n.OnIsVisibleChanged(); } } } // Tell the flattener about the new nodes: if (updateFlattener && newIsVisible) { var flattener = GetListRoot().treeFlattener; if (flattener != null) { flattener.NodesInserted(GetVisibleIndexForNode(this), VisibleDescendantsAndSelf()); foreach (var n in VisibleDescendantsAndSelf()) { n.OnIsVisibleChanged(); } } } } }
static void DeleteNode(FileNodeViewModel node) { FileNodeViewModel 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 { FileNodeViewModel 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); } }
public object this[int index] { get { if (index < 0 || index >= this.Count) { throw new ArgumentOutOfRangeException(); } return(FileNodeViewModel.GetNodeByVisibleIndex(root, includeRoot ? index : index + 1)); } set => throw new NotSupportedException();
internal FileNodeViewModel GetListRoot() { FileNodeViewModel node = this; while (node.listParent != null) { node = node.listParent; } return(node); }
public TreeFlattener(FileNodeViewModel modelRoot, bool includeRoot) { this.root = modelRoot; while (root.listParent != null) { root = root.listParent; } root.treeFlattener = this; this.includeRoot = includeRoot; }
static FileNodeViewModel ConcatTrees(FileNodeViewModel first, FileNodeViewModel second) { FileNodeViewModel tmp = first; while (tmp.right != null) { tmp = tmp.right; } InsertNodeAfter(tmp, second); return(tmp.GetListRoot()); }
void RemoveNodes(FileNodeViewModel start, FileNodeViewModel 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 <FileNodeViewModel> removedSubtrees = new List <FileNodeViewModel>(); FileNodeViewModel oldPos; FileNodeViewModel pos = start; do { // recalculate the endAncestors every time, because the tree might have been rebalanced HashSet <FileNodeViewModel> endAncestors = new HashSet <FileNodeViewModel>(); for (FileNodeViewModel 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; } } FileNodeViewModel 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: FileNodeViewModel removed = removedSubtrees[0]; for (int i = 1; i < removedSubtrees.Count; i++) { removed = ConcatTrees(removed, removedSubtrees[i]); } }
internal static int GetVisibleIndexForNode(FileNodeViewModel 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); }
/// <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 FileNodeViewModel Rebalance(FileNodeViewModel 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); }
static void InsertNodeAfter(FileNodeViewModel pos, FileNodeViewModel 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); }
FileNodeViewModel RotateRight() { /* Rotate tree to the right * * this left * / \ / \ * left C ===> A this * / \ / \ * A B B C */ FileNodeViewModel b = left.right; FileNodeViewModel 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); }
FileNodeViewModel Successor() { if (right != null) { FileNodeViewModel node = right; while (node.left != null) { node = node.left; } return(node); } else { FileNodeViewModel node = this; FileNodeViewModel 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); } }
public void Add(FileNodeViewModel item) { throw new NotSupportedException(); }
public bool Remove(FileNodeViewModel item) { throw new NotSupportedException(); }
public int IndexOf(FileNodeViewModel item) { return(-1); }
static void DumpTree(FileNodeViewModel node) { node.GetListRoot().DumpTree(); }
public void Insert(int index, FileNodeViewModel item) { throw new NotSupportedException(); }
static int Height(FileNodeViewModel node) { return(node != null ? node.height : 0); }
public bool Contains(FileNodeViewModel item) { return(false); }