MenuBar.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. using System.ComponentModel;
  2. using System.Diagnostics;
  3. namespace Terminal.Gui.Views;
  4. /// <summary>
  5. /// A horizontal list of <see cref="MenuBarItem"/>s. Each <see cref="MenuBarItem"/> can have a
  6. /// <see cref="PopoverMenu"/> that is shown when the <see cref="MenuBarItem"/> is selected.
  7. /// </summary>
  8. /// <remarks>
  9. /// <para>
  10. /// MenuBars may be hosted by any View and will, by default, be positioned the full width across the top of the View's
  11. /// Viewport.
  12. /// </para>
  13. /// <para>
  14. /// <strong>Using MenuBar as a Dropdown List (ComboBox Alternative):</strong>
  15. /// MenuBar can be used as a dropdown list by configuring it with a single MenuBarItem that has a PopoverMenu.
  16. /// Use <see cref="OpenMenu()"/> to programmatically open the menu, and optionally specify a custom position
  17. /// with <see cref="OpenMenu(Point?)"/> to align it with another control (e.g., a TextField).
  18. /// </para>
  19. /// <code>
  20. /// var tf = new TextField { Width = 10 };
  21. /// var menuBarItem = new MenuBarItem ("▼",
  22. /// new MenuItem [] {
  23. /// new ("Item 1", () => tf.Text = "Item 1"),
  24. /// new ("Item 2", () => tf.Text = "Item 2"),
  25. /// new ("Item 3", () => tf.Text = "Item 3")
  26. /// });
  27. /// var mb = new MenuBar ([menuBarItem]) {
  28. /// Width = 1,
  29. /// Y = Pos.Top (tf),
  30. /// X = Pos.Right (tf)
  31. /// };
  32. /// mb.Enter += (s, e) => mb.OpenMenu (new Point (tf.FrameToScreen ().X, tf.FrameToScreen ().Bottom));
  33. /// </code>
  34. /// </remarks>
  35. public class MenuBar : Menu, IDesignable
  36. {
  37. /// <inheritdoc/>
  38. public MenuBar () : this ([]) { }
  39. /// <inheritdoc/>
  40. public MenuBar (IEnumerable<MenuBarItem> menuBarItems) : base (menuBarItems)
  41. {
  42. CanFocus = false;
  43. TabStop = TabBehavior.TabGroup;
  44. Y = 0;
  45. Width = Dim.Fill ();
  46. Height = Dim.Auto ();
  47. Orientation = Orientation.Horizontal;
  48. Key = DefaultKey;
  49. AddCommand (
  50. Command.HotKey,
  51. (ctx) =>
  52. {
  53. // Logging.Debug ($"{Title} - Command.HotKey");
  54. if (RaiseHandlingHotKey (ctx) is true)
  55. {
  56. return true;
  57. }
  58. if (HideActiveItem ())
  59. {
  60. return true;
  61. }
  62. if (SubViews.OfType<MenuBarItem> ().FirstOrDefault (mbi => mbi.PopoverMenu is { }) is { } first)
  63. {
  64. Active = true;
  65. ShowItem (first);
  66. return true;
  67. }
  68. return false;
  69. });
  70. // If we're not focused, Key activates/deactivates
  71. HotKeyBindings.Add (Key, Command.HotKey);
  72. KeyBindings.Add (Key, Command.Quit);
  73. KeyBindings.ReplaceCommands (Application.QuitKey, Command.Quit);
  74. AddCommand (
  75. Command.Quit,
  76. ctx =>
  77. {
  78. // Logging.Debug ($"{Title} - Command.Quit");
  79. if (HideActiveItem ())
  80. {
  81. return true;
  82. }
  83. if (CanFocus)
  84. {
  85. CanFocus = false;
  86. Active = false;
  87. return true;
  88. }
  89. return false; //RaiseAccepted (ctx);
  90. });
  91. AddCommand (Command.Right, MoveRight);
  92. KeyBindings.Add (Key.CursorRight, Command.Right);
  93. AddCommand (Command.Left, MoveLeft);
  94. KeyBindings.Add (Key.CursorLeft, Command.Left);
  95. BorderStyle = DefaultBorderStyle;
  96. ConfigurationManager.Applied += OnConfigurationManagerApplied;
  97. SuperViewChanged += OnSuperViewChanged;
  98. return;
  99. bool? MoveLeft (ICommandContext? ctx) { return AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop); }
  100. bool? MoveRight (ICommandContext? ctx) { return AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); }
  101. }
  102. private void OnSuperViewChanged (object? sender, SuperViewChangedEventArgs e)
  103. {
  104. if (SuperView is null)
  105. {
  106. // BUGBUG: This is a hack for avoiding a race condition in ConfigurationManager.Apply
  107. // BUGBUG: For some reason in some unit tests, when Top is disposed, MenuBar.Dispose does not get called.
  108. // BUGBUG: Yet, the MenuBar does get Removed from Top (and it's SuperView set to null).
  109. // BUGBUG: Related: https://github.com/gui-cs/Terminal.Gui/issues/4021
  110. ConfigurationManager.Applied -= OnConfigurationManagerApplied;
  111. }
  112. }
  113. private void OnConfigurationManagerApplied (object? sender, ConfigurationManagerEventArgs e) { BorderStyle = DefaultBorderStyle; }
  114. /// <inheritdoc/>
  115. protected override bool OnBorderStyleChanged ()
  116. {
  117. //HideActiveItem ();
  118. return base.OnBorderStyleChanged ();
  119. }
  120. /// <summary>
  121. /// Gets or sets the default Border Style for the MenuBar. The default is <see cref="LineStyle.None"/>.
  122. /// </summary>
  123. [ConfigurationProperty (Scope = typeof (ThemeScope))]
  124. public new static LineStyle DefaultBorderStyle { get; set; } = LineStyle.None;
  125. private Key _key = DefaultKey;
  126. /// <summary>Specifies the key that will activate the context menu.</summary>
  127. public Key Key
  128. {
  129. get => _key;
  130. set
  131. {
  132. Key oldKey = _key;
  133. _key = value;
  134. KeyChanged?.Invoke (this, new (oldKey, _key));
  135. }
  136. }
  137. /// <summary>
  138. /// Sets the Menu Bar Items for this Menu Bar. This will replace any existing Menu Bar Items.
  139. /// </summary>
  140. /// <remarks>
  141. /// <para>
  142. /// This is a convenience property to help porting from the v1 MenuBar.
  143. /// </para>
  144. /// </remarks>
  145. public MenuBarItem []? Menus
  146. {
  147. set
  148. {
  149. RemoveAll ();
  150. if (value is null)
  151. {
  152. return;
  153. }
  154. foreach (MenuBarItem mbi in value)
  155. {
  156. Add (mbi);
  157. }
  158. }
  159. }
  160. /// <inheritdoc/>
  161. protected override void OnSubViewAdded (View view)
  162. {
  163. base.OnSubViewAdded (view);
  164. if (view is MenuBarItem mbi)
  165. {
  166. mbi.Accepted += OnMenuBarItemAccepted;
  167. mbi.PopoverMenuOpenChanged += OnMenuBarItemPopoverMenuOpenChanged;
  168. }
  169. }
  170. /// <inheritdoc/>
  171. protected override void OnSubViewRemoved (View view)
  172. {
  173. base.OnSubViewRemoved (view);
  174. if (view is MenuBarItem mbi)
  175. {
  176. mbi.Accepted -= OnMenuBarItemAccepted;
  177. mbi.PopoverMenuOpenChanged -= OnMenuBarItemPopoverMenuOpenChanged;
  178. }
  179. }
  180. private void OnMenuBarItemPopoverMenuOpenChanged (object? sender, EventArgs<bool> e)
  181. {
  182. if (sender is MenuBarItem mbi)
  183. {
  184. if (e.Value)
  185. {
  186. Active = true;
  187. }
  188. }
  189. }
  190. private void OnMenuBarItemAccepted (object? sender, CommandEventArgs e)
  191. {
  192. // Logging.Debug ($"{Title} ({e.Context?.Source?.Title}) Command: {e.Context?.Command}");
  193. RaiseAccepted (e.Context);
  194. }
  195. /// <summary>Raised when <see cref="Key"/> is changed.</summary>
  196. public event EventHandler<KeyChangedEventArgs>? KeyChanged;
  197. /// <summary>The default key for activating menu bars.</summary>
  198. [ConfigurationProperty (Scope = typeof (SettingsScope))]
  199. public static Key DefaultKey { get; set; } = Key.F9;
  200. /// <summary>
  201. /// Gets whether any of the menu bar items have a visible <see cref="PopoverMenu"/>.
  202. /// </summary>
  203. /// <exception cref="NotImplementedException"></exception>
  204. public bool IsOpen () { return SubViews.OfType<MenuBarItem> ().Count (sv => sv is { PopoverMenuOpen: true }) > 0; }
  205. /// <summary>
  206. /// Opens the first menu item with a <see cref="PopoverMenu"/>. This is useful for programmatically opening
  207. /// the menu, for example when using the MenuBar as a dropdown list.
  208. /// </summary>
  209. /// <returns><see langword="true"/> if a menu was opened; <see langword="false"/> otherwise.</returns>
  210. /// <remarks>
  211. /// <para>
  212. /// This method activates the MenuBar and shows the first MenuBarItem that has a PopoverMenu.
  213. /// The first menu item in the PopoverMenu will be selected and focused.
  214. /// </para>
  215. /// </remarks>
  216. public bool OpenMenu () { return OpenMenu (null); }
  217. /// <summary>
  218. /// Opens the first menu item with a <see cref="PopoverMenu"/> at the specified screen position.
  219. /// This is useful for programmatically opening the menu, for example when using the MenuBar as a dropdown list.
  220. /// </summary>
  221. /// <param name="position">
  222. /// The screen position at which to open the menu. If <see langword="null"/>, the menu will be positioned
  223. /// at the default location (bottom-left of the first MenuBarItem).
  224. /// </param>
  225. /// <returns><see langword="true"/> if a menu was opened; <see langword="false"/> otherwise.</returns>
  226. /// <remarks>
  227. /// <para>
  228. /// This method activates the MenuBar and shows the first MenuBarItem that has a PopoverMenu.
  229. /// The first menu item in the PopoverMenu will be selected and focused.
  230. /// </para>
  231. /// <para>
  232. /// When using MenuBar as a dropdown button next to a TextField, you can position the menu
  233. /// to align with the left edge of the TextField by passing the TextField's screen position.
  234. /// </para>
  235. /// </remarks>
  236. public bool OpenMenu (Point? position)
  237. {
  238. if (SubViews.OfType<MenuBarItem> ().FirstOrDefault (mbi => mbi.PopoverMenu is { }) is { } first)
  239. {
  240. Active = true;
  241. ShowItem (first, position);
  242. return true;
  243. }
  244. return false;
  245. }
  246. private bool _active;
  247. /// <summary>
  248. /// Gets or sets whether the menu bar is active or not. When active, the MenuBar can focus and moving the mouse
  249. /// over a MenuBarItem will switch focus to that item. Use <see cref="IsOpen"/> to determine if a PopoverMenu of
  250. /// a MenuBarItem is open.
  251. /// </summary>
  252. /// <returns></returns>
  253. public bool Active
  254. {
  255. get => _active;
  256. internal set
  257. {
  258. if (_active == value)
  259. {
  260. return;
  261. }
  262. _active = value;
  263. // Logging.Debug ($"Active set to {_active} - CanFocus: {CanFocus}, HasFocus: {HasFocus}");
  264. if (!_active)
  265. {
  266. // Hide open Popovers
  267. HideActiveItem ();
  268. }
  269. CanFocus = value;
  270. // Logging.Debug ($"Set CanFocus: {CanFocus}, HasFocus: {HasFocus}");
  271. }
  272. }
  273. /// <inheritdoc/>
  274. protected override bool OnMouseEnter (CancelEventArgs eventArgs)
  275. {
  276. // If the MenuBar does not have focus and the mouse enters: Enable CanFocus
  277. // But do NOT show a Popover unless the user clicks or presses a hotkey
  278. // Logging.Debug ($"CanFocus = {CanFocus}, HasFocus = {HasFocus}");
  279. if (!HasFocus)
  280. {
  281. Active = true;
  282. }
  283. return base.OnMouseEnter (eventArgs);
  284. }
  285. /// <inheritdoc/>
  286. protected override void OnMouseLeave ()
  287. {
  288. // Logging.Debug ($"CanFocus = {CanFocus}, HasFocus = {HasFocus}");
  289. if (!IsOpen ())
  290. {
  291. Active = false;
  292. }
  293. base.OnMouseLeave ();
  294. }
  295. /// <inheritdoc/>
  296. protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
  297. {
  298. // Logging.Debug ($"CanFocus = {CanFocus}, HasFocus = {HasFocus}");
  299. if (!newHasFocus)
  300. {
  301. Active = false;
  302. }
  303. }
  304. /// <inheritdoc/>
  305. protected override void OnSelectedMenuItemChanged (MenuItem? selected)
  306. {
  307. // Logging.Debug ($"{Title} ({selected?.Title}) - IsOpen: {IsOpen ()}");
  308. if (IsOpen () && selected is MenuBarItem { PopoverMenuOpen: false } selectedMenuBarItem)
  309. {
  310. ShowItem (selectedMenuBarItem);
  311. }
  312. }
  313. /// <inheritdoc/>
  314. public override void EndInit ()
  315. {
  316. base.EndInit ();
  317. if (Border is { })
  318. {
  319. Border.Thickness = new (0);
  320. Border.LineStyle = LineStyle.None;
  321. }
  322. // TODO: This needs to be done whenever a menuitem in any MenuBarItem changes
  323. foreach (MenuBarItem? mbi in SubViews.Select (s => s as MenuBarItem))
  324. {
  325. App?.Popover?.Register (mbi?.PopoverMenu);
  326. }
  327. }
  328. /// <inheritdoc/>
  329. protected override bool OnAccepting (CommandEventArgs args)
  330. {
  331. // Logging.Debug ($"{Title} ({args.Context?.Source?.Title})");
  332. // TODO: Ensure sourceMenuBar is actually one of our bar items
  333. if (Visible && Enabled && args.Context?.Source is MenuBarItem { PopoverMenuOpen: false } sourceMenuBarItem)
  334. {
  335. if (!CanFocus)
  336. {
  337. Debug.Assert (!Active);
  338. // We are not Active; change that
  339. Active = true;
  340. ShowItem (sourceMenuBarItem);
  341. if (!sourceMenuBarItem.HasFocus)
  342. {
  343. sourceMenuBarItem.SetFocus ();
  344. }
  345. }
  346. else
  347. {
  348. Debug.Assert (Active);
  349. ShowItem (sourceMenuBarItem);
  350. }
  351. return true;
  352. }
  353. return false;
  354. }
  355. /// <inheritdoc/>
  356. protected override void OnAccepted (CommandEventArgs args)
  357. {
  358. // Logging.Debug ($"{Title} ({args.Context?.Source?.Title}) Command: {args.Context?.Command}");
  359. base.OnAccepted (args);
  360. if (SubViews.OfType<MenuBarItem> ().Contains (args.Context?.Source))
  361. {
  362. return;
  363. }
  364. Active = false;
  365. }
  366. /// <summary>
  367. /// Shows the specified popover, but only if the menu bar is active.
  368. /// </summary>
  369. /// <param name="menuBarItem"></param>
  370. /// <param name="position">
  371. /// The screen position at which to show the popover. If <see langword="null"/>, the menu will be positioned
  372. /// at the default location (bottom-left of the MenuBarItem).
  373. /// </param>
  374. private void ShowItem (MenuBarItem? menuBarItem, Point? position = null)
  375. {
  376. // Logging.Debug ($"{Title} - {menuBarItem?.Id}");
  377. if (!Active || !Visible)
  378. {
  379. // Logging.Debug ($"{Title} - {menuBarItem?.Id} - Not Active, not showing.");
  380. return;
  381. }
  382. // TODO: We should init the PopoverMenu in a smarter way
  383. if (menuBarItem?.PopoverMenu is { IsInitialized: false })
  384. {
  385. menuBarItem.PopoverMenu.BeginInit ();
  386. menuBarItem.PopoverMenu.EndInit ();
  387. }
  388. // If the active Application Popover is part of this MenuBar, hide it.
  389. if (App?.Popover?.GetActivePopover () is PopoverMenu popoverMenu
  390. && popoverMenu.Root?.SuperMenuItem?.SuperView == this)
  391. {
  392. // Logging.Debug ($"{Title} - Calling App?.Popover?.Hide ({popoverMenu.Title})");
  393. App?.Popover.Hide (popoverMenu);
  394. }
  395. if (menuBarItem is null)
  396. {
  397. // Logging.Debug ($"{Title} - menuBarItem is null.");
  398. return;
  399. }
  400. Active = true;
  401. menuBarItem.SetFocus ();
  402. if (menuBarItem.PopoverMenu?.Root is { })
  403. {
  404. menuBarItem.PopoverMenu.Root.SuperMenuItem = menuBarItem;
  405. menuBarItem.PopoverMenu.Root.SchemeName = SchemeName;
  406. }
  407. // Logging.Debug ($"{Title} - \"{menuBarItem.PopoverMenu?.Title}\".MakeVisible");
  408. if (menuBarItem.PopoverMenu is { })
  409. {
  410. menuBarItem.PopoverMenu.App ??= App;
  411. Point menuPosition = position ?? new Point (menuBarItem.FrameToScreen ().X, menuBarItem.FrameToScreen ().Bottom);
  412. menuBarItem.PopoverMenu.MakeVisible (menuPosition);
  413. }
  414. menuBarItem.Accepting += OnMenuItemAccepted;
  415. return;
  416. void OnMenuItemAccepted (object? sender, EventArgs args)
  417. {
  418. // Logging.Debug ($"{Title} - OnMenuItemAccepted");
  419. if (menuBarItem.PopoverMenu is { })
  420. {
  421. menuBarItem.PopoverMenu.VisibleChanged -= OnMenuItemAccepted;
  422. }
  423. if (Active && menuBarItem.PopoverMenu is { Visible: false })
  424. {
  425. Active = false;
  426. HasFocus = false;
  427. }
  428. }
  429. }
  430. private MenuBarItem? GetActiveItem () { return SubViews.OfType<MenuBarItem> ().FirstOrDefault (sv => sv is { PopoverMenu: { Visible: true } }); }
  431. /// <summary>
  432. /// Hides the popover menu associated with the active menu bar item and updates the focus state.
  433. /// </summary>
  434. /// <returns><see langword="true"/> if the popover was hidden</returns>
  435. public bool HideActiveItem () { return HideItem (GetActiveItem ()); }
  436. /// <summary>
  437. /// Hides popover menu associated with the specified menu bar item and updates the focus state.
  438. /// </summary>
  439. /// <param name="activeItem"></param>
  440. /// <returns><see langword="true"/> if the popover was hidden</returns>
  441. public bool HideItem (MenuBarItem? activeItem)
  442. {
  443. // Logging.Debug ($"{Title} ({activeItem?.Title}) - Active: {Active}, CanFocus: {CanFocus}, HasFocus: {HasFocus}");
  444. if (activeItem is null || !activeItem.PopoverMenu!.Visible)
  445. {
  446. // Logging.Debug ($"{Title} No active item.");
  447. return false;
  448. }
  449. // IMPORTANT: Set Visible false before setting Active to false (Active changes Can/HasFocus)
  450. activeItem.PopoverMenu!.Visible = false;
  451. Active = false;
  452. HasFocus = false;
  453. return true;
  454. }
  455. /// <summary>
  456. /// Gets all menu items with the specified Title, anywhere in the menu hierarchy.
  457. /// </summary>
  458. /// <param name="title"></param>
  459. /// <returns></returns>
  460. public IEnumerable<MenuItem> GetMenuItemsWithTitle (string title)
  461. {
  462. List<MenuItem> menuItems = new ();
  463. if (string.IsNullOrEmpty (title))
  464. {
  465. return menuItems;
  466. }
  467. foreach (MenuBarItem mbi in SubViews.OfType<MenuBarItem> ())
  468. {
  469. if (mbi.PopoverMenu is { })
  470. {
  471. menuItems.AddRange (mbi.PopoverMenu.GetMenuItemsOfAllSubMenus ());
  472. }
  473. }
  474. return menuItems.Where (mi => mi.Title == title);
  475. }
  476. /// <inheritdoc/>
  477. public bool EnableForDesign<TContext> (ref TContext targetView) where TContext : notnull
  478. {
  479. // Note: This menu is used by unit tests. If you modify it, you'll likely have to update
  480. // unit tests.
  481. if (targetView is View target)
  482. {
  483. App ??= target.App;
  484. }
  485. Id = "DemoBar";
  486. var bordersCb = new CheckBox
  487. {
  488. Title = "_Borders",
  489. CheckedState = CheckState.Checked
  490. };
  491. var autoSaveCb = new CheckBox
  492. {
  493. Title = "_Auto Save"
  494. };
  495. var enableOverwriteCb = new CheckBox
  496. {
  497. Title = "Enable _Overwrite"
  498. };
  499. var mutuallyExclusiveOptionsSelector = new OptionSelector
  500. {
  501. Labels = ["G_ood", "_Bad", "U_gly"],
  502. Value = 0
  503. };
  504. var menuBgColorCp = new ColorPicker
  505. {
  506. Width = 30
  507. };
  508. menuBgColorCp.ColorChanged += (sender, args) =>
  509. {
  510. // BUGBUG: This is weird.
  511. SetScheme (
  512. GetScheme () with
  513. {
  514. Normal = new (
  515. GetAttributeForRole (VisualRole.Normal).Foreground,
  516. args.Result,
  517. GetAttributeForRole (VisualRole.Normal).Style)
  518. });
  519. };
  520. Add (
  521. new MenuBarItem (
  522. "_File",
  523. [
  524. new MenuItem (targetView as View, Command.New),
  525. new MenuItem (targetView as View, Command.Open),
  526. new MenuItem (targetView as View, Command.Save),
  527. new MenuItem (targetView as View, Command.SaveAs),
  528. new Line (),
  529. new MenuItem
  530. {
  531. Title = "_File Options",
  532. SubMenu = new (
  533. [
  534. new ()
  535. {
  536. Id = "AutoSave",
  537. Text = "(no Command)",
  538. Key = Key.F10,
  539. CommandView = autoSaveCb
  540. },
  541. new ()
  542. {
  543. Text = "Overwrite",
  544. Id = "Overwrite",
  545. Key = Key.W.WithCtrl,
  546. CommandView = enableOverwriteCb,
  547. Command = Command.EnableOverwrite,
  548. TargetView = targetView as View
  549. },
  550. new ()
  551. {
  552. Title = "_File Settings...",
  553. HelpText = "More file settings",
  554. Action = () => MessageBox.Query (App,
  555. "File Settings",
  556. "This is the File Settings Dialog\n",
  557. "_Ok",
  558. "_Cancel")
  559. }
  560. ]
  561. )
  562. },
  563. new Line (),
  564. new MenuItem
  565. {
  566. Title = "_Preferences",
  567. SubMenu = new (
  568. [
  569. new MenuItem
  570. {
  571. CommandView = bordersCb,
  572. HelpText = "Toggle Menu Borders",
  573. Action = ToggleMenuBorders
  574. },
  575. new MenuItem
  576. {
  577. HelpText = "3 Mutually Exclusive Options",
  578. CommandView = mutuallyExclusiveOptionsSelector,
  579. Key = Key.F7
  580. },
  581. new Line (),
  582. new MenuItem
  583. {
  584. HelpText = "MenuBar BG Color",
  585. CommandView = menuBgColorCp,
  586. Key = Key.F8
  587. }
  588. ]
  589. )
  590. },
  591. new Line (),
  592. new MenuItem
  593. {
  594. TargetView = targetView as View,
  595. Key = Application.QuitKey,
  596. Command = Command.Quit
  597. }
  598. ]
  599. )
  600. );
  601. Add (
  602. new MenuBarItem (
  603. "_Edit",
  604. [
  605. new MenuItem (targetView as View, Command.Cut),
  606. new MenuItem (targetView as View, Command.Copy),
  607. new MenuItem (targetView as View, Command.Paste),
  608. new Line (),
  609. new MenuItem (targetView as View, Command.SelectAll),
  610. new Line (),
  611. new MenuItem
  612. {
  613. Title = "_Details",
  614. SubMenu = new (ConfigureDetailsSubMenu ())
  615. }
  616. ]
  617. )
  618. );
  619. Add (
  620. new MenuBarItem (
  621. "_Help",
  622. [
  623. new MenuItem
  624. {
  625. Title = "_Online Help...",
  626. Action = () => MessageBox.Query (App, "Online Help", "https://gui-cs.github.io/Terminal.Gui", "Ok")
  627. },
  628. new MenuItem
  629. {
  630. Title = "About...",
  631. Action = () => MessageBox.Query (App, "About", "Something About Mary.", "Ok")
  632. }
  633. ]
  634. )
  635. );
  636. return true;
  637. void ToggleMenuBorders ()
  638. {
  639. foreach (MenuBarItem mbi in SubViews.OfType<MenuBarItem> ())
  640. {
  641. if (mbi is not { PopoverMenu: { } })
  642. {
  643. continue;
  644. }
  645. foreach (Menu? subMenu in mbi.PopoverMenu.GetAllSubMenus ())
  646. {
  647. if (bordersCb.CheckedState == CheckState.Checked)
  648. {
  649. subMenu.Border!.Thickness = new (1);
  650. }
  651. else
  652. {
  653. subMenu.Border!.Thickness = new (0);
  654. }
  655. }
  656. }
  657. }
  658. MenuItem [] ConfigureDetailsSubMenu ()
  659. {
  660. var detail = new MenuItem
  661. {
  662. Title = "_Detail 1",
  663. Text = "Some detail #1"
  664. };
  665. var nestedSubMenu = new MenuItem
  666. {
  667. Title = "_Moar Details",
  668. SubMenu = new (ConfigureMoreDetailsSubMenu ())
  669. };
  670. var editMode = new MenuItem
  671. {
  672. Text = "App Binding to Command.Edit",
  673. Id = "EditMode",
  674. Command = Command.Edit,
  675. CommandView = new CheckBox
  676. {
  677. Title = "E_dit Mode"
  678. }
  679. };
  680. return [detail, nestedSubMenu, null!, editMode];
  681. View [] ConfigureMoreDetailsSubMenu ()
  682. {
  683. var deeperDetail = new MenuItem
  684. {
  685. Title = "_Deeper Detail",
  686. Text = "Deeper Detail",
  687. Action = () => { MessageBox.Query (App, "Deeper Detail", "Lots of details", "_Ok"); }
  688. };
  689. var belowLineDetail = new MenuItem
  690. {
  691. Title = "_Even more detail",
  692. Text = "Below the line"
  693. };
  694. // This ensures the checkbox state toggles when the hotkey of Title is pressed.
  695. //shortcut4.Accepting += (sender, args) => args.Cancel = true;
  696. return [deeperDetail, new Line (), belowLineDetail];
  697. }
  698. }
  699. }
  700. /// <inheritdoc/>
  701. protected override void Dispose (bool disposing)
  702. {
  703. base.Dispose (disposing);
  704. if (disposing)
  705. {
  706. SuperViewChanged += OnSuperViewChanged;
  707. ConfigurationManager.Applied -= OnConfigurationManagerApplied;
  708. }
  709. }
  710. }