MenuBarItem.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. using System.Diagnostics;
  2. namespace Terminal.Gui.Views;
  3. /// <summary>
  4. /// A <see cref="Shortcut"/>-derived object to be used as items in a <see cref="MenuBar"/>.
  5. /// MenuBarItems hold a <see cref="PopoverMenu"/> instead of a <see cref="SubMenu"/>.
  6. /// </summary>
  7. public class MenuBarItem : MenuItem
  8. {
  9. /// <summary>
  10. /// Creates a new instance of <see cref="MenuBarItem"/>.
  11. /// </summary>
  12. public MenuBarItem () : base (null, Command.NotBound) { }
  13. /// <summary>
  14. /// Creates a new instance of <see cref="MenuBarItem"/>. Each MenuBarItem typically has a <see cref="PopoverMenu"/>
  15. /// that is
  16. /// shown when the item is selected.
  17. /// </summary>
  18. /// <remarks>
  19. /// </remarks>
  20. /// <param name="targetView">
  21. /// The View that <paramref name="command"/> will be invoked on when user does something that causes the MenuBarItems's
  22. /// Accept event to be raised.
  23. /// </param>
  24. /// <param name="command">
  25. /// The Command to invoke on <paramref name="targetView"/>. The Key <paramref name="targetView"/>
  26. /// has bound to <paramref name="command"/> will be used as <see cref="Key"/>
  27. /// </param>
  28. /// <param name="commandText">The text to display for the command.</param>
  29. /// <param name="popoverMenu">The Popover Menu that will be displayed when this item is selected.</param>
  30. public MenuBarItem (View? targetView, Command command, string? commandText, PopoverMenu? popoverMenu = null)
  31. : base (
  32. targetView,
  33. command,
  34. commandText)
  35. {
  36. TargetView = targetView;
  37. Command = command;
  38. PopoverMenu = popoverMenu;
  39. }
  40. /// <summary>
  41. /// Creates a new instance of <see cref="MenuBarItem"/> with the specified <paramref name="popoverMenu"/>. This is a
  42. /// helper for the most common MenuBar use-cases.
  43. /// </summary>
  44. /// <remarks>
  45. /// </remarks>
  46. /// <param name="commandText">The text to display for the command.</param>
  47. /// <param name="popoverMenu">The Popover Menu that will be displayed when this item is selected.</param>
  48. public MenuBarItem (string commandText, PopoverMenu? popoverMenu = null)
  49. : this (
  50. null,
  51. Command.NotBound,
  52. commandText,
  53. popoverMenu)
  54. { }
  55. /// <summary>
  56. /// Creates a new instance of <see cref="MenuBarItem"/> with the <paramref name="menuItems"/> automatcialy added to a
  57. /// <see cref="PopoverMenu"/>.
  58. /// This is a helper for the most common MenuBar use-cases.
  59. /// </summary>
  60. /// <remarks>
  61. /// </remarks>
  62. /// <param name="commandText">The text to display for the command.</param>
  63. /// <param name="menuItems">
  64. /// The menu items that will be added to the Popover Menu that will be displayed when this item is
  65. /// selected.
  66. /// </param>
  67. public MenuBarItem (string commandText, IEnumerable<View> menuItems)
  68. : this (
  69. null,
  70. Command.NotBound,
  71. commandText,
  72. new (menuItems) { Title = $"PopoverMenu for {commandText}" })
  73. { }
  74. /// <summary>
  75. /// Do not use this property. MenuBarItem does not support SubMenu. Use <see cref="PopoverMenu"/> instead.
  76. /// </summary>
  77. /// <exception cref="InvalidOperationException"></exception>
  78. public new Menu? SubMenu
  79. {
  80. get => null;
  81. set => throw new InvalidOperationException ("MenuBarItem does not support SubMenu. Use PopoverMenu instead.");
  82. }
  83. private PopoverMenu? _popoverMenu;
  84. /// <summary>
  85. /// The Popover Menu that will be displayed when this item is selected.
  86. /// </summary>
  87. public PopoverMenu? PopoverMenu
  88. {
  89. get => _popoverMenu;
  90. set
  91. {
  92. if (_popoverMenu == value)
  93. {
  94. return;
  95. }
  96. if (_popoverMenu is { })
  97. {
  98. _popoverMenu.VisibleChanged -= OnPopoverVisibleChanged;
  99. _popoverMenu.Accepted -= OnPopoverMenuOnAccepted;
  100. }
  101. _popoverMenu = value;
  102. if (_popoverMenu is { })
  103. {
  104. _popoverMenu.App = App;
  105. PopoverMenuOpen = _popoverMenu.Visible;
  106. _popoverMenu.VisibleChanged += OnPopoverVisibleChanged;
  107. _popoverMenu.Accepted += OnPopoverMenuOnAccepted;
  108. }
  109. return;
  110. void OnPopoverVisibleChanged (object? sender, EventArgs args)
  111. {
  112. // Logging.Debug ($"OnPopoverVisibleChanged - {Title} - Visible = {_popoverMenu?.Visible} ");
  113. PopoverMenuOpen = _popoverMenu?.Visible ?? false;
  114. }
  115. void OnPopoverMenuOnAccepted (object? sender, CommandEventArgs args)
  116. {
  117. // Logging.Debug ($"OnPopoverMenuOnAccepted - {Title} - {args.Context?.Source?.Title} - {args.Context?.Command}");
  118. RaiseAccepted (args.Context);
  119. }
  120. }
  121. }
  122. private bool _popoverMenuOpen;
  123. /// <summary>
  124. /// Gets or sets whether the MenuBarItem is active. This is used to determine if the MenuBarItem should be
  125. /// </summary>
  126. public bool PopoverMenuOpen
  127. {
  128. get => _popoverMenuOpen;
  129. set
  130. {
  131. if (_popoverMenuOpen == value)
  132. {
  133. return;
  134. }
  135. _popoverMenuOpen = value;
  136. RaisePopoverMenuOpenChanged();
  137. }
  138. }
  139. /// <summary>
  140. ///
  141. /// </summary>
  142. public void RaisePopoverMenuOpenChanged ()
  143. {
  144. OnPopoverMenuOpenChanged();
  145. PopoverMenuOpenChanged?.Invoke (this, new EventArgs<bool> (PopoverMenuOpen));
  146. }
  147. /// <summary>
  148. ///
  149. /// </summary>
  150. protected virtual void OnPopoverMenuOpenChanged () {}
  151. /// <summary>
  152. ///
  153. /// </summary>
  154. public event EventHandler<EventArgs<bool>>? PopoverMenuOpenChanged;
  155. /// <inheritdoc />
  156. protected override bool OnKeyDownNotHandled (Key key)
  157. {
  158. Logging.Trace ($"{key}");
  159. if (PopoverMenu is { Visible: true } && HotKeyBindings.TryGet (key, out _))
  160. {
  161. // If the user presses the hotkey for a menu item that is already open,
  162. // it should close the menu item (Test: MenuBarItem_HotKey_DeActivates)
  163. if (SuperView is MenuBar { } menuBar)
  164. {
  165. menuBar.HideActiveItem ();
  166. }
  167. return true;
  168. }
  169. return false;
  170. }
  171. /// <inheritdoc/>
  172. protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
  173. {
  174. // Logging.Debug ($"CanFocus = {CanFocus}, HasFocus = {HasFocus}");
  175. }
  176. /// <inheritdoc/>
  177. protected override void Dispose (bool disposing)
  178. {
  179. if (disposing)
  180. {
  181. PopoverMenu?.Dispose ();
  182. PopoverMenu = null;
  183. }
  184. base.Dispose (disposing);
  185. }
  186. }