#nullable enable using System.Collections.Generic; namespace Terminal.Gui; public abstract class Bindings where TBind : IInputBinding, new() { protected readonly Dictionary _bindings = new (); private readonly Func _constructBinding; protected Bindings (Func constructBinding) { _constructBinding = constructBinding; } /// Adds a to the collection. /// /// public void Add (TKey mouseEventArgs, TBind binding) { if (TryGet (mouseEventArgs, out TBind _)) { throw new InvalidOperationException (@$"A binding for {mouseEventArgs} exists ({binding})."); } // IMPORTANT: Add a COPY of the mouseEventArgs. This is needed because ConfigurationManager.Apply uses DeepMemberWiseCopy // IMPORTANT: update the memory referenced by the key, and Dictionary uses caching for performance, and thus // IMPORTANT: Apply will update the Dictionary with the new mouseEventArgs, but the old mouseEventArgs will still be in the dictionary. // IMPORTANT: See the ConfigurationManager.Illustrate_DeepMemberWiseCopy_Breaks_Dictionary test for details. _bindings.Add (mouseEventArgs, binding); } /// Gets the commands bound with the specified . /// /// The key to check. /// /// When this method returns, contains the commands bound with the specified mouse flags, if the mouse flags are /// found; otherwise, null. This parameter is passed uninitialized. /// /// if the mouse flags are bound; otherwise . public bool TryGet (TKey mouseEventArgs, out TBind? binding) { return _bindings.TryGetValue (mouseEventArgs, out binding); } /// /// Adds a new mouse flag combination that will trigger the commands in . /// /// If the key is already bound to a different array of s it will be rebound /// . /// /// /// /// Commands are only ever applied to the current (i.e. this feature cannot be used to switch /// focus to another view and perform multiple commands there). /// /// The mouse flags to check. /// /// The command to invoked on the when is received. When /// multiple commands are provided,they will be applied in sequence. The bound event /// will be /// consumed if any took effect. /// public void Add (TKey mouseFlags, params Command [] commands) { if (EqualityComparer.Default.Equals (mouseFlags, default)) { throw new ArgumentException (@"Invalid MouseFlag", nameof (mouseFlags)); } if (commands.Length == 0) { throw new ArgumentException (@"At least one command must be specified", nameof (commands)); } if (TryGet (mouseFlags, out var binding)) { throw new InvalidOperationException (@$"A binding for {mouseFlags} exists ({binding})."); } Add (mouseFlags, _constructBinding(commands,mouseFlags)); } /// /// Gets the bindings. /// /// public IEnumerable> GetBindings () { return _bindings; } /// Removes all objects from the collection. public void Clear () { _bindings.Clear (); } /// /// Removes all bindings that trigger the given command set. Views can have multiple different events bound to /// the same command sets and this method will clear all of them. /// /// public void Clear (params Command [] command) { KeyValuePair [] kvps = _bindings .Where (kvp => kvp.Value.Commands.SequenceEqual (command)) .ToArray (); foreach (KeyValuePair kvp in kvps) { Remove (kvp.Key); } } /// Gets the for the specified combination of . /// /// public TBind? Get (TKey mouseEventArgs) { if (TryGet (mouseEventArgs, out var binding)) { return binding; } throw new InvalidOperationException ($"{mouseEventArgs} is not bound."); } /// Removes a from the collection. /// public void Remove (TKey mouseEventArgs) { if (!TryGet (mouseEventArgs, out var _)) { return; } _bindings.Remove (mouseEventArgs); } } /// /// Provides a collection of objects bound to a combination of . /// /// /// public class MouseBindings : Bindings { /// /// Initializes a new instance. This constructor is used when the are not bound to a /// . This is used for Application.MouseBindings and unit tests. /// public MouseBindings ():base( (commands, flags)=> new MouseBinding (commands, flags)) { } /// /// Gets combination of bound to the set of commands specified by /// . /// /// The set of commands to search. /// /// The combination of bound to the set of commands specified by /// . An empty list if the set of caommands was not found. /// public IEnumerable GetAllMouseFlagsFromCommands (params Command [] commands) { return _bindings.Where (a => a.Value.Commands.SequenceEqual (commands)).Select (a => a.Key); } /// /// Gets the that are bound. /// /// public IEnumerable GetBoundMouseFlags () { return _bindings.Keys; } /// Gets the array of s bound to if it exists. /// The key to check. /// /// The array of s if is bound. An empty /// array /// if not. /// public Command [] GetCommands (MouseFlags mouseFlags) { if (TryGet (mouseFlags, out MouseBinding bindings)) { return bindings.Commands; } return []; } /// /// Gets the first combination of bound to the set of commands specified by /// . /// /// The set of commands to search. /// /// The first combination of bound to the set of commands specified by /// . if the set of caommands was not found. /// public MouseFlags GetMouseFlagsFromCommands (params Command [] commands) { return _bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key; } /// Replaces the commands already bound to a combination of . /// /// /// If the combination of is not already bound, it will be added. /// /// /// The combination of bound to the command to be replaced. /// The set of commands to replace the old ones with. public void ReplaceCommands (MouseFlags mouseEventArgs, params Command [] newCommands) { if (TryGet (mouseEventArgs, out MouseBinding binding)) { Remove (mouseEventArgs); Add (mouseEventArgs, newCommands); } else { Add (mouseEventArgs, newCommands); } } /// Replaces a combination already bound to a set of s. /// /// The to be replaced. /// /// The new to be used. If no action /// will be taken. /// public void ReplaceMouseFlag (MouseFlags oldMouseFlags, MouseFlags newMouseFlags) { if (newMouseFlags == MouseFlags.None) { throw new ArgumentException (@"Invalid MouseFlag", nameof (newMouseFlags)); } if (TryGet (oldMouseFlags, out MouseBinding binding)) { Remove (oldMouseFlags); Add (newMouseFlags, binding); } else { Add (newMouseFlags, binding); } } /// Gets the commands bound with the specified . /// /// The key to check. /// /// When this method returns, contains the commands bound with the specified mouse flags, if the mouse flags are /// found; otherwise, null. This parameter is passed uninitialized. /// /// if the mouse flags are bound; otherwise . public bool TryGet (MouseFlags mouseEventArgs, out MouseBinding binding) { binding = new ([], mouseEventArgs); return _bindings.TryGetValue (mouseEventArgs, out binding); } }