diff --git a/Terminal.Gui/Windows/FileDialog2.cs b/Terminal.Gui/Windows/FileDialog2.cs index 2c0afa42a..44f4ffb30 100644 --- a/Terminal.Gui/Windows/FileDialog2.cs +++ b/Terminal.Gui/Windows/FileDialog2.cs @@ -8,6 +8,8 @@ using NStack; using Terminal.Gui.Trees; using static System.Environment; using System.Text.RegularExpressions; +using static Terminal.Gui.OpenDialog; +using System.Collections.ObjectModel; namespace Terminal.Gui { @@ -17,6 +19,13 @@ namespace Terminal.Gui { /// public class FileDialog2 : Dialog { + /// + /// Determine which type to open. + /// Defaults to (i.e. or + /// ). + /// + public OpenMode OpenMode { get; set; } = OpenMode.Mixed; + /// /// The currently selected path in the dialog. This is the result that should /// be used if is off and @@ -42,15 +51,7 @@ namespace Terminal.Gui { /// not or . /// /// If selecting only a single file/directory then you should use instead. - public IReadOnlyList MultiSelected { - get { - if (!AllowsMultipleSelection || Canceled || state == null) { - return new List ().AsReadOnly (); - } - - return state.Selected.Select (s => s.FileSystemInfo).ToList ().AsReadOnly (); - } - } + public IReadOnlyList MultiSelected { get; private set; } // TODO : expose these somehow for localization without compromising case/switch statements private const string HeaderFilename = "Filename"; @@ -233,8 +234,6 @@ namespace Terminal.Gui { treeView.ColorScheme = ColorSchemeDefault; treeView.KeyDown += (k) => k.Handled = this.TreeView_KeyDown (k.KeyEvent); - // TODO: delay or consider not doing this to avoid double load - tbPath.Text = Environment.CurrentDirectory; this.AllowsMultipleSelection = false; UpdateNavigationVisibility (); @@ -270,6 +269,17 @@ namespace Terminal.Gui { } } + private void Accept (IEnumerable toMultiAccept) + { + if(!AllowsMultipleSelection) { + return; + } + + MultiSelected = toMultiAccept.Select(s=>s.FileSystemInfo).ToList().AsReadOnly(); + tbPath.Text = MultiSelected.Count == 1 ? MultiSelected[0].FullName : ""; + Canceled = false; + Application.RequestStop (); + } private void Accept (FileInfo f) { tbPath.Text = f.FullName; @@ -279,7 +289,13 @@ namespace Terminal.Gui { private void Accept () { - tbPath.AcceptSelectionIfAny (); + // if an autocomplete is showing + if (tbPath.AcceptSelectionIfAny ()) { + + // enter just accepts it + return; + } + Canceled = false; Application.RequestStop (); } @@ -298,6 +314,7 @@ namespace Terminal.Gui { if (e.NewValue == null) { return; } + tbPath.Text = FileDialogTreeBuilder.NodeToDirectory (e.NewValue).FullName; } @@ -357,6 +374,10 @@ namespace Terminal.Gui { return; } + if(tableView.MultiSelect && tableView.MultiSelectedRegions.Any()) { + return; + } + var stats = RowToStats (obj.NewRow); if (stats == null) { @@ -444,6 +465,12 @@ namespace Terminal.Gui { return currentFragment != null && HasFocus && CursorIsAtEnd (); } + /// + /// Accepts the current autocomplete suggestion displaying in the text box. + /// Returns true if a valid suggestion was being rendered and acceptable or + /// false if no suggestion was showing. + /// + /// internal bool AcceptSelectionIfAny () { if (MakingSuggestion ()) { @@ -544,11 +571,16 @@ namespace Terminal.Gui { { base.OnLoaded (); + // if no path has been provided + if (tbPath.Text.Length <= 0) { + tbPath.Text = Environment.CurrentDirectory; + } + // to streamline user experience and allow direct typing of paths // with zero navigation we start with focus in the text box and any // default/current path fully selected and ready to be overwritten tbPath.FocusFirst (); - tbPath.SelectAll (); + tbPath.SelectAll (); } private bool TableView_KeyUp (KeyEvent keyEvent) { @@ -620,6 +652,12 @@ namespace Terminal.Gui { private void CellActivate (TableView.CellActivatedEventArgs obj) { + var multi = MultiRowToStats (); + if(multi.Any()) { + Accept (multi); + } + + var stats = RowToStats (obj.Row); @@ -656,7 +694,7 @@ namespace Terminal.Gui { tbPath.MoveCursorToEnd (); } - state = new FileDialogState (d); + state = new FileDialogState (d, OpenMode); tbPath.GenerateSuggestions (state); WriteStateToTableView (); @@ -712,6 +750,29 @@ namespace Terminal.Gui { return ColorSchemeDefault; } + /// + /// If is on and multiple rows are selected + /// this returns a union of all in the selection. + /// + /// Returns an empty collection if there are not at least 2 rows in the selection + /// + private IEnumerable MultiRowToStats () + { + var toReturn = new HashSet(); + + if(AllowsMultipleSelection && tableView.MultiSelectedRegions.Any()) { + + foreach(var p in tableView.GetAllSelectedCells()) { + + var add = state?.Children[(int)tableView.Table.Rows [p.Y] [0]]; + if(add != null) { + toReturn.Add (add); + } + } + } + + return toReturn.Count > 1 ? toReturn : Enumerable.Empty (); + } private FileSystemInfoStats RowToStats (int rowIndex) { return state?.Children [(int)tableView.Table.Rows [rowIndex] [0]]; @@ -871,12 +932,19 @@ namespace Terminal.Gui { public List selected = new List (); public IReadOnlyCollection Selected => selected.AsReadOnly (); - public FileDialogState (DirectoryInfo dir) + public FileDialogState (DirectoryInfo dir, OpenMode openMode) { Directory = dir; try { - var children = dir.GetFileSystemInfos ().Select (e => new FileSystemInfoStats (e)).ToList (); + List children; + + // if directories only + if (openMode == OpenMode.Directory) { + children = dir.GetDirectories ().Select (e => new FileSystemInfoStats (e)).ToList (); + } else { + children = dir.GetFileSystemInfos ().Select (e => new FileSystemInfoStats (e)).ToList (); + } // allow navigating up as '..' if (dir.Parent != null) { @@ -947,7 +1015,7 @@ namespace Terminal.Gui { var parent = dlg.state?.Directory.Parent; if (parent != null) { - back.Push (new FileDialogState (parent)); + back.Push (new FileDialogState (parent, dlg.OpenMode)); dlg.PushState (parent, true); return true; } diff --git a/UICatalog/Scenarios/FileDialog2Examples.cs b/UICatalog/Scenarios/FileDialog2Examples.cs index e4cbd4f62..3a53de8d6 100644 --- a/UICatalog/Scenarios/FileDialog2Examples.cs +++ b/UICatalog/Scenarios/FileDialog2Examples.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -11,58 +12,50 @@ namespace UICatalog.Scenarios { public class FileDialog2Examples : Scenario { public override void Setup () { - var btnOneFile = new Button ("Select One File") { - X = 1, - Y = 1 - }; - btnOneFile.Clicked += BtnOneFile_Clicked; - Win.Add (btnOneFile); + var y = 1; - var btnManyFiles = new Button ("Select Many Files") { - X = 1, - Y = Pos.Bottom(btnOneFile) + 1 - }; - btnManyFiles.Clicked += BtnManyFiles_Clicked; - Win.Add (btnManyFiles); - } - - - private void BtnOneFile_Clicked () - { - var fd = new FileDialog2 (); - Application.Run (fd); - - if (fd.Canceled) { - MessageBox.Query ( - "Dialog Canceled", - "You canceled file navigation and did not pick anything", - "Yup!"); - } else { - MessageBox.Query ( - "File chosen!", - "You chose " + Environment.NewLine + fd.Path, - "Oh yeah!"); + foreach(var multi in new bool [] {false, true }) { + foreach (OpenDialog.OpenMode openMode in Enum.GetValues (typeof (OpenDialog.OpenMode))) { + var btn = new Button ($"Select {(multi?"Many": "One")} {openMode}") { + X = 1, + Y = y + }; + SetupHandler (btn, openMode, multi); + y += 2; + Win.Add (btn); + } } } - private void BtnManyFiles_Clicked () - { - var fd = new FileDialog2 { - AllowsMultipleSelection = true - }; - Application.Run (fd); - if (fd.Canceled) { - MessageBox.Query ( - "Dialog Canceled", - "You canceled file navigation and did not pick anything", - "Yup!"); - } else { - MessageBox.Query ( - "File chosen!", - "You chose " + Environment.NewLine + - string.Join(Environment.NewLine,fd.MultiSelected.Select(m=>m.FullName)), - "Oh yeah!"); - } + private void SetupHandler (Button btn, OpenDialog.OpenMode mode, bool isMulti) + { + btn.Clicked += ()=>{ + var fd = new FileDialog2 { + AllowsMultipleSelection = isMulti, + OpenMode = mode, + }; + + Application.Run (fd); + + if (fd.Canceled) { + MessageBox.Query ( + "Canceled", + "You canceled navigation and did not pick anything", + "Ok"); + } else if (isMulti) { + MessageBox.Query ( + "Chosen!", + "You chose:" + Environment.NewLine + + string.Join (Environment.NewLine, fd.MultiSelected.Select (m => m.FullName)), + "Ok"); + } + else{ + MessageBox.Query ( + "Chosen!", + "You chose:" + Environment.NewLine + fd.Path, + "Ok"); + } + }; } } }