KeyBindingsDialog.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Terminal.Gui;
  5. namespace UICatalog;
  6. internal class KeyBindingsDialog : Dialog
  7. {
  8. // TODO: Update to use Key instead of KeyCode
  9. private static readonly Dictionary<Command, KeyCode> CurrentBindings = new ();
  10. private readonly Command [] _commands;
  11. private readonly ListView _commandsListView;
  12. private readonly Label _keyLabel;
  13. public KeyBindingsDialog ()
  14. {
  15. Title = "Keybindings";
  16. //Height = 50;
  17. //Width = 10;
  18. if (ViewTracker.Instance == null)
  19. {
  20. ViewTracker.Initialize ();
  21. }
  22. // known commands that views can support
  23. _commands = Enum.GetValues (typeof (Command)).Cast<Command> ().ToArray ();
  24. _commandsListView = new ListView
  25. {
  26. Width = Dim.Percent (50),
  27. Height = Dim.Percent (100) - 1,
  28. Source = new ListWrapper (_commands),
  29. SelectedItem = 0
  30. };
  31. Add (_commandsListView);
  32. _keyLabel = new Label { Text = "Key: None", Width = Dim.Fill (), X = Pos.Percent (50), Y = 0 };
  33. Add (_keyLabel);
  34. var btnChange = new Button { X = Pos.Percent (50), Y = 1, Text = "Ch_ange" };
  35. Add (btnChange);
  36. btnChange.Accept += RemapKey;
  37. var close = new Button { Text = "Ok" };
  38. close.Accept += (s, e) =>
  39. {
  40. Application.RequestStop ();
  41. ViewTracker.Instance.StartUsingNewKeyMap (CurrentBindings);
  42. };
  43. AddButton (close);
  44. var cancel = new Button { Text = "Cancel" };
  45. cancel.Accept += (s, e) => Application.RequestStop ();
  46. AddButton (cancel);
  47. // Register event handler as the last thing in constructor to prevent early calls
  48. // before it is even shown (e.g. OnEnter)
  49. _commandsListView.SelectedItemChanged += CommandsListView_SelectedItemChanged;
  50. // Setup to show first ListView entry
  51. SetTextBoxToShowBinding (_commands.First ());
  52. }
  53. private void CommandsListView_SelectedItemChanged (object sender, ListViewItemEventArgs obj) { SetTextBoxToShowBinding ((Command)obj.Value); }
  54. private void RemapKey (object sender, EventArgs e)
  55. {
  56. Command cmd = _commands [_commandsListView.SelectedItem];
  57. KeyCode? key = null;
  58. // prompt user to hit a key
  59. var dlg = new Dialog { Title = "Enter Key" };
  60. dlg.KeyDown += (s, k) =>
  61. {
  62. key = k.KeyCode;
  63. Application.RequestStop ();
  64. };
  65. Application.Run (dlg);
  66. dlg.Dispose ();
  67. if (key.HasValue)
  68. {
  69. CurrentBindings [cmd] = key.Value;
  70. SetTextBoxToShowBinding (cmd);
  71. }
  72. }
  73. private void SetTextBoxToShowBinding (Command cmd)
  74. {
  75. if (CurrentBindings.ContainsKey (cmd))
  76. {
  77. _keyLabel.Text = "Key: " + CurrentBindings [cmd];
  78. }
  79. else
  80. {
  81. _keyLabel.Text = "Key: None";
  82. }
  83. SetNeedsDisplay ();
  84. }
  85. /// <summary>Tracks views as they are created in UICatalog so that their keybindings can be managed.</summary>
  86. private class ViewTracker
  87. {
  88. /// <summary>All views seen so far and a bool to indicate if we have applied keybindings to them</summary>
  89. private readonly Dictionary<View, bool> _knownViews = new ();
  90. private readonly object _lockKnownViews = new ();
  91. private Dictionary<Command, KeyCode> _keybindings;
  92. private ViewTracker (View top)
  93. {
  94. RecordView (top);
  95. // Refresh known windows
  96. Application.AddTimeout (
  97. TimeSpan.FromMilliseconds (100),
  98. () =>
  99. {
  100. lock (_lockKnownViews)
  101. {
  102. RecordView (Application.Top);
  103. ApplyKeyBindingsToAllKnownViews ();
  104. }
  105. return true;
  106. }
  107. );
  108. }
  109. public static ViewTracker Instance { get; private set; }
  110. internal static void Initialize () { Instance = new ViewTracker (Application.Top); }
  111. internal void StartUsingNewKeyMap (Dictionary<Command, KeyCode> currentBindings)
  112. {
  113. lock (_lockKnownViews)
  114. {
  115. // change our knowledge of what keys to bind
  116. _keybindings = currentBindings;
  117. // Mark that we have not applied the key bindings yet to any views
  118. foreach (View view in _knownViews.Keys)
  119. {
  120. _knownViews [view] = false;
  121. }
  122. }
  123. }
  124. private void ApplyKeyBindingsToAllKnownViews ()
  125. {
  126. if (_keybindings == null)
  127. {
  128. return;
  129. }
  130. // Key is the view Value is whether we have already done it
  131. foreach (KeyValuePair<View, bool> viewDone in _knownViews)
  132. {
  133. View view = viewDone.Key;
  134. bool done = viewDone.Value;
  135. if (done)
  136. {
  137. // we have already applied keybindings to this view
  138. continue;
  139. }
  140. HashSet<Command> supported = new (view.GetSupportedCommands ());
  141. foreach (KeyValuePair<Command, KeyCode> kvp in _keybindings)
  142. {
  143. // if the view supports the keybinding
  144. if (supported.Contains (kvp.Key))
  145. {
  146. // if the key was bound to any other commands clear that
  147. view.KeyBindings.Remove (kvp.Value);
  148. view.KeyBindings.Add (kvp.Value, kvp.Key);
  149. }
  150. // mark that we have done this view so don't need to set keybindings again on it
  151. _knownViews [view] = true;
  152. }
  153. }
  154. }
  155. private void RecordView (View view)
  156. {
  157. if (!_knownViews.ContainsKey (view))
  158. {
  159. _knownViews.Add (view, false);
  160. }
  161. // may already have subviews that were added to it
  162. // before we got to it
  163. foreach (View sub in view.Subviews)
  164. {
  165. RecordView (sub);
  166. }
  167. // TODO: BUG: Based on my new understanding of Added event I think this is wrong
  168. // (and always was wrong). Parents don't get to be told when new views are added
  169. // to them
  170. view.Added += (s, e) => RecordView (e.Child);
  171. }
  172. }
  173. }