diff --git a/Terminal.Gui/Core/Command.cs b/Terminal.Gui/Core/Command.cs
index 42f0d0f1e..9d106f664 100644
--- a/Terminal.Gui/Core/Command.cs
+++ b/Terminal.Gui/Core/Command.cs
@@ -10,54 +10,54 @@ namespace Terminal.Gui {
public enum Command {
///
- /// Moves the caret down one line.
+ /// Moves down one item (cell, line, etc...).
///
LineDown,
///
- /// Extends the selection down one line.
+ /// Extends the selection down one (cell, line, etc...).
///
LineDownExtend,
///
- /// 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.
///
LineDownToLastBranch,
///
- /// Scrolls down one line (without changing the selection).
+ /// Scrolls down one (cell, line, etc...) (without changing the selection).
///
ScrollDown,
// --------------------------------------------------------------------
///
- /// Moves the caret up one line.
+ /// Moves up one (cell, line, etc...).
///
LineUp,
///
- /// Extends the selection up one line.
+ /// Extends the selection up one item (cell, line, etc...).
///
LineUpExtend,
///
- /// 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.
///
LineUpToFirstBranch,
///
- /// Scrolls up one line (without changing the selection).
+ /// Scrolls up one item (cell, line, etc...) (without changing the selection).
///
ScrollUp,
///
- /// 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 e.g. single character, cell, item etc.
///
Left,
///
- /// Scrolls one character to the left
+ /// Scrolls one item (cell, character, etc...) to the left
///
ScrollLeft,
@@ -72,7 +72,7 @@ namespace Terminal.Gui {
Right,
///
- /// Scrolls one character to the right.
+ /// Scrolls one item (cell, character, etc...) to the right.
///
ScrollRight,
@@ -102,12 +102,12 @@ namespace Terminal.Gui {
WordRightExtend,
///
- /// 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.
///
CutToEndLine,
///
- /// 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.
///
CutToStartLine,
@@ -140,47 +140,47 @@ namespace Terminal.Gui {
DisableOverwrite,
///
- /// Move the page down.
+ /// Move one page down.
///
PageDown,
///
- /// Move the page down increase selection area to cover revealed objects/characters.
+ /// Move one page page extending the selection to cover revealed objects/characters.
///
PageDownExtend,
///
- /// Move the page up.
+ /// Move one page up.
///
PageUp,
///
- /// Move the page up increase selection area to cover revealed objects/characters.
+ /// Move one page up extending the selection to cover revealed objects/characters.
///
PageUpExtend,
///
- /// Moves to top begin.
+ /// Moves to the top/home.
///
TopHome,
///
- /// Extends the selection to the top begin.
+ /// Extends the selection to the top/home.
///
TopHomeExtend,
///
- /// Moves to bottom end.
+ /// Moves to the bottom/end.
///
BottomEnd,
///
- /// Extends the selection to the bottom end.
+ /// Extends the selection to the bottom/end.
///
BottomEndExtend,
///
- /// Open selected item.
+ /// Open the selected item.
///
OpenSelectedItem,
@@ -190,43 +190,43 @@ namespace Terminal.Gui {
ToggleChecked,
///
- /// Accepts the current state (e.g. selection, button press etc)
+ /// Accepts the current state (e.g. selection, button press etc).
///
Accept,
///
- /// 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).
///
ToggleExpandCollapse,
///
- /// Expands a list or item (with subitems)
+ /// Expands a list or item (with subitems).
///
Expand,
///
- /// Recursively Expands all child items and their child items (if any)
+ /// Recursively Expands all child items and their child items (if any).
///
ExpandAll,
///
- /// Collapses a list or item (with subitems)
+ /// Collapses a list or item (with subitems).
///
Collapse,
///
- /// Recursively collapses a list items of their children (if any)
+ /// Recursively collapses a list items of their children (if any).
///
CollapseAll,
///
- /// 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.
///
Cancel,
///
- /// Unix emulation
+ /// Unix emulation.
///
UnixEmulation,
@@ -241,12 +241,12 @@ namespace Terminal.Gui {
DeleteCharLeft,
///
- /// Selects all objects in the control.
+ /// Selects all objects.
///
SelectAll,
///
- /// Deletes all objects in the control.
+ /// Deletes all objects.
///
DeleteAll,
@@ -336,7 +336,7 @@ namespace Terminal.Gui {
Paste,
///
- /// Quit a toplevel.
+ /// Quit a .
///
QuitToplevel,
@@ -356,37 +356,37 @@ namespace Terminal.Gui {
PreviousView,
///
- /// Moves focus to the next view or toplevel (case of Mdi).
+ /// Moves focus to the next view or toplevel (case of MDI).
///
NextViewOrTop,
///
- /// Moves focus to the next previous or toplevel (case of Mdi).
+ /// Moves focus to the next previous or toplevel (case of MDI).
///
PreviousViewOrTop,
///
- /// Refresh the application.
+ /// Refresh.
///
Refresh,
///
- /// Toggles the extended selection.
+ /// Toggles the selection.
///
ToggleExtend,
///
- /// Inserts a new line.
+ /// Inserts a new item.
///
NewLine,
///
- /// Inserts a tab.
+ /// Tabs to the next item.
///
Tab,
///
- /// Inserts a shift tab.
+ /// Tabs back to the previous item.
///
BackTab
}
diff --git a/Terminal.Gui/Core/SearchCollectionNavigator.cs b/Terminal.Gui/Core/SearchCollectionNavigator.cs
index 47d62b661..9d113e256 100644
--- a/Terminal.Gui/Core/SearchCollectionNavigator.cs
+++ b/Terminal.Gui/Core/SearchCollectionNavigator.cs
@@ -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 Collection { get => _collection; set => _collection = value; }
- public int CalculateNewIndex (string [] collection, int currentIndex, char keyStruck)
+ private IEnumerable _collection;
+
+ public SearchCollectionNavigator (IEnumerable collection) { _collection = collection; }
+
+
+ public int CalculateNewIndex (IEnumerable 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 collection, int currentIndex, string search, bool preferNotToMoveToNewIndexes = false)
{
if (string.IsNullOrEmpty (search)) {
return -1;
diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs
index 61ed818c7..b58aa9490 100644
--- a/Terminal.Gui/Views/ListView.cs
+++ b/Terminal.Gui/Views/ListView.cs
@@ -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);
}
///
@@ -416,39 +421,30 @@ namespace Terminal.Gui {
///
public event Action RowRender;
- private string search { get; set; }
+ private SearchCollectionNavigator navigator;
///
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());
}
+ SelectedItem = navigator.CalculateNewIndex (SelectedItem, (char)kb.KeyValue);
+ EnsuresVisibilitySelectedItem ();
+ SetNeedsDisplay ();
+ return true;
}
return false;
diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json
index f890e66cf..1d9bae358 100644
--- a/UICatalog/Properties/launchSettings.json
+++ b/UICatalog/Properties/launchSettings.json
@@ -26,6 +26,10 @@
"Issue1719Repro": {
"commandName": "Project",
"commandLineArgs": "\"ProgressBar Styles\""
+ },
+ "SearchCollectionNavTester": {
+ "commandName": "Project",
+ "commandLineArgs": "\"Search Collection Nav\""
}
}
}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/SearchCollectionNavigatorTester.cs b/UICatalog/Scenarios/SearchCollectionNavigatorTester.cs
new file mode 100644
index 000000000..1e731dfd1
--- /dev/null
+++ b/UICatalog/Scenarios/SearchCollectionNavigatorTester.cs
@@ -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 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 ();
+ items.Sort (StringComparer.OrdinalIgnoreCase);
+ _listView.SetSource (items);
+ }
+
+ TreeView _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 () {
+ 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 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 ();
+ items.Sort (StringComparer.OrdinalIgnoreCase);
+ _treeView.AddObjects (items);
+ }
+ private void Quit ()
+ {
+ Application.RequestStop ();
+ }
+ }
+}
diff --git a/UnitTests/SearchCollectionNavigatorTests.cs b/UnitTests/SearchCollectionNavigatorTests.cs
index ac39b8864..eea4c76d0 100644
--- a/UnitTests/SearchCollectionNavigatorTests.cs
+++ b/UnitTests/SearchCollectionNavigatorTests.cs
@@ -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'));
}
}
}