mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* WIP: Add ITableDataSource * WIP: Refactor TableView * WIP: Port CSVEditor * WIP: Port TableEditor * WIP: Port MultiColouredTable scenario * Fix bug of adding duplicate column styles * Update tests to use DataTableSource * Tidy up * Add EnumerableTableDataSource<T> * Add test for EnumerableTableDataSource * Add test for EnumerableTableDataSource * Add code example to xmldoc * Add ProcessTable scenario * Rename ITableDataSource to ITableSource and update docs * Rename EnumerableTableDataSource to EnumerableTableSource * Fixed Frame != Bounds; changed UICat Scenarios list to use tableview! * Fix scroll resetting in ProcessTable scenario * Fix unit tests by setting Frame to same as Bounds * Document why we have to measure our data for use with TableView * WIP: Simplify FileDialogs use of TableView * WIP start migrating sorter * WIP new filedialog table source mostly working * WIP remove sorter class * Refactor GetOrderByValue to be adjacent to GetColumnValue * Fix collection navigator back so it ignores icon * Fix unit tests * Tidy up * Fix UseColors * Add test for UseColors --------- Co-authored-by: Tig Kindel <tig@users.noreply.github.com>
This commit is contained in:
@@ -28,7 +28,7 @@ namespace Terminal.Gui {
|
||||
|
||||
public IDirectoryInfo Directory { get; }
|
||||
|
||||
public FileSystemInfoStats [] Children { get; protected set; }
|
||||
public FileSystemInfoStats [] Children { get; internal set; }
|
||||
|
||||
internal virtual void RefreshChildren ()
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets or sets the culture to use (e.g. for number formatting).
|
||||
/// Defaults to <see cref="CultureInfo.CurrentUICulture"/>.
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public CultureInfo Culture {get;set;} = CultureInfo.CurrentUICulture;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace Terminal.Gui {
|
||||
|
||||
public bool IsImage ()
|
||||
{
|
||||
return this.FileSystemInfo is FileSystemInfo f &&
|
||||
return this.FileSystemInfo is IFileSystemInfo f &&
|
||||
ImageExtensions.Contains (
|
||||
f.Extension,
|
||||
StringComparer.InvariantCultureIgnoreCase);
|
||||
@@ -82,38 +82,12 @@ namespace Terminal.Gui {
|
||||
public bool IsExecutable ()
|
||||
{
|
||||
// TODO: handle linux executable status
|
||||
return this.FileSystemInfo is FileSystemInfo f &&
|
||||
return this.FileSystemInfo is IFileSystemInfo f &&
|
||||
ExecutableExtensions.Contains (
|
||||
f.Extension,
|
||||
StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
internal object GetOrderByValue (FileDialog dlg, string columnName)
|
||||
{
|
||||
if (dlg.Style.FilenameColumnName == columnName)
|
||||
return this.FileSystemInfo.Name;
|
||||
|
||||
if (dlg.Style.SizeColumnName == columnName)
|
||||
return this.MachineReadableLength;
|
||||
|
||||
if (dlg.Style.ModifiedColumnName == columnName)
|
||||
return this.LastWriteTime;
|
||||
|
||||
if (dlg.Style.TypeColumnName == columnName)
|
||||
return this.Type;
|
||||
|
||||
throw new ArgumentOutOfRangeException ("Unknown column " + nameof (columnName));
|
||||
}
|
||||
|
||||
internal object GetOrderByDefault ()
|
||||
{
|
||||
if (this.IsDir ()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 100;
|
||||
}
|
||||
|
||||
private static string GetHumanReadableFileSize (long value, CultureInfo culture)
|
||||
{
|
||||
|
||||
|
||||
@@ -85,10 +85,8 @@ namespace Terminal.Gui {
|
||||
private IFileSystem fileSystem;
|
||||
private TextField tbPath;
|
||||
|
||||
private FileDialogSorter sorter;
|
||||
private FileDialogHistory history;
|
||||
|
||||
private DataTable dtFiles;
|
||||
private TableView tableView;
|
||||
private TreeView<object> treeView;
|
||||
private TileView splitContainer;
|
||||
@@ -107,7 +105,10 @@ namespace Terminal.Gui {
|
||||
private MenuBar allowedTypeMenuBar;
|
||||
private MenuBarItem allowedTypeMenu;
|
||||
private MenuItem [] allowedTypeMenuItems;
|
||||
private int filenameColumn;
|
||||
|
||||
private int currentSortColumn;
|
||||
|
||||
private bool currentSortIsAsc = true;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when user attempts to confirm a selection (or multi selection).
|
||||
@@ -220,8 +221,25 @@ namespace Terminal.Gui {
|
||||
};
|
||||
|
||||
this.tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
|
||||
this.tableView.MouseClick += OnTableViewMouseClick;
|
||||
Style.TableStyle = tableView.Style;
|
||||
|
||||
var nameStyle = Style.TableStyle.GetOrCreateColumnStyle (0);
|
||||
nameStyle.MinWidth = 10;
|
||||
nameStyle.ColorGetter = this.ColorGetter;
|
||||
|
||||
var sizeStyle = Style.TableStyle.GetOrCreateColumnStyle (1);
|
||||
sizeStyle.MinWidth = 10;
|
||||
sizeStyle.ColorGetter = this.ColorGetter;
|
||||
|
||||
var dateModifiedStyle = Style.TableStyle.GetOrCreateColumnStyle (2);
|
||||
dateModifiedStyle.MinWidth = 30;
|
||||
dateModifiedStyle.ColorGetter = this.ColorGetter;
|
||||
|
||||
var typeStyle = Style.TableStyle.GetOrCreateColumnStyle (3);
|
||||
typeStyle.MinWidth = 6;
|
||||
typeStyle.ColorGetter = this.ColorGetter;
|
||||
|
||||
this.tableView.KeyPress += (s, k) => {
|
||||
if (this.tableView.SelectedRow <= 0) {
|
||||
this.NavigateIf (k, Key.CursorUp, this.tbPath);
|
||||
@@ -313,13 +331,8 @@ namespace Terminal.Gui {
|
||||
this.tableView.Style.ShowHorizontalHeaderUnderline = true;
|
||||
this.tableView.Style.ShowHorizontalScrollIndicators = true;
|
||||
|
||||
this.SetupTableColumns ();
|
||||
|
||||
this.sorter = new FileDialogSorter (this, this.tableView);
|
||||
this.history = new FileDialogHistory (this);
|
||||
|
||||
this.tableView.Table = new DataTableSource(this.dtFiles);
|
||||
|
||||
this.tbPath.TextChanged += (s, e) => this.PathChanged ();
|
||||
|
||||
this.tableView.CellActivated += this.CellActivate;
|
||||
@@ -366,9 +379,29 @@ namespace Terminal.Gui {
|
||||
this.Add (this.btnForward);
|
||||
this.Add (this.tbPath);
|
||||
this.Add (this.splitContainer);
|
||||
}
|
||||
|
||||
// Default sort order is by name
|
||||
sorter.SortColumn(this.filenameColumn,true);
|
||||
private void OnTableViewMouseClick (object sender, MouseEventEventArgs e)
|
||||
{
|
||||
var clickedCell = this.tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol);
|
||||
|
||||
if (clickedCol != null) {
|
||||
if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
|
||||
|
||||
// left click in a header
|
||||
this.SortColumn (clickedCol.Value);
|
||||
} else if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
|
||||
|
||||
// right click in a header
|
||||
this.ShowHeaderContextMenu (clickedCol.Value, e);
|
||||
}
|
||||
} else {
|
||||
if (clickedCell != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
|
||||
|
||||
// right click in rest of table
|
||||
this.ShowCellContextMenu (clickedCell, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetForwardButtonText ()
|
||||
@@ -541,15 +574,7 @@ namespace Terminal.Gui {
|
||||
var col = tableView.SelectedColumn;
|
||||
var style = tableView.Style.GetColumnStyleIfAny (col);
|
||||
|
||||
|
||||
var collection = dtFiles
|
||||
.Rows
|
||||
.Cast<DataRow> ()
|
||||
.Select ((o, idx) => col == 0 ?
|
||||
RowToStats(idx).FileSystemInfo.Name :
|
||||
style.GetRepresentation (o [0])?.TrimStart('.'))
|
||||
.ToArray ();
|
||||
|
||||
var collection = State.Children.Select (s=> FileDialogTableSource.GetRawColumnValue(col,s));
|
||||
collectionNavigator = new CollectionNavigator (collection);
|
||||
}
|
||||
|
||||
@@ -963,61 +988,6 @@ namespace Terminal.Gui {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetupTableColumns ()
|
||||
{
|
||||
this.dtFiles = new DataTable ();
|
||||
|
||||
var nameStyle = this.tableView.Style.GetOrCreateColumnStyle (
|
||||
filenameColumn = this.dtFiles.Columns.Add (Style.FilenameColumnName, typeof (int)).Ordinal
|
||||
);
|
||||
nameStyle.RepresentationGetter = (i) => {
|
||||
|
||||
var stats = this.State?.Children [(int)i];
|
||||
|
||||
if (stats == null) {
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var icon = stats.IsParent ? null : Style.IconGetter?.Invoke (stats.FileSystemInfo);
|
||||
|
||||
if (icon != null) {
|
||||
return icon + stats.Name;
|
||||
}
|
||||
return stats.Name;
|
||||
};
|
||||
|
||||
nameStyle.MinWidth = 50;
|
||||
|
||||
var sizeStyle = this.tableView.Style.GetOrCreateColumnStyle (
|
||||
this.dtFiles.Columns.Add (Style.SizeColumnName, typeof (int)).Ordinal);
|
||||
sizeStyle.RepresentationGetter = (i) => this.State?.Children [(int)i].HumanReadableLength ?? string.Empty;
|
||||
nameStyle.MinWidth = 10;
|
||||
|
||||
var dateModifiedStyle = this.tableView.Style.GetOrCreateColumnStyle (
|
||||
this.dtFiles.Columns.Add (Style.ModifiedColumnName, typeof (int)).Ordinal);
|
||||
dateModifiedStyle.RepresentationGetter = (i) =>
|
||||
{
|
||||
var s = this.State?.Children [(int)i];
|
||||
if(s == null || s.IsParent || s.LastWriteTime == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
return s.LastWriteTime.Value.ToString (Style.DateFormat);
|
||||
};
|
||||
|
||||
dateModifiedStyle.MinWidth = 30;
|
||||
|
||||
var typeStyle = this.tableView.Style.GetOrCreateColumnStyle (
|
||||
this.dtFiles.Columns.Add (Style.TypeColumnName, typeof (int)).Ordinal);
|
||||
typeStyle.RepresentationGetter = (i) => this.State?.Children [(int)i].Type ?? string.Empty;
|
||||
typeStyle.MinWidth = 6;
|
||||
|
||||
foreach(var colStyle in Style.TableStyle.ColumnStyles) {
|
||||
colStyle.Value.ColorGetter = this.ColorGetter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void CellActivate (object sender, CellActivatedEventArgs obj)
|
||||
{
|
||||
if(TryAcceptMulti())
|
||||
@@ -1225,23 +1195,13 @@ namespace Terminal.Gui {
|
||||
if (this.State == null) {
|
||||
return;
|
||||
}
|
||||
this.tableView.Table = new FileDialogTableSource (this.State, this.Style, currentSortColumn, currentSortIsAsc);
|
||||
|
||||
this.dtFiles.Rows.Clear ();
|
||||
|
||||
for (int i = 0; i < this.State.Children.Length; i++) {
|
||||
this.BuildRow (i);
|
||||
}
|
||||
|
||||
this.sorter.ApplySort ();
|
||||
this.ApplySort ();
|
||||
this.tableView.Update ();
|
||||
UpdateCollectionNavigator ();
|
||||
}
|
||||
|
||||
private void BuildRow (int idx)
|
||||
{
|
||||
dtFiles.Rows.Add (idx, idx, idx, idx);
|
||||
}
|
||||
|
||||
private ColorScheme ColorGetter (TableView.CellColorGetterArgs args)
|
||||
{
|
||||
var stats = this.RowToStats (args.RowIndex);
|
||||
@@ -1279,7 +1239,7 @@ namespace Terminal.Gui {
|
||||
|
||||
foreach (var p in this.tableView.GetAllSelectedCells ()) {
|
||||
|
||||
var add = this.State?.Children [(int)this.tableView.Table[p.Y, 0]];
|
||||
var add = this.State?.Children [p.Y];
|
||||
if (add != null) {
|
||||
toReturn.Add (add);
|
||||
}
|
||||
@@ -1290,31 +1250,9 @@ namespace Terminal.Gui {
|
||||
}
|
||||
private FileSystemInfoStats RowToStats (int rowIndex)
|
||||
{
|
||||
return this.State?.Children [(int)this.tableView.Table[rowIndex,0]];
|
||||
return this.State?.Children [rowIndex];
|
||||
}
|
||||
private int? StatsToRow (IFileSystemInfo fileSystemInfo)
|
||||
{
|
||||
// find array index of the current state for the stats
|
||||
var idx = State?.Children.IndexOf ((f) => f.FileSystemInfo.FullName == fileSystemInfo.FullName);
|
||||
|
||||
if (idx != -1 && idx != null) {
|
||||
|
||||
// find the row number in our DataTable where the cell
|
||||
// contains idx
|
||||
var match = dtFiles.Rows
|
||||
.Cast<DataRow> ()
|
||||
.Select ((r, rIdx) => new { row = r, rowIdx = rIdx })
|
||||
.Where (t => (int)t.row [0] == idx)
|
||||
.ToArray ();
|
||||
|
||||
if (match.Length == 1) {
|
||||
return match [0].rowIdx;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private void PathChanged ()
|
||||
{
|
||||
// avoid re-entry
|
||||
@@ -1358,185 +1296,110 @@ namespace Terminal.Gui {
|
||||
/// <param name="toRestore"></param>
|
||||
internal void RestoreSelection (IFileSystemInfo toRestore)
|
||||
{
|
||||
var toReselect = StatsToRow (toRestore);
|
||||
tableView.SelectedRow = State.Children.IndexOf (r=>r.FileSystemInfo == toRestore);
|
||||
tableView.EnsureSelectedCellIsVisible ();
|
||||
}
|
||||
|
||||
if (toReselect.HasValue) {
|
||||
tableView.SelectedRow = toReselect.Value;
|
||||
tableView.EnsureSelectedCellIsVisible ();
|
||||
internal void ApplySort ()
|
||||
{
|
||||
var stats = State?.Children ?? new FileSystemInfoStats [0];
|
||||
|
||||
// This portion is never reordered (aways .. at top then folders)
|
||||
var forcedOrder = stats
|
||||
.OrderByDescending (f => f.IsParent)
|
||||
.ThenBy (f => f.IsDir() ? -1:100);
|
||||
|
||||
// This portion is flexible based on the column clicked (e.g. alphabetical)
|
||||
var ordered =
|
||||
this.currentSortIsAsc ?
|
||||
forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue(currentSortColumn,f)):
|
||||
forcedOrder.ThenByDescending (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f));
|
||||
|
||||
State.Children = ordered.ToArray();
|
||||
|
||||
this.tableView.Update ();
|
||||
UpdateCollectionNavigator ();
|
||||
}
|
||||
|
||||
private void SortColumn (int clickedCol)
|
||||
{
|
||||
this.GetProposedNewSortOrder (clickedCol, out var isAsc);
|
||||
this.SortColumn (clickedCol, isAsc);
|
||||
this.tableView.Table = new FileDialogTableSource(State,Style,currentSortColumn,currentSortIsAsc);
|
||||
}
|
||||
|
||||
internal void SortColumn (int col, bool isAsc)
|
||||
{
|
||||
// set a sort order
|
||||
this.currentSortColumn = col;
|
||||
this.currentSortIsAsc = isAsc;
|
||||
|
||||
this.ApplySort ();
|
||||
}
|
||||
|
||||
private string GetProposedNewSortOrder (int clickedCol, out bool isAsc)
|
||||
{
|
||||
// work out new sort order
|
||||
if (this.currentSortColumn == clickedCol && this.currentSortIsAsc) {
|
||||
isAsc = false;
|
||||
return $"{tableView.Table.ColumnNames[clickedCol]} DESC";
|
||||
} else {
|
||||
isAsc = true;
|
||||
return $"{tableView.Table.ColumnNames [clickedCol]} ASC";
|
||||
}
|
||||
}
|
||||
private class FileDialogSorter {
|
||||
private readonly FileDialog dlg;
|
||||
private TableView tableView;
|
||||
|
||||
private int? currentSort = null;
|
||||
private bool currentSortIsAsc = true;
|
||||
private void ShowHeaderContextMenu (int clickedCol, MouseEventEventArgs e)
|
||||
{
|
||||
var sort = this.GetProposedNewSortOrder (clickedCol, out var isAsc);
|
||||
|
||||
public FileDialogSorter (FileDialog dlg, TableView tableView)
|
||||
{
|
||||
this.dlg = dlg;
|
||||
this.tableView = tableView;
|
||||
var contextMenu = new ContextMenu (
|
||||
e.MouseEvent.X + 1,
|
||||
e.MouseEvent.Y + 1,
|
||||
new MenuBarItem (new MenuItem []
|
||||
{
|
||||
new MenuItem($"Hide {StripArrows(tableView.Table.ColumnNames[clickedCol])}", string.Empty, () => this.HideColumn(clickedCol)),
|
||||
new MenuItem($"Sort {StripArrows(sort)}",string.Empty, ()=> this.SortColumn(clickedCol,isAsc)),
|
||||
})
|
||||
);
|
||||
|
||||
// if user clicks the mouse in TableView
|
||||
this.tableView.MouseClick += (s, e) => {
|
||||
|
||||
var clickedCell = this.tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol);
|
||||
|
||||
if (clickedCol != null) {
|
||||
if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
|
||||
|
||||
// left click in a header
|
||||
this.SortColumn (clickedCol.Value);
|
||||
} else if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
|
||||
|
||||
// right click in a header
|
||||
this.ShowHeaderContextMenu (clickedCol.Value, e);
|
||||
}
|
||||
} else {
|
||||
if (clickedCell != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
|
||||
|
||||
// right click in rest of table
|
||||
this.ShowCellContextMenu (clickedCell, e);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
internal void ApplySort ()
|
||||
{
|
||||
var col = this.currentSort;
|
||||
|
||||
if(col == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Consider preserving selection
|
||||
dlg.dtFiles.Rows.Clear ();
|
||||
|
||||
var colName = col == null ? null : StripArrows (tableView.Table.ColumnNames[col.Value]);
|
||||
|
||||
var stats = this.dlg.State?.Children ?? new FileSystemInfoStats [0];
|
||||
|
||||
// Do we sort on a column or just use the default sort order?
|
||||
Func<FileSystemInfoStats, object> sortAlgorithm;
|
||||
|
||||
if (colName == null) {
|
||||
sortAlgorithm = (v) => v.GetOrderByDefault ();
|
||||
this.currentSortIsAsc = true;
|
||||
} else {
|
||||
sortAlgorithm = (v) => v.GetOrderByValue (dlg, colName);
|
||||
}
|
||||
|
||||
// This portion is never reordered (aways .. at top then folders)
|
||||
var forcedOrder = stats.Select ((v, i) => new { v, i })
|
||||
.OrderByDescending (f => f.v.IsParent)
|
||||
.ThenBy (f => f.v.IsDir() ? -1:100);
|
||||
|
||||
// This portion is flexible based on the column clicked (e.g. alphabetical)
|
||||
var ordered =
|
||||
this.currentSortIsAsc ?
|
||||
forcedOrder.ThenBy (f => sortAlgorithm (f.v)):
|
||||
forcedOrder.ThenByDescending (f => sortAlgorithm (f.v));
|
||||
|
||||
foreach (var o in ordered) {
|
||||
this.dlg.BuildRow (o.i);
|
||||
}
|
||||
|
||||
foreach (DataColumn c in dlg.dtFiles.Columns) {
|
||||
|
||||
// remove any lingering sort indicator
|
||||
c.ColumnName = StripArrows (c.ColumnName);
|
||||
|
||||
// add a new one if this the one that is being sorted
|
||||
if (c.Ordinal == col) {
|
||||
c.ColumnName += this.currentSortIsAsc ? " (▲)" : " (▼)";
|
||||
}
|
||||
}
|
||||
|
||||
this.tableView.Update ();
|
||||
dlg.UpdateCollectionNavigator ();
|
||||
}
|
||||
|
||||
private static string StripArrows (string columnName)
|
||||
{
|
||||
return columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty);
|
||||
}
|
||||
|
||||
private void SortColumn (int clickedCol)
|
||||
{
|
||||
this.GetProposedNewSortOrder (clickedCol, out var isAsc);
|
||||
this.SortColumn (clickedCol, isAsc);
|
||||
}
|
||||
|
||||
internal void SortColumn (int col, bool isAsc)
|
||||
{
|
||||
// set a sort order
|
||||
this.currentSort = col;
|
||||
this.currentSortIsAsc = isAsc;
|
||||
|
||||
this.ApplySort ();
|
||||
}
|
||||
|
||||
private string GetProposedNewSortOrder (int clickedCol, out bool isAsc)
|
||||
{
|
||||
// work out new sort order
|
||||
if (this.currentSort == clickedCol && this.currentSortIsAsc) {
|
||||
isAsc = false;
|
||||
return $"{tableView.Table.ColumnNames[clickedCol]} DESC";
|
||||
} else {
|
||||
isAsc = true;
|
||||
return $"{tableView.Table.ColumnNames [clickedCol]} ASC";
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowHeaderContextMenu (int clickedCol, MouseEventEventArgs e)
|
||||
{
|
||||
var sort = this.GetProposedNewSortOrder (clickedCol, out var isAsc);
|
||||
|
||||
var contextMenu = new ContextMenu (
|
||||
e.MouseEvent.X + 1,
|
||||
e.MouseEvent.Y + 1,
|
||||
new MenuBarItem (new MenuItem []
|
||||
{
|
||||
new MenuItem($"Hide {StripArrows(tableView.Table.ColumnNames[clickedCol])}", string.Empty, () => this.HideColumn(clickedCol)),
|
||||
new MenuItem($"Sort {StripArrows(sort)}",string.Empty, ()=> this.SortColumn(clickedCol,isAsc)),
|
||||
})
|
||||
);
|
||||
|
||||
contextMenu.Show ();
|
||||
}
|
||||
|
||||
private void ShowCellContextMenu (Point? clickedCell, MouseEventEventArgs e)
|
||||
{
|
||||
if (clickedCell == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var contextMenu = new ContextMenu (
|
||||
e.MouseEvent.X + 1,
|
||||
e.MouseEvent.Y + 1,
|
||||
new MenuBarItem (new MenuItem []
|
||||
{
|
||||
new MenuItem($"New", string.Empty, () => dlg.New()),
|
||||
new MenuItem($"Rename",string.Empty, ()=> dlg.Rename()),
|
||||
new MenuItem($"Delete",string.Empty, ()=> dlg.Delete()),
|
||||
})
|
||||
);
|
||||
|
||||
dlg.tableView.SetSelection (clickedCell.Value.X, clickedCell.Value.Y, false);
|
||||
|
||||
contextMenu.Show ();
|
||||
}
|
||||
|
||||
private void HideColumn (int clickedCol)
|
||||
{
|
||||
var style = this.tableView.Style.GetOrCreateColumnStyle (clickedCol);
|
||||
style.Visible = false;
|
||||
this.tableView.Update ();
|
||||
}
|
||||
contextMenu.Show ();
|
||||
}
|
||||
|
||||
private static string StripArrows (string columnName)
|
||||
{
|
||||
return columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty);
|
||||
}
|
||||
|
||||
private void ShowCellContextMenu (Point? clickedCell, MouseEventEventArgs e)
|
||||
{
|
||||
if (clickedCell == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var contextMenu = new ContextMenu (
|
||||
e.MouseEvent.X + 1,
|
||||
e.MouseEvent.Y + 1,
|
||||
new MenuBarItem (new MenuItem []
|
||||
{
|
||||
new MenuItem($"New", string.Empty, () => New()),
|
||||
new MenuItem($"Rename",string.Empty, ()=> Rename()),
|
||||
new MenuItem($"Delete",string.Empty, ()=> Delete()),
|
||||
})
|
||||
);
|
||||
|
||||
tableView.SetSelection (clickedCell.Value.X, clickedCell.Value.Y, false);
|
||||
|
||||
contextMenu.Show ();
|
||||
}
|
||||
|
||||
private void HideColumn (int clickedCol)
|
||||
{
|
||||
var style = this.tableView.Style.GetOrCreateColumnStyle (clickedCol);
|
||||
style.Visible = false;
|
||||
this.tableView.Update ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State representing a recursive search from <see cref="FileDialogState.Directory"/>
|
||||
/// downwards.
|
||||
|
||||
72
Terminal.Gui/Views/FileDialogTableSource.cs
Normal file
72
Terminal.Gui/Views/FileDialogTableSource.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
internal class FileDialogTableSource : ITableSource {
|
||||
readonly FileDialogStyle style;
|
||||
readonly int currentSortColumn;
|
||||
readonly bool currentSortIsAsc;
|
||||
readonly FileDialogState state;
|
||||
|
||||
public FileDialogTableSource (FileDialogState state, FileDialogStyle style, int currentSortColumn, bool currentSortIsAsc)
|
||||
{
|
||||
this.style = style;
|
||||
this.currentSortColumn = currentSortColumn;
|
||||
this.currentSortIsAsc = currentSortIsAsc;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public object this [int row, int col] => GetColumnValue (col, state.Children [row]);
|
||||
|
||||
private object GetColumnValue (int col, FileSystemInfoStats stats)
|
||||
{
|
||||
switch (col) {
|
||||
case 0:
|
||||
var icon = stats.IsParent ? null : style.IconGetter?.Invoke (stats.FileSystemInfo);
|
||||
return icon + (stats?.Name ?? string.Empty);
|
||||
case 1:
|
||||
return stats?.HumanReadableLength ?? string.Empty;
|
||||
case 2:
|
||||
if (stats == null || stats.IsParent || stats.LastWriteTime == null) {
|
||||
return string.Empty;
|
||||
}
|
||||
return stats.LastWriteTime.Value.ToString (style.DateFormat);
|
||||
case 3:
|
||||
return stats?.Type ?? string.Empty;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException (nameof (col));
|
||||
}
|
||||
}
|
||||
|
||||
internal static object GetRawColumnValue (int col, FileSystemInfoStats stats)
|
||||
{
|
||||
switch (col) {
|
||||
case 0: return stats.FileSystemInfo.Name;
|
||||
case 1: return stats.MachineReadableLength;
|
||||
case 2: return stats.LastWriteTime;
|
||||
case 3: return stats.Type;
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException (nameof (col));
|
||||
}
|
||||
public int Rows => state.Children.Count ();
|
||||
|
||||
public int Columns => 4;
|
||||
|
||||
public string [] ColumnNames => new string []{
|
||||
MaybeAddSortArrows(style.FilenameColumnName,0),
|
||||
MaybeAddSortArrows(style.SizeColumnName,1),
|
||||
MaybeAddSortArrows(style.ModifiedColumnName,2),
|
||||
MaybeAddSortArrows(style.TypeColumnName,3)
|
||||
};
|
||||
|
||||
private string MaybeAddSortArrows (string name, int idx)
|
||||
{
|
||||
if (idx == currentSortColumn) {
|
||||
return name + (currentSortIsAsc ? " (▲)" : " (▼)");
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,13 +130,13 @@ namespace Terminal.Gui.FileServicesTests {
|
||||
}
|
||||
|
||||
[Theory, AutoInitShutdown]
|
||||
[InlineData(true,true)]
|
||||
[InlineData(true,false)]
|
||||
[InlineData(false,true)]
|
||||
[InlineData(false,false)]
|
||||
[InlineData (true, true)]
|
||||
[InlineData (true, false)]
|
||||
[InlineData (false, true)]
|
||||
[InlineData (false, false)]
|
||||
public void PickDirectory_DirectTyping (bool openModeMixed, bool multiple)
|
||||
{
|
||||
var dlg = GetDialog();
|
||||
var dlg = GetDialog ();
|
||||
dlg.OpenMode = openModeMixed ? OpenMode.Mixed : OpenMode.Directory;
|
||||
dlg.AllowsMultipleSelection = multiple;
|
||||
|
||||
@@ -144,61 +144,60 @@ namespace Terminal.Gui.FileServicesTests {
|
||||
// so to add to current path user must press End or right
|
||||
Send ('>', ConsoleKey.RightArrow, false);
|
||||
|
||||
Send("subfolder");
|
||||
Send ("subfolder");
|
||||
|
||||
// Dialog has not yet been confirmed with a choice
|
||||
Assert.True(dlg.Canceled);
|
||||
Assert.True (dlg.Canceled);
|
||||
|
||||
// Now it has
|
||||
Send ('\n', ConsoleKey.Enter, false);
|
||||
Assert.False(dlg.Canceled);
|
||||
AssertIsTheSubfolder(dlg.Path);
|
||||
Assert.False (dlg.Canceled);
|
||||
AssertIsTheSubfolder (dlg.Path);
|
||||
}
|
||||
|
||||
[Theory, AutoInitShutdown]
|
||||
[InlineData(true,true)]
|
||||
[InlineData(true,false)]
|
||||
[InlineData(false,true)]
|
||||
[InlineData(false,false)]
|
||||
[InlineData (true, true)]
|
||||
[InlineData (true, false)]
|
||||
[InlineData (false, true)]
|
||||
[InlineData (false, false)]
|
||||
public void PickDirectory_ArrowNavigation (bool openModeMixed, bool multiple)
|
||||
{
|
||||
var dlg = GetDialog();
|
||||
var dlg = GetDialog ();
|
||||
dlg.OpenMode = openModeMixed ? OpenMode.Mixed : OpenMode.Directory;
|
||||
dlg.AllowsMultipleSelection = multiple;
|
||||
|
||||
Assert.IsType<TextField>(dlg.MostFocused);
|
||||
Assert.IsType<TextField> (dlg.MostFocused);
|
||||
Send ('v', ConsoleKey.DownArrow, false);
|
||||
Assert.IsType<TableView>(dlg.MostFocused);
|
||||
Assert.IsType<TableView> (dlg.MostFocused);
|
||||
|
||||
// Should be selecting ..
|
||||
Send ('v', ConsoleKey.DownArrow, false);
|
||||
|
||||
// Down to the directory
|
||||
Assert.True(dlg.Canceled);
|
||||
Assert.True (dlg.Canceled);
|
||||
// Alt+O to open (enter would just navigate into the child dir)
|
||||
Send ('o', ConsoleKey.O, false,true);
|
||||
Assert.False(dlg.Canceled);
|
||||
Send ('o', ConsoleKey.O, false, true);
|
||||
Assert.False (dlg.Canceled);
|
||||
|
||||
AssertIsTheSubfolder(dlg.Path);
|
||||
AssertIsTheSubfolder (dlg.Path);
|
||||
}
|
||||
|
||||
[Theory, AutoInitShutdown]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
[InlineData (true)]
|
||||
[InlineData (false)]
|
||||
public void MultiSelectDirectory_CannotToggleDotDot (bool acceptWithEnter)
|
||||
{
|
||||
var dlg = GetDialog();
|
||||
var dlg = GetDialog ();
|
||||
dlg.OpenMode = OpenMode.Directory;
|
||||
dlg.AllowsMultipleSelection = true;
|
||||
IReadOnlyCollection<string> eventMultiSelected = null;
|
||||
dlg.FilesSelected += (s,e)=>
|
||||
{
|
||||
eventMultiSelected = e.Dialog.MultiSelected;
|
||||
dlg.FilesSelected += (s, e) => {
|
||||
eventMultiSelected = e.Dialog.MultiSelected;
|
||||
};
|
||||
|
||||
Assert.IsType<TextField>(dlg.MostFocused);
|
||||
Assert.IsType<TextField> (dlg.MostFocused);
|
||||
Send ('v', ConsoleKey.DownArrow, false);
|
||||
Assert.IsType<TableView>(dlg.MostFocused);
|
||||
Assert.IsType<TableView> (dlg.MostFocused);
|
||||
|
||||
// Try to toggle '..'
|
||||
Send (' ', ConsoleKey.Spacebar, false);
|
||||
@@ -206,202 +205,184 @@ namespace Terminal.Gui.FileServicesTests {
|
||||
// Toggle subfolder
|
||||
Send (' ', ConsoleKey.Spacebar, false);
|
||||
|
||||
Assert.True(dlg.Canceled);
|
||||
Assert.True (dlg.Canceled);
|
||||
|
||||
if(acceptWithEnter)
|
||||
{
|
||||
if (acceptWithEnter) {
|
||||
Send ('\n', ConsoleKey.Enter);
|
||||
} else {
|
||||
Send ('o', ConsoleKey.O, false, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Send ('o', ConsoleKey.O,false,true);
|
||||
}
|
||||
Assert.False(dlg.Canceled);
|
||||
Assert.False (dlg.Canceled);
|
||||
|
||||
Assert.Multiple(
|
||||
()=>{
|
||||
Assert.Multiple (
|
||||
() => {
|
||||
// Only the subfolder should be selected
|
||||
Assert.Equal(1,dlg.MultiSelected.Count);
|
||||
AssertIsTheSubfolder(dlg.Path);
|
||||
AssertIsTheSubfolder(dlg.MultiSelected.Single());
|
||||
Assert.Equal (1, dlg.MultiSelected.Count);
|
||||
AssertIsTheSubfolder (dlg.Path);
|
||||
AssertIsTheSubfolder (dlg.MultiSelected.Single ());
|
||||
},
|
||||
()=>{
|
||||
() => {
|
||||
// Event should also agree with the final state
|
||||
Assert.NotNull(eventMultiSelected);
|
||||
Assert.Equal(1,eventMultiSelected.Count);
|
||||
AssertIsTheSubfolder(eventMultiSelected.Single());
|
||||
Assert.NotNull (eventMultiSelected);
|
||||
Assert.Equal (1, eventMultiSelected.Count);
|
||||
AssertIsTheSubfolder (eventMultiSelected.Single ());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void DotDot_MovesToRoot_ThenPressBack ()
|
||||
{
|
||||
var dlg = GetDialog();
|
||||
var dlg = GetDialog ();
|
||||
dlg.OpenMode = OpenMode.Directory;
|
||||
dlg.AllowsMultipleSelection = true;
|
||||
bool selected = false;
|
||||
dlg.FilesSelected += (s,e)=>
|
||||
{
|
||||
dlg.FilesSelected += (s, e) => {
|
||||
selected = true;
|
||||
};
|
||||
|
||||
AssertIsTheStartingDirectory(dlg.Path);
|
||||
AssertIsTheStartingDirectory (dlg.Path);
|
||||
|
||||
Assert.IsType<TextField>(dlg.MostFocused);
|
||||
Assert.IsType<TextField> (dlg.MostFocused);
|
||||
Send ('v', ConsoleKey.DownArrow, false);
|
||||
Assert.IsType<TableView>(dlg.MostFocused);
|
||||
|
||||
Assert.IsType<TableView> (dlg.MostFocused);
|
||||
|
||||
// ".." should be the first thing selected
|
||||
// ".." should not mess with the displayed path
|
||||
AssertIsTheStartingDirectory(dlg.Path);
|
||||
AssertIsTheStartingDirectory (dlg.Path);
|
||||
|
||||
// Accept navigation up a directory
|
||||
Send ('\n', ConsoleKey.Enter);
|
||||
|
||||
AssertIsTheRootDirectory(dlg.Path);
|
||||
|
||||
Assert.True(dlg.Canceled);
|
||||
Assert.False(selected);
|
||||
AssertIsTheRootDirectory (dlg.Path);
|
||||
|
||||
Assert.True (dlg.Canceled);
|
||||
Assert.False (selected);
|
||||
|
||||
// Now press the back button (in table view)
|
||||
Send ('<', ConsoleKey.Backspace);
|
||||
|
||||
// Should move us back to the root
|
||||
AssertIsTheStartingDirectory(dlg.Path);
|
||||
AssertIsTheStartingDirectory (dlg.Path);
|
||||
|
||||
Assert.True(dlg.Canceled);
|
||||
Assert.False(selected);
|
||||
Assert.True (dlg.Canceled);
|
||||
Assert.False (selected);
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void MultiSelectDirectory_EnterOpensFolder ()
|
||||
{
|
||||
var dlg = GetDialog();
|
||||
var dlg = GetDialog ();
|
||||
dlg.OpenMode = OpenMode.Directory;
|
||||
dlg.AllowsMultipleSelection = true;
|
||||
IReadOnlyCollection<string> eventMultiSelected = null;
|
||||
dlg.FilesSelected += (s,e)=>
|
||||
{
|
||||
eventMultiSelected = e.Dialog.MultiSelected;
|
||||
dlg.FilesSelected += (s, e) => {
|
||||
eventMultiSelected = e.Dialog.MultiSelected;
|
||||
};
|
||||
|
||||
Assert.IsType<TextField>(dlg.MostFocused);
|
||||
Assert.IsType<TextField> (dlg.MostFocused);
|
||||
Send ('v', ConsoleKey.DownArrow, false);
|
||||
Assert.IsType<TableView>(dlg.MostFocused);
|
||||
Assert.IsType<TableView> (dlg.MostFocused);
|
||||
// Move selection to subfolder
|
||||
Send ('v', ConsoleKey.DownArrow, false);
|
||||
|
||||
Send ('\n', ConsoleKey.Enter);
|
||||
|
||||
// Path should update to the newly opened folder
|
||||
AssertIsTheSubfolder(dlg.Path);
|
||||
AssertIsTheSubfolder (dlg.Path);
|
||||
|
||||
// No selection will have been confirmed
|
||||
Assert.True(dlg.Canceled);
|
||||
Assert.Empty(dlg.MultiSelected);
|
||||
Assert.Null(eventMultiSelected);
|
||||
Assert.True (dlg.Canceled);
|
||||
Assert.Empty (dlg.MultiSelected);
|
||||
Assert.Null (eventMultiSelected);
|
||||
}
|
||||
|
||||
[Theory, AutoInitShutdown]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
[InlineData (true)]
|
||||
[InlineData (false)]
|
||||
public void MultiSelectDirectory_CanToggleThenAccept (bool acceptWithEnter)
|
||||
{
|
||||
var dlg = GetDialog();
|
||||
var dlg = GetDialog ();
|
||||
dlg.OpenMode = OpenMode.Directory;
|
||||
dlg.AllowsMultipleSelection = true;
|
||||
IReadOnlyCollection<string> eventMultiSelected = null;
|
||||
dlg.FilesSelected += (s,e)=>
|
||||
{
|
||||
eventMultiSelected = e.Dialog.MultiSelected;
|
||||
dlg.FilesSelected += (s, e) => {
|
||||
eventMultiSelected = e.Dialog.MultiSelected;
|
||||
};
|
||||
|
||||
Assert.IsType<TextField>(dlg.MostFocused);
|
||||
Assert.IsType<TextField> (dlg.MostFocused);
|
||||
Send ('v', ConsoleKey.DownArrow, false);
|
||||
Assert.IsType<TableView>(dlg.MostFocused);
|
||||
Assert.IsType<TableView> (dlg.MostFocused);
|
||||
// Move selection to subfolder
|
||||
Send ('v', ConsoleKey.DownArrow, false);
|
||||
// Toggle subfolder
|
||||
Send (' ', ConsoleKey.Spacebar, false);
|
||||
|
||||
Assert.True(dlg.Canceled);
|
||||
Assert.True (dlg.Canceled);
|
||||
|
||||
if(acceptWithEnter)
|
||||
{
|
||||
if (acceptWithEnter) {
|
||||
Send ('\n', ConsoleKey.Enter);
|
||||
} else {
|
||||
Send ('o', ConsoleKey.O, false, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Send ('o', ConsoleKey.O,false,true);
|
||||
}
|
||||
Assert.False(dlg.Canceled);
|
||||
Assert.False (dlg.Canceled);
|
||||
|
||||
Assert.Multiple(
|
||||
()=>{
|
||||
Assert.Multiple (
|
||||
() => {
|
||||
// Only the subfolder should be selected
|
||||
Assert.Equal(1,dlg.MultiSelected.Count);
|
||||
AssertIsTheSubfolder(dlg.Path);
|
||||
AssertIsTheSubfolder(dlg.MultiSelected.Single());
|
||||
Assert.Equal (1, dlg.MultiSelected.Count);
|
||||
AssertIsTheSubfolder (dlg.Path);
|
||||
AssertIsTheSubfolder (dlg.MultiSelected.Single ());
|
||||
},
|
||||
()=>{
|
||||
() => {
|
||||
// Event should also agree with the final state
|
||||
Assert.NotNull(eventMultiSelected);
|
||||
Assert.Equal(1,eventMultiSelected.Count);
|
||||
AssertIsTheSubfolder(eventMultiSelected.Single());
|
||||
Assert.NotNull (eventMultiSelected);
|
||||
Assert.Equal (1, eventMultiSelected.Count);
|
||||
AssertIsTheSubfolder (eventMultiSelected.Single ());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void AssertIsTheStartingDirectory (string path)
|
||||
{
|
||||
if(IsWindows())
|
||||
{
|
||||
Assert.Equal (@"c:\demo\",path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal ("/demo/",path);
|
||||
if (IsWindows ()) {
|
||||
Assert.Equal (@"c:\demo\", path);
|
||||
} else {
|
||||
Assert.Equal ("/demo/", path);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertIsTheRootDirectory (string path)
|
||||
{
|
||||
if(IsWindows())
|
||||
{
|
||||
Assert.Equal (@"c:\",path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal ("/",path);
|
||||
if (IsWindows ()) {
|
||||
Assert.Equal (@"c:\", path);
|
||||
} else {
|
||||
Assert.Equal ("/", path);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertIsTheSubfolder (string path)
|
||||
{
|
||||
if(IsWindows())
|
||||
{
|
||||
Assert.Equal (@"c:\demo\subfolder",path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal ("/demo/subfolder",path);
|
||||
if (IsWindows ()) {
|
||||
Assert.Equal (@"c:\demo\subfolder", path);
|
||||
} else {
|
||||
Assert.Equal ("/demo/subfolder", path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void TestDirectoryContents_Linux ()
|
||||
{
|
||||
if (IsWindows()) {
|
||||
if (IsWindows ()) {
|
||||
return;
|
||||
}
|
||||
var fd = GetLinuxDialog();
|
||||
var fd = GetLinuxDialog ();
|
||||
fd.Title = string.Empty;
|
||||
|
||||
fd.Style.Culture = new CultureInfo("en-US");
|
||||
fd.Style.Culture = new CultureInfo ("en-US");
|
||||
|
||||
fd.Redraw (fd.Bounds);
|
||||
|
||||
|
||||
string expected =
|
||||
@"
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
@@ -426,14 +407,14 @@ namespace Terminal.Gui.FileServicesTests {
|
||||
[Fact, AutoInitShutdown]
|
||||
public void TestDirectoryContents_Windows ()
|
||||
{
|
||||
if (!IsWindows()) {
|
||||
if (!IsWindows ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fd = GetWindowsDialog();
|
||||
var fd = GetWindowsDialog ();
|
||||
fd.Title = string.Empty;
|
||||
|
||||
fd.Style.Culture = new CultureInfo("en-US");
|
||||
fd.Style.Culture = new CultureInfo ("en-US");
|
||||
|
||||
fd.Redraw (fd.Bounds);
|
||||
|
||||
@@ -450,7 +431,7 @@ namespace Terminal.Gui.FileServicesTests {
|
||||
││\subfolder │ │2002-01-01T22:42:10 │dir ││
|
||||
││image.gif │4.00 bytes│2002-01-01T22:42:10 │.gif ││
|
||||
││jQuery.js │7.00 bytes│2001-01-01T11:44:42 │.js ││
|
||||
│ │
|
||||
││mybinary.exe│7.00 bytes│2001-01-01T11:44:42 │.exe ││
|
||||
│ │
|
||||
│ │
|
||||
│[ ►► ] Enter Search [ Cancel ] [ Ok ] │
|
||||
@@ -459,6 +440,47 @@ namespace Terminal.Gui.FileServicesTests {
|
||||
TestHelpers.AssertDriverContentsAre (expected, output, true);
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void TestDirectoryContents_Windows_Colors ()
|
||||
{
|
||||
if (!IsWindows ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fd = GetWindowsDialog ();
|
||||
fd.Title = string.Empty;
|
||||
|
||||
fd.Style.Culture = new CultureInfo ("en-US");
|
||||
fd.Style.UseColors = true;
|
||||
|
||||
var dir = new Attribute (Color.Magenta);
|
||||
fd.Style.ColorSchemeDirectory = GetColorScheme (dir);
|
||||
|
||||
var img = new Attribute (Color.Cyan);
|
||||
fd.Style.ColorSchemeImage = GetColorScheme (img);
|
||||
|
||||
var other = new Attribute (Color.BrightGreen);
|
||||
fd.Style.ColorSchemeOther = GetColorScheme (other);
|
||||
|
||||
var exe = new Attribute (Color.BrightYellow);
|
||||
fd.Style.ColorSchemeExeOrRecommended = GetColorScheme (exe);
|
||||
|
||||
fd.Redraw (fd.Bounds);
|
||||
|
||||
TestHelpers.AssertDriverUsedColors (other,dir,img,exe);
|
||||
}
|
||||
|
||||
private ColorScheme GetColorScheme (Attribute a)
|
||||
{
|
||||
return new ColorScheme {
|
||||
Normal = a,
|
||||
Focus = a,
|
||||
Disabled = a,
|
||||
HotFocus = a,
|
||||
HotNormal = a,
|
||||
};
|
||||
}
|
||||
|
||||
[Theory, AutoInitShutdown]
|
||||
[InlineData (true)]
|
||||
[InlineData (false)]
|
||||
@@ -518,17 +540,17 @@ namespace Terminal.Gui.FileServicesTests {
|
||||
Assert.Equal (@"/bob/fish", tb.Text);
|
||||
}*/
|
||||
|
||||
private bool IsWindows()
|
||||
private bool IsWindows ()
|
||||
{
|
||||
return System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform (System.Runtime.InteropServices.OSPlatform.Windows);
|
||||
}
|
||||
|
||||
private FileDialog GetDialog()
|
||||
private FileDialog GetDialog ()
|
||||
{
|
||||
return IsWindows() ? GetWindowsDialog() : GetLinuxDialog();
|
||||
return IsWindows () ? GetWindowsDialog () : GetLinuxDialog ();
|
||||
}
|
||||
|
||||
private FileDialog GetWindowsDialog()
|
||||
private FileDialog GetWindowsDialog ()
|
||||
{
|
||||
// Arrange
|
||||
var fileSystem = new MockFileSystem (new Dictionary<string, MockFileData> (), @"c:\");
|
||||
@@ -536,6 +558,7 @@ namespace Terminal.Gui.FileServicesTests {
|
||||
|
||||
fileSystem.AddFile (@"c:\myfile.txt", new MockFileData ("Testing is meh.") { LastWriteTime = new DateTime (2001, 01, 01, 11, 12, 11) });
|
||||
fileSystem.AddFile (@"c:\demo\jQuery.js", new MockFileData ("some js") { LastWriteTime = new DateTime (2001, 01, 01, 11, 44, 42) });
|
||||
fileSystem.AddFile (@"c:\demo\mybinary.exe", new MockFileData ("some js") { LastWriteTime = new DateTime (2001, 01, 01, 11, 44, 42) });
|
||||
fileSystem.AddFile (@"c:\demo\image.gif", new MockFileData (new byte [] { 0x12, 0x34, 0x56, 0xd2 }) { LastWriteTime = new DateTime (2002, 01, 01, 22, 42, 10) });
|
||||
|
||||
var m = (MockDirectoryInfo)fileSystem.DirectoryInfo.New (@"c:\demo\subfolder");
|
||||
@@ -552,7 +575,7 @@ namespace Terminal.Gui.FileServicesTests {
|
||||
return fd;
|
||||
}
|
||||
|
||||
private FileDialog GetLinuxDialog()
|
||||
private FileDialog GetLinuxDialog ()
|
||||
{
|
||||
// Arrange
|
||||
var fileSystem = new MockFileSystem (new Dictionary<string, MockFileData> (), "/");
|
||||
|
||||
@@ -305,7 +305,44 @@ class TestHelpers {
|
||||
r++;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Verifies the console used all the <paramref name="expectedColors"/> when rendering.
|
||||
/// If one or more of the expected colors are not used then the failure will output both
|
||||
/// the colors that were found to be used and which of your expectations was not met.
|
||||
/// </summary>
|
||||
/// <param name="expectedColors"></param>
|
||||
internal static void AssertDriverUsedColors (params Attribute [] expectedColors)
|
||||
{
|
||||
var driver = ((FakeDriver)Application.Driver);
|
||||
|
||||
var contents = driver.Contents;
|
||||
|
||||
var toFind = expectedColors.ToList ();
|
||||
|
||||
var colorsUsed = new HashSet<int> ();
|
||||
|
||||
for (int r = 0; r < driver.Rows; r++) {
|
||||
for (int c = 0; c < driver.Cols; c++) {
|
||||
int val = contents [r, c, 1];
|
||||
|
||||
colorsUsed.Add (val);
|
||||
|
||||
var match = toFind.FirstOrDefault (e => e.Value == val);
|
||||
|
||||
// need to check twice because Attribute is a struct and therefore cannot be null
|
||||
if (toFind.Any (e => e.Value == val)) {
|
||||
toFind.Remove (match);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(toFind.Any()) {
|
||||
var sb = new StringBuilder ();
|
||||
sb.AppendLine ("The following colors were not used:" + string.Join ("; ", toFind.Select (a => DescribeColor (a))));
|
||||
sb.AppendLine ("Colors used were:" + string.Join ("; ", colorsUsed.Select (DescribeColor)));
|
||||
throw new Exception (sb.ToString());
|
||||
}
|
||||
}
|
||||
private static object DescribeColor (int userExpected)
|
||||
{
|
||||
var a = new Attribute (userExpected);
|
||||
|
||||
Reference in New Issue
Block a user