Replaced internal "menus" array with chained instances.

This commit is contained in:
Peter Kirmeier 2023-05-08 00:14:23 +02:00
parent 56476e095d
commit 14d0896419
7 changed files with 288 additions and 267 deletions

View file

@ -16,17 +16,11 @@ namespace SystemTrayMenu.Handler
internal class KeyboardInput : IDisposable internal class KeyboardInput : IDisposable
{ {
private readonly Menu?[] menus;
private readonly KeyboardHook hook = new(); private readonly KeyboardHook hook = new();
private Menu? focussedMenu; private Menu? focussedMenu;
private ListViewItemData? focussedRow; private ListViewItemData? focussedRow;
public KeyboardInput(Menu?[] menus)
{
this.menus = menus;
}
internal event Action? HotKeyPressed; internal event Action? HotKeyPressed;
internal event Action? ClosePressed; internal event Action? ClosePressed;
@ -44,9 +38,9 @@ namespace SystemTrayMenu.Handler
hook.Dispose(); hook.Dispose();
} }
internal void RegisterHotKey() internal bool RegisterHotKey(string hotKey)
{ {
if (!string.IsNullOrEmpty(Properties.Settings.Default.HotKey)) if (!string.IsNullOrEmpty(hotKey))
{ {
try try
{ {
@ -55,11 +49,12 @@ namespace SystemTrayMenu.Handler
} }
catch (InvalidOperationException ex) catch (InvalidOperationException ex)
{ {
Log.Warn($"key:'{Properties.Settings.Default.HotKey}'", ex); Log.Warn($"Hotkey cannot be set: '{hotKey}'", ex);
Properties.Settings.Default.HotKey = string.Empty; return false;
Properties.Settings.Default.Save();
} }
} }
return true;
} }
internal void ResetSelectedByKey() internal void ResetSelectedByKey()
@ -110,35 +105,24 @@ namespace SystemTrayMenu.Handler
case Key.Tab: case Key.Tab:
if (modifiers == ModifierKeys.None) if (modifiers == ModifierKeys.None)
{ {
int indexOfTheCurrentMenu = GetMenuIndex(sender); // Walk to previous text box and warp around when main menu reached
int indexMax = menus.Where(m => m != null).Count() - 1; Menu? menu = sender.ParentMenu;
int indexNew = 0; if (menu == null)
if (indexOfTheCurrentMenu > 0)
{ {
indexNew = indexOfTheCurrentMenu - 1; menu = sender;
} while (menu.SubMenu != null)
else {
{ menu = menu.SubMenu;
indexNew = indexMax; }
} }
menus[indexNew]?.FocusTextBox(); menu.FocusTextBox();
} }
else if (modifiers == ModifierKeys.Shift) else if (modifiers == ModifierKeys.Shift)
{ {
int indexOfTheCurrentMenu = GetMenuIndex(sender); // Walk to next text box and warp around back to main menu on last sub menu
int indexMax = menus.Where(m => m != null).Count() - 1; Menu? menu = sender.SubMenu ?? sender.MainMenu;
int indexNew = 0; menu.FocusTextBox();
if (indexOfTheCurrentMenu < indexMax)
{
indexNew = indexOfTheCurrentMenu + 1;
}
else
{
indexNew = 0;
}
menus[indexNew]?.FocusTextBox();
} }
break; break;
@ -164,22 +148,6 @@ namespace SystemTrayMenu.Handler
default: default:
break; break;
} }
int GetMenuIndex(in Menu? currentMenu)
{
int index = 0;
foreach (Menu? menuFindIndex in menus.Where(m => m != null))
{
if (currentMenu == menuFindIndex)
{
break;
}
index++;
}
return index;
}
} }
internal void SearchTextChanged(Menu menu, bool isSearchStringEmpty) internal void SearchTextChanged(Menu menu, bool isSearchStringEmpty)
@ -343,7 +311,6 @@ namespace SystemTrayMenu.Handler
} }
else else
{ {
focussedMenu = menus[0];
while (focussedMenu?.SubMenu != null) while (focussedMenu?.SubMenu != null)
{ {
focussedMenu = focussedMenu.SubMenu; focussedMenu = focussedMenu.SubMenu;

View file

@ -8,6 +8,7 @@ namespace SystemTrayMenu.Business
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Data; using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
@ -20,6 +21,7 @@ namespace SystemTrayMenu.Business
using SystemTrayMenu.Handler; using SystemTrayMenu.Handler;
using SystemTrayMenu.Helpers; using SystemTrayMenu.Helpers;
using SystemTrayMenu.Properties; using SystemTrayMenu.Properties;
using SystemTrayMenu.UserInterface;
using SystemTrayMenu.Utilities; using SystemTrayMenu.Utilities;
using static SystemTrayMenu.UserInterface.Menu; using static SystemTrayMenu.UserInterface.Menu;
using Menu = SystemTrayMenu.UserInterface.Menu; using Menu = SystemTrayMenu.UserInterface.Menu;
@ -27,7 +29,6 @@ namespace SystemTrayMenu.Business
internal class Menus : IDisposable internal class Menus : IDisposable
{ {
private readonly Dispatcher dispatchter = Dispatcher.CurrentDispatcher; private readonly Dispatcher dispatchter = Dispatcher.CurrentDispatcher;
private readonly Menu?[] menus = new Menu?[MenuDefines.MenusMax];
private readonly BackgroundWorker workerMainMenu = new(); private readonly BackgroundWorker workerMainMenu = new();
private readonly List<BackgroundWorker> workersSubMenu = new(); private readonly List<BackgroundWorker> workersSubMenu = new();
private readonly WaitToLoadMenu waitToOpenMenu = new(); private readonly WaitToLoadMenu waitToOpenMenu = new();
@ -43,11 +44,17 @@ namespace SystemTrayMenu.Business
private TaskbarPosition taskbarPosition = new WindowsTaskbar().Position; private TaskbarPosition taskbarPosition = new WindowsTaskbar().Position;
private bool searchTextChanging; private bool searchTextChanging;
private bool showMenuAfterMainPreload; private bool showMenuAfterMainPreload;
private Menu? mainMenu;
public Menus() public Menus()
{ {
keyboardInput = new(menus); keyboardInput = new();
keyboardInput.RegisterHotKey(); if (!keyboardInput.RegisterHotKey(Settings.Default.HotKey))
{
Settings.Default.HotKey = string.Empty;
Settings.Default.Save();
}
keyboardInput.HotKeyPressed += () => SwitchOpenClose(false, false); keyboardInput.HotKeyPressed += () => SwitchOpenClose(false, false);
keyboardInput.ClosePressed += MenusFadeOut; keyboardInput.ClosePressed += MenusFadeOut;
keyboardInput.RowDeselected += waitToOpenMenu.RowDeselected; keyboardInput.RowDeselected += waitToOpenMenu.RowDeselected;
@ -73,9 +80,25 @@ namespace SystemTrayMenu.Business
waitToOpenMenu.StartLoadMenu += StartLoadMenu; waitToOpenMenu.StartLoadMenu += StartLoadMenu;
void StartLoadMenu(RowData rowData) void StartLoadMenu(RowData rowData)
{ {
if (IsMainUsable && if (!IsMainUsable)
(menus[rowData.Level + 1] == null || {
menus[rowData.Level + 1]?.RowDataParent != rowData)) return;
}
Menu? menu = mainMenu?.SubMenu;
int nextLevel = rowData.Level + 1;
while (menu != null)
{
if (menu.Level == nextLevel)
{
break;
}
menu = menu.SubMenu;
}
// sanity check not creating same sub menu twice
if (menu?.RowDataParent != rowData)
{ {
Create(new(rowData), rowData.Path); // Level 1+ Sub Menu (loading) Create(new(rowData), rowData.Path); // Level 1+ Sub Menu (loading)
@ -96,19 +119,8 @@ namespace SystemTrayMenu.Business
} }
} }
waitToOpenMenu.MouseEnterOk += (menu, itemData) => MouseEnterOk(menu, itemData); waitToOpenMenu.MouseEnterOk += MouseEnterOk;
waitToOpenMenu.CloseMenu += CloseMenu; waitToOpenMenu.CloseMenu += (menu) => HideOldMenu(menu);
void CloseMenu(int level)
{
if (level < menus.Length)
{
Menu? menu = menus[level];
if (menu != null)
{
HideOldMenu(menu);
}
}
}
if (Settings.Default.SupportGamepad) if (Settings.Default.SupportGamepad)
{ {
@ -117,7 +129,7 @@ namespace SystemTrayMenu.Business
{ {
if (IsMainUsable) if (IsMainUsable)
{ {
Menu? menu = AsEnumerable.FirstOrDefault(m => m != null && (m.IsActive || m.IsKeyboardFocusWithin), MainMenu); Menu? menu = GetActiveMenu(mainMenu) ?? mainMenu;
menu?.Dispatcher.Invoke(keyboardInput.CmdKeyProcessed, new object[] { menu, key, modifiers }); menu?.Dispatcher.Invoke(keyboardInput.CmdKeyProcessed, new object[] { menu, key, modifiers });
} }
}; };
@ -129,7 +141,7 @@ namespace SystemTrayMenu.Business
void StillActiveTick() void StillActiveTick()
{ {
timerStillActiveCheck.Stop(); timerStillActiveCheck.Stop();
if (!IsActive()) if (!IsActiveApp())
{ {
FadeHalfOrOutIfNeeded(); FadeHalfOrOutIfNeeded();
} }
@ -189,13 +201,8 @@ namespace SystemTrayMenu.Business
Closing, Closing,
} }
private Menu? MainMenu => menus[0]; [MemberNotNullWhen(true, nameof(mainMenu))]
private bool IsMainUsable => mainMenu?.IsUsable ?? false;
private bool IsMainUsable => MainMenu?.IsUsable ?? false;
private IEnumerable<Menu> AsEnumerable => menus.Where(m => m != null && !m.IsClosed)!;
private List<Menu> AsList => AsEnumerable.ToList();
public void Dispose() public void Dispose()
{ {
@ -206,14 +213,6 @@ namespace SystemTrayMenu.Business
worker.Dispose(); worker.Dispose();
} }
waitToOpenMenu.Dispose();
keyboardInput.Dispose();
joystickHelper?.Dispose();
timerShowProcessStartedAsLoadingIcon.Stop();
timerStillActiveCheck.Stop();
waitLeave.Stop();
MainMenu?.Close();
foreach (FileSystemWatcher watcher in watchers) foreach (FileSystemWatcher watcher in watchers)
{ {
watcher.Created -= WatcherProcessItem; watcher.Created -= WatcherProcessItem;
@ -222,6 +221,14 @@ namespace SystemTrayMenu.Business
watcher.Changed -= WatcherProcessItem; watcher.Changed -= WatcherProcessItem;
watcher.Dispose(); watcher.Dispose();
} }
waitToOpenMenu.Dispose();
keyboardInput.Dispose();
joystickHelper?.Dispose();
timerShowProcessStartedAsLoadingIcon.Stop();
timerStillActiveCheck.Stop();
waitLeave.Stop();
mainMenu?.Close();
} }
internal static void OpenFolder(string? path = null) internal static void OpenFolder(string? path = null)
@ -271,12 +278,12 @@ namespace SystemTrayMenu.Business
else else
{ {
if (openCloseState == OpenCloseState.Opening || if (openCloseState == OpenCloseState.Opening ||
((MainMenu?.Visibility ?? Visibility.Collapsed) == Visibility.Visible && openCloseState == OpenCloseState.Default)) ((mainMenu?.Visibility ?? Visibility.Collapsed) == Visibility.Visible && openCloseState == OpenCloseState.Default))
{ {
openCloseState = OpenCloseState.Closing; openCloseState = OpenCloseState.Closing;
MenusFadeOut(); MenusFadeOut();
StopWorker(); StopWorker();
if (!AsEnumerable.Any(m => m.Visibility == Visibility.Visible)) if (IsVisibleAnyMenu(mainMenu) == null)
{ {
openCloseState = OpenCloseState.Default; openCloseState = OpenCloseState.Default;
} }
@ -313,6 +320,51 @@ namespace SystemTrayMenu.Business
} }
} }
private static Menu? IsVisibleAnyMenu(Menu? menu)
{
while (menu != null)
{
if (menu.Visibility == Visibility.Visible)
{
break;
}
menu = menu.SubMenu;
}
return menu;
}
private static Menu? IsMouseOverAnyMenu(Menu? menu)
{
while (menu != null)
{
if (menu.IsMouseOver())
{
break;
}
menu = menu.SubMenu;
}
return menu;
}
private static Menu? GetActiveMenu(Menu? menu)
{
while (menu != null)
{
if (menu.IsActive || menu.IsKeyboardFocusWithin)
{
break;
}
menu = menu.SubMenu;
}
return menu;
}
private static void LoadMenu(object? sender, DoWorkEventArgs eDoWork) private static void LoadMenu(object? sender, DoWorkEventArgs eDoWork)
{ {
BackgroundWorker? workerSelf = sender as BackgroundWorker; BackgroundWorker? workerSelf = sender as BackgroundWorker;
@ -338,7 +390,7 @@ namespace SystemTrayMenu.Business
if (e.Result == null) if (e.Result == null)
{ {
Menu? menu = MainMenu; Menu? menu = mainMenu;
if (menu != null) if (menu != null)
{ {
// The main menu gets loaded again // The main menu gets loaded again
@ -355,9 +407,9 @@ namespace SystemTrayMenu.Business
RefreshSelection(dgvMainMenu); RefreshSelection(dgvMainMenu);
menu.RelocateOnNextShow = true; menu.RelocateOnNextShow = true;
}
AsEnumerable.ToList().ForEach(m => { m.ShowWithFade(); }); menu.ShowWithFade(false, true);
}
} }
else else
{ {
@ -368,17 +420,17 @@ namespace SystemTrayMenu.Business
case MenuDataDirectoryState.Valid: case MenuDataDirectoryState.Valid:
if (IconReader.IsPreloading) if (IconReader.IsPreloading)
{ {
Create(menuData, Config.Path); // Level 0 Main Menu Menu menu = Create(menuData, Config.Path); // Level 0 Main Menu
IconReader.IsPreloading = false; IconReader.IsPreloading = false;
if (showMenuAfterMainPreload) if (showMenuAfterMainPreload)
{ {
MainMenu?.ShowWithFade(); menu.ShowWithFade(false, false);
} }
} }
else else
{ {
AsEnumerable.ToList().ForEach(m => { m.ShowWithFade(); }); mainMenu?.ShowWithFade(false, true);
} }
break; break;
@ -407,44 +459,48 @@ namespace SystemTrayMenu.Business
private void LoadSubMenuCompleted(object? senderCompleted, RunWorkerCompletedEventArgs e) private void LoadSubMenuCompleted(object? senderCompleted, RunWorkerCompletedEventArgs e)
{ {
if (e.Result == null) if (e.Result == null || !IsMainUsable)
{ {
return; return;
} }
MenuData menuData = (MenuData)e.Result; MenuData menuData = (MenuData)e.Result;
Menu? menu = menus[menuData.Level]; Menu? menu = mainMenu.SubMenu;
while (menu != null)
{
if (menu.Level == menuData.Level)
{
break;
}
menu = menu.SubMenu;
}
if (menu == null) if (menu == null)
{ {
return; return;
} }
if (IsMainUsable) if (menuData.DirectoryState != MenuDataDirectoryState.Undefined)
{ {
if (menuData.DirectoryState != MenuDataDirectoryState.Undefined) // Sub Menu (completed)
{ menu.AddItemsToMenu(menuData.RowDatas, menuData.DirectoryState, true);
// Sub Menu (completed) AdjustMenusSizeAndLocation(menu.Level);
menu.AddItemsToMenu(menuData.RowDatas, menuData.DirectoryState, true); }
AdjustMenusSizeAndLocation(menu.Level); else
} {
else // TODO: Main menu should destroy sub menu(s?) when it becomes unusable
{ menu.HideWithFade(false);
menu.HideWithFade();
menus[menu.Level] = null;
ListView? lv = menus[menuData.Level - 1]?.GetDataGridView(); ListView? lv = menu.ParentMenu?.GetDataGridView();
if (lv != null) if (lv != null)
{ {
RefreshSelection(lv); RefreshSelection(lv);
}
} }
} }
} }
private bool IsActive() private bool IsActiveApp() => GetActiveMenu(mainMenu) != null || (App.TaskbarLogo?.IsActive ?? false);
{
return menus.Where(m => m != null && (m.IsActive || m.IsKeyboardFocusWithin)).FirstOrDefault() != null || (App.TaskbarLogo?.IsActive ?? false);
}
private Menu Create(MenuData menuData, string path) private Menu Create(MenuData menuData, string path)
{ {
@ -475,9 +531,9 @@ namespace SystemTrayMenu.Business
searchTextChanging = false; searchTextChanging = false;
// if any open menu close // if any open menu close
if (!causedByWatcherUpdate && menu.Level + 1 < menus.Length) if (!causedByWatcherUpdate)
{ {
Menu? menuToClose = menus[menu.Level + 1]; Menu? menuToClose = menu.SubMenu;
if (menuToClose != null) if (menuToClose != null)
{ {
HideOldMenu(menuToClose); HideOldMenu(menuToClose);
@ -486,11 +542,12 @@ namespace SystemTrayMenu.Business
} }
menu.UserDragsMenu += Menu_UserDragsMenu; menu.UserDragsMenu += Menu_UserDragsMenu;
void Menu_UserDragsMenu() void Menu_UserDragsMenu(Menu mainMenu)
{ {
Menu? menu = menus[1]; Menu? menu = mainMenu.SubMenu;
if (menu != null) if (menu != null)
{ {
// TODO: menus array not updated? Remove any way? (Call HideOldMenu within Menu_MouseDown direcly?)
HideOldMenu(menu); HideOldMenu(menu);
} }
} }
@ -505,7 +562,7 @@ namespace SystemTrayMenu.Business
else if (!Settings.Default.StaysOpenWhenFocusLostAfterEnterPressed) else if (!Settings.Default.StaysOpenWhenFocusLostAfterEnterPressed)
{ {
FadeHalfOrOutIfNeeded(); FadeHalfOrOutIfNeeded();
if (!IsActive()) if (!IsActiveApp())
{ {
deactivatedTime = DateTime.Now; deactivatedTime = DateTime.Now;
} }
@ -516,10 +573,7 @@ namespace SystemTrayMenu.Business
void Activated() void Activated()
{ {
// Bring transparent menus back // Bring transparent menus back
foreach (Menu? menu in menus.Where(m => m != null && m.Opacity != 1D)) mainMenu?.ActivateWithFade(true);
{
menu!.ActivateWithFade();
}
timerStillActiveCheck.Stop(); timerStillActiveCheck.Stop();
timerStillActiveCheck.Start(); timerStillActiveCheck.Start();
@ -538,7 +592,7 @@ namespace SystemTrayMenu.Business
if (menu.Level == 0) if (menu.Level == 0)
{ {
// Main Menu // Main Menu
menus[menu.Level] = menu; mainMenu = menu;
menu.Loaded += (s, e) => ExecuteWatcherHistory(); menu.Loaded += (s, e) => ExecuteWatcherHistory();
} }
else else
@ -546,9 +600,10 @@ namespace SystemTrayMenu.Business
// Sub Menu (loading) // Sub Menu (loading)
if (IsMainUsable) if (IsMainUsable)
{ {
HideOldMenu(menu, true); RefreshSelection(menu.GetDataGridView());
menus[menu.Level] = menu;
menu.ShowWithFade(!IsActive()); // TODO: Re-enable again? HideOldMenu(menu, true);
menu.ShowWithFade(!IsActiveApp(), false);
} }
} }
@ -573,7 +628,7 @@ namespace SystemTrayMenu.Business
menu.Close(); menu.Close();
} }
if (!AsEnumerable.Any(m => m.Visibility == Visibility.Visible)) if (IsVisibleAnyMenu(mainMenu) == null)
{ {
IconReader.ClearCacheWhenLimitReached(); IconReader.ClearCacheWhenLimitReached();
@ -638,7 +693,7 @@ namespace SystemTrayMenu.Business
{ {
if (IsMainUsable) if (IsMainUsable)
{ {
Menu? menu = MainMenu; Menu? menu = mainMenu;
if (menu != null) if (menu != null)
{ {
menu.RelocateOnNextShow = true; menu.RelocateOnNextShow = true;
@ -649,7 +704,7 @@ namespace SystemTrayMenu.Business
private void HideOldMenu(Menu menuToShow, bool keepOrSetIsMenuOpen = false) private void HideOldMenu(Menu menuToShow, bool keepOrSetIsMenuOpen = false)
{ {
Menu? menuPrevious = menus[menuToShow.Level - 1]; Menu? menuPrevious = menuToShow.ParentMenu;
if (menuPrevious != null) if (menuPrevious != null)
{ {
// Clean up menu status IsMenuOpen for previous one // Clean up menu status IsMenuOpen for previous one
@ -669,13 +724,7 @@ namespace SystemTrayMenu.Business
RefreshSelection(dgvPrevious); RefreshSelection(dgvPrevious);
// Hide old menu menuPrevious.SubMenu?.HideWithFade(true);
foreach (Menu? menuToClose in menus.Where(
m => m != null && m.Level > menuPrevious.Level))
{
menuToClose!.HideWithFade();
menus[menuToClose.Level] = null;
}
} }
} }
@ -683,19 +732,18 @@ namespace SystemTrayMenu.Business
{ {
if (IsMainUsable) if (IsMainUsable)
{ {
if (!IsActive()) if (!IsActiveApp())
{ {
if (Settings.Default.StaysOpenWhenFocusLost && if (Settings.Default.StaysOpenWhenFocusLost && IsMouseOverAnyMenu(mainMenu) != null)
AsList.Any(m => m.IsMouseOn()))
{ {
if (!keyboardInput.InUse) if (!keyboardInput.InUse)
{ {
AsList.ForEach(menu => menu.ShowWithFade(true)); mainMenu?.ShowWithFade(true, true);
} }
} }
else if (Config.AlwaysOpenByPin) else if (Config.AlwaysOpenByPin)
{ {
AsList.ForEach(menu => menu.ShowWithFade(true)); mainMenu?.ShowWithFade(true, true);
} }
else else
{ {
@ -708,15 +756,8 @@ namespace SystemTrayMenu.Business
private void MenusFadeOut() private void MenusFadeOut()
{ {
openCloseState = OpenCloseState.Closing; openCloseState = OpenCloseState.Closing;
AsList.ForEach(menu =>
{
if (menu.Level > 0)
{
menus[menu.Level] = null;
}
menu.HideWithFade(); mainMenu?.HideWithFade(true);
});
Config.AlwaysOpenByPin = false; Config.AlwaysOpenByPin = false;
} }
@ -744,9 +785,8 @@ namespace SystemTrayMenu.Business
} }
// Only apply taskbar position change when no menu is currently open // Only apply taskbar position change when no menu is currently open
List<Menu> list = AsList;
WindowsTaskbar taskbar = new(); WindowsTaskbar taskbar = new();
if (list.Count == 1) if (IsMainUsable && mainMenu.SubMenu == null)
{ {
taskbarPosition = taskbar.Position; taskbarPosition = taskbar.Position;
} }
@ -785,14 +825,12 @@ namespace SystemTrayMenu.Business
{ {
GetScreenBounds(out Rect screenBounds, out bool useCustomLocation, out StartLocation startLocation); GetScreenBounds(out Rect screenBounds, out bool useCustomLocation, out StartLocation startLocation);
Menu menu; Menu? menu = mainMenu;
Menu? menuPredecessor = null; Menu? menuPredecessor = null;
List<Menu> list = AsList;
for (int i = 0; i < list.Count; i++)
{
menu = list[i];
if (startLevel <= i) while (menu != null)
{
if (startLevel <= menu.Level)
{ {
menu.AdjustSizeAndLocation(screenBounds, menuPredecessor, startLocation, useCustomLocation); menu.AdjustSizeAndLocation(screenBounds, menuPredecessor, startLocation, useCustomLocation);
} }
@ -805,7 +843,7 @@ namespace SystemTrayMenu.Business
if (!Settings.Default.AppearAtTheBottomLeft && if (!Settings.Default.AppearAtTheBottomLeft &&
!Settings.Default.AppearAtMouseLocation && !Settings.Default.AppearAtMouseLocation &&
!Settings.Default.UseCustomLocation && !Settings.Default.UseCustomLocation &&
i == 0) menu.Level == 0)
{ {
const double overlapTolerance = 4D; const double overlapTolerance = 4D;
@ -819,6 +857,7 @@ namespace SystemTrayMenu.Business
} }
menuPredecessor = menu; menuPredecessor = menu;
menu = menu.SubMenu;
} }
} }
@ -834,18 +873,10 @@ namespace SystemTrayMenu.Business
private void WatcherProcessItem(object sender, EventArgs e) private void WatcherProcessItem(object sender, EventArgs e)
{ {
Menu? menu = MainMenu; Menu? menu = mainMenu;
bool useHistory = false;
if (menu == null)
{
useHistory = true;
}
else
{
menu.Dispatcher.Invoke(() => useHistory = !menu.IsLoaded);
}
if (useHistory) // Store event in history as long as menu is not loaded
if (menu?.Dispatcher.Invoke(() => !menu.IsLoaded) ?? true)
{ {
watcherHistory.Add(e); watcherHistory.Add(e);
return; return;
@ -853,71 +884,67 @@ namespace SystemTrayMenu.Business
if (e is RenamedEventArgs renamedEventArgs) if (e is RenamedEventArgs renamedEventArgs)
{ {
MainMenu?.Dispatcher.Invoke(() => RenameItem(renamedEventArgs)); menu.Dispatcher.Invoke(() => RenameItem(menu, renamedEventArgs));
} }
else if (e is FileSystemEventArgs fileSystemEventArgs) else if (e is FileSystemEventArgs fileSystemEventArgs)
{ {
if (fileSystemEventArgs.ChangeType == WatcherChangeTypes.Deleted) if (fileSystemEventArgs.ChangeType == WatcherChangeTypes.Deleted)
{ {
MainMenu?.Dispatcher.Invoke(() => DeleteItem(fileSystemEventArgs)); menu.Dispatcher.Invoke(() => DeleteItem(menu, fileSystemEventArgs));
} }
else if (fileSystemEventArgs.ChangeType == WatcherChangeTypes.Created) else if (fileSystemEventArgs.ChangeType == WatcherChangeTypes.Created)
{ {
MainMenu?.Dispatcher.Invoke(() => CreateItem(fileSystemEventArgs)); menu.Dispatcher.Invoke(() => CreateItem(menu, fileSystemEventArgs));
} }
} }
} }
private void RenameItem(RenamedEventArgs e) private void RenameItem(Menu menu, RenamedEventArgs e)
{ {
try try
{ {
List<RowData> rowDatas = new(); List<RowData> rowDatas = new();
ListView? dgv = MainMenu?.GetDataGridView(); foreach (ListViewItemData item in menu.GetDataGridView().Items)
if (dgv != null)
{ {
foreach (ListViewItemData item in dgv.Items) RowData rowData = item.data;
if (rowData.Path.StartsWith($"{e.OldFullPath}"))
{ {
RowData rowData = item.data; string path = rowData.Path.Replace(e.OldFullPath, e.FullPath);
if (rowData.Path.StartsWith($"{e.OldFullPath}")) FileAttributes attr = File.GetAttributes(path);
bool isFolder = (attr & FileAttributes.Directory) == FileAttributes.Directory;
if (isFolder)
{ {
string path = rowData.Path.Replace(e.OldFullPath, e.FullPath); string? dirpath = Path.GetDirectoryName(path);
FileAttributes attr = File.GetAttributes(path); if (string.IsNullOrEmpty(dirpath))
bool isFolder = (attr & FileAttributes.Directory) == FileAttributes.Directory;
if (isFolder)
{
string? dirpath = Path.GetDirectoryName(path);
if (string.IsNullOrEmpty(dirpath))
{
continue;
}
path = dirpath;
}
RowData rowDataRenamed = new(isFolder, rowData.IsAdditionalItem, 0, path);
FolderOptions.ReadHiddenAttributes(rowDataRenamed.Path, out bool hasHiddenFlag, out bool isDirectoryToHide);
if (isDirectoryToHide)
{ {
continue; continue;
} }
IconReader.RemoveIconFromCache(rowData.Path); path = dirpath;
rowDataRenamed.HiddenEntry = hasHiddenFlag;
rowDataRenamed.ReadIcon(true);
rowDatas.Add(rowDataRenamed);
} }
else
RowData rowDataRenamed = new(isFolder, rowData.IsAdditionalItem, 0, path);
FolderOptions.ReadHiddenAttributes(rowDataRenamed.Path, out bool hasHiddenFlag, out bool isDirectoryToHide);
if (isDirectoryToHide)
{ {
rowDatas.Add(rowData); continue;
} }
IconReader.RemoveIconFromCache(rowData.Path);
rowDataRenamed.HiddenEntry = hasHiddenFlag;
rowDataRenamed.ReadIcon(true);
rowDatas.Add(rowDataRenamed);
}
else
{
rowDatas.Add(rowData);
} }
} }
rowDatas = DirectoryHelpers.SortItems(rowDatas); rowDatas = DirectoryHelpers.SortItems(rowDatas);
keyboardInput.ClearIsSelectedByKey(); keyboardInput.ClearIsSelectedByKey();
MainMenu?.AddItemsToMenu(rowDatas, null, true); menu.AddItemsToMenu(rowDatas, null, true);
MainMenu?.OnWatcherUpdate(); menu.OnWatcherUpdate();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -925,34 +952,31 @@ namespace SystemTrayMenu.Business
} }
} }
private void DeleteItem(FileSystemEventArgs e) private void DeleteItem(Menu menu, FileSystemEventArgs e)
{ {
try try
{ {
ListView? dgv = MainMenu?.GetDataGridView(); ListView? dgv = menu.GetDataGridView();
if (dgv != null) List<ListViewItemData> rowsToRemove = new();
foreach (ListViewItemData item in dgv.ItemsSource)
{ {
List<ListViewItemData> rowsToRemove = new(); RowData rowData = item.data;
if (rowData.Path == e.FullPath ||
foreach (ListViewItemData item in dgv.ItemsSource) rowData.Path.StartsWith($"{e.FullPath}\\"))
{ {
RowData rowData = item.data; IconReader.RemoveIconFromCache(rowData.Path);
if (rowData.Path == e.FullPath || rowsToRemove.Add(item);
rowData.Path.StartsWith($"{e.FullPath}\\"))
{
IconReader.RemoveIconFromCache(rowData.Path);
rowsToRemove.Add(item);
}
}
foreach (ListViewItemData rowToRemove in rowsToRemove)
{
((IEditableCollectionView)dgv.Items).Remove(rowToRemove);
} }
} }
foreach (ListViewItemData rowToRemove in rowsToRemove)
{
((IEditableCollectionView)dgv.Items).Remove(rowToRemove);
}
keyboardInput.ClearIsSelectedByKey(); keyboardInput.ClearIsSelectedByKey();
MainMenu?.OnWatcherUpdate(); menu.OnWatcherUpdate();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -960,7 +984,7 @@ namespace SystemTrayMenu.Business
} }
} }
private void CreateItem(FileSystemEventArgs e) private void CreateItem(Menu menu, FileSystemEventArgs e)
{ {
try try
{ {
@ -977,24 +1001,17 @@ namespace SystemTrayMenu.Business
rowData.HiddenEntry = hasHiddenFlag; rowData.HiddenEntry = hasHiddenFlag;
rowData.ReadIcon(true); rowData.ReadIcon(true);
List<RowData> rowDatas = new() var items = menu.GetDataGridView().Items;
List<RowData> rowDatas = new(items.Count + 1) { rowData };
foreach (ListViewItemData item in items)
{ {
rowData, rowDatas.Add(item.data);
};
ListView? dgv = MainMenu?.GetDataGridView();
if (dgv != null)
{
foreach (ListViewItemData item in dgv.Items)
{
rowDatas.Add(item.data);
}
} }
rowDatas = DirectoryHelpers.SortItems(rowDatas); rowDatas = DirectoryHelpers.SortItems(rowDatas);
keyboardInput.ClearIsSelectedByKey(); keyboardInput.ClearIsSelectedByKey();
MainMenu?.AddItemsToMenu(rowDatas, null, true); menu.AddItemsToMenu(rowDatas, null, true);
MainMenu?.OnWatcherUpdate(); menu.OnWatcherUpdate();
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -33,13 +33,11 @@ namespace SystemTrayMenu
Scaling.Initialize(); Scaling.Initialize();
FolderOptions.Initialize(); FolderOptions.Initialize();
using (App app = new App()) using App app = new ();
{ app.InitializeComponent();
app.InitializeComponent(); isStartup = false;
isStartup = false; Log.WriteApplicationRuns();
Log.WriteApplicationRuns(); app.Run();
app.Run();
}
} }
} }
catch (Exception ex) catch (Exception ex)

View file

@ -28,7 +28,7 @@ namespace SystemTrayMenu.Handler
internal event Action<RowData>? StartLoadMenu; internal event Action<RowData>? StartLoadMenu;
internal event Action<int>? CloseMenu; internal event Action<Menu>? CloseMenu;
internal event Action? StopLoadMenu; internal event Action? StopLoadMenu;
@ -129,10 +129,13 @@ namespace SystemTrayMenu.Handler
menu?.Activate(); menu?.Activate();
menu?.FocusTextBox(); menu?.FocusTextBox();
CloseMenu?.Invoke(rowData.Level + 1); Menu? menuToClose = menu?.SubMenu;
if (menuToClose != null)
{
CloseMenu?.Invoke(menuToClose);
}
if (rowData.IsPointingToFolder && if (rowData.IsPointingToFolder)
rowData.Level + 1 < MenuDefines.MenusMax)
{ {
StartLoadMenu?.Invoke(rowData); StartLoadMenu?.Invoke(rowData);
} }

View file

@ -110,7 +110,7 @@ namespace SystemTrayMenu.DataClasses
internal Menu? SubMenu { get; set; } internal Menu? SubMenu { get; set; }
internal bool IsMenuOpen { get; set; } internal bool IsMenuOpen { get; set; } // TODO: Implicitly set when SubMenu != null?
internal bool IsClicking { get; set; } internal bool IsClicking { get; set; }

View file

@ -92,6 +92,7 @@ namespace SystemTrayMenu.UserInterface
{ {
// This will be a main menu // This will be a main menu
Level = 0; Level = 0;
MainMenu = this;
// Use Main Menu DPI for all further calculations // Use Main Menu DPI for all further calculations
Scaling.CalculateFactorByDpi(this); Scaling.CalculateFactorByDpi(this);
@ -99,8 +100,16 @@ namespace SystemTrayMenu.UserInterface
else else
{ {
// This will be a sub menu // This will be a sub menu
if (ParentMenu == null)
{
// Should never happen as each parent menu must have a valid entry which's owner is set
throw new ArgumentNullException(new (nameof(ParentMenu)));
}
Level = RowDataParent.Level + 1; Level = RowDataParent.Level + 1;
MainMenu = ParentMenu.MainMenu;
RowDataParent.SubMenu = this; RowDataParent.SubMenu = this;
RowDataParent.IsMenuOpen = true;
buttonOpenFolder.Visibility = Visibility.Collapsed; buttonOpenFolder.Visibility = Visibility.Collapsed;
buttonSettings.Visibility = Visibility.Collapsed; buttonSettings.Visibility = Visibility.Collapsed;
@ -203,6 +212,7 @@ namespace SystemTrayMenu.UserInterface
if (RowDataParent?.SubMenu == this) if (RowDataParent?.SubMenu == this)
{ {
RowDataParent.SubMenu = null; RowDataParent.SubMenu = null;
RowDataParent.IsMenuOpen = false;
} }
foreach (ListViewItemData item in dgv.Items) foreach (ListViewItemData item in dgv.Items)
@ -224,7 +234,7 @@ namespace SystemTrayMenu.UserInterface
internal event Action<Menu, bool, bool>? SearchTextChanged; internal event Action<Menu, bool, bool>? SearchTextChanged;
internal event Action? UserDragsMenu; internal event Action<Menu>? UserDragsMenu;
internal event Action<Menu, ListViewItemData>? CellMouseEnter; internal event Action<Menu, ListViewItemData>? CellMouseEnter;
@ -269,6 +279,8 @@ namespace SystemTrayMenu.UserInterface
internal RowData? RowDataParent { get; set; } internal RowData? RowDataParent { get; set; }
internal Menu MainMenu { get; init; }
internal Menu? ParentMenu => RowDataParent?.Owner; internal Menu? ParentMenu => RowDataParent?.Owner;
internal Menu? SubMenu internal Menu? SubMenu
@ -364,13 +376,14 @@ namespace SystemTrayMenu.UserInterface
} }
} }
internal bool IsMouseOn() // TODO: Check if we can just use original IsMouseOver instead? (Check if it requires Mouse.Capture(this))
internal new bool IsMouseOver()
{ {
Point mousePos = NativeMethods.Screen.CursorPosition; Point mousePos = NativeMethods.Screen.CursorPosition;
bool isMouseOn = Visibility == Visibility.Visible && bool isMouseOver = Visibility == Visibility.Visible &&
mousePos.X >= 0 && mousePos.X < Width && mousePos.X >= 0 && mousePos.X < Width &&
mousePos.Y >= 0 && mousePos.Y < Height; mousePos.Y >= 0 && mousePos.Y < Height;
return isMouseOn; return isMouseOver;
} }
internal ListView GetDataGridView() => dgv; // TODO WPF Replace Forms wrapper internal ListView GetDataGridView() => dgv; // TODO WPF Replace Forms wrapper
@ -427,24 +440,37 @@ namespace SystemTrayMenu.UserInterface
} }
} }
internal void ActivateWithFade() internal void ActivateWithFade(bool recursive)
{ {
if (Settings.Default.UseFading) if (recursive)
{ {
isFading = true; SubMenu?.ActivateWithFade(true);
RaiseEvent(new(routedEvent: FadeInEvent));
} }
else
if (Opacity != 1D)
{ {
Opacity = 1D; if (Settings.Default.UseFading)
FadeIn_Completed(this, new()); {
isFading = true;
RaiseEvent(new(routedEvent: FadeInEvent));
}
else
{
Opacity = 1D;
FadeIn_Completed(this, new());
}
} }
} }
internal void ShowWithFade(bool transparency = false) internal void ShowWithFade(bool transparency, bool recursive)
{ {
timerUpdateIcons.Start(); timerUpdateIcons.Start();
if (recursive)
{
SubMenu?.ShowWithFade(transparency, true);
}
if (Level > 0) if (Level > 0)
{ {
ShowActivated = false; ShowActivated = false;
@ -472,8 +498,18 @@ namespace SystemTrayMenu.UserInterface
} }
} }
internal void HideWithFade() internal void HideWithFade(bool recursive)
{ {
if (recursive)
{
SubMenu?.HideWithFade(true);
}
if (RowDataParent != null)
{
RowDataParent.SubMenu = null;
}
if (Settings.Default.UseFading) if (Settings.Default.UseFading)
{ {
isFading = true; isFading = true;
@ -1108,7 +1144,7 @@ namespace SystemTrayMenu.UserInterface
{ {
mouseDown = true; mouseDown = true;
lastLocation = NativeMethods.Screen.CursorPosition; lastLocation = NativeMethods.Screen.CursorPosition;
UserDragsMenu?.Invoke(); UserDragsMenu?.Invoke(this);
Mouse.Capture(this); Mouse.Capture(this);
} }
} }

View file

@ -11,7 +11,7 @@ namespace SystemTrayMenu.Utilities
{ {
public class NativeWindow : HwndSource public class NativeWindow : HwndSource
{ {
private HwndSourceHook hook; private readonly HwndSourceHook hook;
public NativeWindow() public NativeWindow()
: base(new()) : base(new())