You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

287 lines
11 KiB

// This code is distributed under MIT license.
// Copyright (c) 2015 George Mamaladze
// See license.txt or http://opensource.org/licenses/mit-license.php
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Gma.System.MouseKeyHook.Implementation;
namespace Gma.System.MouseKeyHook.HotKeys
{
/// <summary>
/// An immutable set of Hot Keys that provides an event for when the set is activated.
/// </summary>
public class HotKeySet
{
/// <summary>
/// A delegate representing the signature for the OnHotKeysDownHold event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void HotKeyHandler(object sender, HotKeyArgs e);
private readonly IEnumerable<Keys> m_hotkeys; //hotkeys provided by the user.
private readonly Dictionary<Keys, bool> m_hotkeystate; //Keeps track of the status of the set of Keys
/*
* Example of m_remapping:
* a single key from the set of Keys requested is chosen to be the reference key (aka primary key)
*
* m_remapping[ Keys.LShiftKey ] = Keys.LShiftKey
* m_remapping[ Keys.RShiftKey ] = Keys.LShiftKey
*
* This allows the m_hotkeystate to use a single key (primary key) from the set that will act on behalf of all the keys in the set,
* which in turn reduces to this:
*
* Keys k = Keys.RShiftKey
* Keys primaryKey = PrimaryKeyOf( k ) = Keys.LShiftKey
* m_hotkeystate[ primaryKey ] = true/false
*/
private readonly Dictionary<Keys, Keys> m_remapping; //Used for mapping multiple keys to a single key
private bool m_enabled = true; //enabled by default
//These provide the actual status of whether a set is truly activated or not.
private int m_hotkeydowncount; //number of hot keys down
private int m_remappingCount;
//the number of remappings, i.e., a set of mappings, not the individual count in m_remapping
/// <summary>
/// Creates an instance of the HotKeySet class. Once created, the keys cannot be changed.
/// </summary>
/// <param name="hotkeys">Set of Hot Keys</param>
public HotKeySet(IEnumerable<Keys> hotkeys)
{
m_hotkeystate = new Dictionary<Keys, bool>();
m_remapping = new Dictionary<Keys, Keys>();
m_hotkeys = hotkeys;
InitializeKeys();
}
/// <summary>
/// Enables the ability to name the set
/// </summary>
public string Name { get; set; }
/// <summary>
/// Enables the ability to describe what the set is used for or supposed to do
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets the set of hotkeys that this class handles.
/// </summary>
public IEnumerable<Keys> HotKeys
{
get { return m_hotkeys; }
}
/// <summary>
/// Returns whether the set of Keys is activated
/// </summary>
public bool HotKeysActivated
{
//The number of sets of remapped keys is used to offset the amount originally specified by the user.
get { return m_hotkeydowncount == (m_hotkeystate.Count - m_remappingCount); }
}
/// <summary>
/// Gets or sets the enabled state of the HotKey set.
/// </summary>
public bool Enabled
{
get { return m_enabled; }
set
{
if (value)
InitializeKeys(); //must get the actual current state of each key to update
m_enabled = value;
}
}
/// <summary>
/// Called as the user holds down the keys in the set. It is NOT triggered the first time the keys are set.
/// <see cref="OnHotKeysDownOnce" />
/// </summary>
public event HotKeyHandler OnHotKeysDownHold;
/// <summary>
/// Called whenever the hot key set is no longer active. This is essentially a KeyPress event, indicating that a full
/// key cycle has occurred, only for HotKeys because a single key removed from the set constitutes an incomplete set.
/// </summary>
public event HotKeyHandler OnHotKeysUp;
/// <summary>
/// Called the first time the down keys are set. It does not get called throughout the duration the user holds it but
/// only the
/// first time it's activated.
/// </summary>
public event HotKeyHandler OnHotKeysDownOnce;
/// <summary>
/// General invocation handler
/// </summary>
/// <param name="hotKeyDelegate"></param>
private void InvokeHotKeyHandler(HotKeyHandler hotKeyDelegate)
{
if (hotKeyDelegate != null)
hotKeyDelegate(this, new HotKeyArgs(DateTime.Now));
}
/// <summary>
/// Adds the keys into the dictionary tracking the keys and gets the real-time status of the Keys
/// from the OS
/// </summary>
private void InitializeKeys()
{
foreach (Keys k in HotKeys)
{
if (m_hotkeystate.ContainsKey(k))
m_hotkeystate.Add(k, false);
//assign using the current state of the keyboard
m_hotkeystate[k] = KeyboardState.GetCurrent().IsDown(k);
}
}
/// <summary>
/// Unregisters a previously set exclusive or based on the primary key.
/// </summary>
/// <param name="anyKeyInTheExclusiveOrSet">Any key used in the Registration method used to create an exclusive or set</param>
/// <returns>
/// True if successful. False doesn't indicate a failure to unregister, it indicates that the Key is not
/// registered as an Exclusive Or key or it's not the Primary Key.
/// </returns>
public bool UnregisterExclusiveOrKey(Keys anyKeyInTheExclusiveOrSet)
{
Keys primaryKey = GetExclusiveOrPrimaryKey(anyKeyInTheExclusiveOrSet);
if (primaryKey == Keys.None || !m_remapping.ContainsValue(primaryKey))
return false;
List<Keys> keystoremove = new List<Keys>();
foreach (KeyValuePair<Keys, Keys> pair in m_remapping)
{
if (pair.Value == primaryKey)
keystoremove.Add(pair.Key);
}
foreach (Keys k in keystoremove)
m_remapping.Remove(k);
--m_remappingCount;
return true;
}
/// <summary>
/// Registers a group of Keys that are already part of the HotKeySet in order to provide better flexibility among keys.
/// <example>
/// <code>
/// HotKeySet hks = new HotKeySet( new [] { Keys.T, Keys.LShiftKey, Keys.RShiftKey } );
/// RegisterExclusiveOrKey( new [] { Keys.LShiftKey, Keys.RShiftKey } );
/// </code>
/// allows either Keys.LShiftKey or Keys.RShiftKey to be combined with Keys.T.
/// </example>
/// </summary>
/// <param name="orKeySet"></param>
/// <returns>Primary key used for mapping or Keys.None on error</returns>
public Keys RegisterExclusiveOrKey(IEnumerable<Keys> orKeySet)
{
//Verification first, so as to not leave the m_remapping with a partial set.
foreach (Keys k in orKeySet)
{
if (!m_hotkeystate.ContainsKey(k))
return Keys.None;
}
int i = 0;
Keys primaryKey = Keys.None;
//Commit after verification
foreach (Keys k in orKeySet)
{
if (i == 0)
primaryKey = k;
m_remapping[k] = primaryKey;
++i;
}
//Must increase to keep a true count of how many keys are necessary for the activation to be true
++m_remappingCount;
return primaryKey;
}
/// <summary>
/// Gets the primary key
/// </summary>
/// <param name="k"></param>
/// <returns>The primary key if it exists, otherwise Keys.None</returns>
private Keys GetExclusiveOrPrimaryKey(Keys k)
{
return (m_remapping.ContainsKey(k) ? m_remapping[k] : Keys.None);
}
/// <summary>
/// Resolves obtaining the key used for state checking.
/// </summary>
/// <param name="k"></param>
/// <returns>The primary key if it exists, otherwise the key entered</returns>
private Keys GetPrimaryKey(Keys k)
{
//If the key is remapped then get the primary keys
return (m_remapping.ContainsKey(k) ? m_remapping[k] : k);
}
/// <summary>
/// </summary>
/// <param name="kex"></param>
internal void OnKey(KeyEventArgsExt kex)
{
if (!Enabled)
return;
//Gets the primary key if mapped to a single key or gets the key itself
Keys primaryKey = GetPrimaryKey(kex.KeyCode);
if (kex.IsKeyDown)
OnKeyDown(primaryKey);
else //reset
OnKeyUp(primaryKey);
}
private void OnKeyDown(Keys k)
{
//If the keys are activated still then keep invoking the event
if (HotKeysActivated)
InvokeHotKeyHandler(OnHotKeysDownHold); //Call the duration event
//indicates the key's state is current false but the key is now down
else if (m_hotkeystate.ContainsKey(k) && !m_hotkeystate[k])
{
m_hotkeystate[k] = true; //key's state is down
++m_hotkeydowncount; //increase the number of keys down in this set
if (HotKeysActivated) //because of the increase, check whether the set is activated
InvokeHotKeyHandler(OnHotKeysDownOnce); //Call the initial event
}
}
private void OnKeyUp(Keys k)
{
if (m_hotkeystate.ContainsKey(k) && m_hotkeystate[k]) //indicates the key's state was down but now it's up
{
bool wasActive = HotKeysActivated;
m_hotkeystate[k] = false; //key's state is up
--m_hotkeydowncount; //this set is no longer ready
if (wasActive)
InvokeHotKeyHandler(OnHotKeysUp); //call the KeyUp event because the set is no longer active
}
}
}
}