├── .gitattributes ├── .gitignore ├── Converters.cs ├── EditTextBox.cs ├── ExtensionMethods.cs ├── FlatListTreeNode.cs ├── GeneralAdorner.cs ├── ICSharpCode.NRefactory.snk ├── ICSharpCode.TreeView.csproj ├── InsertMarker.cs ├── LinesRenderer.cs ├── Properties ├── AssemblyInfo.cs └── GlobalAssemblyInfo.cs ├── README.md ├── SharpGridView.cs ├── SharpTreeNode.cs ├── SharpTreeNodeCollection.cs ├── SharpTreeNodeProxy.cs ├── SharpTreeNodeView.cs ├── SharpTreeView.cs ├── SharpTreeViewItem.cs ├── Themes └── Generic.xaml ├── TreeFlattener.cs ├── TreeTraversal.cs ├── copyright.txt └── license.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.cs text diff=csharp 3 | *.sln text eol=crlf 4 | *.csproj text eol=crlf 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | obj/ 4 | *.user 5 | _ReSharper*/ 6 | *.ReSharper 7 | *_wpftmp.csproj 8 | -------------------------------------------------------------------------------- /Converters.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows; 9 | using System.Windows.Markup; 10 | using System.Windows.Data; 11 | using System.Globalization; 12 | 13 | namespace ICSharpCode.TreeView 14 | { 15 | public class CollapsedWhenFalse : MarkupExtension, IValueConverter 16 | { 17 | public static CollapsedWhenFalse Instance = new CollapsedWhenFalse(); 18 | 19 | public override object ProvideValue(IServiceProvider serviceProvider) 20 | { 21 | return Instance; 22 | } 23 | 24 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 25 | { 26 | return (bool)value ? Visibility.Visible : Visibility.Collapsed; 27 | } 28 | 29 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /EditTextBox.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Controls; 9 | using System.Windows.Input; 10 | using System.Windows.Data; 11 | using System.Windows; 12 | 13 | namespace ICSharpCode.TreeView 14 | { 15 | public class EditTextBox : TextBox 16 | { 17 | static EditTextBox() 18 | { 19 | DefaultStyleKeyProperty.OverrideMetadata(typeof(EditTextBox), 20 | new FrameworkPropertyMetadata(typeof(EditTextBox))); 21 | } 22 | 23 | public EditTextBox() 24 | { 25 | Loaded += delegate { Init(); }; 26 | } 27 | 28 | public SharpTreeViewItem Item { get; set; } 29 | 30 | public SharpTreeNode Node { 31 | get { return Item.Node; } 32 | } 33 | 34 | void Init() 35 | { 36 | if (Node != null) 37 | Text = Node.LoadEditText(); 38 | Focus(); 39 | SelectAll(); 40 | } 41 | 42 | protected override void OnKeyDown(KeyEventArgs e) 43 | { 44 | if (e.Key == Key.Enter) { 45 | Commit(); 46 | } else if (e.Key == Key.Escape) { 47 | if (Node != null) 48 | Node.IsEditing = false; 49 | } 50 | } 51 | 52 | protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) 53 | { 54 | if (Node != null && Node.IsEditing) { 55 | Commit(); 56 | } 57 | } 58 | 59 | bool commiting; 60 | 61 | void Commit() 62 | { 63 | if (!commiting) { 64 | commiting = true; 65 | 66 | if (Node != null) { 67 | Node.IsEditing = false; 68 | if (!Node.SaveEditText(Text)) { 69 | Item.Focus(); 70 | } 71 | Node.RaisePropertyChanged("Text"); 72 | } 73 | 74 | //if (Node.SaveEditText(Text)) { 75 | // Node.IsEditing = false; 76 | // Node.RaisePropertyChanged("Text"); 77 | //} 78 | //else { 79 | // Init(); 80 | //} 81 | 82 | commiting = false; 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Media; 9 | using System.Windows; 10 | using System.Collections; 11 | using System.Windows.Input; 12 | 13 | namespace ICSharpCode.TreeView 14 | { 15 | static class ExtensionMethods 16 | { 17 | public static T FindAncestor(this DependencyObject d) where T : class 18 | { 19 | return AncestorsAndSelf(d).OfType().FirstOrDefault(); 20 | } 21 | 22 | public static IEnumerable AncestorsAndSelf(this DependencyObject d) 23 | { 24 | while (d != null) { 25 | yield return d; 26 | d = VisualTreeHelper.GetParent(d); 27 | } 28 | } 29 | 30 | public static void AddOnce(this IList list, object item) 31 | { 32 | if (!list.Contains(item)) { 33 | list.Add(item); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FlatListTreeNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | 8 | namespace ICSharpCode.TreeView 9 | { 10 | // This part of SharpTreeNode controls the 'flat list' data structure, which emulates 11 | // a big flat list containing the whole tree; allowing access by visible index. 12 | partial class SharpTreeNode 13 | { 14 | /// The parent in the flat list 15 | internal SharpTreeNode listParent; 16 | /// Left/right nodes in the flat list 17 | SharpTreeNode left, right; 18 | 19 | internal TreeFlattener treeFlattener; 20 | 21 | /// Subtree height in the flat list tree 22 | byte height = 1; 23 | 24 | /// Length in the flat list, including children (children within the flat list). -1 = invalidated 25 | int totalListLength = -1; 26 | 27 | int Balance { 28 | get { return Height(right) - Height(left); } 29 | } 30 | 31 | static int Height(SharpTreeNode node) 32 | { 33 | return node != null ? node.height : 0; 34 | } 35 | 36 | internal SharpTreeNode GetListRoot() 37 | { 38 | SharpTreeNode node = this; 39 | while (node.listParent != null) 40 | node = node.listParent; 41 | return node; 42 | } 43 | 44 | #region Debugging 45 | [Conditional("DEBUG")] 46 | void CheckRootInvariants() 47 | { 48 | GetListRoot().CheckInvariants(); 49 | } 50 | 51 | [Conditional("DATACONSISTENCYCHECK")] 52 | void CheckInvariants() 53 | { 54 | Debug.Assert(left == null || left.listParent == this); 55 | Debug.Assert(right == null || right.listParent == this); 56 | Debug.Assert(height == 1 + Math.Max(Height(left), Height(right))); 57 | Debug.Assert(Math.Abs(this.Balance) <= 1); 58 | Debug.Assert(totalListLength == -1 || totalListLength == (left != null ? left.totalListLength : 0) + (isVisible ? 1 : 0) + (right != null ? right.totalListLength : 0)); 59 | if (left != null) left.CheckInvariants(); 60 | if (right != null) right.CheckInvariants(); 61 | } 62 | 63 | [Conditional("DEBUG")] 64 | static void DumpTree(SharpTreeNode node) 65 | { 66 | node.GetListRoot().DumpTree(); 67 | } 68 | 69 | [Conditional("DEBUG")] 70 | void DumpTree() 71 | { 72 | Debug.Indent(); 73 | if (left != null) 74 | left.DumpTree(); 75 | Debug.Unindent(); 76 | Debug.WriteLine("{0}, totalListLength={1}, height={2}, Balance={3}, isVisible={4}", ToString(), totalListLength, height, Balance, isVisible); 77 | Debug.Indent(); 78 | if (right != null) 79 | right.DumpTree(); 80 | Debug.Unindent(); 81 | } 82 | #endregion 83 | 84 | #region GetNodeByVisibleIndex / GetVisibleIndexForNode 85 | internal static SharpTreeNode GetNodeByVisibleIndex(SharpTreeNode root, int index) 86 | { 87 | root.GetTotalListLength(); // ensure all list lengths are calculated 88 | Debug.Assert(index >= 0); 89 | Debug.Assert(index < root.totalListLength); 90 | SharpTreeNode node = root; 91 | Debug.Assert(node != null); 92 | while (true) { 93 | if (node.left != null && index < node.left.totalListLength) { 94 | Debug.Assert(node.left != null); 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 | Debug.Assert(node.right != null); 106 | node = node.right; 107 | } 108 | } 109 | } 110 | 111 | internal static int GetVisibleIndexForNode(SharpTreeNode node) 112 | { 113 | int index = node.left != null ? node.left.GetTotalListLength() : 0; 114 | while (node.listParent != null) { 115 | if (node == node.listParent.right) { 116 | if (node.listParent.left != null) 117 | index += node.listParent.left.GetTotalListLength(); 118 | if (node.listParent.isVisible) 119 | index++; 120 | } 121 | node = node.listParent; 122 | } 123 | return index; 124 | } 125 | #endregion 126 | 127 | #region Balancing 128 | /// 129 | /// Balances the subtree rooted in and recomputes the 'height' field. 130 | /// This method assumes that the children of this node are already balanced and have an up-to-date 'height' value. 131 | /// 132 | /// The new root node 133 | static SharpTreeNode Rebalance(SharpTreeNode node) 134 | { 135 | Debug.Assert(node.left == null || Math.Abs(node.left.Balance) <= 1); 136 | Debug.Assert(node.right == null || Math.Abs(node.right.Balance) <= 1); 137 | // Keep looping until it's balanced. Not sure if this is stricly required; this is based on 138 | // the Rope code where node merging made this necessary. 139 | while (Math.Abs(node.Balance) > 1) { 140 | // AVL balancing 141 | // note: because we don't care about the identity of concat nodes, this works a little different than usual 142 | // tree rotations: in our implementation, the "this" node will stay at the top, only its children are rearranged 143 | if (node.Balance > 1) { 144 | if (node.right.Balance < 0) { 145 | node.right = node.right.RotateRight(); 146 | } 147 | node = node.RotateLeft(); 148 | // If 'node' was unbalanced by more than 2, we've shifted some of the inbalance to the left node; so rebalance that. 149 | node.left = Rebalance(node.left); 150 | } else if (node.Balance < -1) { 151 | if (node.left.Balance > 0) { 152 | node.left = node.left.RotateLeft(); 153 | } 154 | node = node.RotateRight(); 155 | // If 'node' was unbalanced by more than 2, we've shifted some of the inbalance to the right node; so rebalance that. 156 | node.right = Rebalance(node.right); 157 | } 158 | } 159 | Debug.Assert(Math.Abs(node.Balance) <= 1); 160 | node.height = (byte)(1 + Math.Max(Height(node.left), Height(node.right))); 161 | node.totalListLength = -1; // mark for recalculation 162 | // since balancing checks the whole tree up to the root, the whole path will get marked as invalid 163 | return node; 164 | } 165 | 166 | internal int GetTotalListLength() 167 | { 168 | if (totalListLength >= 0) 169 | return totalListLength; 170 | int length = (isVisible ? 1 : 0); 171 | if (left != null) { 172 | length += left.GetTotalListLength(); 173 | } 174 | if (right != null) { 175 | length += right.GetTotalListLength(); 176 | } 177 | return totalListLength = length; 178 | } 179 | 180 | SharpTreeNode RotateLeft() 181 | { 182 | /* Rotate tree to the left 183 | * 184 | * this right 185 | * / \ / \ 186 | * A right ===> this C 187 | * / \ / \ 188 | * B C A B 189 | */ 190 | SharpTreeNode b = right.left; 191 | SharpTreeNode newTop = right; 192 | 193 | if (b != null) b.listParent = this; 194 | this.right = b; 195 | newTop.left = this; 196 | newTop.listParent = this.listParent; 197 | this.listParent = newTop; 198 | // rebalance the 'this' node - this is necessary in some bulk insertion cases: 199 | newTop.left = Rebalance(this); 200 | return newTop; 201 | } 202 | 203 | SharpTreeNode RotateRight() 204 | { 205 | /* Rotate tree to the right 206 | * 207 | * this left 208 | * / \ / \ 209 | * left C ===> A this 210 | * / \ / \ 211 | * A B B C 212 | */ 213 | SharpTreeNode b = left.right; 214 | SharpTreeNode newTop = left; 215 | 216 | if (b != null) b.listParent = this; 217 | this.left = b; 218 | newTop.right = this; 219 | newTop.listParent = this.listParent; 220 | this.listParent = newTop; 221 | newTop.right = Rebalance(this); 222 | return newTop; 223 | } 224 | 225 | static void RebalanceUntilRoot(SharpTreeNode pos) 226 | { 227 | while (pos.listParent != null) { 228 | if (pos == pos.listParent.left) { 229 | pos = pos.listParent.left = Rebalance(pos); 230 | } else { 231 | Debug.Assert(pos == pos.listParent.right); 232 | pos = pos.listParent.right = Rebalance(pos); 233 | } 234 | pos = pos.listParent; 235 | } 236 | SharpTreeNode newRoot = Rebalance(pos); 237 | if (newRoot != pos && pos.treeFlattener != null) { 238 | Debug.Assert(newRoot.treeFlattener == null); 239 | newRoot.treeFlattener = pos.treeFlattener; 240 | pos.treeFlattener = null; 241 | newRoot.treeFlattener.root = newRoot; 242 | } 243 | Debug.Assert(newRoot.listParent == null); 244 | newRoot.CheckInvariants(); 245 | } 246 | #endregion 247 | 248 | #region Insertion 249 | static void InsertNodeAfter(SharpTreeNode pos, SharpTreeNode newNode) 250 | { 251 | // newNode might be the model root of a whole subtree, so go to the list root of that subtree: 252 | newNode = newNode.GetListRoot(); 253 | if (pos.right == null) { 254 | pos.right = newNode; 255 | newNode.listParent = pos; 256 | } else { 257 | // insert before pos.right's leftmost: 258 | pos = pos.right; 259 | while (pos.left != null) 260 | pos = pos.left; 261 | Debug.Assert(pos.left == null); 262 | pos.left = newNode; 263 | newNode.listParent = pos; 264 | } 265 | RebalanceUntilRoot(pos); 266 | } 267 | #endregion 268 | 269 | #region Removal 270 | void RemoveNodes(SharpTreeNode start, SharpTreeNode end) 271 | { 272 | // Removes all nodes from start to end (inclusive) 273 | // All removed nodes will be reorganized in a separate tree, do not delete 274 | // regions that don't belong together in the tree model! 275 | 276 | List removedSubtrees = new List(); 277 | SharpTreeNode oldPos; 278 | SharpTreeNode pos = start; 279 | do { 280 | // recalculate the endAncestors every time, because the tree might have been rebalanced 281 | HashSet endAncestors = new HashSet(); 282 | for (SharpTreeNode tmp = end; tmp != null; tmp = tmp.listParent) 283 | endAncestors.Add(tmp); 284 | 285 | removedSubtrees.Add(pos); 286 | if (!endAncestors.Contains(pos)) { 287 | // we can remove pos' right subtree in a single step: 288 | if (pos.right != null) { 289 | removedSubtrees.Add(pos.right); 290 | pos.right.listParent = null; 291 | pos.right = null; 292 | } 293 | } 294 | SharpTreeNode succ = pos.Successor(); 295 | DeleteNode(pos); // this will also rebalance out the deletion of the right subtree 296 | 297 | oldPos = pos; 298 | pos = succ; 299 | } while (oldPos != end); 300 | 301 | // merge back together the removed subtrees: 302 | SharpTreeNode removed = removedSubtrees[0]; 303 | for (int i = 1; i < removedSubtrees.Count; i++) { 304 | removed = ConcatTrees(removed, removedSubtrees[i]); 305 | } 306 | } 307 | 308 | static SharpTreeNode ConcatTrees(SharpTreeNode first, SharpTreeNode second) 309 | { 310 | SharpTreeNode tmp = first; 311 | while (tmp.right != null) 312 | tmp = tmp.right; 313 | InsertNodeAfter(tmp, second); 314 | return tmp.GetListRoot(); 315 | } 316 | 317 | SharpTreeNode Successor() 318 | { 319 | if (right != null) { 320 | SharpTreeNode node = right; 321 | while (node.left != null) 322 | node = node.left; 323 | return node; 324 | } else { 325 | SharpTreeNode node = this; 326 | SharpTreeNode oldNode; 327 | do { 328 | oldNode = node; 329 | node = node.listParent; 330 | // loop while we are on the way up from the right part 331 | } while (node != null && node.right == oldNode); 332 | return node; 333 | } 334 | } 335 | 336 | static void DeleteNode(SharpTreeNode node) 337 | { 338 | SharpTreeNode balancingNode; 339 | if (node.left == null) { 340 | balancingNode = node.listParent; 341 | node.ReplaceWith(node.right); 342 | node.right = null; 343 | } else if (node.right == null) { 344 | balancingNode = node.listParent; 345 | node.ReplaceWith(node.left); 346 | node.left = null; 347 | } else { 348 | SharpTreeNode tmp = node.right; 349 | while (tmp.left != null) 350 | tmp = tmp.left; 351 | // First replace tmp with tmp.right 352 | balancingNode = tmp.listParent; 353 | tmp.ReplaceWith(tmp.right); 354 | tmp.right = null; 355 | Debug.Assert(tmp.left == null); 356 | Debug.Assert(tmp.listParent == null); 357 | // Now move node's children to tmp: 358 | tmp.left = node.left; node.left = null; 359 | tmp.right = node.right; node.right = null; 360 | if (tmp.left != null) tmp.left.listParent = tmp; 361 | if (tmp.right != null) tmp.right.listParent = tmp; 362 | // Then replace node with tmp 363 | node.ReplaceWith(tmp); 364 | if (balancingNode == node) 365 | balancingNode = tmp; 366 | } 367 | Debug.Assert(node.listParent == null); 368 | Debug.Assert(node.left == null); 369 | Debug.Assert(node.right == null); 370 | node.height = 1; 371 | node.totalListLength = -1; 372 | if (balancingNode != null) 373 | RebalanceUntilRoot(balancingNode); 374 | } 375 | 376 | void ReplaceWith(SharpTreeNode node) 377 | { 378 | if (listParent != null) { 379 | if (listParent.left == this) { 380 | listParent.left = node; 381 | } else { 382 | Debug.Assert(listParent.right == this); 383 | listParent.right = node; 384 | } 385 | if (node != null) 386 | node.listParent = listParent; 387 | listParent = null; 388 | } else { 389 | // this was a root node 390 | Debug.Assert(node != null); // cannot delete the only node in the tree 391 | node.listParent = null; 392 | if (treeFlattener != null) { 393 | Debug.Assert(node.treeFlattener == null); 394 | node.treeFlattener = this.treeFlattener; 395 | this.treeFlattener = null; 396 | node.treeFlattener.root = node; 397 | } 398 | } 399 | } 400 | #endregion 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /GeneralAdorner.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Documents; 9 | using System.Windows; 10 | using System.Windows.Media; 11 | 12 | namespace ICSharpCode.TreeView 13 | { 14 | public class GeneralAdorner : Adorner 15 | { 16 | public GeneralAdorner(UIElement target) 17 | : base(target) 18 | { 19 | } 20 | 21 | FrameworkElement child; 22 | 23 | public FrameworkElement Child 24 | { 25 | get 26 | { 27 | return child; 28 | } 29 | set 30 | { 31 | if (child != value) { 32 | RemoveVisualChild(child); 33 | RemoveLogicalChild(child); 34 | child = value; 35 | AddLogicalChild(value); 36 | AddVisualChild(value); 37 | InvalidateMeasure(); 38 | } 39 | } 40 | } 41 | 42 | protected override int VisualChildrenCount 43 | { 44 | get { return child == null ? 0 : 1; } 45 | } 46 | 47 | protected override Visual GetVisualChild(int index) 48 | { 49 | return child; 50 | } 51 | 52 | protected override Size MeasureOverride(Size constraint) 53 | { 54 | if (child != null) { 55 | child.Measure(constraint); 56 | return child.DesiredSize; 57 | } 58 | return new Size(); 59 | } 60 | 61 | protected override Size ArrangeOverride(Size finalSize) 62 | { 63 | if (child != null) { 64 | child.Arrange(new Rect(finalSize)); 65 | return finalSize; 66 | } 67 | return new Size(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ICSharpCode.NRefactory.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dnSpy/ICSharpCode.TreeView/dd6e2ae107b7756dda894f5d1bf47e45fd401144/ICSharpCode.NRefactory.snk -------------------------------------------------------------------------------- /ICSharpCode.TreeView.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | ICSharpCode.NRefactory.snk 8 | File 9 | false 10 | false 11 | false 12 | false 13 | false 14 | false 15 | false 16 | false 17 | false 18 | false 19 | true 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /InsertMarker.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Controls; 9 | using System.Windows; 10 | 11 | namespace ICSharpCode.TreeView 12 | { 13 | public class InsertMarker : Control 14 | { 15 | static InsertMarker() 16 | { 17 | DefaultStyleKeyProperty.OverrideMetadata(typeof(InsertMarker), 18 | new FrameworkPropertyMetadata(typeof(InsertMarker))); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LinesRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows; 9 | using System.Windows.Media; 10 | 11 | namespace ICSharpCode.TreeView 12 | { 13 | class LinesRenderer : FrameworkElement 14 | { 15 | static LinesRenderer() 16 | { 17 | pen = new Pen(Brushes.LightGray, 1); 18 | pen.Freeze(); 19 | } 20 | 21 | static Pen pen; 22 | 23 | SharpTreeNodeView NodeView 24 | { 25 | get { return TemplatedParent as SharpTreeNodeView; } 26 | } 27 | 28 | protected override void OnRender(DrawingContext dc) 29 | { 30 | var indent = NodeView.CalculateIndent(NodeView.Node); 31 | var p = new Point(indent + 4.5, 0); 32 | 33 | if (!NodeView.Node.IsRoot || NodeView.ParentTreeView.ShowRootExpander) { 34 | dc.DrawLine(pen, new Point(p.X, ActualHeight / 2), new Point(p.X + 10, ActualHeight / 2)); 35 | } 36 | 37 | if (NodeView.Node.IsRoot) return; 38 | 39 | if (NodeView.Node.IsLast) { 40 | dc.DrawLine(pen, p, new Point(p.X, ActualHeight / 2)); 41 | } 42 | else { 43 | dc.DrawLine(pen, p, new Point(p.X, ActualHeight)); 44 | } 45 | 46 | var current = NodeView.Node; 47 | while (true) { 48 | p.X -= 19; 49 | current = current.Parent; 50 | if (p.X < 0) break; 51 | if (!current.IsLast) { 52 | dc.DrawLine(pen, p, new Point(p.X, ActualHeight)); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System.Reflection; 5 | using System.Resources; 6 | using System.Runtime.CompilerServices; 7 | using System.Runtime.InteropServices; 8 | using System.Windows; 9 | using System.Windows.Markup; 10 | 11 | // General Information about an assembly is controlled through the following 12 | // set of attributes. Change these attribute values to modify the information 13 | // associated with an assembly. 14 | [assembly: AssemblyTitle("ICSharpCode.TreeView")] 15 | [assembly: AssemblyDescription("")] 16 | [assembly: AssemblyConfiguration("")] 17 | [assembly: AssemblyTrademark("")] 18 | [assembly: AssemblyCulture("")] 19 | 20 | //In order to begin building localizable applications, set 21 | //CultureYouAreCodingWith in your .csproj file 22 | //inside a . For example, if you are using US english 23 | //in your source files, set the to en-US. Then uncomment 24 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 25 | //the line below to match the UICulture setting in the project file. 26 | 27 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 28 | 29 | 30 | [assembly: ThemeInfo( 31 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 32 | //(used if a resource is not found in the page, 33 | // or application resource dictionaries) 34 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 35 | //(used if a resource is not found in the page, 36 | // app, or any theme specific resource dictionaries) 37 | )] 38 | 39 | 40 | [assembly: XmlnsPrefix("http://icsharpcode.net/sharpdevelop/treeview", "treeview")] 41 | 42 | [assembly: XmlnsDefinition("http://icsharpcode.net/sharpdevelop/treeview", "ICSharpCode.TreeView")] 43 | -------------------------------------------------------------------------------- /Properties/GlobalAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | ///////////////////////////////////////////////////////////////////////////////////////////// 5 | ///////////////////////////////////////////////////////////////////////////////////////////// 6 | // // 7 | // DO NOT EDIT GlobalAssemblyInfo.cs, it is recreated using AssemblyInfo.template whenever // 8 | // ICSharpCode.Core is compiled. // 9 | // // 10 | ///////////////////////////////////////////////////////////////////////////////////////////// 11 | ///////////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | using System.Resources; 14 | using System.Reflection; 15 | 16 | [assembly: System.Runtime.InteropServices.ComVisible(false)] 17 | [assembly: AssemblyCompany("ic#code")] 18 | [assembly: AssemblyProduct("SharpDevelop")] 19 | [assembly: AssemblyCopyright("2000-2012 AlphaSierraPapa for the SharpDevelop Team")] 20 | [assembly: AssemblyVersion(RevisionClass.Major + "." + RevisionClass.Minor + "." + RevisionClass.Build + "." + RevisionClass.Revision)] 21 | [assembly: AssemblyInformationalVersion(RevisionClass.FullVersion + "-ca8a8e28")] 22 | [assembly: NeutralResourcesLanguage("en-US")] 23 | 24 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2243:AttributeStringLiteralsShouldParseCorrectly", 25 | Justification = "AssemblyInformationalVersion does not need to be a parsable version")] 26 | 27 | internal static class RevisionClass 28 | { 29 | public const string Major = "4"; 30 | public const string Minor = "2"; 31 | public const string Build = "0"; 32 | public const string Revision = "8752"; 33 | public const string VersionName = "Beta 2"; 34 | 35 | public const string FullVersion = Major + "." + Minor + "." + Build + ".8752-Beta 2"; 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a modified version of ICSharpCode.TreeView used by [dnSpy](https://github.com/0xd4d/dnSpy) 2 | 3 | Licensed under GPLv3. 4 | -------------------------------------------------------------------------------- /SharpGridView.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Controls; 9 | using System.Windows; 10 | 11 | namespace ICSharpCode.TreeView 12 | { 13 | public class SharpGridView : GridView 14 | { 15 | static SharpGridView() 16 | { 17 | ItemContainerStyleKey = 18 | new ComponentResourceKey(typeof(SharpTreeView), "GridViewItemContainerStyleKey"); 19 | } 20 | 21 | public static ResourceKey ItemContainerStyleKey { get; private set; } 22 | 23 | protected override object ItemContainerDefaultStyleKey 24 | { 25 | get 26 | { 27 | return ItemContainerStyleKey; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SharpTreeNode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Windows; 10 | using System.ComponentModel; 11 | using System.Collections.ObjectModel; 12 | using System.Windows.Controls; 13 | using System.Collections.Specialized; 14 | using System.Windows.Input; 15 | using System.Windows.Media; 16 | 17 | namespace ICSharpCode.TreeView 18 | { 19 | public partial class SharpTreeNode : INotifyPropertyChanged 20 | { 21 | SharpTreeNodeCollection modelChildren; 22 | internal SharpTreeNode modelParent; 23 | bool isVisible = true; 24 | 25 | void UpdateIsVisible(bool parentIsVisible, bool updateFlattener) 26 | { 27 | bool newIsVisible = parentIsVisible && !isHidden; 28 | if (isVisible != newIsVisible) { 29 | isVisible = newIsVisible; 30 | 31 | // invalidate the augmented data 32 | SharpTreeNode node = this; 33 | while (node != null && node.totalListLength >= 0) { 34 | node.totalListLength = -1; 35 | node = node.listParent; 36 | } 37 | // Remember the removed nodes: 38 | List removedNodes = null; 39 | if (updateFlattener && !newIsVisible) { 40 | removedNodes = VisibleDescendantsAndSelf().ToList(); 41 | } 42 | // also update the model children: 43 | UpdateChildIsVisible(false); 44 | 45 | // Validate our invariants: 46 | if (updateFlattener) 47 | CheckRootInvariants(); 48 | 49 | // Tell the flattener about the removed nodes: 50 | if (removedNodes != null) { 51 | var flattener = GetListRoot().treeFlattener; 52 | if (flattener != null) { 53 | flattener.NodesRemoved(GetVisibleIndexForNode(this), removedNodes); 54 | foreach (var n in removedNodes) 55 | n.OnIsVisibleChanged(); 56 | } 57 | } 58 | // Tell the flattener about the new nodes: 59 | if (updateFlattener && newIsVisible) { 60 | var flattener = GetListRoot().treeFlattener; 61 | if (flattener != null) { 62 | flattener.NodesInserted(GetVisibleIndexForNode(this), VisibleDescendantsAndSelf()); 63 | foreach (var n in VisibleDescendantsAndSelf()) 64 | n.OnIsVisibleChanged(); 65 | } 66 | } 67 | } 68 | } 69 | 70 | protected virtual void OnIsVisibleChanged() {} 71 | 72 | void UpdateChildIsVisible(bool updateFlattener) 73 | { 74 | if (modelChildren != null && modelChildren.Count > 0) { 75 | bool showChildren = isVisible && isExpanded; 76 | foreach (SharpTreeNode child in modelChildren) { 77 | child.UpdateIsVisible(showChildren, updateFlattener); 78 | } 79 | } 80 | } 81 | 82 | #region Main 83 | 84 | public SharpTreeNode() 85 | { 86 | } 87 | 88 | public SharpTreeNodeCollection Children { 89 | get { 90 | if (modelChildren == null) 91 | modelChildren = new SharpTreeNodeCollection(this); 92 | return modelChildren; 93 | } 94 | } 95 | 96 | public SharpTreeNode Parent { 97 | get { return modelParent; } 98 | } 99 | 100 | public virtual object Text 101 | { 102 | get { return null; } 103 | } 104 | 105 | public virtual Brush Foreground { 106 | get { return SystemColors.WindowTextBrush; } 107 | } 108 | 109 | public virtual object Icon 110 | { 111 | get { return null; } 112 | } 113 | 114 | public virtual object ToolTip 115 | { 116 | get { return null; } 117 | } 118 | 119 | public int Level 120 | { 121 | get { return Parent != null ? Parent.Level + 1 : 0; } 122 | } 123 | 124 | public bool IsRoot 125 | { 126 | get { return Parent == null; } 127 | } 128 | 129 | public virtual bool SingleClickExpandsChildren { 130 | get { return false; } 131 | } 132 | 133 | bool isHidden; 134 | 135 | public bool IsHidden 136 | { 137 | get { return isHidden; } 138 | set { 139 | if (isHidden != value) { 140 | isHidden = value; 141 | if (modelParent != null) 142 | UpdateIsVisible(modelParent.isVisible && modelParent.isExpanded, true); 143 | RaisePropertyChanged("IsHidden"); 144 | if (Parent != null) 145 | Parent.RaisePropertyChanged("ShowExpander"); 146 | } 147 | } 148 | } 149 | 150 | /// 151 | /// Return true when this node is not hidden and when all parent nodes are expanded and not hidden. 152 | /// 153 | public bool IsVisible { 154 | get { return isVisible; } 155 | } 156 | 157 | bool isSelected; 158 | 159 | public bool IsSelected { 160 | get { return isSelected; } 161 | set { 162 | if (isSelected != value) { 163 | isSelected = value; 164 | RaisePropertyChanged("IsSelected"); 165 | } 166 | } 167 | } 168 | 169 | #endregion 170 | 171 | #region OnChildrenChanged 172 | internal protected virtual void OnChildrenChanged(NotifyCollectionChangedEventArgs e) 173 | { 174 | var flattener = GetListRoot().treeFlattener; 175 | 176 | if (e.OldItems != null) { 177 | foreach (SharpTreeNode node in e.OldItems) { 178 | Debug.Assert(node.modelParent == this); 179 | node.modelParent = null; 180 | //Debug.WriteLine("Removing {0} from {1}", node, this); 181 | SharpTreeNode removeEnd = node; 182 | while (removeEnd.modelChildren != null && removeEnd.modelChildren.Count > 0) 183 | removeEnd = removeEnd.modelChildren.Last(); 184 | 185 | List removedNodes = null; 186 | int visibleIndexOfRemoval = 0; 187 | if (node.isVisible) { 188 | visibleIndexOfRemoval = GetVisibleIndexForNode(node); 189 | removedNodes = node.VisibleDescendantsAndSelf().ToList(); 190 | } 191 | 192 | RemoveNodes(node, removeEnd); 193 | 194 | if (removedNodes != null) { 195 | if (flattener != null) { 196 | flattener.NodesRemoved(visibleIndexOfRemoval, removedNodes); 197 | } 198 | } 199 | } 200 | } 201 | if (e.NewItems != null) { 202 | SharpTreeNode insertionPos; 203 | if (e.NewStartingIndex == 0) 204 | insertionPos = null; 205 | else 206 | insertionPos = modelChildren[e.NewStartingIndex - 1]; 207 | 208 | foreach (SharpTreeNode node in e.NewItems) { 209 | Debug.Assert(node.modelParent == null); 210 | node.modelParent = this; 211 | node.UpdateIsVisible(isVisible && isExpanded, false); 212 | //Debug.WriteLine("Inserting {0} after {1}", node, insertionPos); 213 | 214 | while (insertionPos != null && insertionPos.modelChildren != null && insertionPos.modelChildren.Count > 0) { 215 | insertionPos = insertionPos.modelChildren.Last(); 216 | } 217 | InsertNodeAfter(insertionPos ?? this, node); 218 | 219 | insertionPos = node; 220 | if (node.isVisible) { 221 | if (flattener != null) { 222 | flattener.NodesInserted(GetVisibleIndexForNode(node), node.VisibleDescendantsAndSelf()); 223 | } 224 | } 225 | } 226 | } 227 | 228 | RaisePropertyChanged("ShowExpander"); 229 | RaiseIsLastChangedIfNeeded(e); 230 | } 231 | #endregion 232 | 233 | #region Expanding / LazyLoading 234 | 235 | public virtual object ExpandedIcon 236 | { 237 | get { return Icon; } 238 | } 239 | 240 | public virtual bool ShowExpander 241 | { 242 | get { return LazyLoading || Children.Any(c => !c.isHidden); } 243 | } 244 | 245 | bool isExpanded; 246 | 247 | public bool IsExpanded 248 | { 249 | get { return isExpanded; } 250 | set 251 | { 252 | if (isExpanded != value) { 253 | isExpanded = value; 254 | if (isExpanded) { 255 | EnsureLazyChildren(); 256 | OnExpanding(); 257 | } else { 258 | OnCollapsing(); 259 | } 260 | UpdateChildIsVisible(true); 261 | RaisePropertyChanged("IsExpanded"); 262 | } 263 | } 264 | } 265 | 266 | protected virtual void OnExpanding() {} 267 | protected virtual void OnCollapsing() {} 268 | 269 | bool lazyLoading; 270 | 271 | public bool LazyLoading 272 | { 273 | get { return lazyLoading; } 274 | set 275 | { 276 | lazyLoading = value; 277 | if (lazyLoading) { 278 | IsExpanded = false; 279 | if (canExpandRecursively) { 280 | canExpandRecursively = false; 281 | RaisePropertyChanged("CanExpandRecursively"); 282 | } 283 | } 284 | RaisePropertyChanged("LazyLoading"); 285 | RaisePropertyChanged("ShowExpander"); 286 | } 287 | } 288 | 289 | bool canExpandRecursively = true; 290 | 291 | /// 292 | /// Gets whether this node can be expanded recursively. 293 | /// If not overridden, this property returns false if the node is using lazy-loading, and true otherwise. 294 | /// 295 | public virtual bool CanExpandRecursively { 296 | get { return canExpandRecursively; } 297 | } 298 | 299 | public virtual bool ShowIcon 300 | { 301 | get { return Icon != null; } 302 | } 303 | 304 | protected virtual void LoadChildren() 305 | { 306 | throw new NotSupportedException(GetType().Name + " does not support lazy loading"); 307 | } 308 | 309 | /// 310 | /// Ensures the children were initialized (loads children if lazy loading is enabled) 311 | /// 312 | public void EnsureLazyChildren() 313 | { 314 | if (LazyLoading) { 315 | LazyLoading = false; 316 | LoadChildren(); 317 | } 318 | } 319 | 320 | #endregion 321 | 322 | #region Ancestors / Descendants 323 | 324 | public IEnumerable Descendants() 325 | { 326 | return TreeTraversal.PreOrder(this.Children, n => n.Children); 327 | } 328 | 329 | public IEnumerable DescendantsAndSelf() 330 | { 331 | return TreeTraversal.PreOrder(this, n => n.Children); 332 | } 333 | 334 | internal IEnumerable VisibleDescendants() 335 | { 336 | return TreeTraversal.PreOrder(this.Children.Where(c => c.isVisible), n => n.Children.Where(c => c.isVisible)); 337 | } 338 | 339 | internal IEnumerable VisibleDescendantsAndSelf() 340 | { 341 | return TreeTraversal.PreOrder(this, n => n.Children.Where(c => c.isVisible)); 342 | } 343 | 344 | public IEnumerable Ancestors() 345 | { 346 | var node = this; 347 | while (node.Parent != null) { 348 | yield return node.Parent; 349 | node = node.Parent; 350 | } 351 | } 352 | 353 | public IEnumerable AncestorsAndSelf() 354 | { 355 | yield return this; 356 | foreach (var node in Ancestors()) { 357 | yield return node; 358 | } 359 | } 360 | 361 | #endregion 362 | 363 | #region Editing 364 | 365 | public virtual bool IsEditable 366 | { 367 | get { return false; } 368 | } 369 | 370 | bool isEditing; 371 | 372 | public bool IsEditing 373 | { 374 | get { return isEditing; } 375 | set 376 | { 377 | if (isEditing != value) { 378 | isEditing = value; 379 | RaisePropertyChanged("IsEditing"); 380 | } 381 | } 382 | } 383 | 384 | public virtual string LoadEditText() 385 | { 386 | return null; 387 | } 388 | 389 | public virtual bool SaveEditText(string value) 390 | { 391 | return true; 392 | } 393 | 394 | #endregion 395 | 396 | #region Checkboxes 397 | 398 | public virtual bool IsCheckable { 399 | get { return false; } 400 | } 401 | 402 | bool? isChecked; 403 | 404 | public bool? IsChecked { 405 | get { return isChecked; } 406 | set { 407 | SetIsChecked(value, true); 408 | } 409 | } 410 | 411 | void SetIsChecked(bool? value, bool update) 412 | { 413 | if (isChecked != value) { 414 | isChecked = value; 415 | 416 | if (update) { 417 | if (IsChecked != null) { 418 | foreach (var child in Descendants()) { 419 | if (child.IsCheckable) { 420 | child.SetIsChecked(IsChecked, false); 421 | } 422 | } 423 | } 424 | 425 | foreach (var parent in Ancestors()) { 426 | if (parent.IsCheckable) { 427 | if (!parent.TryValueForIsChecked(true)) { 428 | if (!parent.TryValueForIsChecked(false)) { 429 | parent.SetIsChecked(null, false); 430 | } 431 | } 432 | } 433 | } 434 | } 435 | 436 | RaisePropertyChanged("IsChecked"); 437 | } 438 | } 439 | 440 | bool TryValueForIsChecked(bool? value) 441 | { 442 | if (Children.Where(n => n.IsCheckable).All(n => n.IsChecked == value)) { 443 | SetIsChecked(value, false); 444 | return true; 445 | } 446 | return false; 447 | } 448 | 449 | #endregion 450 | 451 | #region Cut / Copy / Paste / Delete 452 | 453 | public bool IsCut { get { return false; } } 454 | /* 455 | static List cuttedNodes = new List(); 456 | static IDataObject cuttedData; 457 | static EventHandler requerySuggestedHandler; // for weak event 458 | 459 | static void StartCuttedDataWatcher() 460 | { 461 | requerySuggestedHandler = new EventHandler(CommandManager_RequerySuggested); 462 | CommandManager.RequerySuggested += requerySuggestedHandler; 463 | } 464 | 465 | static void CommandManager_RequerySuggested(object sender, EventArgs e) 466 | { 467 | if (cuttedData != null && !Clipboard.IsCurrent(cuttedData)) { 468 | ClearCuttedData(); 469 | } 470 | } 471 | 472 | static void ClearCuttedData() 473 | { 474 | foreach (var node in cuttedNodes) { 475 | node.IsCut = false; 476 | } 477 | cuttedNodes.Clear(); 478 | cuttedData = null; 479 | } 480 | 481 | //static public IEnumerable PurifyNodes(IEnumerable nodes) 482 | //{ 483 | // var list = nodes.ToList(); 484 | // var array = list.ToArray(); 485 | // foreach (var node1 in array) { 486 | // foreach (var node2 in array) { 487 | // if (node1.Descendants().Contains(node2)) { 488 | // list.Remove(node2); 489 | // } 490 | // } 491 | // } 492 | // return list; 493 | //} 494 | 495 | bool isCut; 496 | 497 | public bool IsCut 498 | { 499 | get { return isCut; } 500 | private set 501 | { 502 | isCut = value; 503 | RaisePropertyChanged("IsCut"); 504 | } 505 | } 506 | 507 | internal bool InternalCanCut() 508 | { 509 | return InternalCanCopy() && InternalCanDelete(); 510 | } 511 | 512 | internal void InternalCut() 513 | { 514 | ClearCuttedData(); 515 | cuttedData = Copy(ActiveNodesArray); 516 | Clipboard.SetDataObject(cuttedData); 517 | 518 | foreach (var node in ActiveNodes) { 519 | node.IsCut = true; 520 | cuttedNodes.Add(node); 521 | } 522 | } 523 | 524 | internal bool InternalCanCopy() 525 | { 526 | return CanCopy(ActiveNodesArray); 527 | } 528 | 529 | internal void InternalCopy() 530 | { 531 | Clipboard.SetDataObject(Copy(ActiveNodesArray)); 532 | } 533 | 534 | internal bool InternalCanPaste() 535 | { 536 | return CanPaste(Clipboard.GetDataObject()); 537 | } 538 | 539 | internal void InternalPaste() 540 | { 541 | Paste(Clipboard.GetDataObject()); 542 | 543 | if (cuttedData != null) { 544 | DeleteCore(cuttedNodes.ToArray()); 545 | ClearCuttedData(); 546 | } 547 | } 548 | */ 549 | 550 | public virtual bool CanDelete() 551 | { 552 | return false; 553 | } 554 | 555 | public virtual void Delete() 556 | { 557 | throw new NotSupportedException(GetType().Name + " does not support deletion"); 558 | } 559 | 560 | public virtual void DeleteCore() 561 | { 562 | throw new NotSupportedException(GetType().Name + " does not support deletion"); 563 | } 564 | 565 | public virtual IDataObject Copy(SharpTreeNode[] nodes) 566 | { 567 | throw new NotSupportedException(GetType().Name + " does not support copy/paste or drag'n'drop"); 568 | } 569 | 570 | /* 571 | public virtual bool CanCopy(SharpTreeNode[] nodes) 572 | { 573 | return false; 574 | } 575 | 576 | public virtual bool CanPaste(IDataObject data) 577 | { 578 | return false; 579 | } 580 | 581 | public virtual void Paste(IDataObject data) 582 | { 583 | EnsureLazyChildren(); 584 | Drop(data, Children.Count, DropEffect.Copy); 585 | } 586 | */ 587 | #endregion 588 | 589 | #region Drag and Drop 590 | public virtual bool CanDrag(SharpTreeNode[] nodes) 591 | { 592 | return false; 593 | } 594 | 595 | public virtual void StartDrag(DependencyObject dragSource, SharpTreeNode[] nodes) 596 | { 597 | DragDropEffects effects = DragDropEffects.All; 598 | if (!nodes.All(n => n.CanDelete())) 599 | effects &= ~DragDropEffects.Move; 600 | DragDropEffects result = DragDrop.DoDragDrop(dragSource, Copy(nodes), effects); 601 | if (result == DragDropEffects.Move) { 602 | foreach (SharpTreeNode node in nodes) 603 | node.DeleteCore(); 604 | } 605 | } 606 | 607 | public virtual bool CanDrop(DragEventArgs e, int index) 608 | { 609 | return false; 610 | } 611 | 612 | internal void InternalDrop(DragEventArgs e, int index) 613 | { 614 | if (LazyLoading) { 615 | EnsureLazyChildren(); 616 | index = Children.Count; 617 | } 618 | 619 | Drop(e, index); 620 | } 621 | 622 | public virtual void Drop(DragEventArgs e, int index) 623 | { 624 | throw new NotSupportedException(GetType().Name + " does not support Drop()"); 625 | } 626 | #endregion 627 | 628 | #region IsLast (for TreeView lines) 629 | 630 | public bool IsLast 631 | { 632 | get 633 | { 634 | return Parent == null || 635 | Parent.Children[Parent.Children.Count - 1] == this; 636 | } 637 | } 638 | 639 | void RaiseIsLastChangedIfNeeded(NotifyCollectionChangedEventArgs e) 640 | { 641 | switch (e.Action) { 642 | case NotifyCollectionChangedAction.Add: 643 | if (e.NewStartingIndex == Children.Count - 1) { 644 | if (Children.Count > 1) { 645 | Children[Children.Count - 2].RaisePropertyChanged("IsLast"); 646 | } 647 | Children[Children.Count - 1].RaisePropertyChanged("IsLast"); 648 | } 649 | break; 650 | case NotifyCollectionChangedAction.Remove: 651 | if (e.OldStartingIndex == Children.Count) { 652 | if (Children.Count > 0) { 653 | Children[Children.Count - 1].RaisePropertyChanged("IsLast"); 654 | } 655 | } 656 | break; 657 | } 658 | } 659 | 660 | #endregion 661 | 662 | #region INotifyPropertyChanged Members 663 | 664 | public event PropertyChangedEventHandler PropertyChanged; 665 | 666 | protected bool HasPropertyChangedHandlers { 667 | get { return PropertyChanged != null; } 668 | } 669 | 670 | public void RaisePropertyChanged(string name) 671 | { 672 | if (PropertyChanged != null) { 673 | PropertyChanged(this, new PropertyChangedEventArgs(name)); 674 | } 675 | } 676 | 677 | #endregion 678 | 679 | /// 680 | /// Gets called when the item is double-clicked. 681 | /// 682 | public virtual void ActivateItem(RoutedEventArgs e) 683 | { 684 | } 685 | 686 | public override string ToString() 687 | { 688 | // used for keyboard navigation 689 | object text = this.Text; 690 | return text != null ? text.ToString() : string.Empty; 691 | } 692 | } 693 | } 694 | -------------------------------------------------------------------------------- /SharpTreeNodeCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Collections.ObjectModel; 10 | using System.Collections.Specialized; 11 | 12 | namespace ICSharpCode.TreeView 13 | { 14 | /// 15 | /// Collection that validates that inserted nodes do not have another parent. 16 | /// 17 | public sealed class SharpTreeNodeCollection : IList, INotifyCollectionChanged 18 | { 19 | readonly SharpTreeNode parent; 20 | List list = new List(); 21 | bool isRaisingEvent; 22 | 23 | public SharpTreeNodeCollection(SharpTreeNode parent) 24 | { 25 | this.parent = parent; 26 | } 27 | 28 | public event NotifyCollectionChangedEventHandler CollectionChanged; 29 | 30 | void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 31 | { 32 | Debug.Assert(!isRaisingEvent); 33 | isRaisingEvent = true; 34 | try { 35 | parent.OnChildrenChanged(e); 36 | if (CollectionChanged != null) 37 | CollectionChanged(this, e); 38 | } finally { 39 | isRaisingEvent = false; 40 | } 41 | } 42 | 43 | void ThrowOnReentrancy() 44 | { 45 | if (isRaisingEvent) 46 | throw new InvalidOperationException(); 47 | } 48 | 49 | void ThrowIfValueIsNullOrHasParent(SharpTreeNode node) 50 | { 51 | if (node == null) 52 | throw new ArgumentNullException("node"); 53 | if (node.modelParent != null) 54 | throw new ArgumentException("The node already has a parent", "node"); 55 | } 56 | 57 | public SharpTreeNode this[int index] { 58 | get { 59 | return list[index]; 60 | } 61 | set { 62 | ThrowOnReentrancy(); 63 | var oldItem = list[index]; 64 | if (oldItem == value) 65 | return; 66 | ThrowIfValueIsNullOrHasParent(value); 67 | list[index] = value; 68 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldItem, index)); 69 | } 70 | } 71 | 72 | public int Count { 73 | get { return list.Count; } 74 | } 75 | 76 | bool ICollection.IsReadOnly { 77 | get { return false; } 78 | } 79 | 80 | public int IndexOf(SharpTreeNode node) 81 | { 82 | if (node == null || node.modelParent != parent) 83 | return -1; 84 | else 85 | return list.IndexOf(node); 86 | } 87 | 88 | public void Insert(int index, SharpTreeNode node) 89 | { 90 | ThrowOnReentrancy(); 91 | ThrowIfValueIsNullOrHasParent(node); 92 | list.Insert(index, node); 93 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, node, index)); 94 | } 95 | 96 | public void InsertRange(int index, IEnumerable nodes) 97 | { 98 | if (nodes == null) 99 | throw new ArgumentNullException("nodes"); 100 | ThrowOnReentrancy(); 101 | List newNodes = nodes.ToList(); 102 | if (newNodes.Count == 0) 103 | return; 104 | foreach (SharpTreeNode node in newNodes) { 105 | ThrowIfValueIsNullOrHasParent(node); 106 | } 107 | list.InsertRange(index, newNodes); 108 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newNodes, index)); 109 | } 110 | 111 | public void RemoveAt(int index) 112 | { 113 | ThrowOnReentrancy(); 114 | var oldItem = list[index]; 115 | list.RemoveAt(index); 116 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItem, index)); 117 | } 118 | 119 | public void RemoveRange(int index, int count) 120 | { 121 | ThrowOnReentrancy(); 122 | if (count == 0) 123 | return; 124 | var oldItems = list.GetRange(index, count); 125 | list.RemoveRange(index, count); 126 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, index)); 127 | } 128 | 129 | public void Add(SharpTreeNode node) 130 | { 131 | ThrowOnReentrancy(); 132 | ThrowIfValueIsNullOrHasParent(node); 133 | list.Add(node); 134 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, node, list.Count - 1)); 135 | } 136 | 137 | public void AddRange(IEnumerable nodes) 138 | { 139 | InsertRange(this.Count, nodes); 140 | } 141 | 142 | public void Clear() 143 | { 144 | ThrowOnReentrancy(); 145 | var oldList = list; 146 | list = new List(); 147 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldList, 0)); 148 | } 149 | 150 | public bool Contains(SharpTreeNode node) 151 | { 152 | return IndexOf(node) >= 0; 153 | } 154 | 155 | public void CopyTo(SharpTreeNode[] array, int arrayIndex) 156 | { 157 | list.CopyTo(array, arrayIndex); 158 | } 159 | 160 | public bool Remove(SharpTreeNode item) 161 | { 162 | int pos = IndexOf(item); 163 | if (pos >= 0) { 164 | RemoveAt(pos); 165 | return true; 166 | } else { 167 | return false; 168 | } 169 | } 170 | 171 | public IEnumerator GetEnumerator() 172 | { 173 | return list.GetEnumerator(); 174 | } 175 | 176 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 177 | { 178 | return list.GetEnumerator(); 179 | } 180 | 181 | public void RemoveAll(Predicate match) 182 | { 183 | if (match == null) 184 | throw new ArgumentNullException("match"); 185 | ThrowOnReentrancy(); 186 | int firstToRemove = 0; 187 | for (int i = 0; i < list.Count; i++) { 188 | bool removeNode; 189 | isRaisingEvent = true; 190 | try { 191 | removeNode = match(list[i]); 192 | } finally { 193 | isRaisingEvent = false; 194 | } 195 | if (!removeNode) { 196 | if (firstToRemove < i) { 197 | RemoveRange(firstToRemove, i - firstToRemove); 198 | i = firstToRemove - 1; 199 | } else { 200 | firstToRemove = i + 1; 201 | } 202 | Debug.Assert(firstToRemove == i + 1); 203 | } 204 | } 205 | if (firstToRemove < list.Count) { 206 | RemoveRange(firstToRemove, list.Count - firstToRemove); 207 | } 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /SharpTreeNodeProxy.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Ki 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.ComponentModel; 26 | using System.Linq; 27 | 28 | namespace ICSharpCode.TreeView { 29 | struct ObjectChangedEventArgs { 30 | public SharpTreeNode OldNode { get; } 31 | public SharpTreeNode NewNode { get; } 32 | public ObjectChangedEventArgs(SharpTreeNode oldNode, SharpTreeNode newNode) { 33 | OldNode = oldNode; 34 | NewNode = newNode; 35 | } 36 | } 37 | 38 | internal class SharpTreeNodeProxy : CustomTypeDescriptor { 39 | static SharpTreeNodeProxy() { 40 | descMap = new Dictionary(); 41 | AddPropertyDesc("Foreground", node => node.Foreground); 42 | AddPropertyDesc("IsExpanded", node => node.IsExpanded, (node, value) => node.IsExpanded = value); 43 | AddPropertyDesc("IsChecked", node => node.IsChecked, (node, value) => node.IsChecked = value); 44 | AddPropertyDesc("ToolTip", node => node.ToolTip); 45 | AddPropertyDesc("Icon", node => node.Icon); 46 | AddPropertyDesc("Text", node => node.Text); 47 | AddPropertyDesc("IsEditing", node => node.IsEditing, (node, value) => node.IsEditing = value); 48 | AddPropertyDesc("ShowIcon", node => node.ShowIcon); 49 | AddPropertyDesc("ShowExpander", node => node.ShowExpander); 50 | AddPropertyDesc("ExpandedIcon", node => node.ExpandedIcon); 51 | AddPropertyDesc("IsCheckable", node => node.IsCheckable); 52 | AddPropertyDesc("IsCut", node => node.IsCut); 53 | descs = new PropertyDescriptorCollection(descMap.Values.Cast().ToArray()); 54 | } 55 | 56 | static readonly PropertyDescriptorCollection descs; 57 | static readonly Dictionary descMap; 58 | 59 | static void AddPropertyDesc(string name, Func getter, Action setter = null) { 60 | var desc = new PropDesc(name, getter, setter); 61 | descMap.Add(name, desc); 62 | } 63 | 64 | public SharpTreeNodeProxy(SharpTreeNode obj) { 65 | UpdateObject(obj); 66 | } 67 | 68 | public void UpdateObject(SharpTreeNode obj) { 69 | if (Object == obj) 70 | return; 71 | 72 | if (Object != null) 73 | Object.PropertyChanged -= OnPropertyChanged; 74 | 75 | var oldNode = Object; 76 | Object = obj; 77 | 78 | if (obj == null) 79 | IsNull = true; 80 | else { 81 | IsNull = false; 82 | obj.PropertyChanged += OnPropertyChanged; 83 | 84 | foreach (var desc in descMap) { 85 | desc.Value.OnValueChanged(this); 86 | } 87 | } 88 | 89 | if (ObjectChanged != null) 90 | ObjectChanged(this, new ObjectChangedEventArgs(oldNode, obj)); 91 | } 92 | 93 | void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { 94 | IPropDesc desc; 95 | if (descMap.TryGetValue(e.PropertyName, out desc)) 96 | desc.OnValueChanged(this); 97 | } 98 | 99 | public event EventHandler ObjectChanged; 100 | 101 | public bool IsNull { get; private set; } 102 | public SharpTreeNode Object { get; private set; } 103 | 104 | public override PropertyDescriptorCollection GetProperties() { 105 | return descs; 106 | } 107 | 108 | public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) { 109 | return GetProperties(); 110 | } 111 | 112 | interface IPropDesc { 113 | void OnValueChanged(object component); 114 | } 115 | 116 | class PropDesc : PropertyDescriptor, IPropDesc { 117 | readonly Func getter; 118 | readonly Action setter; 119 | 120 | public PropDesc(string name, Func getter, Action setter) 121 | : base(name, null) { 122 | this.getter = getter; 123 | this.setter = setter; 124 | } 125 | 126 | public override object GetValue(object component) { 127 | return getter(((SharpTreeNodeProxy)component).Object); 128 | } 129 | 130 | public override bool IsReadOnly { 131 | get { 132 | return setter == null; 133 | ; 134 | } 135 | } 136 | 137 | public override Type PropertyType { 138 | get { return typeof(T); } 139 | } 140 | 141 | public override void SetValue(object component, object value) { 142 | setter(((SharpTreeNodeProxy)component).Object, (T)value); 143 | } 144 | 145 | public void OnValueChanged(object component) { 146 | OnValueChanged(component, new PropertyChangedEventArgs(Name)); 147 | } 148 | 149 | public override bool CanResetValue(object component) { 150 | return false; 151 | } 152 | 153 | public override bool ShouldSerializeValue(object component) { 154 | return false; 155 | } 156 | 157 | public override void ResetValue(object component) { 158 | throw new NotSupportedException(); 159 | } 160 | 161 | public override Type ComponentType { 162 | get { return null; } 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /SharpTreeNodeView.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Controls; 9 | using System.Windows; 10 | using System.Windows.Controls.Primitives; 11 | using System.Windows.Media; 12 | using System.Windows.Input; 13 | using System.ComponentModel; 14 | using System.Collections.Specialized; 15 | 16 | namespace ICSharpCode.TreeView 17 | { 18 | public class SharpTreeNodeView : Control 19 | { 20 | static SharpTreeNodeView() 21 | { 22 | DefaultStyleKeyProperty.OverrideMetadata(typeof(SharpTreeNodeView), 23 | new FrameworkPropertyMetadata(typeof(SharpTreeNodeView))); 24 | } 25 | 26 | public static readonly DependencyProperty TextBackgroundProperty = 27 | DependencyProperty.Register("TextBackground", typeof(Brush), typeof(SharpTreeNodeView)); 28 | 29 | public Brush TextBackground 30 | { 31 | get { return (Brush)GetValue(TextBackgroundProperty); } 32 | set { SetValue(TextBackgroundProperty, value); } 33 | } 34 | 35 | SharpTreeNode GetNode(object dataCtx) 36 | { 37 | if (dataCtx is SharpTreeNode) 38 | return (SharpTreeNode)dataCtx; 39 | else if (dataCtx is SharpTreeNodeProxy) 40 | return ((SharpTreeNodeProxy)dataCtx).Object; 41 | else 42 | return null; 43 | } 44 | 45 | public SharpTreeNode Node 46 | { 47 | get { return GetNode(DataContext); } 48 | } 49 | 50 | public SharpTreeViewItem ParentItem { get; private set; } 51 | 52 | public static readonly DependencyProperty CellEditorProperty = 53 | DependencyProperty.Register("CellEditor", typeof(Control), typeof(SharpTreeNodeView), 54 | new FrameworkPropertyMetadata()); 55 | 56 | public Control CellEditor { 57 | get { return (Control)GetValue(CellEditorProperty); } 58 | set { SetValue(CellEditorProperty, value); } 59 | } 60 | 61 | public SharpTreeView ParentTreeView 62 | { 63 | get { return ParentItem.ParentTreeView; } 64 | } 65 | 66 | internal LinesRenderer LinesRenderer { get; private set; } 67 | 68 | public override void OnApplyTemplate() 69 | { 70 | base.OnApplyTemplate(); 71 | LinesRenderer = Template.FindName("linesRenderer", this) as LinesRenderer; 72 | UpdateTemplate(); 73 | } 74 | 75 | protected override void OnVisualParentChanged(DependencyObject oldParent) 76 | { 77 | base.OnVisualParentChanged(oldParent); 78 | ParentItem = this.FindAncestor(); 79 | ParentItem.NodeView = this; 80 | } 81 | 82 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) 83 | { 84 | base.OnPropertyChanged(e); 85 | if (e.Property == DataContextProperty) { 86 | UpdateDataContext(e.OldValue as SharpTreeNodeProxy, e.NewValue as SharpTreeNodeProxy); 87 | } 88 | } 89 | 90 | void UpdateDataContext(SharpTreeNodeProxy oldAdaptor, SharpTreeNodeProxy newAdaptor) 91 | { 92 | if (newAdaptor != null) { 93 | newAdaptor.ObjectChanged += AdaptorObjectChanged; 94 | if (newAdaptor.Object != null) 95 | newAdaptor.Object.PropertyChanged += Node_PropertyChanged; 96 | } 97 | if (oldAdaptor != null) { 98 | oldAdaptor.ObjectChanged -= AdaptorObjectChanged; 99 | if (oldAdaptor.Object != null) 100 | oldAdaptor.Object.PropertyChanged -= Node_PropertyChanged; 101 | } 102 | } 103 | 104 | void AdaptorObjectChanged(object sender, ObjectChangedEventArgs e) { 105 | if (e.OldNode != null) 106 | e.OldNode.PropertyChanged -= Node_PropertyChanged; 107 | 108 | if (e.NewNode != null) 109 | e.NewNode.PropertyChanged += Node_PropertyChanged; 110 | 111 | UpdateTemplate(); 112 | } 113 | 114 | protected virtual void Node_PropertyChanged(object sender, PropertyChangedEventArgs e) 115 | { 116 | var node = (SharpTreeNode)sender; 117 | if (e.PropertyName == "IsEditing") { 118 | OnIsEditingChanged(); 119 | } else if (e.PropertyName == "IsLast") { 120 | if (ParentTreeView.ShowLines && Node != null) { 121 | foreach (var child in Node.VisibleDescendantsAndSelf()) { 122 | var container = ParentTreeView.ItemContainerGenerator.ContainerFromItem(child) as SharpTreeViewItem; 123 | if (container != null && container.NodeView != null && container.NodeView.LinesRenderer != null) { 124 | container.NodeView.LinesRenderer.InvalidateVisual(); 125 | } 126 | } 127 | } 128 | } else if (e.PropertyName == "IsExpanded") { 129 | if (node.IsExpanded) 130 | ParentTreeView.HandleExpanding(node); 131 | } 132 | } 133 | 134 | void OnIsEditingChanged() 135 | { 136 | if (Template == null) 137 | return; 138 | 139 | var textEditorContainer = Template.FindName("textEditorContainer", this) as Border; 140 | if (textEditorContainer != null && Node != null && Node.IsEditing) { 141 | if (CellEditor == null) 142 | textEditorContainer.Child = new EditTextBox() { Item = ParentItem }; 143 | else 144 | textEditorContainer.Child = CellEditor; 145 | } 146 | else { 147 | textEditorContainer.Child = null; 148 | } 149 | } 150 | 151 | protected internal virtual void UpdateTemplate() 152 | { 153 | if (Template == null || Node == null) 154 | return; 155 | 156 | var spacer = Template.FindName("spacer", this) as FrameworkElement; 157 | spacer.Width = CalculateIndent(Node); 158 | 159 | var expander = Template.FindName("expander", this) as ToggleButton; 160 | if (ParentTreeView.Root == Node && !ParentTreeView.ShowRootExpander) { 161 | expander.Visibility = Visibility.Collapsed; 162 | } 163 | else { 164 | expander.ClearValue(VisibilityProperty); 165 | } 166 | } 167 | 168 | protected internal double CalculateIndent(SharpTreeNode node) 169 | { 170 | var result = 19 * node.Level; 171 | if (ParentTreeView.ShowRoot) { 172 | if (!ParentTreeView.ShowRootExpander) { 173 | if (ParentTreeView.Root != node) { 174 | result -= 15; 175 | } 176 | } 177 | } 178 | else { 179 | result -= 19; 180 | } 181 | if (result < 0) { 182 | // BUG: Node is sometimes an already deleted node so result will be set to 0 above 183 | // and will eventually get a negative value. 184 | result = 0; 185 | } 186 | if (result < 0) 187 | throw new InvalidOperationException(); 188 | return result; 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /SharpTreeView.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.Specialized; 7 | using System.Diagnostics; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Windows; 11 | using System.Windows.Controls; 12 | using System.Windows.Controls.Primitives; 13 | using System.Windows.Data; 14 | using System.Windows.Documents; 15 | using System.Windows.Input; 16 | using System.Windows.Media; 17 | using System.Windows.Threading; 18 | 19 | namespace ICSharpCode.TreeView 20 | { 21 | public class SharpTreeView : ListView 22 | { 23 | static SharpTreeView() 24 | { 25 | DefaultStyleKeyProperty.OverrideMetadata(typeof(SharpTreeView), 26 | new FrameworkPropertyMetadata(typeof(SharpTreeView))); 27 | 28 | SelectionModeProperty.OverrideMetadata(typeof(SharpTreeView), 29 | new FrameworkPropertyMetadata(SelectionMode.Extended)); 30 | 31 | AlternationCountProperty.OverrideMetadata(typeof(SharpTreeView), 32 | new FrameworkPropertyMetadata(2)); 33 | 34 | DefaultItemContainerStyleKey = 35 | new ComponentResourceKey(typeof(SharpTreeView), "DefaultItemContainerStyleKey"); 36 | 37 | VirtualizingStackPanel.VirtualizationModeProperty.OverrideMetadata(typeof(SharpTreeView), 38 | new FrameworkPropertyMetadata(VirtualizationMode.Recycling)); 39 | 40 | RegisterCommands(); 41 | } 42 | 43 | public static ResourceKey DefaultItemContainerStyleKey { get; private set; } 44 | 45 | public SharpTreeView() 46 | { 47 | SetResourceReference(ItemContainerStyleProperty, DefaultItemContainerStyleKey); 48 | } 49 | 50 | public static readonly DependencyProperty CanDragAndDropProperty = 51 | DependencyProperty.Register("CanDragAndDrop", typeof(bool), typeof(SharpTreeView), new PropertyMetadata(true)); 52 | public bool CanDragAndDrop { 53 | get { return (bool)GetValue(CanDragAndDropProperty); } 54 | set { SetValue(CanDragAndDropProperty, value); } 55 | } 56 | 57 | public static readonly DependencyProperty RootProperty = 58 | DependencyProperty.Register("Root", typeof(SharpTreeNode), typeof(SharpTreeView)); 59 | 60 | public SharpTreeNode Root 61 | { 62 | get { return (SharpTreeNode)GetValue(RootProperty); } 63 | set { SetValue(RootProperty, value); } 64 | } 65 | 66 | public static readonly DependencyProperty ShowRootProperty = 67 | DependencyProperty.Register("ShowRoot", typeof(bool), typeof(SharpTreeView), 68 | new FrameworkPropertyMetadata(true)); 69 | 70 | public bool ShowRoot 71 | { 72 | get { return (bool)GetValue(ShowRootProperty); } 73 | set { SetValue(ShowRootProperty, value); } 74 | } 75 | 76 | public static readonly DependencyProperty ShowRootExpanderProperty = 77 | DependencyProperty.Register("ShowRootExpander", typeof(bool), typeof(SharpTreeView), 78 | new FrameworkPropertyMetadata(false)); 79 | 80 | public bool ShowRootExpander 81 | { 82 | get { return (bool)GetValue(ShowRootExpanderProperty); } 83 | set { SetValue(ShowRootExpanderProperty, value); } 84 | } 85 | 86 | public static readonly DependencyProperty AllowDropOrderProperty = 87 | DependencyProperty.Register("AllowDropOrder", typeof(bool), typeof(SharpTreeView)); 88 | 89 | public bool AllowDropOrder 90 | { 91 | get { return (bool)GetValue(AllowDropOrderProperty); } 92 | set { SetValue(AllowDropOrderProperty, value); } 93 | } 94 | 95 | public static readonly DependencyProperty ShowLinesProperty = 96 | DependencyProperty.Register("ShowLines", typeof(bool), typeof(SharpTreeView), 97 | new FrameworkPropertyMetadata(true)); 98 | 99 | public bool ShowLines 100 | { 101 | get { return (bool)GetValue(ShowLinesProperty); } 102 | set { SetValue(ShowLinesProperty, value); } 103 | } 104 | 105 | public static bool GetShowAlternation(DependencyObject obj) 106 | { 107 | return (bool)obj.GetValue(ShowAlternationProperty); 108 | } 109 | 110 | public static void SetShowAlternation(DependencyObject obj, bool value) 111 | { 112 | obj.SetValue(ShowAlternationProperty, value); 113 | } 114 | 115 | public static readonly DependencyProperty ShowAlternationProperty = 116 | DependencyProperty.RegisterAttached("ShowAlternation", typeof(bool), typeof(SharpTreeView), 117 | new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits)); 118 | 119 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) 120 | { 121 | base.OnPropertyChanged(e); 122 | if (e.Property == RootProperty || 123 | e.Property == ShowRootProperty || 124 | e.Property == ShowRootExpanderProperty) { 125 | Reload(); 126 | } 127 | } 128 | 129 | TreeFlattener flattener; 130 | bool updatesLocked; 131 | 132 | void Reload() 133 | { 134 | if (flattener != null) { 135 | flattener.Stop(); 136 | } 137 | if (Root != null) { 138 | if (!(ShowRoot && ShowRootExpander)) { 139 | Root.IsExpanded = true; 140 | } 141 | flattener = new TreeFlattener(Root, ShowRoot); 142 | flattener.CollectionChanged += flattener_CollectionChanged; 143 | this.ItemsSource = flattener; 144 | } 145 | } 146 | 147 | void flattener_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 148 | { 149 | // Deselect nodes that are being hidden, if any remain in the tree 150 | if (e.Action == NotifyCollectionChangedAction.Remove && Items.Count > 0) { 151 | List selectedOldItems = null; 152 | foreach (SharpTreeNode node in e.OldItems) { 153 | if (node.IsSelected) { 154 | if (selectedOldItems == null) 155 | selectedOldItems = new List(); 156 | selectedOldItems.Add(node); 157 | } 158 | } 159 | if (!updatesLocked && selectedOldItems != null) { 160 | var list = SelectedItems.Cast().Except(selectedOldItems).ToList(); 161 | UpdateFocusedNode(list, Math.Max(0, e.OldStartingIndex - 1)); 162 | } 163 | } 164 | } 165 | 166 | void UpdateFocusedNode(List newSelection, int topSelectedIndex) 167 | { 168 | if (updatesLocked) return; 169 | SetSelectedItems(newSelection ?? Enumerable.Empty()); 170 | if (SelectedItem == null) { 171 | SelectedIndex = topSelectedIndex; 172 | if (SelectedItem != null && IsKeyboardFocusWithin) 173 | FocusNode((SharpTreeNode)SelectedItem); 174 | } 175 | } 176 | 177 | protected override void ClearContainerForItemOverride(DependencyObject element, object item) { 178 | var item2 = element as SharpTreeViewItem; 179 | if (item2 != null) { 180 | var nv = item2.NodeView; 181 | if (nv != null) 182 | nv.DataContext = null; 183 | item2.ClearValue(DataContextProperty); 184 | } 185 | base.ClearContainerForItemOverride(element, item); 186 | } 187 | 188 | protected override DependencyObject GetContainerForItemOverride() 189 | { 190 | return new SharpTreeViewItem(); 191 | } 192 | 193 | protected override bool IsItemItsOwnContainerOverride(object item) 194 | { 195 | return item is SharpTreeViewItem; 196 | } 197 | 198 | protected override void PrepareContainerForItemOverride(DependencyObject element, object item) 199 | { 200 | base.PrepareContainerForItemOverride(element, item); 201 | SharpTreeViewItem container = element as SharpTreeViewItem; 202 | container.ParentTreeView = this; 203 | // Make sure that the line renderer takes into account the new bound data 204 | if (container.NodeView != null && container.NodeView.LinesRenderer != null) { 205 | container.NodeView.LinesRenderer.InvalidateVisual(); 206 | } 207 | } 208 | 209 | bool doNotScrollOnExpanding; 210 | 211 | /// 212 | /// Handles the node expanding event in the tree view. 213 | /// This method gets called only if the node is in the visible region (a SharpTreeNodeView exists). 214 | /// 215 | internal void HandleExpanding(SharpTreeNode node) 216 | { 217 | if (doNotScrollOnExpanding) 218 | return; 219 | SharpTreeNode lastVisibleChild = node; 220 | while (true) { 221 | SharpTreeNode tmp = lastVisibleChild.Children.LastOrDefault(c => c.IsVisible); 222 | if (tmp != null) { 223 | lastVisibleChild = tmp; 224 | } else { 225 | break; 226 | } 227 | } 228 | if (lastVisibleChild != node) { 229 | Debug.Assert(flattener != null); 230 | if (flattener == null) 231 | return; 232 | // A new scrollViewer.ViewportHeight value is available at the earliest at Render prio. 233 | // If there are many empty lines, it will equal the number of visible items (eg. 5, 234 | // but viewport can show eg. 10). 235 | Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => { 236 | var itemsPerPage = STVUTILS.GetItemsPerPage(this, 0); 237 | int nodeIndex = flattener.IndexOf(node); 238 | int lastVisibleChildIndex = flattener.IndexOf(lastVisibleChild); 239 | Debug.Assert(nodeIndex >= 0 && lastVisibleChildIndex >= 0); 240 | if (itemsPerPage > 0 && nodeIndex >= 0 && lastVisibleChildIndex >= 0) { 241 | int lastIndex = Math.Min(lastVisibleChildIndex, nodeIndex + itemsPerPage - 1); 242 | ScrollIntoView(Items[lastIndex]); 243 | Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => ScrollIntoView(node))); 244 | } 245 | })); 246 | } 247 | } 248 | 249 | static class STVUTILS { 250 | static T FindVisualChild(DependencyObject obj) where T : DependencyObject { 251 | int childrenCount = VisualTreeHelper.GetChildrenCount(obj); 252 | for (int i = 0; i < childrenCount; i++) { 253 | var child = VisualTreeHelper.GetChild(obj, i); 254 | if (child is T res) 255 | return res; 256 | 257 | res = FindVisualChild(child); 258 | if (res != null) 259 | return res; 260 | } 261 | 262 | return null; 263 | } 264 | 265 | public static ScrollViewer TryGetScrollViewer(ListBox lb) => FindVisualChild(lb); 266 | 267 | public static int GetItemsPerPage(ListBox lb, int defaultValue) { 268 | var scrollViewer = TryGetScrollViewer(lb); 269 | if (scrollViewer == null) 270 | return defaultValue; 271 | return (int)Math.Max(1, Math.Floor(scrollViewer.ViewportHeight)); 272 | } 273 | } 274 | 275 | protected override void OnKeyDown(KeyEventArgs e) 276 | { 277 | SharpTreeViewItem container = e.OriginalSource as SharpTreeViewItem; 278 | if (container != null && container.Node != null) switch (e.Key) { 279 | case Key.Left: 280 | if (container != null && ItemsControl.ItemsControlFromItemContainer(container) == this) { 281 | if (container.Node.IsExpanded) { 282 | container.Node.IsExpanded = false; 283 | } else if (container.Node.Parent != null) { 284 | this.FocusNode(container.Node.Parent); 285 | } 286 | e.Handled = true; 287 | } 288 | break; 289 | case Key.Right: 290 | if (container != null && ItemsControl.ItemsControlFromItemContainer(container) == this) { 291 | if (!container.Node.IsExpanded && container.Node.ShowExpander) { 292 | container.Node.IsExpanded = true; 293 | } else if (container.Node.Children.Count > 0) { 294 | // jump to first child: 295 | container.MoveFocus(new TraversalRequest(FocusNavigationDirection.Down)); 296 | } 297 | e.Handled = true; 298 | } 299 | break; 300 | case Key.Return: 301 | case Key.Space: 302 | if (container != null && Keyboard.Modifiers == ModifierKeys.None && this.SelectedItems.Count == 1 && this.SelectedItem == container.Node) { 303 | container.Node.ActivateItem(e); 304 | } 305 | break; 306 | case Key.Add: 307 | if (container != null && ItemsControl.ItemsControlFromItemContainer(container) == this) { 308 | container.Node.IsExpanded = true; 309 | e.Handled = true; 310 | } 311 | break; 312 | case Key.Subtract: 313 | if (container != null && ItemsControl.ItemsControlFromItemContainer(container) == this) { 314 | container.Node.IsExpanded = false; 315 | e.Handled = true; 316 | } 317 | break; 318 | case Key.Multiply: 319 | if (container != null && ItemsControl.ItemsControlFromItemContainer(container) == this) { 320 | container.Node.IsExpanded = true; 321 | ExpandRecursively(container.Node); 322 | e.Handled = true; 323 | } 324 | break; 325 | } 326 | if (!e.Handled) 327 | base.OnKeyDown(e); 328 | } 329 | 330 | void ExpandRecursively(SharpTreeNode node) 331 | { 332 | if (node.CanExpandRecursively) { 333 | node.IsExpanded = true; 334 | foreach (SharpTreeNode child in node.Children) { 335 | ExpandRecursively(child); 336 | } 337 | } 338 | } 339 | 340 | /// 341 | /// Scrolls the specified node in view and sets keyboard focus on it. 342 | /// 343 | public void FocusNode(SharpTreeNode node) 344 | { 345 | if (node == null) 346 | throw new ArgumentNullException("node"); 347 | ScrollIntoView(node); 348 | // WPF's ScrollIntoView() uses the same if/dispatcher construct, so we call OnFocusItem() after the item was brought into view. 349 | if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { 350 | OnFocusItem(node); 351 | } else { 352 | this.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new DispatcherOperationCallback(this.OnFocusItem), node); 353 | } 354 | } 355 | 356 | public void ScrollIntoView(SharpTreeNode node) 357 | { 358 | if (node == null) 359 | throw new ArgumentNullException("node"); 360 | doNotScrollOnExpanding = true; 361 | foreach (SharpTreeNode ancestor in node.Ancestors()) 362 | ancestor.IsExpanded = true; 363 | doNotScrollOnExpanding = false; 364 | base.ScrollIntoView(node); 365 | } 366 | 367 | object OnFocusItem(object item) 368 | { 369 | FrameworkElement element = this.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement; 370 | if (element != null) { 371 | element.Focus(); 372 | } 373 | return null; 374 | } 375 | 376 | #region Track selection 377 | 378 | protected override void OnSelectionChanged(SelectionChangedEventArgs e) 379 | { 380 | foreach (SharpTreeNode node in e.RemovedItems) { 381 | node.IsSelected = false; 382 | } 383 | foreach (SharpTreeNode node in e.AddedItems) { 384 | node.IsSelected = true; 385 | } 386 | base.OnSelectionChanged(e); 387 | } 388 | 389 | #endregion 390 | 391 | #region Drag and Drop 392 | protected override void OnDragEnter(DragEventArgs e) 393 | { 394 | OnDragOver(e); 395 | } 396 | 397 | protected override void OnDragOver(DragEventArgs e) 398 | { 399 | e.Effects = DragDropEffects.None; 400 | 401 | if (Root != null && !ShowRoot) { 402 | e.Handled = true; 403 | Root.CanDrop(e, Root.Children.Count); 404 | } 405 | } 406 | 407 | protected override void OnDrop(DragEventArgs e) 408 | { 409 | e.Effects = DragDropEffects.None; 410 | 411 | if (Root != null && !ShowRoot) { 412 | e.Handled = true; 413 | Root.InternalDrop(e, Root.Children.Count); 414 | } 415 | } 416 | 417 | internal void HandleDragEnter(SharpTreeViewItem item, DragEventArgs e) 418 | { 419 | HandleDragOver(item, e); 420 | } 421 | 422 | internal void HandleDragOver(SharpTreeViewItem item, DragEventArgs e) 423 | { 424 | HidePreview(); 425 | 426 | var target = GetDropTarget(item, e); 427 | if (target != null) { 428 | e.Handled = true; 429 | ShowPreview(target.Item, target.Place); 430 | } 431 | } 432 | 433 | internal void HandleDrop(SharpTreeViewItem item, DragEventArgs e) 434 | { 435 | try { 436 | HidePreview(); 437 | 438 | var target = GetDropTarget(item, e); 439 | if (target != null) { 440 | e.Handled = true; 441 | target.Node.InternalDrop(e, target.Index); 442 | } 443 | } catch (Exception ex) { 444 | Debug.WriteLine(ex.ToString()); 445 | throw; 446 | } 447 | } 448 | 449 | internal void HandleDragLeave(SharpTreeViewItem item, DragEventArgs e) 450 | { 451 | HidePreview(); 452 | e.Handled = true; 453 | } 454 | 455 | class DropTarget 456 | { 457 | public SharpTreeViewItem Item; 458 | public DropPlace Place; 459 | public double Y; 460 | public SharpTreeNode Node; 461 | public int Index; 462 | } 463 | 464 | DropTarget GetDropTarget(SharpTreeViewItem item, DragEventArgs e) 465 | { 466 | var dropTargets = BuildDropTargets(item, e); 467 | var y = e.GetPosition(item).Y; 468 | foreach (var target in dropTargets) { 469 | if (target.Y >= y) { 470 | return target; 471 | } 472 | } 473 | return null; 474 | } 475 | 476 | List BuildDropTargets(SharpTreeViewItem item, DragEventArgs e) 477 | { 478 | var result = new List(); 479 | var node = item.Node; 480 | 481 | if (AllowDropOrder) { 482 | TryAddDropTarget(result, item, DropPlace.Before, e); 483 | } 484 | 485 | TryAddDropTarget(result, item, DropPlace.Inside, e); 486 | 487 | if (AllowDropOrder) { 488 | if (node != null && node.IsExpanded && node.Children.Count > 0) { 489 | var firstChildItem = ItemContainerGenerator.ContainerFromItem(node.Children[0]) as SharpTreeViewItem; 490 | TryAddDropTarget(result, firstChildItem, DropPlace.Before, e); 491 | } 492 | else { 493 | TryAddDropTarget(result, item, DropPlace.After, e); 494 | } 495 | } 496 | 497 | var h = item.ActualHeight; 498 | var y1 = 0.2 * h; 499 | var y2 = h / 2; 500 | var y3 = h - y1; 501 | 502 | if (result.Count == 2) { 503 | if (result[0].Place == DropPlace.Inside && 504 | result[1].Place != DropPlace.Inside) { 505 | result[0].Y = y3; 506 | } 507 | else if (result[0].Place != DropPlace.Inside && 508 | result[1].Place == DropPlace.Inside) { 509 | result[0].Y = y1; 510 | } 511 | else { 512 | result[0].Y = y2; 513 | } 514 | } 515 | else if (result.Count == 3) { 516 | result[0].Y = y1; 517 | result[1].Y = y3; 518 | } 519 | if (result.Count > 0) { 520 | result[result.Count - 1].Y = h; 521 | } 522 | return result; 523 | } 524 | 525 | void TryAddDropTarget(List targets, SharpTreeViewItem item, DropPlace place, DragEventArgs e) 526 | { 527 | SharpTreeNode node; 528 | int index; 529 | 530 | GetNodeAndIndex(item, place, out node, out index); 531 | 532 | if (node != null) { 533 | e.Effects = DragDropEffects.None; 534 | if (node.CanDrop(e, index)) { 535 | DropTarget target = new DropTarget() { 536 | Item = item, 537 | Place = place, 538 | Node = node, 539 | Index = index 540 | }; 541 | targets.Add(target); 542 | } 543 | } 544 | } 545 | 546 | void GetNodeAndIndex(SharpTreeViewItem item, DropPlace place, out SharpTreeNode node, out int index) 547 | { 548 | node = null; 549 | index = 0; 550 | 551 | if (item.Node == null) { 552 | } 553 | else if (place == DropPlace.Inside) { 554 | node = item.Node; 555 | index = node.Children.Count; 556 | } 557 | else if (place == DropPlace.Before) { 558 | if (item.Node.Parent != null) { 559 | node = item.Node.Parent; 560 | index = node.Children.IndexOf(item.Node); 561 | } 562 | } 563 | else { 564 | if (item.Node.Parent != null) { 565 | node = item.Node.Parent; 566 | index = node.Children.IndexOf(item.Node) + 1; 567 | } 568 | } 569 | } 570 | 571 | SharpTreeNodeView previewNodeView; 572 | InsertMarker insertMarker; 573 | DropPlace previewPlace; 574 | 575 | enum DropPlace 576 | { 577 | Before, Inside, After 578 | } 579 | 580 | public Func GetPreviewInsideTextBackground = () => SystemColors.HighlightBrush; 581 | public Func GetPreviewInsideForeground = () => SystemColors.HighlightTextBrush; 582 | 583 | void ShowPreview(SharpTreeViewItem item, DropPlace place) 584 | { 585 | previewNodeView = item.NodeView; 586 | previewPlace = place; 587 | 588 | if (place == DropPlace.Inside) { 589 | previewNodeView.TextBackground = GetPreviewInsideTextBackground(); 590 | previewNodeView.Foreground = GetPreviewInsideForeground(); 591 | } 592 | else { 593 | if (insertMarker == null) { 594 | var adornerLayer = AdornerLayer.GetAdornerLayer(this); 595 | var adorner = new GeneralAdorner(this); 596 | insertMarker = new InsertMarker(); 597 | adorner.Child = insertMarker; 598 | adornerLayer.Add(adorner); 599 | } 600 | 601 | insertMarker.Visibility = Visibility.Visible; 602 | 603 | var p1 = previewNodeView.TransformToVisual(this).Transform(new Point()); 604 | var p = new Point(p1.X + previewNodeView.CalculateIndent(item.Node) + 4.5, p1.Y - 3); 605 | 606 | if (place == DropPlace.After) { 607 | p.Y += previewNodeView.ActualHeight; 608 | } 609 | 610 | insertMarker.Margin = new Thickness(p.X, p.Y, 0, 0); 611 | 612 | SharpTreeNodeView secondNodeView = null; 613 | var index = flattener.IndexOf(item.Node); 614 | 615 | if (place == DropPlace.Before) { 616 | if (index > 0) { 617 | secondNodeView = (ItemContainerGenerator.ContainerFromIndex(index - 1) as SharpTreeViewItem).NodeView; 618 | } 619 | } 620 | else if (index + 1 < flattener.Count) { 621 | secondNodeView = (ItemContainerGenerator.ContainerFromIndex(index + 1) as SharpTreeViewItem).NodeView; 622 | } 623 | 624 | var w = p1.X + previewNodeView.ActualWidth - p.X; 625 | 626 | if (secondNodeView != null) { 627 | var p2 = secondNodeView.TransformToVisual(this).Transform(new Point()); 628 | w = Math.Max(w, p2.X + secondNodeView.ActualWidth - p.X); 629 | } 630 | 631 | insertMarker.Width = w + 10; 632 | } 633 | } 634 | 635 | void HidePreview() 636 | { 637 | if (previewNodeView != null) { 638 | previewNodeView.ClearValue(SharpTreeNodeView.TextBackgroundProperty); 639 | previewNodeView.ClearValue(SharpTreeNodeView.ForegroundProperty); 640 | if (insertMarker != null) { 641 | insertMarker.Visibility = Visibility.Collapsed; 642 | } 643 | previewNodeView = null; 644 | } 645 | } 646 | #endregion 647 | 648 | #region Cut / Copy / Paste / Delete Commands 649 | 650 | static void RegisterCommands() 651 | { 652 | // The asm editor should be the only one removing nodes 653 | // CommandManager.RegisterClassCommandBinding(typeof(SharpTreeView), 654 | // new CommandBinding(ApplicationCommands.Cut, HandleExecuted_Cut, HandleCanExecute_Cut)); 655 | // 656 | // CommandManager.RegisterClassCommandBinding(typeof(SharpTreeView), 657 | // new CommandBinding(ApplicationCommands.Copy, HandleExecuted_Copy, HandleCanExecute_Copy)); 658 | // 659 | // CommandManager.RegisterClassCommandBinding(typeof(SharpTreeView), 660 | // new CommandBinding(ApplicationCommands.Paste, HandleExecuted_Paste, HandleCanExecute_Paste)); 661 | // 662 | // CommandManager.RegisterClassCommandBinding(typeof(SharpTreeView), 663 | // new CommandBinding(ApplicationCommands.Delete, HandleExecuted_Delete, HandleCanExecute_Delete)); 664 | } 665 | 666 | static void HandleExecuted_Cut(object sender, ExecutedRoutedEventArgs e) 667 | { 668 | 669 | } 670 | 671 | static void HandleCanExecute_Cut(object sender, CanExecuteRoutedEventArgs e) 672 | { 673 | e.CanExecute = false; 674 | } 675 | 676 | static void HandleExecuted_Copy(object sender, ExecutedRoutedEventArgs e) 677 | { 678 | 679 | } 680 | 681 | static void HandleCanExecute_Copy(object sender, CanExecuteRoutedEventArgs e) 682 | { 683 | e.CanExecute = false; 684 | } 685 | 686 | static void HandleExecuted_Paste(object sender, ExecutedRoutedEventArgs e) 687 | { 688 | 689 | } 690 | 691 | static void HandleCanExecute_Paste(object sender, CanExecuteRoutedEventArgs e) 692 | { 693 | e.CanExecute = false; 694 | } 695 | 696 | static void HandleExecuted_Delete(object sender, ExecutedRoutedEventArgs e) 697 | { 698 | SharpTreeView treeView = (SharpTreeView)sender; 699 | treeView.updatesLocked = true; 700 | int selectedIndex = -1; 701 | try { 702 | foreach (SharpTreeNode node in treeView.GetTopLevelSelection().ToArray()) { 703 | if (selectedIndex == -1) 704 | selectedIndex = treeView.flattener.IndexOf(node); 705 | node.Delete(); 706 | } 707 | } finally { 708 | treeView.updatesLocked = false; 709 | treeView.UpdateFocusedNode(null, Math.Max(0, selectedIndex - 1)); 710 | } 711 | } 712 | 713 | static void HandleCanExecute_Delete(object sender, CanExecuteRoutedEventArgs e) 714 | { 715 | SharpTreeView treeView = (SharpTreeView)sender; 716 | e.CanExecute = treeView.GetTopLevelSelection().All(node => node.CanDelete()); 717 | } 718 | 719 | /// 720 | /// Gets the selected items which do not have any of their ancestors selected. 721 | /// 722 | public IEnumerable GetTopLevelSelection() 723 | { 724 | var selection = this.SelectedItems.OfType(); 725 | var selectionHash = new HashSet(selection); 726 | return selection.Where(item => item.Ancestors().All(a => !selectionHash.Contains(a))); 727 | } 728 | 729 | #endregion 730 | 731 | public void SetSelectedNodes(IEnumerable nodes) 732 | { 733 | this.SetSelectedItems(nodes.ToList()); 734 | } 735 | } 736 | } 737 | -------------------------------------------------------------------------------- /SharpTreeViewItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Controls; 9 | using System.Windows; 10 | using System.Windows.Media; 11 | using System.Windows.Input; 12 | using System.Diagnostics; 13 | 14 | namespace ICSharpCode.TreeView 15 | { 16 | public class SharpTreeViewItem : ListViewItem 17 | { 18 | static SharpTreeViewItem() 19 | { 20 | DefaultStyleKeyProperty.OverrideMetadata(typeof(SharpTreeViewItem), 21 | new FrameworkPropertyMetadata(typeof(SharpTreeViewItem))); 22 | } 23 | 24 | public SharpTreeNode Node 25 | { 26 | get { return DataContext as SharpTreeNode; } 27 | } 28 | 29 | void UpdateAdaptor(SharpTreeNode node) 30 | { 31 | if (nodeView == null) 32 | return; 33 | if (node == null) 34 | return; 35 | 36 | var doAdaptor = nodeView.DataContext as SharpTreeNodeProxy; 37 | if (doAdaptor == null) 38 | nodeView.DataContext = (doAdaptor = new SharpTreeNodeProxy(node)); 39 | else 40 | doAdaptor.UpdateObject(node); 41 | 42 | nodeView.UpdateTemplate(); 43 | } 44 | 45 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) 46 | { 47 | base.OnPropertyChanged(e); 48 | if (e.Property == DataContextProperty) 49 | { 50 | UpdateAdaptor(e.NewValue as SharpTreeNode); 51 | } 52 | } 53 | 54 | 55 | SharpTreeNodeView nodeView; 56 | public SharpTreeNodeView NodeView 57 | { 58 | get { return nodeView; } 59 | internal set 60 | { 61 | if (nodeView != value) 62 | { 63 | nodeView = value; 64 | UpdateAdaptor(Node); 65 | } 66 | } 67 | } 68 | public SharpTreeView ParentTreeView { get; internal set; } 69 | 70 | protected override void OnKeyDown(KeyEventArgs e) 71 | { 72 | switch (e.Key) { 73 | case Key.F2: 74 | // if (SharpTreeNode.ActiveNodes.Count == 1 && Node.IsEditable) { 75 | // Node.IsEditing = true; 76 | // e.Handled = true; 77 | // } 78 | break; 79 | case Key.Escape: 80 | if (Node != null) 81 | Node.IsEditing = false; 82 | break; 83 | } 84 | } 85 | 86 | #region Mouse 87 | 88 | Point startPoint; 89 | bool wasSelected; 90 | bool wasDoubleClick; 91 | 92 | protected override void OnMouseDoubleClick(MouseButtonEventArgs e) 93 | { 94 | if (!ParentTreeView.CanDragAndDrop) { 95 | OnDoubleClick(e); 96 | e.Handled = true; 97 | return; 98 | } 99 | 100 | base.OnMouseDoubleClick(e); 101 | } 102 | 103 | protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) 104 | { 105 | wasSelected = IsSelected; 106 | if (!IsSelected) { 107 | base.OnMouseLeftButtonDown(e); 108 | } 109 | 110 | if (!ParentTreeView.CanDragAndDrop) 111 | wasDoubleClick = false; 112 | else if (Mouse.LeftButton == MouseButtonState.Pressed) { 113 | startPoint = e.GetPosition(null); 114 | CaptureMouse(); 115 | 116 | if (e.ClickCount == 2) { 117 | wasDoubleClick = true; 118 | } 119 | } 120 | } 121 | 122 | protected override void OnMouseMove(MouseEventArgs e) 123 | { 124 | if (IsMouseCaptured) { 125 | var currentPoint = e.GetPosition(null); 126 | if (Math.Abs(currentPoint.X - startPoint.X) >= SystemParameters.MinimumHorizontalDragDistance || 127 | Math.Abs(currentPoint.Y - startPoint.Y) >= SystemParameters.MinimumVerticalDragDistance) { 128 | 129 | var selection = ParentTreeView.GetTopLevelSelection().ToArray(); 130 | if (Node != null && Node.CanDrag(selection)) { 131 | Node.StartDrag(this, selection); 132 | } 133 | } 134 | } 135 | } 136 | 137 | protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) 138 | { 139 | if (Node == null) { 140 | // Ignore it: Node is sometimes null 141 | } 142 | else if (wasDoubleClick) { 143 | wasDoubleClick = false; 144 | OnDoubleClick(e); 145 | } 146 | else if (!Node.IsExpanded && Node.SingleClickExpandsChildren) { 147 | if (!Node.IsRoot || ParentTreeView.ShowRootExpander) { 148 | Node.IsExpanded = !Node.IsExpanded; 149 | } 150 | } 151 | 152 | ReleaseMouseCapture(); 153 | if (wasSelected) { 154 | wasSelected = false; 155 | // Make sure the TV doesn't steal focus when double clicking something that will 156 | // trigger setting focus to eg. the text editor. 157 | if (!ignoreOnMouseLeftButtonDown) 158 | base.OnMouseLeftButtonDown(e); 159 | } 160 | ignoreOnMouseLeftButtonDown = false; 161 | } 162 | 163 | bool ignoreOnMouseLeftButtonDown = false; 164 | void OnDoubleClick(RoutedEventArgs e) 165 | { 166 | ignoreOnMouseLeftButtonDown = true; 167 | if (Node == null) 168 | return; 169 | Node.ActivateItem(e); 170 | if (!e.Handled) { 171 | if (!Node.IsRoot || ParentTreeView.ShowRootExpander) { 172 | Node.IsExpanded = !Node.IsExpanded; 173 | } 174 | } 175 | } 176 | 177 | #endregion 178 | 179 | #region Drag and Drop 180 | 181 | protected override void OnDragEnter(DragEventArgs e) 182 | { 183 | ParentTreeView.HandleDragEnter(this, e); 184 | } 185 | 186 | protected override void OnDragOver(DragEventArgs e) 187 | { 188 | ParentTreeView.HandleDragOver(this, e); 189 | } 190 | 191 | protected override void OnDrop(DragEventArgs e) 192 | { 193 | ParentTreeView.HandleDrop(this, e); 194 | } 195 | 196 | protected override void OnDragLeave(DragEventArgs e) 197 | { 198 | ParentTreeView.HandleDragLeave(this, e); 199 | } 200 | 201 | #endregion 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | 8 | 49 | 50 | 71 | 72 | 96 | 97 | 107 | 108 | 123 | 124 | 174 | 175 | 223 | 224 | 319 | 320 | 321 | -------------------------------------------------------------------------------- /TreeFlattener.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Collections.Specialized; 8 | using System.ComponentModel; 9 | using System.Diagnostics; 10 | using System.Linq; 11 | using System.Text; 12 | 13 | namespace ICSharpCode.TreeView 14 | { 15 | sealed class TreeFlattener : IList, INotifyCollectionChanged 16 | { 17 | /// 18 | /// The root node of the flat list tree. 19 | /// Tjis is not necessarily the root of the model! 20 | /// 21 | internal SharpTreeNode root; 22 | readonly bool includeRoot; 23 | readonly object syncRoot = new object(); 24 | 25 | public TreeFlattener(SharpTreeNode modelRoot, bool includeRoot) 26 | { 27 | this.root = modelRoot; 28 | while (root.listParent != null) 29 | root = root.listParent; 30 | root.treeFlattener = this; 31 | this.includeRoot = includeRoot; 32 | } 33 | 34 | public event NotifyCollectionChangedEventHandler CollectionChanged; 35 | 36 | Dictionary nodeCache = new Dictionary(); 37 | 38 | void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e) 39 | { 40 | nodeCache.Clear(); 41 | if (CollectionChanged != null) 42 | CollectionChanged(this, e); 43 | } 44 | 45 | public void NodesInserted(int index, IEnumerable nodes) 46 | { 47 | if (!includeRoot) index--; 48 | foreach (SharpTreeNode node in nodes) { 49 | RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, node, index++)); 50 | } 51 | } 52 | 53 | public void NodesRemoved(int index, IEnumerable nodes) 54 | { 55 | if (!includeRoot) index--; 56 | foreach (SharpTreeNode node in nodes) { 57 | RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, node, index)); 58 | } 59 | } 60 | 61 | public void Stop() 62 | { 63 | Debug.Assert(root.treeFlattener == this); 64 | root.treeFlattener = null; 65 | } 66 | 67 | public object this[int index] { 68 | get { 69 | if (index < 0 || index >= this.Count) 70 | throw new ArgumentOutOfRangeException(); 71 | 72 | SharpTreeNode node; 73 | if (nodeCache.TryGetValue(index, out node)) 74 | return node; 75 | 76 | node = SharpTreeNode.GetNodeByVisibleIndex(root, includeRoot ? index : index + 1); 77 | nodeCache[index] = node; 78 | return node; 79 | } 80 | set { 81 | throw new NotSupportedException(); 82 | } 83 | } 84 | 85 | public int Count { 86 | get { 87 | return includeRoot ? root.GetTotalListLength() : root.GetTotalListLength() - 1; 88 | } 89 | } 90 | 91 | public int IndexOf(object item) 92 | { 93 | SharpTreeNode node = item as SharpTreeNode; 94 | if (node != null && node.IsVisible && node.GetListRoot() == root) { 95 | if (includeRoot) 96 | return SharpTreeNode.GetVisibleIndexForNode(node); 97 | else 98 | return SharpTreeNode.GetVisibleIndexForNode(node) - 1; 99 | } else { 100 | return -1; 101 | } 102 | } 103 | 104 | bool IList.IsReadOnly { 105 | get { return true; } 106 | } 107 | 108 | bool IList.IsFixedSize { 109 | get { return false; } 110 | } 111 | 112 | bool ICollection.IsSynchronized { 113 | get { return false; } 114 | } 115 | 116 | object ICollection.SyncRoot { 117 | get { 118 | return syncRoot; 119 | } 120 | } 121 | 122 | void IList.Insert(int index, object item) 123 | { 124 | throw new NotSupportedException(); 125 | } 126 | 127 | void IList.RemoveAt(int index) 128 | { 129 | throw new NotSupportedException(); 130 | } 131 | 132 | int IList.Add(object item) 133 | { 134 | throw new NotSupportedException(); 135 | } 136 | 137 | void IList.Clear() 138 | { 139 | throw new NotSupportedException(); 140 | } 141 | 142 | public bool Contains(object item) 143 | { 144 | return IndexOf(item) >= 0; 145 | } 146 | 147 | public void CopyTo(Array array, int arrayIndex) 148 | { 149 | foreach (object item in this) 150 | array.SetValue(item, arrayIndex++); 151 | } 152 | 153 | void IList.Remove(object item) 154 | { 155 | throw new NotSupportedException(); 156 | } 157 | 158 | public IEnumerator GetEnumerator() 159 | { 160 | for (int i = 0; i < this.Count; i++) { 161 | yield return this[i]; 162 | } 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /TreeTraversal.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) 2 | // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace ICSharpCode.TreeView 8 | { 9 | /// 10 | /// Static helper methods for traversing trees. 11 | /// 12 | static class TreeTraversal 13 | { 14 | /// 15 | /// Converts a tree data structure into a flat list by traversing it in pre-order. 16 | /// 17 | /// The root element of the tree. 18 | /// The function that gets the children of an element. 19 | /// Iterator that enumerates the tree structure in pre-order. 20 | public static IEnumerable PreOrder(T root, Func> recursion) 21 | { 22 | return PreOrder(new T[] { root }, recursion); 23 | } 24 | 25 | /// 26 | /// Converts a tree data structure into a flat list by traversing it in pre-order. 27 | /// 28 | /// The root elements of the forest. 29 | /// The function that gets the children of an element. 30 | /// Iterator that enumerates the tree structure in pre-order. 31 | public static IEnumerable PreOrder(IEnumerable input, Func> recursion) 32 | { 33 | Stack> stack = new Stack>(); 34 | try { 35 | stack.Push(input.GetEnumerator()); 36 | while (stack.Count > 0) { 37 | while (stack.Peek().MoveNext()) { 38 | T element = stack.Peek().Current; 39 | yield return element; 40 | IEnumerable children = recursion(element); 41 | if (children != null) { 42 | stack.Push(children.GetEnumerator()); 43 | } 44 | } 45 | stack.Pop().Dispose(); 46 | } 47 | } finally { 48 | while (stack.Count > 0) { 49 | stack.Pop().Dispose(); 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /copyright.txt: -------------------------------------------------------------------------------- 1 | Copyright 2002-2011 by 2 | 3 | AlphaSierraPapa, Christoph Wille 4 | Vordernberger Strasse 27/8 5 | A-8700 Leoben 6 | Austria 7 | 8 | email: office@alphasierrapapa.com 9 | court of jurisdiction: Landesgericht Leoben 10 | 11 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | --------------------------------------------------------------------------------