// These classes use a key binding system based on the design implemented in Scintilla.Net which is an // MIT licensed open source project https://github.com/jacobslusser/ScintillaNET/blob/master/src/ScintillaNET/Command.cs using System; using System.Collections.Generic; using System.Linq; namespace Terminal.Gui; /// /// Defines the scope of a that has been bound to a key with . /// /// /// /// Key bindings are scoped to the most-focused view () by default. /// /// public enum KeyBindingScope { /// /// The key binding is scoped to just the view that has focus. /// Focused = 0, /// /// The key binding is scoped to the View's SuperView and will be triggered even when the View does not have focus, as long as the /// SuperView does have focus. This is typically used for s. /// /// /// Use for Views such as MenuBar and StatusBar which provide commands (shortcuts etc...) that trigger even when not focused. /// /// /// HotKey-scoped key bindings are only invoked if the key down event was not handled by the focused view or any of its subviews. /// /// /// HotKey, /// /// The key binding will be triggered regardless of which view has focus. This is typically used for global commands. /// /// /// Application-scoped key bindings are only invoked if the key down event was not handled by the focused view or any of its subviews, /// and if the key down event was not bound to a . /// Application } /// /// Provides a collection of objects that are scoped to . /// public class KeyBinding { /// /// Initializes a new instance. /// /// /// public KeyBinding (Command [] commands, KeyBindingScope scope) { Commands = commands; Scope = scope; } /// /// The actions which can be performed by the application or bound to keys in a control. /// public Command [] Commands { get; set; } /// /// The scope of the bound to a key. /// public KeyBindingScope Scope { get; set; } } /// /// A class that provides a collection of objects bound to a . /// public class KeyBindings { /// /// The collection of objects. /// public Dictionary Bindings { get; } = new (); /// /// Adds a to the collection. /// /// /// public void Add (Key key, KeyBinding binding) => Bindings.Add (key, binding); /// /// Removes a from the collection. /// /// public void Remove (Key key) => Bindings.Remove (key); /// /// Removes all objects from the collection. /// public void Clear () => Bindings.Clear (); /// /// /// Adds a new key combination that will trigger the commands in . /// /// /// If the key is already bound to a different array of s it will be /// rebound . /// /// /// Commands are only ever applied to the current (i.e. this feature /// cannot be used to switch focus to another view and perform multiple commands there). /// /// /// The key to check. /// /// The scope for the command. /// The command to invoked on the when is pressed. /// When multiple commands are provided,they will be applied in sequence. The bound strike /// will be consumed if any took effect. public void Add (Key key, KeyBindingScope scope, params Command [] commands) { if (commands.Length == 0) { throw new ArgumentException (@"At least one command must be specified", nameof (commands)); } if (key == null || !key.IsValid) { //throw new ArgumentException ("Invalid Key", nameof (commands)); return; } if (TryGet (key, out var _)) { Bindings [key] = new KeyBinding (commands, scope); } else { Bindings.Add (key, new KeyBinding (commands, scope)); } } /// /// /// Adds a new key combination that will trigger the commands in /// (if supported by the View - see ). /// /// /// This is a helper function for /// for scoped commands. /// /// /// If the key is already bound to a different array of s it will be /// rebound . /// /// /// Commands are only ever applied to the current (i.e. this feature /// cannot be used to switch focus to another view and perform multiple commands there). /// /// /// The key to check. /// /// The command to invoked on the when is pressed. /// When multiple commands are provided,they will be applied in sequence. The bound strike /// will be consumed if any took effect. public void Add (Key key, params Command [] commands) => Add (key, KeyBindingScope.Focused, commands); /// /// Replaces a key combination already bound to a set of s. /// /// /// /// The key to be replaced. /// The new key to be used. public void Replace (Key fromKey, Key toKey) { if (!TryGet (fromKey, out var _)) { return; } var value = Bindings [fromKey]; Bindings.Remove (fromKey); Bindings [toKey] = value; } /// /// Gets the commands bound with the specified Key. /// /// /// /// /// The key to check. /// /// /// When this method returns, contains the commands bound with the specified Key, if the Key is found; /// otherwise, null. This parameter is passed uninitialized. /// /// /// if the Key is bound; otherwise . /// public bool TryGet (Key key, out KeyBinding binding) { if (key.IsValid) { return Bindings.TryGetValue (key, out binding); } binding = new KeyBinding (Array.Empty (), KeyBindingScope.Focused); return false; } /// /// Gets the for the specified . /// /// /// public KeyBinding Get (Key key) => TryGet (key, out var binding) ? binding : null; /// /// Gets the commands bound with the specified Key that are scoped to a particular scope. /// /// /// /// /// The key to check. /// /// the scope to filter on /// /// When this method returns, contains the commands bound with the specified Key, if the Key is found; /// otherwise, null. This parameter is passed uninitialized. /// /// /// if the Key is bound; otherwise . /// public bool TryGet (Key key, KeyBindingScope scope, out KeyBinding binding) { if (key.IsValid && Bindings.TryGetValue (key, out binding)) { if (binding.Scope == scope) { return true; } } binding = new KeyBinding (Array.Empty (), KeyBindingScope.Focused); return false; } /// /// Gets the for the specified . /// /// /// /// public KeyBinding Get (Key key, KeyBindingScope scope) => TryGet (key, scope, out var binding) ? binding : null; /// /// Gets the array of s bound to if it exists. /// /// /// The key to check. /// /// The array of s if is bound. An empty array if not. public Command [] GetCommands (Key key) { if (TryGet (key, out var bindings)) { return bindings.Commands; } return Array.Empty (); } /// /// Removes all key bindings that trigger the given command set. Views can have multiple different /// keys bound to the same command sets and this method will clear all of them. /// /// public void Clear (params Command [] command) { foreach (var kvp in Bindings.Where (kvp => kvp.Value.Commands.SequenceEqual (command)).ToArray ()) { Bindings.Remove (kvp.Key); } } /// /// Gets the Key used by a set of commands. /// /// /// /// The set of commands to search. /// The used by a /// If no matching set of commands was found. public Key GetKeyFromCommands (params Command [] commands) { return Bindings.First (a => a.Value.Commands.SequenceEqual (commands)).Key; } }