diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 1501e5c14..8e414969c 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -157,6 +157,7 @@ public static partial class Application KeyDown = null; KeyUp = null; SizeChanging = null; + ClearKeyBindings (); Colors.Reset (); @@ -1948,6 +1949,32 @@ public static partial class Application _keyBindings [key].Add (view); } + /// + /// Gets the list of Views that have key bindings. + /// + /// + /// This is an internal method used by the class to add Application key bindings. + /// + /// The list of Views that have Application-scoped key bindings. + internal static List GetViewsWithKeyBindings () + { + return _keyBindings.Values.SelectMany (v => v).ToList (); + } + + /// + /// Gets the list of Views that have key bindings for the specified key. + /// + /// + /// This is an internal method used by the class to add Application key bindings. + /// + /// The key to check. + /// Outputs the list of views bound to + /// if successful. + internal static bool TryGetKeyBindings (Key key, out List views) + { + return _keyBindings.TryGetValue (key, out views); + } + /// /// Removes an scoped key binding. /// @@ -1958,9 +1985,14 @@ public static partial class Application /// The view that is bound to the key. internal static void RemoveKeyBinding (Key key, View view) { - if (_keyBindings.TryGetValue (key, out List binding)) + if (_keyBindings.TryGetValue (key, out List views)) { - binding.Remove (view); + views.Remove (view); + + if (views.Count == 0) + { + _keyBindings.Remove (key); + } } } @@ -1971,7 +2003,7 @@ public static partial class Application /// This is an internal method used by the class to remove Application key bindings. /// /// The view that is bound to the key. - internal static void RemoveAllKeyBindings (View view) + internal static void ClearKeyBindings (View view) { foreach (Key key in _keyBindings.Keys) { @@ -1979,5 +2011,17 @@ public static partial class Application } } + /// + /// Removes all scoped key bindings for the specified view. + /// + /// + /// This is an internal method used by the class to remove Application key bindings. + /// + /// The view that is bound to the key. + internal static void ClearKeyBindings () + { + _keyBindings.Clear (); + } + #endregion Keyboard handling } diff --git a/Terminal.Gui/Input/KeyBindings.cs b/Terminal.Gui/Input/KeyBindings.cs index 5285e3c55..bb487773f 100644 --- a/Terminal.Gui/Input/KeyBindings.cs +++ b/Terminal.Gui/Input/KeyBindings.cs @@ -81,10 +81,6 @@ public class KeyBindings else { Add (key, new KeyBinding (commands, scope)); - if (scope.FastHasFlags (KeyBindingScope.Application)) - { - Application.AddKeyBinding (key, BoundView); - } } } @@ -120,7 +116,7 @@ public class KeyBindings /// Removes all objects from the collection. public void Clear () { - Application.RemoveAllKeyBindings (BoundView); + Application.ClearKeyBindings (BoundView); Bindings.Clear (); } diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index 1910df0be..498842437 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -193,6 +193,9 @@ public class ApplicationTests Assert.Empty (Application._topLevels); Assert.Null (Application._mouseEnteredView); + // Keyboard + Assert.Empty (Application.GetViewsWithKeyBindings ()); + // Events - Can't check //Assert.Null (Application.NotifyNewRunState); //Assert.Null (Application.NotifyNewRunState); @@ -225,6 +228,7 @@ public class ApplicationTests Application.AlternateBackwardKey = Key.A; Application.AlternateForwardKey = Key.B; Application.QuitKey = Key.C; + Application.AddKeyBinding(Key.A, new View ()); //Application.OverlappedChildren = new List (); //Application.OverlappedTop = diff --git a/UnitTests/Application/KeyboardTests.cs b/UnitTests/Application/KeyboardTests.cs index 8e4b0167e..a293de45a 100644 --- a/UnitTests/Application/KeyboardTests.cs +++ b/UnitTests/Application/KeyboardTests.cs @@ -2,6 +2,9 @@ namespace Terminal.Gui.ApplicationTests; +/// +/// Application tests for keyboard support. +/// public class KeyboardTests { private readonly ITestOutputHelper _output; @@ -15,6 +18,46 @@ public class KeyboardTests #endif } + + [Fact] + [AutoInitShutdown] + public void QuitKey_Getter_Setter () + { + Toplevel top = new (); + var isQuiting = false; + + top.Closing += (s, e) => + { + isQuiting = true; + e.Cancel = true; + }; + + Application.Begin (top); + top.Running = true; + + Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode); + Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); + Assert.True (isQuiting); + + isQuiting = false; + Application.OnKeyDown (Key.Q.WithCtrl); + Assert.True (isQuiting); + + isQuiting = false; + Application.QuitKey = Key.C.WithCtrl; + Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); + Assert.False (isQuiting); + Application.OnKeyDown (Key.Q.WithCtrl); + Assert.False (isQuiting); + + Application.OnKeyDown (Application.QuitKey); + Assert.True (isQuiting); + + // Reset the QuitKey to avoid throws errors on another tests + Application.QuitKey = Key.Q.WithCtrl; + top.Dispose (); + } + [Fact] public void AlternateForwardKey_AlternateBackwardKey_Tests () { @@ -320,7 +363,7 @@ public class KeyboardTests [Fact] [AutoInitShutdown] - public void OnKeyDown_Application_KeyBinding () + public void KeyBinding_OnKeyDown () { var view = new ScopedKeyBindingView (); var invoked = false; @@ -365,7 +408,7 @@ public class KeyboardTests [Fact] [AutoInitShutdown] - public void OnKeyDown_Application_KeyBinding_Negative () + public void KeyBinding_OnKeyDown_Negative () { var view = new ScopedKeyBindingView (); var invoked = false; @@ -391,46 +434,73 @@ public class KeyboardTests top.Dispose (); } + [Fact] [AutoInitShutdown] - public void QuitKey_Getter_Setter () + public void KeyBinding_AddKeyBinding_Adds () { - Toplevel top = new (); - var isQuiting = false; + View view1 = new (); + Application.AddKeyBinding (Key.A, view1); - top.Closing += (s, e) => - { - isQuiting = true; - e.Cancel = true; - }; + View view2 = new (); + Application.AddKeyBinding (Key.A, view2); - Application.Begin (top); - top.Running = true; + Assert.True (Application.TryGetKeyBindings (Key.A, out List views)); + Assert.Contains (view1, views); + Assert.Contains (view2, views); - Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode); - Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); - Assert.True (isQuiting); - - isQuiting = false; - Application.OnKeyDown (Key.Q.WithCtrl); - Assert.True (isQuiting); - - isQuiting = false; - Application.QuitKey = Key.C.WithCtrl; - Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true); - Assert.False (isQuiting); - Application.OnKeyDown (Key.Q.WithCtrl); - Assert.False (isQuiting); - - Application.OnKeyDown (Application.QuitKey); - Assert.True (isQuiting); - - // Reset the QuitKey to avoid throws errors on another tests - Application.QuitKey = Key.Q.WithCtrl; - top.Dispose (); + Assert.False (Application.TryGetKeyBindings (Key.B, out List _)); } - // test Application key Bindings + [Fact] + [AutoInitShutdown] + public void KeyBinding_ViewKeyBindings_Add_Adds () + { + View view1 = new (); + view1.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save); + view1.KeyBindings.Add (Key.B, KeyBindingScope.HotKey, Command.Left); + Assert.Single (Application.GetViewsWithKeyBindings ()); + + View view2 = new (); + view2.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save); + view2.KeyBindings.Add (Key.B, KeyBindingScope.HotKey, Command.Left); + + Assert.True (Application.TryGetKeyBindings (Key.A, out List views)); + Assert.Contains (view1, views); + Assert.Contains (view2, views); + + Assert.False (Application.TryGetKeyBindings (Key.B, out List _)); + } + + [Fact] + [AutoInitShutdown] + public void KeyBinding_RemoveKeyBinding_Removes () + { + View view1 = new (); + Application.AddKeyBinding (Key.A, view1); + + Assert.True (Application.TryGetKeyBindings (Key.A, out List views)); + Assert.Contains (view1, views); + + Application.RemoveKeyBinding (Key.A, view1); + Assert.False (Application.TryGetKeyBindings (Key.A, out List _)); + } + + [Fact] + [AutoInitShutdown] + public void KeyBinding_ViewKeyBindings_RemoveKeyBinding_Removes () + { + View view1 = new (); + view1.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save); + + Assert.True (Application.TryGetKeyBindings (Key.A, out List views)); + Assert.Contains (view1, views); + + view1.KeyBindings.Remove (Key.A); + Assert.False (Application.TryGetKeyBindings (Key.A, out List _)); + } + + // Test View for testing Application key Bindings public class ScopedKeyBindingView : View { public ScopedKeyBindingView ()