using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Duplicati.Scheduler.Utility
{
///
/// A folder tree with 3-state checkboxes
///
public partial class FolderTreeControl : UserControl
{
///
/// A file tree with 3-state checkboxes
///
public FolderTreeControl()
{
InitializeComponent();
InitializeTree();
this.treeView.BackColor = this.BackColor;
}
///
/// Make the tree, only populate root nodes
///
private void InitializeTree()
{
this.treeView.Nodes.Clear();
TreeNode Computer = UpdateNode(this.treeView.Nodes, "Computer", false);
foreach (System.IO.DriveInfo Drive in System.IO.DriveInfo.GetDrives())
if (Drive.IsReady) UpdateNode(Computer.Nodes, Drive.RootDirectory.Name, false);
Computer.ForeColor = this.ForeColor;
}
private bool InHere = false; // No re-entry
///
/// Expanding, make the children nodes
///
private void treeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
if (!InHere)
{
InHere = true;
this.treeView.BeginUpdate();
Populate((string)e.Node.Tag, e.Node, e.Node.Checked && e.Node.ImageIndex != 1);
this.treeView.Sort();
e.Node.Expand(); // Sort collapses all, so we gotta force expand, which is why we need that InHere thing
this.treeView.EndUpdate();
InHere = false;
}
}
public string[] itsSelectedFolders = new string[0];
///
/// List of selected folders
///
public string[] SelectedFolders
{
get { return Selected(this.treeView.Nodes[0]); }
}
///
/// Sets the list as selected
///
/// Folders to select
public void SetSelectedFolders(string[] aFolderList)
{
itsSelectedFolders = aFolderList;
AddSelections();
}
///
/// Get list of selected nodes [recursive]
///
/// Node to search
/// List of selected nodes
private string[] Selected(TreeNode aNode)
{
if (aNode.Checked && aNode.ImageIndex != 1) return new string[] { (string)aNode.Tag };
List Result = new List();
foreach (TreeNode Node in aNode.Nodes)
Result.AddRange(Selected(Node));
return Result.ToArray();
}
///
/// Returns true if node is selected
///
/// Node
/// true if node is selected
private bool IsChecked(TreeNode aNode)
{
return aNode.Checked && aNode.ImageIndex == 2;
}
///
/// A handy look up
///
private Dictionary NodesByPath = new Dictionary();
///
/// Gets a list of sub directories, returns exception if encountered
///
/// Path to list
/// null of OK, otherwise error
/// List of subs
private string[] GetDirectories(string aRoot, out Exception outError)
{
string[] Result = new string[0];
outError = Duplicati.Scheduler.Utility.Tools.TryCatch((Action)delegate() { Result = System.IO.Directory.GetDirectories(aRoot); });
return Result;
}
///
/// Fill a node with child nodes if any
///
/// Path to fill
/// Parent node
/// Check the nodes or not
private void Populate(string aRoot, TreeNode aParent, bool aChecked)
{
Exception Ignored;
foreach (string Folder in GetDirectories(aRoot, out Ignored))
UpdateNode(aParent.Nodes, Folder, aChecked);
}
///
/// Gets the name of the node, special case for Recycle Bin
///
/// Folder to get name
/// Name
private string NodeName(string aFolder)
{
string Result = System.IO.Path.GetFileName(aFolder);
if (string.IsNullOrEmpty(Result)) Result = aFolder;
return Result.Replace("$Recycle.Bin", " Recycle Bin");
}
///
/// Add/Change a node
///
/// Nodes for this one to join
/// Folder to use
/// Checked or not
/// The new node
private TreeNode UpdateNode(TreeNodeCollection aNodes, string aFolder, bool aChecked)
{
if (aNodes.Count > 0 && string.IsNullOrEmpty(aNodes[0].Text)) aNodes.RemoveAt(0); // Remove the fake
TreeNode NewNode = null;
if (NodesByPath.ContainsKey(aFolder))
NewNode = NodesByPath[aFolder]; // We already got one...
else
NewNode =
aNodes[
aNodes.Add(new TreeNode(NodeName( aFolder ))
{
Tag = aFolder,
Checked = aChecked,
SelectedImageIndex = aChecked ? 2 : 0,
ImageIndex = aChecked ? 2 : 0,
})];
NodesByPath[aFolder] = NewNode;
Exception HasErrs;
bool HasSubs = GetDirectories(aFolder, out HasErrs).Length > 0;
if (HasErrs != null)
{
NewNode.Tag = null;
NewNode.ForeColor = Color.DarkGray;
NewNode.ToolTipText = HasErrs.Message;
}
// Add a fake node so that the little '+' will show
if (HasSubs)
NewNode.Nodes.Add(string.Empty);
return NewNode;
}
///
/// Breaks the path into its parts
///
/// Path to parse
/// Components of the path
private string[] PathParts(string aPath)
{
List Result = new List();
for (string Part = System.IO.Path.GetDirectoryName(aPath); !string.IsNullOrEmpty(Part); Part = System.IO.Path.GetDirectoryName(Part) )
Result.Add(Part);
Result.Reverse();
return Result.ToArray();
}
///
/// This adds a list of nodes to the tree
///
private void AddSelections()
{
foreach (string Entry in itsSelectedFolders)
{
TreeNode Parent = this.treeView.Nodes[0];
foreach (string Part in PathParts(Entry))
{
Parent = UpdateNode(Parent.Nodes, Part, true);
Parent.SelectedImageIndex = Parent.ImageIndex = 1;
}
UpdateNode(Parent.Nodes, Entry, true);
}
}
///
/// This goes up the tree setting any partial checks
///
/// Node to start
private void Propagate(TreeNode aNode)
{
if (aNode.Parent == null) return;
for (TreeNode Up = aNode.Parent; Up != null; Up = Up.Parent)
{
// Sets partial check if some of the nodes are not checked
int CheckCount = (from TreeNode qN in Up.Nodes where qN.Checked select qN).Count();
Up.Checked = CheckCount != 0;
Up.SelectedImageIndex = Up.ImageIndex = CheckCount == 0 ? 0 : (CheckCount == aNode.Parent.Nodes.Count ? 2 : 1);
}
}
///
/// Set a node checked or not
///
/// Node to set
/// Set up-tree nodes
private void CheckNodes(TreeNode aNode, bool aPropagate)
{
if (aPropagate) Propagate(aNode);
SetChecked(aNode, aNode.Checked);
}
///
/// Sets a node and children checked or not [recursive]
///
/// Node to set
/// Checked or not
private void SetChecked(TreeNode aNode, bool aValue)
{
aNode.SelectedImageIndex = aNode.ImageIndex = aValue ? 2 : 0;
aNode.Checked = aValue;
foreach (TreeNode Node in aNode.Nodes)
SetChecked(Node, aValue);
}
private TreeNode itsContextNode;
///
/// Pressed NODE - Determine if User has tried to check a node
///
private void treeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
itsContextNode = e.Node;
if (e.Button != MouseButtons.Left) return;
// See if clicked inside checked zone (36 pixels left of node bounds)
Rectangle ExpandedBounds = Rectangle.FromLTRB(e.Node.Bounds.Left - 36, e.Node.Bounds.Top, e.Node.Bounds.Right, e.Node.Bounds.Bottom);
if (ExpandedBounds.Contains(e.Location) && e.Node.Tag != null)
{
e.Node.Checked = !e.Node.Checked;
CheckNodes(e.Node, true);
}
}
///
/// Context menu allows explorer to open
///
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
if (itsContextNode != null)
System.Diagnostics.Process.Start((string)itsContextNode.Tag);
}
}
}