integrated tznind's stuff

This commit is contained in:
Tig Kindel
2022-10-24 18:56:06 -06:00
parent c38af801b6
commit 18ec9a2a70
6 changed files with 327 additions and 97 deletions

View File

@@ -10,54 +10,54 @@ namespace Terminal.Gui {
public enum Command {
/// <summary>
/// Moves the caret down one line.
/// Moves down one item (cell, line, etc...).
/// </summary>
LineDown,
/// <summary>
/// Extends the selection down one line.
/// Extends the selection down one (cell, line, etc...).
/// </summary>
LineDownExtend,
/// <summary>
/// Moves the caret down to the last child node of the branch that holds the current selection
/// Moves down to the last child node of the branch that holds the current selection.
/// </summary>
LineDownToLastBranch,
/// <summary>
/// Scrolls down one line (without changing the selection).
/// Scrolls down one (cell, line, etc...) (without changing the selection).
/// </summary>
ScrollDown,
// --------------------------------------------------------------------
/// <summary>
/// Moves the caret up one line.
/// Moves up one (cell, line, etc...).
/// </summary>
LineUp,
/// <summary>
/// Extends the selection up one line.
/// Extends the selection up one item (cell, line, etc...).
/// </summary>
LineUpExtend,
/// <summary>
/// Moves the caret up to the first child node of the branch that holds the current selection
/// Moves up to the first child node of the branch that holds the current selection.
/// </summary>
LineUpToFirstBranch,
/// <summary>
/// Scrolls up one line (without changing the selection).
/// Scrolls up one item (cell, line, etc...) (without changing the selection).
/// </summary>
ScrollUp,
/// <summary>
/// Moves the selection left one by the minimum increment supported by the view e.g. single character, cell, item etc.
/// Moves the selection left one by the minimum increment supported by the <see cref="View"/> e.g. single character, cell, item etc.
/// </summary>
Left,
/// <summary>
/// Scrolls one character to the left
/// Scrolls one item (cell, character, etc...) to the left
/// </summary>
ScrollLeft,
@@ -72,7 +72,7 @@ namespace Terminal.Gui {
Right,
/// <summary>
/// Scrolls one character to the right.
/// Scrolls one item (cell, character, etc...) to the right.
/// </summary>
ScrollRight,
@@ -102,12 +102,12 @@ namespace Terminal.Gui {
WordRightExtend,
/// <summary>
/// Deletes and copies to the clipboard the characters from the current position to the end of the line.
/// Cuts to the clipboard the characters from the current position to the end of the line.
/// </summary>
CutToEndLine,
/// <summary>
/// Deletes and copies to the clipboard the characters from the current position to the start of the line.
/// Cuts to the clipboard the characters from the current position to the start of the line.
/// </summary>
CutToStartLine,
@@ -140,47 +140,47 @@ namespace Terminal.Gui {
DisableOverwrite,
/// <summary>
/// Move the page down.
/// Move one page down.
/// </summary>
PageDown,
/// <summary>
/// Move the page down increase selection area to cover revealed objects/characters.
/// Move one page page extending the selection to cover revealed objects/characters.
/// </summary>
PageDownExtend,
/// <summary>
/// Move the page up.
/// Move one page up.
/// </summary>
PageUp,
/// <summary>
/// Move the page up increase selection area to cover revealed objects/characters.
/// Move one page up extending the selection to cover revealed objects/characters.
/// </summary>
PageUpExtend,
/// <summary>
/// Moves to top begin.
/// Moves to the top/home.
/// </summary>
TopHome,
/// <summary>
/// Extends the selection to the top begin.
/// Extends the selection to the top/home.
/// </summary>
TopHomeExtend,
/// <summary>
/// Moves to bottom end.
/// Moves to the bottom/end.
/// </summary>
BottomEnd,
/// <summary>
/// Extends the selection to the bottom end.
/// Extends the selection to the bottom/end.
/// </summary>
BottomEndExtend,
/// <summary>
/// Open selected item.
/// Open the selected item.
/// </summary>
OpenSelectedItem,
@@ -190,43 +190,43 @@ namespace Terminal.Gui {
ToggleChecked,
/// <summary>
/// Accepts the current state (e.g. selection, button press etc)
/// Accepts the current state (e.g. selection, button press etc).
/// </summary>
Accept,
/// <summary>
/// Toggles the Expanded or collapsed state of a a list or item (with subitems)
/// Toggles the Expanded or collapsed state of a a list or item (with subitems).
/// </summary>
ToggleExpandCollapse,
/// <summary>
/// Expands a list or item (with subitems)
/// Expands a list or item (with subitems).
/// </summary>
Expand,
/// <summary>
/// Recursively Expands all child items and their child items (if any)
/// Recursively Expands all child items and their child items (if any).
/// </summary>
ExpandAll,
/// <summary>
/// Collapses a list or item (with subitems)
/// Collapses a list or item (with subitems).
/// </summary>
Collapse,
/// <summary>
/// Recursively collapses a list items of their children (if any)
/// Recursively collapses a list items of their children (if any).
/// </summary>
CollapseAll,
/// <summary>
/// Cancels any current temporary states on the control e.g. expanding
/// a combo list
/// Cancels an action or any temporary states on the control e.g. expanding
/// a combo list.
/// </summary>
Cancel,
/// <summary>
/// Unix emulation
/// Unix emulation.
/// </summary>
UnixEmulation,
@@ -241,12 +241,12 @@ namespace Terminal.Gui {
DeleteCharLeft,
/// <summary>
/// Selects all objects in the control.
/// Selects all objects.
/// </summary>
SelectAll,
/// <summary>
/// Deletes all objects in the control.
/// Deletes all objects.
/// </summary>
DeleteAll,
@@ -336,7 +336,7 @@ namespace Terminal.Gui {
Paste,
/// <summary>
/// Quit a toplevel.
/// Quit a <see cref="Toplevel"/>.
/// </summary>
QuitToplevel,
@@ -356,37 +356,37 @@ namespace Terminal.Gui {
PreviousView,
/// <summary>
/// Moves focus to the next view or toplevel (case of Mdi).
/// Moves focus to the next view or toplevel (case of MDI).
/// </summary>
NextViewOrTop,
/// <summary>
/// Moves focus to the next previous or toplevel (case of Mdi).
/// Moves focus to the next previous or toplevel (case of MDI).
/// </summary>
PreviousViewOrTop,
/// <summary>
/// Refresh the application.
/// Refresh.
/// </summary>
Refresh,
/// <summary>
/// Toggles the extended selection.
/// Toggles the selection.
/// </summary>
ToggleExtend,
/// <summary>
/// Inserts a new line.
/// Inserts a new item.
/// </summary>
NewLine,
/// <summary>
/// Inserts a tab.
/// Tabs to the next item.
/// </summary>
Tab,
/// <summary>
/// Inserts a shift tab.
/// Tabs back to the previous item.
/// </summary>
BackTab
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Terminal.Gui {
@@ -11,11 +12,17 @@ namespace Terminal.Gui {
DateTime lastKeystroke = DateTime.MinValue;
const int TypingDelay = 250;
public StringComparer Comparer { get; set; } = StringComparer.InvariantCultureIgnoreCase;
private IEnumerable<string> Collection { get => _collection; set => _collection = value; }
public int CalculateNewIndex (string [] collection, int currentIndex, char keyStruck)
private IEnumerable<string> _collection;
public SearchCollectionNavigator (IEnumerable<string> collection) { _collection = collection; }
public int CalculateNewIndex (IEnumerable<string> collection, int currentIndex, char keyStruck)
{
// if user presses a letter key
if (char.IsLetterOrDigit (keyStruck) || char.IsPunctuation (keyStruck)) {
// if user presses a key
if (true) {//char.IsLetterOrDigit (keyStruck) || char.IsPunctuation (keyStruck) || char.IsSymbol(keyStruck)) {
// maybe user pressed 'd' and now presses 'd' again.
// a candidate search is things that begin with "dd"
@@ -73,7 +80,12 @@ namespace Terminal.Gui {
}
}
private int GetNextIndexMatching (string [] collection, int currentIndex, string search, bool preferNotToMoveToNewIndexes = false)
public int CalculateNewIndex (int currentIndex, char keyStruck)
{
return CalculateNewIndex (Collection, currentIndex, keyStruck);
}
private int GetNextIndexMatching (IEnumerable<string> collection, int currentIndex, string search, bool preferNotToMoveToNewIndexes = false)
{
if (string.IsNullOrEmpty (search)) {
return -1;

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NStack;
@@ -179,6 +180,12 @@ namespace Terminal.Gui {
get => allowsMarking;
set {
allowsMarking = value;
if (allowsMarking) {
AddKeyBinding (Key.Space, Command.ToggleChecked);
} else {
ClearKeybinding (Key.Space);
}
SetNeedsDisplay ();
}
}
@@ -353,8 +360,6 @@ namespace Terminal.Gui {
AddKeyBinding (Key.End, Command.BottomEnd);
AddKeyBinding (Key.Enter, Command.OpenSelectedItem);
AddKeyBinding (Key.Space, Command.ToggleChecked);
}
///<inheritdoc/>
@@ -416,39 +421,30 @@ namespace Terminal.Gui {
/// </summary>
public event Action<ListViewRowEventArgs> RowRender;
private string search { get; set; }
private SearchCollectionNavigator navigator;
///<inheritdoc/>
public override bool ProcessKey (KeyEvent kb)
{
if (source == null)
if (source == null) {
return base.ProcessKey (kb);
}
var result = InvokeKeybindings (kb);
if (result != null)
if (result != null) {
return (bool)result;
}
// Enable user to find & select an item by typing text
if (source is IListDataSourceSearchable &&
!(kb.IsCapslock && kb.IsCtrl && kb.IsAlt && kb.IsScrolllock && kb.IsNumlock && kb.IsCapslock)) {
if (kb.KeyValue >= 32 && kb.KeyValue < 127) {
if (searchTimer == null) {
searchTimer = new System.Timers.Timer (500);
searchTimer.Elapsed += (o, e) => {
searchTimer.Stop ();
searchTimer = null;
search = "";
};
searchTimer.Start ();
}
search += (char)kb.KeyValue;
var found = ((IListDataSourceSearchable)source).StartsWith (search);
if (found != -1) {
SelectedItem = found;
SetNeedsDisplay ();
}
return true;
if (!kb.IsAlt && !kb.IsCapslock && !kb.IsCtrl && !kb.IsScrolllock && !kb.IsNumlock) {
if (navigator == null) {
// BUGBUG: If items change this needs to be recreated.
navigator = new SearchCollectionNavigator (source.ToList().Cast<string>());
}
SelectedItem = navigator.CalculateNewIndex (SelectedItem, (char)kb.KeyValue);
EnsuresVisibilitySelectedItem ();
SetNeedsDisplay ();
return true;
}
return false;

View File

@@ -26,6 +26,10 @@
"Issue1719Repro": {
"commandName": "Project",
"commandLineArgs": "\"ProgressBar Styles\""
},
"SearchCollectionNavTester": {
"commandName": "Project",
"commandLineArgs": "\"Search Collection Nav\""
}
}
}

View File

@@ -0,0 +1,218 @@
using System;
using System.IO;
using System.Linq;
using Terminal.Gui;
using Terminal.Gui.Trees;
namespace UICatalog.Scenarios {
[ScenarioMetadata (Name: "Search Collection Nav", Description: "Demonstrates & tests SearchCollectionNavigator.")]
[ScenarioCategory ("Controls"), ScenarioCategory ("ListView")]
public class SearchCollectionNavigatorTester : Scenario {
TabView tabView;
private int numbeOfNewTabs = 1;
// Don't create a Window, just return the top-level view
public override void Init (Toplevel top, ColorScheme colorScheme)
{
Application.Init ();
Top = top != null ? top : Application.Top;
Top.ColorScheme = Colors.Base;
}
public override void Setup ()
{
var allowMarking = new MenuItem ("Allow _Marking", "", null) {
CheckType = MenuItemCheckStyle.Checked,
Checked = false
};
allowMarking.Action = () => allowMarking.Checked = _listView.AllowsMarking = !_listView.AllowsMarking;
var allowMultiSelection = new MenuItem ("Allow Multi _Selection", "", null) {
CheckType = MenuItemCheckStyle.Checked,
Checked = false
};
allowMultiSelection.Action = () => allowMultiSelection.Checked = _listView.AllowsMultipleSelection = !_listView.AllowsMultipleSelection;
allowMultiSelection.CanExecute = () => allowMarking.Checked;
var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_Configure", new MenuItem [] {
allowMarking,
allowMultiSelection,
null,
new MenuItem ("_Quit", "", () => Quit(), null, null, Key.Q | Key.CtrlMask),
}),
new MenuBarItem("_Quit", "CTRL-Q", () => Quit())
});
Top.Add (menu);
CreateListView ();
var vsep = new LineView (Terminal.Gui.Graphs.Orientation.Vertical) {
X = Pos.Right (_listView),
Y = 1,
Height = Dim.Fill ()
};
Top.Add (vsep);
}
ListView _listView = null;
private void CreateListView ()
{
var label = new Label () {
Text = "ListView",
TextAlignment = TextAlignment.Centered,
X = 0,
Y = 1, // for menu
Width = Dim.Percent (50),
Height = 1,
};
Top.Add (label);
_listView = new ListView () {
X = 0,
Y = Pos.Bottom(label),
Width = Dim.Percent (50) - 1,
Height = Dim.Fill (),
AllowsMarking = false,
AllowsMultipleSelection = false,
ColorScheme = Colors.TopLevel
};
Top.Add (_listView);
System.Collections.Generic.List<string> items = new string [] {
"a",
"b",
"bb",
"c",
"ccc",
"ccc",
"cccc",
"ddd",
"dddd",
"dddd",
"ddddd",
"dddddd",
"ddddddd",
"this",
"this is a test",
"this was a test",
"this and",
"that and that",
"the",
"think",
"thunk",
"thunks",
"zip",
"zap",
"zoo",
"@jack",
"@sign",
"@at",
"@ateme",
"n@",
"n@brown",
".net",
"$100.00",
"$101.00",
"$101.10",
"$101.11",
"appricot",
"arm",
"丗丙业丞",
"丗丙丛",
"text",
"egg",
"candle",
" <- space",
"q",
"quit",
"quitter"
}.ToList<string> ();
items.Sort (StringComparer.OrdinalIgnoreCase);
_listView.SetSource (items);
}
TreeView<string> _treeView = null;
private void CreateTreeView ()
{
var label = new Label () {
Text = "TreeView",
TextAlignment = TextAlignment.Centered,
X = Pos.Right(_listView) + 2,
Y = 1, // for menu
Width = Dim.Percent (50),
Height = 1,
};
Top.Add (label);
_treeView = new TreeView<string> () {
X = Pos.Right (_listView) + 2,
Y = Pos.Bottom (label),
Width = Dim.Percent (50) - 1,
Height = Dim.Fill (),
ColorScheme = Colors.TopLevel
};
Top.Add (_treeView);
System.Collections.Generic.List<string> items = new string [] { "a",
"b",
"bb",
"c",
"ccc",
"ccc",
"cccc",
"ddd",
"dddd",
"dddd",
"ddddd",
"dddddd",
"ddddddd",
"this",
"this is a test",
"this was a test",
"this and",
"that and that",
"the",
"think",
"thunk",
"thunks",
"zip",
"zap",
"zoo",
"@jack",
"@sign",
"@at",
"@ateme",
"n@",
"n@brown",
".net",
"$100.00",
"$101.00",
"$101.10",
"$101.11",
"appricot",
"arm",
"丗丙业丞",
"丗丙丛",
"text",
"egg",
"candle",
" <- space",
"q",
"quit",
"quitter"
}.ToList<string> ();
items.Sort (StringComparer.OrdinalIgnoreCase);
_treeView.AddObjects (items);
}
private void Quit ()
{
Application.RequestStop ();
}
}
}

View File

@@ -1,13 +1,13 @@
using Terminal.Gui;
using Xunit;
namespace UnitTests {
namespace Terminal.Gui.Core {
public class SearchCollectionNavigatorTests {
[Fact]
public void TestSearchCollectionNavigator_Cycling ()
{
var s = new string []{
var strings = new string []{
"appricot",
"arm",
"bat",
@@ -15,12 +15,12 @@ namespace UnitTests {
"candle"
};
var n = new SearchCollectionNavigator ();
Assert.Equal (2, n.CalculateNewIndex (s, 0, 'b'));
Assert.Equal (3, n.CalculateNewIndex (s, 2, 'b'));
var n = new SearchCollectionNavigator (strings);
Assert.Equal (2, n.CalculateNewIndex ( 0, 'b'));
Assert.Equal (3, n.CalculateNewIndex ( 2, 'b'));
// if 4 (candle) is selected it should loop back to bat
Assert.Equal (2, n.CalculateNewIndex (s, 4, 'b'));
Assert.Equal (2, n.CalculateNewIndex ( 4, 'b'));
}
@@ -28,7 +28,7 @@ namespace UnitTests {
[Fact]
public void TestSearchCollectionNavigator_ToSearchText ()
{
var s = new string []{
var strings = new string []{
"appricot",
"arm",
"bat",
@@ -37,19 +37,19 @@ namespace UnitTests {
"candle"
};
var n = new SearchCollectionNavigator ();
Assert.Equal (2, n.CalculateNewIndex (s, 0, 'b'));
Assert.Equal (4, n.CalculateNewIndex (s, 2, 'b'));
var n = new SearchCollectionNavigator (strings);
Assert.Equal (2, n.CalculateNewIndex (0, 'b'));
Assert.Equal (4, n.CalculateNewIndex (2, 'b'));
// another 'b' means searching for "bbb" which does not exist
// so we go back to looking for "b" as a fresh key strike
Assert.Equal (4, n.CalculateNewIndex (s, 2, 'b'));
Assert.Equal (4, n.CalculateNewIndex (2, 'b'));
}
[Fact]
public void TestSearchCollectionNavigator_FullText ()
{
var s = new string []{
var strings = new string []{
"appricot",
"arm",
"ta",
@@ -59,23 +59,23 @@ namespace UnitTests {
"candle"
};
var n = new SearchCollectionNavigator ();
Assert.Equal (2, n.CalculateNewIndex (s, 0, 't'));
var n = new SearchCollectionNavigator (strings);
Assert.Equal (2, n.CalculateNewIndex (0, 't'));
// should match "te" in "text"
Assert.Equal (4, n.CalculateNewIndex (s, 2, 'e'));
Assert.Equal (4, n.CalculateNewIndex (2, 'e'));
// still matches text
Assert.Equal (4, n.CalculateNewIndex (s, 4, 'x'));
Assert.Equal (4, n.CalculateNewIndex (4, 'x'));
// nothing starts texa so it jumps to a for appricot
Assert.Equal (0, n.CalculateNewIndex (s, 4, 'a'));
Assert.Equal (0, n.CalculateNewIndex (4, 'a'));
}
[Fact]
public void TestSearchCollectionNavigator_Unicode ()
{
var s = new string []{
var strings = new string []{
"appricot",
"arm",
"ta",
@@ -86,27 +86,27 @@ namespace UnitTests {
"candle"
};
var n = new SearchCollectionNavigator ();
Assert.Equal (3, n.CalculateNewIndex (s, 0, '丗'));
var n = new SearchCollectionNavigator (strings);
Assert.Equal (3, n.CalculateNewIndex (0, '丗'));
// 丗丙业丞 is as good a match as 丗丙丛
// so when doing multi character searches we should
// prefer to stay on the same index unless we invalidate
// our typed text
Assert.Equal (3, n.CalculateNewIndex (s, 3, '丙'));
Assert.Equal (3, n.CalculateNewIndex (3, '丙'));
// No longer matches 丗丙业丞 and now only matches 丗丙丛
// so we should move to the new match
Assert.Equal (4, n.CalculateNewIndex (s, 3, '丛'));
Assert.Equal (4, n.CalculateNewIndex (3, '丛'));
// nothing starts "丗丙丛a" so it jumps to a for appricot
Assert.Equal (0, n.CalculateNewIndex (s, 4, 'a'));
Assert.Equal (0, n.CalculateNewIndex (4, 'a'));
}
[Fact]
public void TestSearchCollectionNavigator_AtSymbol ()
{
var s = new string []{
var strings = new string []{
"appricot",
"arm",
"ta",
@@ -117,10 +117,10 @@ namespace UnitTests {
"candle"
};
var n = new SearchCollectionNavigator ();
Assert.Equal (3, n.CalculateNewIndex (s, 0, '@'));
Assert.Equal (3, n.CalculateNewIndex (s, 3, 'b'));
Assert.Equal (4, n.CalculateNewIndex (s, 3, 'b'));
var n = new SearchCollectionNavigator (strings);
Assert.Equal (3, n.CalculateNewIndex (0, '@'));
Assert.Equal (3, n.CalculateNewIndex (3, 'b'));
Assert.Equal (4, n.CalculateNewIndex (3, 'b'));
}
}
}