// This code is distributed under MIT license.
// Copyright (c) 2015 George Mamaladze
// See license.txt or https://mit-license.org/
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Gma.System.MouseKeyHook.Implementation;
namespace Gma.System.MouseKeyHook.HotKeys
{
///
/// An immutable set of Hot Keys that provides an event for when the set is activated.
///
public class HotKeySet
{
///
/// A delegate representing the signature for the OnHotKeysDownHold event
///
///
///
public delegate void HotKeyHandler(object sender, HotKeyArgs e);
private readonly Dictionary 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 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
///
/// Creates an instance of the HotKeySet class. Once created, the keys cannot be changed.
///
/// Set of Hot Keys
public HotKeySet(IEnumerable hotkeys)
{
m_hotkeystate = new Dictionary();
m_remapping = new Dictionary();
HotKeys = hotkeys;
InitializeKeys();
}
///
/// Enables the ability to name the set
///
public string Name { get; set; }
///
/// Enables the ability to describe what the set is used for or supposed to do
///
public string Description { get; set; }
///
/// Gets the set of hotkeys that this class handles.
///
public IEnumerable HotKeys { get; }
///
/// Returns whether the set of Keys is activated
///
public bool HotKeysActivated
{
get { return m_hotkeydowncount == m_hotkeystate.Count - m_remappingCount; }
}
///
/// Gets or sets the enabled state of the HotKey set.
///
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;
}
}
///
/// Called as the user holds down the keys in the set. It is NOT triggered the first time the keys are set.
///
///
public event HotKeyHandler OnHotKeysDownHold;
///
/// 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.
///
public event HotKeyHandler OnHotKeysUp;
///
/// 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.
///
public event HotKeyHandler OnHotKeysDownOnce;
///
/// General invocation handler
///
///
private void InvokeHotKeyHandler(HotKeyHandler hotKeyDelegate)
{
if (hotKeyDelegate != null)
hotKeyDelegate(this, new HotKeyArgs(DateTime.Now));
}
///
/// Adds the keys into the dictionary tracking the keys and gets the real-time status of the Keys
/// from the OS
///
private void InitializeKeys()
{
foreach (var 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);
}
}
///
/// Unregisters a previously set exclusive or based on the primary key.
///
/// Any key used in the Registration method used to create an exclusive or set
///
/// 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.
///
public bool UnregisterExclusiveOrKey(Keys anyKeyInTheExclusiveOrSet)
{
var primaryKey = GetExclusiveOrPrimaryKey(anyKeyInTheExclusiveOrSet);
if (primaryKey == Keys.None || !m_remapping.ContainsValue(primaryKey))
return false;
var keystoremove = new List();
foreach (var pair in m_remapping)
if (pair.Value == primaryKey)
keystoremove.Add(pair.Key);
foreach (var k in keystoremove)
m_remapping.Remove(k);
--m_remappingCount;
return true;
}
///
/// Registers a group of Keys that are already part of the HotKeySet in order to provide better flexibility among keys.
///
///
/// HotKeySet hks = new HotKeySet( new [] { Keys.T, Keys.LShiftKey, Keys.RShiftKey } );
/// RegisterExclusiveOrKey( new [] { Keys.LShiftKey, Keys.RShiftKey } );
///
/// allows either Keys.LShiftKey or Keys.RShiftKey to be combined with Keys.T.
///
///
///
/// Primary key used for mapping or Keys.None on error
public Keys RegisterExclusiveOrKey(IEnumerable orKeySet)
{
//Verification first, so as to not leave the m_remapping with a partial set.
foreach (var k in orKeySet)
if (!m_hotkeystate.ContainsKey(k))
return Keys.None;
var i = 0;
var primaryKey = Keys.None;
//Commit after verification
foreach (var 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;
}
///
/// Gets the primary key
///
///
/// The primary key if it exists, otherwise Keys.None
private Keys GetExclusiveOrPrimaryKey(Keys k)
{
return m_remapping.ContainsKey(k) ? m_remapping[k] : Keys.None;
}
///
/// Resolves obtaining the key used for state checking.
///
///
/// The primary key if it exists, otherwise the key entered
private Keys GetPrimaryKey(Keys k)
{
//If the key is remapped then get the primary keys
return m_remapping.ContainsKey(k) ? m_remapping[k] : k;
}
///
///
///
internal void OnKey(KeyEventArgsExt kex)
{
if (!Enabled)
return;
//Gets the primary key if mapped to a single key or gets the key itself
var 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
{
var 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
}
}
}
}