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.

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