Files
Terminal.Gui/UICatalog/KeyBindingsDialog.cs
Tig 16055c53b0 Fixes #3039. Fix View.HotKey (#3249)
* Added View.DefaultCommand etc... Started on dedicated scenario

* Fixed un-shifted hotkeys -> Fixed Key Equals. Fixed WindowsDriver passing wrong key. Etc.

* Fixed Key Bindings and HotKeys

* Fixed Key Bindings and HotKeys

* Label now correctly supports hotkey

* Disabled unix hot keys because they are annoying and get in the way

* Updated nuget. fixed warnings

* Trying to fix ci/ci issue

* Trying to fix ci/ci issue

* Trying to fix ci/ci issue

* Changed TextChangingEventArgs to inherit from CancelEventArgs

* TextChangingEventArgs -> TextEventArgs

* Simplified Text events by having only on args class

* Fixed unit tests fail

* Simplified by removing TitleEventArgs

* POC of Title being primary for hotkey. Label and Button hacked to work

* POC of Title being primary for hotkey. Label and Button hacked to work - all unit tests pass

* Dropped Microsoft.NETFramework.ReferenceAssemblies

* Fixed Dialogs scenario hotkeys

* Fixed build warnings

* Fixed Border Title render bug

* Regiggering default command handling

* Regiggering default command handling

* Checkbox clean up

* Added StateEventArgs POC

* Command.Default -> Command.HotKey

* Command.Default -> Command.HotKey - fixed TableView

* Command.Default -> Command.HotKey - fixed TableView

* Updated reactive example

* Fixed Toplevel.BringOverlappedTopToFront - was reordering SubViews when it shouldn't

* WIP - broke

* Finished impl of StateEventArgs

* Deleted ToggleEventArgs.cs. Added StateEventArgs.cs

* XML doc fix

* Removed old code

* Removed commented out code

* Label.Clicked -> Label.Accept (missed this before)

* Removed Labels as Buttons scenario as it's not really  useful

* Moved SubView tests to own file

* Moved SubView tests to own file

* Simplified Text test

* Added OnAccept test

* Deleted DefaultCommand

* Modernized CheckBox

* New button test

* Cleaned up RadioGroup; added tests

* KeyCode->Key in ListView

* Added ListView unit tests

* ListView now does Accept correctly

* TreeView now does Accept correctly

* Cleaned up some TextField tests

* TextView now handles Accept properly; updated CharMap and Adornments scenarios to test

* Fixed ComboBox to deal with TextView now handles Accept properly; updated CharMap and Adornments scenarios to test

* Removed un-needed using statement
2024-02-22 15:11:26 -07:00

