2
0

ShortcutHelper.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace Terminal.Gui {
  7. /// <summary>
  8. /// Represents a helper to manipulate shortcut keys used on views.
  9. /// </summary>
  10. public class ShortcutHelper {
  11. private Key shortcut;
  12. /// <summary>
  13. /// This is the global setting that can be used as a global shortcut to invoke the action on the view.
  14. /// </summary>
  15. public virtual Key Shortcut {
  16. get => shortcut;
  17. set {
  18. if (shortcut != value && (PostShortcutValidation (value) || value == Key.Null)) {
  19. shortcut = value;
  20. }
  21. }
  22. }
  23. /// <summary>
  24. /// The keystroke combination used in the <see cref="Shortcut"/> as string.
  25. /// </summary>
  26. public virtual string ShortcutTag => GetShortcutTag (shortcut);
  27. /// <summary>
  28. /// The action to run if the <see cref="Shortcut"/> is defined.
  29. /// </summary>
  30. public virtual Action ShortcutAction { get; set; }
  31. /// <summary>
  32. /// Gets the key with all the keys modifiers, especially the shift key that sometimes have to be injected later.
  33. /// </summary>
  34. /// <param name="kb">The <see cref="KeyEvent"/> to check.</param>
  35. /// <returns>The <see cref="KeyEvent.Key"/> with all the keys modifiers.</returns>
  36. public static Key GetModifiersKey (KeyEvent kb)
  37. {
  38. var key = kb.Key;
  39. if (kb.IsAlt && (key & Key.AltMask) == 0) {
  40. key |= Key.AltMask;
  41. }
  42. if (kb.IsCtrl && (key & Key.CtrlMask) == 0) {
  43. key |= Key.CtrlMask;
  44. }
  45. if (kb.IsShift && (key & Key.ShiftMask) == 0) {
  46. key |= Key.ShiftMask;
  47. }
  48. return key;
  49. }
  50. /// <summary>
  51. /// Get the <see cref="Shortcut"/> key as string.
  52. /// </summary>
  53. /// <param name="shortcut">The shortcut key.</param>
  54. /// <param name="delimiter">The delimiter string.</param>
  55. /// <returns></returns>
  56. public static string GetShortcutTag (Key shortcut, string delimiter = null)
  57. {
  58. if (shortcut == Key.Null) {
  59. return "";
  60. }
  61. var k = shortcut;
  62. if (delimiter == null) {
  63. delimiter = MenuBar.ShortcutDelimiter;
  64. }
  65. string tag = string.Empty;
  66. var sCut = GetKeyToString (k, out Key knm).ToString ();
  67. if (knm == Key.Unknown) {
  68. k &= ~Key.Unknown;
  69. sCut = GetKeyToString (k, out _).ToString ();
  70. }
  71. if ((k & Key.CtrlMask) != 0) {
  72. tag = "Ctrl";
  73. }
  74. if ((k & Key.ShiftMask) != 0) {
  75. if (!string.IsNullOrEmpty(tag)) {
  76. tag += delimiter;
  77. }
  78. tag += "Shift";
  79. }
  80. if ((k & Key.AltMask) != 0) {
  81. if (!string.IsNullOrEmpty(tag)) {
  82. tag += delimiter;
  83. }
  84. tag += "Alt";
  85. }
  86. string [] keys = sCut.Split (",");
  87. for (int i = 0; i < keys.Length; i++) {
  88. var key = keys [i].Trim ();
  89. if (key == Key.AltMask.ToString () || key == Key.ShiftMask.ToString () || key == Key.CtrlMask.ToString ()) {
  90. continue;
  91. }
  92. if (!string.IsNullOrEmpty(tag)) {
  93. tag += delimiter;
  94. }
  95. if (!key.Contains ("F") && key.Length > 2 && keys.Length == 1) {
  96. k = (uint)Key.AltMask + k;
  97. tag += ((char)k).ToString ();
  98. } else if (key.Length == 2 && key.StartsWith ("D")) {
  99. tag += ((char)key.ElementAt (1)).ToString ();
  100. } else {
  101. tag += key;
  102. }
  103. }
  104. return tag;
  105. }
  106. /// <summary>
  107. /// Return key as string.
  108. /// </summary>
  109. /// <param name="key">The key to extract.</param>
  110. /// <param name="knm">Correspond to the non modifier key.</param>
  111. public static string GetKeyToString (Key key, out Key knm)
  112. {
  113. if (key == Key.Null) {
  114. knm = Key.Null;
  115. return "";
  116. }
  117. knm = key;
  118. var mK = key & (Key.AltMask | Key.CtrlMask | Key.ShiftMask);
  119. knm &= ~mK;
  120. for (uint i = (uint)Key.F1; i < (uint)Key.F12; i++) {
  121. if (knm == (Key)i) {
  122. mK |= (Key)i;
  123. }
  124. }
  125. knm &= ~mK;
  126. uint.TryParse (knm.ToString (), out uint c);
  127. var s = mK == Key.Null ? "" : mK.ToString ();
  128. if (s != "" && (knm != Key.Null || c > 0)) {
  129. s += ",";
  130. }
  131. s += c == 0 ? knm == Key.Null ? "" : knm.ToString () : ((char)c).ToString ();
  132. return s;
  133. }
  134. /// <summary>
  135. /// Allows to retrieve a <see cref="Key"/> from a <see cref="ShortcutTag"/>
  136. /// </summary>
  137. /// <param name="tag">The key as string.</param>
  138. /// <param name="delimiter">The delimiter string.</param>
  139. public static Key GetShortcutFromTag (string tag, string delimiter = null)
  140. {
  141. var sCut = tag;
  142. if (string.IsNullOrEmpty(sCut)) {
  143. return default;
  144. }
  145. Key key = Key.Null;
  146. //var hasCtrl = false;
  147. if (delimiter == null) {
  148. delimiter = MenuBar.ShortcutDelimiter;
  149. }
  150. string [] keys = sCut.Split (delimiter);
  151. for (int i = 0; i < keys.Length; i++) {
  152. var k = keys [i];
  153. if (k == "Ctrl") {
  154. //hasCtrl = true;
  155. key |= Key.CtrlMask;
  156. } else if (k == "Shift") {
  157. key |= Key.ShiftMask;
  158. } else if (k == "Alt") {
  159. key |= Key.AltMask;
  160. } else if (k.StartsWith ("F") && k.Length > 1) {
  161. int.TryParse (k.Substring (1).ToString (), out int n);
  162. for (uint j = (uint)Key.F1; j <= (uint)Key.F12; j++) {
  163. int.TryParse (((Key)j).ToString ().Substring (1), out int f);
  164. if (f == n) {
  165. key |= (Key)j;
  166. }
  167. }
  168. } else {
  169. key |= (Key)Enum.Parse (typeof (Key), k.ToString ());
  170. }
  171. }
  172. return key;
  173. }
  174. /// <summary>
  175. /// Lookup for a <see cref="Key"/> on range of keys.
  176. /// </summary>
  177. /// <param name="key">The source key.</param>
  178. /// <param name="first">First key in range.</param>
  179. /// <param name="last">Last key in range.</param>
  180. public static bool CheckKeysFlagRange (Key key, Key first, Key last)
  181. {
  182. for (uint i = (uint)first; i < (uint)last; i++) {
  183. if ((key | (Key)i) == key) {
  184. return true;
  185. }
  186. }
  187. return false;
  188. }
  189. /// <summary>
  190. /// Used at key down or key press validation.
  191. /// </summary>
  192. /// <param name="key">The key to validate.</param>
  193. /// <returns><c>true</c> if is valid.<c>false</c>otherwise.</returns>
  194. public static bool PreShortcutValidation (Key key)
  195. {
  196. if ((key & (Key.CtrlMask | Key.ShiftMask | Key.AltMask)) == 0 && !CheckKeysFlagRange (key, Key.F1, Key.F12)) {
  197. return false;
  198. }
  199. return true;
  200. }
  201. /// <summary>
  202. /// Used at key up validation.
  203. /// </summary>
  204. /// <param name="key">The key to validate.</param>
  205. /// <returns><c>true</c> if is valid.<c>false</c>otherwise.</returns>
  206. public static bool PostShortcutValidation (Key key)
  207. {
  208. GetKeyToString (key, out Key knm);
  209. if (CheckKeysFlagRange (key, Key.F1, Key.F12) ||
  210. ((key & (Key.CtrlMask | Key.ShiftMask | Key.AltMask)) != 0 && knm != Key.Null && knm != Key.Unknown)) {
  211. return true;
  212. }
  213. return false;
  214. }
  215. /// <summary>
  216. /// Allows a view to run a <see cref="View.ShortcutAction"/> if defined.
  217. /// </summary>
  218. /// <param name="kb">The <see cref="KeyEvent"/></param>
  219. /// <param name="view">The <see cref="View"/></param>
  220. /// <returns><c>true</c> if defined <c>false</c>otherwise.</returns>
  221. public static bool FindAndOpenByShortcut (KeyEvent kb, View view = null)
  222. {
  223. if (view == null) {
  224. return false; }
  225. var key = kb.KeyValue;
  226. var keys = GetModifiersKey (kb);
  227. key |= (int)keys;
  228. foreach (var v in view.Subviews) {
  229. if (v.Shortcut != Key.Null && v.Shortcut == (Key)key) {
  230. var action = v.ShortcutAction;
  231. if (action != null) {
  232. Application.MainLoop.AddIdle (() => {
  233. action ();
  234. return false;
  235. });
  236. }
  237. return true;
  238. }
  239. if (FindAndOpenByShortcut (kb, v)) {
  240. return true;
  241. }
  242. }
  243. return false;
  244. }
  245. }
  246. }