You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

728 lines
16 KiB

  1. // Copyright (c) 2020 AlphaSierraPapa for the SharpDevelop Team
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Collections.Specialized;
  21. using System.ComponentModel;
  22. using System.Diagnostics;
  23. using System.Linq;
  24. using System.Windows;
  25. using System.Windows.Input;
  26. using System.Windows.Media;
  27. namespace ICSharpCode.TreeView
  28. {
  29. public partial class SharpTreeNode : INotifyPropertyChanged
  30. {
  31. SharpTreeNodeCollection modelChildren;
  32. internal SharpTreeNode modelParent;
  33. bool isVisible = true;
  34. void UpdateIsVisible(bool parentIsVisible, bool updateFlattener)
  35. {
  36. bool newIsVisible = parentIsVisible && !isHidden;
  37. if (isVisible != newIsVisible)
  38. {
  39. isVisible = newIsVisible;
  40. // invalidate the augmented data
  41. SharpTreeNode node = this;
  42. while (node != null && node.totalListLength >= 0)
  43. {
  44. node.totalListLength = -1;
  45. node = node.listParent;
  46. }
  47. // Remember the removed nodes:
  48. List<SharpTreeNode> removedNodes = null;
  49. if (updateFlattener && !newIsVisible)
  50. {
  51. removedNodes = VisibleDescendantsAndSelf().ToList();
  52. }
  53. // also update the model children:
  54. UpdateChildIsVisible(false);
  55. // Validate our invariants:
  56. if (updateFlattener)
  57. CheckRootInvariants();
  58. // Tell the flattener about the removed nodes:
  59. if (removedNodes != null)
  60. {
  61. var flattener = GetListRoot().treeFlattener;
  62. if (flattener != null)
  63. {
  64. flattener.NodesRemoved(GetVisibleIndexForNode(this), removedNodes);
  65. foreach (var n in removedNodes)
  66. n.OnIsVisibleChanged();
  67. }
  68. }
  69. // Tell the flattener about the new nodes:
  70. if (updateFlattener && newIsVisible)
  71. {
  72. var flattener = GetListRoot().treeFlattener;
  73. if (flattener != null)
  74. {
  75. flattener.NodesInserted(GetVisibleIndexForNode(this), VisibleDescendantsAndSelf());
  76. foreach (var n in VisibleDescendantsAndSelf())
  77. n.OnIsVisibleChanged();
  78. }
  79. }
  80. }
  81. }
  82. protected virtual void OnIsVisibleChanged() { }
  83. void UpdateChildIsVisible(bool updateFlattener)
  84. {
  85. if (modelChildren != null && modelChildren.Count > 0)
  86. {
  87. bool showChildren = isVisible && isExpanded;
  88. foreach (SharpTreeNode child in modelChildren)
  89. {
  90. child.UpdateIsVisible(showChildren, updateFlattener);
  91. }
  92. }
  93. }
  94. #region Main
  95. public SharpTreeNode()
  96. {
  97. }
  98. public SharpTreeNodeCollection Children {
  99. get {
  100. if (modelChildren == null)
  101. modelChildren = new SharpTreeNodeCollection(this);
  102. return modelChildren;
  103. }
  104. }
  105. public SharpTreeNode Parent {
  106. get { return modelParent; }
  107. }
  108. public virtual object Text {
  109. get { return null; }
  110. }
  111. public virtual Brush Foreground {
  112. get { return SystemColors.WindowTextBrush; }
  113. }
  114. public virtual object Icon {
  115. get { return null; }
  116. }
  117. public virtual object ToolTip {
  118. get { return null; }
  119. }
  120. public int Level {
  121. get { return Parent != null ? Parent.Level + 1 : 0; }
  122. }
  123. public bool IsRoot {
  124. get { return Parent == null; }
  125. }
  126. bool isHidden;
  127. public bool IsHidden {
  128. get { return isHidden; }
  129. set {
  130. if (isHidden != value)
  131. {
  132. isHidden = value;
  133. if (modelParent != null)
  134. UpdateIsVisible(modelParent.isVisible && modelParent.isExpanded, true);
  135. RaisePropertyChanged(nameof(IsHidden));
  136. if (Parent != null)
  137. Parent.RaisePropertyChanged(nameof(ShowExpander));
  138. }
  139. }
  140. }
  141. /// <summary>
  142. /// Return true when this node is not hidden and when all parent nodes are expanded and not hidden.
  143. /// </summary>
  144. public bool IsVisible {
  145. get { return isVisible; }
  146. }
  147. bool isSelected;
  148. public bool IsSelected {
  149. get { return isSelected; }
  150. set {
  151. if (isSelected != value)
  152. {
  153. isSelected = value;
  154. RaisePropertyChanged(nameof(IsSelected));
  155. }
  156. }
  157. }
  158. #endregion
  159. #region OnChildrenChanged
  160. internal protected virtual void OnChildrenChanged(NotifyCollectionChangedEventArgs e)
  161. {
  162. if (e.OldItems != null)
  163. {
  164. foreach (SharpTreeNode node in e.OldItems)
  165. {
  166. Debug.Assert(node.modelParent == this);
  167. node.modelParent = null;
  168. Debug.WriteLine("Removing {0} from {1}", node, this);
  169. SharpTreeNode removeEnd = node;
  170. while (removeEnd.modelChildren != null && removeEnd.modelChildren.Count > 0)
  171. removeEnd = removeEnd.modelChildren.Last();
  172. List<SharpTreeNode> removedNodes = null;
  173. int visibleIndexOfRemoval = 0;
  174. if (node.isVisible)
  175. {
  176. visibleIndexOfRemoval = GetVisibleIndexForNode(node);
  177. removedNodes = node.VisibleDescendantsAndSelf().ToList();
  178. }
  179. RemoveNodes(node, removeEnd);
  180. if (removedNodes != null)
  181. {
  182. var flattener = GetListRoot().treeFlattener;
  183. if (flattener != null)
  184. {
  185. flattener.NodesRemoved(visibleIndexOfRemoval, removedNodes);
  186. }
  187. }
  188. }
  189. }
  190. if (e.NewItems != null)
  191. {
  192. SharpTreeNode insertionPos;
  193. if (e.NewStartingIndex == 0)
  194. insertionPos = null;
  195. else
  196. insertionPos = modelChildren[e.NewStartingIndex - 1];
  197. foreach (SharpTreeNode node in e.NewItems)
  198. {
  199. Debug.Assert(node.modelParent == null);
  200. node.modelParent = this;
  201. node.UpdateIsVisible(isVisible && isExpanded, false);
  202. //Debug.WriteLine("Inserting {0} after {1}", node, insertionPos);
  203. while (insertionPos != null && insertionPos.modelChildren != null && insertionPos.modelChildren.Count > 0)
  204. {
  205. insertionPos = insertionPos.modelChildren.Last();
  206. }
  207. InsertNodeAfter(insertionPos ?? this, node);
  208. insertionPos = node;
  209. if (node.isVisible)
  210. {
  211. var flattener = GetListRoot().treeFlattener;
  212. if (flattener != null)
  213. {
  214. flattener.NodesInserted(GetVisibleIndexForNode(node), node.VisibleDescendantsAndSelf());
  215. }
  216. }
  217. }
  218. }
  219. RaisePropertyChanged(nameof(ShowExpander));
  220. RaiseIsLastChangedIfNeeded(e);
  221. }
  222. #endregion
  223. #region Expanding / LazyLoading
  224. public virtual object ExpandedIcon {
  225. get { return Icon; }
  226. }
  227. public virtual bool ShowExpander {
  228. get { return LazyLoading || Children.Any(c => !c.isHidden); }
  229. }
  230. bool isExpanded;
  231. public bool IsExpanded {
  232. get { return isExpanded; }
  233. set {
  234. if (isExpanded != value)
  235. {
  236. isExpanded = value;
  237. if (isExpanded)
  238. {
  239. EnsureLazyChildren();
  240. OnExpanding();
  241. }
  242. else
  243. {
  244. OnCollapsing();
  245. }
  246. UpdateChildIsVisible(true);
  247. RaisePropertyChanged(nameof(IsExpanded));
  248. }
  249. }
  250. }
  251. protected virtual void OnExpanding() { }
  252. protected virtual void OnCollapsing() { }
  253. bool lazyLoading;
  254. public bool LazyLoading {
  255. get { return lazyLoading; }
  256. set {
  257. lazyLoading = value;
  258. if (lazyLoading)
  259. {
  260. IsExpanded = false;
  261. if (canExpandRecursively)
  262. {
  263. canExpandRecursively = false;
  264. RaisePropertyChanged(nameof(CanExpandRecursively));
  265. }
  266. }
  267. RaisePropertyChanged(nameof(LazyLoading));
  268. RaisePropertyChanged(nameof(ShowExpander));
  269. }
  270. }
  271. bool canExpandRecursively = true;
  272. /// <summary>
  273. /// Gets whether this node can be expanded recursively.
  274. /// If not overridden, this property returns false if the node is using lazy-loading, and true otherwise.
  275. /// </summary>
  276. public virtual bool CanExpandRecursively {
  277. get { return canExpandRecursively; }
  278. }
  279. public virtual bool ShowIcon {
  280. get { return Icon != null; }
  281. }
  282. protected virtual void LoadChildren()
  283. {
  284. throw new NotSupportedException(GetType().Name + " does not support lazy loading");
  285. }
  286. /// <summary>
  287. /// Ensures the children were initialized (loads children if lazy loading is enabled)
  288. /// </summary>
  289. public void EnsureLazyChildren()
  290. {
  291. if (LazyLoading)
  292. {
  293. LazyLoading = false;
  294. LoadChildren();
  295. }
  296. }
  297. #endregion
  298. #region Ancestors / Descendants
  299. public IEnumerable<SharpTreeNode> Descendants()
  300. {
  301. return TreeTraversal.PreOrder(this.Children, n => n.Children);
  302. }
  303. public IEnumerable<SharpTreeNode> DescendantsAndSelf()
  304. {
  305. return TreeTraversal.PreOrder(this, n => n.Children);
  306. }
  307. internal IEnumerable<SharpTreeNode> VisibleDescendants()
  308. {
  309. return TreeTraversal.PreOrder(this.Children.Where(c => c.isVisible), n => n.Children.Where(c => c.isVisible));
  310. }
  311. internal IEnumerable<SharpTreeNode> VisibleDescendantsAndSelf()
  312. {
  313. return TreeTraversal.PreOrder(this, n => n.Children.Where(c => c.isVisible));
  314. }
  315. public IEnumerable<SharpTreeNode> Ancestors()
  316. {
  317. for (SharpTreeNode n = this.Parent; n != null; n = n.Parent)
  318. yield return n;
  319. }
  320. public IEnumerable<SharpTreeNode> AncestorsAndSelf()
  321. {
  322. for (SharpTreeNode n = this; n != null; n = n.Parent)
  323. yield return n;
  324. }
  325. #endregion
  326. #region Editing
  327. public virtual bool IsEditable {
  328. get { return false; }
  329. }
  330. bool isEditing;
  331. public bool IsEditing {
  332. get { return isEditing; }
  333. set {
  334. if (isEditing != value)
  335. {
  336. isEditing = value;
  337. RaisePropertyChanged(nameof(IsEditing));
  338. }
  339. }
  340. }
  341. public virtual string LoadEditText()
  342. {
  343. return null;
  344. }
  345. public virtual bool SaveEditText(string value)
  346. {
  347. return true;
  348. }
  349. #endregion
  350. #region Checkboxes
  351. public virtual bool IsCheckable {
  352. get { return false; }
  353. }
  354. bool? isChecked;
  355. public bool? IsChecked {
  356. get { return isChecked; }
  357. set {
  358. SetIsChecked(value, true);
  359. }
  360. }
  361. void SetIsChecked(bool? value, bool update)
  362. {
  363. if (isChecked != value)
  364. {
  365. isChecked = value;
  366. if (update)
  367. {
  368. if (IsChecked != null)
  369. {
  370. foreach (var child in Descendants())
  371. {
  372. if (child.IsCheckable)
  373. {
  374. child.SetIsChecked(IsChecked, false);
  375. }
  376. }
  377. }
  378. foreach (var parent in Ancestors())
  379. {
  380. if (parent.IsCheckable)
  381. {
  382. if (!parent.TryValueForIsChecked(true))
  383. {
  384. if (!parent.TryValueForIsChecked(false))
  385. {
  386. parent.SetIsChecked(null, false);
  387. }
  388. }
  389. }
  390. }
  391. }
  392. RaisePropertyChanged(nameof(IsChecked));
  393. }
  394. }
  395. bool TryValueForIsChecked(bool? value)
  396. {
  397. if (Children.Where(n => n.IsCheckable).All(n => n.IsChecked == value))
  398. {
  399. SetIsChecked(value, false);
  400. return true;
  401. }
  402. return false;
  403. }
  404. #endregion
  405. #region Cut / Copy / Paste / Delete
  406. public bool IsCut { get { return false; } }
  407. /*
  408. static List<SharpTreeNode> cuttedNodes = new List<SharpTreeNode>();
  409. static IDataObject cuttedData;
  410. static EventHandler requerySuggestedHandler; // for weak event
  411. static void StartCuttedDataWatcher()
  412. {
  413. requerySuggestedHandler = new EventHandler(CommandManager_RequerySuggested);
  414. CommandManager.RequerySuggested += requerySuggestedHandler;
  415. }
  416. static void CommandManager_RequerySuggested(object sender, EventArgs e)
  417. {
  418. if (cuttedData != null && !Clipboard.IsCurrent(cuttedData)) {
  419. ClearCuttedData();
  420. }
  421. }
  422. static void ClearCuttedData()
  423. {
  424. foreach (var node in cuttedNodes) {
  425. node.IsCut = false;
  426. }
  427. cuttedNodes.Clear();
  428. cuttedData = null;
  429. }
  430. //static public IEnumerable<SharpTreeNode> PurifyNodes(IEnumerable<SharpTreeNode> nodes)
  431. //{
  432. // var list = nodes.ToList();
  433. // var array = list.ToArray();
  434. // foreach (var node1 in array) {
  435. // foreach (var node2 in array) {
  436. // if (node1.Descendants().Contains(node2)) {
  437. // list.Remove(node2);
  438. // }
  439. // }
  440. // }
  441. // return list;
  442. //}
  443. bool isCut;
  444. public bool IsCut
  445. {
  446. get { return isCut; }
  447. private set
  448. {
  449. isCut = value;
  450. RaisePropertyChanged("IsCut");
  451. }
  452. }
  453. internal bool InternalCanCut()
  454. {
  455. return InternalCanCopy() && InternalCanDelete();
  456. }
  457. internal void InternalCut()
  458. {
  459. ClearCuttedData();
  460. cuttedData = Copy(ActiveNodesArray);
  461. Clipboard.SetDataObject(cuttedData);
  462. foreach (var node in ActiveNodes) {
  463. node.IsCut = true;
  464. cuttedNodes.Add(node);
  465. }
  466. }
  467. internal bool InternalCanCopy()
  468. {
  469. return CanCopy(ActiveNodesArray);
  470. }
  471. internal void InternalCopy()
  472. {
  473. Clipboard.SetDataObject(Copy(ActiveNodesArray));
  474. }
  475. internal bool InternalCanPaste()
  476. {
  477. return CanPaste(Clipboard.GetDataObject());
  478. }
  479. internal void InternalPaste()
  480. {
  481. Paste(Clipboard.GetDataObject());
  482. if (cuttedData != null) {
  483. DeleteCore(cuttedNodes.ToArray());
  484. ClearCuttedData();
  485. }
  486. }
  487. */
  488. public virtual bool CanDelete()
  489. {
  490. return false;
  491. }
  492. public virtual void Delete()
  493. {
  494. throw new NotSupportedException(GetType().Name + " does not support deletion");
  495. }
  496. public virtual void DeleteCore()
  497. {
  498. throw new NotSupportedException(GetType().Name + " does not support deletion");
  499. }
  500. public virtual IDataObject Copy(SharpTreeNode[] nodes)
  501. {
  502. throw new NotSupportedException(GetType().Name + " does not support copy/paste or drag'n'drop");
  503. }
  504. /*
  505. public virtual bool CanCopy(SharpTreeNode[] nodes)
  506. {
  507. return false;
  508. }
  509. public virtual bool CanPaste(IDataObject data)
  510. {
  511. return false;
  512. }
  513. public virtual void Paste(IDataObject data)
  514. {
  515. EnsureLazyChildren();
  516. Drop(data, Children.Count, DropEffect.Copy);
  517. }
  518. */
  519. #endregion
  520. #region Drag and Drop
  521. public virtual bool CanDrag(SharpTreeNode[] nodes)
  522. {
  523. return false;
  524. }
  525. public virtual void StartDrag(DependencyObject dragSource, SharpTreeNode[] nodes)
  526. {
  527. DragDropEffects effects = DragDropEffects.All;
  528. if (!nodes.All(n => n.CanDelete()))
  529. effects &= ~DragDropEffects.Move;
  530. DragDropEffects result = DragDrop.DoDragDrop(dragSource, Copy(nodes), effects);
  531. if (result == DragDropEffects.Move)
  532. {
  533. foreach (SharpTreeNode node in nodes)
  534. node.DeleteCore();
  535. }
  536. }
  537. public virtual bool CanDrop(DragEventArgs e, int index)
  538. {
  539. return false;
  540. }
  541. internal void InternalDrop(DragEventArgs e, int index)
  542. {
  543. if (LazyLoading)
  544. {
  545. EnsureLazyChildren();
  546. index = Children.Count;
  547. }
  548. Drop(e, index);
  549. }
  550. public virtual void Drop(DragEventArgs e, int index)
  551. {
  552. throw new NotSupportedException(GetType().Name + " does not support Drop()");
  553. }
  554. #endregion
  555. #region IsLast (for TreeView lines)
  556. public bool IsLast {
  557. get {
  558. return Parent == null ||
  559. Parent.Children[Parent.Children.Count - 1] == this;
  560. }
  561. }
  562. void RaiseIsLastChangedIfNeeded(NotifyCollectionChangedEventArgs e)
  563. {
  564. switch (e.Action)
  565. {
  566. case NotifyCollectionChangedAction.Add:
  567. if (e.NewStartingIndex == Children.Count - 1)
  568. {
  569. if (Children.Count > 1)
  570. {
  571. Children[Children.Count - 2].RaisePropertyChanged(nameof(IsLast));
  572. }
  573. Children[Children.Count - 1].RaisePropertyChanged(nameof(IsLast));
  574. }
  575. break;
  576. case NotifyCollectionChangedAction.Remove:
  577. if (e.OldStartingIndex == Children.Count)
  578. {
  579. if (Children.Count > 0)
  580. {
  581. Children[Children.Count - 1].RaisePropertyChanged(nameof(IsLast));
  582. }
  583. }
  584. break;
  585. }
  586. }
  587. #endregion
  588. #region INotifyPropertyChanged Members
  589. public event PropertyChangedEventHandler PropertyChanged;
  590. public void RaisePropertyChanged(string name)
  591. {
  592. if (PropertyChanged != null)
  593. {
  594. PropertyChanged(this, new PropertyChangedEventArgs(name));
  595. }
  596. }
  597. #endregion
  598. /// <summary>
  599. /// Gets called when the item is double-clicked.
  600. /// </summary>
  601. public virtual void ActivateItem(RoutedEventArgs e)
  602. {
  603. }
  604. /// <summary>
  605. /// Gets called when the item is clicked with the middle mouse button.
  606. /// </summary>
  607. public virtual void ActivateItemSecondary(RoutedEventArgs e)
  608. {
  609. }
  610. public override string ToString()
  611. {
  612. // used for keyboard navigation
  613. object text = this.Text;
  614. return text != null ? text.ToString() : string.Empty;
  615. }
  616. }
  617. }