ViewKeyboard.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Text;
  6. namespace Terminal.Gui {
  7. public partial class View {
  8. ShortcutHelper _shortcutHelper;
  9. /// <summary>
  10. /// Event invoked when the <see cref="HotKey"/> is changed.
  11. /// </summary>
  12. public event EventHandler<KeyChangedEventArgs> HotKeyChanged;
  13. Key _hotKey = Key.Null;
  14. /// <summary>
  15. /// Gets or sets the HotKey defined for this view. A user pressing HotKey on the keyboard while this view has focus will cause the Clicked event to fire.
  16. /// </summary>
  17. public virtual Key HotKey {
  18. get => _hotKey;
  19. set {
  20. if (_hotKey != value) {
  21. var v = value == Key.Unknown ? Key.Null : value;
  22. if (_hotKey != Key.Null && ContainsKeyBinding (Key.Space | _hotKey)) {
  23. if (v == Key.Null) {
  24. ClearKeyBinding (Key.Space | _hotKey);
  25. } else {
  26. ReplaceKeyBinding (Key.Space | _hotKey, Key.Space | v);
  27. }
  28. } else if (v != Key.Null) {
  29. AddKeyBinding (Key.Space | v, Command.Accept);
  30. }
  31. _hotKey = TextFormatter.HotKey = v;
  32. }
  33. }
  34. }
  35. /// <summary>
  36. /// Gets or sets the specifier character for the hotkey (e.g. '_'). Set to '\xffff' to disable hotkey support for this View instance. The default is '\xffff'.
  37. /// </summary>
  38. public virtual Rune HotKeySpecifier {
  39. get {
  40. if (TextFormatter != null) {
  41. return TextFormatter.HotKeySpecifier;
  42. } else {
  43. return new Rune ('\xFFFF');
  44. }
  45. }
  46. set {
  47. TextFormatter.HotKeySpecifier = value;
  48. SetHotKey ();
  49. }
  50. }
  51. /// <summary>
  52. /// This is the global setting that can be used as a global shortcut to invoke an action if provided.
  53. /// </summary>
  54. public Key Shortcut {
  55. get => _shortcutHelper.Shortcut;
  56. set {
  57. if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == Key.Null)) {
  58. _shortcutHelper.Shortcut = value;
  59. }
  60. }
  61. }
  62. /// <summary>
  63. /// The keystroke combination used in the <see cref="Shortcut"/> as string.
  64. /// </summary>
  65. public string ShortcutTag => ShortcutHelper.GetShortcutTag (_shortcutHelper.Shortcut);
  66. /// <summary>
  67. /// The action to run if the <see cref="Shortcut"/> is defined.
  68. /// </summary>
  69. public virtual Action ShortcutAction { get; set; }
  70. // This is null, and allocated on demand.
  71. List<View> _tabIndexes;
  72. /// <summary>
  73. /// Configurable keybindings supported by the control
  74. /// </summary>
  75. private Dictionary<Key, Command []> KeyBindings { get; set; } = new Dictionary<Key, Command []> ();
  76. private Dictionary<Command, Func<bool?>> CommandImplementations { get; set; } = new Dictionary<Command, Func<bool?>> ();
  77. /// <summary>
  78. /// This returns a tab index list of the subviews contained by this view.
  79. /// </summary>
  80. /// <value>The tabIndexes.</value>
  81. public IList<View> TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty;
  82. int _tabIndex = -1;
  83. /// <summary>
  84. /// Indicates the index of the current <see cref="View"/> from the <see cref="TabIndexes"/> list.
  85. /// </summary>
  86. public int TabIndex {
  87. get { return _tabIndex; }
  88. set {
  89. if (!CanFocus) {
  90. _tabIndex = -1;
  91. return;
  92. } else if (SuperView?._tabIndexes == null || SuperView?._tabIndexes.Count == 1) {
  93. _tabIndex = 0;
  94. return;
  95. } else if (_tabIndex == value) {
  96. return;
  97. }
  98. _tabIndex = value > SuperView._tabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 : value < 0 ? 0 : value;
  99. _tabIndex = GetTabIndex (_tabIndex);
  100. if (SuperView._tabIndexes.IndexOf (this) != _tabIndex) {
  101. SuperView._tabIndexes.Remove (this);
  102. SuperView._tabIndexes.Insert (_tabIndex, this);
  103. SetTabIndex ();
  104. }
  105. }
  106. }
  107. int GetTabIndex (int idx)
  108. {
  109. var i = 0;
  110. foreach (var v in SuperView._tabIndexes) {
  111. if (v._tabIndex == -1 || v == this) {
  112. continue;
  113. }
  114. i++;
  115. }
  116. return Math.Min (i, idx);
  117. }
  118. void SetTabIndex ()
  119. {
  120. var i = 0;
  121. foreach (var v in SuperView._tabIndexes) {
  122. if (v._tabIndex == -1) {
  123. continue;
  124. }
  125. v._tabIndex = i;
  126. i++;
  127. }
  128. }
  129. bool _tabStop = true;
  130. /// <summary>
  131. /// This only be <see langword="true"/> if the <see cref="CanFocus"/> is also <see langword="true"/>
  132. /// and the focus can be avoided by setting this to <see langword="false"/>
  133. /// </summary>
  134. public bool TabStop {
  135. get => _tabStop;
  136. set {
  137. if (_tabStop == value) {
  138. return;
  139. }
  140. _tabStop = CanFocus && value;
  141. }
  142. }
  143. int _oldTabIndex;
  144. /// <summary>
  145. /// Invoked when a character key is pressed and occurs after the key up event.
  146. /// </summary>
  147. public event EventHandler<KeyEventEventArgs> KeyPressed;
  148. /// <inheritdoc/>
  149. public override bool ProcessKey (KeyEvent keyEvent)
  150. {
  151. if (!Enabled) {
  152. return false;
  153. }
  154. var args = new KeyEventEventArgs (keyEvent);
  155. KeyPressed?.Invoke (this, args);
  156. if (args.Handled)
  157. return true;
  158. if (Focused?.Enabled == true) {
  159. Focused?.KeyPressed?.Invoke (this, args);
  160. if (args.Handled)
  161. return true;
  162. }
  163. return Focused?.Enabled == true && Focused?.ProcessKey (keyEvent) == true;
  164. }
  165. /// <summary>
  166. /// Invokes any binding that is registered on this <see cref="View"/>
  167. /// and matches the <paramref name="keyEvent"/>
  168. /// </summary>
  169. /// <param name="keyEvent">The key event passed.</param>
  170. protected bool? InvokeKeybindings (KeyEvent keyEvent)
  171. {
  172. bool? toReturn = null;
  173. if (KeyBindings.ContainsKey (keyEvent.Key)) {
  174. foreach (var command in KeyBindings [keyEvent.Key]) {
  175. if (!CommandImplementations.ContainsKey (command)) {
  176. throw new NotSupportedException ($"A KeyBinding was set up for the command {command} ({keyEvent.Key}) but that command is not supported by this View ({GetType ().Name})");
  177. }
  178. // each command has its own return value
  179. var thisReturn = CommandImplementations [command] ();
  180. // if we haven't got anything yet, the current command result should be used
  181. if (toReturn == null) {
  182. toReturn = thisReturn;
  183. }
  184. // if ever see a true then that's what we will return
  185. if (thisReturn ?? false) {
  186. toReturn = true;
  187. }
  188. }
  189. }
  190. return toReturn;
  191. }
  192. /// <summary>
  193. /// <para>Adds a new key combination that will trigger the given <paramref name="command"/>
  194. /// (if supported by the View - see <see cref="GetSupportedCommands"/>)
  195. /// </para>
  196. /// <para>If the key is already bound to a different <see cref="Command"/> it will be
  197. /// rebound to this one</para>
  198. /// <remarks>Commands are only ever applied to the current <see cref="View"/>(i.e. this feature
  199. /// cannot be used to switch focus to another view and perform multiple commands there) </remarks>
  200. /// </summary>
  201. /// <param name="key"></param>
  202. /// <param name="command">The command(s) to run on the <see cref="View"/> when <paramref name="key"/> is pressed.
  203. /// When specifying multiple commands, all commands will be applied in sequence. The bound <paramref name="key"/> strike
  204. /// will be consumed if any took effect.</param>
  205. public void AddKeyBinding (Key key, params Command [] command)
  206. {
  207. if (command.Length == 0) {
  208. throw new ArgumentException ("At least one command must be specified", nameof (command));
  209. }
  210. if (KeyBindings.ContainsKey (key)) {
  211. KeyBindings [key] = command;
  212. } else {
  213. KeyBindings.Add (key, command);
  214. }
  215. }
  216. /// <summary>
  217. /// Replaces a key combination already bound to <see cref="Command"/>.
  218. /// </summary>
  219. /// <param name="fromKey">The key to be replaced.</param>
  220. /// <param name="toKey">The new key to be used.</param>
  221. protected void ReplaceKeyBinding (Key fromKey, Key toKey)
  222. {
  223. if (KeyBindings.ContainsKey (fromKey)) {
  224. var value = KeyBindings [fromKey];
  225. KeyBindings.Remove (fromKey);
  226. KeyBindings [toKey] = value;
  227. }
  228. }
  229. /// <summary>
  230. /// Checks if the key binding already exists.
  231. /// </summary>
  232. /// <param name="key">The key to check.</param>
  233. /// <returns><see langword="true"/> If the key already exist, <see langword="false"/> otherwise.</returns>
  234. public bool ContainsKeyBinding (Key key)
  235. {
  236. return KeyBindings.ContainsKey (key);
  237. }
  238. /// <summary>
  239. /// Removes all bound keys from the View and resets the default bindings.
  240. /// </summary>
  241. public void ClearKeyBindings ()
  242. {
  243. KeyBindings.Clear ();
  244. }
  245. /// <summary>
  246. /// Clears the existing keybinding (if any) for the given <paramref name="key"/>.
  247. /// </summary>
  248. /// <param name="key"></param>
  249. public void ClearKeyBinding (Key key)
  250. {
  251. KeyBindings.Remove (key);
  252. }
  253. /// <summary>
  254. /// Removes all key bindings that trigger the given command. Views can have multiple different
  255. /// keys bound to the same command and this method will clear all of them.
  256. /// </summary>
  257. /// <param name="command"></param>
  258. public void ClearKeyBinding (params Command [] command)
  259. {
  260. foreach (var kvp in KeyBindings.Where (kvp => kvp.Value.SequenceEqual (command)).ToArray ()) {
  261. KeyBindings.Remove (kvp.Key);
  262. }
  263. }
  264. /// <summary>
  265. /// <para>States that the given <see cref="View"/> supports a given <paramref name="command"/>
  266. /// and what <paramref name="f"/> to perform to make that command happen
  267. /// </para>
  268. /// <para>If the <paramref name="command"/> already has an implementation the <paramref name="f"/>
  269. /// will replace the old one</para>
  270. /// </summary>
  271. /// <param name="command">The command.</param>
  272. /// <param name="f">The function.</param>
  273. protected void AddCommand (Command command, Func<bool?> f)
  274. {
  275. // if there is already an implementation of this command
  276. if (CommandImplementations.ContainsKey (command)) {
  277. // replace that implementation
  278. CommandImplementations [command] = f;
  279. } else {
  280. // else record how to perform the action (this should be the normal case)
  281. CommandImplementations.Add (command, f);
  282. }
  283. }
  284. /// <summary>
  285. /// Returns all commands that are supported by this <see cref="View"/>.
  286. /// </summary>
  287. /// <returns></returns>
  288. public IEnumerable<Command> GetSupportedCommands ()
  289. {
  290. return CommandImplementations.Keys;
  291. }
  292. /// <summary>
  293. /// Gets the key used by a command.
  294. /// </summary>
  295. /// <param name="command">The command to search.</param>
  296. /// <returns>The <see cref="Key"/> used by a <see cref="Command"/></returns>
  297. public Key GetKeyFromCommand (params Command [] command)
  298. {
  299. return KeyBindings.First (kb => kb.Value.SequenceEqual (command)).Key;
  300. }
  301. /// <inheritdoc/>
  302. public override bool ProcessHotKey (KeyEvent keyEvent)
  303. {
  304. if (!Enabled) {
  305. return false;
  306. }
  307. var args = new KeyEventEventArgs (keyEvent);
  308. if (MostFocused?.Enabled == true) {
  309. MostFocused?.KeyPressed?.Invoke (this, args);
  310. if (args.Handled)
  311. return true;
  312. }
  313. if (MostFocused?.Enabled == true && MostFocused?.ProcessKey (keyEvent) == true)
  314. return true;
  315. if (_subviews == null || _subviews.Count == 0)
  316. return false;
  317. foreach (var view in _subviews)
  318. if (view.Enabled && view.ProcessHotKey (keyEvent))
  319. return true;
  320. return false;
  321. }
  322. /// <inheritdoc/>
  323. public override bool ProcessColdKey (KeyEvent keyEvent)
  324. {
  325. if (!Enabled) {
  326. return false;
  327. }
  328. var args = new KeyEventEventArgs (keyEvent);
  329. KeyPressed?.Invoke (this, args);
  330. if (args.Handled)
  331. return true;
  332. if (MostFocused?.Enabled == true) {
  333. MostFocused?.KeyPressed?.Invoke (this, args);
  334. if (args.Handled)
  335. return true;
  336. }
  337. if (MostFocused?.Enabled == true && MostFocused?.ProcessKey (keyEvent) == true)
  338. return true;
  339. if (_subviews == null || _subviews.Count == 0)
  340. return false;
  341. foreach (var view in _subviews)
  342. if (view.Enabled && view.ProcessColdKey (keyEvent))
  343. return true;
  344. return false;
  345. }
  346. /// <summary>
  347. /// Invoked when a key is pressed.
  348. /// </summary>
  349. public event EventHandler<KeyEventEventArgs> KeyDown;
  350. /// <inheritdoc/>
  351. public override bool OnKeyDown (KeyEvent keyEvent)
  352. {
  353. if (!Enabled) {
  354. return false;
  355. }
  356. var args = new KeyEventEventArgs (keyEvent);
  357. KeyDown?.Invoke (this, args);
  358. if (args.Handled) {
  359. return true;
  360. }
  361. if (Focused?.Enabled == true) {
  362. Focused.KeyDown?.Invoke (this, args);
  363. if (args.Handled) {
  364. return true;
  365. }
  366. if (Focused?.OnKeyDown (keyEvent) == true) {
  367. return true;
  368. }
  369. }
  370. return false;
  371. }
  372. /// <summary>
  373. /// Invoked when a key is released.
  374. /// </summary>
  375. public event EventHandler<KeyEventEventArgs> KeyUp;
  376. /// <inheritdoc/>
  377. public override bool OnKeyUp (KeyEvent keyEvent)
  378. {
  379. if (!Enabled) {
  380. return false;
  381. }
  382. var args = new KeyEventEventArgs (keyEvent);
  383. KeyUp?.Invoke (this, args);
  384. if (args.Handled) {
  385. return true;
  386. }
  387. if (Focused?.Enabled == true) {
  388. Focused.KeyUp?.Invoke (this, args);
  389. if (args.Handled) {
  390. return true;
  391. }
  392. if (Focused?.OnKeyUp (keyEvent) == true) {
  393. return true;
  394. }
  395. }
  396. return false;
  397. }
  398. void SetHotKey ()
  399. {
  400. if (TextFormatter == null) {
  401. return; // throw new InvalidOperationException ("Can't set HotKey unless a TextFormatter has been created");
  402. }
  403. TextFormatter.FindHotKey (_text, HotKeySpecifier, true, out _, out var hk);
  404. if (_hotKey != hk) {
  405. HotKey = hk;
  406. }
  407. }
  408. }
  409. }