214 lines
6.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Terminal.Gui;
namespace UICatalog;
internal class KeyBindingsDialog : Dialog
{
// TODO: Update to use Key instead of KeyCode
private static readonly Dictionary<Command, KeyCode> CurrentBindings = new ();
private readonly Command [] _commands;
private readonly ListView _commandsListView;
private readonly Label _keyLabel;
public KeyBindingsDialog ()
{
Title = "Keybindings";
//Height = 50;
//Width = 10;
if (ViewTracker.Instance == null)
{
ViewTracker.Initialize ();
}
// known commands that views can support
_commands = Enum.GetValues (typeof (Command)).Cast<Command> ().ToArray ();
_commandsListView = new ListView
{
Width = Dim.Percent (50),
Height = Dim.Percent (100) - 1,
Source = new ListWrapper (_commands),
SelectedItem = 0
};
Add (_commandsListView);
_keyLabel = new Label { Text = "Key: None", Width = Dim.Fill (), X = Pos.Percent (50), Y = 0 };
Add (_keyLabel);
var btnChange = new Button { X = Pos.Percent (50), Y = 1, Text = "Ch_ange" };
Add (btnChange);
btnChange.Accept += RemapKey;
var close = new Button { Text = "Ok" };
close.Accept += (s, e) =>
{
Application.RequestStop ();
ViewTracker.Instance.StartUsingNewKeyMap (CurrentBindings);
};
AddButton (close);
var cancel = new Button { Text = "Cancel" };
cancel.Accept += (s, e) => Application.RequestStop ();
AddButton (cancel);
// Register event handler as the last thing in constructor to prevent early calls
// before it is even shown (e.g. OnEnter)
_commandsListView.SelectedItemChanged += CommandsListView_SelectedItemChanged;
// Setup to show first ListView entry
SetTextBoxToShowBinding (_commands.First ());
}
private void CommandsListView_SelectedItemChanged (object sender, ListViewItemEventArgs obj) { SetTextBoxToShowBinding ((Command)obj.Value); }
private void RemapKey (object sender, EventArgs e)
{
Command cmd = _commands [_commandsListView.SelectedItem];
KeyCode? key = null;
// prompt user to hit a key
var dlg = new Dialog { Title = "Enter Key" };
dlg.KeyDown += (s, k) =>
{
key = k.KeyCode;
Application.RequestStop ();
};
Application.Run (dlg);
if (key.HasValue)
{
CurrentBindings [cmd] = key.Value;
SetTextBoxToShowBinding (cmd);
}
}
private void SetTextBoxToShowBinding (Command cmd)
{
if (CurrentBindings.ContainsKey (cmd))
{
_keyLabel.Text = "Key: " + CurrentBindings [cmd];
}
else
{
_keyLabel.Text = "Key: None";
}
SetNeedsDisplay ();
}
/// <summary>Tracks views as they are created in UICatalog so that their keybindings can be managed.</summary>
private class ViewTracker
{
/// <summary>All views seen so far and a bool to indicate if we have applied keybindings to them</summary>
private readonly Dictionary<View, bool> _knownViews = new ();
private readonly object _lockKnownViews = new ();
private Dictionary<Command, KeyCode> _keybindings;
private ViewTracker (View top)
{
RecordView (top);
// Refresh known windows
Application.AddTimeout (
TimeSpan.FromMilliseconds (100),
() =>
{
lock (_lockKnownViews)
{
RecordView (Application.Top);
ApplyKeyBindingsToAllKnownViews ();
}
return true;
}
);
}
public static ViewTracker Instance { get; private set; }
internal static void Initialize () { Instance = new ViewTracker (Application.Top); }
internal void StartUsingNewKeyMap (Dictionary<Command, KeyCode> currentBindings)
{
lock (_lockKnownViews)
{
// change our knowledge of what keys to bind
_keybindings = currentBindings;
// Mark that we have not applied the key bindings yet to any views
foreach (View view in _knownViews.Keys)
{
_knownViews [view] = false;
}
}
}
private void ApplyKeyBindingsToAllKnownViews ()
{
if (_keybindings == null)
{
return;
}
// Key is the view Value is whether we have already done it
foreach (KeyValuePair<View, bool> viewDone in _knownViews)
{
View view = viewDone.Key;
bool done = viewDone.Value;
if (done)
{
// we have already applied keybindings to this view
continue;
}
HashSet<Command> supported = new (view.GetSupportedCommands ());
foreach (KeyValuePair<Command, KeyCode> kvp in _keybindings)
{
// if the view supports the keybinding
if (supported.Contains (kvp.Key))
{
// if the key was bound to any other commands clear that
view.KeyBindings.Remove (kvp.Value);
view.KeyBindings.Add (kvp.Value, kvp.Key);
}
// mark that we have done this view so don't need to set keybindings again on it
_knownViews [view] = true;
}
}
}
private void RecordView (View view)
{
if (!_knownViews.ContainsKey (view))
{
_knownViews.Add (view, false);
}
// may already have subviews that were added to it
// before we got to it
foreach (View sub in view.Subviews)
{
RecordView (sub);
}
// TODO: BUG: Based on my new understanding of Added event I think this is wrong
// (and always was wrong). Parents don't get to be told when new views are added
// to them
view.Added += (s, e) => RecordView (e.Child);
}
}
}