Skip to content
This repository has been archived by the owner on Feb 2, 2023. It is now read-only.

Commit

Permalink
Improve validity checks
Browse files Browse the repository at this point in the history
Improved use cases:

1.
- hotkey1 and hotkey2 use Key.Left and Key.Right, respectively, and have the same modifiers
- set hotkey1 to Key.Right; it's shown as invalid as the key is already used by hotkey2
- set hotkey2 to Key.Left (or any other key different from Key.Right)
- hotkey1 changes its state back to valid, since Key.Right is not used anymore by hotkey2

2. (Same as case 1., but with mouse buttons)

3.
- hotkey1 and hotkey2 both use Key.Left and have the same modifiers; hotkey1 is shown as invalid
- set hotkey2 to MouseAction.XButton1 (or any other MouseAction)
- hotkey1 changes its state to valid, since Key.Left is not used anymore by hotkey2

4. (Same as case 3., but with keys in place of mouse buttons and mouse buttons in place of keys)
  • Loading branch information
aleab committed Aug 31, 2018
1 parent 5b468da commit 9f94a50
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 22 deletions.
97 changes: 97 additions & 0 deletions Toastify.Test/Model/GenericHotkeyProxyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,27 @@ public void TestIsValid(Action test)
Test(test);
}

[Test(Author = "aleab"), Apartment(ApartmentState.STA)]
[TestCaseSource(typeof(GenericHotkeyProxyData), nameof(GenericHotkeyProxyData.InvalidReasonTestCases))]
public void TestInvalidReason(Action test)
{
Test(test);
}

[Test(Author = "aleab"), Apartment(ApartmentState.STA)]
[TestCaseSource(typeof(GenericHotkeyProxyData), nameof(GenericHotkeyProxyData.SetActivatorTestCases))]
public void TestSetActivator(Action test)
{
Test(test);
}

[Test(Author = "aleab"), Apartment(ApartmentState.STA)]
[TestCaseSource(typeof(GenericHotkeyProxyData), nameof(GenericHotkeyProxyData.GetActivatorTestCases))]
public void TestGetActivator(Action test)
{
Test(test);
}

[Test(Author = "aleab"), Apartment(ApartmentState.STA)]
[TestCaseSource(typeof(GenericHotkeyProxyData), nameof(GenericHotkeyProxyData.IsAlreadyInUseByTestCases))]
public bool TestIsAlreadyInUseBy([NotNull] Func<GenericHotkeyProxy> getHotkey, Func<GenericHotkeyProxy> getArg)
Expand Down Expand Up @@ -290,6 +304,47 @@ public static IEnumerable IsValidTestCases
}
}

public static IEnumerable InvalidReasonTestCases
{
get
{
yield return new TestCaseData(new Action(() =>
{
using (Hotkey hotkey = new KeyboardHotkey(FakeAction) { Key = Key.Left, Modifiers = ModifierKeys.Control })
{
hotkey.SetIsValid(false, "Invalid");
var genericHotkeyProxy = new GenericHotkeyProxy(hotkey);
Assert.That(genericHotkeyProxy.InvalidReason, Is.EqualTo(genericHotkeyProxy.Hotkey.InvalidReason));
}
})).SetName("Same as the underlying hotkey | SetIsValid(false)");
yield return new TestCaseData(new Action(() =>
{
using (Hotkey hotkey = new KeyboardHotkey(FakeAction) { Key = Key.Left, Modifiers = ModifierKeys.Control })
{
hotkey.SetIsValid(true, string.Empty);
var genericHotkeyProxy = new GenericHotkeyProxy(hotkey);
Assert.That(genericHotkeyProxy.InvalidReason, Is.EqualTo(genericHotkeyProxy.Hotkey.InvalidReason));
}
})).SetName("Same as the underlying hotkey | SetIsValid(true)");

yield return new TestCaseData(new Action(() =>
{
using (Hotkey hotkey = new KeyboardHotkey(FakeAction) { Key = Key.Left, Modifiers = ModifierKeys.Control })
{
var genericHotkeyProxy = new GenericHotkeyProxy(hotkey);
genericHotkeyProxy.Hotkey.SetIsValid(false, "Invalid keayboard hotkey");
genericHotkeyProxy.Type = HotkeyType.MouseHook;
genericHotkeyProxy.Hotkey.SetIsValid(false, "Invalid mouse hotkey");
Assert.That(genericHotkeyProxy.InvalidReason, Is.EqualTo("Invalid mouse hotkey"));
genericHotkeyProxy.Type = HotkeyType.Keyboard;
Assert.That(genericHotkeyProxy.InvalidReason, Is.EqualTo("Invalid keayboard hotkey"));
}
})).SetName("Changing HotkeyType doesn't transfer the invalid reason");
}
}

