mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* Remove custom navigation in FileDialog and change Ok/Cancel to use AddButton * Fix most tests * Update drawing tests to use contents asserts instead * Update positioning to be less absolute * TableView to not swallow cursors if no selection change manifests * Make SetSelection cleverer about how it decides selection changed * Refactor TableView key handling/Tab to be simpler and faster * Tests for TableView selection moving out * Add test for TableView selection change swallow cursor * Fix formatting * Fix split container height when buttons have shadows * Fix alignment of buttons and input boxes
This commit is contained in:
@@ -10,6 +10,9 @@ namespace Terminal.Gui;
|
||||
/// </summary>
|
||||
public class FileDialog : Dialog
|
||||
{
|
||||
private const int alignmentGroupInput = 32;
|
||||
private const int alignmentGroupComplete = 55;
|
||||
|
||||
/// <summary>Gets the Path separators for the operating system</summary>
|
||||
internal static char [] Separators =
|
||||
[
|
||||
@@ -71,24 +74,20 @@ public class FileDialog : Dialog
|
||||
|
||||
_btnOk = new Button
|
||||
{
|
||||
Y = Pos.AnchorEnd (1), X = Pos.Func (CalculateOkButtonPosX), IsDefault = true, Text = Style.OkButtonText
|
||||
X = Pos.Align (Alignment.End, AlignmentModes.AddSpaceBetweenItems, alignmentGroupComplete),
|
||||
Y = Pos.AnchorEnd (),
|
||||
IsDefault = true, Text = Style.OkButtonText
|
||||
};
|
||||
_btnOk.Accept += (s, e) => Accept (true);
|
||||
|
||||
_btnOk.KeyDown += (s, k) =>
|
||||
{
|
||||
NavigateIf (k, KeyCode.CursorLeft, _btnCancel);
|
||||
NavigateIf (k, KeyCode.CursorUp, _tableView);
|
||||
};
|
||||
|
||||
_btnCancel = new Button { Y = Pos.AnchorEnd (1), X = Pos.Right (_btnOk) + 1, Text = Strings.btnCancel };
|
||||
_btnCancel = new Button
|
||||
{
|
||||
X = Pos.Align (Alignment.End, AlignmentModes.AddSpaceBetweenItems, alignmentGroupComplete),
|
||||
Y = Pos.AnchorEnd(),
|
||||
Text = Strings.btnCancel
|
||||
};
|
||||
|
||||
_btnCancel.KeyDown += (s, k) =>
|
||||
{
|
||||
NavigateIf (k, KeyCode.CursorLeft, _btnToggleSplitterCollapse);
|
||||
NavigateIf (k, KeyCode.CursorUp, _tableView);
|
||||
NavigateIf (k, KeyCode.CursorRight, _btnOk);
|
||||
};
|
||||
_btnCancel.Accept += (s, e) =>
|
||||
{
|
||||
Canceled = true;
|
||||
@@ -121,7 +120,13 @@ public class FileDialog : Dialog
|
||||
_tbPath.Autocomplete = new AppendAutocomplete (_tbPath);
|
||||
_tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator ();
|
||||
|
||||
_splitContainer = new TileView { X = 0, Y = 2, Width = Dim.Fill (), Height = Dim.Fill (1) };
|
||||
_splitContainer = new TileView
|
||||
{
|
||||
X = 0,
|
||||
Y = Pos.Bottom (_btnBack),
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (Dim.Func (() => IsInitialized ? _btnOk.Frame.Height : 1)),
|
||||
};
|
||||
|
||||
Initialized += (s, e) =>
|
||||
{
|
||||
@@ -129,7 +134,7 @@ public class FileDialog : Dialog
|
||||
_splitContainer.Tiles.ElementAt (0).ContentView.Visible = false;
|
||||
};
|
||||
|
||||
// this.splitContainer.Border.BorderStyle = BorderStyle.None;
|
||||
// this.splitContainer.Border.BorderStyle = BorderStyle.None;
|
||||
|
||||
_tableView = new TableView
|
||||
{
|
||||
@@ -158,28 +163,7 @@ public class FileDialog : Dialog
|
||||
ColumnStyle typeStyle = Style.TableStyle.GetOrCreateColumnStyle (3);
|
||||
typeStyle.MinWidth = 6;
|
||||
typeStyle.ColorGetter = ColorGetter;
|
||||
|
||||
_tableView.KeyDown += (s, k) =>
|
||||
{
|
||||
if (_tableView.SelectedRow <= 0)
|
||||
{
|
||||
NavigateIf (k, KeyCode.CursorUp, _tbPath);
|
||||
}
|
||||
|
||||
if (_tableView.SelectedRow == _tableView.Table.Rows - 1)
|
||||
{
|
||||
NavigateIf (k, KeyCode.CursorDown, _btnToggleSplitterCollapse);
|
||||
}
|
||||
|
||||
if (_splitContainer.Tiles.First ().ContentView.Visible && _tableView.SelectedColumn == 0)
|
||||
{
|
||||
NavigateIf (k, KeyCode.CursorLeft, _treeView);
|
||||
}
|
||||
|
||||
if (k.Handled)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
_treeView = new TreeView<IFileSystemInfo> { Width = Dim.Fill (), Height = Dim.Fill () };
|
||||
|
||||
var fileDialogTreeBuilder = new FileSystemTreeBuilder ();
|
||||
@@ -192,7 +176,11 @@ public class FileDialog : Dialog
|
||||
_splitContainer.Tiles.ElementAt (0).ContentView.Add (_treeView);
|
||||
_splitContainer.Tiles.ElementAt (1).ContentView.Add (_tableView);
|
||||
|
||||
_btnToggleSplitterCollapse = new Button { Y = Pos.AnchorEnd (1), Text = GetToggleSplitterText (false) };
|
||||
_btnToggleSplitterCollapse = new Button
|
||||
{
|
||||
X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput),
|
||||
Y = Pos.AnchorEnd (), Text = GetToggleSplitterText (false)
|
||||
};
|
||||
|
||||
_btnToggleSplitterCollapse.Accept += (s, e) =>
|
||||
{
|
||||
@@ -206,13 +194,13 @@ public class FileDialog : Dialog
|
||||
|
||||
_tbFind = new TextField
|
||||
{
|
||||
X = Pos.Right (_btnToggleSplitterCollapse) + 1,
|
||||
X = Pos.Align (Alignment.Start,AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput),
|
||||
CaptionColor = new Color (Color.Black),
|
||||
Width = 30,
|
||||
Y = Pos.AnchorEnd (1),
|
||||
Y = Pos.Top (_btnToggleSplitterCollapse),
|
||||
HotKey = Key.F.WithAlt
|
||||
};
|
||||
_spinnerView = new SpinnerView { X = Pos.Right (_tbFind) + 1, Y = Pos.AnchorEnd (1), Visible = false };
|
||||
_spinnerView = new SpinnerView { X = Pos.Align (Alignment.Start, AlignmentModes.AddSpaceBetweenItems, alignmentGroupInput), Y = Pos.AnchorEnd (1), Visible = false };
|
||||
|
||||
_tbFind.TextChanged += (s, o) => RestartSearch ();
|
||||
|
||||
@@ -231,16 +219,6 @@ public class FileDialog : Dialog
|
||||
o.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_tbFind.CursorIsAtEnd ())
|
||||
{
|
||||
NavigateIf (o, KeyCode.CursorRight, _btnCancel);
|
||||
}
|
||||
|
||||
if (_tbFind.CursorIsAtStart ())
|
||||
{
|
||||
NavigateIf (o, KeyCode.CursorLeft, _btnToggleSplitterCollapse);
|
||||
}
|
||||
};
|
||||
|
||||
_tableView.Style.ShowHorizontalHeaderOverline = true;
|
||||
@@ -262,48 +240,22 @@ public class FileDialog : Dialog
|
||||
_tableView.KeyBindings.ReplaceCommands (Key.End, Command.BottomEnd);
|
||||
_tableView.KeyBindings.ReplaceCommands (Key.Home.WithShift, Command.TopHomeExtend);
|
||||
_tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.BottomEndExtend);
|
||||
|
||||
_treeView.KeyDown += (s, k) =>
|
||||
{
|
||||
IFileSystemInfo selected = _treeView.SelectedObject;
|
||||
|
||||
if (selected is { })
|
||||
{
|
||||
if (!_treeView.CanExpand (selected) || _treeView.IsExpanded (selected))
|
||||
{
|
||||
NavigateIf (k, KeyCode.CursorRight, _tableView);
|
||||
}
|
||||
else if (_treeView.GetObjectRow (selected) == 0)
|
||||
{
|
||||
NavigateIf (k, KeyCode.CursorUp, _tbPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (k.Handled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
k.Handled = TreeView_KeyDown (k);
|
||||
};
|
||||
|
||||
|
||||
AllowsMultipleSelection = false;
|
||||
|
||||
UpdateNavigationVisibility ();
|
||||
|
||||
// BUGBUG: This TabOrder is counter-intuitive. The tab order for a dialog should match the
|
||||
// order the Views' are presented, left to right, top to bottom.
|
||||
// Determines tab order
|
||||
Add (_btnToggleSplitterCollapse);
|
||||
Add (_tbFind);
|
||||
Add (_spinnerView);
|
||||
Add (_btnOk);
|
||||
Add (_btnCancel);
|
||||
Add (_tbPath);
|
||||
Add (_btnUp);
|
||||
Add (_btnBack);
|
||||
Add (_btnForward);
|
||||
Add (_tbPath);
|
||||
Add (_splitContainer);
|
||||
Add (_btnToggleSplitterCollapse);
|
||||
Add (_tbFind);
|
||||
Add (_spinnerView);
|
||||
|
||||
Add(_btnOk);
|
||||
Add(_btnCancel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1041,23 +993,6 @@ public class FileDialog : Dialog
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private bool NavigateIf (Key keyEvent, KeyCode isKey, View to)
|
||||
{
|
||||
if (keyEvent.KeyCode == isKey)
|
||||
{
|
||||
to.FocusDeepest (NavigationDirection.Forward, null);
|
||||
|
||||
if (to == _tbPath)
|
||||
{
|
||||
_tbPath.MoveEnd ();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void New ()
|
||||
{
|
||||
if (State is { })
|
||||
@@ -1430,19 +1365,6 @@ public class FileDialog : Dialog
|
||||
}
|
||||
}
|
||||
|
||||
private bool TreeView_KeyDown (Key keyEvent)
|
||||
{
|
||||
if (_treeView.HasFocus && Separators.Contains ((char)keyEvent))
|
||||
{
|
||||
_tbPath.FocusDeepest (NavigationDirection.Forward, null);
|
||||
|
||||
// let that keystroke go through on the tbPath instead
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e)
|
||||
{
|
||||
if (e.NewValue is null)
|
||||
|
||||
@@ -54,47 +54,19 @@ public class TableView : View
|
||||
// Things this view knows how to do
|
||||
AddCommand (
|
||||
Command.Right,
|
||||
() =>
|
||||
{
|
||||
// BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
|
||||
ChangeSelectionByOffset (1, 0, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
() => ChangeSelectionByOffsetWithReturn (1, 0));
|
||||
|
||||
AddCommand (
|
||||
Command.Left,
|
||||
() =>
|
||||
{
|
||||
// BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
|
||||
ChangeSelectionByOffset (-1, 0, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
() => ChangeSelectionByOffsetWithReturn (-1, 0));
|
||||
|
||||
AddCommand (
|
||||
Command.LineUp,
|
||||
() =>
|
||||
{
|
||||
// BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
|
||||
ChangeSelectionByOffset (0, -1, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
() => ChangeSelectionByOffsetWithReturn (0, -1));
|
||||
|
||||
AddCommand (
|
||||
Command.LineDown,
|
||||
() =>
|
||||
{
|
||||
// BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
|
||||
ChangeSelectionByOffset (0, 1, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
() => ChangeSelectionByOffsetWithReturn (0, 1));
|
||||
|
||||
AddCommand (
|
||||
Command.PageUp,
|
||||
@@ -519,6 +491,41 @@ public class TableView : View
|
||||
return new Point (colHit.X, tableRow + headerHeight - RowOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private override of <see cref="ChangeSelectionByOffset"/> that returns true if the selection has
|
||||
/// changed as a result of moving the selection. Used by key handling logic to determine whether e.g.
|
||||
/// the cursor right resulted in a change or should be forwarded on to toggle logic handling.
|
||||
/// </summary>
|
||||
/// <param name="offsetX"></param>
|
||||
/// <param name="offsetY"></param>
|
||||
/// <returns></returns>
|
||||
private bool ChangeSelectionByOffsetWithReturn (int offsetX, int offsetY)
|
||||
{
|
||||
var oldSelection = GetSelectionSnapshot ();
|
||||
SetSelection (SelectedColumn + offsetX, SelectedRow + offsetY, false);
|
||||
Update ();
|
||||
|
||||
return !SelectionIsSame (oldSelection);
|
||||
}
|
||||
|
||||
private TableViewSelectionSnapshot GetSelectionSnapshot ()
|
||||
{
|
||||
return new (
|
||||
SelectedColumn,
|
||||
SelectedRow,
|
||||
MultiSelectedRegions.Select (s => s.Rectangle).ToArray ());
|
||||
}
|
||||
|
||||
private bool SelectionIsSame (TableViewSelectionSnapshot oldSelection)
|
||||
{
|
||||
var newSelection = GetSelectionSnapshot ();
|
||||
|
||||
return oldSelection.SelectedColumn == newSelection.SelectedColumn
|
||||
&& oldSelection.SelectedRow == newSelection.SelectedRow
|
||||
&& oldSelection.multiSelection.SequenceEqual (newSelection.multiSelection);
|
||||
}
|
||||
private record TableViewSelectionSnapshot (int SelectedColumn, int SelectedRow, Rectangle [] multiSelection);
|
||||
|
||||
/// <summary>
|
||||
/// Moves the <see cref="SelectedRow"/> and <see cref="SelectedColumn"/> by the provided offsets. Optionally
|
||||
/// starting a box selection (see <see cref="MultiSelect"/>)
|
||||
|
||||
@@ -99,12 +99,13 @@ public class FileDialogTests (ITestOutputHelper output)
|
||||
string openIn = Path.Combine (Environment.CurrentDirectory, "zz");
|
||||
Directory.CreateDirectory (openIn);
|
||||
dlg.Path = openIn + Path.DirectorySeparatorChar;
|
||||
Application.OnKeyDown (Key.Tab);
|
||||
Application.OnKeyDown (Key.Tab);
|
||||
Application.OnKeyDown (Key.Tab);
|
||||
|
||||
var tf = GetTextField (dlg, FileDialogPart.SearchField);
|
||||
tf.SetFocus ();
|
||||
|
||||
Assert.IsType<TextField> (dlg.MostFocused);
|
||||
var tf = (TextField)dlg.MostFocused;
|
||||
Assert.Same (tf, dlg.MostFocused);
|
||||
|
||||
Assert.Equal ("Enter Search", tf.Caption);
|
||||
|
||||
// Dialog has not yet been confirmed with a choice
|
||||
@@ -140,6 +141,10 @@ public class FileDialogTests (ITestOutputHelper output)
|
||||
|
||||
Assert.IsType<TextField> (dlg.MostFocused);
|
||||
Send ('v', ConsoleKey.DownArrow);
|
||||
|
||||
var tv = GetTableView(dlg);
|
||||
tv.SetFocus ();
|
||||
|
||||
Assert.IsType<TableView> (dlg.MostFocused);
|
||||
|
||||
// ".." should be the first thing selected
|
||||
@@ -177,8 +182,10 @@ public class FileDialogTests (ITestOutputHelper output)
|
||||
IReadOnlyCollection<string> eventMultiSelected = null;
|
||||
dlg.FilesSelected += (s, e) => { eventMultiSelected = e.Dialog.MultiSelected; };
|
||||
|
||||
Assert.IsType<TextField> (dlg.MostFocused);
|
||||
Send ('v', ConsoleKey.DownArrow);
|
||||
|
||||
var tv = GetTableView (dlg);
|
||||
tv.SetFocus ();
|
||||
|
||||
Assert.IsType<TableView> (dlg.MostFocused);
|
||||
|
||||
// Try to toggle '..'
|
||||
@@ -232,8 +239,9 @@ public class FileDialogTests (ITestOutputHelper output)
|
||||
IReadOnlyCollection<string> eventMultiSelected = null;
|
||||
dlg.FilesSelected += (s, e) => { eventMultiSelected = e.Dialog.MultiSelected; };
|
||||
|
||||
Assert.IsType<TextField> (dlg.MostFocused);
|
||||
Send ('v', ConsoleKey.DownArrow);
|
||||
var tv = GetTableView (dlg);
|
||||
tv.SetFocus ();
|
||||
|
||||
Assert.IsType<TableView> (dlg.MostFocused);
|
||||
|
||||
// Move selection to subfolder
|
||||
@@ -284,8 +292,9 @@ public class FileDialogTests (ITestOutputHelper output)
|
||||
IReadOnlyCollection<string> eventMultiSelected = null;
|
||||
dlg.FilesSelected += (s, e) => { eventMultiSelected = e.Dialog.MultiSelected; };
|
||||
|
||||
Assert.IsType<TextField> (dlg.MostFocused);
|
||||
Send ('v', ConsoleKey.DownArrow);
|
||||
var tv = GetTableView (dlg);
|
||||
tv.SetFocus ();
|
||||
|
||||
Assert.IsType<TableView> (dlg.MostFocused);
|
||||
|
||||
// Move selection to subfolder
|
||||
@@ -327,8 +336,9 @@ public class FileDialogTests (ITestOutputHelper output)
|
||||
dlg.OpenMode = openModeMixed ? OpenMode.Mixed : OpenMode.Directory;
|
||||
dlg.AllowsMultipleSelection = multiple;
|
||||
|
||||
Assert.IsType<TextField> (dlg.MostFocused);
|
||||
Send ('v', ConsoleKey.DownArrow);
|
||||
var tv = GetTableView (dlg);
|
||||
tv.SetFocus ();
|
||||
|
||||
Assert.IsType<TableView> (dlg.MostFocused);
|
||||
|
||||
// Should be selecting ..
|
||||
@@ -421,45 +431,60 @@ public class FileDialogTests (ITestOutputHelper output)
|
||||
|
||||
fd.Draw ();
|
||||
|
||||
var expected =
|
||||
@$"
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│/demo/ │
|
||||
│{
|
||||
CM.Glyphs.LeftBracket
|
||||
}▲{
|
||||
CM.Glyphs.RightBracket
|
||||
} │
|
||||
│┌────────────┬──────────┬──────────────────────────────┬────────────────┐│
|
||||
││Filename (▲)│Size │Modified │Type ││
|
||||
│├────────────┼──────────┼──────────────────────────────┼────────────────┤│
|
||||
││.. │ │ │<Directory> ││
|
||||
││/subfolder │ │2002-01-01T22:42:10 │<Directory> ││
|
||||
││image.gif │4.00 B │2002-01-01T22:42:10 │.gif ││
|
||||
││jQuery.js │7.00 B │2001-01-01T11:44:42 │.js ││
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│{
|
||||
CM.Glyphs.LeftBracket
|
||||
} ►► {
|
||||
CM.Glyphs.RightBracket
|
||||
} Enter Search {
|
||||
CM.Glyphs.LeftBracket
|
||||
}{
|
||||
CM.Glyphs.LeftDefaultIndicator
|
||||
} OK {
|
||||
CM.Glyphs.RightDefaultIndicator
|
||||
}{
|
||||
CM.Glyphs.RightBracket
|
||||
} {
|
||||
CM.Glyphs.LeftBracket
|
||||
} Cancel {
|
||||
CM.Glyphs.RightBracket
|
||||
} │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
";
|
||||
TestHelpers.AssertDriverContentsAre (expected, output, ignoreLeadingWhitespace: true);
|
||||
/*
|
||||
*
|
||||
*
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│/demo/ │
|
||||
│⟦▲⟧ │
|
||||
│┌────────────┬──────────┬──────────────────────────────┬────────────────┐│
|
||||
││Filename (▲)│Size │Modified │Type ││
|
||||
│├────────────┼──────────┼──────────────────────────────┼────────────────┤│
|
||||
││.. │ │ │<Directory> ││
|
||||
││/subfolder │ │2002-01-01T22:42:10 │<Directory> ││
|
||||
││image.gif │4.00 B │2002-01-01T22:42:10 │.gif ││
|
||||
││jQuery.js │7.00 B │2001-01-01T11:44:42 │.js ││
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│⟦ ►► ⟧ Enter Search ⟦► OK ◄⟧ ⟦ Cancel ⟧ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
var path = GetTextField (fd, FileDialogPart.Path);
|
||||
Assert.Equal ("/demo/", path.Text);
|
||||
|
||||
var tv = GetTableView (fd);
|
||||
|
||||
// Asserting the headers
|
||||
Assert.Equal ("Filename (▲)", tv.Table.ColumnNames.ElementAt (0));
|
||||
Assert.Equal ("Size", tv.Table.ColumnNames.ElementAt (1));
|
||||
Assert.Equal ("Modified", tv.Table.ColumnNames.ElementAt (2));
|
||||
Assert.Equal ("Type", tv.Table.ColumnNames.ElementAt (3));
|
||||
|
||||
// Asserting the table contents
|
||||
Assert.Equal ("..", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [0, 0]));
|
||||
Assert.Equal ("/subfolder", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [1, 0]));
|
||||
Assert.Equal ("image.gif", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [2, 0]));
|
||||
Assert.Equal ("jQuery.js", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [3, 0]));
|
||||
|
||||
Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [0, 1]));
|
||||
Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [1, 1]));
|
||||
Assert.Equal ("4.00 B", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [2, 1]));
|
||||
Assert.Equal ("7.00 B", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [3, 1]));
|
||||
|
||||
Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [0, 2]));
|
||||
Assert.Equal ("2002-01-01T22:42:10", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [1, 2]));
|
||||
Assert.Equal ("2002-01-01T22:42:10", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [2, 2]));
|
||||
Assert.Equal ("2001-01-01T11:44:42", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [3, 2]));
|
||||
|
||||
Assert.Equal ("<Directory>", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [0, 3]));
|
||||
Assert.Equal ("<Directory>", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [1, 3]));
|
||||
Assert.Equal (".gif", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [2, 3]));
|
||||
Assert.Equal (".js", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [3, 3]));
|
||||
|
||||
fd.Dispose ();
|
||||
}
|
||||
|
||||
@@ -479,45 +504,64 @@ public class FileDialogTests (ITestOutputHelper output)
|
||||
|
||||
fd.Draw ();
|
||||
|
||||
var expected =
|
||||
@$"
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│c:\demo\ │
|
||||
│{
|
||||
CM.Glyphs.LeftBracket
|
||||
}▲{
|
||||
CM.Glyphs.RightBracket
|
||||
} │
|
||||
│┌────────────┬──────────┬──────────────────────────────┬────────────────┐│
|
||||
││Filename (▲)│Size │Modified │Type ││
|
||||
│├────────────┼──────────┼──────────────────────────────┼────────────────┤│
|
||||
││.. │ │ │<Directory> ││
|
||||
││\subfolder │ │2002-01-01T22:42:10 │<Directory> ││
|
||||
││image.gif │4.00 B │2002-01-01T22:42:10 │.gif ││
|
||||
││jQuery.js │7.00 B │2001-01-01T11:44:42 │.js ││
|
||||
││mybinary.exe│7.00 B │2001-01-01T11:44:42 │.exe ││
|
||||
│ │
|
||||
│ │
|
||||
│{
|
||||
CM.Glyphs.LeftBracket
|
||||
} ►► {
|
||||
CM.Glyphs.RightBracket
|
||||
} Enter Search {
|
||||
CM.Glyphs.LeftBracket
|
||||
}{
|
||||
CM.Glyphs.LeftDefaultIndicator
|
||||
} OK {
|
||||
CM.Glyphs.RightDefaultIndicator
|
||||
}{
|
||||
CM.Glyphs.RightBracket
|
||||
} {
|
||||
CM.Glyphs.LeftBracket
|
||||
} Cancel {
|
||||
CM.Glyphs.RightBracket
|
||||
} │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
";
|
||||
TestHelpers.AssertDriverContentsAre (expected, output, ignoreLeadingWhitespace: true);
|
||||
/*
|
||||
*
|
||||
*
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│c:\demo\ │
|
||||
│⟦▲⟧ │
|
||||
│┌────────────┬──────────┬──────────────────────────────┬────────────────┐│
|
||||
││Filename (▲)│Size │Modified │Type ││
|
||||
│├────────────┼──────────┼──────────────────────────────┼────────────────┤│
|
||||
││.. │ │ │<Directory> ││
|
||||
││\subfolder │ │2002-01-01T22:42:10 │<Directory> ││
|
||||
││image.gif │4.00 B │2002-01-01T22:42:10 │.gif ││
|
||||
││jQuery.js │7.00 B │2001-01-01T11:44:42 │.js ││
|
||||
││mybinary.exe│7.00 B │2001-01-01T11:44:42 │.exe ││
|
||||
│ │
|
||||
│ │
|
||||
│⟦ ►► ⟧ Enter Search ⟦► OK ◄⟧ ⟦ Cancel ⟧ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
var path = GetTextField (fd, FileDialogPart.Path);
|
||||
Assert.Equal ("c:\\demo\\",path.Text);
|
||||
|
||||
var tv = GetTableView (fd);
|
||||
|
||||
// Asserting the headers
|
||||
Assert.Equal ("Filename (▲)", tv.Table.ColumnNames.ElementAt (0));
|
||||
Assert.Equal ("Size", tv.Table.ColumnNames.ElementAt (1));
|
||||
Assert.Equal ("Modified", tv.Table.ColumnNames.ElementAt (2));
|
||||
Assert.Equal ("Type", tv.Table.ColumnNames.ElementAt (3));
|
||||
|
||||
// Asserting the table contents
|
||||
Assert.Equal ("..", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [0, 0]));
|
||||
Assert.Equal (@"\subfolder", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [1, 0]));
|
||||
Assert.Equal ("image.gif", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [2, 0]));
|
||||
Assert.Equal ("jQuery.js", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [3, 0]));
|
||||
Assert.Equal ("mybinary.exe", tv.Style.GetOrCreateColumnStyle (0).GetRepresentation (tv.Table [4, 0]));
|
||||
|
||||
Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [0, 1]));
|
||||
Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [1, 1]));
|
||||
Assert.Equal ("4.00 B", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [2, 1]));
|
||||
Assert.Equal ("7.00 B", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [3, 1]));
|
||||
Assert.Equal ("7.00 B", tv.Style.GetOrCreateColumnStyle (1).GetRepresentation (tv.Table [4, 1]));
|
||||
|
||||
Assert.Equal ("", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [0, 2]));
|
||||
Assert.Equal ("2002-01-01T22:42:10", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [1, 2]));
|
||||
Assert.Equal ("2002-01-01T22:42:10", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [2, 2]));
|
||||
Assert.Equal ("2001-01-01T11:44:42", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [3, 2]));
|
||||
Assert.Equal ("2001-01-01T11:44:42", tv.Style.GetOrCreateColumnStyle (2).GetRepresentation (tv.Table [4, 2]));
|
||||
|
||||
Assert.Equal ("<Directory>", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [0, 3]));
|
||||
Assert.Equal ("<Directory>", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [1, 3]));
|
||||
Assert.Equal (".gif", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [2, 3]));
|
||||
Assert.Equal (".js", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [3, 3]));
|
||||
Assert.Equal (".exe", tv.Style.GetOrCreateColumnStyle (3).GetRepresentation (tv.Table [4, 3]));
|
||||
|
||||
fd.Dispose ();
|
||||
}
|
||||
|
||||
@@ -734,4 +778,30 @@ public class FileDialogTests (ITestOutputHelper output)
|
||||
Send ('\\', ConsoleKey.Separator);
|
||||
}
|
||||
}
|
||||
|
||||
private TextField GetTextField (FileDialog dlg, FileDialogPart part)
|
||||
{
|
||||
switch (part)
|
||||
{
|
||||
case FileDialogPart.Path:
|
||||
return dlg.Subviews.OfType<TextField> ().ElementAt (0);
|
||||
case FileDialogPart.SearchField:
|
||||
return dlg.Subviews.OfType<TextField> ().ElementAt (1);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException (nameof (part), part, null);
|
||||
}
|
||||
}
|
||||
|
||||
private TableView GetTableView (FileDialog dlg)
|
||||
{
|
||||
var tile = dlg.Subviews.OfType<TileView> ().Single ();
|
||||
return (TableView)tile.Tiles.ElementAt (1).ContentView.Subviews.ElementAt(0);
|
||||
}
|
||||
|
||||
private enum FileDialogPart
|
||||
{
|
||||
Path,
|
||||
SearchField,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1068,7 +1068,7 @@ public class TableViewTests (ITestOutputHelper output)
|
||||
Application.Begin (top);
|
||||
|
||||
tv.HasFocus = focused;
|
||||
Assert.Equal(focused, tv.HasFocus);
|
||||
Assert.Equal (focused, tv.HasFocus);
|
||||
|
||||
tv.Draw ();
|
||||
|
||||
@@ -1155,7 +1155,7 @@ public class TableViewTests (ITestOutputHelper output)
|
||||
|
||||
// when B is 2 use the custom highlight color for the row
|
||||
tv.Style.RowColorGetter += e => Convert.ToInt32 (e.Table [e.RowIndex, 1]) == 2 ? rowHighlight : null;
|
||||
|
||||
|
||||
var top = new Toplevel ();
|
||||
top.Add (tv);
|
||||
Application.Begin (top);
|
||||
@@ -3169,7 +3169,7 @@ A B C
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDataColumnCaption()
|
||||
public void TestDataColumnCaption ()
|
||||
{
|
||||
var tableView = new TableView ();
|
||||
|
||||
@@ -3191,6 +3191,175 @@ A B C
|
||||
Assert.Equal ("Column Name 2", cn [1]);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CanTabOutOfTableViewUsingCursor_Left ()
|
||||
{
|
||||
GetTableViewWithSiblings (out var tf1, out var tableView, out var tf2);
|
||||
|
||||
// Make the selected cell one in
|
||||
tableView.SelectedColumn = 1;
|
||||
|
||||
// Pressing left should move us to the first column without changing focus
|
||||
Application.OnKeyDown (Key.CursorLeft);
|
||||
Assert.Same (tableView, Application.Current.MostFocused);
|
||||
Assert.True (tableView.HasFocus);
|
||||
|
||||
// Because we are now on the leftmost cell a further left press should move focus
|
||||
Application.OnKeyDown (Key.CursorLeft);
|
||||
|
||||
Assert.NotSame (tableView, Application.Current.MostFocused);
|
||||
Assert.False (tableView.HasFocus);
|
||||
|
||||
Assert.Same (tf1, Application.Current.MostFocused);
|
||||
Assert.True (tf1.HasFocus);
|
||||
|
||||
Application.Current.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanTabOutOfTableViewUsingCursor_Up ()
|
||||
{
|
||||
GetTableViewWithSiblings (out var tf1, out var tableView, out var tf2);
|
||||
|
||||
// Make the selected cell one in
|
||||
tableView.SelectedRow = 1;
|
||||
|
||||
// First press should move us up
|
||||
Application.OnKeyDown (Key.CursorUp);
|
||||
Assert.Same (tableView, Application.Current.MostFocused);
|
||||
Assert.True (tableView.HasFocus);
|
||||
|
||||
// Because we are now on the top row a further press should move focus
|
||||
Application.OnKeyDown (Key.CursorUp);
|
||||
|
||||
Assert.NotSame (tableView, Application.Current.MostFocused);
|
||||
Assert.False (tableView.HasFocus);
|
||||
|
||||
Assert.Same (tf1, Application.Current.MostFocused);
|
||||
Assert.True (tf1.HasFocus);
|
||||
|
||||
Application.Current.Dispose ();
|
||||
}
|
||||
[Fact]
|
||||
public void CanTabOutOfTableViewUsingCursor_Right ()
|
||||
{
|
||||
GetTableViewWithSiblings (out var tf1, out var tableView, out var tf2);
|
||||
|
||||
// Make the selected cell one in from the rightmost column
|
||||
tableView.SelectedColumn = tableView.Table.Columns - 2;
|
||||
|
||||
// First press should move us to the rightmost column without changing focus
|
||||
Application.OnKeyDown (Key.CursorRight);
|
||||
Assert.Same (tableView, Application.Current.MostFocused);
|
||||
Assert.True (tableView.HasFocus);
|
||||
|
||||
// Because we are now on the rightmost cell, a further right press should move focus
|
||||
Application.OnKeyDown (Key.CursorRight);
|
||||
|
||||
Assert.NotSame (tableView, Application.Current.MostFocused);
|
||||
Assert.False (tableView.HasFocus);
|
||||
|
||||
Assert.Same (tf2, Application.Current.MostFocused);
|
||||
Assert.True (tf2.HasFocus);
|
||||
|
||||
Application.Current.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanTabOutOfTableViewUsingCursor_Down ()
|
||||
{
|
||||
GetTableViewWithSiblings (out var tf1, out var tableView, out var tf2);
|
||||
|
||||
// Make the selected cell one in from the bottommost row
|
||||
tableView.SelectedRow = tableView.Table.Rows - 2;
|
||||
|
||||
// First press should move us to the bottommost row without changing focus
|
||||
Application.OnKeyDown (Key.CursorDown);
|
||||
Assert.Same (tableView, Application.Current.MostFocused);
|
||||
Assert.True (tableView.HasFocus);
|
||||
|
||||
// Because we are now on the bottommost cell, a further down press should move focus
|
||||
Application.OnKeyDown (Key.CursorDown);
|
||||
|
||||
Assert.NotSame (tableView, Application.Current.MostFocused);
|
||||
Assert.False (tableView.HasFocus);
|
||||
|
||||
Assert.Same (tf2, Application.Current.MostFocused);
|
||||
Assert.True (tf2.HasFocus);
|
||||
|
||||
Application.Current.Dispose ();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CanTabOutOfTableViewUsingCursor_Left_ClearsSelectionFirst ()
|
||||
{
|
||||
GetTableViewWithSiblings (out var tf1, out var tableView, out var tf2);
|
||||
|
||||
// Make the selected cell one in
|
||||
tableView.SelectedColumn = 1;
|
||||
|
||||
// Pressing shift-left should give us a multi selection
|
||||
Application.OnKeyDown (Key.CursorLeft.WithShift);
|
||||
Assert.Same (tableView, Application.Current.MostFocused);
|
||||
Assert.True (tableView.HasFocus);
|
||||
Assert.Equal (2, tableView.GetAllSelectedCells ().Count ());
|
||||
|
||||
// Because we are now on the leftmost cell a further left press would normally move focus
|
||||
// However there is an ongoing selection so instead the operation clears the selection and
|
||||
// gets swallowed (not resulting in a focus change)
|
||||
Application.OnKeyDown (Key.CursorLeft);
|
||||
|
||||
// Selection 'clears' just to the single cell and we remain focused
|
||||
Assert.Single (tableView.GetAllSelectedCells ());
|
||||
Assert.Same (tableView, Application.Current.MostFocused);
|
||||
Assert.True (tableView.HasFocus);
|
||||
|
||||
// A further left will switch focus
|
||||
Application.OnKeyDown (Key.CursorLeft);
|
||||
|
||||
Assert.NotSame (tableView, Application.Current.MostFocused);
|
||||
Assert.False (tableView.HasFocus);
|
||||
|
||||
Assert.Same (tf1, Application.Current.MostFocused);
|
||||
Assert.True (tf1.HasFocus);
|
||||
|
||||
Application.Current.Dispose ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates 3 views on <see cref="Application.Current"/> with the focus in the
|
||||
/// <see cref="TableView"/>. This is a helper method to setup tests that want to
|
||||
/// explore moving input focus out of a tableview.
|
||||
/// </summary>
|
||||
/// <param name="tv"></param>
|
||||
/// <param name="tf1"></param>
|
||||
/// <param name="tf2"></param>
|
||||
private void GetTableViewWithSiblings (out TextField tf1, out TableView tableView, out TextField tf2)
|
||||
{
|
||||
tableView = new TableView ();
|
||||
tableView.BeginInit ();
|
||||
tableView.EndInit ();
|
||||
|
||||
Application.Navigation = new ();
|
||||
Application.Current = new ();
|
||||
tf1 = new TextField ();
|
||||
tf2 = new TextField ();
|
||||
Application.Current.Add (tf1);
|
||||
Application.Current.Add (tableView);
|
||||
Application.Current.Add (tf2);
|
||||
|
||||
tableView.SetFocus ();
|
||||
|
||||
Assert.Same (tableView, Application.Current.MostFocused);
|
||||
Assert.True (tableView.HasFocus);
|
||||
|
||||
|
||||
// Set big table
|
||||
tableView.Table = BuildTable (25, 50);
|
||||
}
|
||||
|
||||
private TableView GetABCDEFTableView (out DataTable dt)
|
||||
{
|
||||
var tableView = new TableView ();
|
||||
|
||||
Reference in New Issue
Block a user