#nullable enable
using System.ComponentModel;
using System.Diagnostics;
namespace Terminal.Gui;
///
/// A horizontal list of s. Each can have a
/// that is shown when the is selected.
///
///
/// MenuBars may be hosted by any View and will, by default, be positioned the full width across the top of the View's
/// Viewport.
///
public class MenuBarv2 : Menuv2, IDesignable
{
///
public MenuBarv2 () : this ([]) { }
///
public MenuBarv2 (IEnumerable menuBarItems) : base (menuBarItems)
{
CanFocus = false;
TabStop = TabBehavior.TabGroup;
Y = 0;
Width = Dim.Fill ();
Orientation = Orientation.Horizontal;
Key = DefaultKey;
AddCommand (Command.HotKey,
() =>
{
if (HideActiveItem ())
{
return true;
}
if (SubViews.FirstOrDefault (sv => sv is MenuBarItemv2 { PopoverMenu: { } }) is MenuBarItemv2 { } first)
{
_active = true;
ShowPopover (first);
return true;
}
return false;
});
HotKeyBindings.Add (Key, Command.HotKey);
KeyBindings.Add (Key, Command.Quit);
KeyBindings.ReplaceCommands (Application.QuitKey, Command.Quit);
AddCommand (
Command.Quit,
ctx =>
{
if (HideActiveItem ())
{
return true;
}
if (CanFocus)
{
CanFocus = false;
_active = false;
return true;
}
return false;//RaiseAccepted (ctx);
});
AddCommand (Command.Right, MoveRight);
KeyBindings.Add (Key.CursorRight, Command.Right);
AddCommand (Command.Left, MoveLeft);
KeyBindings.Add (Key.CursorLeft, Command.Left);
return;
bool? MoveLeft (ICommandContext? ctx) { return AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop); }
bool? MoveRight (ICommandContext? ctx) { return AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); }
}
private Key _key = DefaultKey;
/// Specifies the key that will activate the context menu.
public Key Key
{
get => _key;
set
{
Key oldKey = _key;
_key = value;
KeyChanged?.Invoke (this, new (oldKey, _key));
}
}
///
/// Sets the Menu Bar Items for this Menu Bar. This will replace any existing Menu Bar Items.
///
///
///
/// This is a convenience property to help porting from the v1 MenuBar.
///
///
public MenuBarItemv2 []? Menus
{
set
{
RemoveAll ();
if (value is null)
{
return;
}
foreach (MenuBarItemv2 mbi in value)
{
Add (mbi);
}
}
}
/// Raised when is changed.
public event EventHandler? KeyChanged;
/// The default key for activating menu bars.
[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
public static Key DefaultKey { get; set; } = Key.F9;
///
/// Gets whether any of the menu bar items have a visible .
///
///
public bool IsOpen ()
{
return SubViews.Count (sv => sv is MenuBarItemv2 { PopoverMenu: { Visible: true } }) > 0;
}
private bool _active;
///
/// Returns a value indicating whether the menu bar is active or not. When active, moving the mouse
/// over a menu bar item will activate it.
///
///
public bool IsActive ()
{
return _active;
}
///
protected override bool OnMouseEnter (CancelEventArgs eventArgs)
{
// If the MenuBar does not have focus and the mouse enters: Enable CanFocus
// But do NOT show a Popover unless the user clicks or presses a hotkey
if (!HasFocus)
{
CanFocus = true;
}
return base.OnMouseEnter (eventArgs);
}
///
protected override void OnMouseLeave ()
{
if (!IsOpen ())
{
CanFocus = false;
}
base.OnMouseLeave ();
}
///
protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
{
if (!newHasFocus)
{
_active = false;
CanFocus = false;
}
}
///
protected override void OnSelectedMenuItemChanged (MenuItemv2? selected)
{
if (selected is MenuBarItemv2 { PopoverMenu.Visible: false } selectedMenuBarItem)
{
ShowPopover (selectedMenuBarItem);
}
}
///
public override void EndInit ()
{
base.EndInit ();
if (Border is { })
{
Border.Thickness = new (0);
Border.LineStyle = LineStyle.None;
}
// TODO: This needs to be done whenever a menuitem in any MenuBarItem changes
foreach (MenuBarItemv2? mbi in SubViews.Select (s => s as MenuBarItemv2))
{
Application.Popover?.Register (mbi?.PopoverMenu);
}
}
///
protected override bool OnAccepting (CommandEventArgs args)
{
Logging.Trace ($"{args.Context?.Source?.Title}");
if (Visible && args.Context?.Source is MenuBarItemv2 { PopoverMenu.Visible: false } sourceMenuBarItem)
{
_active = true;
if (!CanFocus)
{
// Enabling CanFocus will cause focus to change, which will cause OnSelectedMenuItem to change
// This will call ShowPopover
CanFocus = true;
sourceMenuBarItem.SetFocus ();
}
else
{
ShowPopover (sourceMenuBarItem);
}
return true;
}
return base.OnAccepting (args);
}
///
/// Shows the specified popover, but only if the menu bar is active.
///
///
private void ShowPopover (MenuBarItemv2? menuBarItem)
{
Logging.Trace ($"{menuBarItem?.Id}");
if (!_active || !Visible)
{
return;
}
//menuBarItem!.PopoverMenu.Id = menuBarItem.Id;
// TODO: We should init the PopoverMenu in a smarter way
if (menuBarItem?.PopoverMenu is { IsInitialized: false })
{
menuBarItem.PopoverMenu.BeginInit ();
menuBarItem.PopoverMenu.EndInit ();
}
// If the active Application Popover is part of this MenuBar, hide it.
//HideActivePopover ();
if (Application.Popover?.GetActivePopover () is PopoverMenu popoverMenu
&& popoverMenu?.Root?.SuperMenuItem?.SuperView == this)
{
Application.Popover?.Hide (popoverMenu);
}
if (menuBarItem is null)
{
return;
}
if (menuBarItem.PopoverMenu is { })
{
menuBarItem.PopoverMenu.Accepted += (sender, args) =>
{
if (HasFocus)
{
CanFocus = false;
}
};
}
_active = true;
CanFocus = true;
menuBarItem.SetFocus ();
if (menuBarItem.PopoverMenu?.Root is { })
{
menuBarItem.PopoverMenu.Root.SuperMenuItem = menuBarItem;
}
menuBarItem.PopoverMenu?.MakeVisible (new Point (menuBarItem.FrameToScreen ().X, menuBarItem.FrameToScreen ().Bottom));
}
private MenuBarItemv2? GetActiveItem ()
{
return SubViews.FirstOrDefault (sv => sv is MenuBarItemv2 { PopoverMenu: { Visible: true } }) as MenuBarItemv2;
}
///
/// Hides the popover menu associated with the active menu bar item and updates the focus state.
///
/// if the popover was hidden
public bool HideActiveItem ()
{
return HideItem (GetActiveItem ());
}
///
/// Hides popover menu associated with the specified menu bar item and updates the focus state.
///
///
/// if the popover was hidden
public bool HideItem (MenuBarItemv2? activeItem)
{
if (activeItem is null || !activeItem.PopoverMenu!.Visible)
{
return false;
}
_active = false;
HasFocus = false;
activeItem.PopoverMenu!.Visible = false;
CanFocus = false;
return true;
}
///
public bool EnableForDesign (ref readonly TContext context) where TContext : notnull
{
Add (
new MenuBarItemv2 (
"_File",
[
new MenuItemv2 (this, Command.New),
new MenuItemv2 (this, Command.Open),
new MenuItemv2 (this, Command.Save),
new MenuItemv2 (this, Command.SaveAs),
new Line (),
new MenuItemv2
{
Title = "_Preferences",
SubMenu = new (
[
new MenuItemv2
{
CommandView = new CheckBox ()
{
Title = "O_ption",
},
HelpText = "Toggle option"
},
new MenuItemv2
{
Title = "_Settings...",
HelpText = "More settings",
Action = () => MessageBox.Query ("Settings", "This is the Settings Dialog\n", ["_Ok", "_Cancel"])
}
]
)
},
new Line (),
new MenuItemv2 (this, Command.Quit)
]
)
);
Add (
new MenuBarItemv2 (
"_Edit",
[
new MenuItemv2 (this, Command.Cut),
new MenuItemv2 (this, Command.Copy),
new MenuItemv2 (this, Command.Paste),
new Line (),
new MenuItemv2 (this, Command.SelectAll)
]
)
);
Add (
new MenuBarItemv2 (
"_Help",
[
new MenuItemv2
{
Title = "_Online Help...",
Action = () => MessageBox.Query ("Online Help", "https://gui-cs.github.io/Terminal.GuiV2Docs", "Ok")
},
new MenuItemv2
{
Title = "About...",
Action = () => MessageBox.Query ("About", "Something About Mary.", "Ok")
}
]
)
);
return true;
}
}