diff --git a/Terminal.Gui/FileServices/FileDialogStyle.cs b/Terminal.Gui/FileServices/FileDialogStyle.cs index b8d55430f3..66ddc30892 100644 --- a/Terminal.Gui/FileServices/FileDialogStyle.cs +++ b/Terminal.Gui/FileServices/FileDialogStyle.cs @@ -21,7 +21,7 @@ public class FileDialogStyle { /// Gets or sets the default value to use for . /// This can be populated from .tui config files via /// - [SerializableConfigurationProperty(Scope = typeof (SettingsScope))] + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] public static bool DefaultUseColors { get; set; } /// @@ -42,20 +42,20 @@ public class FileDialogStyle { /// Gets or sets the class responsible for determining which symbol /// to use to represent files and directories. /// - public FileSystemIconProvider IconProvider { get; set;} = new FileSystemIconProvider(); + public FileSystemIconProvider IconProvider { get; set; } = new FileSystemIconProvider (); /// /// Gets or sets the class thatis responsible for determining which color /// to use to represent files and directories when is /// . /// - public FileSystemColorProvider ColorProvider { get;set;} = new FileSystemColorProvider(); + public FileSystemColorProvider ColorProvider { get; set; } = new FileSystemColorProvider (); /// /// Gets or sets the culture to use (e.g. for number formatting). /// Defaults to . /// - public CultureInfo Culture {get;set;} = CultureInfo.CurrentUICulture; + public CultureInfo Culture { get; set; } = CultureInfo.CurrentUICulture; /// /// Gets or sets the header text displayed in the Filename column of the files table. @@ -80,12 +80,12 @@ public class FileDialogStyle { /// /// Gets or sets the text displayed in the 'Search' text box when user has not supplied any input yet. /// - public string SearchCaption { get; internal set; } = Strings.fdSearchCaption; + public string SearchCaption { get; set; } = Strings.fdSearchCaption; /// /// Gets or sets the text displayed in the 'Path' text box when user has not supplied any input yet. /// - public string PathCaption { get; internal set; } = Strings.fdPathCaption; + public string PathCaption { get; set; } = Strings.fdPathCaption; /// /// Gets or sets the text on the 'Ok' button. Typically you may want to change this to @@ -93,40 +93,52 @@ public class FileDialogStyle { /// public string OkButtonText { get; set; } = "Ok"; + /// + /// Gets or sets the text on the 'Cancel' button. + /// + public string CancelButtonText { get; set; } = "Cancel"; + + /// + /// Gets or sets whether to flip the order of the Ok and Cancel buttons. Defaults + /// to false (Ok button then Cancel button). Set to true to show Cancel button on + /// left then Ok button instead. + /// + public bool FlipOkCancelButtonLayoutOrder { get; set; } + /// /// Gets or sets error message when user attempts to select a file type that is not one of /// - public string WrongFileTypeFeedback { get; internal set; } = Strings.fdWrongFileTypeFeedback; + public string WrongFileTypeFeedback { get; set; } = Strings.fdWrongFileTypeFeedback; /// /// Gets or sets error message when user selects a directory that does not exist and /// is and is . /// - public string DirectoryMustExistFeedback { get; internal set; } = Strings.fdDirectoryMustExistFeedback; + public string DirectoryMustExistFeedback { get; set; } = Strings.fdDirectoryMustExistFeedback; /// /// Gets or sets error message when user is /// and user enters the name of an existing file (File system cannot have a folder with the same name as a file). /// - public string FileAlreadyExistsFeedback { get; internal set; } = Strings.fdFileAlreadyExistsFeedback; + public string FileAlreadyExistsFeedback { get; set; } = Strings.fdFileAlreadyExistsFeedback; /// /// Gets or sets error message when user selects a file that does not exist and /// is and is . /// - public string FileMustExistFeedback { get; internal set; } = Strings.fdFileMustExistFeedback; + public string FileMustExistFeedback { get; set; } = Strings.fdFileMustExistFeedback; /// /// Gets or sets error message when user is /// and user enters the name of an existing directory (File system cannot have a folder with the same name as a file). /// - public string DirectoryAlreadyExistsFeedback { get; internal set; } = Strings.fdDirectoryAlreadyExistsFeedback; + public string DirectoryAlreadyExistsFeedback { get; set; } = Strings.fdDirectoryAlreadyExistsFeedback; /// /// Gets or sets error message when user selects a file/dir that does not exist and /// is and is . /// - public string FileOrDirectoryMustExistFeedback { get; internal set; } = Strings.fdFileOrDirectoryMustExistFeedback; + public string FileOrDirectoryMustExistFeedback { get; set; } = Strings.fdFileOrDirectoryMustExistFeedback; /// /// Gets the style settings for the table of files (in currently selected directory). @@ -172,7 +184,7 @@ public FileDialogStyle (IFileSystem fileSystem) } - private Dictionary DefaultTreeRootGetter () + private Dictionary DefaultTreeRootGetter () { var roots = new Dictionary (); try { @@ -180,7 +192,7 @@ private Dictionary DefaultTreeRootGetter () var dir = _fileSystem.DirectoryInfo.New (d); - if (!roots.ContainsKey(dir)) { + if (!roots.ContainsKey (dir)) { roots.Add (dir, d); } } @@ -194,14 +206,14 @@ private Dictionary DefaultTreeRootGetter () try { var path = Environment.GetFolderPath (special); - if(string.IsNullOrWhiteSpace (path)) { + if (string.IsNullOrWhiteSpace (path)) { continue; } var dir = _fileSystem.DirectoryInfo.New (path); if (!roots.ContainsKey (dir) && dir.Exists) { - roots.Add (dir, special.ToString()); + roots.Add (dir, special.ToString ()); } } catch (Exception) { // Special file exists but contents are unreadable (permissions?) diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index e71fdf9c68..db98d5ee25 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -105,7 +105,7 @@ public partial class FileDialog : Dialog { private int currentSortColumn; private bool currentSortIsAsc = true; - private Dictionary _treeRoots = new Dictionary(); + private Dictionary _treeRoots = new Dictionary (); /// /// Event fired when user attempts to confirm a selection (or multi selection). @@ -142,11 +142,7 @@ public FileDialog (IFileSystem fileSystem) this.btnOk = new Button (Style.OkButtonText) { Y = Pos.AnchorEnd (1), - X = Pos.Function (() => - this.Bounds.Width - - btnOk.Bounds.Width - // TODO: Fiddle factor, seems the Bounds are wrong for someone - - 2) + X = Pos.Function (CalculateOkButtonPosX) }; this.btnOk.Clicked += (s, e) => this.Accept (true); this.btnOk.KeyPress += (s, k) => { @@ -156,14 +152,7 @@ public FileDialog (IFileSystem fileSystem) this.btnCancel = new Button (Strings.btnCancel) { Y = Pos.AnchorEnd (1), - X = Pos.Function (() => - this.Bounds.Width - - btnOk.Bounds.Width - - btnCancel.Bounds.Width - - 1 - // TODO: Fiddle factor, seems the Bounds are wrong for someone - - 2 - ) + X = Pos.Right (btnOk) + 1 }; this.btnCancel.KeyPress += (s, k) => { this.NavigateIf (k, Key.CursorLeft, this.btnToggleSplitterCollapse); @@ -188,7 +177,6 @@ public FileDialog (IFileSystem fileSystem) this.tbPath = new TextField { Width = Dim.Fill (0), - Caption = Style.PathCaption, CaptionColor = Color.Black }; this.tbPath.KeyPress += (s, k) => { @@ -285,7 +273,6 @@ public FileDialog (IFileSystem fileSystem) tbFind = new TextField { X = Pos.Right (this.btnToggleSplitterCollapse) + 1, - Caption = Style.SearchCaption, CaptionColor = Color.Black, Width = 30, Y = Pos.AnchorEnd (1), @@ -373,17 +360,27 @@ public FileDialog (IFileSystem fileSystem) this.Add (this.splitContainer); } + private int CalculateOkButtonPosX () + { + return this.Bounds.Width + - btnOk.Bounds.Width + - btnCancel.Bounds.Width + - 1 + // TODO: Fiddle factor, seems the Bounds are wrong for someone + - 2; + } + private string AspectGetter (object o) { var fsi = (IFileSystemInfo)o; - if(o is IDirectoryInfo dir && _treeRoots.ContainsKey(dir)) { + if (o is IDirectoryInfo dir && _treeRoots.ContainsKey (dir)) { // Directory has a special name e.g. 'Pictures' return _treeRoots [dir]; } - return (Style.IconProvider.GetIconWithOptionalSpace(fsi) + fsi.Name).Trim(); + return (Style.IconProvider.GetIconWithOptionalSpace (fsi) + fsi.Name).Trim (); } private void OnTableViewMouseClick (object sender, MouseEventEventArgs e) @@ -644,11 +641,28 @@ public override void OnLoaded () // May have been updated after instance was constructed this.btnOk.Text = Style.OkButtonText; + this.btnCancel.Text = Style.CancelButtonText; this.btnUp.Text = this.GetUpButtonText (); this.btnBack.Text = this.GetBackButtonText (); this.btnForward.Text = this.GetForwardButtonText (); this.btnToggleSplitterCollapse.Text = this.GetToggleSplitterText (false); + if (Style.FlipOkCancelButtonLayoutOrder) { + btnCancel.X = Pos.Function (this.CalculateOkButtonPosX); + btnOk.X = Pos.Right (btnCancel) + 1; + + + // Flip tab order too for consistency + var p1 = this.btnOk.TabIndex; + var p2 = this.btnCancel.TabIndex; + + this.btnOk.TabIndex = p2; + this.btnCancel.TabIndex = p1; + } + + tbPath.Caption = Style.PathCaption; + tbFind.Caption = Style.SearchCaption; + tbPath.Autocomplete.ColorScheme.Normal = Attribute.Make (Color.Black, tbPath.ColorScheme.Normal.Background); _treeRoots = Style.TreeRootGetter (); @@ -700,7 +714,7 @@ public override void OnLoaded () // if no path has been provided if (this.tbPath.Text.Length <= 0) { - this.tbPath.Text = Environment.CurrentDirectory; + this.Path = Environment.CurrentDirectory; } // to streamline user experience and allow direct typing of paths @@ -803,7 +817,7 @@ private void Accept (IEnumerable toMultiAccept) .Select (s => s.FileSystemInfo.FullName) .ToList ().AsReadOnly (); - this.tbPath.Text = this.MultiSelected.Count == 1 ? this.MultiSelected [0] : string.Empty; + this.Path = this.MultiSelected.Count == 1 ? this.MultiSelected [0] : string.Empty; FinishAccept (); } @@ -816,7 +830,7 @@ private void Accept (IFileInfo f) return; } - this.tbPath.Text = f.FullName; + this.Path = f.FullName; if (AllowsMultipleSelection) { this.MultiSelected = new List { f.FullName }.AsReadOnly (); @@ -895,7 +909,7 @@ private void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs return; } - this.tbPath.Text = e.NewValue.FullName; + this.Path = e.NewValue.FullName; } private void UpdateNavigationVisibility () @@ -931,7 +945,7 @@ private void TableView_SelectedCellChanged (object sender, SelectedCellChangedEv try { this.pushingState = true; - this.tbPath.Text = dest.FullName; + this.Path = dest.FullName; this.State.Selected = stats; this.tbPath.Autocomplete.ClearSuggestions (); @@ -1134,12 +1148,10 @@ private void PushState (FileDialogState newState, bool addCurrentStateToHistory, this.tbPath.Autocomplete.ClearSuggestions (); if (pathText != null) { - this.tbPath.Text = pathText; - this.tbPath.MoveEnd (); + this.Path = pathText; } else if (setPathText) { - this.tbPath.Text = newState.Directory.FullName; - this.tbPath.MoveEnd (); + this.Path = newState.Directory.FullName; } this.State = newState; @@ -1185,13 +1197,13 @@ private ColorScheme ColorGetter (CellColorGetterArgs args) } - var color = Style.ColorProvider.GetTrueColor(stats.FileSystemInfo) - ?? TrueColor.FromConsoleColor(Color.White); - var black = TrueColor.FromConsoleColor(Color.Black); + var color = Style.ColorProvider.GetTrueColor (stats.FileSystemInfo) + ?? TrueColor.FromConsoleColor (Color.White); + var black = TrueColor.FromConsoleColor (Color.Black); // TODO: Add some kind of cache for this - return new ColorScheme{ - Normal = new Attribute (color,black), + return new ColorScheme { + Normal = new Attribute (color, black), HotNormal = new Attribute (color, black), Focus = new Attribute (black, color), HotFocus = new Attribute (black, color), diff --git a/UICatalog/Scenarios/FileDialogExamples.cs b/UICatalog/Scenarios/FileDialogExamples.cs index e110d27a24..8a0fcf11ec 100644 --- a/UICatalog/Scenarios/FileDialogExamples.cs +++ b/UICatalog/Scenarios/FileDialogExamples.cs @@ -25,6 +25,9 @@ public class FileDialogExamples : Scenario { private RadioGroup rgIcons; private RadioGroup rgAllowedTypes; + private TextField tbOkButton; + private TextField tbCancelButton; + private CheckBox cbFlipButtonOrder; public override void Setup () { var y = 0; @@ -110,6 +113,25 @@ public override void Setup () rgAllowedTypes.RadioLabels = new string [] { "Any", "Csv (Recommended)", "Csv (Strict)" }; Win.Add (rgAllowedTypes); + y = 5; + x = 45; + + Win.Add (new LineView (Orientation.Vertical) { + X = x++, + Y = y + 1, + Height = 4 + }); + Win.Add (new Label ("Buttons") { X = x++, Y = y++ }); + + Win.Add (new Label ("Ok Text:") { X = x, Y = y++ }); + tbOkButton = new TextField () { X = x, Y = y++, Width = 12 }; + Win.Add (tbOkButton); + Win.Add (new Label ("Cancel Text:") { X = x, Y = y++ }); + tbCancelButton = new TextField () { X = x, Y = y++, Width = 12 }; + Win.Add (tbCancelButton); + cbFlipButtonOrder = new CheckBox ("Flip Order") { X = x, Y = y++ }; + Win.Add (cbFlipButtonOrder); + var btn = new Button ($"Run Dialog") { X = 1, Y = 9 @@ -165,7 +187,7 @@ rgOpenMode.RadioLabels [rgOpenMode.SelectedItem].ToString ()), if (cbDrivesOnlyInTree.Checked ?? false) { fd.Style.TreeRootGetter = () => { - return System.Environment.GetLogicalDrives ().ToDictionary(dirInfoFactory.New,k=>k); + return System.Environment.GetLogicalDrives ().ToDictionary (dirInfoFactory.New, k => k); }; } @@ -178,6 +200,16 @@ rgOpenMode.RadioLabels [rgOpenMode.SelectedItem].ToString ()), } + if (!string.IsNullOrWhiteSpace (tbOkButton.Text)) { + fd.Style.OkButtonText = tbOkButton.Text; + } + if (!string.IsNullOrWhiteSpace (tbCancelButton.Text)) { + fd.Style.CancelButtonText = tbCancelButton.Text; + } + if (cbFlipButtonOrder.Checked ?? false) { + fd.Style.FlipOkCancelButtonLayoutOrder = true; + } + Application.Run (fd); if (fd.Canceled) { diff --git a/UnitTests/FileServices/FileDialogTests.cs b/UnitTests/FileServices/FileDialogTests.cs index 88fe00230d..fb1b28b3fa 100644 --- a/UnitTests/FileServices/FileDialogTests.cs +++ b/UnitTests/FileServices/FileDialogTests.cs @@ -398,7 +398,7 @@ public void TestDirectoryContents_Linux () │ │ │ │ │ │ -│{CM.Glyphs.LeftBracket} ►► {CM.Glyphs.RightBracket} Enter Search {CM.Glyphs.LeftBracket} Cancel {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket} │ +│{CM.Glyphs.LeftBracket} ►► {CM.Glyphs.RightBracket} Enter Search {CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Cancel {CM.Glyphs.RightBracket} │ └──────────────────────────────────────────────────────────────────┘ "; TestHelpers.AssertDriverContentsAre (expected, output, true); @@ -434,7 +434,7 @@ public void TestDirectoryContents_Windows () ││mybinary.exe│7.00 bytes│2001-01-01T11:44:42 │.exe ││ │ │ │ │ -│{CM.Glyphs.LeftBracket} ►► {CM.Glyphs.RightBracket} Enter Search {CM.Glyphs.LeftBracket} Cancel {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket} │ +│{CM.Glyphs.LeftBracket} ►► {CM.Glyphs.RightBracket} Enter Search {CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Cancel {CM.Glyphs.RightBracket} │ └──────────────────────────────────────────────────────────────────┘ "; TestHelpers.AssertDriverContentsAre (expected, output, true);