using System;
namespace Terminal.Gui {
///
/// ContextMenu provides a pop-up menu that can be positioned anywhere within a .
/// ContextMenu is analogous to and, once activated, works like a sub-menu
/// of a (but can be positioned anywhere).
///
/// By default, a ContextMenu with sub-menus is displayed in a cascading manner, where each sub-menu pops out of the ContextMenu frame
/// (either to the right or left, depending on where the ContextMenu is relative to the edge of the screen). By setting
/// to , this behavior can be changed such that all sub-menus are
/// drawn within the ContextMenu frame.
///
///
/// ContextMenus can be activated using the Shift-F10 key (by default; use the to change to another key).
///
///
/// Callers can cause the ContextMenu to be activated on a right-mouse click (or other interaction) by calling .
///
///
/// ContextMenus are located using screen using screen coordinates and appear above all other Views.
///
///
public sealed class ContextMenu : IDisposable {
private static MenuBar menuBar;
private Key key = Key.F10 | Key.ShiftMask;
private MouseFlags mouseFlags = MouseFlags.Button3Clicked;
private Toplevel container;
///
/// Initializes a context menu with no menu items.
///
public ContextMenu () : this (0, 0, new MenuBarItem ()) { }
///
/// Initializes a context menu, with a specifiying the parent/hose of the menu.
///
/// The host view.
/// The menu items for the context menu.
public ContextMenu (View host, MenuBarItem menuItems) :
this (host.Frame.X, host.Frame.Y, menuItems)
{
Host = host;
}
///
/// Initializes a context menu with menu items at a specific screen location.
///
/// The left position (screen relative).
/// The top position (screen relative).
/// The menu items.
public ContextMenu (int x, int y, MenuBarItem menuItems)
{
if (IsShow) {
if (menuBar.SuperView != null) {
Hide ();
}
IsShow = false;
}
MenuItems = menuItems;
Position = new Point (x, y);
}
private void MenuBar_MenuAllClosed (object sender, EventArgs e)
{
Dispose ();
}
///
/// Disposes the context menu object.
///
public void Dispose ()
{
if (IsShow) {
menuBar.MenuAllClosed -= MenuBar_MenuAllClosed;
menuBar.Dispose ();
menuBar = null;
IsShow = false;
}
if (container != null) {
container.Closing -= Container_Closing;
container.Resized -= Container_Resized;
}
}
///
/// Shows (opens) the ContextMenu, displaying the s it contains.
///
public void Show ()
{
if (menuBar != null) {
Hide ();
}
container = Application.Top;
container.Closing += Container_Closing;
container.Resized += Container_Resized;
var frame = container.Frame;
var position = Position;
if (Host != null) {
Host.ViewToScreen (container.Frame.X, container.Frame.Y, out int x, out int y);
var pos = new Point (x, y);
pos.Y += Host.Frame.Height - 1;
if (position != pos) {
Position = position = pos;
}
}
var rect = Menu.MakeFrame (position.X, position.Y, MenuItems.Children);
if (rect.Right >= frame.Right) {
if (frame.Right - rect.Width >= 0 || !ForceMinimumPosToZero) {
position.X = frame.Right - rect.Width;
} else if (ForceMinimumPosToZero) {
position.X = 0;
}
} else if (ForceMinimumPosToZero && position.X < 0) {
position.X = 0;
}
if (rect.Bottom >= frame.Bottom) {
if (frame.Bottom - rect.Height - 1 >= 0 || !ForceMinimumPosToZero) {
if (Host == null) {
position.Y = frame.Bottom - rect.Height - 1;
} else {
Host.ViewToScreen (container.Frame.X, container.Frame.Y, out int x, out int y);
var pos = new Point (x, y);
position.Y = pos.Y - rect.Height - 1;
}
} else if (ForceMinimumPosToZero) {
position.Y = 0;
}
} else if (ForceMinimumPosToZero && position.Y < 0) {
position.Y = 0;
}
menuBar = new MenuBar (new [] { MenuItems }) {
X = position.X,
Y = position.Y,
Width = 0,
Height = 0,
UseSubMenusSingleFrame = UseSubMenusSingleFrame,
Key = Key
};
menuBar.isContextMenuLoading = true;
menuBar.MenuAllClosed += MenuBar_MenuAllClosed;
IsShow = true;
menuBar.OpenMenu ();
}
private void Container_Resized (object sender, SizeChangedEventArgs e)
{
if (IsShow) {
Show ();
}
}
private void Container_Closing (object sender, ToplevelClosingEventArgs obj)
{
Hide ();
}
///
/// Hides (closes) the ContextMenu.
///
public void Hide ()
{
menuBar?.CleanUp ();
Dispose ();
}
///
/// Event invoked when the is changed.
///
public event EventHandler KeyChanged;
///
/// Event invoked when the is changed.
///
public event EventHandler MouseFlagsChanged;
///
/// Gets or sets the menu position.
///
public Point Position { get; set; }
///
/// Gets or sets the menu items for this context menu.
///
public MenuBarItem MenuItems { get; set; }
///
/// specifies they keyboard key that will activate the context menu with the keyboard.
///
public Key Key {
get => key;
set {
var oldKey = key;
key = value;
KeyChanged?.Invoke (this, new KeyChangedEventArgs (oldKey, key));
}
}
///
/// specifies the mouse action used to activate the context menu by mouse.
///
public MouseFlags MouseFlags {
get => mouseFlags;
set {
var oldFlags = mouseFlags;
mouseFlags = value;
MouseFlagsChanged?.Invoke (this, new MouseFlagsChangedEventArgs (oldFlags, value));
}
}
///
/// Gets whether the ContextMenu is showing or not.
///
public static bool IsShow { get; private set; }
///
/// The host which position will be used,
/// otherwise if it's null the container will be used.
///
public View Host { get; set; }
///
/// Sets or gets whether the context menu be forced to the right, ensuring it is not clipped, if the x position
/// is less than zero. The default is which means the context menu will be forced to the right.
/// If set to , the context menu will be clipped on the left if x is less than zero.
///
public bool ForceMinimumPosToZero { get; set; } = true;
///
/// Gets the that is hosting this context menu.
///
public MenuBar MenuBar { get => menuBar; }
///
/// Gets or sets if sub-menus will be displayed using a "single frame" menu style. If , the ContextMenu
/// and any sub-menus that would normally cascade will be displayed within a single frame. If (the default),
/// sub-menus will cascade using separate frames for each level of the menu hierarchy.
///
public bool UseSubMenusSingleFrame { get; set; }
}
}