KeyBindingsDialog.cs 7.1 KB

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