diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index 48170eb22b..21e3df5ab7 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -9,7 +9,6 @@ public static partial class Application // Keyboard handling /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - [JsonConverter (typeof (KeyJsonConverter))] public static Key NextTabKey { get => _nextTabKey; @@ -27,7 +26,6 @@ public static Key NextTabKey /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - [JsonConverter (typeof (KeyJsonConverter))] public static Key PrevTabKey { get => _prevTabKey; @@ -45,7 +43,6 @@ public static Key PrevTabKey /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - [JsonConverter (typeof (KeyJsonConverter))] public static Key NextTabGroupKey { get => _nextTabGroupKey; @@ -63,7 +60,6 @@ public static Key NextTabGroupKey /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - [JsonConverter (typeof (KeyJsonConverter))] public static Key PrevTabGroupKey { get => _prevTabGroupKey; @@ -81,7 +77,6 @@ public static Key PrevTabGroupKey /// Gets or sets the key to quit the application. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] - [JsonConverter (typeof (KeyJsonConverter))] public static Key QuitKey { get => _quitKey; diff --git a/Terminal.Gui/Input/Key.cs b/Terminal.Gui/Input/Key.cs index d2972bc04b..5c92cc4898 100644 --- a/Terminal.Gui/Input/Key.cs +++ b/Terminal.Gui/Input/Key.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Text.Json.Serialization; namespace Terminal.Gui; @@ -448,9 +449,9 @@ public override bool Equals (object obj) #region String conversion - /// Pretty prints the KeyEvent + /// Pretty prints the Key. /// - public override string ToString () { return ToString (KeyCode, (Rune)'+'); } + public override string ToString () { return ToString (KeyCode, Separator); } private static string GetKeyString (KeyCode key) { @@ -483,7 +484,7 @@ private static string GetKeyString (KeyCode key) /// The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key /// name will be returned. /// - public static string ToString (KeyCode key) { return ToString (key, (Rune)'+'); } + public static string ToString (KeyCode key) { return ToString (key, Separator); } /// Formats a as a string. /// The key to format. @@ -584,7 +585,7 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Key key) key = null; // Split the string into parts - string [] parts = text.Split ('+', '-'); + string [] parts = text.Split ('+', '-', (char)Separator.Value); if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty)) { @@ -971,4 +972,20 @@ out parsedInt public static Key F24 => new (KeyCode.F24); #endregion + + private static Rune _separator = new ('+'); + + /// Gets or sets the separator character used when parsing and printing Keys. E.g. Ctrl+A. The default is '+'. + [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] + public static Rune Separator + { + get => _separator; + set + { + if (_separator != value) + { + _separator = value == default (Rune) ? new ('+') : value; + } + } + } } diff --git a/Terminal.Gui/Input/ShortcutHelper.cs b/Terminal.Gui/Input/ShortcutHelper.cs index ea4aa2d8b4..d5e776a538 100644 --- a/Terminal.Gui/Input/ShortcutHelper.cs +++ b/Terminal.Gui/Input/ShortcutHelper.cs @@ -23,7 +23,7 @@ public virtual KeyCode Shortcut } /// The keystroke combination used in the as string. - public virtual string ShortcutTag => Key.ToString (shortcut, MenuBar.ShortcutDelimiter); + public virtual string ShortcutTag => Key.ToString (shortcut, Key.Separator); /// Lookup for a on range of keys. /// The source key. @@ -59,7 +59,7 @@ public static KeyCode GetShortcutFromTag (string tag, Rune delimiter = default) //var hasCtrl = false; if (delimiter == default (Rune)) { - delimiter = MenuBar.ShortcutDelimiter; + delimiter = Key.Separator; } string [] keys = sCut.Split (delimiter.ToString ()); diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json index d001326a08..adba1e584b 100644 --- a/Terminal.Gui/Resources/config.json +++ b/Terminal.Gui/Resources/config.json @@ -22,6 +22,7 @@ "Application.NextTabGroupKey": "F6", "Application.PrevTabGroupKey": "Shift+F6", "Application.QuitKey": "Esc", + "Key.Separator": "+", "Theme": "Default", "Themes": [ diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index 1b8e7d11ab..6bff5c9ad5 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -120,7 +120,7 @@ _barItems.Children [_currentChild] } ); - AddKeyBindings (_barItems); + AddKeyBindingsHotKey (_barItems); } public Menu () @@ -179,7 +179,7 @@ public Menu () KeyBindings.Add (Key.Enter, Command.Accept); } - private void AddKeyBindings (MenuBarItem menuBarItem) + private void AddKeyBindingsHotKey (MenuBarItem menuBarItem) { if (menuBarItem is null || menuBarItem.Children is null) { @@ -190,23 +190,30 @@ private void AddKeyBindings (MenuBarItem menuBarItem) { KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, menuItem); - if ((KeyCode)menuItem.HotKey.Value != KeyCode.Null) + if (menuItem.HotKey != Key.Empty) { - KeyBindings.Remove ((KeyCode)menuItem.HotKey.Value); - KeyBindings.Add ((KeyCode)menuItem.HotKey.Value, keyBinding); - KeyBindings.Remove ((KeyCode)menuItem.HotKey.Value | KeyCode.AltMask); - KeyBindings.Add ((KeyCode)menuItem.HotKey.Value | KeyCode.AltMask, keyBinding); + KeyBindings.Remove (menuItem.HotKey); + KeyBindings.Add (menuItem.HotKey, keyBinding); + KeyBindings.Remove (menuItem.HotKey.WithAlt); + KeyBindings.Add (menuItem.HotKey.WithAlt, keyBinding); } + } + } + + private void RemoveKeyBindingsHotKey (MenuBarItem menuBarItem) + { + if (menuBarItem is null || menuBarItem.Children is null) + { + return; + } - if (menuItem.Shortcut != KeyCode.Null) + foreach (MenuItem menuItem in menuBarItem.Children.Where (m => m is { })) + { + if (menuItem.HotKey != Key.Empty) { - keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem); - KeyBindings.Remove (menuItem.Shortcut); - KeyBindings.Add (menuItem.Shortcut, keyBinding); + KeyBindings.Remove (menuItem.HotKey); + KeyBindings.Remove (menuItem.HotKey.WithAlt); } - - MenuBarItem subMenu = menuBarItem.SubMenu (menuItem); - AddKeyBindings (subMenu); } } @@ -910,6 +917,8 @@ internal bool CheckSubMenu () protected override void Dispose (bool disposing) { + RemoveKeyBindingsHotKey (_barItems); + if (Application.Current is { }) { Application.Current.DrawContentComplete -= Current_DrawContentComplete; diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 75acefe853..4f0db8da77 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -66,6 +66,8 @@ public class MenuBar : View, IDesignable /// Initializes a new instance of the . public MenuBar () { + MenuItem._menuBar = this; + TabStop = TabBehavior.NoStop; X = 0; Y = 0; @@ -122,7 +124,7 @@ public MenuBar () return true; } ); - AddCommand (Command.ToggleExpandCollapse, ctx => Select ((int)ctx.KeyBinding?.Context!)); + AddCommand (Command.ToggleExpandCollapse, ctx => Select (Menus.IndexOf (ctx.KeyBinding?.Context))); AddCommand (Command.Select, ctx => Run ((ctx.KeyBinding?.Context as MenuItem)?.Action)); // Default key bindings for this view @@ -172,19 +174,23 @@ public MenuBarItem [] Menus { MenuBarItem menuBarItem = Menus [i]; - if (menuBarItem?.HotKey != default (Rune)) + if (menuBarItem?.HotKey != Key.Empty) { - KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.Focused, i); - KeyBindings.Add ((KeyCode)menuBarItem.HotKey.Value, keyBinding); - keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, i); - KeyBindings.Add ((KeyCode)menuBarItem.HotKey.Value | KeyCode.AltMask, keyBinding); + KeyBindings.Remove (menuBarItem!.HotKey); + KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.Focused, menuBarItem); + KeyBindings.Add (menuBarItem!.HotKey, keyBinding); + KeyBindings.Remove (menuBarItem.HotKey.WithAlt); + keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, menuBarItem); + KeyBindings.Add (menuBarItem.HotKey.WithAlt, keyBinding); } - if (menuBarItem?.Shortcut != KeyCode.Null) + if (menuBarItem?.ShortcutKey != Key.Empty) { // Technically this will never run because MenuBarItems don't have shortcuts - KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, i); - KeyBindings.Add (menuBarItem.Shortcut, keyBinding); + // unless the IsTopLevel is true + KeyBindings.Remove (menuBarItem.ShortcutKey); + KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuBarItem); + KeyBindings.Add (menuBarItem.ShortcutKey, keyBinding); } menuBarItem?.AddShortcutKeyBindings (this); @@ -1255,21 +1261,6 @@ public bool UseKeysUpDownAsKeysLeftRight } } - private static Rune _shortcutDelimiter = new ('+'); - - /// Sets or gets the shortcut delimiter separator. The default is "+". - public static Rune ShortcutDelimiter - { - get => _shortcutDelimiter; - set - { - if (_shortcutDelimiter != value) - { - _shortcutDelimiter = value == default (Rune) ? new ('+') : value; - } - } - } - /// The specifier character for the hot keys. public new static Rune HotKeySpecifier => (Rune)'_'; @@ -1321,6 +1312,10 @@ private bool Select (int index) { OpenMenu (); } + else if (Menus [index].IsTopLevel) + { + Run (Menus [index].Action); + } else { Activate (index); @@ -1766,4 +1761,12 @@ public bool EnableForDesign (ref readonly TContext context) where TCon ]; return true; } + + /// + protected override void Dispose (bool disposing) + { + MenuItem._menuBar = null; + + base.Dispose (disposing); + } } diff --git a/Terminal.Gui/Views/Menu/MenuBarItem.cs b/Terminal.Gui/Views/Menu/MenuBarItem.cs index 81e7557370..698499cd76 100644 --- a/Terminal.Gui/Views/Menu/MenuBarItem.cs +++ b/Terminal.Gui/Views/Menu/MenuBarItem.cs @@ -2,7 +2,7 @@ namespace Terminal.Gui; /// /// is a menu item on . MenuBarItems do not support -/// . +/// . /// public class MenuBarItem : MenuItem { @@ -100,11 +100,9 @@ internal void AddShortcutKeyBindings (MenuBar menuBar) { // For MenuBar only add shortcuts for submenus - if (menuItem.Shortcut != KeyCode.Null) + if (menuItem.ShortcutKey != Key.Empty) { - KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem); - menuBar.KeyBindings.Remove (menuItem.Shortcut); - menuBar.KeyBindings.Add (menuItem.Shortcut, keyBinding); + menuItem.UpdateShortcutKeyBinding (Key.Empty); } SubMenu (menuItem)?.AddShortcutKeyBindings (menuBar); @@ -176,4 +174,75 @@ private void SetTitle (string title) title ??= string.Empty; Title = title; } + + /// + /// Add a dynamically into the .Menus. + /// + /// + public void AddMenuBarItem (MenuItem menuItem = null) + { + if (menuItem is null) + { + MenuBarItem [] menus = _menuBar.Menus; + Array.Resize (ref menus, menus.Length + 1); + menus [^1] = this; + _menuBar.Menus = menus; + } + else + { + MenuItem [] childrens = Children ?? []; + Array.Resize (ref childrens, childrens.Length + 1); + childrens [^1] = menuItem; + Children = childrens; + } + } + + /// + public override void RemoveMenuItem () + { + if (Children is { }) + { + foreach (MenuItem menuItem in Children) + { + if (menuItem.ShortcutKey != Key.Empty) + { + // Remove an existent ShortcutKey + _menuBar?.KeyBindings.Remove (menuItem.ShortcutKey); + } + } + } + + if (ShortcutKey != Key.Empty) + { + // Remove an existent ShortcutKey + _menuBar?.KeyBindings.Remove (ShortcutKey); + } + + var index = _menuBar!.Menus.IndexOf (this); + if (index > -1) + { + if (_menuBar!.Menus [index].HotKey != Key.Empty) + { + // Remove an existent HotKey + _menuBar?.KeyBindings.Remove (HotKey.WithAlt); + } + + _menuBar!.Menus [index] = null; + } + + var i = 0; + + foreach (MenuBarItem m in _menuBar.Menus) + { + if (m != null) + { + _menuBar.Menus [i] = m; + i++; + } + } + + MenuBarItem [] menus = _menuBar.Menus; + Array.Resize (ref menus, menus.Length - 1); + _menuBar.Menus = menus; + } } diff --git a/Terminal.Gui/Views/Menu/MenuItem.cs b/Terminal.Gui/Views/Menu/MenuItem.cs index 5743c47682..8b72cbb422 100644 --- a/Terminal.Gui/Views/Menu/MenuItem.cs +++ b/Terminal.Gui/Views/Menu/MenuItem.cs @@ -6,31 +6,25 @@ namespace Terminal.Gui; /// public class MenuItem { - private readonly ShortcutHelper _shortcutHelper; - private bool _allowNullChecked; - private MenuItemCheckStyle _checkType; + internal static MenuBar _menuBar; - private string _title; - - // TODO: Update to use Key instead of KeyCode /// Initializes a new instance of - public MenuItem (KeyCode shortcut = KeyCode.Null) : this ("", "", null, null, null, shortcut) { } + public MenuItem (Key shortcutKey = null) : this ("", "", null, null, null, shortcutKey) { } - // TODO: Update to use Key instead of KeyCode /// Initializes a new instance of . /// Title for the menu item. /// Help text to display. /// Action to invoke when the menu item is activated. /// Function to determine if the action can currently be executed. /// The of this menu item. - /// The keystroke combination. + /// The keystroke combination. public MenuItem ( string title, string help, Action action, Func canExecute = null, MenuItem parent = null, - KeyCode shortcut = KeyCode.Null + Key shortcutKey = null ) { Title = title ?? ""; @@ -38,14 +32,20 @@ public MenuItem ( Action = action; CanExecute = canExecute; Parent = parent; - _shortcutHelper = new (); - if (shortcut != KeyCode.Null) + if (Parent is { } && Parent.ShortcutKey != Key.Empty) { - Shortcut = shortcut; + Parent.ShortcutKey = Key.Empty; } + // Setter will ensure Key.Empty if it's null + ShortcutKey = shortcutKey; } + private bool _allowNullChecked; + private MenuItemCheckStyle _checkType; + + private string _title; + /// Gets or sets the action to be invoked when the menu item is triggered. /// Method to invoke. public Action Action { get; set; } @@ -104,6 +104,12 @@ public MenuItemCheckStyle CheckType /// The help text. public string Help { get; set; } + /// + /// Returns if the menu item is enabled. This method is a wrapper around + /// . + /// + public bool IsEnabled () { return CanExecute?.Invoke () ?? true; } + /// Gets the parent for this . /// The parent. public MenuItem Parent { get; set; } @@ -125,46 +131,6 @@ public string Title } } - /// Gets if this is from a sub-menu. - internal bool IsFromSubMenu => Parent != null; - - internal int TitleLength => GetMenuBarItemLength (Title); - - // - // ┌─────────────────────────────┐ - // │ Quit Quit UI Catalog Ctrl+Q │ - // └─────────────────────────────┘ - // ┌─────────────────┐ - // │ ◌ TopLevel Alt+T │ - // └─────────────────┘ - // TODO: Replace the `2` literals with named constants - internal int Width => 1 - + // space before Title - TitleLength - + 2 - + // space after Title - BUGBUG: This should be 1 - (Checked == true || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) - ? 2 - : 0) - + // check glyph + space - (Help.GetColumns () > 0 ? 2 + Help.GetColumns () : 0) - + // Two spaces before Help - (ShortcutTag.GetColumns () > 0 - ? 2 + ShortcutTag.GetColumns () - : 0); // Pad two spaces before shortcut tag (which are also aligned right) - - /// Merely a debugging aid to see the interaction with main. - internal bool GetMenuBarItem () { return IsFromSubMenu; } - - /// Merely a debugging aid to see the interaction with main. - internal MenuItem GetMenuItem () { return this; } - - /// - /// Returns if the menu item is enabled. This method is a wrapper around - /// . - /// - public bool IsEnabled () { return CanExecute?.Invoke () ?? true; } - /// /// Toggle the between three states if is /// or between two states if is . @@ -193,6 +159,40 @@ public void ToggleChecked () } } + /// Merely a debugging aid to see the interaction with main. + internal bool GetMenuBarItem () { return IsFromSubMenu; } + + /// Merely a debugging aid to see the interaction with main. + internal MenuItem GetMenuItem () { return this; } + + /// Gets if this is from a sub-menu. + internal bool IsFromSubMenu => Parent != null; + + internal int TitleLength => GetMenuBarItemLength (Title); + + // + // ┌─────────────────────────────┐ + // │ Quit Quit UI Catalog Ctrl+Q │ + // └─────────────────────────────┘ + // ┌─────────────────┐ + // │ ◌ TopLevel Alt+T │ + // └─────────────────┘ + // TODO: Replace the `2` literals with named constants + internal int Width => 1 + + // space before Title + TitleLength + + 2 + + // space after Title - BUGBUG: This should be 1 + (Checked == true || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) + ? 2 + : 0) + + // check glyph + space + (Help.GetColumns () > 0 ? 2 + Help.GetColumns () : 0) + + // Two spaces before Help + (ShortcutTag.GetColumns () > 0 + ? 2 + ShortcutTag.GetColumns () + : 0); // Pad two spaces before shortcut tag (which are also aligned right) + private static int GetMenuBarItemLength (string title) { return title.EnumerateRunes () @@ -202,21 +202,32 @@ private static int GetMenuBarItemLength (string title) #region Keyboard Handling - // TODO: Update to use Key instead of Rune + private Key _hotKey = Key.Empty; + /// /// The HotKey is used to activate a with the keyboard. HotKeys are defined by prefixing the /// of a MenuItem with an underscore ('_'). /// /// Pressing Alt-Hotkey for a (menu items on the menu bar) works even if the menu is - /// not active). Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem. + /// not active. Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem. /// /// /// For example for a MenuBar with a "_File" MenuBarItem that contains a "_New" MenuItem, Alt-F will open the /// File menu. Pressing the N key will then activate the New MenuItem. /// - /// See also which enable global key-bindings to menu items. + /// See also which enable global key-bindings to menu items. /// - public Rune HotKey { get; set; } + public Key HotKey + { + get => _hotKey; + private set + { + var oldKey = _hotKey ?? Key.Empty; + _hotKey = value ?? Key.Empty; + UpdateHotKeyBinding (oldKey); + } + } + private void GetHotKey () { var nextIsHot = false; @@ -227,47 +238,130 @@ private void GetHotKey () { nextIsHot = true; } - else + else if (nextIsHot) { - if (nextIsHot) - { - HotKey = (Rune)char.ToUpper (x); + HotKey = char.ToLower (x); - break; - } - - nextIsHot = false; - HotKey = default (Rune); + return; } } + + HotKey = Key.Empty; } - // TODO: Update to use Key instead of KeyCode + private Key _shortcutKey = Key.Empty; + /// /// Shortcut defines a key binding to the MenuItem that will invoke the MenuItem's action globally for the /// that is the parent of the or this /// . /// - /// The will be drawn on the MenuItem to the right of the and + /// The will be drawn on the MenuItem to the right of the and /// text. See . /// /// - public KeyCode Shortcut + public Key ShortcutKey { - get => _shortcutHelper.Shortcut; + get => _shortcutKey; set { - if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == KeyCode.Null)) + var oldKey = _shortcutKey ?? Key.Empty; + _shortcutKey = value ?? Key.Empty; + UpdateShortcutKeyBinding (oldKey); + } + } + + /// Gets the text describing the keystroke combination defined by . + public string ShortcutTag => ShortcutKey != Key.Empty ? ShortcutKey.ToString () : string.Empty; + + private void UpdateHotKeyBinding (Key oldKey) + { + if (_menuBar is null || _menuBar?.IsInitialized == false) + { + return; + } + + if (oldKey != Key.Empty) + { + var index = _menuBar.Menus?.IndexOf (this); + + if (index > -1) + { + _menuBar.KeyBindings.Remove (oldKey.WithAlt); + } + } + + if (HotKey != Key.Empty) + { + var index = _menuBar.Menus?.IndexOf (this); + + if (index > -1) { - _shortcutHelper.Shortcut = value; + _menuBar.KeyBindings.Remove (HotKey.WithAlt); + KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, this); + _menuBar.KeyBindings.Add (HotKey.WithAlt, keyBinding); } } } - /// Gets the text describing the keystroke combination defined by . - public string ShortcutTag => _shortcutHelper.Shortcut == KeyCode.Null - ? string.Empty - : Key.ToString (_shortcutHelper.Shortcut, MenuBar.ShortcutDelimiter); + internal void UpdateShortcutKeyBinding (Key oldKey) + { + if (_menuBar is null) + { + return; + } + + if (oldKey != Key.Empty) + { + _menuBar.KeyBindings.Remove (oldKey); + } + + if (ShortcutKey != Key.Empty) + { + KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, this); + // Remove an existent ShortcutKey + _menuBar?.KeyBindings.Remove (ShortcutKey); + _menuBar?.KeyBindings.Add (ShortcutKey, keyBinding); + } + } #endregion Keyboard Handling + + /// + /// Removes a dynamically from the . + /// + public virtual void RemoveMenuItem () + { + if (Parent is { }) + { + MenuItem [] childrens = ((MenuBarItem)Parent).Children; + var i = 0; + + foreach (MenuItem c in childrens) + { + if (c != this) + { + childrens [i] = c; + i++; + } + } + + Array.Resize (ref childrens, childrens.Length - 1); + + if (childrens.Length == 0) + { + ((MenuBarItem)Parent).Children = null; + } + else + { + ((MenuBarItem)Parent).Children = childrens; + } + } + + if (ShortcutKey != Key.Empty) + { + // Remove an existent ShortcutKey + _menuBar?.KeyBindings.Remove (ShortcutKey); + } + } } diff --git a/UICatalog/Scenarios/ContextMenus.cs b/UICatalog/Scenarios/ContextMenus.cs index 4e3b4dc34c..c0ae3cb80a 100644 --- a/UICatalog/Scenarios/ContextMenus.cs +++ b/UICatalog/Scenarios/ContextMenus.cs @@ -179,7 +179,7 @@ private void ShowContextMenu (int x, int y) "This would open setup dialog", "Ok" ), - shortcut: KeyCode.T + shortcutKey: KeyCode.T | KeyCode .CtrlMask ), diff --git a/UICatalog/Scenarios/DynamicMenuBar.cs b/UICatalog/Scenarios/DynamicMenuBar.cs index 79af23d233..1cd3ae0ef6 100644 --- a/UICatalog/Scenarios/DynamicMenuBar.cs +++ b/UICatalog/Scenarios/DynamicMenuBar.cs @@ -111,36 +111,55 @@ public DynamicMenuBarDetails (MenuItem menuItem = null, bool hasParent = false) public DynamicMenuBarDetails () { - var _lblTitle = new Label { Y = 1, Text = "Title:" }; - Add (_lblTitle); + var lblTitle = new Label { Y = 1, Text = "Title:" }; + Add (lblTitle); - TextTitle = new () { X = Pos.Right (_lblTitle) + 2, Y = Pos.Top (_lblTitle), Width = Dim.Fill () }; + TextTitle = new () { X = Pos.Right (lblTitle) + 2, Y = Pos.Top (lblTitle), Width = Dim.Fill () }; Add (TextTitle); - var _lblHelp = new Label { X = Pos.Left (_lblTitle), Y = Pos.Bottom (_lblTitle) + 1, Text = "Help:" }; - Add (_lblHelp); + var lblHelp = new Label { X = Pos.Left (lblTitle), Y = Pos.Bottom (lblTitle) + 1, Text = "Help:" }; + Add (lblHelp); - TextHelp = new () { X = Pos.Left (TextTitle), Y = Pos.Top (_lblHelp), Width = Dim.Fill () }; + TextHelp = new () { X = Pos.Left (TextTitle), Y = Pos.Top (lblHelp), Width = Dim.Fill () }; Add (TextHelp); - var _lblAction = new Label { X = Pos.Left (_lblTitle), Y = Pos.Bottom (_lblHelp) + 1, Text = "Action:" }; - Add (_lblAction); + var lblAction = new Label { X = Pos.Left (lblTitle), Y = Pos.Bottom (lblHelp) + 1, Text = "Action:" }; + Add (lblAction); TextAction = new () { - X = Pos.Left (TextTitle), Y = Pos.Top (_lblAction), Width = Dim.Fill (), Height = 5 + X = Pos.Left (TextTitle), Y = Pos.Top (lblAction), Width = Dim.Fill (), Height = 5 }; Add (TextAction); + var lblHotKey = new Label { X = Pos.Left (lblTitle), Y = Pos.Bottom (lblAction) + 5, Text = "HotKey:" }; + Add (lblHotKey); + + TextHotKey = new () + { + X = Pos.Left (TextTitle), Y = Pos.Bottom (lblAction) + 5, Width = 2, ReadOnly = true + }; + + TextHotKey.TextChanging += (s, e) => + { + if (!string.IsNullOrEmpty (e.NewValue) && char.IsLower (e.NewValue [0])) + { + e.NewValue = e.NewValue.ToUpper (); + } + }; + TextHotKey.TextChanged += (s, _) => TextHotKey.SelectAll (); + TextHotKey.SelectAll (); + Add (TextHotKey); + CkbIsTopLevel = new () { - X = Pos.Left (_lblTitle), Y = Pos.Bottom (_lblAction) + 5, Text = "IsTopLevel" + X = Pos.Left (lblTitle), Y = Pos.Bottom (lblHotKey) + 1, Text = "IsTopLevel" }; Add (CkbIsTopLevel); CkbSubMenu = new () { - X = Pos.Left (_lblTitle), + X = Pos.Left (lblTitle), Y = Pos.Bottom (CkbIsTopLevel), CheckedState = (_menuItem == null ? !_hasParent : HasSubMenus (_menuItem)) ? CheckState.Checked : CheckState.UnChecked, Text = "Has sub-menus" @@ -149,130 +168,66 @@ public DynamicMenuBarDetails () CkbNullCheck = new () { - X = Pos.Left (_lblTitle), Y = Pos.Bottom (CkbSubMenu), Text = "Allow null checked" + X = Pos.Left (lblTitle), Y = Pos.Bottom (CkbSubMenu), Text = "Allow null checked" }; Add (CkbNullCheck); - var _rChkLabels = new [] { "NoCheck", "Checked", "Radio" }; + var rChkLabels = new [] { "NoCheck", "Checked", "Radio" }; RbChkStyle = new () { - X = Pos.Left (_lblTitle), Y = Pos.Bottom (CkbSubMenu) + 1, RadioLabels = _rChkLabels + X = Pos.Left (lblTitle), Y = Pos.Bottom (CkbSubMenu) + 1, RadioLabels = rChkLabels }; Add (RbChkStyle); - var _lblShortcut = new Label + var lblShortcut = new Label { X = Pos.Right (CkbSubMenu) + 10, Y = Pos.Top (CkbSubMenu), Text = "Shortcut:" }; - Add (_lblShortcut); + Add (lblShortcut); - TextShortcut = new () + TextShortcutKey = new () { - X = Pos.X (_lblShortcut), Y = Pos.Bottom (_lblShortcut), Width = Dim.Fill (), ReadOnly = true + X = Pos.X (lblShortcut), Y = Pos.Bottom (lblShortcut), Width = Dim.Fill (), ReadOnly = true }; - TextShortcut.KeyDown += (s, e) => + TextShortcutKey.KeyDown += (s, e) => { - if (!ProcessKey (e)) - { - return; - } + TextShortcutKey.Text = e.ToString (); - if (CheckShortcut (e.KeyCode, true)) - { - e.Handled = true; - } }; - bool ProcessKey (Key ev) - { - switch (ev.KeyCode) - { - case KeyCode.CursorUp: - case KeyCode.CursorDown: - case KeyCode.Tab: - case KeyCode.Tab | KeyCode.ShiftMask: - return false; - } - - return true; - } + Add (TextShortcutKey); - bool CheckShortcut (KeyCode k, bool pre) + var btnShortcut = new Button { - MenuItem m = _menuItem != null ? _menuItem : new (); - - if (pre && !ShortcutHelper.PreShortcutValidation (k)) - { - TextShortcut.Text = ""; - - return false; - } - - if (!pre) - { - if (!ShortcutHelper.PostShortcutValidation ( - ShortcutHelper.GetShortcutFromTag (TextShortcut.Text) - )) - { - TextShortcut.Text = ""; - - return false; - } - - return true; - } - - TextShortcut.Text = - Key.ToString ( - k, - MenuBar.ShortcutDelimiter - ); // ShortcutHelper.GetShortcutTag (k); - - return true; - } - - TextShortcut.KeyUp += (s, e) => - { - if (CheckShortcut (e.KeyCode, false)) - { - e.Handled = true; - } - }; - Add (TextShortcut); - - var _btnShortcut = new Button - { - X = Pos.X (_lblShortcut), Y = Pos.Bottom (TextShortcut) + 1, Text = "Clear Shortcut" + X = Pos.X (lblShortcut), Y = Pos.Bottom (TextShortcutKey) + 1, Text = "Clear Shortcut" }; - _btnShortcut.Accept += (s, e) => { TextShortcut.Text = ""; }; - Add (_btnShortcut); + btnShortcut.Accept += (s, e) => { TextShortcutKey.Text = ""; }; + Add (btnShortcut); CkbIsTopLevel.CheckedStateChanging += (s, e) => { - if ((_menuItem != null && _menuItem.Parent != null && CkbIsTopLevel.CheckedState == CheckState.Checked) - || (_menuItem == null && _hasParent && CkbIsTopLevel.CheckedState == CheckState.Checked)) + if ((_menuItem != null && _menuItem.Parent != null && e.NewValue == CheckState.Checked) + || (_menuItem == null && _hasParent && e.NewValue == CheckState.Checked)) { MessageBox.ErrorQuery ( "Invalid IsTopLevel", "Only menu bar can have top level menu item!", "Ok" ); - CkbIsTopLevel.CheckedState = CheckState.UnChecked; + e.Cancel = true; return; } - if (CkbIsTopLevel.CheckedState == CheckState.Checked) + if (e.NewValue == CheckState.Checked) { CkbSubMenu.CheckedState = CheckState.UnChecked; CkbSubMenu.SetNeedsDisplay (); TextHelp.Enabled = true; TextAction.Enabled = true; - - TextShortcut.Enabled = - CkbIsTopLevel.CheckedState == CheckState.UnChecked && CkbSubMenu.CheckedState == CheckState.UnChecked; + TextShortcutKey.Enabled = true; } else { @@ -280,13 +235,15 @@ bool CheckShortcut (KeyCode k, bool pre) { CkbSubMenu.CheckedState = CheckState.Checked; CkbSubMenu.SetNeedsDisplay (); - TextShortcut.Enabled = false; + TextShortcutKey.Enabled = false; } TextHelp.Text = ""; TextHelp.Enabled = false; TextAction.Text = ""; - TextAction.Enabled = false; + + TextShortcutKey.Enabled = + e.NewValue == CheckState.Checked && CkbSubMenu.CheckedState == CheckState.UnChecked; } }; @@ -300,8 +257,8 @@ bool CheckShortcut (KeyCode k, bool pre) TextHelp.Enabled = false; TextAction.Text = ""; TextAction.Enabled = false; - TextShortcut.Text = ""; - TextShortcut.Enabled = false; + TextShortcutKey.Text = ""; + TextShortcutKey.Enabled = false; } else { @@ -309,14 +266,17 @@ bool CheckShortcut (KeyCode k, bool pre) { CkbIsTopLevel.CheckedState = CheckState.Checked; CkbIsTopLevel.SetNeedsDisplay (); - TextShortcut.Enabled = false; + TextShortcutKey.Enabled = true; } TextHelp.Enabled = true; TextAction.Enabled = true; - TextShortcut.Enabled = - CkbIsTopLevel.CheckedState == CheckState.UnChecked && CkbSubMenu.CheckedState == CheckState.UnChecked; + if (_hasParent) + { + TextShortcutKey.Enabled = CkbIsTopLevel.CheckedState == CheckState.UnChecked + && e.NewValue == CheckState.UnChecked; + } } }; @@ -337,7 +297,8 @@ bool CheckShortcut (KeyCode k, bool pre) public RadioGroup RbChkStyle { get; } public TextView TextAction { get; } public TextField TextHelp { get; } - public TextField TextShortcut { get; } + public TextField TextHotKey { get; } + public TextField TextShortcutKey { get; } public TextField TextTitle { get; } public Action CreateAction (MenuItem menuItem, DynamicMenuItem item) @@ -391,17 +352,20 @@ public void EditMenuBarItem (MenuItem menuItem) TextTitle.Text = menuItem?.Title ?? ""; TextHelp.Text = menuItem?.Help ?? ""; - TextAction.Text = menuItem != null && menuItem.Action != null + TextAction.Text = menuItem.Action != null ? GetTargetAction (menuItem.Action) : string.Empty; + TextHotKey.Text = menuItem?.HotKey != Key.Empty ? menuItem.HotKey.ToString () : ""; CkbIsTopLevel.CheckedState = IsTopLevel (menuItem) ? CheckState.Checked : CheckState.UnChecked; CkbSubMenu.CheckedState = HasSubMenus (menuItem) ? CheckState.Checked : CheckState.UnChecked; CkbNullCheck.CheckedState = menuItem.AllowNullChecked ? CheckState.Checked : CheckState.UnChecked; - TextHelp.Enabled = CkbSubMenu.CheckedState == CheckState.Checked; - TextAction.Enabled = CkbSubMenu.CheckedState == CheckState.Checked; + TextHelp.Enabled = CkbSubMenu.CheckedState == CheckState.UnChecked; + TextAction.Enabled = CkbSubMenu.CheckedState == CheckState.UnChecked; RbChkStyle.SelectedItem = (int)(menuItem?.CheckType ?? MenuItemCheckStyle.NoCheck); - TextShortcut.Text = menuItem?.ShortcutTag ?? ""; - TextShortcut.Enabled = CkbIsTopLevel.CheckedState == CheckState.UnChecked && CkbSubMenu.CheckedState == CheckState.UnChecked; + TextShortcutKey.Text = menuItem?.ShortcutTag ?? ""; + + TextShortcutKey.Enabled = CkbIsTopLevel.CheckedState == CheckState.Checked && CkbSubMenu.CheckedState == CheckState.UnChecked + || CkbIsTopLevel.CheckedState == CheckState.UnChecked && CkbSubMenu.CheckedState == CheckState.UnChecked; } public DynamicMenuItem EnterMenuItem () @@ -414,12 +378,13 @@ public DynamicMenuItem EnterMenuItem () TextTitle.Text = m.Title; TextHelp.Text = m.Help; TextAction.Text = m.Action; + TextHotKey.Text = m.HotKey ?? string.Empty; CkbIsTopLevel.CheckedState = CheckState.UnChecked; CkbSubMenu.CheckedState = !_hasParent ? CheckState.Checked : CheckState.UnChecked; CkbNullCheck.CheckedState = CheckState.UnChecked; TextHelp.Enabled = _hasParent; TextAction.Enabled = _hasParent; - TextShortcut.Enabled = _hasParent; + TextShortcutKey.Enabled = _hasParent; } else { @@ -466,12 +431,13 @@ public DynamicMenuItem EnterMenuItem () Title = TextTitle.Text, Help = TextHelp.Text, Action = TextAction.Text, + HotKey = TextHotKey.Text, IsTopLevel = CkbIsTopLevel?.CheckedState == CheckState.Checked, - HasSubMenu = CkbSubMenu?.CheckedState == CheckState.UnChecked, + HasSubMenu = CkbSubMenu?.CheckedState == CheckState.Checked, CheckStyle = RbChkStyle.SelectedItem == 0 ? MenuItemCheckStyle.NoCheck : RbChkStyle.SelectedItem == 1 ? MenuItemCheckStyle.Checked : MenuItemCheckStyle.Radio, - Shortcut = TextShortcut.Text, + ShortcutKey = TextShortcutKey.Text, AllowNullChecked = CkbNullCheck?.CheckedState == CheckState.Checked, }; } @@ -515,10 +481,11 @@ private void CleanEditMenuBarItem () TextTitle.Text = ""; TextHelp.Text = ""; TextAction.Text = ""; + TextHotKey.Text = ""; CkbIsTopLevel.CheckedState = CheckState.UnChecked; CkbSubMenu.CheckedState = CheckState.UnChecked; RbChkStyle.SelectedItem = (int)MenuItemCheckStyle.NoCheck; - TextShortcut.Text = ""; + TextShortcutKey.Text = ""; } private string GetTargetAction (Action action) @@ -580,7 +547,7 @@ public DynamicMenuBarSample () { DataContext = new (); - var _frmDelimiter = new FrameView + var frmDelimiter = new FrameView { X = Pos.Center (), Y = 3, @@ -589,114 +556,117 @@ public DynamicMenuBarSample () Title = "Shortcut Delimiter:" }; - var _txtDelimiter = new TextField + var txtDelimiter = new TextField { - X = Pos.Center (), Width = 2, Text = MenuBar.ShortcutDelimiter.ToString () + X = Pos.Center (), Width = 2, Text = Key.Separator.ToString () }; - _txtDelimiter.TextChanged += (s, _) => - MenuBar.ShortcutDelimiter = _txtDelimiter.Text.ToRunes () [0]; - _frmDelimiter.Add (_txtDelimiter); - - Add (_frmDelimiter); - var _frmMenu = new FrameView { Y = 7, Width = Dim.Percent (50), Height = Dim.Fill (), Title = "Menus:" }; + var frmMenu = new FrameView { Y = 7, Width = Dim.Percent (50), Height = Dim.Fill (), Title = "Menus:" }; - var _btnAddMenuBar = new Button { Y = 1, Text = "Add a MenuBar" }; - _frmMenu.Add (_btnAddMenuBar); + var btnAddMenuBar = new Button { Y = 1, Text = "Add a MenuBar" }; + frmMenu.Add (btnAddMenuBar); - var _btnMenuBarUp = new Button { X = Pos.Center (), Text = CM.Glyphs.UpArrow.ToString () }; - _frmMenu.Add (_btnMenuBarUp); + var btnMenuBarUp = new Button { X = Pos.Center (), Text = CM.Glyphs.UpArrow.ToString () }; + frmMenu.Add (btnMenuBarUp); - var _btnMenuBarDown = new Button { X = Pos.Center (), Y = Pos.Bottom (_btnMenuBarUp), Text = CM.Glyphs.DownArrow.ToString () }; - _frmMenu.Add (_btnMenuBarDown); + var btnMenuBarDown = new Button { X = Pos.Center (), Y = Pos.Bottom (btnMenuBarUp), Text = CM.Glyphs.DownArrow.ToString () }; + frmMenu.Add (btnMenuBarDown); - var _btnRemoveMenuBar = new Button { Y = 1, Text = "Remove a MenuBar" }; + var btnRemoveMenuBar = new Button { Y = 1, Text = "Remove a MenuBar" }; - _btnRemoveMenuBar.X = Pos.AnchorEnd (0) - (Pos.Right (_btnRemoveMenuBar) - Pos.Left (_btnRemoveMenuBar)); - _frmMenu.Add (_btnRemoveMenuBar); + btnRemoveMenuBar.X = Pos.AnchorEnd (0) - (Pos.Right (btnRemoveMenuBar) - Pos.Left (btnRemoveMenuBar)); + frmMenu.Add (btnRemoveMenuBar); - var _btnPrevious = new Button + var btnPrevious = new Button { - X = Pos.Left (_btnAddMenuBar), Y = Pos.Top (_btnAddMenuBar) + 2, Text = CM.Glyphs.LeftArrow.ToString () + X = Pos.Left (btnAddMenuBar), Y = Pos.Top (btnAddMenuBar) + 2, Text = CM.Glyphs.LeftArrow.ToString () }; - _frmMenu.Add (_btnPrevious); + frmMenu.Add (btnPrevious); - var _btnAdd = new Button { Y = Pos.Top (_btnPrevious) + 2, Text = " Add " }; - _btnAdd.X = Pos.AnchorEnd (); - _frmMenu.Add (_btnAdd); + var btnAdd = new Button { Y = Pos.Top (btnPrevious) + 2, Text = " Add " }; + btnAdd.X = Pos.AnchorEnd (); + frmMenu.Add (btnAdd); - var _btnNext = new Button { X = Pos.X (_btnAdd), Y = Pos.Top (_btnPrevious), Text = CM.Glyphs.RightArrow.ToString () }; - _frmMenu.Add (_btnNext); + var btnNext = new Button { X = Pos.X (btnAdd), Y = Pos.Top (btnPrevious), Text = CM.Glyphs.RightArrow.ToString () }; + frmMenu.Add (btnNext); - var _lblMenuBar = new Label + var lblMenuBar = new Label { ColorScheme = Colors.ColorSchemes ["Dialog"], TextAlignment = Alignment.Center, - X = Pos.Right (_btnPrevious) + 1, - Y = Pos.Top (_btnPrevious), + X = Pos.Right (btnPrevious) + 1, + Y = Pos.Top (btnPrevious), - Width = Dim.Fill () - Dim.Func (() => _btnAdd.Frame.Width + 1), + Width = Dim.Fill () - Dim.Func (() => btnAdd.Frame.Width + 1), Height = 1 }; - _frmMenu.Add (_lblMenuBar); - _lblMenuBar.WantMousePositionReports = true; - _lblMenuBar.CanFocus = true; - var _lblParent = new Label + lblMenuBar.TextChanged += (s, e) => + { + if (lblMenuBar.Text.Contains ("_")) + { + lblMenuBar.Text = lblMenuBar.Text.Replace ("_", ""); + } + }; + frmMenu.Add (lblMenuBar); + lblMenuBar.WantMousePositionReports = true; + lblMenuBar.CanFocus = true; + + var lblParent = new Label { TextAlignment = Alignment.Center, - X = Pos.Right (_btnPrevious) + 1, - Y = Pos.Top (_btnPrevious) + 1, + X = Pos.Right (btnPrevious) + 1, + Y = Pos.Top (btnPrevious) + 1, - Width = Dim.Fill () - Dim.Width (_btnAdd) - 1 + Width = Dim.Fill () - Dim.Width (btnAdd) - 1 }; - _frmMenu.Add (_lblParent); + frmMenu.Add (lblParent); - var _btnPreviowsParent = new Button + var btnPreviowsParent = new Button { - X = Pos.Left (_btnAddMenuBar), Y = Pos.Top (_btnPrevious) + 1, Text = ".." + X = Pos.Left (btnAddMenuBar), Y = Pos.Top (btnPrevious) + 1, Text = ".." }; - _frmMenu.Add (_btnPreviowsParent); + frmMenu.Add (btnPreviowsParent); _lstMenus = new () { ColorScheme = Colors.ColorSchemes ["Dialog"], - X = Pos.Right (_btnPrevious) + 1, - Y = Pos.Top (_btnPrevious) + 2, - Width = _lblMenuBar.Width, + X = Pos.Right (btnPrevious) + 1, + Y = Pos.Top (btnPrevious) + 2, + Width = lblMenuBar.Width, Height = Dim.Fill (), Source = new ListWrapper ([]) }; - _frmMenu.Add (_lstMenus); + frmMenu.Add (_lstMenus); - _lblMenuBar.TabIndex = _btnPrevious.TabIndex + 1; - _lstMenus.TabIndex = _lblMenuBar.TabIndex + 1; - _btnNext.TabIndex = _lstMenus.TabIndex + 1; - _btnAdd.TabIndex = _btnNext.TabIndex + 1; + lblMenuBar.TabIndex = btnPrevious.TabIndex + 1; + _lstMenus.TabIndex = lblMenuBar.TabIndex + 1; + btnNext.TabIndex = _lstMenus.TabIndex + 1; + btnAdd.TabIndex = btnNext.TabIndex + 1; - var _btnRemove = new Button { X = Pos.Left (_btnAdd), Y = Pos.Top (_btnAdd) + 1, Text = "Remove" }; - _frmMenu.Add (_btnRemove); + var btnRemove = new Button { X = Pos.Left (btnAdd), Y = Pos.Top (btnAdd) + 1, Text = "Remove" }; + frmMenu.Add (btnRemove); - var _btnUp = new Button { X = Pos.Right (_lstMenus) + 2, Y = Pos.Top (_btnRemove) + 2, Text = CM.Glyphs.UpArrow.ToString () }; - _frmMenu.Add (_btnUp); + var btnUp = new Button { X = Pos.Right (_lstMenus) + 2, Y = Pos.Top (btnRemove) + 2, Text = CM.Glyphs.UpArrow.ToString () }; + frmMenu.Add (btnUp); - var _btnDown = new Button { X = Pos.Right (_lstMenus) + 2, Y = Pos.Top (_btnUp) + 1, Text = CM.Glyphs.DownArrow.ToString () }; - _frmMenu.Add (_btnDown); + var btnDown = new Button { X = Pos.Right (_lstMenus) + 2, Y = Pos.Top (btnUp) + 1, Text = CM.Glyphs.DownArrow.ToString () }; + frmMenu.Add (btnDown); - Add (_frmMenu); + Add (frmMenu); - var _frmMenuDetails = new DynamicMenuBarDetails + var frmMenuDetails = new DynamicMenuBarDetails { - X = Pos.Right (_frmMenu), - Y = Pos.Top (_frmMenu), + X = Pos.Right (frmMenu), + Y = Pos.Top (frmMenu), Width = Dim.Fill (), Height = Dim.Fill (2), Title = "Menu Details:" }; - Add (_frmMenuDetails); + Add (frmMenuDetails); - _btnMenuBarUp.Accept += (s, e) => + btnMenuBarUp.Accept += (s, e) => { int i = _currentSelectedMenuBar; @@ -718,7 +688,7 @@ public DynamicMenuBarSample () } }; - _btnMenuBarDown.Accept += (s, e) => + btnMenuBarDown.Accept += (s, e) => { int i = _currentSelectedMenuBar; @@ -740,7 +710,7 @@ public DynamicMenuBarSample () } }; - _btnUp.Accept += (s, e) => + btnUp.Accept += (s, e) => { int i = _lstMenus.SelectedItem; MenuItem menuItem = DataContext.Menus.Count > 0 ? DataContext.Menus [i].MenuItem : null; @@ -762,7 +732,7 @@ public DynamicMenuBarSample () } }; - _btnDown.Accept += (s, e) => + btnDown.Accept += (s, e) => { int i = _lstMenus.SelectedItem; MenuItem menuItem = DataContext.Menus.Count > 0 ? DataContext.Menus [i].MenuItem : null; @@ -784,7 +754,7 @@ public DynamicMenuBarSample () } }; - _btnPreviowsParent.Accept += (s, e) => + btnPreviowsParent.Accept += (s, e) => { if (_currentMenuBarItem != null && _currentMenuBarItem.Parent != null) { @@ -813,18 +783,39 @@ public DynamicMenuBarSample () } }; - var _btnOk = new Button { X = Pos.Right (_frmMenu) + 20, Y = Pos.Bottom (_frmMenuDetails), Text = "Ok" }; - Add (_btnOk); + var btnOk = new Button { X = Pos.Right (frmMenu) + 20, Y = Pos.Bottom (frmMenuDetails), Text = "Ok" }; + Add (btnOk); - var _btnCancel = new Button { X = Pos.Right (_btnOk) + 3, Y = Pos.Top (_btnOk), Text = "Cancel" }; - _btnCancel.Accept += (s, e) => { SetFrameDetails (_currentEditMenuBarItem); }; - Add (_btnCancel); + var btnCancel = new Button { X = Pos.Right (btnOk) + 3, Y = Pos.Top (btnOk), Text = "Cancel" }; + btnCancel.Accept += (s, e) => { SetFrameDetails (_currentEditMenuBarItem); }; + Add (btnCancel); + + txtDelimiter.TextChanging += (s, e) => + { + if (!string.IsNullOrEmpty (e.NewValue)) + { + Key.Separator = e.NewValue.ToRunes () [0]; + } + else + { + e.Cancel = true; + txtDelimiter.SelectAll (); + } + }; + txtDelimiter.TextChanged += (s, _) => + { + txtDelimiter.SelectAll (); + SetFrameDetails (); + }; + frmDelimiter.Add (txtDelimiter); + txtDelimiter.SelectAll (); + Add (frmDelimiter); _lstMenus.SelectedItemChanged += (s, e) => { SetFrameDetails (); }; - _btnOk.Accept += (s, e) => + btnOk.Accept += (s, e) => { - if (string.IsNullOrEmpty (_frmMenuDetails.TextTitle.Text) && _currentEditMenuBarItem != null) + if (string.IsNullOrEmpty (frmMenuDetails.TextTitle.Text) && _currentEditMenuBarItem != null) { MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok"); } @@ -832,28 +823,29 @@ public DynamicMenuBarSample () { var menuItem = new DynamicMenuItem { - Title = _frmMenuDetails.TextTitle.Text, - Help = _frmMenuDetails.TextHelp.Text, - Action = _frmMenuDetails.TextAction.Text, - IsTopLevel = _frmMenuDetails.CkbIsTopLevel?.CheckedState == CheckState.UnChecked, - HasSubMenu = _frmMenuDetails.CkbSubMenu?.CheckedState == CheckState.UnChecked, - CheckStyle = _frmMenuDetails.RbChkStyle.SelectedItem == 0 + Title = frmMenuDetails.TextTitle.Text, + Help = frmMenuDetails.TextHelp.Text, + Action = frmMenuDetails.TextAction.Text, + HotKey = frmMenuDetails.TextHotKey.Text, + IsTopLevel = frmMenuDetails.CkbIsTopLevel?.CheckedState == CheckState.Checked, + HasSubMenu = frmMenuDetails.CkbSubMenu?.CheckedState == CheckState.Checked, + CheckStyle = frmMenuDetails.RbChkStyle.SelectedItem == 0 ? MenuItemCheckStyle.NoCheck - : _frmMenuDetails.RbChkStyle.SelectedItem == 1 + : frmMenuDetails.RbChkStyle.SelectedItem == 1 ? MenuItemCheckStyle.Checked : MenuItemCheckStyle.Radio, - Shortcut = _frmMenuDetails.TextShortcut.Text + ShortcutKey = frmMenuDetails.TextShortcutKey.Text }; UpdateMenuItem (_currentEditMenuBarItem, menuItem, _lstMenus.SelectedItem); } }; - _btnAdd.Accept += (s, e) => + btnAdd.Accept += (s, e) => { if (MenuBar == null) { MessageBox.ErrorQuery ("Menu Bar Error", "Must add a MenuBar first!", "Ok"); - _btnAddMenuBar.SetFocus (); + btnAddMenuBar.SetFocus (); return; } @@ -883,97 +875,50 @@ public DynamicMenuBarSample () { MenuItem newMenu = CreateNewMenu (item, _currentMenuBarItem); var menuBarItem = _currentMenuBarItem as MenuBarItem; + menuBarItem.AddMenuBarItem (newMenu); - if (menuBarItem == null) - { - menuBarItem = new ( - _currentMenuBarItem.Title, - new [] { newMenu }, - _currentMenuBarItem.Parent - ); - } - else if (menuBarItem.Children == null) - { - menuBarItem.Children = new [] { newMenu }; - } - else - { - MenuItem [] childrens = menuBarItem.Children; - Array.Resize (ref childrens, childrens.Length + 1); - childrens [childrens.Length - 1] = newMenu; - menuBarItem.Children = childrens; - } DataContext.Menus.Add (new () { Title = newMenu.Title, MenuItem = newMenu }); _lstMenus.MoveDown (); } }; - _btnRemove.Accept += (s, e) => - { - MenuItem menuItem = DataContext.Menus.Count > 0 + btnRemove.Accept += (s, e) => + { + MenuItem menuItem = (DataContext.Menus.Count > 0 && _lstMenus.SelectedItem > -1 ? DataContext.Menus [_lstMenus.SelectedItem].MenuItem - : null; - - if (menuItem != null) - { - MenuItem [] childrens = ((MenuBarItem)_currentMenuBarItem).Children; - childrens [_lstMenus.SelectedItem] = null; - var i = 0; + : _currentEditMenuBarItem); - foreach (MenuItem c in childrens) - { - if (c != null) - { - childrens [i] = c; - i++; - } - } + if (menuItem != null) + { + menuItem.RemoveMenuItem (); - Array.Resize (ref childrens, childrens.Length - 1); + if (_currentEditMenuBarItem == menuItem) + { + _currentEditMenuBarItem = null; - if (childrens.Length == 0) - { - if (_currentMenuBarItem.Parent == null) - { - ((MenuBarItem)_currentMenuBarItem).Children = null; + if (menuItem.Parent is null) + { + _currentSelectedMenuBar = Math.Max (Math.Min (_currentSelectedMenuBar, _menuBar.Menus.Length - 1), 0); + } - //_currentMenuBarItem.Action = _frmMenuDetails.CreateAction (_currentEditMenuBarItem, new DynamicMenuItem (_currentMenuBarItem.Title)); - } - else - { - _currentMenuBarItem = new ( - _currentMenuBarItem.Title, - _currentMenuBarItem.Help, - _frmMenuDetails.CreateAction ( - _currentEditMenuBarItem, - new () - { - Title = _currentEditMenuBarItem - .Title - } - ), - null, - _currentMenuBarItem.Parent - ); - } - } - else - { - ((MenuBarItem)_currentMenuBarItem).Children = childrens; - } + SelectCurrentMenuBarItem (); + } - DataContext.Menus.RemoveAt (_lstMenus.SelectedItem); + if (_lstMenus.SelectedItem > -1) + { + DataContext.Menus?.RemoveAt (_lstMenus.SelectedItem); + } - if (_lstMenus.Source.Count > 0 && _lstMenus.SelectedItem > _lstMenus.Source.Count - 1) - { - _lstMenus.SelectedItem = _lstMenus.Source.Count - 1; - } + if (_lstMenus.Source.Count > 0 && _lstMenus.SelectedItem > _lstMenus.Source.Count - 1) + { + _lstMenus.SelectedItem = _lstMenus.Source.Count - 1; + } - _lstMenus.SetNeedsDisplay (); - SetFrameDetails (); - } - }; + _lstMenus.SetNeedsDisplay (); + SetFrameDetails (); + } + }; _lstMenus.OpenSelectedItem += (s, e) => { @@ -1001,7 +946,7 @@ public DynamicMenuBarSample () SetFrameDetails (menuBarItem); }; - _btnNext.Accept += (s, e) => + btnNext.Accept += (s, e) => { if (_menuBar != null && _currentSelectedMenuBar + 1 < _menuBar.Menus.Length) { @@ -1011,7 +956,7 @@ public DynamicMenuBarSample () SelectCurrentMenuBarItem (); }; - _btnPrevious.Accept += (s, e) => + btnPrevious.Accept += (s, e) => { if (_currentSelectedMenuBar - 1 > -1) { @@ -1021,7 +966,7 @@ public DynamicMenuBarSample () SelectCurrentMenuBarItem (); }; - _lblMenuBar.Enter += (s, e) => + lblMenuBar.Enter += (s, e) => { if (_menuBar?.Menus != null) { @@ -1030,7 +975,7 @@ public DynamicMenuBarSample () } }; - _btnAddMenuBar.Accept += (s, e) => + btnAddMenuBar.Accept += (s, e) => { var frameDetails = new DynamicMenuBarDetails (null); DynamicMenuItem item = frameDetails.EnterMenuItem (); @@ -1047,22 +992,25 @@ public DynamicMenuBarSample () } var newMenu = CreateNewMenu (item) as MenuBarItem; + newMenu.AddMenuBarItem (); - MenuBarItem [] menus = _menuBar.Menus; - Array.Resize (ref menus, menus.Length + 1); - menus [^1] = newMenu; - _menuBar.Menus = menus; _currentMenuBarItem = newMenu; _currentMenuBarItem.CheckType = item.CheckStyle; - _currentSelectedMenuBar = menus.Length - 1; + + if (Key.TryParse (item.ShortcutKey, out Key key)) + { + _currentMenuBarItem.ShortcutKey = key; + } + + _currentSelectedMenuBar = _menuBar.Menus.Length - 1; _menuBar.Menus [_currentSelectedMenuBar] = newMenu; - _lblMenuBar.Text = newMenu.Title; + lblMenuBar.Text = newMenu.Title; SetListViewSource (_currentMenuBarItem, true); SetFrameDetails (_menuBar.Menus [_currentSelectedMenuBar]); _menuBar.SetNeedsDisplay (); }; - _btnRemoveMenuBar.Accept += (s, e) => + btnRemoveMenuBar.Accept += (s, e) => { if (_menuBar == null || _menuBar.Menus.Length == 0) { @@ -1071,21 +1019,9 @@ public DynamicMenuBarSample () if (_menuBar != null && _menuBar.Menus.Length > 0) { - _menuBar.Menus [_currentSelectedMenuBar] = null; - var i = 0; + _currentMenuBarItem.RemoveMenuItem (); - foreach (MenuBarItem m in _menuBar.Menus) - { - if (m != null) - { - _menuBar.Menus [i] = m; - i++; - } - } - MenuBarItem [] menus = _menuBar.Menus; - Array.Resize (ref menus, menus.Length - 1); - _menuBar.Menus = menus; if (_currentSelectedMenuBar - 1 >= 0 && _menuBar.Menus.Length > 0) { @@ -1100,15 +1036,16 @@ public DynamicMenuBarSample () if (MenuBar != null && _currentMenuBarItem == null && _menuBar.Menus.Length == 0) { Remove (_menuBar); + _menuBar.Dispose (); _menuBar = null; DataContext.Menus = new (); _currentMenuBarItem = null; _currentSelectedMenuBar = -1; - _lblMenuBar.Text = string.Empty; + lblMenuBar.Text = string.Empty; } else { - _lblMenuBar.Text = _menuBar.Menus [_currentSelectedMenuBar].Title; + lblMenuBar.Text = _menuBar.Menus [_currentSelectedMenuBar].Title; } SetListViewSource (_currentMenuBarItem, true); @@ -1120,9 +1057,9 @@ public DynamicMenuBarSample () var ustringConverter = new UStringValueConverter (); ListWrapperConverter listWrapperConverter = new ListWrapperConverter (); - var lblMenuBar = new Binding (this, "MenuBar", _lblMenuBar, "Text", ustringConverter); - var lblParent = new Binding (this, "Parent", _lblParent, "Text", ustringConverter); - var lstMenus = new Binding (this, "Menus", _lstMenus, "Source", listWrapperConverter); + var bdgMenuBar = new Binding (this, "MenuBar", lblMenuBar, "Text", ustringConverter); + var bdgParent = new Binding (this, "Parent", lblParent, "Text", ustringConverter); + var bdgMenus = new Binding (this, "Menus", _lstMenus, "Source", listWrapperConverter); void SetFrameDetails (MenuItem menuBarItem = null) { @@ -1132,7 +1069,7 @@ void SetFrameDetails (MenuItem menuBarItem = null) { menuItem = _lstMenus.SelectedItem > -1 && DataContext.Menus.Count > 0 ? DataContext.Menus [_lstMenus.SelectedItem].MenuItem - : null; + : _currentEditMenuBarItem; } else { @@ -1140,13 +1077,13 @@ void SetFrameDetails (MenuItem menuBarItem = null) } _currentEditMenuBarItem = menuItem; - _frmMenuDetails.EditMenuBarItem (menuItem); - bool f = _btnOk.Enabled == _frmMenuDetails.Enabled; + frmMenuDetails.EditMenuBarItem (menuItem); + bool f = btnOk.Enabled == frmMenuDetails.Enabled; if (!f) { - _btnOk.Enabled = _frmMenuDetails.Enabled; - _btnCancel.Enabled = _frmMenuDetails.Enabled; + btnOk.Enabled = frmMenuDetails.Enabled; + btnCancel.Enabled = frmMenuDetails.Enabled; } } @@ -1154,23 +1091,28 @@ void SelectCurrentMenuBarItem () { MenuBarItem menuBarItem = null; - if (_menuBar?.Menus != null) + if (_menuBar?.Menus is { Length: > 0 }) { menuBarItem = _menuBar.Menus [_currentSelectedMenuBar]; - _lblMenuBar.Text = menuBarItem.Title; + lblMenuBar.Text = menuBarItem.Title; } SetFrameDetails (menuBarItem); _currentMenuBarItem = menuBarItem; DataContext.Menus = new (); SetListViewSource (_currentMenuBarItem, true); - _lblParent.Text = string.Empty; + lblParent.Text = string.Empty; + + if (_currentMenuBarItem is null) + { + lblMenuBar.Text = string.Empty; + } } - void SetListViewSource (MenuItem _currentMenuBarItem, bool fill = false) + void SetListViewSource (MenuItem currentMenuBarItem, bool fill = false) { DataContext.Menus = []; - var menuBarItem = _currentMenuBarItem as MenuBarItem; + var menuBarItem = currentMenuBarItem as MenuBarItem; if (menuBarItem != null && menuBarItem?.Children == null) { @@ -1204,103 +1146,123 @@ MenuItem CreateNewMenu (DynamicMenuItem item, MenuItem parent = null) { newMenu = new (item.Title, item.Help, null, null, parent); newMenu.CheckType = item.CheckStyle; - newMenu.Action = _frmMenuDetails.CreateAction (newMenu, item); - newMenu.Shortcut = ShortcutHelper.GetShortcutFromTag (item.Shortcut); + newMenu.Action = frmMenuDetails.CreateAction (newMenu, item); + + if (Key.TryParse (item.ShortcutKey, out Key key)) + { + newMenu.ShortcutKey = key; + } newMenu.AllowNullChecked = item.AllowNullChecked; } else if (item.IsTopLevel) { newMenu = new MenuBarItem (item.Title, item.Help, null); - newMenu.Action = _frmMenuDetails.CreateAction (newMenu, item); + newMenu.Action = frmMenuDetails.CreateAction (newMenu, item); + + if (Key.TryParse (item.ShortcutKey, out Key key)) + { + newMenu.ShortcutKey = key; + } } else { newMenu = new MenuBarItem (item.Title, item.Help, null); ((MenuBarItem)newMenu).Children [0].Action = - _frmMenuDetails.CreateAction (newMenu, item); + frmMenuDetails.CreateAction (newMenu, item); - ((MenuBarItem)newMenu).Children [0].Shortcut = - ShortcutHelper.GetShortcutFromTag (item.Shortcut); + if (Key.TryParse (item.ShortcutKey, out Key key)) + { + ((MenuBarItem)newMenu).Children [0].ShortcutKey = key; + } } return newMenu; } - void UpdateMenuItem (MenuItem _currentEditMenuBarItem, DynamicMenuItem menuItem, int index) + void UpdateMenuItem (MenuItem currentEditMenuBarItem, DynamicMenuItem menuItem, int index) { - _currentEditMenuBarItem.Title = menuItem.Title; - _currentEditMenuBarItem.Help = menuItem.Help; - _currentEditMenuBarItem.CheckType = menuItem.CheckStyle; - var parent = _currentEditMenuBarItem.Parent as MenuBarItem; + currentEditMenuBarItem.Title = menuItem.Title; + currentEditMenuBarItem.Help = menuItem.Help; + currentEditMenuBarItem.CheckType = menuItem.CheckStyle; - if (parent != null && parent.Children.Length == 1 && _currentEditMenuBarItem.CheckType == MenuItemCheckStyle.Radio) + if (currentEditMenuBarItem.Parent is MenuBarItem parent + && parent.Children.Length == 1 + && currentEditMenuBarItem.CheckType == MenuItemCheckStyle.Radio) { - _currentEditMenuBarItem.Checked = true; + currentEditMenuBarItem.Checked = true; } - if (menuItem.IsTopLevel && _currentEditMenuBarItem is MenuBarItem) + if (menuItem.IsTopLevel && currentEditMenuBarItem is MenuBarItem) { - ((MenuBarItem)_currentEditMenuBarItem).Children = null; + ((MenuBarItem)currentEditMenuBarItem).Children = null; + + currentEditMenuBarItem.Action = + frmMenuDetails.CreateAction (currentEditMenuBarItem, menuItem); - _currentEditMenuBarItem.Action = - _frmMenuDetails.CreateAction (_currentEditMenuBarItem, menuItem); - SetListViewSource (_currentEditMenuBarItem, true); + if (Key.TryParse (menuItem.ShortcutKey, out Key key)) + { + currentEditMenuBarItem.ShortcutKey = key; + } + + SetListViewSource (currentEditMenuBarItem, true); } else if (menuItem.HasSubMenu) { - _currentEditMenuBarItem.Action = null; + currentEditMenuBarItem.Action = null; - if (_currentEditMenuBarItem is MenuBarItem && ((MenuBarItem)_currentEditMenuBarItem).Children == null) + if (currentEditMenuBarItem is MenuBarItem && ((MenuBarItem)currentEditMenuBarItem).Children == null) { - ((MenuBarItem)_currentEditMenuBarItem).Children = new MenuItem [] { }; + ((MenuBarItem)currentEditMenuBarItem).Children = new MenuItem [] { }; } - else if (_currentEditMenuBarItem.Parent != null) + else if (currentEditMenuBarItem.Parent != null) { - _frmMenuDetails.UpdateParent (ref _currentEditMenuBarItem); + frmMenuDetails.UpdateParent (ref currentEditMenuBarItem); } else { - _currentEditMenuBarItem = + currentEditMenuBarItem = new MenuBarItem ( - _currentEditMenuBarItem.Title, + currentEditMenuBarItem.Title, new MenuItem [] { }, - _currentEditMenuBarItem.Parent + currentEditMenuBarItem.Parent ); } - SetListViewSource (_currentEditMenuBarItem, true); + SetListViewSource (currentEditMenuBarItem, true); } - else if (_currentEditMenuBarItem is MenuBarItem && _currentEditMenuBarItem.Parent != null) + else if (currentEditMenuBarItem is MenuBarItem && currentEditMenuBarItem.Parent != null) { - _frmMenuDetails.UpdateParent (ref _currentEditMenuBarItem); + frmMenuDetails.UpdateParent (ref currentEditMenuBarItem); - _currentEditMenuBarItem = new ( + currentEditMenuBarItem = new ( menuItem.Title, menuItem.Help, - _frmMenuDetails.CreateAction (_currentEditMenuBarItem, menuItem), + frmMenuDetails.CreateAction (currentEditMenuBarItem, menuItem), null, - _currentEditMenuBarItem.Parent + currentEditMenuBarItem.Parent ); } else { - if (_currentEditMenuBarItem is MenuBarItem) + if (currentEditMenuBarItem is MenuBarItem) { - ((MenuBarItem)_currentEditMenuBarItem).Children = null; + ((MenuBarItem)currentEditMenuBarItem).Children = null; DataContext.Menus = new (); } - _currentEditMenuBarItem.Action = - _frmMenuDetails.CreateAction (_currentEditMenuBarItem, menuItem); + currentEditMenuBarItem.Action = + frmMenuDetails.CreateAction (currentEditMenuBarItem, menuItem); - _currentEditMenuBarItem.Shortcut = - ShortcutHelper.GetShortcutFromTag (menuItem.Shortcut); + if (Key.TryParse (menuItem.ShortcutKey, out Key key)) + { + currentEditMenuBarItem.ShortcutKey = key; + } } - if (_currentEditMenuBarItem.Parent == null) + if (currentEditMenuBarItem.Parent == null) { - DataContext.MenuBar = _currentEditMenuBarItem.Title; + DataContext.MenuBar = currentEditMenuBarItem.Title; } else { @@ -1309,7 +1271,7 @@ void UpdateMenuItem (MenuItem _currentEditMenuBarItem, DynamicMenuItem menuItem, DataContext.Menus.Add ( new () { - Title = _currentEditMenuBarItem.Title, MenuItem = _currentEditMenuBarItem + Title = currentEditMenuBarItem.Title, MenuItem = currentEditMenuBarItem } ); } @@ -1317,12 +1279,12 @@ void UpdateMenuItem (MenuItem _currentEditMenuBarItem, DynamicMenuItem menuItem, DataContext.Menus [index] = new () { - Title = _currentEditMenuBarItem.Title, MenuItem = _currentEditMenuBarItem + Title = currentEditMenuBarItem.Title, MenuItem = currentEditMenuBarItem }; } - _currentEditMenuBarItem.CheckType = menuItem.CheckStyle; - SetFrameDetails (_currentEditMenuBarItem); + currentEditMenuBarItem.CheckType = menuItem.CheckStyle; + SetFrameDetails (currentEditMenuBarItem); } //_frmMenuDetails.Initialized += (s, e) => _frmMenuDetails.Enabled = false; @@ -1339,7 +1301,8 @@ public class DynamicMenuItem public bool HasSubMenu { get; set; } public string Help { get; set; } = string.Empty; public bool IsTopLevel { get; set; } - public string Shortcut { get; set; } + public string HotKey { get; set; } + public string ShortcutKey { get; set; } public string Title { get; set; } = "_New"; } @@ -1347,7 +1310,7 @@ public class DynamicMenuItemList { public MenuItem MenuItem { get; set; } public string Title { get; set; } - public override string ToString () { return $"{Title}, {MenuItem}"; } + public override string ToString () { return $"{Title}, {MenuItem.HotKey}, {MenuItem.ShortcutKey} "; } } public class DynamicMenuItemModel : INotifyPropertyChanged diff --git a/UICatalog/Scenarios/DynamicStatusBar.cs b/UICatalog/Scenarios/DynamicStatusBar.cs index 551e4ad36f..828b2dab71 100644 --- a/UICatalog/Scenarios/DynamicStatusBar.cs +++ b/UICatalog/Scenarios/DynamicStatusBar.cs @@ -15,7 +15,7 @@ public class DynamicStatusBar : Scenario { public override void Main () { - + Application.Init (); Application.Run ().Dispose (); Application.Shutdown (); } @@ -125,58 +125,9 @@ public DynamicStatusBarDetails () TextShortcut.KeyDown += (s, e) => { - if (!ProcessKey (e)) - { - return; - } + TextShortcut.Text = e.ToString (); - if (CheckShortcut (e.KeyCode, true)) - { - e.Handled = true; - } }; - - bool ProcessKey (Key ev) - { - switch (ev.KeyCode) - { - case KeyCode.CursorUp: - case KeyCode.CursorDown: - case KeyCode.Tab: - case KeyCode.Tab | KeyCode.ShiftMask: - return false; - } - - return true; - } - - bool CheckShortcut (KeyCode k, bool pre) - { - Shortcut m = _statusItem != null ? _statusItem : new Shortcut (k, "", null); - - if (pre && !ShortcutHelper.PreShortcutValidation (k)) - { - TextShortcut.Text = ""; - - return false; - } - - if (!pre) - { - return true; - } - - TextShortcut.Text = k.ToString (); - return true; - } - - TextShortcut.KeyUp += (s, e) => - { - if (CheckShortcut (e.KeyCode, true)) - { - e.Handled = true; - } - }; Add (TextShortcut); var _btnShortcut = new Button @@ -210,7 +161,7 @@ public void EditStatusItem (Shortcut statusItem) ? GetTargetAction (statusItem.Action) : string.Empty; - TextShortcut.Text = statusItem.CommandView.Text; + TextShortcut.Text = statusItem.Key; } public DynamicStatusItem EnterStatusItem () @@ -238,13 +189,6 @@ public DynamicStatusItem EnterStatusItem () } else { - if (!string.IsNullOrEmpty (TextShortcut.Text)) - { - TextTitle.Text = DynamicStatusBarSample.SetTitleText ( - TextTitle.Text, - TextShortcut.Text - ); - } valid = true; Application.RequestStop (); @@ -433,10 +377,6 @@ public DynamicStatusBarSample () } else if (_currentEditStatusItem != null) { - _frmStatusBarDetails.TextTitle.Text = SetTitleText ( - _frmStatusBarDetails.TextTitle.Text, - _frmStatusBarDetails.TextShortcut.Text - ); var statusItem = new DynamicStatusItem { @@ -487,6 +427,7 @@ public DynamicStatusBarSample () if (statusItem != null) { _statusBar.RemoveShortcut (_currentSelectedStatusBar); + statusItem.Dispose (); DataContext.Items.RemoveAt (_lstItems.SelectedItem); if (_lstItems.Source.Count > 0 && _lstItems.SelectedItem > _lstItems.Source.Count - 1) @@ -526,6 +467,7 @@ public DynamicStatusBarSample () } Remove (_statusBar); + _statusBar.Dispose (); _statusBar = null; DataContext.Items = []; _currentStatusItem = null; @@ -588,7 +530,7 @@ void SetListViewSource (Shortcut _currentStatusItem, bool fill = false) Shortcut CreateNewStatusBar (DynamicStatusItem item) { - var newStatusItem = new Shortcut (Key.Empty, item.Title, null); + var newStatusItem = new Shortcut (item.Shortcut, item.Title, _frmStatusBarDetails.CreateAction (item)); return newStatusItem; } @@ -599,8 +541,9 @@ void UpdateStatusItem ( int index ) { - _currentEditStatusItem = CreateNewStatusBar (statusItem); - //_statusBar.Items [index] = _currentEditStatusItem; + _statusBar.Subviews [index].Title = statusItem.Title; + ((Shortcut)_statusBar.Subviews [index]).Action = _frmStatusBarDetails.CreateAction (statusItem); + ((Shortcut)_statusBar.Subviews [index]).Key = statusItem.Shortcut; if (DataContext.Items.Count == 0) { @@ -624,23 +567,9 @@ int index public DynamicStatusItemModel DataContext { get; set; } - public static string SetTitleText (string title, string shortcut) - { - string txt = title; - string [] split = title.Split ('~'); - if (split.Length > 1) - { - txt = split [2].Trim (); - } - if (string.IsNullOrEmpty (shortcut) || shortcut == "Null") - { - return txt; - } - return $"~{shortcut}~ {txt}"; - } } public class DynamicStatusItem @@ -662,7 +591,7 @@ public DynamicStatusItemList (string title, Shortcut statusItem) public Shortcut Shortcut { get; set; } public string Title { get; set; } - public override string ToString () { return $"{Title}, {Shortcut}"; } + public override string ToString () { return $"{Title}, {Shortcut.Key}"; } } public class DynamicStatusItemModel : INotifyPropertyChanged diff --git a/UICatalog/Scenarios/VkeyPacketSimulator.cs b/UICatalog/Scenarios/VkeyPacketSimulator.cs index 975775f454..66e8267432 100644 --- a/UICatalog/Scenarios/VkeyPacketSimulator.cs +++ b/UICatalog/Scenarios/VkeyPacketSimulator.cs @@ -119,7 +119,7 @@ public override void Main () "Keys", $"'{Key.ToString ( e.KeyCode, - MenuBar.ShortcutDelimiter + Key.Separator )}' pressed!", "Ok" ) diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 25425782db..60353f59fd 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -676,7 +676,7 @@ public void ConfigChanged () ColorScheme = Colors.ColorSchemes [_topLevelColorScheme]; - MenuBar!.Menus [0].Children [0].Shortcut = (KeyCode)Application.QuitKey; + MenuBar!.Menus [0].Children [0].ShortcutKey = Application.QuitKey; if (StatusBar is { }) { @@ -700,8 +700,8 @@ public void ConfigChanged () { var item = new MenuItem { - Title = $"_{theme.Key}", - Shortcut = (KeyCode)new Key ((KeyCode)((uint)KeyCode.D1 + schemeCount++)) + Title = theme.Key == "Dark" ? $"{theme.Key.Substring (0, 3)}_{theme.Key.Substring (3, 1)}" : $"_{theme.Key}", + ShortcutKey = new Key ((KeyCode)((uint)KeyCode.D1 + schemeCount++)) .WithCtrl }; item.CheckType |= MenuItemCheckStyle.Checked; @@ -735,6 +735,7 @@ public void ConfigChanged () ColorScheme = Colors.ColorSchemes [_topLevelColorScheme]; Application.Top!.SetNeedsDisplay (); }; + item.ShortcutKey = ((Key)sc.Key [0].ToString ().ToLower ()).WithCtrl; schemeMenuItems.Add (item); } @@ -796,7 +797,7 @@ private MenuItem [] CreateDiagnosticFlagsMenuItems () { var item = new MenuItem { - Title = GetDiagnosticsTitle (diag), Shortcut = (KeyCode)new Key (index.ToString () [0]).WithAlt + Title = GetDiagnosticsTitle (diag), ShortcutKey = new Key (index.ToString () [0]).WithAlt }; index++; item.CheckType |= MenuItemCheckStyle.Checked; @@ -951,9 +952,8 @@ private MenuItem [] CreateDisabledEnabledMenuBorder () List menuItems = new (); MiIsMenuBorderDisabled = new () { Title = "Disable Menu _Border" }; - MiIsMenuBorderDisabled.Shortcut = - (KeyCode)new Key (MiIsMenuBorderDisabled!.Title!.Substring (14, 1) [0]).WithAlt - .WithCtrl.NoShift; + MiIsMenuBorderDisabled.ShortcutKey = + new Key (MiIsMenuBorderDisabled!.Title!.Substring (14, 1) [0]).WithAlt.WithCtrl.NoShift; MiIsMenuBorderDisabled.CheckType |= MenuItemCheckStyle.Checked; MiIsMenuBorderDisabled.Action += () => @@ -974,8 +974,8 @@ private MenuItem [] CreateDisabledEnabledMouseItems () List menuItems = new (); MiIsMouseDisabled = new () { Title = "_Disable Mouse" }; - MiIsMouseDisabled.Shortcut = - (KeyCode)new Key (MiIsMouseDisabled!.Title!.Substring (1, 1) [0]).WithAlt.WithCtrl.NoShift; + MiIsMouseDisabled.ShortcutKey = + new Key (MiIsMouseDisabled!.Title!.Substring (1, 1) [0]).WithAlt.WithCtrl.NoShift; MiIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked; MiIsMouseDisabled.Action += () => @@ -994,7 +994,7 @@ private MenuItem [] CreateDisabledEnableUseSubMenusSingleFrame () List menuItems = new (); MiUseSubMenusSingleFrame = new () { Title = "Enable _Sub-Menus Single Frame" }; - MiUseSubMenusSingleFrame.Shortcut = KeyCode.CtrlMask + MiUseSubMenusSingleFrame.ShortcutKey = KeyCode.CtrlMask | KeyCode.AltMask | (KeyCode)MiUseSubMenusSingleFrame!.Title!.Substring (8, 1) [ 0]; @@ -1017,7 +1017,7 @@ private MenuItem [] CreateForce16ColorItems () MiForce16Colors = new () { Title = "Force _16 Colors", - Shortcut = (KeyCode)Key.F6, + ShortcutKey = Key.F6, Checked = Application.Force16Colors, CanExecute = () => Application.Driver?.SupportsTrueColor ?? false }; diff --git a/UnitTests/Configuration/SerializableConfigurationPropertyTests.cs b/UnitTests/Configuration/SerializableConfigurationPropertyTests.cs index 71ca1b84f3..4b6bb12ddc 100644 --- a/UnitTests/Configuration/SerializableConfigurationPropertyTests.cs +++ b/UnitTests/Configuration/SerializableConfigurationPropertyTests.cs @@ -41,17 +41,11 @@ public void Test_SerializableConfigurationProperty_Types_Added_To_JsonSerializer } // Ensure no property has the generic JsonStringEnumConverter<> - foreach (var property in properties) - { - var jsonConverterAttributes = property.GetCustomAttributes (typeof (JsonConverterAttribute), false) - .Cast (); - - foreach (var attribute in jsonConverterAttributes) - { - Assert.False (attribute.ConverterType!.IsGenericType && - attribute.ConverterType.GetGenericTypeDefinition () == typeof (JsonStringEnumConverter<>)); - } - } + EnsureNoSpecifiedConverters (properties, new [] { typeof (JsonStringEnumConverter<>) }); + // Ensure no property has the type RuneJsonConverter + EnsureNoSpecifiedConverters (properties, new [] { typeof (RuneJsonConverter) }); + // Ensure no property has the type KeyJsonConverter + EnsureNoSpecifiedConverters (properties, new [] { typeof (KeyJsonConverter) }); // Find all classes with the JsonConverter attribute of type ScopeJsonConverter<> var classesWithScopeJsonConverter = types.Where (t => @@ -66,7 +60,7 @@ public void Test_SerializableConfigurationProperty_Types_Added_To_JsonSerializer } } - private IEnumerable GetRegisteredTypes (Type contextType) + private static IEnumerable GetRegisteredTypes (Type contextType) { // Use reflection to find which types are registered in the JsonSerializerContext var registeredTypes = new List (); @@ -83,4 +77,32 @@ private IEnumerable GetRegisteredTypes (Type contextType) return registeredTypes.Distinct (); } + + private static void EnsureNoSpecifiedConverters (List properties, IEnumerable converterTypes) + { + // Ensure no property has any of the specified converter types + foreach (var property in properties) + { + var jsonConverterAttributes = property.GetCustomAttributes (typeof (JsonConverterAttribute), false) + .Cast (); + + foreach (var attribute in jsonConverterAttributes) + { + foreach (var converterType in converterTypes) + { + if (attribute.ConverterType!.IsGenericType && + attribute.ConverterType.GetGenericTypeDefinition () == converterType) + { + Assert.Fail ($"Property '{property.Name}' should not use the converter '{converterType.Name}'."); + } + + if (!attribute.ConverterType!.IsGenericType && + attribute.ConverterType == converterType) + { + Assert.Fail ($"Property '{property.Name}' should not use the converter '{converterType.Name}'."); + } + } + } + } + } } diff --git a/UnitTests/Input/KeyTests.cs b/UnitTests/Input/KeyTests.cs index 0a23a0fccd..b3973b80e2 100644 --- a/UnitTests/Input/KeyTests.cs +++ b/UnitTests/Input/KeyTests.cs @@ -533,7 +533,7 @@ public void Equals_ShouldReturnTrue_WhenEqual () var b = Key.A; Assert.True (a.Equals (b)); } - + [Fact] public void Equals_Handled_Changed_ShouldReturnTrue_WhenEqual () { @@ -552,4 +552,20 @@ public void Equals_Handled_Changed_ShouldReturnFalse_WhenNotEqual () var b = Key.A; Assert.False (a.Equals (b)); } + + [Fact] + public void Set_Key_Separator_With_Rune_Default_Ensure_Using_The_Default_Plus () + { + Key key = new (Key.A.WithCtrl); + Assert.Equal ((Rune)'+', Key.Separator); + Assert.Equal ("Ctrl+A", key.ToString ()); + + Key.Separator = new ('-'); + Assert.Equal ((Rune)'-', Key.Separator); + Assert.Equal ("Ctrl-A", key.ToString ()); + + Key.Separator = new (); + Assert.Equal ((Rune)'+', Key.Separator); + Assert.Equal ("Ctrl+A", key.ToString ()); + } } diff --git a/UnitTests/Views/MenuBarTests.cs b/UnitTests/Views/MenuBarTests.cs index e8d5ad3dc0..ac8de4db0c 100644 --- a/UnitTests/Views/MenuBarTests.cs +++ b/UnitTests/Views/MenuBarTests.cs @@ -1,4 +1,5 @@ -using Xunit.Abstractions; +using System.Text; +using Xunit.Abstractions; namespace Terminal.Gui.ViewsTests; @@ -217,7 +218,7 @@ public void Constructors_Defaults () Assert.Null (menuBarItem.Action); Assert.Null (menuBarItem.CanExecute); Assert.Null (menuBarItem.Parent); - Assert.Equal (KeyCode.Null, menuBarItem.Shortcut); + Assert.Equal (Key.Empty, menuBarItem.ShortcutKey); } [Fact] @@ -1229,7 +1230,7 @@ public void Key_Open_And_Close_The_MenuBar () )] [InlineData ("Closed", "None", "About", KeyCode.F9, KeyCode.CursorRight, KeyCode.CursorRight, KeyCode.Enter)] - // Hotkeys + //// Hotkeys [InlineData ("_File", "_New", "", KeyCode.AltMask | KeyCode.F)] [InlineData ("Closed", "None", "", KeyCode.AltMask | KeyCode.ShiftMask | KeyCode.F)] [InlineData ("Closed", "None", "", KeyCode.AltMask | KeyCode.F, KeyCode.Esc)] @@ -1245,9 +1246,10 @@ public void Key_Open_And_Close_The_MenuBar () [InlineData ("_Edit", "_1st", "", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3)] [InlineData ("Closed", "None", "1", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.D1)] [InlineData ("Closed", "None", "1", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.Enter)] - [InlineData ("_Edit", "_3rd Level", "", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.D4)] + [InlineData ("Closed", "None", "2", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.D2)] + [InlineData ("_Edit", "_5th", "", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.D4)] [InlineData ("Closed", "None", "5", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D4, KeyCode.D5)] - [InlineData ("_About", "_About", "", KeyCode.AltMask | KeyCode.A)] + [InlineData ("Closed", "None", "About", KeyCode.AltMask | KeyCode.A)] public void KeyBindings_Navigation_Commands ( string expectedBarTitle, string expectedItemTitle, @@ -1283,7 +1285,7 @@ params KeyCode [] keys top.Add (menu); Application.Begin (top); - foreach (KeyCode key in keys) + foreach (Key key in keys) { top.NewKeyDownEvent (new (key)); Application.MainLoop.RunIteration (); @@ -3725,4 +3727,87 @@ public void Click_Another_View_Close_An_Open_Menu () Assert.True (btnClicked); top.Dispose (); } + + [Fact] + public void Update_ShortcutKey_KeyBindings_Old_ShortcutKey_Is_Removed () + { + var menuBar = new MenuBar () + { + Menus = + [ + new MenuBarItem ( + "_File", + new MenuItem [] + { + new MenuItem ("New", "Create New", null, null, null, Key.A.WithCtrl) + } + ) + ] + }; + + Assert.Contains (Key.A.WithCtrl, menuBar.KeyBindings.Bindings); + + menuBar.Menus [0].Children [0].ShortcutKey = Key.B.WithCtrl; + + Assert.DoesNotContain (Key.A.WithCtrl, menuBar.KeyBindings.Bindings); + Assert.Contains (Key.B.WithCtrl, menuBar.KeyBindings.Bindings); + } + + [Fact] + public void SetMenus_With_Same_HotKey_Does_Not_Throws () + { + var mb = new MenuBar (); + + var i1 = new MenuBarItem ("_heey", "fff", () => { }, () => true); + + mb.Menus = new MenuBarItem [] { i1 }; + mb.Menus = new MenuBarItem [] { i1 }; + + Assert.Equal (Key.H, mb.Menus [0].HotKey); + } + + [Fact] + [AutoInitShutdown] + public void AddMenuBarItem_RemoveMenuItem_Dynamically () + { + var menuBar = new MenuBar (); + var menuBarItem = new MenuBarItem { Title = "_New" }; + var action = ""; + var menuItem = new MenuItem { Title = "_Item", Action = () => action = "I", Parent = menuBarItem }; + Assert.Equal ("n", menuBarItem.HotKey); + Assert.Equal ("i", menuItem.HotKey); + Assert.Empty (menuBar.Menus); + menuBarItem.AddMenuBarItem (menuItem); + menuBar.Menus = [menuBarItem]; + Assert.Single (menuBar.Menus); + Assert.Single (menuBar.Menus [0].Children); + Assert.Contains (Key.N.WithAlt, menuBar.KeyBindings.Bindings); + Assert.DoesNotContain (Key.I, menuBar.KeyBindings.Bindings); + + var top = new Toplevel (); + top.Add (menuBar); + Application.Begin (top); + + top.NewKeyDownEvent (Key.N.WithAlt); + Application.MainLoop.RunIteration (); + Assert.True (menuBar.IsMenuOpen); + Assert.Equal ("", action); + + top.NewKeyDownEvent (Key.I); + Application.MainLoop.RunIteration (); + Assert.False (menuBar.IsMenuOpen); + Assert.Equal ("I", action); + + menuItem.RemoveMenuItem (); + Assert.Single (menuBar.Menus); + Assert.Equal (null, menuBar.Menus [0].Children); + Assert.Contains (Key.N.WithAlt, menuBar.KeyBindings.Bindings); + Assert.DoesNotContain (Key.I, menuBar.KeyBindings.Bindings); + + menuBarItem.RemoveMenuItem (); + Assert.Empty (menuBar.Menus); + Assert.DoesNotContain (Key.N.WithAlt, menuBar.KeyBindings.Bindings); + + top.Dispose (); + } } diff --git a/UnitTests/Views/MenuTests.cs b/UnitTests/Views/MenuTests.cs index f87e3c73f7..05cda33495 100644 --- a/UnitTests/Views/MenuTests.cs +++ b/UnitTests/Views/MenuTests.cs @@ -31,7 +31,7 @@ public void MenuItem_Constructors_Defaults () Assert.Null (menuItem.Action); Assert.Null (menuItem.CanExecute); Assert.Null (menuItem.Parent); - Assert.Equal (KeyCode.Null, menuItem.Shortcut); + Assert.Equal (Key.Empty, menuItem.ShortcutKey); menuItem = new MenuItem ("Test", "Help", Run, () => { return true; }, new MenuItem (), KeyCode.F1); Assert.Equal ("Test", menuItem.Title); @@ -39,7 +39,7 @@ public void MenuItem_Constructors_Defaults () Assert.Equal (Run, menuItem.Action); Assert.NotNull (menuItem.CanExecute); Assert.NotNull (menuItem.Parent); - Assert.Equal (KeyCode.F1, menuItem.Shortcut); + Assert.Equal (KeyCode.F1, menuItem.ShortcutKey); void Run () { } }