// 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
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); }
///
/// 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 KeyBinding _))
{
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); }
/// Removes all objects from the collection.
public void Clear () { Bindings.Clear (); }
///
/// 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 (KeyValuePair kvp in Bindings.Where (kvp => kvp.Value.Commands.SequenceEqual (command))
.ToArray ())
{
Bindings.Remove (kvp.Key);
}
}
/// Gets the for the specified .
///
///
public KeyBinding Get (Key key) { return TryGet (key, out KeyBinding binding) ? binding : null; }
/// Gets the for the specified .
///
///
///
public KeyBinding Get (Key key, KeyBindingScope scope) { return TryGet (key, scope, out KeyBinding 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 KeyBinding bindings))
{
return bindings.Commands;
}
return Array.Empty ();
}
/// 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; }
/// Removes a from the collection.
///
public void Remove (Key key) { Bindings.Remove (key); }
/// 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 KeyBinding _))
{
return;
}
KeyBinding 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 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;
}
}