1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2006 Alexander Olk
23 // Alexander Olk (alex.olk@googlemail.com)
24 // Gert Driesen (drieseng@users.sourceforge.net)
30 using System.ComponentModel;
31 using System.Resources;
33 using System.Collections;
35 namespace System.Windows.Forms {
36 [DefaultEvent ("HelpRequest")]
37 [DefaultProperty ("SelectedPath")]
38 [Designer ("System.Windows.Forms.Design.FolderBrowserDialogDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
39 public sealed class FolderBrowserDialog : CommonDialog
41 #region Local Variables
42 private Environment.SpecialFolder rootFolder = Environment.SpecialFolder.Desktop;
43 private string selectedPath = string.Empty;
44 private bool showNewFolderButton = true;
46 private Label descriptionLabel;
47 private Button cancelButton;
48 private Button okButton;
49 private FolderBrowserTreeView folderBrowserTreeView;
50 private Button newFolderButton;
51 private ContextMenu folderBrowserTreeViewContextMenu;
52 private MenuItem newFolderMenuItem;
54 private string old_selectedPath = string.Empty;
56 private readonly string folderbrowserdialog_string = "FolderBrowserDialog";
57 private readonly string width_string = "Width";
58 private readonly string height_string = "Height";
59 private readonly string x_string = "X";
60 private readonly string y_string = "Y";
61 #endregion // Local Variables
63 #region Public Constructors
64 public FolderBrowserDialog ()
66 form = new DialogForm (this);
67 Size formConfigSize = Size.Empty;
68 Point formConfigLocation = Point.Empty;
70 object formWidth = MWFConfig.GetValue (folderbrowserdialog_string, width_string);
72 object formHeight = MWFConfig.GetValue (folderbrowserdialog_string, height_string);
74 if (formHeight != null && formWidth != null)
75 formConfigSize = new Size ((int)formWidth, (int)formHeight);
77 object formLocationX = MWFConfig.GetValue (folderbrowserdialog_string, x_string);
78 object formLocationY = MWFConfig.GetValue (folderbrowserdialog_string, y_string);
80 if (formLocationX != null && formLocationY != null)
81 formConfigLocation = new Point ((int)formLocationX, (int)formLocationY);
83 newFolderButton = new Button ();
84 folderBrowserTreeView = new FolderBrowserTreeView (this);
85 okButton = new Button ();
86 cancelButton = new Button ();
87 descriptionLabel = new Label ();
88 folderBrowserTreeViewContextMenu = new ContextMenu ();
90 form.AcceptButton = okButton;
91 form.CancelButton = cancelButton;
93 form.SuspendLayout ();
94 form.ClientSize = new Size (322, 324);
95 form.MinimumSize = new Size (310, 254);
96 form.Text = "Browse For Folder";
97 form.SizeGripStyle = SizeGripStyle.Show;
99 newFolderMenuItem = new MenuItem("New Folder", new EventHandler (OnClickNewFolderButton));
100 folderBrowserTreeViewContextMenu.MenuItems.Add(newFolderMenuItem);
103 descriptionLabel.Anchor = ((AnchorStyles)(((AnchorStyles.Top | AnchorStyles.Left)
104 | AnchorStyles.Right)));
105 descriptionLabel.Location = new Point (15, 14);
106 descriptionLabel.Size = new Size (292, 40);
107 descriptionLabel.TabIndex = 0;
108 descriptionLabel.Text = string.Empty;
110 // folderBrowserTreeView
111 folderBrowserTreeView.Anchor = ((AnchorStyles)((((AnchorStyles.Top | AnchorStyles.Bottom)
113 | AnchorStyles.Right)));
114 folderBrowserTreeView.ImageIndex = -1;
115 folderBrowserTreeView.Location = new Point (15, 60);
116 folderBrowserTreeView.SelectedImageIndex = -1;
117 folderBrowserTreeView.Size = new Size (292, 212);
118 folderBrowserTreeView.TabIndex = 3;
119 folderBrowserTreeView.ShowLines = false;
120 folderBrowserTreeView.ShowPlusMinus = true;
121 folderBrowserTreeView.HotTracking = true;
122 folderBrowserTreeView.BorderStyle = BorderStyle.Fixed3D;
123 folderBrowserTreeView.ContextMenu = folderBrowserTreeViewContextMenu;
124 //folderBrowserTreeView.Indent = 2;
127 newFolderButton.Anchor = ((AnchorStyles)((AnchorStyles.Bottom | AnchorStyles.Left)));
128 newFolderButton.FlatStyle = FlatStyle.System;
129 newFolderButton.Location = new Point (15, 285);
130 newFolderButton.Size = new Size (105, 23);
131 newFolderButton.TabIndex = 4;
132 newFolderButton.Text = "Make New Folder";
133 newFolderButton.Enabled = true;
136 okButton.Anchor = ((AnchorStyles)((AnchorStyles.Bottom | AnchorStyles.Right)));
137 okButton.FlatStyle = FlatStyle.System;
138 okButton.Location = new Point (135, 285);
139 okButton.Size = new Size (80, 23);
140 okButton.TabIndex = 1;
141 okButton.Text = "OK";
144 cancelButton.Anchor = ((AnchorStyles)((AnchorStyles.Bottom | AnchorStyles.Right)));
145 cancelButton.DialogResult = DialogResult.Cancel;
146 cancelButton.FlatStyle = FlatStyle.System;
147 cancelButton.Location = new Point (227, 285);
148 cancelButton.Size = new Size (80, 23);
149 cancelButton.TabIndex = 2;
150 cancelButton.Text = "Cancel";
152 form.Controls.Add (cancelButton);
153 form.Controls.Add (okButton);
154 form.Controls.Add (newFolderButton);
155 form.Controls.Add (folderBrowserTreeView);
156 form.Controls.Add (descriptionLabel);
158 form.ResumeLayout (false);
160 if (formConfigSize != Size.Empty) {
161 form.Size = formConfigSize;
164 if (formConfigLocation != Point.Empty) {
165 form.Location = formConfigLocation;
168 okButton.Click += new EventHandler (OnClickOKButton);
169 cancelButton.Click += new EventHandler (OnClickCancelButton);
170 newFolderButton.Click += new EventHandler (OnClickNewFolderButton);
172 form.VisibleChanged += new EventHandler (OnFormVisibleChanged);
174 RootFolder = rootFolder;
177 #endregion // Public Constructors
179 #region Public Instance Properties
183 public string Description {
185 descriptionLabel.Text = value;
189 return descriptionLabel.Text;
194 [DefaultValue(Environment.SpecialFolder.Desktop)]
196 [TypeConverter (typeof (SpecialFolderEnumConverter))]
197 public Environment.SpecialFolder RootFolder {
201 Type enumType = typeof (Environment.SpecialFolder);
202 if (!Enum.IsDefined (enumType, v))
203 throw new InvalidEnumArgumentException (
204 "value", v, enumType);
215 [Editor("System.Windows.Forms.Design.SelectedPathEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))]
217 public string SelectedPath {
220 value = string.Empty;
221 selectedPath = value;
222 old_selectedPath = value;
232 public bool ShowNewFolderButton {
234 if (value != showNewFolderButton) {
235 newFolderButton.Visible = value;
236 showNewFolderButton = value;
241 return showNewFolderButton;
244 #endregion // Public Instance Properties
246 #region Public Instance Methods
247 public override void Reset ()
249 Description = string.Empty;
250 RootFolder = Environment.SpecialFolder.Desktop;
251 selectedPath = string.Empty;
252 ShowNewFolderButton = true;
255 protected override bool RunDialog (IntPtr hWndOwner)
257 folderBrowserTreeView.RootFolder = RootFolder;
258 folderBrowserTreeView.SelectedPath = SelectedPath;
264 #endregion // Public Instance Methods
266 #region Internal Methods
267 void OnClickOKButton (object sender, EventArgs e)
269 WriteConfigValues ();
271 form.DialogResult = DialogResult.OK;
274 void OnClickCancelButton (object sender, EventArgs e)
276 WriteConfigValues ();
278 selectedPath = old_selectedPath;
279 form.DialogResult = DialogResult.Cancel;
282 void OnClickNewFolderButton (object sender, EventArgs e)
284 folderBrowserTreeView.CreateNewFolder ();
287 void OnFormVisibleChanged (object sender, EventArgs e)
289 if (form.Visible && okButton.Enabled)
293 private void WriteConfigValues ()
295 MWFConfig.SetValue (folderbrowserdialog_string, width_string, form.Width);
296 MWFConfig.SetValue (folderbrowserdialog_string, height_string, form.Height);
297 MWFConfig.SetValue (folderbrowserdialog_string, x_string, form.Location.X);
298 MWFConfig.SetValue (folderbrowserdialog_string, y_string, form.Location.Y);
300 #endregion // Internal Methods
304 [EditorBrowsable(EditorBrowsableState.Never)]
305 public new event EventHandler HelpRequest {
306 add { base.HelpRequest += value; }
307 remove { base.HelpRequest -= value; }
311 internal class FolderBrowserTreeView : TreeView
313 private MWFVFS vfs = new MWFVFS ();
314 new private FBTreeNode root_node;
315 private FolderBrowserDialog parentDialog;
316 private ImageList imageList = new ImageList ();
317 private Environment.SpecialFolder rootFolder;
318 private bool dont_enable = false;
319 private TreeNode node_under_mouse;
321 public FolderBrowserTreeView (FolderBrowserDialog parent_dialog)
323 parentDialog = parent_dialog;
324 HideSelection = false;
325 ImageList = imageList;
329 public Environment.SpecialFolder RootFolder {
333 string root_path = string.Empty;
335 switch (rootFolder) {
336 case Environment.SpecialFolder.Desktop:
337 root_node = new FBTreeNode ("Desktop");
338 root_node.RealPath = ThemeEngine.Current.Places (UIIcon.PlacesDesktop);
339 root_path = MWFVFS.DesktopPrefix;
341 case Environment.SpecialFolder.Recent:
342 root_node = new FBTreeNode ("My Recent Documents");
343 root_node.RealPath = ThemeEngine.Current.Places (UIIcon.PlacesRecentDocuments);
344 root_path = MWFVFS.RecentlyUsedPrefix;
346 case Environment.SpecialFolder.MyComputer:
347 root_node = new FBTreeNode ("My Computer");
348 root_path = MWFVFS.MyComputerPrefix;
350 case Environment.SpecialFolder.Personal:
351 root_node = new FBTreeNode ("Personal");
352 root_path = MWFVFS.PersonalPrefix;
353 root_node.RealPath = ThemeEngine.Current.Places (UIIcon.PlacesPersonal);
356 root_node = new FBTreeNode (rootFolder.ToString ());
357 root_node.RealPath = Environment.GetFolderPath (rootFolder);
358 root_path = root_node.RealPath;
362 root_node.Tag = root_path;
363 root_node.ImageIndex = NodeImageIndex (root_path);
369 FillNode (root_node);
373 Nodes.Add (root_node);
377 public string SelectedPath {
379 if (value.Length == 0)
382 if (!Path.IsPathRooted (value))
386 if (Check_if_path_is_child_of_RootFolder (value))
387 SetSelectedPath (Path.GetFullPath (value));
388 } catch (Exception) {
389 // If we can't set the user's requested path, the
390 // best we can do is not crash and reset to the default
392 RootFolder = rootFolder;
397 private string parent_real_path;
398 private bool dont_do_onbeforeexpand;
400 public void CreateNewFolder ()
402 FBTreeNode fbnode = node_under_mouse == null ? SelectedNode as FBTreeNode : node_under_mouse as FBTreeNode;
404 if (fbnode == null || fbnode.RealPath == null)
407 string tmp_filename = "New Folder";
409 if (Directory.Exists (Path.Combine (fbnode.RealPath, tmp_filename))) {
412 if (XplatUI.RunningOnUnix) {
413 tmp_filename = tmp_filename + "-" + i;
415 tmp_filename = tmp_filename + " (" + i + ")";
418 while (Directory.Exists (Path.Combine (fbnode.RealPath, tmp_filename))) {
420 if (XplatUI.RunningOnUnix) {
421 tmp_filename = "New Folder" + "-" + i;
423 tmp_filename = "New Folder" + " (" + i + ")";
428 parent_real_path = fbnode.RealPath;
431 dont_do_onbeforeexpand = true;
433 dont_do_onbeforeexpand = false;
435 // to match MS, immediately create the new folder
436 // and rename it once the label edit completes
437 string fullPath = Path.Combine (fbnode.RealPath, tmp_filename);
438 if (!vfs.CreateFolder (fullPath))
441 FBTreeNode new_node = new FBTreeNode (tmp_filename);
442 new_node.ImageIndex = NodeImageIndex (tmp_filename);
443 new_node.Tag = new_node.RealPath = fullPath;
444 fbnode.Nodes.Add (new_node);
447 new_node.BeginEdit();
450 protected override void OnAfterLabelEdit (NodeLabelEditEventArgs e)
452 if (e.Label != null) {
453 if (e.Label.Length > 0) {
454 FBTreeNode fbnode = e.Node as FBTreeNode;
456 string originalPath = fbnode.RealPath;
457 string newPath = Path.Combine (parent_real_path, e.Label);
459 if (vfs.MoveFolder (originalPath, newPath)) {
460 fbnode.Tag = fbnode.RealPath = newPath;
473 // select new folder only if both the curren node under
474 // mouse pointer and SelectedNode are the same (match .Net)
475 if (node_under_mouse == SelectedNode)
476 SelectedNode = e.Node;
478 // disable LabelEdit when either edit has finished
479 // or has been cancelled, to prevent the user from
480 // editing label of existing folders
484 private void SetSelectedPath (string path)
488 FBTreeNode node = FindPathInNodes (path, Nodes);
491 Stack stack = new Stack ();
493 string path_cut = path.Substring (0, path.LastIndexOf (Path.DirectorySeparatorChar));
494 if (!XplatUI.RunningOnUnix && path_cut.Length == 2)
495 path_cut += Path.DirectorySeparatorChar;
497 while (node == null && path_cut.Length > 0) {
498 node = FindPathInNodes (path_cut, Nodes);
501 string path_cut_new = path_cut.Substring (0, path_cut.LastIndexOf (Path.DirectorySeparatorChar));
502 string leftover = path_cut.Replace (path_cut_new, string.Empty);
504 stack.Push (leftover);
506 path_cut = path_cut_new;
510 // If we didn't find anything, just display the full, unselected tree
513 RootFolder = rootFolder;
520 // walk through the subdirs and fill the nodes
521 while (stack.Count > 0) {
522 string part_name = stack.Pop () as string;
524 foreach (TreeNode treeNode in node.Nodes) {
525 FBTreeNode fbnode = treeNode as FBTreeNode;
527 if (path_cut + part_name == fbnode.RealPath) {
529 path_cut += part_name;
538 // finally find the node for the complete path
539 foreach (TreeNode treeNode in node.Nodes) {
540 FBTreeNode fbnode = treeNode as FBTreeNode;
542 if (path == fbnode.RealPath) {
551 node.EnsureVisible ();
557 private FBTreeNode FindPathInNodes (string path, TreeNodeCollection nodes)
559 // On Windows the devices can be passed as C: yet match
562 // Hackish, but works
563 if (!XplatUI.RunningOnUnix && path.Length == 2)
564 path += Path.DirectorySeparatorChar;
566 foreach (TreeNode node in nodes) {
567 FBTreeNode fbnode = node as FBTreeNode;
569 if (fbnode != null && fbnode.RealPath != null) {
570 if (fbnode.RealPath == path)
574 FBTreeNode n = FindPathInNodes (path, node.Nodes);
582 private bool Check_if_path_is_child_of_RootFolder (string path)
584 string root_path = (string) root_node.RealPath;
586 if (root_path != null || rootFolder == Environment.SpecialFolder.MyComputer) {
588 if (!Directory.Exists (path))
591 switch (rootFolder) {
592 case Environment.SpecialFolder.Desktop:
593 case Environment.SpecialFolder.MyComputer:
595 case Environment.SpecialFolder.Personal:
596 if (!path.StartsWith (root_path))
609 private void FillNode (TreeNode node)
614 vfs.ChangeDirectory ((string)node.Tag);
615 ArrayList folders = vfs.GetFoldersOnly ();
617 foreach (FSEntry fsentry in folders) {
618 if (fsentry.Name.StartsWith ("."))
621 FBTreeNode child = new FBTreeNode (fsentry.Name);
622 child.Tag = fsentry.FullName;
623 child.RealPath = fsentry.RealName == null ? fsentry.FullName : fsentry.RealName;
624 child.ImageIndex = NodeImageIndex (fsentry.FullName);
626 vfs.ChangeDirectory (fsentry.FullName);
627 ArrayList sub_folders = vfs.GetFoldersOnly ();
629 foreach (FSEntry fsentry_sub in sub_folders) {
630 if (!fsentry_sub.Name.StartsWith (".")) {
631 child.Nodes.Add (new TreeNode (String.Empty));
636 node.Nodes.Add (child);
642 private void SetupImageList ()
644 imageList.ColorDepth = ColorDepth.Depth32Bit;
645 imageList.ImageSize = new Size (16, 16);
646 imageList.Images.Add (ThemeEngine.Current.Images (UIIcon.PlacesRecentDocuments, 16));
647 imageList.Images.Add (ThemeEngine.Current.Images (UIIcon.PlacesDesktop, 16));
648 imageList.Images.Add (ThemeEngine.Current.Images (UIIcon.PlacesPersonal, 16));
649 imageList.Images.Add (ThemeEngine.Current.Images (UIIcon.PlacesMyComputer, 16));
650 imageList.Images.Add (ThemeEngine.Current.Images (UIIcon.PlacesMyNetwork, 16));
651 imageList.Images.Add (ThemeEngine.Current.Images (UIIcon.NormalFolder, 16));
652 imageList.TransparentColor = Color.Transparent;
655 private int NodeImageIndex (string path)
659 if (path == MWFVFS.DesktopPrefix)
661 else if (path == MWFVFS.RecentlyUsedPrefix)
663 else if (path == MWFVFS.PersonalPrefix)
665 else if (path == MWFVFS.MyComputerPrefix)
667 else if (path == MWFVFS.MyNetworkPrefix)
673 protected override void OnAfterSelect (TreeViewEventArgs e)
678 FBTreeNode fbnode = e.Node as FBTreeNode;
680 if (fbnode.RealPath == null || fbnode.RealPath.IndexOf ("://") != -1) {
681 parentDialog.okButton.Enabled = false;
682 parentDialog.newFolderButton.Enabled = false;
683 parentDialog.newFolderMenuItem.Enabled = false;
686 parentDialog.okButton.Enabled = true;
687 parentDialog.newFolderButton.Enabled = true;
688 parentDialog.newFolderMenuItem.Enabled = true;
689 parentDialog.selectedPath = fbnode.RealPath;
693 base.OnAfterSelect (e);
696 protected internal override void OnBeforeExpand (TreeViewCancelEventArgs e)
698 if (!dont_do_onbeforeexpand) {
699 if (e.Node == root_node)
704 base.OnBeforeExpand (e);
707 protected override void OnMouseDown (MouseEventArgs e)
709 node_under_mouse = GetNodeAt (e.X, e.Y);
710 base.OnMouseDown (e);
713 protected override void OnMouseUp (MouseEventArgs e)
715 if (SelectedNode == null) {
716 parentDialog.okButton.Enabled = false;
717 parentDialog.newFolderButton.Enabled = false;
718 parentDialog.newFolderMenuItem.Enabled = false;
721 parentDialog.okButton.Enabled = true;
722 parentDialog.newFolderButton.Enabled = true;
723 parentDialog.newFolderMenuItem.Enabled = true;
726 node_under_mouse = null;
732 internal class FBTreeNode : TreeNode
734 private string realPath = null;
736 public FBTreeNode (string text)
741 public string RealPath {
753 internal class SpecialFolderEnumConverter : TypeConverter
755 public override object ConvertFrom (ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
757 if ((value == null) || !(value is String))
758 return base.ConvertFrom (context, culture, value);
760 return Enum.Parse (typeof (Environment.SpecialFolder), (string)value, true);
763 public override object ConvertTo (ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
765 if ((value == null) || !(value is Environment.SpecialFolder) || (destinationType != typeof (string)))
766 return base.ConvertTo (context, culture, value, destinationType);
768 return ((Environment.SpecialFolder)value).ToString ();