Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #2575 - TableView to use interface instead of System.Data.DataTable #2576

Merged
merged 20 commits into from
Apr 28, 2023
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
70 changes: 38 additions & 32 deletions Terminal.Gui/Views/FileDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public partial class FileDialog : Dialog {
private MenuBar allowedTypeMenuBar;
private MenuBarItem allowedTypeMenu;
private MenuItem [] allowedTypeMenuItems;
private DataColumn filenameColumn;
private int filenameColumn;

/// <summary>
/// Event fired when user attempts to confirm a selection (or multi selection).
Expand Down Expand Up @@ -226,7 +226,7 @@ public FileDialog (IFileSystem fileSystem)
if (this.tableView.SelectedRow <= 0) {
this.NavigateIf (k, Key.CursorUp, this.tbPath);
}
if (this.tableView.SelectedRow == this.tableView.Table.Rows.Count-1) {
if (this.tableView.SelectedRow == this.tableView.Table.Rows - 1) {
this.NavigateIf (k, Key.CursorDown, this.btnToggleSplitterCollapse);
}

Expand Down Expand Up @@ -318,7 +318,7 @@ public FileDialog (IFileSystem fileSystem)
this.sorter = new FileDialogSorter (this, this.tableView);
this.history = new FileDialogHistory (this);

this.tableView.Table = this.dtFiles;
this.tableView.Table = new DataTableSource(this.dtFiles);

this.tbPath.TextChanged += (s, e) => this.PathChanged ();

Expand Down Expand Up @@ -514,7 +514,7 @@ private void ClearFeedback ()

private void CycleToNextTableEntryBeginningWith (KeyEventEventArgs keyEvent)
{
if (tableView.Table.Rows.Count == 0) {
if (tableView.Table.Rows == 0) {
return;
}

Expand All @@ -539,11 +539,10 @@ private void UpdateCollectionNavigator ()
{
tableView.EnsureValidSelection ();
var col = tableView.SelectedColumn;
var style = tableView.Style.GetColumnStyleIfAny (tableView.Table.Columns [col]);
var style = tableView.Style.GetColumnStyleIfAny (col);


var collection = tableView
.Table
var collection = dtFiles
.Rows
.Cast<DataRow> ()
.Select ((o, idx) => col == 0 ?
Expand Down Expand Up @@ -897,7 +896,7 @@ private void UpdateNavigationVisibility ()

private void TableView_SelectedCellChanged (object sender, SelectedCellChangedEventArgs obj)
{
if (!this.tableView.HasFocus || obj.NewRow == -1 || obj.Table.Rows.Count == 0) {
if (!this.tableView.HasFocus || obj.NewRow == -1 || obj.Table.Rows == 0) {
return;
}

Expand Down Expand Up @@ -969,7 +968,7 @@ private void SetupTableColumns ()
this.dtFiles = new DataTable ();

var nameStyle = this.tableView.Style.GetOrCreateColumnStyle (
filenameColumn = this.dtFiles.Columns.Add (Style.FilenameColumnName, typeof (int))
filenameColumn = this.dtFiles.Columns.Add (Style.FilenameColumnName, typeof (int)).Ordinal
);
nameStyle.RepresentationGetter = (i) => {

Expand All @@ -989,11 +988,13 @@ private void SetupTableColumns ()

nameStyle.MinWidth = 50;

var sizeStyle = this.tableView.Style.GetOrCreateColumnStyle (this.dtFiles.Columns.Add (Style.SizeColumnName, typeof (int)));
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)));
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];
Expand All @@ -1006,7 +1007,8 @@ private void SetupTableColumns ()

dateModifiedStyle.MinWidth = 30;

var typeStyle = this.tableView.Style.GetOrCreateColumnStyle (this.dtFiles.Columns.Add (Style.TypeColumnName, typeof (int)));
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;

Expand Down Expand Up @@ -1237,7 +1239,7 @@ private void WriteStateToTableView ()

private void BuildRow (int idx)
{
this.tableView.Table.Rows.Add (idx, idx, idx, idx);
dtFiles.Rows.Add (idx, idx, idx, idx);
}

private ColorScheme ColorGetter (TableView.CellColorGetterArgs args)
Expand Down Expand Up @@ -1277,7 +1279,7 @@ private IEnumerable<FileSystemInfoStats> MultiRowToStats ()

foreach (var p in this.tableView.GetAllSelectedCells ()) {

var add = this.State?.Children [(int)this.tableView.Table.Rows [p.Y] [0]];
var add = this.State?.Children [(int)this.tableView.Table[p.Y, 0]];
if (add != null) {
toReturn.Add (add);
}
Expand All @@ -1288,7 +1290,7 @@ private IEnumerable<FileSystemInfoStats> MultiRowToStats ()
}
private FileSystemInfoStats RowToStats (int rowIndex)
{
return this.State?.Children [(int)this.tableView.Table.Rows [rowIndex] [0]];
return this.State?.Children [(int)this.tableView.Table[rowIndex,0]];
}
private int? StatsToRow (IFileSystemInfo fileSystemInfo)
{
Expand All @@ -1299,7 +1301,7 @@ private FileSystemInfoStats RowToStats (int rowIndex)

// find the row number in our DataTable where the cell
// contains idx
var match = tableView.Table.Rows
var match = dtFiles.Rows
.Cast<DataRow> ()
.Select ((r, rIdx) => new { row = r, rowIdx = rIdx })
.Where (t => (int)t.row [0] == idx)
Expand Down Expand Up @@ -1367,7 +1369,7 @@ private class FileDialogSorter {
private readonly FileDialog dlg;
private TableView tableView;

private DataColumn currentSort = null;
private int? currentSort = null;
private bool currentSortIsAsc = true;

public FileDialogSorter (FileDialog dlg, TableView tableView)
Expand All @@ -1378,17 +1380,17 @@ public FileDialogSorter (FileDialog dlg, TableView tableView)
// if user clicks the mouse in TableView
this.tableView.MouseClick += (s, e) => {

var clickedCell = this.tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out DataColumn clickedCol);
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);
this.SortColumn (clickedCol.Value);
} else if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {

// right click in a header
this.ShowHeaderContextMenu (clickedCol, e);
this.ShowHeaderContextMenu (clickedCol.Value, e);
}
} else {
if (clickedCell != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
Expand All @@ -1406,10 +1408,14 @@ internal void ApplySort ()
{
var col = this.currentSort;

if(col == null) {
return;
}

// TODO: Consider preserving selection
this.tableView.Table.Rows.Clear ();
dlg.dtFiles.Rows.Clear ();

var colName = col == null ? null : StripArrows (col.ColumnName);
var colName = col == null ? null : StripArrows (tableView.Table.ColumnNames[col.Value]);

var stats = this.dlg.State?.Children ?? new FileSystemInfoStats [0];

Expand Down Expand Up @@ -1438,13 +1444,13 @@ internal void ApplySort ()
this.dlg.BuildRow (o.i);
}

foreach (DataColumn c in this.tableView.Table.Columns) {
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 == col) {
if (c.Ordinal == col) {
c.ColumnName += this.currentSortIsAsc ? " (▲)" : " (▼)";
}
}
Expand All @@ -1458,13 +1464,13 @@ private static string StripArrows (string columnName)
return columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty);
}

private void SortColumn (DataColumn clickedCol)
private void SortColumn (int clickedCol)
{
this.GetProposedNewSortOrder (clickedCol, out var isAsc);
this.SortColumn (clickedCol, isAsc);
}

internal void SortColumn (DataColumn col, bool isAsc)
internal void SortColumn (int col, bool isAsc)
{
// set a sort order
this.currentSort = col;
Expand All @@ -1473,19 +1479,19 @@ internal void SortColumn (DataColumn col, bool isAsc)
this.ApplySort ();
}

private string GetProposedNewSortOrder (DataColumn clickedCol, out bool isAsc)
private string GetProposedNewSortOrder (int clickedCol, out bool isAsc)
{
// work out new sort order
if (this.currentSort == clickedCol && this.currentSortIsAsc) {
isAsc = false;
return $"{clickedCol.ColumnName} DESC";
return $"{tableView.Table.ColumnNames[clickedCol]} DESC";
} else {
isAsc = true;
return $"{clickedCol.ColumnName} ASC";
return $"{tableView.Table.ColumnNames [clickedCol]} ASC";
}
}

private void ShowHeaderContextMenu (DataColumn clickedCol, MouseEventEventArgs e)
private void ShowHeaderContextMenu (int clickedCol, MouseEventEventArgs e)
{
var sort = this.GetProposedNewSortOrder (clickedCol, out var isAsc);

Expand All @@ -1494,7 +1500,7 @@ private void ShowHeaderContextMenu (DataColumn clickedCol, MouseEventEventArgs e
e.MouseEvent.Y + 1,
new MenuBarItem (new MenuItem []
{
new MenuItem($"Hide {StripArrows(clickedCol.ColumnName)}", string.Empty, () => this.HideColumn(clickedCol)),
new MenuItem($"Hide {StripArrows(tableView.Table.ColumnNames[clickedCol])}", string.Empty, () => this.HideColumn(clickedCol)),
new MenuItem($"Sort {StripArrows(sort)}",string.Empty, ()=> this.SortColumn(clickedCol,isAsc)),
})
);
Expand Down Expand Up @@ -1524,7 +1530,7 @@ private void ShowCellContextMenu (Point? clickedCell, MouseEventEventArgs e)
contextMenu.Show ();
}

private void HideColumn (DataColumn clickedCol)
private void HideColumn (int clickedCol)
{
var style = this.tableView.Style.GetOrCreateColumnStyle (clickedCol);
style.Visible = false;
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/Views/TableView/CellActivatedEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class CellActivatedEventArgs : EventArgs {
/// The current table to which the new indexes refer. May be null e.g. if selection change is the result of clearing the table from the view
/// </summary>
/// <value></value>
public DataTable Table { get; }
public ITableSource Table { get; }

/// <summary>
/// The column index of the <see cref="Table"/> cell that is being activated
Expand All @@ -31,7 +31,7 @@ public class CellActivatedEventArgs : EventArgs {
/// <param name="t"></param>
/// <param name="col"></param>
/// <param name="row"></param>
public CellActivatedEventArgs (DataTable t, int col, int row)
public CellActivatedEventArgs (ITableSource t, int col, int row)
{
Table = t;
Col = col;
Expand Down
35 changes: 35 additions & 0 deletions Terminal.Gui/Views/TableView/DataTableSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Data;
using System.Linq;

namespace Terminal.Gui {
/// <summary>
/// <see cref="ITableSource"/> implementation that wraps
/// a <see cref="System.Data.DataTable"/>. This class is
/// mutable: changes are permitted to the wrapped <see cref="DataTable"/>.
/// </summary>
public class DataTableSource : ITableSource
{
private readonly DataTable table;

/// <summary>
/// Creates a new instance based on the data in <paramref name="table"/>.
/// </summary>
/// <param name="table"></param>
public DataTableSource(DataTable table)
{
this.table = table;
}

/// <inheritdoc/>
public object this [int row, int col] => table.Rows[row][col];

/// <inheritdoc/>
public int Rows => table.Rows.Count;

/// <inheritdoc/>
public int Columns => table.Columns.Count;

/// <inheritdoc/>
public string [] ColumnNames => table.Columns.Cast<DataColumn>().Select (c => c.ColumnName).ToArray ();
}
}
54 changes: 54 additions & 0 deletions Terminal.Gui/Views/TableView/EnumerableTableSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Terminal.Gui {

/// <summary>
/// <see cref="ITableSource"/> implementation that wraps arbitrary data.
/// </summary>
/// <typeparam name="T"></typeparam>
public class EnumerableTableSource<T> : ITableSource {
private T [] data;
private string [] cols;
private Dictionary<string, Func<T, object>> lamdas;

/// <summary>
/// Creates a new instance of the class that presents <paramref name="data"/>
/// collection as a table.
/// </summary>
/// <remarks>The elements of the <paramref name="data"/> collection are recorded during
/// construction (immutable) but the properties of those objects are permitted to
/// change.</remarks>
/// <param name="data">The data that you want to present. The members of this collection
/// will be frozen after construction.</param>
/// <param name="columnDefinitions">
/// Getter methods for each property you want to present in the table. For example:
/// <code>
/// new () {
/// { "Colname1", (t)=>t.SomeField},
/// { "Colname2", (t)=>t.SomeOtherField}
///}
/// </code></param>
public EnumerableTableSource (IEnumerable<T> data, Dictionary<string, Func<T, object>> columnDefinitions)
{
this.data = data.ToArray ();
this.cols = columnDefinitions.Keys.ToArray ();
this.lamdas = columnDefinitions;
}

/// <inheritdoc/>
public object this [int row, int col] {
get => this.lamdas [ColumnNames [col]] (this.data [row]);
}

/// <inheritdoc/>
public int Rows => data.Length;

/// <inheritdoc/>
public int Columns => cols.Length;

/// <inheritdoc/>
public string [] ColumnNames => cols;
}
}
33 changes: 33 additions & 0 deletions Terminal.Gui/Views/TableView/ITableSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace Terminal.Gui {
/// <summary>
/// Tabular matrix of data to be displayed in a <see cref="TableView"/>.
/// </summary>
public interface ITableSource
{
/// <summary>
/// Gets the number of rows in the table.
/// </summary>
int Rows { get; }

/// <summary>
/// Gets the number of columns in the table.
/// </summary>
int Columns { get; }

/// <summary>
/// Gets the label for each column.
/// </summary>
string[] ColumnNames { get; }

/// <summary>
/// Returns the data at the given indexes of the table (row, column).
/// </summary>
/// <param name="row"></param>
/// <param name="col"></param>
/// <returns></returns>
object this[int row, int col]
{
get;
}
}
}
Loading