diff --git a/Terminal.Gui/Core/ShortCutHelper.cs b/Terminal.Gui/Core/ShortCutHelper.cs index 3c3abd8eb..3bb8c9931 100644 --- a/Terminal.Gui/Core/ShortCutHelper.cs +++ b/Terminal.Gui/Core/ShortCutHelper.cs @@ -29,6 +29,32 @@ namespace Terminal.Gui { /// public virtual ustring ShortCutTag => GetShortCutTag (shortCut); + /// + /// The action to run if the is defined. + /// + public virtual Action ShortCutAction { get; set; } + + /// + /// Gets the key with all the keys modifiers, especially the shift key that sometimes have to be injected later. + /// + /// The to check. + /// The with all the keys modifiers. + public static Key GetModifiersKey (KeyEvent kb) + { + var key = kb.Key; + if (kb.IsAlt && (key & Key.AltMask) == 0) { + key |= Key.AltMask; + } + if (kb.IsCtrl && (key & Key.CtrlMask) == 0) { + key |= Key.CtrlMask; + } + if (kb.IsShift && (key & Key.ShiftMask) == 0) { + key |= Key.ShiftMask; + } + + return key; + } + /// /// Get the key as string. /// @@ -202,5 +228,38 @@ namespace Terminal.Gui { } return false; } + + /// + /// Allows a view to run a if defined. + /// + /// The + /// The + /// true if defined falseotherwise. + public static bool FindAndOpenByShortCut (KeyEvent kb, View view = null) + { + if (view == null) { + return false; } + + var key = kb.KeyValue; + var keys = GetModifiersKey (kb); + key |= (int)keys; + foreach (var v in view.Subviews) { + if (v.ShortCut != Key.Null && v.ShortCut == (Key)key) { + var action = v.ShortCutAction; + if (action != null) { + Application.MainLoop.AddIdle (() => { + action (); + return false; + }); + } + return true; + } + if (FindAndOpenByShortCut (kb, v)) { + return true; + } + } + + return false; + } } } diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index 1d5dc207f..f6594e7fa 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -213,6 +213,19 @@ namespace Terminal.Gui { return false; } + /// + public override bool ProcessColdKey (KeyEvent keyEvent) + { + if (base.ProcessColdKey (keyEvent)) { + return true; + } + + if (ShortCutHelper.FindAndOpenByShortCut(keyEvent, this)) { + return true; + } + return false; + } + View GetDeepestFocusedSubview (View view) { if (view == null) { diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 97d8d4df8..0417b2c04 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -125,6 +125,8 @@ namespace Terminal.Gui { TextFormatter textFormatter; + ShortCutHelper shortCutHelper; + /// /// Event fired when a subview is being added to this view. /// @@ -170,6 +172,28 @@ namespace Terminal.Gui { /// public Rune HotKeySpecifier { get => textFormatter.HotKeySpecifier; set => textFormatter.HotKeySpecifier = value; } + /// + /// This is the global setting that can be used as a global shortcut to invoke an action if provided. + /// + public Key ShortCut { + get => shortCutHelper.ShortCut; + set { + if (shortCutHelper.ShortCut != value && (ShortCutHelper.PostShortCutValidation (value) || value == Key.Null)) { + shortCutHelper.ShortCut = value; + } + } + } + + /// + /// The keystroke combination used in the as string. + /// + public ustring ShortCutTag => ShortCutHelper.GetShortCutTag (shortCutHelper.ShortCut); + + /// + /// The action to run if the is defined. + /// + public virtual Action ShortCutAction { get; set; } + /// /// Gets or sets arbitrary data for the view. /// @@ -550,6 +574,8 @@ namespace Terminal.Gui { textFormatter = new TextFormatter (); this.Text = ustring.Empty; + shortCutHelper = new ShortCutHelper (); + this.Frame = frame; LayoutStyle = LayoutStyle.Absolute; } @@ -612,6 +638,8 @@ namespace Terminal.Gui { { textFormatter = new TextFormatter (); this.Text = text; + + shortCutHelper = new ShortCutHelper (); } /// @@ -633,6 +661,8 @@ namespace Terminal.Gui { textFormatter = new TextFormatter (); this.Text = text; + shortCutHelper = new ShortCutHelper (); + CanFocus = false; TabIndex = -1; TabStop = false; @@ -2055,26 +2085,5 @@ namespace Terminal.Gui { return true; } - - /// - /// Gets the key with all the keys modifiers, especially the shift key that sometimes have to be injected later. - /// - /// The to check. - /// The with all the keys modifiers. - public Key GetModifiersKey (KeyEvent kb) - { - var key = kb.Key; - if (kb.IsAlt && (key & Key.AltMask) == 0) { - key |= Key.AltMask; - } - if (kb.IsCtrl && (key & Key.CtrlMask) == 0) { - key |= Key.CtrlMask; - } - if (kb.IsShift && (key & Key.ShiftMask) == 0) { - key |= Key.ShiftMask; - } - - return key; - } } } diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index b6bfcb353..7bcde1e55 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -1328,14 +1328,8 @@ namespace Terminal.Gui { } var key = kb.KeyValue; - var keys = GetModifiersKey (kb); + var keys = ShortCutHelper.GetModifiersKey (kb); key |= (int)keys; - //if (kb.IsShift) { - // key |= (int)Key.ShiftMask; - //} - //if (kb.IsAlt) { - // key |= unchecked((int)Key.AltMask); - //} for (int i = 0; i < children.Length; i++) { var mi = children [i]; if (mi == null) { diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 9f084a919..f0d6645ef 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -345,7 +345,7 @@ namespace Terminal.Gui { // Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2 var oldCursorPos = point; - switch (GetModifiersKey (kb)) { + switch (ShortCutHelper.GetModifiersKey (kb)) { case Key.DeleteChar: case Key.D | Key.CtrlMask: if (ReadOnly) diff --git a/UICatalog/Scenarios/DynamicMenuBar.cs b/UICatalog/Scenarios/DynamicMenuBar.cs index 3c8d433da..96c45225f 100644 --- a/UICatalog/Scenarios/DynamicMenuBar.cs +++ b/UICatalog/Scenarios/DynamicMenuBar.cs @@ -725,7 +725,7 @@ namespace UICatalog { return; } - var k = GetModifiersKey (e.KeyEvent); + var k = ShortCutHelper.GetModifiersKey (e.KeyEvent); if (CheckShortCut (k, true)) { e.Handled = true; } @@ -764,7 +764,7 @@ namespace UICatalog { } _txtShortCut.KeyUp += (e) => { - var k = GetModifiersKey (e.KeyEvent); + var k = ShortCutHelper.GetModifiersKey (e.KeyEvent); if (CheckShortCut (k, false)) { e.Handled = true; } diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 288d1331c..e5c4a8f98 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -105,7 +105,7 @@ namespace UICatalog { _rightPane.SetFocus (); _top.Ready -= ReadyHandler; } - + _top.Ready += ReadyHandler; #if DEBUG_IDISPOSABLE @@ -114,7 +114,7 @@ namespace UICatalog { foreach (var inst in Responder.Instances) { Debug.Assert (inst.WasDisposed); } - Responder.Instances.Clear(); + Responder.Instances.Clear (); #endif } @@ -174,8 +174,10 @@ namespace UICatalog { Width = 25, Height = Dim.Fill (1), CanFocus = false, + ShortCut = Key.CtrlMask | Key.C }; - + _leftPane.Title = $"{_leftPane.Title} ({_leftPane.ShortCutTag})"; + _leftPane.ShortCutAction = () => _leftPane.SetFocus (); _categories = Scenario.GetAllCategories ().OrderBy (c => c).ToList (); _categoryListView = new ListView (_categories) { @@ -198,8 +200,10 @@ namespace UICatalog { Width = Dim.Fill (), Height = Dim.Fill (1), CanFocus = true, - + ShortCut = Key.CtrlMask | Key.S }; + _rightPane.Title = $"{_rightPane.Title} ({_rightPane.ShortCutTag})"; + _rightPane.ShortCutAction = () => _rightPane.SetFocus (); _nameColumnWidth = Scenario.ScenarioMetadata.GetName (_scenarios.OrderByDescending (t => Scenario.ScenarioMetadata.GetName (t).Length).FirstOrDefault ()).Length; @@ -222,7 +226,7 @@ namespace UICatalog { _numlock = new StatusItem (Key.CharMask, "Num", null); _scrolllock = new StatusItem (Key.CharMask, "Scroll", null); - _statusBar = new StatusBar () { + _statusBar = new StatusBar () { Visible = true, }; _statusBar.Items = new StatusItem [] {