mirror of
https://github.com/Hofknecht/SystemTrayMenu.git
synced 2024-10-02 10:07:15 +13:00
0e812d207b
This was not possible before and was solved by sending hotkey in version 1. In v2 it will use named pipes for IPC communication, so it don't rely on hotkeys any more.
791 lines
30 KiB
C#
791 lines
30 KiB
C#
// <copyright file="Menus.cs" company="PlaceholderCompany">
|
|
// Copyright (c) PlaceholderCompany. All rights reserved.
|
|
// </copyright>
|
|
|
|
namespace SystemTrayMenu.Business
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Threading;
|
|
using Microsoft.Win32;
|
|
using SystemTrayMenu.DataClasses;
|
|
using SystemTrayMenu.DllImports;
|
|
using SystemTrayMenu.Helpers;
|
|
using SystemTrayMenu.Properties;
|
|
using SystemTrayMenu.UserInterface;
|
|
using SystemTrayMenu.Utilities;
|
|
using Menu = SystemTrayMenu.UserInterface.Menu;
|
|
using StartLocation = SystemTrayMenu.UserInterface.Menu.StartLocation;
|
|
|
|
internal class Menus : IDisposable
|
|
{
|
|
private readonly AppNotifyIcon menuNotifyIcon = new();
|
|
private readonly BackgroundWorker workerMainMenu = new();
|
|
private readonly List<BackgroundWorker> workersSubMenu = new();
|
|
private readonly WaitToLoadMenu waitToOpenMenu = new();
|
|
private readonly KeyboardInput keyboardInput = new();
|
|
private readonly List<FileSystemWatcher> watchers = new();
|
|
private readonly List<EventArgs> watcherHistory = new();
|
|
private readonly DispatcherTimer timerShowProcessStartedAsLoadingIcon = new();
|
|
private readonly DispatcherTimer timerStillActiveCheck = new();
|
|
private readonly DispatcherTimer waitLeave = new();
|
|
private readonly Menu mainMenu;
|
|
private TaskbarPosition taskbarPosition = TaskbarPosition.Unknown;
|
|
private bool showMenuAfterMainPreload;
|
|
private TaskbarLogo? taskbarLogo;
|
|
|
|
public Menus()
|
|
{
|
|
SingleAppInstance.Wakeup += SwitchOpenCloseByKey;
|
|
menuNotifyIcon.Click += () => SwitchOpenClose(true, false);
|
|
|
|
if (!keyboardInput.RegisterHotKey(Settings.Default.HotKey))
|
|
{
|
|
Settings.Default.HotKey = string.Empty;
|
|
Settings.Default.Save();
|
|
}
|
|
|
|
keyboardInput.HotKeyPressed += SwitchOpenCloseByKey;
|
|
keyboardInput.RowSelectionChanged += waitToOpenMenu.RowSelectionChanged;
|
|
keyboardInput.EnterPressed += waitToOpenMenu.OpenSubMenuByKey;
|
|
|
|
workerMainMenu.WorkerSupportsCancellation = true;
|
|
workerMainMenu.DoWork += LoadMenu;
|
|
workerMainMenu.RunWorkerCompleted += LoadMainMenuCompleted;
|
|
|
|
waitToOpenMenu.StopLoadMenu += WaitToOpenMenu_StopLoadMenu;
|
|
void WaitToOpenMenu_StopLoadMenu()
|
|
{
|
|
foreach (BackgroundWorker workerSubMenu in workersSubMenu.
|
|
Where(w => w.IsBusy))
|
|
{
|
|
workerSubMenu.CancelAsync();
|
|
}
|
|
|
|
menuNotifyIcon.LoadingStop();
|
|
}
|
|
|
|
waitToOpenMenu.MouseSelect += keyboardInput.SelectByMouse;
|
|
|
|
// Timer to check after activation if the application lost focus and close/fadeout windows again
|
|
timerStillActiveCheck.Interval = TimeSpan.FromMilliseconds(Settings.Default.TimeUntilClosesAfterEnterPressed + 20);
|
|
timerStillActiveCheck.Tick += (sender, e) => StillActiveTick();
|
|
void StillActiveTick()
|
|
{
|
|
timerStillActiveCheck.Stop();
|
|
FadeHalfOrOutIfNeeded();
|
|
}
|
|
|
|
waitLeave.Interval = TimeSpan.FromMilliseconds(Settings.Default.TimeUntilCloses);
|
|
waitLeave.Tick += (_, _) =>
|
|
{
|
|
waitLeave.Stop();
|
|
FadeHalfOrOutIfNeeded();
|
|
};
|
|
|
|
CreateWatcher(Config.Path, false);
|
|
foreach (var pathAndFlags in DirectoryHelpers.GetAddionalPathsForMainMenu())
|
|
{
|
|
CreateWatcher(pathAndFlags.Path, pathAndFlags.Recursive);
|
|
}
|
|
|
|
void CreateWatcher(string path, bool recursiv)
|
|
{
|
|
try
|
|
{
|
|
FileSystemWatcher watcher = new()
|
|
{
|
|
Path = path,
|
|
NotifyFilter = NotifyFilters.Attributes |
|
|
NotifyFilters.DirectoryName |
|
|
NotifyFilters.FileName |
|
|
NotifyFilters.LastWrite,
|
|
Filter = "*.*",
|
|
};
|
|
watcher.Created += WatcherProcessItem;
|
|
watcher.Deleted += WatcherProcessItem;
|
|
watcher.Renamed += WatcherProcessItem;
|
|
watcher.Changed += WatcherProcessItem;
|
|
watcher.IncludeSubdirectories = recursiv;
|
|
watcher.EnableRaisingEvents = true;
|
|
watchers.Add(watcher);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Warn($"Failed to {nameof(CreateWatcher)}: {path}", ex);
|
|
}
|
|
}
|
|
|
|
SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
|
|
|
|
mainMenu = new(null, Config.Path);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
SingleAppInstance.Wakeup -= SwitchOpenCloseByKey;
|
|
SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged;
|
|
workerMainMenu.Dispose();
|
|
foreach (BackgroundWorker worker in workersSubMenu)
|
|
{
|
|
worker.Dispose();
|
|
}
|
|
|
|
foreach (FileSystemWatcher watcher in watchers)
|
|
{
|
|
watcher.Created -= WatcherProcessItem;
|
|
watcher.Deleted -= WatcherProcessItem;
|
|
watcher.Renamed -= WatcherProcessItem;
|
|
watcher.Changed -= WatcherProcessItem;
|
|
watcher.Dispose();
|
|
}
|
|
|
|
waitToOpenMenu.Dispose();
|
|
keyboardInput.Dispose();
|
|
timerShowProcessStartedAsLoadingIcon.Stop();
|
|
timerStillActiveCheck.Stop();
|
|
waitLeave.Stop();
|
|
taskbarLogo?.Close();
|
|
menuNotifyIcon.Dispose();
|
|
mainMenu.Close();
|
|
}
|
|
|
|
internal static void OpenFolder(string path) => Log.ProcessStart(path);
|
|
|
|
internal void Startup()
|
|
{
|
|
if (Settings.Default.ShowInTaskbar)
|
|
{
|
|
taskbarLogo = new();
|
|
taskbarLogo.Activated += (_, _) =>
|
|
{
|
|
// User started with taskbar or clicked on taskbar: remember to open menu after preload has finished
|
|
showMenuAfterMainPreload = true;
|
|
SwitchOpenClose(true, true);
|
|
};
|
|
|
|
taskbarLogo.Show();
|
|
}
|
|
else
|
|
{
|
|
SwitchOpenClose(false, true);
|
|
}
|
|
}
|
|
|
|
internal void SwitchOpenClose(bool byClick, bool allowPreloading)
|
|
{
|
|
// Ignore open close events during main preload #248
|
|
if (IconReader.IsPreloading && !allowPreloading)
|
|
{
|
|
// User pressed hotkey or clicked on notifyicon: remember to open menu after preload has finished
|
|
showMenuAfterMainPreload = true;
|
|
return;
|
|
}
|
|
|
|
waitToOpenMenu.MouseActive = byClick;
|
|
|
|
if (workerMainMenu.IsBusy)
|
|
{
|
|
// Stop current loading process of main menu
|
|
workerMainMenu.CancelAsync();
|
|
menuNotifyIcon.LoadingStop();
|
|
}
|
|
else if (mainMenu.Visibility == Visibility.Visible)
|
|
{
|
|
// Main menu is visible, hide all menus
|
|
mainMenu.HideWithFade(true);
|
|
}
|
|
else
|
|
{
|
|
// Main menu is hidden or even not created at all, (create and) show it
|
|
if (Settings.Default.GenerateShortcutsToDrives)
|
|
{
|
|
GenerateDriveShortcuts.Start(); // TODO: Once or actually on every startup?
|
|
}
|
|
|
|
menuNotifyIcon.LoadingStart();
|
|
workerMainMenu.RunWorkerAsync(null);
|
|
}
|
|
}
|
|
|
|
internal void KeyPressed(Key key, ModifierKeys modifiers)
|
|
{
|
|
// Look for a valid menu that is visible, active and has focus
|
|
if (mainMenu.Visibility == Visibility.Visible)
|
|
{
|
|
Menu? menu = mainMenu;
|
|
do
|
|
{
|
|
if (menu.IsActive || menu.IsKeyboardFocusWithin)
|
|
{
|
|
// Send the keys to the active menu
|
|
menu.Dispatcher.Invoke(keyboardInput.CmdKeyProcessed, new object[] { menu, key, modifiers });
|
|
return;
|
|
}
|
|
|
|
menu = menu.SubMenu;
|
|
}
|
|
while (menu != null);
|
|
}
|
|
}
|
|
|
|
private static Menu? IsMouseOverAnyMenu(Menu? menu)
|
|
{
|
|
while (menu != null)
|
|
{
|
|
if (menu.IsMouseOver())
|
|
{
|
|
break;
|
|
}
|
|
|
|
menu = menu.SubMenu;
|
|
}
|
|
|
|
return menu;
|
|
}
|
|
|
|
private static void LoadMenu(object? sender, DoWorkEventArgs eDoWork)
|
|
{
|
|
BackgroundWorker? workerSelf = sender as BackgroundWorker;
|
|
RowData? rowData = eDoWork.Argument as RowData;
|
|
string path = rowData?.ResolvedPath ?? Config.Path;
|
|
|
|
MenuData menuData = new(rowData);
|
|
DirectoryHelpers.DiscoverItems(workerSelf, path, ref menuData);
|
|
if (menuData.DirectoryState != MenuDataDirectoryState.Undefined &&
|
|
workerSelf != null && rowData == null)
|
|
{
|
|
// After success of MainMenu loading: never run again
|
|
workerSelf.DoWork -= LoadMenu;
|
|
}
|
|
|
|
eDoWork.Result = menuData;
|
|
}
|
|
|
|
private void LoadMainMenuCompleted(object? sender, RunWorkerCompletedEventArgs e)
|
|
{
|
|
keyboardInput.ResetSelectedByKey();
|
|
menuNotifyIcon.LoadingStop();
|
|
|
|
if (e.Result == null)
|
|
{
|
|
mainMenu.SelectedItem = null;
|
|
mainMenu.RelocateOnNextShow = true;
|
|
mainMenu.ShowWithFade(false, true);
|
|
}
|
|
else
|
|
{
|
|
// First time the main menu gets loaded
|
|
MenuData menuData = (MenuData)e.Result;
|
|
switch (menuData.DirectoryState)
|
|
{
|
|
case MenuDataDirectoryState.Valid:
|
|
if (IconReader.IsPreloading)
|
|
{
|
|
InitializeMenu(mainMenu, menuData.RowDatas); // Level 0 Main Menu
|
|
|
|
IconReader.IsPreloading = false;
|
|
if (showMenuAfterMainPreload)
|
|
{
|
|
mainMenu.ShowWithFade(false, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mainMenu.ShowWithFade(false, true);
|
|
}
|
|
|
|
break;
|
|
case MenuDataDirectoryState.Empty:
|
|
MessageBox.Show(Translator.GetText("Your root directory for the app does not exist or is empty! Change the root directory or put some files, directories or shortcuts into the root directory."));
|
|
OpenFolder(Config.Path);
|
|
Config.SetFolderByUser();
|
|
AppRestart.ByConfigChange();
|
|
break;
|
|
case MenuDataDirectoryState.NoAccess:
|
|
MessageBox.Show(Translator.GetText("You have no access to the root directory of the app. Grant access to the directory or change the root directory."));
|
|
OpenFolder(Config.Path);
|
|
Config.SetFolderByUser();
|
|
AppRestart.ByConfigChange();
|
|
break;
|
|
case MenuDataDirectoryState.Undefined:
|
|
Log.Info($"{nameof(MenuDataDirectoryState)}.{nameof(MenuDataDirectoryState.Undefined)}");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void LoadSubMenuCompleted(object? senderCompleted, RunWorkerCompletedEventArgs e)
|
|
{
|
|
if (e.Result == null || mainMenu.Visibility != Visibility.Visible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MenuData menuData = (MenuData)e.Result;
|
|
Menu? menu = mainMenu.SubMenu;
|
|
while (menu != null)
|
|
{
|
|
if (menu.Level == menuData.Level)
|
|
{
|
|
break;
|
|
}
|
|
|
|
menu = menu.SubMenu;
|
|
}
|
|
|
|
if (menu == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (menuData.DirectoryState != MenuDataDirectoryState.Undefined)
|
|
{
|
|
// Sub Menu (completed)
|
|
menu.AddItemsToMenu(menuData.RowDatas, menuData.DirectoryState);
|
|
AdjustMenusSizeAndLocation(menu.Level);
|
|
}
|
|
else
|
|
{
|
|
// TODO: Main menu should destroy sub menu(s?) when it becomes unusable
|
|
menu.HideWithFade(false);
|
|
|
|
// TODO: Remove when setting SubMenu of RowData notifies about value change
|
|
menu.ParentMenu?.RefreshSelection();
|
|
}
|
|
}
|
|
|
|
private void InitializeMenu(Menu menu, List<RowData> rowDatas)
|
|
{
|
|
menu.AddItemsToMenu(rowDatas, null);
|
|
|
|
menu.MenuScrolled += () => AdjustMenusSizeAndLocation(menu.Level + 1); // TODO: Only update vertical location while scrolling?
|
|
menu.MouseLeave += (_, _) =>
|
|
{
|
|
// Restart timer
|
|
waitLeave.Stop();
|
|
waitLeave.Start();
|
|
};
|
|
menu.MouseEnter += (_, _) => waitLeave.Stop();
|
|
menu.CmdKeyProcessed += keyboardInput.CmdKeyProcessed;
|
|
|
|
menu.SearchTextChanging += Menu_SearchTextChanging;
|
|
void Menu_SearchTextChanging()
|
|
{
|
|
waitToOpenMenu.MouseActive = false;
|
|
}
|
|
|
|
menu.SearchTextChanged += Menu_SearchTextChanged;
|
|
void Menu_SearchTextChanged(Menu menu, bool isSearchStringEmpty, bool causedByWatcherUpdate)
|
|
{
|
|
menu.SelectedItem = null;
|
|
if (!isSearchStringEmpty)
|
|
{
|
|
ListView dgv = menu.GetDataGridView();
|
|
if (dgv.Items.Count > 0)
|
|
{
|
|
keyboardInput.SelectByMouse((RowData)dgv.Items[0]);
|
|
}
|
|
}
|
|
|
|
AdjustMenusSizeAndLocation(menu.Level + 1);
|
|
|
|
if (!causedByWatcherUpdate)
|
|
{
|
|
// if there is any open sub menu, close it
|
|
menu.SubMenu?.HideWithFade(true);
|
|
menu.RefreshSelection();
|
|
}
|
|
}
|
|
|
|
menu.Deactivated += Deactivate;
|
|
void Deactivate(object? sender, EventArgs e)
|
|
{
|
|
// TODO: Does this check make any sense here?
|
|
if (!Settings.Default.StaysOpenWhenFocusLostAfterEnterPressed)
|
|
{
|
|
FadeHalfOrOutIfNeeded();
|
|
}
|
|
}
|
|
|
|
menu.Activated += (sender, e) => Activated();
|
|
void Activated()
|
|
{
|
|
// Bring transparent menus back
|
|
mainMenu.ActivateWithFade(true);
|
|
|
|
timerStillActiveCheck.Stop();
|
|
timerStillActiveCheck.Start();
|
|
}
|
|
|
|
menu.IsVisibleChanged += (sender, _) => MenuVisibleChanged((Menu)sender);
|
|
menu.RowSelectionChanged += waitToOpenMenu.RowSelectionChanged;
|
|
menu.CellMouseEnter += waitToOpenMenu.MouseEnter;
|
|
menu.CellMouseLeave += waitToOpenMenu.MouseLeave;
|
|
menu.CellMouseDown += keyboardInput.SelectByMouse;
|
|
menu.CellOpenOnClick += waitToOpenMenu.OpenSubMenuByMouse;
|
|
|
|
if (menu.Level == 0)
|
|
{
|
|
// Main Menu
|
|
menu.Loaded += (s, e) => ExecuteWatcherHistory();
|
|
}
|
|
else
|
|
{
|
|
// Sub Menu (loading)
|
|
menu.ShowWithFade(!App.IsActiveApp, false);
|
|
menu.RefreshSelection();
|
|
}
|
|
|
|
menu.StartLoadSubMenu += StartLoadSubMenu;
|
|
void StartLoadSubMenu(RowData rowData)
|
|
{
|
|
if (mainMenu.Visibility != Visibility.Visible)
|
|
{
|
|
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)
|
|
{
|
|
InitializeMenu(new(rowData, rowData.Path), new()); // Level 1+ Sub Menu (loading)
|
|
|
|
BackgroundWorker? workerSubMenu = workersSubMenu.
|
|
Where(w => !w.IsBusy).FirstOrDefault();
|
|
if (workerSubMenu == null)
|
|
{
|
|
workerSubMenu = new BackgroundWorker
|
|
{
|
|
WorkerSupportsCancellation = true,
|
|
};
|
|
workerSubMenu.DoWork += LoadMenu;
|
|
workerSubMenu.RunWorkerCompleted += LoadSubMenuCompleted;
|
|
workersSubMenu.Add(workerSubMenu);
|
|
}
|
|
|
|
workerSubMenu.RunWorkerAsync(rowData);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void MenuVisibleChanged(Menu menu)
|
|
{
|
|
if (menu.Visibility == Visibility.Visible)
|
|
{
|
|
AdjustMenusSizeAndLocation(menu.Level);
|
|
|
|
if (menu.Level == 0)
|
|
{
|
|
menu.ResetSearchText();
|
|
menu.Activate();
|
|
}
|
|
}
|
|
else if (menu.Level != 0)
|
|
{
|
|
// Close down non-visible sub menus
|
|
menu.Close();
|
|
}
|
|
else
|
|
{
|
|
// Non-visible main menu, do some housekeeping
|
|
IconReader.ClearCacheWhenLimitReached();
|
|
}
|
|
}
|
|
|
|
private void SwitchOpenCloseByKey() => mainMenu.Dispatcher.Invoke(() => SwitchOpenClose(false, false));
|
|
|
|
private void SystemEvents_DisplaySettingsChanged(object? sender, EventArgs e) =>
|
|
mainMenu.Dispatcher.Invoke(() => mainMenu.RelocateOnNextShow = true);
|
|
|
|
private void FadeHalfOrOutIfNeeded()
|
|
{
|
|
if (!App.IsActiveApp && mainMenu.Visibility == Visibility.Visible)
|
|
{
|
|
if (Settings.Default.StaysOpenWhenFocusLost && IsMouseOverAnyMenu(mainMenu) != null)
|
|
{
|
|
if (!keyboardInput.IsSelectedByKey)
|
|
{
|
|
mainMenu.ShowWithFade(true, true);
|
|
}
|
|
}
|
|
else if (Config.AlwaysOpenByPin)
|
|
{
|
|
mainMenu.ShowWithFade(true, true);
|
|
}
|
|
else
|
|
{
|
|
mainMenu.HideWithFade(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void GetScreenBounds(out Rect screenBounds, out bool useCustomLocation, out StartLocation startLocation)
|
|
{
|
|
if (Settings.Default.AppearAtMouseLocation)
|
|
{
|
|
screenBounds = NativeMethods.Screen.FromPoint(NativeMethods.Screen.CursorPosition);
|
|
useCustomLocation = false;
|
|
}
|
|
else if (Settings.Default.UseCustomLocation)
|
|
{
|
|
screenBounds = NativeMethods.Screen.FromPoint(new(
|
|
Settings.Default.CustomLocationX,
|
|
Settings.Default.CustomLocationY));
|
|
|
|
useCustomLocation = screenBounds.Contains(
|
|
new Point(Settings.Default.CustomLocationX, Settings.Default.CustomLocationY));
|
|
}
|
|
else
|
|
{
|
|
screenBounds = NativeMethods.Screen.PrimaryScreen;
|
|
useCustomLocation = false;
|
|
}
|
|
|
|
// Shrink the usable space depending on taskbar location
|
|
WindowsTaskbar taskbar = new();
|
|
taskbarPosition = taskbar.Position;
|
|
switch (taskbarPosition)
|
|
{
|
|
case TaskbarPosition.Left:
|
|
screenBounds.X += taskbar.Size.Width;
|
|
screenBounds.Width -= taskbar.Size.Width;
|
|
startLocation = StartLocation.BottomLeft;
|
|
break;
|
|
case TaskbarPosition.Right:
|
|
screenBounds.Width -= taskbar.Size.Width;
|
|
startLocation = StartLocation.BottomRight;
|
|
break;
|
|
case TaskbarPosition.Top:
|
|
screenBounds.Y += taskbar.Size.Height;
|
|
screenBounds.Height -= taskbar.Size.Height;
|
|
startLocation = StartLocation.TopRight;
|
|
break;
|
|
case TaskbarPosition.Bottom:
|
|
default:
|
|
screenBounds.Height -= taskbar.Size.Height;
|
|
startLocation = StartLocation.BottomRight;
|
|
break;
|
|
}
|
|
|
|
if (Settings.Default.AppearAtTheBottomLeft)
|
|
{
|
|
startLocation = StartLocation.BottomLeft;
|
|
}
|
|
}
|
|
|
|
private void AdjustMenusSizeAndLocation(int startLevel)
|
|
{
|
|
GetScreenBounds(out Rect screenBounds, out bool useCustomLocation, out StartLocation startLocation);
|
|
|
|
Menu? menu = mainMenu;
|
|
Menu? menuPredecessor = null;
|
|
|
|
while (menu != null)
|
|
{
|
|
if (startLevel <= menu.Level)
|
|
{
|
|
menu.AdjustSizeAndLocation(screenBounds, menuPredecessor, startLocation, useCustomLocation);
|
|
}
|
|
else
|
|
{
|
|
// Make sure further calculations of this menu access updated values (later used as predecessor)
|
|
menu.UpdateLayout();
|
|
}
|
|
|
|
if (!Settings.Default.AppearAtTheBottomLeft &&
|
|
!Settings.Default.AppearAtMouseLocation &&
|
|
!Settings.Default.UseCustomLocation &&
|
|
menu.Level == 0)
|
|
{
|
|
const double overlapTolerance = 4D;
|
|
|
|
// Remember width of the initial menu as we don't want to overlap with it
|
|
if (taskbarPosition == TaskbarPosition.Left)
|
|
{
|
|
screenBounds.X += (int)menu.Width - overlapTolerance;
|
|
}
|
|
|
|
screenBounds.Width -= (int)menu.Width - overlapTolerance;
|
|
}
|
|
|
|
menuPredecessor = menu;
|
|
menu = menu.SubMenu;
|
|
}
|
|
}
|
|
|
|
private void ExecuteWatcherHistory()
|
|
{
|
|
foreach (var fileSystemEventArgs in watcherHistory)
|
|
{
|
|
WatcherProcessItem(watchers, fileSystemEventArgs);
|
|
}
|
|
|
|
watcherHistory.Clear();
|
|
}
|
|
|
|
private void WatcherProcessItem(object sender, EventArgs e)
|
|
{
|
|
// Store event in history as long as menu is not loaded
|
|
if (mainMenu.Dispatcher.Invoke(() => !mainMenu.IsLoaded))
|
|
{
|
|
watcherHistory.Add(e);
|
|
return;
|
|
}
|
|
|
|
if (e is RenamedEventArgs renamedEventArgs)
|
|
{
|
|
mainMenu.Dispatcher.Invoke(() => RenameItem(mainMenu, renamedEventArgs));
|
|
}
|
|
else if (e is FileSystemEventArgs fileSystemEventArgs)
|
|
{
|
|
if (fileSystemEventArgs.ChangeType == WatcherChangeTypes.Deleted)
|
|
{
|
|
mainMenu.Dispatcher.Invoke(() => DeleteItem(mainMenu, fileSystemEventArgs));
|
|
}
|
|
else if (fileSystemEventArgs.ChangeType == WatcherChangeTypes.Created)
|
|
{
|
|
mainMenu.Dispatcher.Invoke(() => CreateItem(mainMenu, fileSystemEventArgs));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RenameItem(Menu menu, RenamedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
List<RowData> rowDatas = new();
|
|
foreach (RowData rowData in menu.GetDataGridView().Items)
|
|
{
|
|
if (rowData.Path.StartsWith($"{e.OldFullPath}"))
|
|
{
|
|
string path = rowData.Path.Replace(e.OldFullPath, e.FullPath);
|
|
FileAttributes attr = File.GetAttributes(path);
|
|
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;
|
|
}
|
|
|
|
IconReader.RemoveIconFromCache(rowData.Path);
|
|
rowDataRenamed.HiddenEntry = hasHiddenFlag;
|
|
rowDataRenamed.LoadIcon();
|
|
rowDatas.Add(rowDataRenamed);
|
|
}
|
|
else
|
|
{
|
|
rowDatas.Add(rowData);
|
|
}
|
|
}
|
|
|
|
rowDatas = DirectoryHelpers.SortItems(rowDatas);
|
|
menu.SelectedItem = null;
|
|
menu.AddItemsToMenu(rowDatas, null);
|
|
menu.OnWatcherUpdate();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Warn($"Failed to {nameof(RenameItem)}: {e.OldFullPath} {e.FullPath}", ex);
|
|
}
|
|
}
|
|
|
|
private void DeleteItem(Menu menu, FileSystemEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
ListView? dgv = menu.GetDataGridView();
|
|
List<RowData> rowsToRemove = new();
|
|
|
|
foreach (RowData rowData in dgv.ItemsSource)
|
|
{
|
|
if (rowData.Path == e.FullPath ||
|
|
rowData.Path.StartsWith($"{e.FullPath}\\"))
|
|
{
|
|
IconReader.RemoveIconFromCache(rowData.Path);
|
|
rowsToRemove.Add(rowData);
|
|
}
|
|
}
|
|
|
|
foreach (RowData rowToRemove in rowsToRemove)
|
|
{
|
|
((IEditableCollectionView)dgv.Items).Remove(rowToRemove);
|
|
}
|
|
|
|
menu.SelectedItem = null;
|
|
menu.OnWatcherUpdate();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Warn($"Failed to {nameof(DeleteItem)}: {e.FullPath}", ex);
|
|
}
|
|
}
|
|
|
|
private void CreateItem(Menu menu, FileSystemEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
FileAttributes attr = File.GetAttributes(e.FullPath);
|
|
bool isFolder = (attr & FileAttributes.Directory) == FileAttributes.Directory;
|
|
bool isAddionalItem = Path.GetDirectoryName(e.FullPath) != Config.Path;
|
|
RowData rowData = new(isFolder, isAddionalItem, 0, e.FullPath);
|
|
FolderOptions.ReadHiddenAttributes(rowData.Path, out bool hasHiddenFlag, out bool isDirectoryToHide);
|
|
if (isDirectoryToHide)
|
|
{
|
|
return;
|
|
}
|
|
|
|
rowData.HiddenEntry = hasHiddenFlag;
|
|
rowData.LoadIcon();
|
|
|
|
var items = menu.GetDataGridView().Items;
|
|
List<RowData> rowDatas = new(items.Count + 1) { rowData };
|
|
foreach (RowData item in items)
|
|
{
|
|
rowDatas.Add(item);
|
|
}
|
|
|
|
rowDatas = DirectoryHelpers.SortItems(rowDatas);
|
|
menu.SelectedItem = null;
|
|
menu.AddItemsToMenu(rowDatas, null);
|
|
menu.OnWatcherUpdate();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Warn($"Failed to {nameof(CreateItem)}: {e.FullPath}", ex);
|
|
}
|
|
}
|
|
}
|
|
}
|