From 904f04df0e001b1ef6067180ab7c99dc4d3a2940 Mon Sep 17 00:00:00 2001 From: Fabian R Date: Sat, 23 May 2020 20:30:46 -0500 Subject: [PATCH 1/2] Removed redundant properties on MenuBar View -- Redundant members isMenuClosed and MenuOpen were replaced with a more consistent and intuitive single IsMenuOpen property. - CloseMenu method is now Public. - StartMenu has been renamed to OpenMenu and made Public. + Added missing XmlDoc to OpenMenu and CloseMenu, and fixed an small typo to MenuOpen/IsMenuOpen This commit breaks compatibility with previous versions. Changes required to host apps: MenuBar.MenuOpen property renamed to IsMenuOpen MenuBar.StartMenu method renamed to OpenMenu, use it to open the Menu Use MenuBar.CloseMenu to close the active menu. --- Terminal.Gui/Views/Menu.cs | 61 +++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 0a920a8ed..7795cd0a4 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -320,7 +320,7 @@ namespace Terminal.Gui { public override void PositionCursor () { - if (host == null || !host.isMenuClosed) + if (host == null || host.IsMenuOpen) if (barItems.IsTopLevel) { host.PositionCursor (); } else @@ -403,11 +403,11 @@ namespace Terminal.Gui { var item = barItems.Children [current]; if (item == null || !item.IsEnabled ()) disabled = true; if (host.UseKeysUpDownAsKeysLeftRight && barItems.Children [current]?.SubMenu != null && - !disabled && !host.isMenuClosed) { + !disabled && host.IsMenuOpen) { CheckSubMenu (); break; } - if (host.isMenuClosed) + if (!host.IsMenuOpen) host.OpenMenu (host.selected); } while (barItems.Children [current] == null || disabled); SetNeedsDisplay (); @@ -563,7 +563,7 @@ namespace Terminal.Gui { selectedSub = -1; ColorScheme = Colors.Menu; WantMousePositionReports = true; - isMenuClosed = true; + IsMenuOpen = false; } bool openedByAltKey; @@ -584,14 +584,14 @@ namespace Terminal.Gui { { if (keyEvent.IsAlt) { // User pressed Alt - this may be a precursor to a menu accelerator (e.g. Alt-F) - if (!keyEvent.IsCtrl && openedByAltKey && isMenuClosed && openMenu == null && ((uint)keyEvent.Key & (uint)Key.CharMask) == 0) { + if (!keyEvent.IsCtrl && openedByAltKey && !IsMenuOpen && openMenu == null && ((uint)keyEvent.Key & (uint)Key.CharMask) == 0) { // There's no open menu, the first menu item should be highlight. // The right way to do this is to SetFocus(MenuBar), but for some reason // that faults. //Activate (0); //StartMenu (); - isMenuClosed = false; + IsMenuOpen = true; selected = 0; CanFocus = true; lastFocused = SuperView.MostFocused; @@ -606,7 +606,7 @@ namespace Terminal.Gui { if (openMenu != null) CloseAllMenus (); openedByAltKey = false; - isMenuClosed = true; + IsMenuOpen = false; selected = -1; CanFocus = false; if (lastFocused != null) @@ -658,13 +658,13 @@ namespace Terminal.Gui { for (int i = 0; i < Menus.Length; i++) { if (i == selected) { pos++; - if (!isMenuClosed) + if (IsMenuOpen) Move (pos + 1, 0); else Move (pos + 1, 0); return; } else { - if (!isMenuClosed) + if (IsMenuOpen) pos += 1 + Menus [i].TitleLength + 2; else pos += 2 + Menus [i].TitleLength + 1; @@ -695,12 +695,11 @@ namespace Terminal.Gui { View previousFocused; internal bool isMenuOpening; internal bool isMenuClosing; - internal bool isMenuClosed; /// - /// True of the menu is open; otherwise false. + /// True if the menu is open; otherwise false. /// - public bool MenuOpen { get; set; } + public bool IsMenuOpen { get; protected set; } View lastFocused; @@ -748,12 +747,13 @@ namespace Terminal.Gui { break; } isMenuOpening = false; - isMenuClosed = false; - MenuOpen = true; + IsMenuOpen = true; } - // Starts the menu from a hotkey - void StartMenu () + /// + /// Opens the current Menu programatically. + /// + public void OpenMenu () { if (openMenu != null) return; @@ -778,6 +778,13 @@ namespace Terminal.Gui { SetNeedsDisplay (); } + /// + /// Closes the current Menu programatically, if open. + /// + public void CloseMenu () + { + CloseMenu (false, false); + } internal void CloseMenu (bool reopen = false, bool isSubMenu = false) { isMenuClosing = true; @@ -799,12 +806,12 @@ namespace Terminal.Gui { if (!reopen) selected = -1; LastFocused.SuperView?.SetFocus (LastFocused); + IsMenuOpen = false; } else { SuperView.SetFocus (this); - isMenuClosed = true; + IsMenuOpen = false; PositionCursor (); } - isMenuClosed = true; break; case true: @@ -816,7 +823,7 @@ namespace Terminal.Gui { break; } isMenuClosing = false; - MenuOpen = false; + IsMenuOpen = false; } void RemoveSubMenu (int index) @@ -879,7 +886,7 @@ namespace Terminal.Gui { if (LastFocused != null && LastFocused != this) selected = -1; } - isMenuClosed = true; + IsMenuOpen = false; openedByHotKey = false; openedByAltKey = false; } @@ -993,8 +1000,8 @@ namespace Terminal.Gui { public override bool ProcessHotKey (KeyEvent kb) { if (kb.Key == Key.F9) { - if (isMenuClosed) - StartMenu (); + if (!IsMenuOpen) + OpenMenu (); else CloseAllMenus (); return true; @@ -1085,7 +1092,7 @@ namespace Terminal.Gui { for (int i = 0; i < Menus.Length; i++) { if (cx > pos && me.X < pos + 1 + Menus [i].TitleLength) { if (selected == i && (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) && - !isMenuClosed) { + IsMenuOpen) { Application.UngrabMouse (); if (Menus [i].IsTopLevel) { var menu = new Menu (this, i, 0, Menus [i]); @@ -1094,7 +1101,7 @@ namespace Terminal.Gui { CloseMenu (); } } else if ((me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) && - isMenuClosed) { + !IsMenuOpen) { if (Menus [i].IsTopLevel) { var menu = new Menu (this, i, 0, Menus [i]); menu.Run (Menus [i].Action); @@ -1102,12 +1109,12 @@ namespace Terminal.Gui { Activate (i); } } else if (selected != i && selected > -1 && me.Flags == MouseFlags.ReportMousePosition) { - if (!isMenuClosed) { + if (IsMenuOpen) { CloseMenu (); Activate (i); } } else { - if (!isMenuClosed) + if (IsMenuOpen) Activate (i); } return true; @@ -1139,7 +1146,7 @@ namespace Terminal.Gui { handled = false; return false; } - } else if (isMenuClosed && (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || + } else if (!IsMenuOpen && (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) { Application.GrabMouse (current); } else { From 46b4c9025b645db79d02c8713a38be8143be4069 Mon Sep 17 00:00:00 2001 From: Fabian R Date: Sun, 24 May 2020 06:36:42 -0500 Subject: [PATCH 2/2] Improved View Key event handling + Added Handled property of type bool to the KeyEventEventArgs class. + Added ability to stop further propagation for already handled events on Views for Keyboard related event subscribers (like KeyDown, KeyUp and KeyPress). The driver will check the Handled property of the KeyEventEventArgs passed to the subscribers and will stop any further invocations when its found true. + Updated Example project to expose the ability to programatically Open/Close a MenuBar from custom keystrokes. This commit fixes an issue where the library would crash if the Subviews collection of the currently active View gets updated from inside any of the Keyboard event handlers, such as when the view is updated as a result of a custom Hotkey/Coldkey press. --- Example/demo.cs | 15 ++++++++++++++- Terminal.Gui/Core.cs | 31 ++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/Example/demo.cs b/Example/demo.cs index 53974b3c0..95a5726e6 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -190,7 +190,7 @@ static class Demo { new DateField (3, 22, DateTime.Now), new DateField (23, 22, DateTime.Now, true), progress, - new Label (3, 24, "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar"), + new Label (3, 24, "Press F9 (on Unix, ESC+9 is an alias) or Ctrl+T to activate the menubar"), menuKeysStyle, menuAutoMouseNav @@ -636,10 +636,23 @@ static class Demo { }; #endif + win.KeyPress += Win_KeyPress; + top.Add (win); //top.Add (menu); top.Add (menu, statusBar); Application.Run (); } + + private static void Win_KeyPress (object sender, View.KeyEventEventArgs e) + { + if (e.KeyEvent.Key == Key.ControlT) { + if (menu.IsMenuOpen) + menu.CloseMenu (); + else + menu.OpenMenu (); + e.Handled = true; + } + } } diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 3e0032e14..55330a50c 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -1087,6 +1087,11 @@ namespace Terminal.Gui { /// The for the event. /// public KeyEvent KeyEvent { get; set; } + /// + /// Indicates if the current Key event has already been processed and the driver should stop notifying any other event subscriber. + /// Its important to set this value to true specially when updating any View's layout from inside the subscriber method. + /// + public bool Handled { get; set; } = false; } /// @@ -1097,7 +1102,11 @@ namespace Terminal.Gui { /// public override bool ProcessKey (KeyEvent keyEvent) { - KeyPress?.Invoke (this, new KeyEventEventArgs(keyEvent)); + + KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); + KeyPress?.Invoke (this, args); + if (args.Handled) + return true; if (Focused?.ProcessKey (keyEvent) == true) return true; @@ -1107,7 +1116,10 @@ namespace Terminal.Gui { /// public override bool ProcessHotKey (KeyEvent keyEvent) { - KeyPress?.Invoke (this, new KeyEventEventArgs (keyEvent)); + KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); + KeyPress?.Invoke (this, args); + if (args.Handled) + return true; if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) @@ -1119,7 +1131,10 @@ namespace Terminal.Gui { /// public override bool ProcessColdKey (KeyEvent keyEvent) { - KeyPress?.Invoke (this, new KeyEventEventArgs(keyEvent)); + KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); + KeyPress?.Invoke (this, args); + if (args.Handled) + return true; if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) @@ -1136,7 +1151,10 @@ namespace Terminal.Gui { /// Contains the details about the key that produced the event. public override bool OnKeyDown (KeyEvent keyEvent) { - KeyDown?.Invoke (this, new KeyEventEventArgs (keyEvent)); + KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); + KeyDown?.Invoke (this, args); + if (args.Handled) + return true; if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) @@ -1154,7 +1172,10 @@ namespace Terminal.Gui { /// Contains the details about the key that produced the event. public override bool OnKeyUp (KeyEvent keyEvent) { - KeyUp?.Invoke (this, new KeyEventEventArgs (keyEvent)); + KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); + KeyUp?.Invoke (this, args); + if (args.Handled) + return true; if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews)