Application.Keyboard.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. #nullable enable
  2. using System.Text.Json.Serialization;
  3. namespace Terminal.Gui;
  4. public static partial class Application // Keyboard handling
  5. {
  6. private static Key _nextTabKey = Key.Tab; // Resources/config.json overrrides
  7. /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
  8. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
  9. public static Key NextTabKey
  10. {
  11. get => _nextTabKey;
  12. set
  13. {
  14. if (_nextTabKey != value)
  15. {
  16. ReplaceKey (_nextTabKey, value);
  17. _nextTabKey = value;
  18. }
  19. }
  20. }
  21. private static Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrrides
  22. /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
  23. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
  24. public static Key PrevTabKey
  25. {
  26. get => _prevTabKey;
  27. set
  28. {
  29. if (_prevTabKey != value)
  30. {
  31. ReplaceKey (_prevTabKey, value);
  32. _prevTabKey = value;
  33. }
  34. }
  35. }
  36. private static Key _nextTabGroupKey = Key.F6; // Resources/config.json overrrides
  37. /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
  38. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
  39. public static Key NextTabGroupKey
  40. {
  41. get => _nextTabGroupKey;
  42. set
  43. {
  44. if (_nextTabGroupKey != value)
  45. {
  46. ReplaceKey (_nextTabGroupKey, value);
  47. _nextTabGroupKey = value;
  48. }
  49. }
  50. }
  51. private static Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrrides
  52. /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
  53. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
  54. public static Key PrevTabGroupKey
  55. {
  56. get => _prevTabGroupKey;
  57. set
  58. {
  59. if (_prevTabGroupKey != value)
  60. {
  61. ReplaceKey (_prevTabGroupKey, value);
  62. _prevTabGroupKey = value;
  63. }
  64. }
  65. }
  66. private static Key _quitKey = Key.Esc; // Resources/config.json overrrides
  67. /// <summary>Gets or sets the key to quit the application.</summary>
  68. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
  69. public static Key QuitKey
  70. {
  71. get => _quitKey;
  72. set
  73. {
  74. if (_quitKey != value)
  75. {
  76. ReplaceKey (_quitKey, value);
  77. _quitKey = value;
  78. }
  79. }
  80. }
  81. private static void ReplaceKey (Key oldKey, Key newKey)
  82. {
  83. if (KeyBindings.Bindings.Count == 0)
  84. {
  85. return;
  86. }
  87. if (newKey == Key.Empty)
  88. {
  89. KeyBindings.Remove (oldKey);
  90. }
  91. else
  92. {
  93. KeyBindings.ReplaceKey (oldKey, newKey);
  94. }
  95. }
  96. /// <summary>
  97. /// Event fired when the user presses a key. Fired by <see cref="OnKeyDown"/>.
  98. /// <para>
  99. /// Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and to prevent
  100. /// additional processing.
  101. /// </para>
  102. /// </summary>
  103. /// <remarks>
  104. /// All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses) do not support firing the
  105. /// <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
  106. /// <para>Fired after <see cref="KeyDown"/> and before <see cref="KeyUp"/>.</para>
  107. /// </remarks>
  108. public static event EventHandler<Key>? KeyDown;
  109. /// <summary>
  110. /// Called by the <see cref="ConsoleDriver"/> when the user presses a key. Fires the <see cref="KeyDown"/> event
  111. /// then calls <see cref="View.NewKeyDownEvent"/> on all top level views. Called after <see cref="OnKeyDown"/> and
  112. /// before <see cref="OnKeyUp"/>.
  113. /// </summary>
  114. /// <remarks>Can be used to simulate key press events.</remarks>
  115. /// <param name="keyEvent"></param>
  116. /// <returns><see langword="true"/> if the key was handled.</returns>
  117. public static bool OnKeyDown (Key keyEvent)
  118. {
  119. //if (!IsInitialized)
  120. //{
  121. // return true;
  122. //}
  123. KeyDown?.Invoke (null, keyEvent);
  124. if (keyEvent.Handled)
  125. {
  126. return true;
  127. }
  128. if (Current is null)
  129. {
  130. foreach (Toplevel topLevel in TopLevels.ToList ())
  131. {
  132. if (topLevel.NewKeyDownEvent (keyEvent))
  133. {
  134. return true;
  135. }
  136. if (topLevel.Modal)
  137. {
  138. break;
  139. }
  140. }
  141. }
  142. else
  143. {
  144. if (Current.NewKeyDownEvent (keyEvent))
  145. {
  146. return true;
  147. }
  148. }
  149. // Invoke any Application-scoped KeyBindings.
  150. // The first view that handles the key will stop the loop.
  151. foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.Bindings.Where (b => b.Key == keyEvent.KeyCode))
  152. {
  153. if (binding.Value.BoundView is { })
  154. {
  155. bool? handled = binding.Value.BoundView?.InvokeCommands (binding.Value.Commands, binding.Key, binding.Value);
  156. if (handled != null && (bool)handled)
  157. {
  158. return true;
  159. }
  160. }
  161. else
  162. {
  163. if (!KeyBindings.TryGet (keyEvent, KeyBindingScope.Application, out KeyBinding appBinding))
  164. {
  165. continue;
  166. }
  167. bool? toReturn = null;
  168. foreach (Command command in appBinding.Commands)
  169. {
  170. if (!CommandImplementations.ContainsKey (command))
  171. {
  172. throw new NotSupportedException (
  173. @$"A KeyBinding was set up for the command {command} ({keyEvent}) but that command is not supported by Application."
  174. );
  175. }
  176. if (CommandImplementations.TryGetValue (command, out Func<CommandContext, bool?>? implementation))
  177. {
  178. var context = new CommandContext (command, keyEvent, appBinding); // Create the context here
  179. toReturn = implementation (context);
  180. }
  181. // if ever see a true then that's what we will return
  182. if (toReturn ?? false)
  183. {
  184. toReturn = true;
  185. }
  186. }
  187. return toReturn ?? true;
  188. }
  189. }
  190. return false;
  191. }
  192. /// <summary>
  193. /// Event fired when the user releases a key. Fired by <see cref="OnKeyUp"/>.
  194. /// <para>
  195. /// Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and to prevent
  196. /// additional processing.
  197. /// </para>
  198. /// </summary>
  199. /// <remarks>
  200. /// All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses) do not support firing the
  201. /// <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
  202. /// <para>Fired after <see cref="KeyDown"/>.</para>
  203. /// </remarks>
  204. public static event EventHandler<Key>? KeyUp;
  205. /// <summary>
  206. /// Called by the <see cref="ConsoleDriver"/> when the user releases a key. Fires the <see cref="KeyUp"/> event
  207. /// then calls <see cref="View.NewKeyUpEvent"/> on all top level views. Called after <see cref="OnKeyDown"/>.
  208. /// </summary>
  209. /// <remarks>Can be used to simulate key press events.</remarks>
  210. /// <param name="a"></param>
  211. /// <returns><see langword="true"/> if the key was handled.</returns>
  212. public static bool OnKeyUp (Key a)
  213. {
  214. if (!IsInitialized)
  215. {
  216. return true;
  217. }
  218. KeyUp?.Invoke (null, a);
  219. if (a.Handled)
  220. {
  221. return true;
  222. }
  223. foreach (Toplevel topLevel in TopLevels.ToList ())
  224. {
  225. if (topLevel.NewKeyUpEvent (a))
  226. {
  227. return true;
  228. }
  229. if (topLevel.Modal)
  230. {
  231. break;
  232. }
  233. }
  234. return false;
  235. }
  236. /// <summary>Gets the key bindings for this view.</summary>
  237. public static KeyBindings KeyBindings { get; internal set; } = new ();
  238. /// <summary>
  239. /// Commands for Application.
  240. /// </summary>
  241. private static Dictionary<Command, Func<CommandContext, bool?>> CommandImplementations { get; set; }
  242. /// <summary>
  243. /// <para>
  244. /// Sets the function that will be invoked for a <see cref="Command"/>.
  245. /// </para>
  246. /// <para>
  247. /// If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
  248. /// replace the old one.
  249. /// </para>
  250. /// </summary>
  251. /// <remarks>
  252. /// <para>
  253. /// This version of AddCommand is for commands that do not require a <see cref="CommandContext"/>.
  254. /// </para>
  255. /// </remarks>
  256. /// <param name="command">The command.</param>
  257. /// <param name="f">The function.</param>
  258. private static void AddCommand (Command command, Func<bool?> f) { CommandImplementations [command] = ctx => f (); }
  259. static Application () { AddApplicationKeyBindings (); }
  260. internal static void AddApplicationKeyBindings ()
  261. {
  262. CommandImplementations = new ();
  263. // Things this view knows how to do
  264. AddCommand (
  265. Command.QuitToplevel, // TODO: IRunnable: Rename to Command.Quit to make more generic.
  266. () =>
  267. {
  268. if (ApplicationOverlapped.OverlappedTop is { })
  269. {
  270. RequestStop (Current!);
  271. }
  272. else
  273. {
  274. RequestStop ();
  275. }
  276. return true;
  277. }
  278. );
  279. AddCommand (
  280. Command.Suspend,
  281. () =>
  282. {
  283. Driver?.Suspend ();
  284. return true;
  285. }
  286. );
  287. AddCommand (
  288. Command.NextView,
  289. () =>
  290. {
  291. ApplicationNavigation.MoveNextView ();
  292. return true;
  293. }
  294. );
  295. AddCommand (
  296. Command.PreviousView,
  297. () =>
  298. {
  299. ApplicationNavigation.MovePreviousView ();
  300. return true;
  301. }
  302. );
  303. AddCommand (
  304. Command.NextViewOrTop,
  305. () =>
  306. {
  307. ApplicationNavigation.MoveNextViewOrTop ();
  308. return true;
  309. }
  310. );
  311. AddCommand (
  312. Command.PreviousViewOrTop,
  313. () =>
  314. {
  315. ApplicationNavigation.MovePreviousViewOrTop ();
  316. return true;
  317. }
  318. );
  319. AddCommand (
  320. Command.Refresh,
  321. () =>
  322. {
  323. Refresh ();
  324. return true;
  325. }
  326. );
  327. KeyBindings.Clear ();
  328. // Resources/config.json overrrides
  329. NextTabKey = Key.Tab;
  330. PrevTabKey = Key.Tab.WithShift;
  331. NextTabGroupKey = Key.F6;
  332. PrevTabGroupKey = Key.F6.WithShift;
  333. QuitKey = Key.Esc;
  334. KeyBindings.Add (QuitKey, KeyBindingScope.Application, Command.QuitToplevel);
  335. KeyBindings.Add (Key.CursorRight, KeyBindingScope.Application, Command.NextView);
  336. KeyBindings.Add (Key.CursorDown, KeyBindingScope.Application, Command.NextView);
  337. KeyBindings.Add (Key.CursorLeft, KeyBindingScope.Application, Command.PreviousView);
  338. KeyBindings.Add (Key.CursorUp, KeyBindingScope.Application, Command.PreviousView);
  339. KeyBindings.Add (NextTabKey, KeyBindingScope.Application, Command.NextView);
  340. KeyBindings.Add (PrevTabKey, KeyBindingScope.Application, Command.PreviousView);
  341. KeyBindings.Add (NextTabGroupKey, KeyBindingScope.Application, Command.NextViewOrTop); // Needed on Unix
  342. KeyBindings.Add (PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousViewOrTop); // Needed on Unix
  343. // TODO: Refresh Key should be configurable
  344. KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
  345. // TODO: Suspend Key should be configurable
  346. if (Environment.OSVersion.Platform == PlatformID.Unix)
  347. {
  348. KeyBindings.Add (Key.Z.WithCtrl, KeyBindingScope.Application, Command.Suspend);
  349. }
  350. #if UNIX_KEY_BINDINGS
  351. KeyBindings.Add (Key.L.WithCtrl, Command.Refresh); // Unix
  352. KeyBindings.Add (Key.F.WithCtrl, Command.NextView); // Unix
  353. KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix
  354. KeyBindings.Add (Key.B.WithCtrl, Command.PreviousView); // Unix
  355. #endif
  356. }
  357. /// <summary>
  358. /// Gets the list of Views that have <see cref="KeyBindingScope.Application"/> key bindings.
  359. /// </summary>
  360. /// <remarks>
  361. /// This is an internal method used by the <see cref="View"/> class to add Application key bindings.
  362. /// </remarks>
  363. /// <returns>The list of Views that have Application-scoped key bindings.</returns>
  364. internal static List<KeyBinding> GetViewKeyBindings ()
  365. {
  366. // Get the list of views that do not have Application-scoped key bindings
  367. return KeyBindings.Bindings
  368. .Where (kv => kv.Value.Scope != KeyBindingScope.Application)
  369. .Select (kv => kv.Value)
  370. .Distinct ()
  371. .ToList ();
  372. }
  373. ///// <summary>
  374. ///// Gets the list of Views that have <see cref="KeyBindingScope.Application"/> key bindings for the specified key.
  375. ///// </summary>
  376. ///// <remarks>
  377. ///// This is an internal method used by the <see cref="View"/> class to add Application key bindings.
  378. ///// </remarks>
  379. ///// <param name="key">The key to check.</param>
  380. ///// <param name="views">Outputs the list of views bound to <paramref name="key"/></param>
  381. ///// <returns><see langword="True"/> if successful.</returns>
  382. //internal static bool TryGetKeyBindings (Key key, out List<View> views) { return _keyBindings.TryGetValue (key, out views); }
  383. /// <summary>
  384. /// Removes all <see cref="KeyBindingScope.Application"/> scoped key bindings for the specified view.
  385. /// </summary>
  386. /// <remarks>
  387. /// This is an internal method used by the <see cref="View"/> class to remove Application key bindings.
  388. /// </remarks>
  389. /// <param name="view">The view that is bound to the key.</param>
  390. internal static void RemoveKeyBindings (View view)
  391. {
  392. List<KeyBinding> list = KeyBindings.Bindings
  393. .Where (kv => kv.Value.Scope != KeyBindingScope.Application)
  394. .Select (kv => kv.Value)
  395. .Distinct ()
  396. .ToList ();
  397. }
  398. }