Ver código fonte

Merge pull request #2 from tznind/listview_keyboard_search_tv

Add SearchCollectionNavigator to TreeView
Tig 2 anos atrás
pai
commit
938ea1640a

+ 12 - 0
Terminal.Gui/Core/SearchCollectionNavigator.cs

@@ -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;
+		}
 	}
 }

+ 1 - 1
Terminal.Gui/Views/ListView.cs

@@ -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> ());
 				}

+ 39 - 13
Terminal.Gui/Views/TreeView.cs

@@ -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));
 

+ 26 - 10
UnitTests/SearchCollectionNavigatorTests.cs

@@ -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'));
-
 		}