MenuItem.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. using System.ComponentModel;
  2. namespace Terminal.Gui.Views;
  3. /// <summary>
  4. /// A <see cref="Shortcut"/>-derived object to be used as a menu item in a <see cref="Menu"/>. Has title, an
  5. /// A <see cref="Shortcut"/>-derived object to be used as a menu item in a <see cref="Menu"/>. Has title, an
  6. /// associated help text, and an action to execute on activation.
  7. /// </summary>
  8. public class MenuItem : Shortcut
  9. {
  10. /// <summary>
  11. /// Creates a new instance of <see cref="MenuItem"/>.
  12. /// </summary>
  13. public MenuItem () : base (Key.Empty, null, null) { }
  14. /// <summary>
  15. /// Creates a new instance of <see cref="MenuItem"/>, binding it to <paramref name="targetView"/> and
  16. /// <paramref name="command"/>. The Key <paramref name="targetView"/>
  17. /// has bound to <paramref name="command"/> will be used as <see cref="Key"/>.
  18. /// </summary>
  19. /// <remarks>
  20. /// </remarks>
  21. /// <param name="targetView">
  22. /// The View that <paramref name="command"/> will be invoked on when user does something that causes the Shortcut's
  23. /// Accept
  24. /// event to be raised.
  25. /// </param>
  26. /// <param name="command">
  27. /// The Command to invoke on <paramref name="targetView"/>. The Key <paramref name="targetView"/>
  28. /// has bound to <paramref name="command"/> will be used as <see cref="Key"/>
  29. /// </param>
  30. /// <param name="commandText">The text to display for the command.</param>
  31. /// <param name="helpText">The help text to display.</param>
  32. /// <param name="subMenu">The submenu to display when the user selects this menu item.</param>
  33. public MenuItem (View? targetView, Command command, string? commandText = null, string? helpText = null, Menu? subMenu = null)
  34. : base (
  35. targetView?.HotKeyBindings.GetFirstFromCommands (command)!,
  36. string.IsNullOrEmpty (commandText) ? GlobalResources.GetString ($"cmd.{command}") : commandText,
  37. null,
  38. string.IsNullOrEmpty (helpText) ? GlobalResources.GetString ($"cmd.{command}.Help") : helpText
  39. )
  40. {
  41. TargetView = targetView;
  42. Command = command;
  43. SubMenu = subMenu;
  44. }
  45. /// <inheritdoc/>
  46. public MenuItem (string? commandText = null, string? helpText = null, Action? action = null, Key? key = null)
  47. : base (key ?? Key.Empty, commandText, action, helpText)
  48. { }
  49. /// <inheritdoc/>
  50. public MenuItem (string commandText, Key key, Action? action = null)
  51. : base (key ?? Key.Empty, commandText, action, null)
  52. { }
  53. /// <inheritdoc/>
  54. public MenuItem (string? commandText = null, string? helpText = null, Menu? subMenu = null)
  55. : base (Key.Empty, commandText, null, helpText)
  56. {
  57. SubMenu = subMenu;
  58. }
  59. // TODO: Consider moving TargetView and Command to Shortcut?
  60. /// <summary>
  61. /// Gets the target <see cref="View"/> that the <see cref="Command"/> will be invoked on.
  62. /// </summary>
  63. public View? TargetView { get; set; }
  64. private Command _command;
  65. /// <summary>
  66. /// Gets the <see cref="Command"/> that will be invoked on <see cref="TargetView"/> when the MenuItem is selected.
  67. /// </summary>
  68. public Command Command
  69. {
  70. get => _command;
  71. set
  72. {
  73. if (_command == value)
  74. {
  75. return;
  76. }
  77. _command = value;
  78. if (string.IsNullOrEmpty (Title))
  79. {
  80. Title = GlobalResources.GetString ($"cmd.{_command}") ?? string.Empty;
  81. }
  82. if (string.IsNullOrEmpty (HelpText))
  83. {
  84. HelpText = GlobalResources.GetString ($"cmd.{_command}.Help") ?? string.Empty;
  85. }
  86. }
  87. }
  88. internal override bool? DispatchCommand (ICommandContext? commandContext)
  89. {
  90. // Logging.Debug ($"{Title} - {commandContext?.Source?.Title} Command: {commandContext?.Command}");
  91. bool? ret = null;
  92. bool quit = false;
  93. if (commandContext is CommandContext<KeyBinding> keyCommandContext)
  94. {
  95. if (keyCommandContext.Binding.Key is { } && keyCommandContext.Binding.Key == Application.QuitKey && SuperView is { Visible: true })
  96. {
  97. // This supports a MenuItem with Key = Application.QuitKey/Command = Command.Quit
  98. // Logging.Debug ($"{Title} - Ignoring Key = Application.QuitKey/Command = Command.Quit");
  99. quit = true;
  100. //ret = true;
  101. }
  102. }
  103. // Translate the incoming command to Command
  104. if (Command != Command.NotBound && commandContext is { })
  105. {
  106. commandContext.Command = Command;
  107. }
  108. if (!quit)
  109. {
  110. if (TargetView is { })
  111. {
  112. // Logging.Debug ($"{Title} - InvokeCommand on TargetView ({TargetView.Title})...");
  113. ret = TargetView.InvokeCommand (Command, commandContext);
  114. }
  115. else
  116. {
  117. // Is this an Application-bound command?
  118. // Logging.Debug ($"{Title} - Application.InvokeCommandsBoundToKey ({Key})...");
  119. ret = App?.Keyboard.InvokeCommandsBoundToKey (Key);
  120. }
  121. }
  122. if (ret is not true)
  123. {
  124. // Logging.Debug ($"{Title} - calling base.DispatchCommand...");
  125. // Base will Raise Selected, then Accepting, then invoke the Action, if any
  126. ret = base.DispatchCommand (commandContext);
  127. }
  128. if (ret is true)
  129. {
  130. // Logging.Debug ($"{Title} - Calling RaiseAccepted");
  131. RaiseAccepted (commandContext);
  132. }
  133. return ret;
  134. }
  135. ///// <inheritdoc />
  136. //protected override bool OnAccepting (CommandEventArgs e)
  137. //{
  138. // // Logging.Debug ($"{Title} - calling base.OnAccepting: {e.Context?.Command}");
  139. // bool? ret = base.OnAccepting (e);
  140. // if (ret is true || e.Cancel)
  141. // {
  142. // return true;
  143. // }
  144. // //RaiseAccepted (e.Context);
  145. // return ret is true;
  146. //}
  147. private Menu? _subMenu;
  148. /// <summary>
  149. /// The submenu to display when the user selects this menu item.
  150. /// </summary>
  151. public Menu? SubMenu
  152. {
  153. get => _subMenu;
  154. set
  155. {
  156. _subMenu = value;
  157. if (_subMenu is { })
  158. {
  159. SubMenu!.App ??= App;
  160. SubMenu!.Visible = false;
  161. // TODO: This is a temporary hack - add a flag or something instead
  162. KeyView.Text = $"{Glyphs.RightArrow}";
  163. _subMenu.SuperMenuItem = this;
  164. }
  165. }
  166. }
  167. /// <inheritdoc/>
  168. protected override bool OnMouseEnter (CancelEventArgs eventArgs)
  169. {
  170. // When the mouse enters a menuitem, we set focus to it automatically.
  171. // Logging.Trace($"OnEnter {Title}");
  172. SetFocus ();
  173. return base.OnMouseEnter (eventArgs);
  174. }
  175. // TODO: Consider moving Accepted to Shortcut?
  176. /// <summary>
  177. /// Raises the <see cref="OnAccepted"/>/<see cref="Accepted"/> event indicating this item (or submenu)
  178. /// was accepted. This is used to determine when to hide the menu.
  179. /// </summary>
  180. /// <param name="ctx"></param>
  181. /// <returns></returns>
  182. protected void RaiseAccepted (ICommandContext? ctx)
  183. {
  184. //Logging.Trace ($"RaiseAccepted: {ctx}");
  185. CommandEventArgs args = new () { Context = ctx };
  186. OnAccepted (args);
  187. Accepted?.Invoke (this, args);
  188. }
  189. /// <summary>
  190. /// Called when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the
  191. /// menu.
  192. /// </summary>
  193. /// <remarks>
  194. /// </remarks>
  195. /// <param name="args"></param>
  196. protected virtual void OnAccepted (CommandEventArgs args) { }
  197. /// <summary>
  198. /// Raised when the user has accepted an item in this menu (or submenu). This is used to determine when to hide the
  199. /// menu.
  200. /// </summary>
  201. /// <remarks>
  202. /// <para>
  203. /// See <see cref="RaiseAccepted"/> for more information.
  204. /// </para>
  205. /// </remarks>
  206. public event EventHandler<CommandEventArgs>? Accepted;
  207. /// <inheritdoc/>
  208. protected override void Dispose (bool disposing)
  209. {
  210. if (disposing)
  211. {
  212. SubMenu?.Dispose ();
  213. SubMenu = null;
  214. }
  215. base.Dispose (disposing);
  216. }
  217. }