2020-07-07 07:15:45 +12:00
|
|
|
|
// <copyright file="KeyboardInput.cs" company="PlaceholderCompany">
|
|
|
|
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
|
|
|
|
// </copyright>
|
2020-06-21 03:38:21 +12:00
|
|
|
|
|
2023-06-04 06:18:10 +12:00
|
|
|
|
namespace SystemTrayMenu.Business
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2020-07-07 07:15:45 +12:00
|
|
|
|
using System;
|
2022-10-23 11:02:31 +13:00
|
|
|
|
using System.Windows.Input;
|
2020-07-07 07:15:45 +12:00
|
|
|
|
using SystemTrayMenu.DataClasses;
|
2023-06-03 10:49:10 +12:00
|
|
|
|
using SystemTrayMenu.DllImports;
|
2023-05-19 09:28:52 +12:00
|
|
|
|
using SystemTrayMenu.UserInterface;
|
2020-07-07 07:15:45 +12:00
|
|
|
|
using SystemTrayMenu.Utilities;
|
2023-05-30 09:56:52 +12:00
|
|
|
|
using static SystemTrayMenu.Helpers.GlobalHotkeys;
|
2020-07-07 07:15:45 +12:00
|
|
|
|
|
2020-06-21 03:38:21 +12:00
|
|
|
|
internal class KeyboardInput : IDisposable
|
|
|
|
|
{
|
2023-05-30 09:56:52 +12:00
|
|
|
|
private readonly IHotkeyFunction hotkeyFunction = Create();
|
2020-06-21 03:38:21 +12:00
|
|
|
|
|
2023-05-04 09:18:03 +12:00
|
|
|
|
private Menu? focussedMenu;
|
2020-07-07 09:37:55 +12:00
|
|
|
|
|
2023-05-30 09:56:52 +12:00
|
|
|
|
public KeyboardInput()
|
|
|
|
|
{
|
|
|
|
|
hotkeyFunction.KeyPressed += (_) => HotKeyPressed?.Invoke();
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-04 10:41:03 +13:00
|
|
|
|
internal event Action? HotKeyPressed;
|
2020-07-07 09:37:55 +12:00
|
|
|
|
|
2023-05-19 09:28:52 +12:00
|
|
|
|
internal event Action<RowData?>? RowSelectionChanged;
|
2020-07-07 09:37:55 +12:00
|
|
|
|
|
2023-05-19 09:28:52 +12:00
|
|
|
|
internal event Action<RowData>? EnterPressed;
|
2020-07-07 09:37:55 +12:00
|
|
|
|
|
2023-05-10 10:55:18 +12:00
|
|
|
|
internal bool IsSelectedByKey { get; set; }
|
2020-07-08 03:05:19 +12:00
|
|
|
|
|
2020-06-21 03:38:21 +12:00
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
2023-05-30 09:56:52 +12:00
|
|
|
|
hotkeyFunction.Unregister();
|
2020-05-31 05:59:33 +12:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-23 09:57:54 +12:00
|
|
|
|
internal bool RegisterHotKey(string hotKeyString)
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2023-05-23 09:57:54 +12:00
|
|
|
|
if (!string.IsNullOrEmpty(hotKeyString))
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2023-05-30 09:56:52 +12:00
|
|
|
|
hotkeyFunction.Register(hotKeyString);
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
|
|
|
|
catch (InvalidOperationException ex)
|
|
|
|
|
{
|
2023-05-23 09:57:54 +12:00
|
|
|
|
Log.Warn($"Hotkey cannot be set: '{hotKeyString}'", ex);
|
2023-05-08 10:14:23 +12:00
|
|
|
|
return false;
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-08 10:14:23 +12:00
|
|
|
|
|
|
|
|
|
return true;
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 11:13:02 +12:00
|
|
|
|
internal void ResetSelectedByKey() => focussedMenu = null;
|
2020-06-21 03:38:21 +12:00
|
|
|
|
|
2023-05-03 08:04:32 +12:00
|
|
|
|
internal void CmdKeyProcessed(Menu sender, Key key, ModifierKeys modifiers)
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2022-11-29 08:27:52 +13:00
|
|
|
|
switch (key)
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2022-10-23 11:02:31 +13:00
|
|
|
|
case Key.Left:
|
|
|
|
|
case Key.Right:
|
|
|
|
|
case Key.Home:
|
|
|
|
|
case Key.End:
|
|
|
|
|
case Key.Up:
|
|
|
|
|
case Key.Down:
|
2023-05-15 05:13:44 +12:00
|
|
|
|
case Key.Enter:
|
2022-11-29 08:27:52 +13:00
|
|
|
|
if (modifiers == ModifierKeys.None)
|
|
|
|
|
{
|
2023-06-04 21:34:29 +12:00
|
|
|
|
if (focussedMenu == null)
|
|
|
|
|
{
|
|
|
|
|
// Start selection based on the key's origin (sender)
|
|
|
|
|
// Usually only needed if no mouse selection was triggered ever before
|
|
|
|
|
if (sender.TrySelectAt(0, -1))
|
|
|
|
|
{
|
|
|
|
|
focussedMenu = sender;
|
|
|
|
|
|
|
|
|
|
// Home and Down would select first item but it was just selected
|
|
|
|
|
if (key != Key.Home && key != Key.Down)
|
|
|
|
|
{
|
|
|
|
|
SelectByKey(key, focussedMenu);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2023-05-15 05:13:44 +12:00
|
|
|
|
{
|
|
|
|
|
SelectByKey(key, focussedMenu);
|
|
|
|
|
}
|
2022-11-29 08:27:52 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case Key.F:
|
|
|
|
|
if (modifiers == ModifierKeys.Control)
|
|
|
|
|
{
|
2023-06-04 21:34:29 +12:00
|
|
|
|
focussedMenu?.FocusTextBox(); // TODO: Keep it? As of now the text box has focus all the time.
|
2022-11-29 08:27:52 +13:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-31 05:59:33 +12:00
|
|
|
|
break;
|
2022-10-23 11:02:31 +13:00
|
|
|
|
case Key.Tab:
|
2022-11-29 08:27:52 +13:00
|
|
|
|
if (modifiers == ModifierKeys.None)
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2023-05-08 10:14:23 +12:00
|
|
|
|
// Walk to previous text box and warp around when main menu reached
|
|
|
|
|
Menu? menu = sender.ParentMenu;
|
|
|
|
|
if (menu == null)
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2023-05-08 10:14:23 +12:00
|
|
|
|
menu = sender;
|
|
|
|
|
while (menu.SubMenu != null)
|
|
|
|
|
{
|
|
|
|
|
menu = menu.SubMenu;
|
|
|
|
|
}
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-08 10:14:23 +12:00
|
|
|
|
menu.FocusTextBox();
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
2022-11-29 08:27:52 +13:00
|
|
|
|
else if (modifiers == ModifierKeys.Shift)
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2023-05-08 10:14:23 +12:00
|
|
|
|
// Walk to next text box and warp around back to main menu on last sub menu
|
|
|
|
|
Menu? menu = sender.SubMenu ?? sender.MainMenu;
|
|
|
|
|
menu.FocusTextBox();
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
2020-07-07 07:15:45 +12:00
|
|
|
|
|
2020-06-21 03:38:21 +12:00
|
|
|
|
break;
|
2022-10-23 11:02:31 +13:00
|
|
|
|
case Key.Apps:
|
2022-11-29 08:27:52 +13:00
|
|
|
|
if (modifiers == ModifierKeys.None)
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2023-06-03 10:49:10 +12:00
|
|
|
|
focussedMenu?.SelectedItem?.OpenShellContextMenu(NativeMethods.Screen.CursorPosition);
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
2020-07-07 07:15:45 +12:00
|
|
|
|
|
2023-05-15 05:13:44 +12:00
|
|
|
|
break;
|
|
|
|
|
case Key.Escape:
|
|
|
|
|
if (modifiers == ModifierKeys.None)
|
|
|
|
|
{
|
|
|
|
|
focussedMenu = null;
|
2023-05-19 06:42:46 +12:00
|
|
|
|
RowSelectionChanged?.Invoke(null); // TODO: Refactor to just a trigger for WaitToLoadMenu ?
|
2023-05-17 05:32:48 +12:00
|
|
|
|
sender.HideAllMenus();
|
2023-05-15 05:13:44 +12:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-21 03:38:21 +12:00
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-19 09:28:52 +12:00
|
|
|
|
internal void SelectByMouse(RowData itemData)
|
2020-07-08 03:05:19 +12:00
|
|
|
|
{
|
2023-05-10 10:55:18 +12:00
|
|
|
|
IsSelectedByKey = false;
|
2021-11-11 11:39:52 +13:00
|
|
|
|
|
2023-05-19 09:28:52 +12:00
|
|
|
|
focussedMenu = itemData.Owner!; // function is only called for itemData that have an Owner set
|
2023-05-19 06:42:46 +12:00
|
|
|
|
focussedMenu.GetDataGridView().SelectedItem = itemData;
|
2020-07-08 03:05:19 +12:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-15 05:13:44 +12:00
|
|
|
|
private void SelectByKey(Key key, Menu menuBefore)
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2023-05-19 09:28:52 +12:00
|
|
|
|
RowData? rowBefore = menuBefore.SelectedItem;
|
2023-05-15 05:13:44 +12:00
|
|
|
|
if (rowBefore == null)
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2023-05-12 11:13:02 +12:00
|
|
|
|
focussedMenu = null;
|
2023-05-15 05:13:44 +12:00
|
|
|
|
return;
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-29 08:27:52 +13:00
|
|
|
|
switch (key)
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2022-10-23 11:02:31 +13:00
|
|
|
|
case Key.Enter:
|
2023-05-15 05:13:44 +12:00
|
|
|
|
// When not sub menu already open, open the sub menu,
|
|
|
|
|
// but when already opened, open the actual folder instead.
|
|
|
|
|
// In case it is a single file, open it right away
|
2023-05-19 09:28:52 +12:00
|
|
|
|
if (rowBefore.SubMenu != null || !rowBefore.IsPointingToFolder)
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2023-05-19 06:42:46 +12:00
|
|
|
|
rowBefore.OpenItem(0);
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
2023-05-15 05:13:44 +12:00
|
|
|
|
else
|
|
|
|
|
{
|
2023-05-19 06:42:46 +12:00
|
|
|
|
RowSelectionChanged?.Invoke(rowBefore); // TODO: Refactor to just a trigger for WaitToLoadMenu ?
|
2023-05-15 05:13:44 +12:00
|
|
|
|
IsSelectedByKey = true;
|
2023-05-19 06:42:46 +12:00
|
|
|
|
EnterPressed?.Invoke(rowBefore);
|
2023-05-15 05:13:44 +12:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
menuBefore?.FocusTextBox(); // TODO: focus placed correctly here?
|
2020-07-07 07:15:45 +12:00
|
|
|
|
|
2020-06-21 03:38:21 +12:00
|
|
|
|
break;
|
2022-10-23 11:02:31 +13:00
|
|
|
|
case Key.Up:
|
2023-05-15 05:13:44 +12:00
|
|
|
|
if (menuBefore.TrySelectAt(menuBefore.GetDataGridView().Items.IndexOf(menuBefore.SelectedItem) - 1, menuBefore.GetDataGridView().Items.Count - 1))
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2023-05-15 05:13:44 +12:00
|
|
|
|
IsSelectedByKey = true;
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
2020-07-07 07:15:45 +12:00
|
|
|
|
|
2020-06-21 03:38:21 +12:00
|
|
|
|
break;
|
2022-10-23 11:02:31 +13:00
|
|
|
|
case Key.Down:
|
2023-05-15 05:13:44 +12:00
|
|
|
|
if (menuBefore.TrySelectAt(menuBefore.GetDataGridView().Items.IndexOf(menuBefore.SelectedItem) + 1, 0))
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2023-05-15 05:13:44 +12:00
|
|
|
|
IsSelectedByKey = true;
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
2020-07-07 07:15:45 +12:00
|
|
|
|
|
2022-02-05 02:37:37 +13:00
|
|
|
|
break;
|
2022-10-23 11:02:31 +13:00
|
|
|
|
case Key.Home:
|
2023-05-15 05:13:44 +12:00
|
|
|
|
if (menuBefore.TrySelectAt(0, -1))
|
2022-02-05 02:37:37 +13:00
|
|
|
|
{
|
2023-05-15 05:13:44 +12:00
|
|
|
|
IsSelectedByKey = true;
|
2022-02-05 02:37:37 +13:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
2022-10-23 11:02:31 +13:00
|
|
|
|
case Key.End:
|
2023-05-15 05:13:44 +12:00
|
|
|
|
if (menuBefore.TrySelectAt(menuBefore.GetDataGridView().Items.Count - 1, -1))
|
2022-02-05 02:37:37 +13:00
|
|
|
|
{
|
2023-05-15 05:13:44 +12:00
|
|
|
|
IsSelectedByKey = true;
|
2022-02-05 02:37:37 +13:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-21 03:38:21 +12:00
|
|
|
|
break;
|
2022-10-23 11:02:31 +13:00
|
|
|
|
case Key.Left:
|
2023-05-05 10:08:39 +12:00
|
|
|
|
case Key.Right:
|
2023-05-15 05:13:44 +12:00
|
|
|
|
Menu? next = menuBefore.SubMenu;
|
|
|
|
|
Menu? prev = menuBefore.ParentMenu;
|
|
|
|
|
bool nextLeft = next != null && next.Location.X < menuBefore.Location.X;
|
|
|
|
|
bool prevLeft = prev != null && prev.Location.X < menuBefore.Location.X;
|
|
|
|
|
|
|
|
|
|
// P = Parent, N = Next ..
|
|
|
|
|
// Menues come from right in examples
|
|
|
|
|
//
|
|
|
|
|
// Key Pressed: <-
|
|
|
|
|
// [N][ ][P] - Select next as in key direction
|
|
|
|
|
// [ ][P/N] - Select prev going left as going right would select next
|
|
|
|
|
// [ ][P] - don't do anything
|
|
|
|
|
// [N][ ] - Select next as in key direction
|
|
|
|
|
// [P/N][ ] - Select next as next has priority
|
|
|
|
|
//
|
|
|
|
|
// Key Pressed: ->
|
|
|
|
|
// [N][ ][P] - Select prev as in key direction
|
|
|
|
|
// [ ][P/N] - Select next as next has priority
|
|
|
|
|
// [ ][P] - Select prev as in key direction
|
|
|
|
|
// [N][ ] - don't do anything
|
|
|
|
|
// [P/N][ ] - Select prev going right as going left would select next
|
|
|
|
|
if (next != null && nextLeft == (key == Key.Left))
|
2020-06-21 03:38:21 +12:00
|
|
|
|
{
|
2023-05-15 05:13:44 +12:00
|
|
|
|
// Select next sub menu, when next exists and
|
|
|
|
|
// next and key point in the same direction
|
|
|
|
|
if (next!.TrySelectAt(0, -1))
|
2022-11-29 08:27:52 +13:00
|
|
|
|
{
|
2023-05-15 05:13:44 +12:00
|
|
|
|
focussedMenu = next;
|
|
|
|
|
IsSelectedByKey = true;
|
2022-11-29 08:27:52 +13:00
|
|
|
|
}
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
2023-05-15 05:13:44 +12:00
|
|
|
|
else if (prev != null &&
|
|
|
|
|
((prevLeft == (key == Key.Left)) ||
|
|
|
|
|
(next != null && nextLeft == prevLeft)))
|
2022-11-29 08:27:52 +13:00
|
|
|
|
{
|
2023-05-15 05:13:44 +12:00
|
|
|
|
// Select previous/parent menu, when prev exists and
|
|
|
|
|
// either prev and key point in the same direction
|
|
|
|
|
// or when next exists while overlapping with prev
|
|
|
|
|
int index = menuBefore.RowDataParent?.RowIndex ?? -1;
|
|
|
|
|
if (prev.TrySelectAt(index, 0))
|
|
|
|
|
{
|
|
|
|
|
focussedMenu = prev;
|
|
|
|
|
IsSelectedByKey = true;
|
|
|
|
|
}
|
2022-11-29 08:27:52 +13:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-21 03:38:21 +12:00
|
|
|
|
break;
|
|
|
|
|
default:
|
2022-11-29 08:27:52 +13:00
|
|
|
|
break;
|
|
|
|
|
}
|
2020-06-21 03:38:21 +12:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-03 10:34:30 +12:00
|
|
|
|
}
|