#nullable enable
namespace Terminal.Gui;
///
/// Provides a collection of objects bound to a combination of .
///
///
///
public class MouseBindings
{
///
/// 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 () { }
/// Adds a to the collection.
///
///
public void Add (MouseEventArgs mouseEventArgs, MouseBinding binding)
{
if (TryGet (mouseEventArgs, out MouseBinding _))
{
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);
}
///
/// 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 (MouseEventArgs mouseEventArgs, params Command [] commands)
{
if (mouseEventArgs.Flags == MouseFlags.None)
{
throw new ArgumentException (@"Invalid MouseFlag", nameof (commands));
}
if (commands.Length == 0)
{
throw new ArgumentException (@"At least one command must be specified", nameof (commands));
}
if (TryGet (mouseEventArgs, out MouseBinding binding))
{
throw new InvalidOperationException (@$"A binding for {mouseEventArgs} exists ({binding}).");
}
Add (mouseEventArgs, new MouseBinding (commands, mouseEventArgs));
}
// TODO: Add a dictionary comparer that ignores Scope
// TODO: This should not be public!
/// The collection of objects.
public Dictionary Bindings { get; } = new ();
/// 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 MouseBinding Get (MouseEventArgs mouseEventArgs)
{
if (TryGet (mouseEventArgs, out MouseBinding binding))
{
return binding;
}
throw new InvalidOperationException ($"{mouseEventArgs} is not bound.");
}
///
/// 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 GetAllMouseEventArgsFromCommands (params Command [] commands)
{
return Bindings.Where (a => a.Value.Commands.SequenceEqual (commands)).Select (a => a.Key);
}
///
/// Gets the that are bound.
///
///
public IEnumerable GetBoundMouseEventArgs () { 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 (MouseEventArgs mouseEventArgs)
{
if (TryGet (mouseEventArgs, 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 MouseEventArgs? GetMouseEventArgsFromCommands (params Command [] commands)
{
return Bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key;
}
/// Removes a from the collection.
///
public void Remove (MouseEventArgs mouseEventArgs)
{
if (!TryGet (mouseEventArgs, out MouseBinding _))
{
return;
}
Bindings.Remove (mouseEventArgs);
}
/// 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 (MouseEventArgs mouseEventArgs, params Command [] commands)
{
if (TryGet (mouseEventArgs, out MouseBinding binding))
{
binding.Commands = commands;
}
else
{
Add (mouseEventArgs, commands);
}
}
/// 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 ReplaceKey (MouseEventArgs oldMouseEventArgs, MouseEventArgs newMouseEventArgs)
{
if (!TryGet (oldMouseEventArgs, out MouseBinding _))
{
throw new InvalidOperationException ($"Key {oldMouseEventArgs} is not bound.");
}
MouseBinding value = Bindings [oldMouseEventArgs];
Remove (oldMouseEventArgs);
Add (newMouseEventArgs, value);
}
/// 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 (MouseEventArgs mouseEventArgs, out MouseBinding binding)
{
binding = new ([], mouseEventArgs);
return Bindings.TryGetValue (mouseEventArgs, out binding);
}
}