using System;
using NStack;
using System.Linq;
using System.Collections.Generic;
namespace Terminal.Gui {
///
/// Specifies how a shows selection state.
///
[Flags]
public enum MenuItemCheckStyle {
///
/// The menu item will be shown normally, with no check indicator. The default.
///
NoCheck = 0b_0000_0000,
///
/// The menu item will indicate checked/un-checked state (see ).
///
Checked = 0b_0000_0001,
///
/// The menu item is part of a menu radio group (see ) and will indicate selected state.
///
Radio = 0b_0000_0010,
};
///
/// 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 {
ustring title;
ShortcutHelper shortcutHelper;
bool allowNullChecked;
MenuItemCheckStyle checkType;
internal int TitleLength => GetMenuBarItemLength (Title);
///
/// Gets or sets arbitrary data for the menu item.
///
/// This property is not used internally.
public object Data { get; set; }
///
/// Initializes a new instance of
///
public MenuItem (Key shortcut = Key.Null) : this ("", "", null, null, null, shortcut) { }
///
/// 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 (ustring title, ustring help, Action action, Func canExecute = null, MenuItem parent = null, Key shortcut = Key.Null)
{
Title = title ?? "";
Help = help ?? "";
Action = action;
CanExecute = canExecute;
Parent = parent;
shortcutHelper = new ShortcutHelper ();
if (shortcut != Key.Null) {
shortcutHelper.Shortcut = shortcut;
}
}
///
/// 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;
///
/// 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 Key Shortcut {
get => shortcutHelper.Shortcut;
set {
if (shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == Key.Null)) {
shortcutHelper.Shortcut = value;
}
}
}
///
/// Gets the text describing the keystroke combination defined by .
///
public ustring ShortcutTag => ShortcutHelper.GetShortcutTag (shortcutHelper.Shortcut);
///
/// Gets or sets the title of the menu item .
///
/// The title.
public ustring Title {
get { return title; }
set {
if (title != value) {
title = value;
GetHotKey ();
}
}
}
///
/// Gets or sets the help text for the menu item. The help text is drawn to the right of the .
///
/// The help text.
public ustring Help { get; set; }
///
/// Gets or sets the action to be invoked when the menu item is triggered.
///
/// Method to invoke.
public Action Action { get; set; }
///
/// 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; }
///
/// Returns if the menu item is enabled. This method is a wrapper around .
///
public bool IsEnabled ()
{
return CanExecute == null ? true : CanExecute ();
}
//
// ┌─────────────────────────────┐
// │ 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.ConsoleWidth > 0 ? 2 + Help.ConsoleWidth : 0) + // Two spaces before Help
(ShortcutTag.ConsoleWidth > 0 ? 2 + ShortcutTag.ConsoleWidth : 0); // Pad two spaces before shortcut tag (which are also aligned right)
///
/// Sets or gets whether the shows a check indicator or not. See .
///
public bool? Checked { set; get; }
///
/// 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;
if (Checked == null) {
Checked = false;
}
}
}
///
/// 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 == null) {
Checked = false;
}
}
}
///
/// Gets the parent for this .
///
/// The parent.
public MenuItem Parent { get; internal set; }
///
/// Gets if this is from a sub-menu.
///
internal bool IsFromSubMenu { get { return Parent != null; } }
///
/// Merely a debugging aid to see the interaction with main.
///
public MenuItem GetMenuItem ()
{
return this;
}
///
/// Merely a debugging aid to see the interaction with main.
///
public bool GetMenuBarItem ()
{
return IsFromSubMenu;
}
///
/// 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!");
}
var previousChecked = Checked;
if (AllowNullChecked) {
switch (previousChecked) {
case null:
Checked = true;
break;
case true:
Checked = false;
break;
case false:
Checked = null;
break;
}
} else {
Checked = !Checked;
}
}
void GetHotKey ()
{
bool nextIsHot = false;
foreach (var x in title) {
if (x == MenuBar.HotKeySpecifier) {
nextIsHot = true;
} else {
if (nextIsHot) {
HotKey = Char.ToUpper ((char)x);
break;
}
nextIsHot = false;
HotKey = default;
}
}
}
int GetMenuBarItemLength (ustring title)
{
int len = 0;
foreach (var ch in title) {
if (ch == MenuBar.HotKeySpecifier)
continue;
len += Math.Max (Rune.ColumnWidth (ch), 1);
}
return len;
}
}
///
/// is a menu item on an app's .
/// MenuBarItems do not support .
///
public class MenuBarItem : MenuItem {
///
/// Initializes a new as a .
///
/// Title for the menu item.
/// Help text to display. Will be displayed next to the Title surrounded by parentheses.
/// Action to invoke when the menu item is activated.
/// Function to determine if the action can currently be executed.
/// The parent of this if exist, otherwise is null.
public MenuBarItem (ustring title, ustring help, Action action, Func canExecute = null, MenuItem parent = null) : base (title, help, action, canExecute, parent)
{
Initialize (title, null, null, true);
}
///
/// Initializes a new .
///
/// Title for the menu item.
/// The items in the current menu.
/// The parent of this if exist, otherwise is null.
public MenuBarItem (ustring title, MenuItem [] children, MenuItem parent = null)
{
Initialize (title, children, parent);
}
///
/// Initializes a new with separate list of items.
///
/// Title for the menu item.
/// The list of items in the current menu.
/// The parent of this if exist, otherwise is null.
public MenuBarItem (ustring title, List