Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Examples/UICatalog/Scenarios/FileDialogExamples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class FileDialogExamples : Scenario
private CheckBox _cbAlwaysTableShowHeaders;
private CheckBox _cbCaseSensitive;
private CheckBox _cbDrivesOnlyInTree;
private CheckBox _cbPreserveFilenameOnDirectoryChanges;
private CheckBox _cbFlipButtonOrder;
private CheckBox _cbMustExist;
private CheckBox _cbShowTreeBranchLines;
Expand Down Expand Up @@ -55,6 +56,9 @@ public override void Main ()
_cbDrivesOnlyInTree = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Only Show _Drives" };
win.Add (_cbDrivesOnlyInTree);

_cbPreserveFilenameOnDirectoryChanges = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Preserve Filename" };
win.Add (_cbPreserveFilenameOnDirectoryChanges);

y = 0;
x = 24;

Expand Down Expand Up @@ -198,6 +202,9 @@ private void CreateDialog ()
fd.Style.TreeRootGetter = () => { return Environment.GetLogicalDrives ().ToDictionary (dirInfoFactory.New, k => k); };
}

fd.Style.PreserveFilenameOnDirectoryChanges = _cbPreserveFilenameOnDirectoryChanges.CheckedState == CheckState.Checked;


if (_rgAllowedTypes.SelectedItem > 0)
{
fd.AllowedTypes.Add (new AllowedType ("Data File", ".csv", ".tsv"));
Expand Down
16 changes: 16 additions & 0 deletions Terminal.Gui/FileServices/FileDialogStyle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Terminal.Gui;
public class FileDialogStyle
{
private readonly IFileSystem _fileSystem;
private bool _preserveFilenameOnDirectoryChanges;
Comment thread
tig marked this conversation as resolved.

/// <summary>Creates a new instance of the <see cref="FileDialogStyle"/> class.</summary>
public FileDialogStyle (IFileSystem fileSystem)
Expand Down Expand Up @@ -144,6 +145,21 @@ public FileDialogStyle (IFileSystem fileSystem)
/// </summary>
public string WrongFileTypeFeedback { get; set; } = Strings.fdWrongFileTypeFeedback;


/// <summary>
/// <para>
/// Gets or sets a flag that determines behaviour when opening (double click/enter) or selecting a
/// directory in a <see cref="FileDialog"/>.
/// </para>
/// <para>If <see langword="false"/> (the default) then the <see cref="FileDialog.Path"/> is simply
/// updated to the new directory path.</para>
/// <para>If <see langword="true"/> then any typed or previously selected file
/// name is preserved (e.g. "c:/hello.csv" when opening "temp" becomes "c:/temp/hello.csv").
/// </para>
/// </summary>
public bool PreserveFilenameOnDirectoryChanges { get; set; }


[UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
private Dictionary<IDirectoryInfo, string> DefaultTreeRootGetter ()
{
Expand Down
30 changes: 26 additions & 4 deletions Terminal.Gui/Views/FileDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Text.RegularExpressions;
using Terminal.Gui.Resources;

#nullable enable

namespace Terminal.Gui;

/// <summary>
Expand Down Expand Up @@ -1135,7 +1137,7 @@ private void PushState (
}
else if (setPathText)
{
Path = newState.Directory.FullName;
SetPathToSelectedObject (newState.Directory);
}

State = newState;
Expand Down Expand Up @@ -1393,7 +1395,7 @@ private void TableView_SelectedCellChanged (object sender, SelectedCellChangedEv
{
_pushingState = true;

Path = dest.FullName;
SetPathToSelectedObject (dest);
State.Selected = stats;
_tbPath.Autocomplete.ClearSuggestions ();
}
Expand All @@ -1405,12 +1407,32 @@ private void TableView_SelectedCellChanged (object sender, SelectedCellChangedEv

private void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e)
{
if (e.NewValue is null)
SetPathToSelectedObject (e.NewValue);
}

Comment thread
tig marked this conversation as resolved.
private void SetPathToSelectedObject (IFileSystemInfo? selected)
{
if (selected is null)
{
return;
}

Path = e.NewValue.FullName;
if (selected is IDirectoryInfo && Style.PreserveFilenameOnDirectoryChanges)
{
if (!string.IsNullOrWhiteSpace (Path) && !_fileSystem.Directory.Exists (Path))
{
var currentFile = _fileSystem.Path.GetFileName (Path);

if (!string.IsNullOrWhiteSpace (currentFile))
{
Path = _fileSystem.Path.Combine (selected.FullName, currentFile);

return;
}
}
}

Path = selected.FullName;
}

private bool TryAcceptMulti ()
Expand Down
170 changes: 170 additions & 0 deletions Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,174 @@ public void SaveFileDialog_PopTree_AndNavigate (V2TestDriver d)
.WriteOutLogs (_out)
.Stop ();
}

[Theory]
[ClassData (typeof (V2TestDrivers))]
public void SaveFileDialog_PopTree_AndNavigate_PreserveFilenameOnDirectoryChanges_True (V2TestDriver d)
{
var sd = new SaveDialog (CreateExampleFileSystem ()) { Modal = false };
sd.Style.PreserveFilenameOnDirectoryChanges = true;

using var c = With.A (sd, 100, 20, d)
.ScreenShot ("Save dialog", _out)
.Then (() => Assert.True (sd.Canceled))
.Focus<TextField> (_=>true)
// Clear selection by pressing right in 'file path' text box
.RaiseKeyDownEvent (Key.CursorRight)
.AssertIsType <TextField>(sd.Focused)
// Type a filename into the dialog
.RaiseKeyDownEvent (Key.H)
.RaiseKeyDownEvent (Key.E)
.RaiseKeyDownEvent (Key.L)
.RaiseKeyDownEvent (Key.L)
.RaiseKeyDownEvent (Key.O)
.WaitIteration ()
.ScreenShot ("After typing filename 'hello'", _out)
.AssertEndsWith ("hello", sd.Path)
.LeftClick<Button> (b => b.Text == "►►")
.ScreenShot ("After pop tree", _out)
.Focus<TreeView<IFileSystemInfo>> (_ => true)
.Right ()
.ScreenShot ("After expand tree", _out)
// Because of PreserveFilenameOnDirectoryChanges we should select the new dir but keep the filename
.AssertEndsWith ("hello", sd.Path)
.Down ()
.ScreenShot ("After navigate down in tree", _out)
// Because of PreserveFilenameOnDirectoryChanges we should select the new dir but keep the filename
.AssertContains ("empty-dir",sd.Path)
.AssertEndsWith ("hello", sd.Path)
.Enter ()
.WaitIteration ()
.Then (() => Assert.False (sd.Canceled))
.AssertContains ("empty-dir", sd.FileName)
.WriteOutLogs (_out)
.Stop ();
}

[Theory]
[ClassData (typeof (V2TestDrivers))]
public void SaveFileDialog_PopTree_AndNavigate_PreserveFilenameOnDirectoryChanges_False (V2TestDriver d)
{
var sd = new SaveDialog (CreateExampleFileSystem ()) { Modal = false };
sd.Style.PreserveFilenameOnDirectoryChanges = false;

using var c = With.A (sd, 100, 20, d)
.ScreenShot ("Save dialog", _out)
.Then (() => Assert.True (sd.Canceled))
.Focus<TextField> (_ => true)
// Clear selection by pressing right in 'file path' text box
.RaiseKeyDownEvent (Key.CursorRight)
.AssertIsType<TextField> (sd.Focused)
// Type a filename into the dialog
.RaiseKeyDownEvent (Key.H)
.RaiseKeyDownEvent (Key.E)
.RaiseKeyDownEvent (Key.L)
.RaiseKeyDownEvent (Key.L)
.RaiseKeyDownEvent (Key.O)
.WaitIteration ()
.ScreenShot ("After typing filename 'hello'", _out)
.AssertEndsWith ("hello", sd.Path)
.LeftClick<Button> (b => b.Text == "►►")
.ScreenShot ("After pop tree", _out)
.Focus<TreeView<IFileSystemInfo>> (_ => true)
.Right ()
.ScreenShot ("After expand tree", _out)
.Down ()
.ScreenShot ("After navigate down in tree", _out)
// PreserveFilenameOnDirectoryChanges is false so just select new path
.AssertEndsWith ("empty-dir", sd.Path)
.AssertDoesNotContain ("hello", sd.Path)
.Enter ()
.WaitIteration ()
.Then (() => Assert.False (sd.Canceled))
.AssertContains ("empty-dir", sd.FileName)
.WriteOutLogs (_out)
.Stop ();
}

[Theory]
[ClassData (typeof (V2TestDrivers_WithTrueFalseParameter))]
public void SaveFileDialog_TableView_UpDown_PreserveFilenameOnDirectoryChanges_True (V2TestDriver d, bool preserve)
{
var sd = new SaveDialog (CreateExampleFileSystem ()) { Modal = false };
sd.Style.PreserveFilenameOnDirectoryChanges = preserve;

using var c = With.A (sd, 100, 20, d)
.ScreenShot ("Save dialog", _out)
.Then (() => Assert.True (sd.Canceled))
.Focus<TextField> (_ => true)
// Clear selection by pressing right in 'file path' text box
.RaiseKeyDownEvent (Key.CursorRight)
.AssertIsType<TextField> (sd.Focused)
// Type a filename into the dialog
.RaiseKeyDownEvent (Key.H)
.RaiseKeyDownEvent (Key.E)
.RaiseKeyDownEvent (Key.L)
.RaiseKeyDownEvent (Key.L)
.RaiseKeyDownEvent (Key.O)
.WaitIteration ()
.ScreenShot ("After typing filename 'hello'", _out)
.AssertEndsWith ("hello", sd.Path)
.Focus<TableView> (_ => true)
.ScreenShot ("After focus table", _out)
.Down ()
.ScreenShot ("After down in table", _out);

if (preserve)
{
c.AssertContains ("logs", sd.Path)
.AssertEndsWith ("hello", sd.Path);
}
else
{
c.AssertContains ("logs", sd.Path)
.AssertDoesNotContain ("hello", sd.Path);
}

c.Up ()
.ScreenShot ("After up in table", _out);

if (preserve)
{
c.AssertContains ("empty-dir", sd.Path)
.AssertEndsWith ("hello", sd.Path);
}
else
{
c.AssertContains ("empty-dir", sd.Path)
.AssertDoesNotContain ("hello", sd.Path);
}

c.Enter ()
.ScreenShot ("After enter in table", _out); ;


if (preserve)
{
c.AssertContains ("empty-dir", sd.Path)
.AssertEndsWith ("hello", sd.Path);
}
else
{
c.AssertContains ("empty-dir", sd.Path)
.AssertDoesNotContain ("hello", sd.Path);
}

c.LeftClick<Button> (b => b.Text == "_Save");
c.AssertFalse (sd.Canceled);

if (preserve)
{
c.AssertContains ("empty-dir", sd.Path)
.AssertEndsWith ("hello", sd.Path);
}
else
{
c.AssertContains ("empty-dir", sd.Path)
.AssertDoesNotContain ("hello", sd.Path);
}

c.WriteOutLogs (_out)
.Stop ();
}
}
17 changes: 17 additions & 0 deletions Tests/IntegrationTests/FluentTests/V2TestDrivers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,20 @@ public IEnumerator<object []> GetEnumerator ()

IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
}

/// <summary>
/// Test cases for functions with signature <code>V2TestDriver d, bool someFlag</code>
/// that enumerates all variations
/// </summary>
public class V2TestDrivers_WithTrueFalseParameter : IEnumerable<object []>
{
public IEnumerator<object []> GetEnumerator ()
{
yield return new object [] { V2TestDriver.V2Win,false };
yield return new object [] { V2TestDriver.V2Net,false };
yield return new object [] { V2TestDriver.V2Win,true };
yield return new object [] { V2TestDriver.V2Net,true };
}

IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
}