public static IEnumerable SetActivatorTestCases
{
get
Expand Down Expand Up @@ -420,6 +475,48 @@ public static IEnumerable SetActivatorTestCases
}
}

public static IEnumerable GetActivatorTestCases
{
get
{
yield return new TestCaseData(new Action(() =>
{
var genericHotkeyProxy = new GenericHotkeyProxy();
Assert.That(genericHotkeyProxy.GetActivator(), Is.Null);
})).SetName("HotkeyType.Undefined");

yield return new TestCaseData(new Action(() =>
{
using (Hotkey hotkey = new KeyboardHotkey(FakeAction) { Key = Key.Left, Modifiers = ModifierKeys.Control })
{
var genericHotkeyProxy = new GenericHotkeyProxy(hotkey);
var underlyingHotkey = genericHotkeyProxy.Hotkey as KeyboardHotkey;
Assert.That(underlyingHotkey, Is.Not.Null);
object activator = genericHotkeyProxy.GetActivator();
Assert.That(activator, Is.Not.Null);
Assert.That(activator, Is.TypeOf<Key?>().Or.TypeOf<Key>());
Assert.That(activator, Is.EqualTo(underlyingHotkey.Key));
}
})).SetName("HotkeyType.Keyboard");

yield return new TestCaseData(new Action(() =>
{
using (Hotkey hotkey = new MouseHookHotkey(FakeAction) { MouseButton = MouseAction.XButton1, Modifiers = ModifierKeys.Control })
{
var genericHotkeyProxy = new GenericHotkeyProxy(hotkey);
var underlyingHotkey = genericHotkeyProxy.Hotkey as MouseHookHotkey;
Assert.That(underlyingHotkey, Is.Not.Null);
object activator = genericHotkeyProxy.GetActivator();
Assert.That(activator, Is.Not.Null);
Assert.That(activator, Is.TypeOf<MouseAction?>().Or.TypeOf<MouseAction>());
Assert.That(activator, Is.EqualTo(underlyingHotkey.MouseButton));
}
})).SetName("HotkeyType.MouseHook");
}
}

public static IEnumerable IsAlreadyInUseByTestCases
{
get
Expand Down
35 changes: 29 additions & 6 deletions Toastify/src/Model/GenericHotkeyProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,38 @@ public void SetActivator(object activator)
}
}

/// <summary>
/// Get the hotkey's activator, i.e. the key or mouse button that activates the hotkey.
/// </summary>
public object GetActivator()
{
switch (this.Type)
{
case HotkeyType.Keyboard:
return this.keyboardHotkey?.Key;

case HotkeyType.MouseHook:
return this.mouseHookHotkey?.MouseButton;

default:
// ignore
break;
}

return null;
}

public bool IsAlreadyInUseBy(GenericHotkeyProxy hotkeyProxy)
{
if (hotkeyProxy == null)
return false;
return hotkeyProxy != null && this.IsAlreadyInUseBy(hotkeyProxy.Hotkey.Modifiers, hotkeyProxy.Type, hotkeyProxy.GetActivator());
}

return this.Hotkey.Modifiers == hotkeyProxy.Hotkey.Modifiers &&
this.Type == hotkeyProxy.Type &&
(this.Type == HotkeyType.Keyboard && this.keyboardHotkey.Key == hotkeyProxy.keyboardHotkey.Key ||
this.Type == HotkeyType.MouseHook && this.mouseHookHotkey.MouseButton == hotkeyProxy.mouseHookHotkey.MouseButton);
public bool IsAlreadyInUseBy(ModifierKeys modifiers, HotkeyType type, object activator)
{
return this.Hotkey.Modifiers == modifiers &&
this.Type == type &&
(this.Type == HotkeyType.Keyboard && this.keyboardHotkey.Key == (activator as Key?) ||
this.Type == HotkeyType.MouseHook && this.mouseHookHotkey.MouseButton == (activator as MouseAction?));
}

