#nullable enable using System.Text.Json.Serialization; namespace Terminal.Gui; public static partial class Application // Keyboard handling { private static Key _alternateForwardKey = Key.Empty; // Defined in config.json /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))] public static Key AlternateForwardKey { get => _alternateForwardKey; set { if (_alternateForwardKey != value) { Key oldKey = _alternateForwardKey; _alternateForwardKey = value; OnAlternateForwardKeyChanged (new (oldKey, value)); } } } private static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) { // TODO: The fact Top has it's own AlternateForwardKey and events is needlessly complex. Remove it. foreach (Toplevel top in _topLevels.ToArray ()) { top.OnAlternateForwardKeyChanged (e); } } private static Key _alternateBackwardKey = Key.Empty; // Defined in config.json /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))] public static Key AlternateBackwardKey { get => _alternateBackwardKey; set { if (_alternateBackwardKey != value) { Key oldKey = _alternateBackwardKey; _alternateBackwardKey = value; OnAlternateBackwardKeyChanged (new (oldKey, value)); } } } private static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey) { // TODO: The fact Top has it's own AlternateBackwardKey and events is needlessly complex. Remove it. foreach (Toplevel top in _topLevels.ToArray ()) { top.OnAlternateBackwardKeyChanged (oldKey); } } private static Key _quitKey = Key.Empty; // Defined in config.json /// Gets or sets the key to quit the application. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))] public static Key QuitKey { get => _quitKey; set { if (_quitKey != value) { Key oldKey = _quitKey; _quitKey = value; OnQuitKeyChanged (new (oldKey, value)); } } } private static void OnQuitKeyChanged (KeyChangedEventArgs e) { // TODO: The fact Top has it's own QuitKey and events is needlessly complex. Remove it. // Duplicate the list so if it changes during enumeration we're safe foreach (Toplevel top in _topLevels.ToArray ()) { top.OnQuitKeyChanged (e); } } /// /// Event fired when the user presses a key. Fired by . /// /// Set to to indicate the key was handled and to prevent /// additional processing. /// /// /// /// All drivers support firing the event. Some drivers (Curses) do not support firing the /// and events. /// Fired after and before . /// public static event EventHandler KeyDown; /// /// Called by the when the user presses a key. Fires the event /// then calls on all top level views. Called after and /// before . /// /// Can be used to simulate key press events. /// /// if the key was handled. public static bool OnKeyDown (Key keyEvent) { if (!_initialized) { return true; } KeyDown?.Invoke (null, keyEvent); if (keyEvent.Handled) { return true; } foreach (Toplevel topLevel in _topLevels.ToList ()) { if (topLevel.NewKeyDownEvent (keyEvent)) { return true; } if (topLevel.Modal) { break; } } // Invoke any global (Application-scoped) KeyBindings. // The first view that handles the key will stop the loop. foreach (KeyValuePair> binding in _keyBindings.Where (b => b.Key == keyEvent.KeyCode)) { foreach (View view in binding.Value) { if (view is { } && view.KeyBindings.TryGet (binding.Key, KeyBindingScope.Focused | KeyBindingScope.HotKey | KeyBindingScope.Application, out KeyBinding kb)) { //bool? handled = view.InvokeCommands (kb.Commands, binding.Key, kb); bool? handled = view?.OnInvokingKeyBindings (keyEvent, kb.Scope); if (handled != null && (bool)handled) { return true; } } } } return false; } /// /// Event fired when the user releases a key. Fired by . /// /// Set to to indicate the key was handled and to prevent /// additional processing. /// /// /// /// All drivers support firing the event. Some drivers (Curses) do not support firing the /// and events. /// Fired after . /// public static event EventHandler KeyUp; /// /// Called by the when the user releases a key. Fires the event /// then calls on all top level views. Called after . /// /// Can be used to simulate key press events. /// /// if the key was handled. public static bool OnKeyUp (Key a) { if (!_initialized) { return true; } KeyUp?.Invoke (null, a); if (a.Handled) { return true; } foreach (Toplevel topLevel in _topLevels.ToList ()) { if (topLevel.NewKeyUpEvent (a)) { return true; } if (topLevel.Modal) { break; } } return false; } /// Gets the key bindings for this view. public static KeyBindings KeyBindings { get; internal set; } = new (); /// /// Commands for Application. /// private static Dictionary> CommandImplementations { get; } = new (); /// /// /// Sets the function that will be invoked for a . /// /// /// If AddCommand has already been called for will /// replace the old one. /// /// /// /// /// This version of AddCommand is for commands that do not require a . /// /// /// The command. /// The function. private static void AddCommand (Command command, Func f) { CommandImplementations [command] = ctx => f (); } ///// ///// The key bindings. ///// //private static readonly Dictionary> _keyBindings = new (); ///// ///// Gets the list of key bindings. ///// //public static Dictionary> GetKeyBindings () { return _keyBindings; } ///// ///// Adds an scoped key binding. ///// ///// ///// This is an internal method used by the class to add Application key bindings. ///// ///// The key being bound. ///// The view that is bound to the key. If , will be used. //internal static void AddKeyBinding (Key key, View? view) //{ // if (!_keyBindings.ContainsKey (key)) // { // _keyBindings [key] = []; // } // _keyBindings [key].Add (view); //} internal static void AddApplicationKeyBindings () { // Things this view knows how to do AddCommand ( Command.QuitToplevel, // TODO: IRunnable: Rename to Command.Quit to make more generic. () => { if (OverlappedTop is { }) { RequestStop (Current); } else { Application.RequestStop (); } return true; } ); AddCommand ( Command.Suspend, () => { Driver?.Suspend (); return true; } ); AddCommand ( Command.NextView, // TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) () => { Current.MoveNextView (); return true; } ); AddCommand ( Command.PreviousView,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) () => { Current.MovePreviousView (); return true; } ); AddCommand ( Command.NextViewOrTop,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) () => { Current.MoveNextViewOrTop (); return true; } ); AddCommand ( Command.PreviousViewOrTop,// TODO: Figure out how to move this to the View that is at the root of the view hierarchy (currently Application.Top) () => { Current.MovePreviousViewOrTop (); return true; } ); AddCommand ( Command.Refresh, () => { Refresh (); return true; } ); KeyBindings.Add (Application.QuitKey, KeyBindingScope.Application, Command.QuitToplevel); KeyBindings.Add (Key.CursorRight, KeyBindingScope.Application, Command.NextView); KeyBindings.Add (Key.CursorDown, KeyBindingScope.Application, Command.NextView); KeyBindings.Add (Key.CursorLeft, KeyBindingScope.Application, Command.PreviousView); KeyBindings.Add (Key.CursorUp, KeyBindingScope.Application, Command.PreviousView); KeyBindings.Add (Key.Tab, KeyBindingScope.Application, Command.NextView); KeyBindings.Add (Key.Tab.WithShift, KeyBindingScope.Application, Command.PreviousView); KeyBindings.Add (Key.Tab.WithCtrl, KeyBindingScope.Application, Command.NextViewOrTop); KeyBindings.Add (Key.Tab.WithShift.WithCtrl, KeyBindingScope.Application, Command.PreviousViewOrTop); // TODO: Refresh Key should be configurable KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh); KeyBindings.Add (Application.AlternateForwardKey, KeyBindingScope.Application, Command.NextViewOrTop); // Needed on Unix KeyBindings.Add (Application.AlternateBackwardKey, KeyBindingScope.Application, Command.PreviousViewOrTop); // Needed on Unix if (Environment.OSVersion.Platform == PlatformID.Unix) { KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend); } #if UNIX_KEY_BINDINGS KeyBindings.Add (Key.L.WithCtrl, Command.Refresh); // Unix KeyBindings.Add (Key.F.WithCtrl, Command.NextView); // Unix KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix KeyBindings.Add (Key.B.WithCtrl, Command.PreviousView); // Unix #endif } /// /// 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 GetViewKeyBindings () { // Get the list of views that do not have Application-scoped key bindings return KeyBindings.Bindings .Where (kv => kv.Value.Scope != KeyBindingScope.Application) .Select (kv => kv.Value) .Distinct () .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. /// /// /// This is an internal method used by the class to remove Application key bindings. /// /// The key that was bound. /// The view that is bound to the key. internal static void RemoveKeyBinding (Key key, View view) { if (_keyBindings.TryGetValue (key, out List views)) { views.Remove (view); if (views.Count == 0) { _keyBindings.Remove (key); } } } /// /// 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 (View view) { foreach (Key key in _keyBindings.Keys) { _keyBindings [key].Remove (view); } } /// /// Removes all scoped key bindings for the specified view. /// /// /// This is an internal method used by the class to remove Application key bindings. /// internal static void ClearKeyBindings () { _keyBindings.Clear (); } }