Skip to content

Commit

Permalink
Merge pull request #2863 from tznind/2859-customize-v2-file-dialog
Browse files Browse the repository at this point in the history
Fixes 2859 - Add more file dialog customization options
  • Loading branch information
tig authored Sep 29, 2023
2 parents d82d40d + 2928508 commit 5e79335
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 51 deletions.
44 changes: 28 additions & 16 deletions Terminal.Gui/FileServices/FileDialogStyle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class FileDialogStyle {
/// Gets or sets the default value to use for <see cref="UseColors"/>.
/// This can be populated from .tui config files via <see cref="ConfigurationManager"/>
/// </summary>
[SerializableConfigurationProperty(Scope = typeof (SettingsScope))]
[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
public static bool DefaultUseColors { get; set; }

/// <summary>
Expand All @@ -42,20 +42,20 @@ public class FileDialogStyle {
/// Gets or sets the class responsible for determining which symbol
/// to use to represent files and directories.
/// </summary>
public FileSystemIconProvider IconProvider { get; set;} = new FileSystemIconProvider();
public FileSystemIconProvider IconProvider { get; set; } = new FileSystemIconProvider ();

/// <summary>
/// Gets or sets the class thatis responsible for determining which color
/// to use to represent files and directories when <see cref="UseColors"/> is
/// <see langword="true"/>.
/// </summary>
public FileSystemColorProvider ColorProvider { get;set;} = new FileSystemColorProvider();
public FileSystemColorProvider ColorProvider { get; set; } = new FileSystemColorProvider ();

/// <summary>
/// Gets or sets the culture to use (e.g. for number formatting).
/// Defaults to <see cref="CultureInfo.CurrentUICulture"/>.
/// </summary>
public CultureInfo Culture {get;set;} = CultureInfo.CurrentUICulture;
public CultureInfo Culture { get; set; } = CultureInfo.CurrentUICulture;

/// <summary>
/// Gets or sets the header text displayed in the Filename column of the files table.
Expand All @@ -80,53 +80,65 @@ public class FileDialogStyle {
/// <summary>
/// Gets or sets the text displayed in the 'Search' text box when user has not supplied any input yet.
/// </summary>
public string SearchCaption { get; internal set; } = Strings.fdSearchCaption;
public string SearchCaption { get; set; } = Strings.fdSearchCaption;

/// <summary>
/// Gets or sets the text displayed in the 'Path' text box when user has not supplied any input yet.
/// </summary>
public string PathCaption { get; internal set; } = Strings.fdPathCaption;
public string PathCaption { get; set; } = Strings.fdPathCaption;

/// <summary>
/// Gets or sets the text on the 'Ok' button. Typically you may want to change this to
/// "Open" or "Save" etc.
/// </summary>
public string OkButtonText { get; set; } = "Ok";

/// <summary>
/// Gets or sets the text on the 'Cancel' button.
/// </summary>
public string CancelButtonText { get; set; } = "Cancel";

/// <summary>
/// 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.
/// </summary>
public bool FlipOkCancelButtonLayoutOrder { get; set; }

/// <summary>
/// Gets or sets error message when user attempts to select a file type that is not one of <see cref="FileDialog.AllowedTypes"/>
/// </summary>
public string WrongFileTypeFeedback { get; internal set; } = Strings.fdWrongFileTypeFeedback;
public string WrongFileTypeFeedback { get; set; } = Strings.fdWrongFileTypeFeedback;

/// <summary>
/// Gets or sets error message when user selects a directory that does not exist and
/// <see cref="OpenMode"/> is <see cref="OpenMode.Directory"/> and <see cref="FileDialog.MustExist"/> is <see langword="true"/>.
/// </summary>
public string DirectoryMustExistFeedback { get; internal set; } = Strings.fdDirectoryMustExistFeedback;
public string DirectoryMustExistFeedback { get; set; } = Strings.fdDirectoryMustExistFeedback;

/// <summary>
/// Gets or sets error message when user <see cref="OpenMode"/> is <see cref="OpenMode.Directory"/>
/// and user enters the name of an existing file (File system cannot have a folder with the same name as a file).
/// </summary>
public string FileAlreadyExistsFeedback { get; internal set; } = Strings.fdFileAlreadyExistsFeedback;
public string FileAlreadyExistsFeedback { get; set; } = Strings.fdFileAlreadyExistsFeedback;

/// <summary>
/// Gets or sets error message when user selects a file that does not exist and
/// <see cref="OpenMode"/> is <see cref="OpenMode.File"/> and <see cref="FileDialog.MustExist"/> is <see langword="true"/>.
/// </summary>
public string FileMustExistFeedback { get; internal set; } = Strings.fdFileMustExistFeedback;
public string FileMustExistFeedback { get; set; } = Strings.fdFileMustExistFeedback;

/// <summary>
/// Gets or sets error message when user <see cref="OpenMode"/> is <see cref="OpenMode.File"/>
/// and user enters the name of an existing directory (File system cannot have a folder with the same name as a file).
/// </summary>
public string DirectoryAlreadyExistsFeedback { get; internal set; } = Strings.fdDirectoryAlreadyExistsFeedback;
public string DirectoryAlreadyExistsFeedback { get; set; } = Strings.fdDirectoryAlreadyExistsFeedback;

/// <summary>
/// Gets or sets error message when user selects a file/dir that does not exist and
/// <see cref="OpenMode"/> is <see cref="OpenMode.Mixed"/> and <see cref="FileDialog.MustExist"/> is <see langword="true"/>.
/// </summary>
public string FileOrDirectoryMustExistFeedback { get; internal set; } = Strings.fdFileOrDirectoryMustExistFeedback;
public string FileOrDirectoryMustExistFeedback { get; set; } = Strings.fdFileOrDirectoryMustExistFeedback;

/// <summary>
/// Gets the style settings for the table of files (in currently selected directory).
Expand Down Expand Up @@ -172,15 +184,15 @@ public FileDialogStyle (IFileSystem fileSystem)
}


private Dictionary<IDirectoryInfo,string> DefaultTreeRootGetter ()
private Dictionary<IDirectoryInfo, string> DefaultTreeRootGetter ()
{
var roots = new Dictionary<IDirectoryInfo, string> ();
try {
foreach (var d in Environment.GetLogicalDrives ()) {

var dir = _fileSystem.DirectoryInfo.New (d);

if (!roots.ContainsKey(dir)) {
if (!roots.ContainsKey (dir)) {
roots.Add (dir, d);
}
}
Expand All @@ -194,14 +206,14 @@ private Dictionary<IDirectoryInfo,string> 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?)
Expand Down
76 changes: 44 additions & 32 deletions Terminal.Gui/Views/FileDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public partial class FileDialog : Dialog {
private int currentSortColumn;

private bool currentSortIsAsc = true;
private Dictionary<IDirectoryInfo,string> _treeRoots = new Dictionary<IDirectoryInfo, string>();
private Dictionary<IDirectoryInfo, string> _treeRoots = new Dictionary<IDirectoryInfo, string> ();

/// <summary>
/// Event fired when user attempts to confirm a selection (or multi selection).
Expand Down Expand Up @@ -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) => {
Expand All @@ -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);
Expand All @@ -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) => {
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 ();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -803,7 +817,7 @@ private void Accept (IEnumerable<FileSystemInfoStats> 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 ();
}
Expand All @@ -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<string> { f.FullName }.AsReadOnly ();
Expand Down Expand Up @@ -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 ()
Expand Down Expand Up @@ -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 ();

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down
34 changes: 33 additions & 1 deletion UICatalog/Scenarios/FileDialogExamples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
};
}

Expand All @@ -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) {
Expand Down
Loading

0 comments on commit 5e79335

Please sign in to comment.