Merge pull request #2 from tznind/listview_keyboard_search_tv

Add SearchCollectionNavigator to TreeView
This commit is contained in:
Tig
2022-10-30 11:43:29 -07:00
committed by GitHub
4 changed files with 78 additions and 24 deletions

View File

@@ -129,5 +129,17 @@ namespace Terminal.Gui {
lastKeystroke = DateTime.MinValue;
}
/// <summary>
/// Returns true if <paramref name="kb"/> is a searchable key
/// (e.g. letters, numbers etc) that is valid to pass to to this
/// class for search filtering
/// </summary>
/// <param name="kb"></param>
/// <returns></returns>
public static bool IsCompatibleKey (KeyEvent kb)
{
return !kb.IsAlt && !kb.IsCapslock && !kb.IsCtrl && !kb.IsScrolllock && !kb.IsNumlock;
}
}
}

View File

@@ -438,7 +438,7 @@ namespace Terminal.Gui {
}
// Enable user to find & select an item by typing text
if (!kb.IsAlt && !kb.IsCapslock && !kb.IsCtrl && !kb.IsScrolllock && !kb.IsNumlock) {
if (SearchCollectionNavigator.IsCompatibleKey(kb)) {
if (navigator == null) {
navigator = new SearchCollectionNavigator (source.ToList ().Cast<object> ());
}

View File

@@ -140,12 +140,12 @@ namespace Terminal.Gui {
/// <value></value>
public MouseFlags? ObjectActivationButton { get; set; } = MouseFlags.Button1DoubleClicked;
/// <summary>
/// Delegate for multi colored tree views. Return the <see cref="ColorScheme"/> to use
/// for each passed object or null to use the default.
/// </summary>
public Func<T,ColorScheme> ColorGetter {get;set;}
public Func<T, ColorScheme> ColorGetter { get; set; }
/// <summary>
/// Secondary selected regions of tree when <see cref="MultiSelect"/> is true
@@ -220,6 +220,7 @@ namespace Terminal.Gui {
public AspectGetterDelegate<T> AspectGetter { get; set; } = (o) => o.ToString () ?? "";
CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible;
private SearchCollectionNavigator searchCollectionNavigator;
/// <summary>
/// Get / Set the wished cursor when the tree is focused.
@@ -227,7 +228,7 @@ namespace Terminal.Gui {
/// Defaults to <see cref="CursorVisibility.Invisible"/>
/// </summary>
public CursorVisibility DesiredCursorVisibility {
get {
get {
return MultiSelect ? desiredCursorVisibility : CursorVisibility.Invisible;
}
set {
@@ -576,19 +577,44 @@ namespace Terminal.Gui {
return false;
}
// if it is a single character pressed without any control keys
if (keyEvent.KeyValue > 0 && keyEvent.KeyValue < 0xFFFF) {
try {
// First of all deal with any registered keybindings
var result = InvokeKeybindings (keyEvent);
if (result != null) {
return (bool)result;
}
// If not a keybinding, is the key a searchable key press?
if (SearchCollectionNavigator.IsCompatibleKey (keyEvent) && AllowLetterBasedNavigation) {
IReadOnlyCollection<Branch<T>> map;
// If there has been a call to InvalidateMap since the last time we allocated a
// SearchCollectionNavigator then we need a new one to reflect the new exposed
// tree state
if (cachedLineMap == null || searchCollectionNavigator == null) {
map = BuildLineMap ();
searchCollectionNavigator = new SearchCollectionNavigator (map.Select (b => AspectGetter (b.Model)).ToArray ());
}
else {
// we still need the map, handily its the cached one which means super fast access
map = BuildLineMap ();
}
// Find the current selected object within the tree
var current = map.IndexOf (b => b.Model == SelectedObject);
var newIndex = searchCollectionNavigator.CalculateNewIndex (current, (char)keyEvent.KeyValue);
if (newIndex != -1) {
SelectedObject = map.ElementAt (newIndex).Model;
EnsureVisible (selectedObject);
SetNeedsDisplay ();
}
if (char.IsLetterOrDigit ((char)keyEvent.KeyValue) && AllowLetterBasedNavigation && !keyEvent.IsShift && !keyEvent.IsAlt && !keyEvent.IsCtrl) {
AdjustSelectionToNextItemBeginningWith ((char)keyEvent.KeyValue);
return true;
}
}
try {
var result = InvokeKeybindings (keyEvent);
if (result != null)
return (bool)result;
} finally {
PositionCursor ();
@@ -626,7 +652,7 @@ namespace Terminal.Gui {
/// </summary>
/// <param name="toFind"></param>
/// <returns></returns>
public int? GetObjectRow(T toFind)
public int? GetObjectRow (T toFind)
{
var idx = BuildLineMap ().IndexOf (o => o.Model.Equals (toFind));

View File

@@ -3,25 +3,41 @@ using Xunit;
namespace Terminal.Gui.Core {
public class SearchCollectionNavigatorTests {
static string [] simpleStrings = new string []{
"appricot", // 0
"arm", // 1
"bat", // 2
"batman", // 3
"candle" // 4
};
[Fact]
public void TestSearchCollectionNavigator_ShouldAcceptNegativeOne ()
{
var n = new SearchCollectionNavigator (simpleStrings);
// Expect that index of -1 (i.e. no selection) should work correctly
// and select the first entry of the letter 'b'
Assert.Equal (2, n.CalculateNewIndex (-1, 'b'));
}
[Fact]
public void TestSearchCollectionNavigator_OutOfBoundsShouldBeIgnored()
{
var n = new SearchCollectionNavigator (simpleStrings);
// Expect saying that index 500 is the current selection should not cause
// error and just be ignored (treated as no selection)
Assert.Equal (2, n.CalculateNewIndex (500, 'b'));
}
[Fact]
public void TestSearchCollectionNavigator_Cycling ()
{
var strings = new string []{
"appricot",
"arm",
"bat",
"batman",
"candle"
};
var n = new SearchCollectionNavigator (strings);
var n = new SearchCollectionNavigator (simpleStrings);
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 ( 4, 'b'));
}