PageRenderTime 53ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Duplicati/Scheduler/Utility/FolderTreeControl.cs

http://duplicati.googlecode.com/
C# | 260 lines | 172 code | 1 blank | 87 comment | 32 complexity | 2d7f1f0faf86e7ed0bdfd334386c6f4f MD5 | raw file
Possible License(s): LGPL-2.1, LGPL-2.0, Apache-2.0, GPL-3.0, CC-BY-SA-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Drawing;
  5. using System.Data;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. namespace Duplicati.Scheduler.Utility
  10. {
  11. /// <summary>
  12. /// A folder tree with 3-state checkboxes
  13. /// </summary>
  14. public partial class FolderTreeControl : UserControl
  15. {
  16. /// <summary>
  17. /// A file tree with 3-state checkboxes
  18. /// </summary>
  19. public FolderTreeControl()
  20. {
  21. InitializeComponent();
  22. InitializeTree();
  23. this.treeView.BackColor = this.BackColor;
  24. }
  25. /// <summary>
  26. /// Make the tree, only populate root nodes
  27. /// </summary>
  28. private void InitializeTree()
  29. {
  30. this.treeView.Nodes.Clear();
  31. TreeNode Computer = UpdateNode(this.treeView.Nodes, "Computer", false);
  32. foreach (System.IO.DriveInfo Drive in System.IO.DriveInfo.GetDrives())
  33. if (Drive.IsReady) UpdateNode(Computer.Nodes, Drive.RootDirectory.Name, false);
  34. Computer.ForeColor = this.ForeColor;
  35. }
  36. private bool InHere = false; // No re-entry
  37. /// <summary>
  38. /// Expanding, make the children nodes
  39. /// </summary>
  40. private void treeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
  41. {
  42. if (!InHere)
  43. {
  44. InHere = true;
  45. this.treeView.BeginUpdate();
  46. Populate((string)e.Node.Tag, e.Node, e.Node.Checked && e.Node.ImageIndex != 1);
  47. this.treeView.Sort();
  48. e.Node.Expand(); // Sort collapses all, so we gotta force expand, which is why we need that InHere thing
  49. this.treeView.EndUpdate();
  50. InHere = false;
  51. }
  52. }
  53. public string[] itsSelectedFolders = new string[0];
  54. /// <summary>
  55. /// List of selected folders
  56. /// </summary>
  57. public string[] SelectedFolders
  58. {
  59. get { return Selected(this.treeView.Nodes[0]); }
  60. }
  61. /// <summary>
  62. /// Sets the list as selected
  63. /// </summary>
  64. /// <param name="aFolderList">Folders to select</param>
  65. public void SetSelectedFolders(string[] aFolderList)
  66. {
  67. itsSelectedFolders = aFolderList;
  68. AddSelections();
  69. }
  70. /// <summary>
  71. /// Get list of selected nodes [recursive]
  72. /// </summary>
  73. /// <param name="aNode">Node to search</param>
  74. /// <returns>List of selected nodes</returns>
  75. private string[] Selected(TreeNode aNode)
  76. {
  77. if (aNode.Checked && aNode.ImageIndex != 1) return new string[] { (string)aNode.Tag };
  78. List<string> Result = new List<string>();
  79. foreach (TreeNode Node in aNode.Nodes)
  80. Result.AddRange(Selected(Node));
  81. return Result.ToArray();
  82. }
  83. /// <summary>
  84. /// Returns true if node is selected
  85. /// </summary>
  86. /// <param name="aNode">Node</param>
  87. /// <returns>true if node is selected</returns>
  88. private bool IsChecked(TreeNode aNode)
  89. {
  90. return aNode.Checked && aNode.ImageIndex == 2;
  91. }
  92. /// <summary>
  93. /// A handy look up
  94. /// </summary>
  95. private Dictionary<string, TreeNode> NodesByPath = new Dictionary<string, TreeNode>();
  96. /// <summary>
  97. /// Gets a list of sub directories, returns exception if encountered
  98. /// </summary>
  99. /// <param name="aRoot">Path to list</param>
  100. /// <param name="outError">null of OK, otherwise error</param>
  101. /// <returns>List of subs</returns>
  102. private string[] GetDirectories(string aRoot, out Exception outError)
  103. {
  104. string[] Result = new string[0];
  105. outError = Duplicati.Scheduler.Utility.Tools.TryCatch((Action)delegate() { Result = System.IO.Directory.GetDirectories(aRoot); });
  106. return Result;
  107. }
  108. /// <summary>
  109. /// Fill a node with child nodes if any
  110. /// </summary>
  111. /// <param name="aRoot">Path to fill</param>
  112. /// <param name="aParent">Parent node</param>
  113. /// <param name="aChecked">Check the nodes or not</param>
  114. private void Populate(string aRoot, TreeNode aParent, bool aChecked)
  115. {
  116. Exception Ignored;
  117. foreach (string Folder in GetDirectories(aRoot, out Ignored))
  118. UpdateNode(aParent.Nodes, Folder, aChecked);
  119. }
  120. /// <summary>
  121. /// Gets the name of the node, special case for Recycle Bin
  122. /// </summary>
  123. /// <param name="aFolder">Folder to get name</param>
  124. /// <returns>Name</returns>
  125. private string NodeName(string aFolder)
  126. {
  127. string Result = System.IO.Path.GetFileName(aFolder);
  128. if (string.IsNullOrEmpty(Result)) Result = aFolder;
  129. return Result.Replace("$Recycle.Bin", " Recycle Bin");
  130. }
  131. /// <summary>
  132. /// Add/Change a node
  133. /// </summary>
  134. /// <param name="aNodes">Nodes for this one to join</param>
  135. /// <param name="aFolder">Folder to use</param>
  136. /// <param name="aChecked">Checked or not</param>
  137. /// <returns>The new node</returns>
  138. private TreeNode UpdateNode(TreeNodeCollection aNodes, string aFolder, bool aChecked)
  139. {
  140. if (aNodes.Count > 0 && string.IsNullOrEmpty(aNodes[0].Text)) aNodes.RemoveAt(0); // Remove the fake
  141. TreeNode NewNode = null;
  142. if (NodesByPath.ContainsKey(aFolder))
  143. NewNode = NodesByPath[aFolder]; // We already got one...
  144. else
  145. NewNode =
  146. aNodes[
  147. aNodes.Add(new TreeNode(NodeName( aFolder ))
  148. {
  149. Tag = aFolder,
  150. Checked = aChecked,
  151. SelectedImageIndex = aChecked ? 2 : 0,
  152. ImageIndex = aChecked ? 2 : 0,
  153. })];
  154. NodesByPath[aFolder] = NewNode;
  155. Exception HasErrs;
  156. bool HasSubs = GetDirectories(aFolder, out HasErrs).Length > 0;
  157. if (HasErrs != null)
  158. {
  159. NewNode.Tag = null;
  160. NewNode.ForeColor = Color.DarkGray;
  161. NewNode.ToolTipText = HasErrs.Message;
  162. }
  163. // Add a fake node so that the little '+' will show
  164. if (HasSubs)
  165. NewNode.Nodes.Add(string.Empty);
  166. return NewNode;
  167. }
  168. /// <summary>
  169. /// Breaks the path into its parts
  170. /// </summary>
  171. /// <param name="aPath">Path to parse</param>
  172. /// <returns>Components of the path</returns>
  173. private string[] PathParts(string aPath)
  174. {
  175. List<string> Result = new List<string>();
  176. for (string Part = System.IO.Path.GetDirectoryName(aPath); !string.IsNullOrEmpty(Part); Part = System.IO.Path.GetDirectoryName(Part) )
  177. Result.Add(Part);
  178. Result.Reverse();
  179. return Result.ToArray();
  180. }
  181. /// <summary>
  182. /// This adds a list of nodes to the tree
  183. /// </summary>
  184. private void AddSelections()
  185. {
  186. foreach (string Entry in itsSelectedFolders)
  187. {
  188. TreeNode Parent = this.treeView.Nodes[0];
  189. foreach (string Part in PathParts(Entry))
  190. {
  191. Parent = UpdateNode(Parent.Nodes, Part, true);
  192. Parent.SelectedImageIndex = Parent.ImageIndex = 1;
  193. }
  194. UpdateNode(Parent.Nodes, Entry, true);
  195. }
  196. }
  197. /// <summary>
  198. /// This goes up the tree setting any partial checks
  199. /// </summary>
  200. /// <param name="aNode">Node to start</param>
  201. private void Propagate(TreeNode aNode)
  202. {
  203. if (aNode.Parent == null) return;
  204. for (TreeNode Up = aNode.Parent; Up != null; Up = Up.Parent)
  205. {
  206. // Sets partial check if some of the nodes are not checked
  207. int CheckCount = (from TreeNode qN in Up.Nodes where qN.Checked select qN).Count();
  208. Up.Checked = CheckCount != 0;
  209. Up.SelectedImageIndex = Up.ImageIndex = CheckCount == 0 ? 0 : (CheckCount == aNode.Parent.Nodes.Count ? 2 : 1);
  210. }
  211. }
  212. /// <summary>
  213. /// Set a node checked or not
  214. /// </summary>
  215. /// <param name="aNode">Node to set</param>
  216. /// <param name="aPropagate">Set up-tree nodes</param>
  217. private void CheckNodes(TreeNode aNode, bool aPropagate)
  218. {
  219. if (aPropagate) Propagate(aNode);
  220. SetChecked(aNode, aNode.Checked);
  221. }
  222. /// <summary>
  223. /// Sets a node and children checked or not [recursive]
  224. /// </summary>
  225. /// <param name="aNode">Node to set</param>
  226. /// <param name="aValue">Checked or not</param>
  227. private void SetChecked(TreeNode aNode, bool aValue)
  228. {
  229. aNode.SelectedImageIndex = aNode.ImageIndex = aValue ? 2 : 0;
  230. aNode.Checked = aValue;
  231. foreach (TreeNode Node in aNode.Nodes)
  232. SetChecked(Node, aValue);
  233. }
  234. private TreeNode itsContextNode;
  235. /// <summary>
  236. /// Pressed NODE - Determine if User has tried to check a node
  237. /// </summary>
  238. private void treeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
  239. {
  240. itsContextNode = e.Node;
  241. if (e.Button != MouseButtons.Left) return;
  242. // See if clicked inside checked zone (36 pixels left of node bounds)
  243. Rectangle ExpandedBounds = Rectangle.FromLTRB(e.Node.Bounds.Left - 36, e.Node.Bounds.Top, e.Node.Bounds.Right, e.Node.Bounds.Bottom);
  244. if (ExpandedBounds.Contains(e.Location) && e.Node.Tag != null)
  245. {
  246. e.Node.Checked = !e.Node.Checked;
  247. CheckNodes(e.Node, true);
  248. }
  249. }
  250. /// <summary>
  251. /// Context menu allows explorer to open
  252. /// </summary>
  253. private void openToolStripMenuItem_Click(object sender, EventArgs e)
  254. {
  255. if (itsContextNode != null)
  256. System.Diagnostics.Process.Start((string)itsContextNode.Tag);
  257. }
  258. }
  259. }