From d136da3da1d8840bda3086b964f68be54c7ea8d4 Mon Sep 17 00:00:00 2001 From: Anton Kasyanov Date: Wed, 15 Jun 2016 01:59:20 +0300 Subject: [PATCH] Provide support of HotKeys to manage thumbnails and corresponding EVE clients - implemented w/o GUI required to set hotkeys --- .../Configuration/ApplicationConfiguration.cs | 22 ++ .../IApplicationConfiguration.cs | 4 + Eve-O-Preview/Eve-O-Preview.csproj | 4 +- Eve-O-Preview/Hotkeys/Hotkey.cs | 332 ------------------ Eve-O-Preview/Hotkeys/HotkeyHandler.cs | 159 +++++++++ .../Hotkeys/HotkeyHandlerNativeMethods.cs | 23 ++ Eve-O-Preview/Hotkeys/HotkeyNativeMethods.cs | 24 -- .../Presentation/ThumbnailManager.cs | 13 +- .../UI/Implementation/ThumbnailView.cs | 50 +++ Eve-O-Preview/UI/Interface/IThumbnailView.cs | 4 + 10 files changed, 269 insertions(+), 366 deletions(-) delete mode 100644 Eve-O-Preview/Hotkeys/Hotkey.cs create mode 100644 Eve-O-Preview/Hotkeys/HotkeyHandler.cs create mode 100644 Eve-O-Preview/Hotkeys/HotkeyHandlerNativeMethods.cs delete mode 100644 Eve-O-Preview/Hotkeys/HotkeyNativeMethods.cs diff --git a/Eve-O-Preview/Configuration/ApplicationConfiguration.cs b/Eve-O-Preview/Configuration/ApplicationConfiguration.cs index 5a47636..a31af49 100644 --- a/Eve-O-Preview/Configuration/ApplicationConfiguration.cs +++ b/Eve-O-Preview/Configuration/ApplicationConfiguration.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Drawing; +using System.Windows.Forms; using Newtonsoft.Json; namespace EveOPreview.Configuration @@ -35,6 +36,7 @@ namespace EveOPreview.Configuration this.PerClientLayout = new Dictionary>(); this.FlatLayout = new Dictionary(); this.ClientLayout = new Dictionary(); + this.ClientHotkey = new Dictionary(); } public bool MinimizeToTray { get; set; } @@ -66,6 +68,8 @@ namespace EveOPreview.Configuration private Dictionary FlatLayout { get; set; } [JsonProperty] private Dictionary ClientLayout { get; set; } + [JsonProperty] + private Dictionary ClientHotkey { get; set; } public Point GetThumbnailLocation(string currentClient, string activeClient, Point defaultLocation) { @@ -129,5 +133,23 @@ namespace EveOPreview.Configuration { this.ClientLayout[currentClient] = layout; } + + public Keys GetClientHotkey(string currentClient) + { + string hotkey; + if (this.ClientHotkey.TryGetValue(currentClient, out hotkey)) + { + // Protect from incorrect values + object rawValue = (new KeysConverter()).ConvertFromInvariantString(hotkey); + return rawValue != null ? (Keys)rawValue : Keys.None; + } + + return Keys.None; + } + + public void SetClientHotkey(string currentClient, Keys hotkey) + { + this.ClientHotkey[currentClient] = (new KeysConverter()).ConvertToInvariantString(hotkey); + } } } \ No newline at end of file diff --git a/Eve-O-Preview/Configuration/IApplicationConfiguration.cs b/Eve-O-Preview/Configuration/IApplicationConfiguration.cs index ea42217..9460a82 100644 --- a/Eve-O-Preview/Configuration/IApplicationConfiguration.cs +++ b/Eve-O-Preview/Configuration/IApplicationConfiguration.cs @@ -1,4 +1,5 @@ using System.Drawing; +using System.Windows.Forms; namespace EveOPreview.Configuration { @@ -32,5 +33,8 @@ namespace EveOPreview.Configuration ClientLayout GetClientLayout(string currentClient); void SetClientLayout(string currentClient, ClientLayout layout); + + Keys GetClientHotkey(string currentClient); + void SetClientHotkey(string currentClient, Keys hotkey); } } \ No newline at end of file diff --git a/Eve-O-Preview/Eve-O-Preview.csproj b/Eve-O-Preview/Eve-O-Preview.csproj index 97000d1..2898e1d 100644 --- a/Eve-O-Preview/Eve-O-Preview.csproj +++ b/Eve-O-Preview/Eve-O-Preview.csproj @@ -119,8 +119,8 @@ - - + + Form diff --git a/Eve-O-Preview/Hotkeys/Hotkey.cs b/Eve-O-Preview/Hotkeys/Hotkey.cs deleted file mode 100644 index 5fc8132..0000000 --- a/Eve-O-Preview/Hotkeys/Hotkey.cs +++ /dev/null @@ -1,332 +0,0 @@ -using System; -using System.Windows.Forms; -using System.ComponentModel; -using System.Xml.Serialization; -using System.Runtime.InteropServices; - -namespace EveOPreview -{ - public class Hotkey : IMessageFilter - { - private static int _currentId; - private const int MaxId = 0xBFFF; - - [XmlElement("keyCode")] - private Keys _keyCode; - [XmlElement("shift")] - private bool _shift; - [XmlElement("control")] - private bool _control; - [XmlElement("alt")] - private bool _alt; - [XmlElement("windows")] - private bool _windows; - - [XmlIgnore] - private int _id; - [XmlIgnore] - private bool _isRegistered; - [XmlIgnore] - private Control _windowControl; - - public event HandledEventHandler Pressed; - - public Hotkey() - : this(Keys.None, false, false, false, false) - { - } - - public Hotkey(Keys keyCode, bool shift, bool control, bool alt, bool windows) - { - // Assign properties - this.KeyCode = keyCode; - this.Shift = shift; - this.Control = control; - this.Alt = alt; - this.Windows = windows; - - // Register us as a message filter - Application.AddMessageFilter(this); - } - - ~Hotkey() - { - // Unregister the hotkey if necessary - if (this.IsRegistered) - { - this.Unregister(); - } - } - - public Hotkey Clone() - { - // Clone the whole object - return new Hotkey(this._keyCode, this._shift, this._control, this._alt, this._windows); - } - - public bool GetCanRegister(Control windowControl) - { - // Handle any exceptions: they mean "no, you can't register" :) - try - { - // Attempt to register - if (this.Register(windowControl)) - { - // Unregister and say we managed it - this.Unregister(); - return true; - } - } - catch (Win32Exception) - { - } - catch (NotSupportedException) - { - } - - return false; - } - - public bool Register(Control windowControl) - { - // Check that we have not registered - if (this._isRegistered) - { - throw new NotSupportedException("You cannot register a hotkey that is already registered"); - } - - // We can't register an empty hotkey - if (this.IsEmpty) - { - throw new NotSupportedException("You cannot register an empty hotkey"); - } - - // Get an ID for the hotkey and increase current ID - this._id = Hotkey._currentId; - Hotkey._currentId = Hotkey._currentId + 1 % Hotkey.MaxId; - - // Translate modifier keys into unmanaged version - uint modifiers = (this.Alt ? HotkeyNativeMethods.MOD_ALT : 0) | (this.Control ? HotkeyNativeMethods.MOD_CONTROL : 0) | - (this.Shift ? HotkeyNativeMethods.MOD_SHIFT : 0) | (this.Windows ? HotkeyNativeMethods.MOD_WIN : 0); - - // Register the hotkey - if (HotkeyNativeMethods.RegisterHotKey(windowControl.Handle, this._id, modifiers, _keyCode) == 0) - { - // Is the error that the hotkey is registered? - if (Marshal.GetLastWin32Error() != HotkeyNativeMethods.ERROR_HOTKEY_ALREADY_REGISTERED) - { - throw new Win32Exception(); - } - - return false; - } - - // Save the control reference and register state - this._isRegistered = true; - this._windowControl = windowControl; - - // We successfully registered - return true; - } - - public void Unregister() - { - // Check that we have registered - if (!this._isRegistered) - { - throw new NotSupportedException("You cannot unregister a hotkey that is not registered"); - } - - // It's possible that the control itself has died: in that case, no need to unregister! - if (!this._windowControl.IsDisposed) - { - // Clean up after ourselves - if (HotkeyNativeMethods.UnregisterHotKey(this._windowControl.Handle, this._id) == 0) - { - throw new Win32Exception(); - } - } - - // Clear the control reference and register state - this._isRegistered = false; - this._windowControl = null; - } - - private void Reregister() - { - // Only do something if the key is already registered - if (!this._isRegistered) - { - return; - } - - // Save control reference - Control windowControl = this._windowControl; - - // Unregister and then reregister again - this.Unregister(); - this.Register(windowControl); - } - - public bool PreFilterMessage(ref Message message) - { - // Only process WM_HOTKEY messages - if (message.Msg != HotkeyNativeMethods.WM_HOTKEY) - { - return false; - } - - // Check that the ID is our key and we are registerd - return this._isRegistered && (message.WParam.ToInt32() == this._id) && this.OnPressed(); - } - - private bool OnPressed() - { - // Fire the event if we can - HandledEventArgs handledEventArgs = new HandledEventArgs(false); - this.Pressed?.Invoke(this, handledEventArgs); - - // Return whether we handled the event or not - return handledEventArgs.Handled; - } - - public override string ToString() - { - // We can be empty - if (this.IsEmpty) - { - return "(none)"; - } - - // Build key name - string keyName = Enum.GetName(typeof(Keys), this._keyCode) ?? " "; - switch (this._keyCode) - { - case Keys.D0: - case Keys.D1: - case Keys.D2: - case Keys.D3: - case Keys.D4: - case Keys.D5: - case Keys.D6: - case Keys.D7: - case Keys.D8: - case Keys.D9: - // Strip the first character - keyName = keyName.Substring(1); - break; - } - - // Build modifiers - string modifiers = ""; - if (this._shift) - { - modifiers += "Shift+"; - } - - if (this._control) - { - modifiers += "Control+"; - } - - if (this._alt) - { - modifiers += "Alt+"; - } - - if (this._windows) - { - modifiers += "Windows+"; - } - - // Return result - return modifiers + keyName; - } - - public bool IsEmpty - { - get - { - return this._keyCode == Keys.None; - } - } - - public bool IsRegistered - { - get - { - return this._isRegistered; - } - } - - public Keys KeyCode - { - get - { - return this._keyCode; - } - set - { - // Save and reregister - this._keyCode = value; - this.Reregister(); - } - } - - public bool Shift - { - get - { - return this._shift; - } - set - { - // Save and reregister - this._shift = value; - this.Reregister(); - } - } - - public bool Control - { - get - { - return this._control; - } - set - { - // Save and reregister - this._control = value; - this.Reregister(); - } - } - - public bool Alt - { - get - { - return this._alt; - } - set - { - // Save and reregister - this._alt = value; - this.Reregister(); - } - } - - public bool Windows - { - get - { - return this._windows; - } - set - { - // Save and reregister - this._windows = value; - this.Reregister(); - } - } - } -} \ No newline at end of file diff --git a/Eve-O-Preview/Hotkeys/HotkeyHandler.cs b/Eve-O-Preview/Hotkeys/HotkeyHandler.cs new file mode 100644 index 0000000..8e12b54 --- /dev/null +++ b/Eve-O-Preview/Hotkeys/HotkeyHandler.cs @@ -0,0 +1,159 @@ +using System; +using System.Windows.Forms; +using System.ComponentModel; + +namespace EveOPreview.UI +{ + class HotkeyHandler : IMessageFilter, IDisposable + { + private static int _currentId; + private const int MaxId = 0xBFFF; + + #region Private fields + private readonly int _hotkeyId; + private readonly IntPtr _hotkeyTarget; + #endregion + + public HotkeyHandler(IntPtr target, Keys hotkey) + { + this._hotkeyId = HotkeyHandler._currentId; + HotkeyHandler._currentId = (HotkeyHandler._currentId + 1) & HotkeyHandler.MaxId; + + this._hotkeyTarget = target; + + // Assign properties + this.IsRegistered = false; + + this.KeyCode = hotkey; + } + + public void Dispose() + { + if (this.IsRegistered) + { + this.Unregister(); + } + + GC.SuppressFinalize(this); + } + + ~HotkeyHandler() + { + // Unregister the hotkey if necessary + if (this.IsRegistered) + { + try + { + this.Unregister(); + } + catch (Exception) + { + // Please no exceptions in the finalizer thread + } + } + } + + public bool IsRegistered { get; private set; } + + public Keys KeyCode { get; private set; } + + public event HandledEventHandler Pressed; + + public bool CanRegister() + { + // Any exception means "no, you can't register" + try + { + // Attempt to register + if (this.Register()) + { + // Unregister and say we managed it + this.Unregister(); + return true; + } + } + catch (Win32Exception) + { + } + catch (NotSupportedException) + { + } + + return false; + } + + public bool Register() + { + // Check that we have not registered + if (this.IsRegistered) + { + throw new NotSupportedException("This hotkey is already registered"); + } + + if (this.KeyCode == Keys.None) + { + throw new NotSupportedException("Cannot register an empty hotkey"); + } + + // Remove all modifiers from the 'main' hotkey + uint key = (uint)this.KeyCode & (~(uint)Keys.Alt) & (~(uint)Keys.Control) & (~(uint)Keys.Shift); + + // Get unmanaged version of the modifiers code + uint modifiers = (this.KeyCode.HasFlag(Keys.Alt) ? HotkeyHandlerNativeMethods.MOD_ALT : 0) + | (this.KeyCode.HasFlag(Keys.Control) ? HotkeyHandlerNativeMethods.MOD_CONTROL : 0) + | (this.KeyCode.HasFlag(Keys.Shift) ? HotkeyHandlerNativeMethods.MOD_SHIFT : 0); + + // Register the hotkey + if (!HotkeyHandlerNativeMethods.RegisterHotKey(this._hotkeyTarget, this._hotkeyId, modifiers, key)) + { + return false; + } + + Application.AddMessageFilter(this); + + this.IsRegistered = true; + + // We successfully registered + return true; + } + + public void Unregister() + { + // Check that we have registered + if (!this.IsRegistered) + { + throw new NotSupportedException("This hotkey was not registered"); + } + + Application.RemoveMessageFilter(this); + + // Clean up after ourselves + if (!HotkeyHandlerNativeMethods.UnregisterHotKey(this._hotkeyTarget, this._hotkeyId)) + { + throw new Win32Exception(); + } + + this.IsRegistered = false; + } + + #region IMessageFilter + public bool PreFilterMessage(ref Message message) + { + return this.IsRegistered + && (message.Msg == HotkeyHandlerNativeMethods.WM_HOTKEY) + && (message.WParam.ToInt32() == this._hotkeyId) + && this.OnPressed(); + } + #endregion + + private bool OnPressed() + { + // Fire the event if we can + HandledEventArgs handledEventArgs = new HandledEventArgs(false); + this.Pressed?.Invoke(this, handledEventArgs); + + // Return whether we handled the event or not + return handledEventArgs.Handled; + } + } +} \ No newline at end of file diff --git a/Eve-O-Preview/Hotkeys/HotkeyHandlerNativeMethods.cs b/Eve-O-Preview/Hotkeys/HotkeyHandlerNativeMethods.cs new file mode 100644 index 0000000..4d15506 --- /dev/null +++ b/Eve-O-Preview/Hotkeys/HotkeyHandlerNativeMethods.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.InteropServices; + +namespace EveOPreview.UI +{ + static class HotkeyHandlerNativeMethods + { + [DllImport("user32.dll")] + public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); + + [DllImport("user32.dll")] + public static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + public const uint WM_HOTKEY = 0x0312; + + public const uint MOD_ALT = 0x1; + public const uint MOD_CONTROL = 0x2; + public const uint MOD_SHIFT = 0x4; + public const uint MOD_WIN = 0x8; + + public const uint ERROR_HOTKEY_ALREADY_REGISTERED = 1409; + } +} \ No newline at end of file diff --git a/Eve-O-Preview/Hotkeys/HotkeyNativeMethods.cs b/Eve-O-Preview/Hotkeys/HotkeyNativeMethods.cs deleted file mode 100644 index 8823fee..0000000 --- a/Eve-O-Preview/Hotkeys/HotkeyNativeMethods.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Windows.Forms; - -namespace EveOPreview -{ - static class HotkeyNativeMethods - { - [DllImport("user32.dll", SetLastError = true)] - public static extern int RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, Keys vk); - - [DllImport("user32.dll", SetLastError = true)] - public static extern int UnregisterHotKey(IntPtr hWnd, int id); - - public const uint WM_HOTKEY = 0x312; - - public const uint MOD_ALT = 0x1; - public const uint MOD_CONTROL = 0x2; - public const uint MOD_SHIFT = 0x4; - public const uint MOD_WIN = 0x8; - - public const uint ERROR_HOTKEY_ALREADY_REGISTERED = 1409; - } -} \ No newline at end of file diff --git a/Eve-O-Preview/Presentation/ThumbnailManager.cs b/Eve-O-Preview/Presentation/ThumbnailManager.cs index 12009bc..667d931 100644 --- a/Eve-O-Preview/Presentation/ThumbnailManager.cs +++ b/Eve-O-Preview/Presentation/ThumbnailManager.cs @@ -215,6 +215,8 @@ namespace EveOPreview.UI view.ThumbnailLostFocus = this.ThumbnailViewLostFocus; view.ThumbnailActivated = this.ThumbnailActivated; + view.RegisterHotkey(this._configuration.GetClientHotkey(processTitle)); + this._thumbnailViews.Add(processHandle, view); this.ApplyClientLayout(processHandle, processTitle); @@ -224,13 +226,7 @@ namespace EveOPreview.UI else if ((view != null) && (processTitle != view.Title)) // update thumbnail title { view.Title = processTitle; - - // TODO Shortcuts should be handled at manager level - //string value; - //if (_flatLayoutShortcuts.TryGetValue(processTitle, out value)) - //{ - // view.RegisterShortcut(value); - //} + view.RegisterHotkey(this._configuration.GetClientHotkey(processTitle)); this.ApplyClientLayout(processHandle, processTitle); @@ -261,7 +257,8 @@ namespace EveOPreview.UI this._thumbnailViews.Remove(processHandle); - // TODO Remove hotkey here + view.UnregisterHotkey(); + view.ThumbnailResized = null; view.ThumbnailMoved = null; view.ThumbnailFocused = null; diff --git a/Eve-O-Preview/UI/Implementation/ThumbnailView.cs b/Eve-O-Preview/UI/Implementation/ThumbnailView.cs index d643c8c..f40048a 100644 --- a/Eve-O-Preview/UI/Implementation/ThumbnailView.cs +++ b/Eve-O-Preview/UI/Implementation/ThumbnailView.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Drawing; using System.Windows.Forms; @@ -24,6 +25,7 @@ namespace EveOPreview.UI private IntPtr _thumbnailHandle; private Size _baseSize; private Point _baseLocation; + private HotkeyHandler _hotkeyHandler; #endregion public ThumbnailView() @@ -207,6 +209,47 @@ namespace EveOPreview.UI this.Location = this._baseLocation; } + public void RegisterHotkey(Keys hotkey) + { + if (this._hotkeyHandler != null) + { + this.UnregisterHotkey(); + } + + if (hotkey == Keys.None) + { + return; + } + + this._hotkeyHandler = new HotkeyHandler(this.Handle, hotkey); + this._hotkeyHandler.Pressed += HotkeyPressed_Handler; + try + { + this._hotkeyHandler.Register(); + System.Diagnostics.Debug.WriteLine("Registered shortcut for " + this.Title); + } + catch (Exception) + { + System.Diagnostics.Debug.WriteLine("Failed to register shortcut for " + this.Title); + // There can be a lot of possible exception reasons here + // In case of any of them the hotkey setting is silently ignored + } + + } + + public void UnregisterHotkey() + { + if (this._hotkeyHandler == null) + { + return; + } + + this._hotkeyHandler.Unregister(); + this._hotkeyHandler.Pressed -= HotkeyPressed_Handler; + this._hotkeyHandler.Dispose(); + this._hotkeyHandler = null; + } + public void Refresh(bool forceRefresh) { // To prevent flickering the old broken thumbnail is removed AFTER the new shiny one is created @@ -327,6 +370,13 @@ namespace EveOPreview.UI //this.Refresh(); } } + + private void HotkeyPressed_Handler(object sender, HandledEventArgs e) + { + this.ThumbnailActivated?.Invoke(this.Id); + + e.Handled = true; + } #endregion private void RegisterThumbnail() diff --git a/Eve-O-Preview/UI/Interface/IThumbnailView.cs b/Eve-O-Preview/UI/Interface/IThumbnailView.cs index 35ae31c..b371fc7 100644 --- a/Eve-O-Preview/UI/Interface/IThumbnailView.cs +++ b/Eve-O-Preview/UI/Interface/IThumbnailView.cs @@ -1,5 +1,6 @@ using System; using System.Drawing; +using System.Windows.Forms; namespace EveOPreview.UI { @@ -24,6 +25,9 @@ namespace EveOPreview.UI void ZoomIn(ViewZoomAnchor anchor, int zoomFactor); void ZoomOut(); + void RegisterHotkey(Keys hotkey); + void UnregisterHotkey(); + void Refresh(bool forceRefresh); Action ThumbnailResized { get; set; }