namespace Terminal.Gui; /// /// A has title, an associated help text, and an action to execute on activation. MenuItems /// can also have a checked indicator (see ). /// public class MenuItem { private readonly ShortcutHelper _shortcutHelper; private bool _allowNullChecked; private MenuItemCheckStyle _checkType; 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) { } // 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. public MenuItem ( string title, string help, Action action, Func canExecute = null, MenuItem parent = null, KeyCode shortcut = KeyCode.Null ) { Title = title ?? ""; Help = help ?? ""; Action = action; CanExecute = canExecute; Parent = parent; _shortcutHelper = new (); if (shortcut != KeyCode.Null) { Shortcut = shortcut; } } /// Gets or sets the action to be invoked when the menu item is triggered. /// Method to invoke. public Action Action { get; set; } /// /// Used only if is of type. If /// allows to be null, true or false. If only /// allows to be true or false. /// public bool AllowNullChecked { get => _allowNullChecked; set { _allowNullChecked = value; Checked ??= false; } } /// /// Gets or sets the action to be invoked to determine if the menu can be triggered. If /// returns the menu item will be enabled. Otherwise, it will be disabled. /// /// Function to determine if the action is can be executed or not. public Func CanExecute { get; set; } /// /// Sets or gets whether the shows a check indicator or not. See /// . /// public bool? Checked { set; get; } /// /// Sets or gets the of a menu item where is set to /// . /// public MenuItemCheckStyle CheckType { get => _checkType; set { _checkType = value; if (_checkType == MenuItemCheckStyle.Checked && !_allowNullChecked && Checked is null) { Checked = false; } } } /// Gets or sets arbitrary data for the menu item. /// This property is not used internally. public object Data { get; set; } /// Gets or sets the help text for the menu item. The help text is drawn to the right of the . /// The help text. public string Help { get; set; } /// Gets the parent for this . /// The parent. public MenuItem Parent { get; set; } /// Gets or sets the title of the menu item . /// The title. public string Title { get => _title; set { if (_title == value) { return; } _title = value; GetHotKey (); } } /// 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 . /// public void ToggleChecked () { if (_checkType != MenuItemCheckStyle.Checked) { throw new InvalidOperationException ("This isn't a Checked MenuItemCheckStyle!"); } bool? previousChecked = Checked; if (AllowNullChecked) { Checked = previousChecked switch { null => true, true => false, false => null }; } else { Checked = !Checked; } } private static int GetMenuBarItemLength (string title) { return title.EnumerateRunes () .Where (ch => ch != MenuBar.HotKeySpecifier) .Sum (ch => Math.Max (ch.GetColumns (), 1)); } #region Keyboard Handling // TODO: Update to use Key instead of Rune /// /// 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. /// /// /// 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. /// public Rune HotKey { get; set; } private void GetHotKey () { var nextIsHot = false; foreach (char x in _title) { if (x == MenuBar.HotKeySpecifier.Value) { nextIsHot = true; } else { if (nextIsHot) { HotKey = (Rune)char.ToUpper (x); break; } nextIsHot = false; HotKey = default (Rune); } } } // TODO: Update to use Key instead of KeyCode /// /// 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 /// text. See . /// /// public KeyCode Shortcut { get => _shortcutHelper.Shortcut; set { if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == KeyCode.Null)) { _shortcutHelper.Shortcut = value; } } } /// Gets the text describing the keystroke combination defined by . public string ShortcutTag => _shortcutHelper.Shortcut == KeyCode.Null ? string.Empty : Key.ToString (_shortcutHelper.Shortcut, MenuBar.ShortcutDelimiter); #endregion Keyboard Handling }