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 { /// /// The default shortcut key for activating the context menu. /// [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] public static Key DefaultKey { get; set; } = Key.F10.WithShift; static MenuBar _menuBar; Key _key = DefaultKey; MouseFlags _mouseFlags = MouseFlags.Button3Clicked; Toplevel _container; /// /// Initializes a context menu with no menu items. /// public ContextMenu () : this (0, 0, new MenuBarItem ()) { } /// /// Initializes a context menu, with a specifying the parent/host 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); } 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; } } /// /// Shows (opens) the ContextMenu, displaying the s it contains. /// public void Show () { if (_menuBar != null) { Hide (); } _container = Application.Current; _container.Closing += Container_Closing; var frame = Application.Driver.Bounds; var position = Position; if (Host != null) { Host.BoundsToScreen (frame.X, 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.BoundsToScreen (frame.X, 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; _menuBar.BeginInit (); _menuBar.EndInit (); IsShow = true; _menuBar.OpenMenu (); } 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 the key that will activate the context menu. /// 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 => _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; } }