Application.Keyboard.cs 16 KB

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