// 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;
}
}