public TreeFlattener(SharpTreeNode modelRoot, bool includeRoot) { this.root = modelRoot; while (root.listParent != null) root = root.listParent; root.treeFlattener = this; this.includeRoot = includeRoot; }
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; }
public int IndexOf(object item) { SharpTreeNode node = item as SharpTreeNode; if (node != null && node.IsVisible && node.GetListRoot() == root) { if (includeRoot) { return(SharpTreeNode.GetVisibleIndexForNode(node)); } else { return(SharpTreeNode.GetVisibleIndexForNode(node) - 1); } } else { return(-1); } }
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); }
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; } } }
/// <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); }
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); }
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); } }
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); }
/// <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; }
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); }
static int Height(SharpTreeNode node) { return node != null ? node.height : 0; }
static void DumpTree(SharpTreeNode node) { node.GetListRoot().DumpTree(); }
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); }
public virtual IDataObject Copy(SharpTreeNode[] nodes) { throw new NotSupportedException(GetType().Name + " does not support copy/paste or drag'n'drop"); }
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; }
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; } } }
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(); }
static int Height(SharpTreeNode node) { return(node != null ? node.height : 0); }
public virtual void StartDrag(DependencyObject dragSource, SharpTreeNode[] nodes) { DragDropEffects effects = DragDropEffects.All; if (!nodes.All(n => n.CanDelete())) effects &= ~DragDropEffects.Move; DragDropEffects result = DragDrop.DoDragDrop(dragSource, Copy(nodes), effects); if (result == DragDropEffects.Move) { foreach (SharpTreeNode node in nodes) node.DeleteCore(); } }
void GetNodeAndIndex(SharpTreeViewItem item, DropPlace place, out SharpTreeNode node, out int index) { node = null; index = 0; if (place == DropPlace.Inside) { node = item.Node; index = node.Children.Count; } else if (place == DropPlace.Before) { if (item.Node.Parent != null) { node = item.Node.Parent; index = node.Children.IndexOf(item.Node); } } else { if (item.Node.Parent != null) { node = item.Node.Parent; index = node.Children.IndexOf(item.Node) + 1; } } }
void UpdateDataContext(SharpTreeNode oldNode, SharpTreeNode newNode) { if (newNode != null) { newNode.PropertyChanged += Node_PropertyChanged; if (Template != null) { UpdateTemplate(); } } if (oldNode != null) { oldNode.PropertyChanged -= Node_PropertyChanged; } }
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]); } }
void ExpandRecursively(SharpTreeNode node) { if (node.CanExpandRecursively) { node.IsExpanded = true; foreach (SharpTreeNode child in node.Children) { ExpandRecursively(child); } } }
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; }
/// <summary> /// Handles the node expanding event in the tree view. /// This method gets called only if the node is in the visible region (a SharpTreeNodeView exists). /// </summary> internal void HandleExpanding(SharpTreeNode node) { if (doNotScrollOnExpanding) return; SharpTreeNode lastVisibleChild = node; while (true) { SharpTreeNode tmp = lastVisibleChild.Children.LastOrDefault(c => c.IsVisible); if (tmp != null) { lastVisibleChild = tmp; } else { break; } } if (lastVisibleChild != node) { // Make the the expanded children are visible; but don't scroll down // to much (keep node itself visible) base.ScrollIntoView(lastVisibleChild); // For some reason, this only works properly when delaying it... Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action( delegate { base.ScrollIntoView(node); })); } }
public void ScrollIntoView(SharpTreeNode node) { if (node == null) throw new ArgumentNullException("node"); doNotScrollOnExpanding = true; foreach (SharpTreeNode ancestor in node.Ancestors()) ancestor.IsExpanded = true; doNotScrollOnExpanding = false; base.ScrollIntoView(node); }
public virtual bool CanDrag(SharpTreeNode[] nodes) { return false; }
internal protected virtual void OnChildrenChanged(NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { foreach (SharpTreeNode node in e.OldItems) { Debug.Assert(node.modelParent == this); node.modelParent = null; Debug.WriteLine("Removing {0} from {1}", node, this); SharpTreeNode removeEnd = node; while (removeEnd.modelChildren != null && removeEnd.modelChildren.Count > 0) { removeEnd = removeEnd.modelChildren.Last(); } List <SharpTreeNode> removedNodes = null; int visibleIndexOfRemoval = 0; if (node.isVisible) { visibleIndexOfRemoval = GetVisibleIndexForNode(node); removedNodes = node.VisibleDescendantsAndSelf().ToList(); } RemoveNodes(node, removeEnd); if (removedNodes != null) { var flattener = GetListRoot().treeFlattener; if (flattener != null) { flattener.NodesRemoved(visibleIndexOfRemoval, removedNodes); } } } } if (e.NewItems != null) { SharpTreeNode insertionPos; if (e.NewStartingIndex == 0) { insertionPos = null; } else { insertionPos = modelChildren[e.NewStartingIndex - 1]; } foreach (SharpTreeNode node in e.NewItems) { Debug.Assert(node.modelParent == null); node.modelParent = this; node.UpdateIsVisible(isVisible && isExpanded, false); //Debug.WriteLine("Inserting {0} after {1}", node, insertionPos); while (insertionPos != null && insertionPos.modelChildren != null && insertionPos.modelChildren.Count > 0) { insertionPos = insertionPos.modelChildren.Last(); } InsertNodeAfter(insertionPos ?? this, node); insertionPos = node; if (node.isVisible) { var flattener = GetListRoot().treeFlattener; if (flattener != null) { flattener.NodesInserted(GetVisibleIndexForNode(node), node.VisibleDescendantsAndSelf()); } } } } RaisePropertyChanged("ShowExpander"); RaiseIsLastChangedIfNeeded(e); }
static SharpTreeNode ConcatTrees(SharpTreeNode first, SharpTreeNode second) { SharpTreeNode tmp = first; while (tmp.right != null) tmp = tmp.right; InsertNodeAfter(tmp, second); return tmp.GetListRoot(); }
/// <summary> /// Scrolls the specified node in view and sets keyboard focus on it. /// </summary> public void FocusNode(SharpTreeNode node) { if (node == null) throw new ArgumentNullException("node"); ScrollIntoView(node); // WPF's ScrollIntoView() uses the same if/dispatcher construct, so we call OnFocusItem() after the item was brought into view. if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { OnFocusItem(node); } else { this.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new DispatcherOperationCallback(this.OnFocusItem), node); } }