using NStack; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Terminal.Gui { /// /// Represents a helper to manipulate shortcut keys used on views. /// public class ShortcutHelper { private Key shortcut; /// /// This is the global setting that can be used as a global shortcut to invoke the action on the view. /// public virtual Key Shortcut { get => shortcut; set { if (shortcut != value && (PostShortcutValidation (value) || value == Key.Null)) { shortcut = value; } } } /// /// The keystroke combination used in the as string. /// public virtual ustring ShortcutTag => GetShortcutTag (shortcut); /// /// The action to run if the is defined. /// public virtual Action ShortcutAction { get; set; } /// /// Gets the key with all the keys modifiers, especially the shift key that sometimes have to be injected later. /// /// The to check. /// The with all the keys modifiers. public static Key GetModifiersKey (KeyEvent kb) { var key = kb.Key; if (kb.IsAlt && (key & Key.AltMask) == 0) { key |= Key.AltMask; } if (kb.IsCtrl && (key & Key.CtrlMask) == 0) { key |= Key.CtrlMask; } if (kb.IsShift && (key & Key.ShiftMask) == 0) { key |= Key.ShiftMask; } return key; } /// /// Get the key as string. /// /// The shortcut key. /// The delimiter string. /// public static ustring GetShortcutTag (Key shortcut, ustring delimiter = null) { if (shortcut == Key.Null) { return ""; } var k = shortcut; if (delimiter == null) { delimiter = MenuBar.ShortcutDelimiter; } ustring tag = ustring.Empty; var sCut = GetKeyToString (k, out Key knm).ToString (); if (knm == Key.Unknown) { k &= ~Key.Unknown; sCut = GetKeyToString (k, out _).ToString (); } if ((k & Key.CtrlMask) != 0) { tag = "Ctrl"; } if ((k & Key.ShiftMask) != 0) { if (!tag.IsEmpty) { tag += delimiter; } tag += "Shift"; } if ((k & Key.AltMask) != 0) { if (!tag.IsEmpty) { tag += delimiter; } tag += "Alt"; } ustring [] keys = ustring.Make (sCut).Split (","); for (int i = 0; i < keys.Length; i++) { var key = keys [i].TrimSpace (); if (key == Key.AltMask.ToString () || key == Key.ShiftMask.ToString () || key == Key.CtrlMask.ToString ()) { continue; } if (!tag.IsEmpty) { tag += delimiter; } if (!key.Contains ("F") && key.Length > 2 && keys.Length == 1) { k = (uint)Key.AltMask + k; tag += ((char)k).ToString (); } else if (key.Length == 2 && key.StartsWith ("D")) { tag += ((char)key.ElementAt (1)).ToString (); } else { tag += key; } } return tag; } /// /// Return key as string. /// /// The key to extract. /// Correspond to the non modifier key. public static ustring GetKeyToString (Key key, out Key knm) { if (key == Key.Null) { knm = Key.Null; return ""; } knm = key; var mK = key & (Key.AltMask | Key.CtrlMask | Key.ShiftMask); knm &= ~mK; for (uint i = (uint)Key.F1; i < (uint)Key.F12; i++) { if (knm == (Key)i) { mK |= (Key)i; } } knm &= ~mK; uint.TryParse (knm.ToString (), out uint c); var s = mK == Key.Null ? "" : mK.ToString (); if (s != "" && (knm != Key.Null || c > 0)) { s += ","; } s += c == 0 ? knm == Key.Null ? "" : knm.ToString () : ((char)c).ToString (); return s; } /// /// Allows to retrieve a from a /// /// The key as string. /// The delimiter string. public static Key GetShortcutFromTag (ustring tag, ustring delimiter = null) { var sCut = tag; if (sCut.IsEmpty) { return default; } Key key = Key.Null; //var hasCtrl = false; if (delimiter == null) { delimiter = MenuBar.ShortcutDelimiter; } ustring [] keys = sCut.Split (delimiter); for (int i = 0; i < keys.Length; i++) { var k = keys [i]; if (k == "Ctrl") { //hasCtrl = true; key |= Key.CtrlMask; } else if (k == "Shift") { key |= Key.ShiftMask; } else if (k == "Alt") { key |= Key.AltMask; } else if (k.StartsWith ("F") && k.Length > 1) { int.TryParse (k.Substring (1).ToString (), out int n); for (uint j = (uint)Key.F1; j <= (uint)Key.F12; j++) { int.TryParse (((Key)j).ToString ().Substring (1), out int f); if (f == n) { key |= (Key)j; } } } else { key |= (Key)Enum.Parse (typeof (Key), k.ToString ()); } } return key; } /// /// Lookup for a on range of keys. /// /// The source key. /// First key in range. /// Last key in range. public static bool CheckKeysFlagRange (Key key, Key first, Key last) { for (uint i = (uint)first; i < (uint)last; i++) { if ((key | (Key)i) == key) { return true; } } return false; } /// /// Used at key down or key press validation. /// /// The key to validate. /// true if is valid.falseotherwise. public static bool PreShortcutValidation (Key key) { if ((key & (Key.CtrlMask | Key.ShiftMask | Key.AltMask)) == 0 && !CheckKeysFlagRange (key, Key.F1, Key.F12)) { return false; } return true; } /// /// Used at key up validation. /// /// The key to validate. /// true if is valid.falseotherwise. public static bool PostShortcutValidation (Key key) { GetKeyToString (key, out Key knm); if (CheckKeysFlagRange (key, Key.F1, Key.F12) || ((key & (Key.CtrlMask | Key.ShiftMask | Key.AltMask)) != 0 && knm != Key.Null && knm != Key.Unknown)) { return true; } return false; } /// /// Allows a view to run a if defined. /// /// The /// The /// true if defined falseotherwise. public static bool FindAndOpenByShortcut (KeyEvent kb, View view = null) { if (view == null) { return false; } var key = kb.KeyValue; var keys = GetModifiersKey (kb); key |= (int)keys; foreach (var v in view.Subviews) { if (v.Shortcut != Key.Null && v.Shortcut == (Key)key) { var action = v.ShortcutAction; if (action != null) { Application.MainLoop.AddIdle (() => { action (); return false; }); } return true; } if (FindAndOpenByShortcut (kb, v)) { return true; } } return false; } } }