#region INotifyPropertyChanged
Expand Down
49 changes: 34 additions & 15 deletions Toastify/src/View/SettingsView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ private IntPtr MouseHookProc(int nCode, WindowsMessagesFlags wParam, LowLevelMou
{
if (nCode >= 0)
{
if (Processes.IsCurrentProcessFocused() && this.TxtSingleKey.IsFocused &&
this.LstHotKeys.SelectedItem is GenericHotkeyProxy hotkeyProxy)
if (this.LstHotKeys.SelectedItem is GenericHotkeyProxy hotkeyProxy &&
this.TxtSingleKey.IsFocused && Processes.IsCurrentProcessFocused())
{
bool validButton = false;
MouseAction mouseAction = 0;
Expand Down Expand Up @@ -162,21 +162,44 @@ private IntPtr MouseHookProc(int nCode, WindowsMessagesFlags wParam, LowLevelMou
mouseAction = MouseAction.MWheelDown;
}
}

// TODO: Fix StackOverflowException when changing type of hotkey from KeyboardHotkey

if (validButton && Enum.IsDefined(typeof(MouseAction), mouseAction))
{
this.TxtSingleKey.Text = mouseAction.ToString();

hotkeyProxy.Type = HotkeyType.MouseHook;
hotkeyProxy.SetActivator(mouseAction);
}
this.UpdateHotkeyActivator(hotkeyProxy, HotkeyType.MouseHook, mouseAction, mouseAction.ToString());
}
}

return User32.CallNextHookEx(this.hHook, nCode, wParam, lParam);
}

private void UpdateHotkeyActivator(GenericHotkeyProxy hotkeyProxy, HotkeyType newHotkeyType, object newActivator, string text)
{
this.TxtSingleKey.Text = text;

// This should be called before changing the hotkey type; otherwise, it will return the Key/MouseAction
// used by the previous *keyboard/mouse hotkey* instead of the activator used by the previous *generic hotkey*.
// This doesn't make any difference if we are not changing hotkey type.
object previousActivator = hotkeyProxy.GetActivator();
HotkeyType previousType = hotkeyProxy.Type;

hotkeyProxy.Type = newHotkeyType;
hotkeyProxy.SetActivator(newActivator);

// Check every hotkey's validity again in case one of them has been set to use
// the same activator this hotkey was previously using.
var hotkeys = this.settingsViewModel.Hotkeys;
foreach (var hp in hotkeys)
{
if (hp == hotkeyProxy)
continue;

if (hp.IsAlreadyInUseBy(hotkeyProxy.Hotkey.Modifiers, previousType, previousActivator) && !previousActivator.Equals(hotkeyProxy.GetActivator()))
{
if (!this.settingsViewModel.CheckIfHotkeyIsAlreadyInUse(hp))
hp.Hotkey.SetIsValid(true, null);
}
}
}

#region Static Members

public static void Launch(ToastView toastView)
Expand Down Expand Up @@ -288,12 +311,8 @@ private void TxtSingleKey_OnPreviewKeyDown(object sender, KeyEventArgs e)
return;
}

this.UpdateHotkeyActivator(hotkeyProxy, HotkeyType.Keyboard, key, key.ToString());
e.Handled = true;

this.TxtSingleKey.Text = key.ToString();

hotkeyProxy.Type = HotkeyType.Keyboard;
hotkeyProxy.SetActivator(key);
}
}

Expand Down
2 changes: 1 addition & 1 deletion Toastify/src/ViewModel/SettingsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ private void SelectFileForSavingTrack()
this.Settings.SaveTrackToFilePath = dialog.FileName;
}

private bool CheckIfHotkeyIsAlreadyInUse(GenericHotkeyProxy hotkeyProxy)
public bool CheckIfHotkeyIsAlreadyInUse(GenericHotkeyProxy hotkeyProxy)
{
int i = 0;
bool alreadyInUse = false;
Expand Down

0 comments on commit 9f94a50

Please sign in to comment.