#nullable enable namespace Terminal.Gui; public static partial class Application // Keyboard handling { /// /// Called when the user presses a key (by the ). Raises the cancelable /// event, then calls on all top level views, and finally /// if the key was not handled, invokes any Application-scoped . /// /// Can be used to simulate key press events. /// /// if the key was handled. public static bool RaiseKeyDownEvent (Key key) { KeyDown?.Invoke (null, key); if (key.Handled) { return true; } if (Top is null) { foreach (Toplevel topLevel in TopLevels.ToList ()) { if (topLevel.NewKeyDownEvent (key)) { return true; } if (topLevel.Modal) { break; } } } else { if (Top.NewKeyDownEvent (key)) { return true; } } // Invoke any Application-scoped KeyBindings. // The first view that handles the key will stop the loop. // foreach (KeyValuePair binding in KeyBindings.GetBindings (key)) if (KeyBindings.TryGet (key, out KeyBinding binding)) { if (binding.Target is { }) { if (!binding.Target.Enabled) { return false; } bool? handled = binding.Target?.InvokeCommands (binding.Commands, binding); if (handled != null && (bool)handled) { return true; } } else { // BUGBUG: this seems unneeded. if (!KeyBindings.TryGet (key, out KeyBinding keybinding)) { return false; } bool? toReturn = null; foreach (Command command in keybinding.Commands) { toReturn = InvokeCommand (command, key, keybinding); } return toReturn ?? true; } } return false; static bool? InvokeCommand (Command command, Key key, KeyBinding binding) { if (!_commandImplementations!.ContainsKey (command)) { throw new NotSupportedException ( @$"A KeyBinding was set up for the command {command} ({key}) but that command is not supported by Application." ); } if (_commandImplementations.TryGetValue (command, out View.CommandImplementation? implementation)) { CommandContext context = new (command, binding); // Create the context here return implementation (context); } return false; } } /// /// Raised when the user presses a key. /// /// 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 when the user releases a key (by the ). Raises the cancelable /// /// event /// then calls on all top level views. Called after . /// /// Can be used to simulate key release events. /// /// if the key was handled. public static bool RaiseKeyUpEvent (Key key) { if (!Initialized) { return true; } KeyUp?.Invoke (null, key); if (key.Handled) { return true; } foreach (Toplevel topLevel in TopLevels.ToList ()) { if (topLevel.NewKeyUpEvent (key)) { return true; } if (topLevel.Modal) { break; } } return false; } #region Application-scoped KeyBindings static Application () { AddKeyBindings (); } /// Gets the Application-scoped key bindings. public static KeyBindings KeyBindings { get; internal set; } = new (null); internal static void AddKeyBindings () { _commandImplementations.Clear (); // Things this view knows how to do AddCommand ( Command.Quit, static () => { RequestStop (); return true; } ); AddCommand ( Command.Suspend, static () => { Driver?.Suspend (); return true; } ); AddCommand ( Command.NextTabStop, static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop)); AddCommand ( Command.PreviousTabStop, static () => Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop)); AddCommand ( Command.NextTabGroup, static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup)); AddCommand ( Command.PreviousTabGroup, static () => Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup)); AddCommand ( Command.Refresh, static () => { LayoutAndDraw (true); return true; } ); AddCommand ( Command.Edit, static () => { View? viewToArrange = Navigation?.GetFocused (); // Go up the superview hierarchy and find the first that is not ViewArrangement.Fixed while (viewToArrange is { SuperView: { }, Arrangement: ViewArrangement.Fixed }) { viewToArrange = viewToArrange.SuperView; } if (viewToArrange is { }) { return viewToArrange.Border?.EnterArrangeMode (ViewArrangement.Fixed); } return false; }); // Resources/config.json overrides QuitKey = Key.Esc; NextTabKey = Key.Tab; PrevTabKey = Key.Tab.WithShift; NextTabGroupKey = Key.F6; PrevTabGroupKey = Key.F6.WithShift; ArrangeKey = Key.F5.WithCtrl; // Need to clear after setting the above to ensure actually clear // because set_QuitKey etc.. may call Add KeyBindings.Clear (); KeyBindings.Add (QuitKey, Command.Quit); KeyBindings.Add (NextTabKey, Command.NextTabStop); KeyBindings.Add (PrevTabKey, Command.PreviousTabStop); KeyBindings.Add (NextTabGroupKey, Command.NextTabGroup); KeyBindings.Add (PrevTabGroupKey, Command.PreviousTabGroup); KeyBindings.Add (ArrangeKey, Command.Edit); KeyBindings.Add (Key.CursorRight, Command.NextTabStop); KeyBindings.Add (Key.CursorDown, Command.NextTabStop); KeyBindings.Add (Key.CursorLeft, Command.PreviousTabStop); KeyBindings.Add (Key.CursorUp, Command.PreviousTabStop); // TODO: Refresh Key should be configurable KeyBindings.Add (Key.F5, Command.Refresh); // TODO: Suspend Key should be configurable if (Environment.OSVersion.Platform == PlatformID.Unix) { KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend); } } #endregion Application-scoped KeyBindings /// /// /// 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 (); } /// /// Commands for Application. /// private static readonly Dictionary _commandImplementations = new (); }