2020-09-27 01:54:24 +12:00
// <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 ;
2022-10-23 11:02:31 +13:00
using System.Windows ;
using System.Windows.Controls ;
using System.Windows.Threading ;
2022-11-26 11:31:20 +13:00
using Microsoft.Win32 ;
2020-09-27 01:54:24 +12:00
using SystemTrayMenu.DataClasses ;
2022-08-06 10:09:19 +12:00
using SystemTrayMenu.DllImports ;
2020-09-27 01:54:24 +12:00
using SystemTrayMenu.Handler ;
2022-09-27 06:02:18 +13:00
using SystemTrayMenu.Helpers ;
2022-12-06 08:06:40 +13:00
using SystemTrayMenu.Properties ;
2020-09-27 01:54:24 +12:00
using SystemTrayMenu.Utilities ;
2022-11-20 07:33:23 +13:00
using static SystemTrayMenu . UserInterface . Menu ;
2021-06-27 21:35:07 +12:00
using Menu = SystemTrayMenu . UserInterface . Menu ;
2020-09-27 01:54:24 +12:00
internal class Menus : IDisposable
{
2021-11-17 12:13:46 +13:00
private readonly BackgroundWorker workerMainMenu = new ( ) ;
private readonly List < BackgroundWorker > workersSubMenu = new ( ) ;
private readonly WaitToLoadMenu waitToOpenMenu = new ( ) ;
2021-04-17 12:39:48 +12:00
private readonly KeyboardInput keyboardInput ;
2023-05-02 02:24:33 +12:00
private readonly JoystickHelper ? joystickHelper ;
2022-06-18 00:38:09 +12:00
private readonly List < FileSystemWatcher > watchers = new ( ) ;
2022-06-21 03:39:39 +12:00
private readonly List < EventArgs > watcherHistory = new ( ) ;
2022-10-23 11:02:31 +13:00
private readonly DispatcherTimer timerShowProcessStartedAsLoadingIcon = new ( ) ;
private readonly DispatcherTimer timerStillActiveCheck = new ( ) ;
2022-12-06 08:06:40 +13:00
private readonly DispatcherTimer waitLeave = new ( ) ;
2023-05-16 08:34:17 +12:00
private TaskbarPosition taskbarPosition = TaskbarPosition . Unknown ;
2021-11-26 11:49:50 +13:00
private bool showMenuAfterMainPreload ;
2023-05-08 10:14:23 +12:00
private Menu ? mainMenu ;
2020-09-27 01:54:24 +12:00
public Menus ( )
{
2023-05-08 10:14:23 +12:00
keyboardInput = new ( ) ;
if ( ! keyboardInput . RegisterHotKey ( Settings . Default . HotKey ) )
{
Settings . Default . HotKey = string . Empty ;
Settings . Default . Save ( ) ;
}
2023-04-30 10:06:00 +12:00
keyboardInput . HotKeyPressed + = ( ) = > SwitchOpenClose ( false , false ) ;
2023-05-12 11:13:02 +12:00
keyboardInput . RowSelectionChanged + = waitToOpenMenu . RowSelectionChanged ;
2023-05-19 06:42:46 +12:00
keyboardInput . EnterPressed + = waitToOpenMenu . OpenSubMenuByKey ;
2023-04-17 06:17:24 +12:00
2020-09-27 01:54:24 +12:00
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 ( ) ;
}
2022-12-06 09:46:53 +13:00
LoadStopped ? . Invoke ( ) ;
2020-09-27 01:54:24 +12:00
}
2023-05-16 08:34:17 +12:00
waitToOpenMenu . MouseSelect + = keyboardInput . SelectByMouse ;
2020-09-27 01:54:24 +12:00
2023-05-02 02:24:33 +12:00
if ( Settings . Default . SupportGamepad )
{
joystickHelper = new ( ) ;
joystickHelper . KeyPressed + = ( key , modifiers ) = >
{
2023-05-17 06:53:01 +12:00
if ( mainMenu ! = null & & mainMenu . Visibility = = Visibility . Visible )
2023-05-02 02:24:33 +12:00
{
2023-05-17 06:53:01 +12:00
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 ) ;
2023-05-02 02:24:33 +12:00
}
} ;
}
2022-09-27 06:02:18 +13:00
2023-05-03 08:04:32 +12:00
// Timer to check after activation if the application lost focus and close/fadeout windows again
2022-12-06 08:06:40 +13:00
timerStillActiveCheck . Interval = TimeSpan . FromMilliseconds ( Settings . Default . TimeUntilClosesAfterEnterPressed + 20 ) ;
2021-10-27 09:09:40 +13:00
timerStillActiveCheck . Tick + = ( sender , e ) = > StillActiveTick ( ) ;
void StillActiveTick ( )
2020-09-27 01:54:24 +12:00
{
2023-05-03 08:04:32 +12:00
timerStillActiveCheck . Stop ( ) ;
2023-05-16 08:34:17 +12:00
FadeHalfOrOutIfNeeded ( ) ;
2020-09-27 01:54:24 +12:00
}
2022-12-06 08:06:40 +13:00
waitLeave . Interval = TimeSpan . FromMilliseconds ( Settings . Default . TimeUntilCloses ) ;
waitLeave . Tick + = ( _ , _ ) = >
{
waitLeave . Stop ( ) ;
FadeHalfOrOutIfNeeded ( ) ;
} ;
2022-06-18 00:38:09 +12:00
CreateWatcher ( Config . Path , false ) ;
2023-04-17 00:17:33 +12:00
foreach ( var pathAndFlags in DirectoryHelpers . GetAddionalPathsForMainMenu ( ) )
2022-06-18 00:38:09 +12:00
{
CreateWatcher ( pathAndFlags . Path , pathAndFlags . Recursive ) ;
}
void CreateWatcher ( string path , bool recursiv )
{
try
{
2022-09-27 06:02:18 +13:00
FileSystemWatcher watcher = new ( )
{
Path = path ,
NotifyFilter = NotifyFilters . Attributes |
2022-06-18 00:38:09 +12:00
NotifyFilters . DirectoryName |
NotifyFilters . FileName |
2022-09-27 06:02:18 +13:00
NotifyFilters . LastWrite ,
Filter = "*.*" ,
} ;
2022-06-18 00:38:09 +12:00
watcher . Created + = WatcherProcessItem ;
watcher . Deleted + = WatcherProcessItem ;
2022-06-21 03:39:39 +12:00
watcher . Renamed + = WatcherProcessItem ;
watcher . Changed + = WatcherProcessItem ;
2022-06-18 00:38:09 +12:00
watcher . IncludeSubdirectories = recursiv ;
watcher . EnableRaisingEvents = true ;
watchers . Add ( watcher ) ;
}
catch ( Exception ex )
{
Log . Warn ( $"Failed to {nameof(CreateWatcher)}: {path}" , ex ) ;
}
}
2022-11-26 11:31:20 +13:00
SystemEvents . DisplaySettingsChanged + = SystemEvents_DisplaySettingsChanged ;
2020-09-27 01:54:24 +12:00
}
2022-12-06 09:46:53 +13:00
internal event Action ? LoadStarted ;
2020-09-27 01:54:24 +12:00
2022-12-06 09:46:53 +13:00
internal event Action ? LoadStopped ;
2020-09-27 01:54:24 +12:00
public void Dispose ( )
{
2022-11-26 11:31:20 +13:00
SystemEvents . DisplaySettingsChanged - = SystemEvents_DisplaySettingsChanged ;
2020-09-27 01:54:24 +12:00
workerMainMenu . Dispose ( ) ;
foreach ( BackgroundWorker worker in workersSubMenu )
{
worker . Dispose ( ) ;
}
2022-06-18 00:38:09 +12:00
foreach ( FileSystemWatcher watcher in watchers )
{
watcher . Created - = WatcherProcessItem ;
watcher . Deleted - = WatcherProcessItem ;
2022-06-21 03:39:39 +12:00
watcher . Renamed - = WatcherProcessItem ;
watcher . Changed - = WatcherProcessItem ;
2022-06-18 00:38:09 +12:00
watcher . Dispose ( ) ;
}
2023-05-08 10:14:23 +12:00
waitToOpenMenu . Dispose ( ) ;
keyboardInput . Dispose ( ) ;
joystickHelper ? . Dispose ( ) ;
timerShowProcessStartedAsLoadingIcon . Stop ( ) ;
timerStillActiveCheck . Stop ( ) ;
waitLeave . Stop ( ) ;
mainMenu ? . Close ( ) ;
2020-09-27 01:54:24 +12:00
}
2023-05-16 08:34:17 +12:00
internal static void OpenFolder ( string path ) = > Log . ProcessStart ( path ) ;
2022-12-04 13:24:30 +13:00
2021-11-10 05:41:39 +13:00
internal void SwitchOpenCloseByTaskbarItem ( )
{
2023-04-20 10:50:29 +12:00
// User started with taskbar or clicked on taskbar: remember to open menu after preload has finished
showMenuAfterMainPreload = true ;
SwitchOpenClose ( true , true ) ;
2021-11-10 05:41:39 +13:00
timerStillActiveCheck . Start ( ) ;
}
2023-04-30 10:06:00 +12:00
internal void SwitchOpenClose ( bool byClick , bool allowPreloading )
2020-09-27 01:54:24 +12:00
{
2021-11-24 11:48:59 +13:00
// Ignore open close events during main preload #248
2023-04-20 10:50:29 +12:00
if ( IconReader . IsPreloading & & ! allowPreloading )
2021-11-24 11:48:59 +13:00
{
2023-04-20 10:50:29 +12:00
// User pressed hotkey or clicked on notifyicon: remember to open menu after preload has finished
2021-11-26 11:49:50 +13:00
showMenuAfterMainPreload = true ;
2021-11-24 11:48:59 +13:00
return ;
}
2020-09-27 01:54:24 +12:00
waitToOpenMenu . MouseActive = byClick ;
2023-05-16 09:26:03 +12:00
2023-05-17 05:32:48 +12:00
if ( workerMainMenu . IsBusy )
2020-09-27 01:54:24 +12:00
{
2023-05-17 05:32:48 +12:00
// Stop current loading process of main menu
workerMainMenu . CancelAsync ( ) ;
LoadStopped ? . Invoke ( ) ;
2023-05-16 09:26:03 +12:00
}
2023-05-17 05:32:48 +12:00
else if ( mainMenu ! = null & & mainMenu . Visibility = = Visibility . Visible )
2023-05-16 09:26:03 +12:00
{
2023-05-17 05:32:48 +12:00
// Main menu is visible, hide all menus
mainMenu . HideWithFade ( true ) ;
2020-09-27 01:54:24 +12:00
}
2023-05-17 05:32:48 +12:00
else
2023-05-08 10:14:23 +12:00
{
2023-05-17 05:32:48 +12:00
// Main menu is hidden or even not created at all, (create and) show it
if ( Settings . Default . GenerateShortcutsToDrives )
2023-05-08 10:14:23 +12:00
{
2023-05-17 05:32:48 +12:00
GenerateDriveShortcuts . Start ( ) ; // TODO: Once or actually on every startup?
2023-05-08 10:14:23 +12:00
}
2023-05-17 05:32:48 +12:00
LoadStarted ? . Invoke ( ) ;
workerMainMenu . RunWorkerAsync ( null ) ;
2023-05-08 10:14:23 +12:00
}
}
private static Menu ? IsMouseOverAnyMenu ( Menu ? menu )
{
while ( menu ! = null )
{
if ( menu . IsMouseOver ( ) )
{
break ;
}
menu = menu . SubMenu ;
}
return menu ;
}
2023-04-23 07:04:34 +12:00
private static void LoadMenu ( object? sender , DoWorkEventArgs eDoWork )
2021-11-11 11:39:52 +13:00
{
2023-04-23 07:04:34 +12:00
BackgroundWorker ? workerSelf = sender as BackgroundWorker ;
2022-12-05 13:27:57 +13:00
RowData ? rowData = eDoWork . Argument as RowData ;
2023-04-30 04:57:39 +12:00
string path = rowData ? . ResolvedPath ? ? Config . Path ;
2021-12-11 03:20:24 +13:00
2023-04-30 04:57:39 +12:00
MenuData menuData = new ( rowData ) ;
2023-04-23 07:04:34 +12:00
DirectoryHelpers . DiscoverItems ( workerSelf , path , ref menuData ) ;
if ( menuData . DirectoryState ! = MenuDataDirectoryState . Undefined & &
2023-04-30 04:57:39 +12:00
workerSelf ! = null & & rowData = = null )
2023-04-23 07:04:34 +12:00
{
// After success of MainMenu loading: never run again
workerSelf . DoWork - = LoadMenu ;
}
2021-11-11 11:39:52 +13:00
eDoWork . Result = menuData ;
}
2023-04-23 07:04:34 +12:00
private void LoadMainMenuCompleted ( object? sender , RunWorkerCompletedEventArgs e )
{
keyboardInput . ResetSelectedByKey ( ) ;
LoadStopped ? . Invoke ( ) ;
if ( e . Result = = null )
{
2023-05-08 10:14:23 +12:00
Menu ? menu = mainMenu ;
2023-04-23 07:04:34 +12:00
if ( menu ! = null )
{
2023-05-11 10:14:05 +12:00
menu . SelectedItem = null ;
2023-05-05 10:08:39 +12:00
2023-04-23 07:04:34 +12:00
menu . RelocateOnNextShow = true ;
2023-05-08 10:14:23 +12:00
menu . ShowWithFade ( false , true ) ;
}
2023-04-23 07:04:34 +12:00
}
else
{
// First time the main menu gets loaded
MenuData menuData = ( MenuData ) e . Result ;
switch ( menuData . DirectoryState )
{
case MenuDataDirectoryState . Valid :
if ( IconReader . IsPreloading )
{
2023-05-08 10:14:23 +12:00
Menu menu = Create ( menuData , Config . Path ) ; // Level 0 Main Menu
2023-04-23 07:04:34 +12:00
IconReader . IsPreloading = false ;
if ( showMenuAfterMainPreload )
{
2023-05-08 10:14:23 +12:00
menu . ShowWithFade ( false , false ) ;
2023-04-23 07:04:34 +12:00
}
}
else
{
2023-05-08 10:14:23 +12:00
mainMenu ? . ShowWithFade ( false , true ) ;
2023-04-23 07:04:34 +12:00
}
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." ) ) ;
2023-05-16 08:34:17 +12:00
OpenFolder ( Config . Path ) ;
2023-04-23 07:04:34 +12:00
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." ) ) ;
2023-05-16 08:34:17 +12:00
OpenFolder ( Config . Path ) ;
2023-04-23 07:04:34 +12:00
Config . SetFolderByUser ( ) ;
AppRestart . ByConfigChange ( ) ;
break ;
case MenuDataDirectoryState . Undefined :
Log . Info ( $"{nameof(MenuDataDirectoryState)}.{nameof(MenuDataDirectoryState.Undefined)}" ) ;
break ;
default :
break ;
}
}
}
2023-04-28 08:19:02 +12:00
private void LoadSubMenuCompleted ( object? senderCompleted , RunWorkerCompletedEventArgs e )
{
2023-05-17 06:53:01 +12:00
if ( e . Result = = null | | mainMenu = = null | | mainMenu . Visibility ! = Visibility . Visible )
2023-04-28 08:19:02 +12:00
{
return ;
}
MenuData menuData = ( MenuData ) e . Result ;
2023-05-08 10:14:23 +12:00
Menu ? menu = mainMenu . SubMenu ;
while ( menu ! = null )
{
if ( menu . Level = = menuData . Level )
{
break ;
}
menu = menu . SubMenu ;
}
2023-04-28 08:19:02 +12:00
if ( menu = = null )
{
return ;
}
2023-05-08 10:14:23 +12:00
if ( menuData . DirectoryState ! = MenuDataDirectoryState . Undefined )
2023-04-28 08:19:02 +12:00
{
2023-05-08 10:14:23 +12:00
// Sub Menu (completed)
menu . AddItemsToMenu ( menuData . RowDatas , menuData . DirectoryState , true ) ;
AdjustMenusSizeAndLocation ( menu . Level ) ;
}
else
{
// TODO: Main menu should destroy sub menu(s?) when it becomes unusable
menu . HideWithFade ( false ) ;
2023-04-28 08:19:02 +12:00
2023-05-09 10:19:29 +12:00
// TODO: Remove when setting SubMenu of RowData notifies about value change
menu . ParentMenu ? . RefreshSelection ( ) ;
2023-04-28 08:19:02 +12:00
}
}
2023-04-16 22:42:42 +12:00
private Menu Create ( MenuData menuData , string path )
2020-09-27 01:54:24 +12:00
{
2023-04-16 07:23:28 +12:00
Menu menu = new ( menuData , path ) ;
2021-06-28 00:44:12 +12:00
2023-04-17 06:17:24 +12:00
menu . MenuScrolled + = ( ) = > AdjustMenusSizeAndLocation ( menu . Level + 1 ) ; // TODO: Only update vertical location while scrolling?
2022-12-06 08:06:40 +13:00
menu . MouseLeave + = ( _ , _ ) = >
{
// Restart timer
waitLeave . Stop ( ) ;
waitLeave . Start ( ) ;
} ;
menu . MouseEnter + = ( _ , _ ) = > waitLeave . Stop ( ) ;
2020-09-27 01:54:24 +12:00
menu . CmdKeyProcessed + = keyboardInput . CmdKeyProcessed ;
2023-04-20 10:50:29 +12:00
2020-10-10 20:47:30 +13:00
menu . SearchTextChanging + = Menu_SearchTextChanging ;
2023-04-17 06:17:24 +12:00
void Menu_SearchTextChanging ( )
{
waitToOpenMenu . MouseActive = false ;
}
2020-09-27 01:54:24 +12:00
menu . SearchTextChanged + = Menu_SearchTextChanged ;
2023-04-28 09:24:25 +12:00
void Menu_SearchTextChanged ( Menu menu , bool isSearchStringEmpty , bool causedByWatcherUpdate )
2023-04-17 06:17:24 +12:00
{
2023-05-15 05:13:44 +12:00
menu . SelectedItem = null ;
if ( ! isSearchStringEmpty )
{
ListView dgv = menu . GetDataGridView ( ) ;
if ( dgv . Items . Count > 0 )
{
2023-05-19 06:42:46 +12:00
keyboardInput . SelectByMouse ( ( ListViewItemData ) dgv . Items [ 0 ] ) ;
2023-05-15 05:13:44 +12:00
}
}
2023-04-17 06:17:24 +12:00
AdjustMenusSizeAndLocation ( menu . Level + 1 ) ;
2023-05-08 10:14:23 +12:00
if ( ! causedByWatcherUpdate )
2023-04-17 06:17:24 +12:00
{
2023-05-19 06:42:46 +12:00
// if there is any open sub menu, close it
menu . SubMenu ? . HideWithFade ( true ) ;
menu . RefreshSelection ( ) ;
2023-04-17 06:17:24 +12:00
}
}
2022-10-23 11:02:31 +13:00
menu . Deactivated + = Deactivate ;
2022-12-06 09:46:53 +13:00
void Deactivate ( object? sender , EventArgs e )
2020-09-27 01:54:24 +12:00
{
2023-05-17 05:32:48 +12:00
// TODO: Does this check make any sense here?
if ( ! Settings . Default . StaysOpenWhenFocusLostAfterEnterPressed )
2020-09-27 01:54:24 +12:00
{
2021-10-27 09:09:40 +13:00
FadeHalfOrOutIfNeeded ( ) ;
2020-09-27 01:54:24 +12:00
}
}
2021-10-27 09:09:40 +13:00
menu . Activated + = ( sender , e ) = > Activated ( ) ;
void Activated ( )
2020-09-27 01:54:24 +12:00
{
2023-05-03 08:04:32 +12:00
// Bring transparent menus back
2023-05-08 10:14:23 +12:00
mainMenu ? . ActivateWithFade ( true ) ;
2023-05-03 08:04:32 +12:00
timerStillActiveCheck . Stop ( ) ;
timerStillActiveCheck . Start ( ) ;
2020-09-27 01:54:24 +12:00
}
2022-12-01 12:16:30 +13:00
menu . IsVisibleChanged + = ( sender , _ ) = > MenuVisibleChanged ( ( Menu ) sender ) ;
2023-05-15 05:13:44 +12:00
menu . RowSelectionChanged + = waitToOpenMenu . RowSelectionChanged ;
2023-04-28 06:27:16 +12:00
menu . CellMouseEnter + = waitToOpenMenu . MouseEnter ;
menu . CellMouseLeave + = waitToOpenMenu . MouseLeave ;
2023-05-16 08:34:17 +12:00
menu . CellMouseDown + = keyboardInput . SelectByMouse ;
2023-05-19 06:42:46 +12:00
menu . CellOpenOnClick + = waitToOpenMenu . OpenSubMenuByMouse ;
2022-11-30 10:48:45 +13:00
2023-04-28 08:19:02 +12:00
if ( menu . Level = = 0 )
2023-04-16 08:47:29 +12:00
{
// Main Menu
2023-05-08 10:14:23 +12:00
mainMenu = menu ;
2023-04-16 08:47:29 +12:00
menu . Loaded + = ( s , e ) = > ExecuteWatcherHistory ( ) ;
}
2023-04-16 22:42:42 +12:00
else
2023-04-16 08:47:29 +12:00
{
2023-04-16 22:42:42 +12:00
// Sub Menu (loading)
2023-05-16 08:34:17 +12:00
menu . ShowWithFade ( ! App . IsActiveApp , false ) ;
menu . RefreshSelection ( ) ;
2023-04-16 08:47:29 +12:00
}
2023-05-19 06:42:46 +12:00
menu . StartLoadSubMenu + = StartLoadSubMenu ;
void StartLoadSubMenu ( RowData rowData )
{
if ( mainMenu = = null | | 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 )
{
Create ( new ( rowData ) , rowData . Path ) ; // 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 ) ;
}
}
2020-09-27 01:54:24 +12:00
return menu ;
}
2022-12-01 12:16:30 +13:00
private void MenuVisibleChanged ( Menu menu )
2020-09-27 01:54:24 +12:00
{
2023-05-16 08:34:17 +12:00
if ( menu . Visibility = = Visibility . Visible )
2020-09-27 01:54:24 +12:00
{
2023-04-17 06:17:24 +12:00
AdjustMenusSizeAndLocation ( menu . Level ) ;
2022-01-08 23:39:23 +13:00
if ( menu . Level = = 0 )
{
2022-06-11 22:45:46 +12:00
menu . ResetSearchText ( ) ;
2023-04-30 10:06:00 +12:00
menu . Activate ( ) ;
2022-01-08 23:39:23 +13:00
}
2020-09-27 01:54:24 +12:00
}
2023-05-17 05:32:48 +12:00
else if ( menu . Level ! = 0 )
2020-09-27 01:54:24 +12:00
{
2023-05-17 05:32:48 +12:00
// Close down non-visible sub menus
2022-11-26 11:31:20 +13:00
menu . Close ( ) ;
2020-09-27 01:54:24 +12:00
}
2023-05-17 05:32:48 +12:00
else
2020-09-27 01:54:24 +12:00
{
2023-05-17 05:32:48 +12:00
// Non-visible main menu, do some housekeeping
2023-04-29 04:48:39 +12:00
IconReader . ClearCacheWhenLimitReached ( ) ;
2020-09-27 01:54:24 +12:00
}
}
2023-05-16 08:34:17 +12:00
private void SystemEvents_DisplaySettingsChanged ( object? sender , EventArgs e ) = >
mainMenu ? . Dispatcher . Invoke ( ( ) = > mainMenu . RelocateOnNextShow = true ) ;
2022-11-26 11:31:20 +13:00
2020-09-27 01:54:24 +12:00
private void FadeHalfOrOutIfNeeded ( )
{
2023-05-17 06:53:01 +12:00
if ( ! App . IsActiveApp & & mainMenu ! = null & & mainMenu . Visibility = = Visibility . Visible )
2020-09-27 01:54:24 +12:00
{
2023-05-17 06:53:01 +12:00
if ( Settings . Default . StaysOpenWhenFocusLost & & IsMouseOverAnyMenu ( mainMenu ) ! = null )
2020-09-27 01:54:24 +12:00
{
2023-05-17 06:53:01 +12:00
if ( ! keyboardInput . IsSelectedByKey )
2020-09-27 01:54:24 +12:00
{
2023-05-17 06:53:01 +12:00
mainMenu . ShowWithFade ( true , true ) ;
2020-09-27 01:54:24 +12:00
}
}
2023-05-17 06:53:01 +12:00
else if ( Config . AlwaysOpenByPin )
{
mainMenu . ShowWithFade ( true , true ) ;
}
else
{
mainMenu . HideWithFade ( true ) ;
}
2020-09-27 01:54:24 +12:00
}
}
2023-04-24 09:53:20 +12:00
private void GetScreenBounds ( out Rect screenBounds , out bool useCustomLocation , out StartLocation startLocation )
2020-09-27 01:54:24 +12:00
{
2022-12-06 08:06:40 +13:00
if ( Settings . Default . AppearAtMouseLocation )
2021-11-10 06:48:30 +13:00
{
2022-10-23 11:02:31 +13:00
screenBounds = NativeMethods . Screen . FromPoint ( NativeMethods . Screen . CursorPosition ) ;
2023-04-17 06:17:24 +12:00
useCustomLocation = false ;
2021-11-10 06:48:30 +13:00
}
2022-12-06 08:06:40 +13:00
else if ( Settings . Default . UseCustomLocation )
2021-12-10 08:00:33 +13:00
{
2023-04-17 06:17:24 +12:00
screenBounds = NativeMethods . Screen . FromPoint ( new (
2022-12-06 08:06:40 +13:00
Settings . Default . CustomLocationX ,
Settings . Default . CustomLocationY ) ) ;
2021-12-28 00:22:52 +13:00
2023-04-23 07:04:34 +12:00
useCustomLocation = screenBounds . Contains (
2022-12-06 08:06:40 +13:00
new Point ( Settings . Default . CustomLocationX , Settings . Default . CustomLocationY ) ) ;
2021-12-10 08:00:33 +13:00
}
2021-11-10 06:48:30 +13:00
else
{
2022-10-23 11:02:31 +13:00
screenBounds = NativeMethods . Screen . PrimaryScreen ;
2023-04-17 06:17:24 +12:00
useCustomLocation = false ;
2021-11-10 06:48:30 +13:00
}
2020-09-27 01:54:24 +12:00
// Shrink the usable space depending on taskbar location
2023-05-16 08:34:17 +12:00
WindowsTaskbar taskbar = new ( ) ;
taskbarPosition = taskbar . Position ;
2020-09-21 03:26:45 +12:00
switch ( taskbarPosition )
{
case TaskbarPosition . Left :
screenBounds . X + = taskbar . Size . Width ;
screenBounds . Width - = taskbar . Size . Width ;
2023-04-24 09:53:20 +12:00
startLocation = StartLocation . BottomLeft ;
2020-09-21 03:26:45 +12:00
break ;
case TaskbarPosition . Right :
screenBounds . Width - = taskbar . Size . Width ;
2023-04-24 09:53:20 +12:00
startLocation = StartLocation . BottomRight ;
2020-09-21 03:26:45 +12:00
break ;
case TaskbarPosition . Top :
screenBounds . Y + = taskbar . Size . Height ;
screenBounds . Height - = taskbar . Size . Height ;
2023-04-24 09:53:20 +12:00
startLocation = StartLocation . TopRight ;
2020-09-21 03:26:45 +12:00
break ;
case TaskbarPosition . Bottom :
default :
screenBounds . Height - = taskbar . Size . Height ;
2023-04-24 09:53:20 +12:00
startLocation = StartLocation . BottomRight ;
2020-09-21 03:26:45 +12:00
break ;
2020-09-27 01:54:24 +12:00
}
2023-04-17 06:17:24 +12:00
if ( Settings . Default . AppearAtTheBottomLeft )
2021-11-10 06:31:29 +13:00
{
2023-04-24 09:53:20 +12:00
startLocation = StartLocation . BottomLeft ;
2021-11-10 06:31:29 +13:00
}
2023-04-17 06:17:24 +12:00
}
private void AdjustMenusSizeAndLocation ( int startLevel )
{
2023-04-24 09:53:20 +12:00
GetScreenBounds ( out Rect screenBounds , out bool useCustomLocation , out StartLocation startLocation ) ;
2021-11-10 06:31:29 +13:00
2023-05-08 10:14:23 +12:00
Menu ? menu = mainMenu ;
2022-12-05 13:27:57 +13:00
Menu ? menuPredecessor = null ;
2020-09-27 01:54:24 +12:00
2023-05-08 10:14:23 +12:00
while ( menu ! = null )
{
if ( startLevel < = menu . Level )
2023-04-17 06:17:24 +12:00
{
menu . AdjustSizeAndLocation ( screenBounds , menuPredecessor , startLocation , useCustomLocation ) ;
}
else
{
// Make sure further calculations of this menu access updated values (later used as predecessor)
menu . UpdateLayout ( ) ;
}
2022-11-26 11:31:20 +13:00
2022-12-06 08:06:40 +13:00
if ( ! Settings . Default . AppearAtTheBottomLeft & &
! Settings . Default . AppearAtMouseLocation & &
! Settings . Default . UseCustomLocation & &
2023-05-08 10:14:23 +12:00
menu . Level = = 0 )
2020-09-21 03:26:45 +12:00
{
2023-04-24 09:53:20 +12:00
const double overlapTolerance = 4D ;
2020-09-21 06:45:12 +12:00
2020-09-21 03:26:45 +12:00
// Remember width of the initial menu as we don't want to overlap with it
2020-09-21 06:45:12 +12:00
if ( taskbarPosition = = TaskbarPosition . Left )
{
2022-10-23 11:02:31 +13:00
screenBounds . X + = ( int ) menu . Width - overlapTolerance ;
2020-09-21 06:45:12 +12:00
}
2022-10-23 11:02:31 +13:00
screenBounds . Width - = ( int ) menu . Width - overlapTolerance ;
2020-09-27 01:54:24 +12:00
}
2022-11-26 11:31:20 +13:00
2020-09-27 01:54:24 +12:00
menuPredecessor = menu ;
2023-05-08 10:14:23 +12:00
menu = menu . SubMenu ;
2020-09-27 01:54:24 +12:00
}
}
2022-06-18 00:38:09 +12:00
private void ExecuteWatcherHistory ( )
{
foreach ( var fileSystemEventArgs in watcherHistory )
{
WatcherProcessItem ( watchers , fileSystemEventArgs ) ;
}
watcherHistory . Clear ( ) ;
}
2022-06-21 03:39:39 +12:00
private void WatcherProcessItem ( object sender , EventArgs e )
2022-06-18 00:38:09 +12:00
{
2023-05-08 10:14:23 +12:00
Menu ? menu = mainMenu ;
2023-04-16 08:19:16 +12:00
2023-05-08 10:14:23 +12:00
// Store event in history as long as menu is not loaded
if ( menu ? . Dispatcher . Invoke ( ( ) = > ! menu . IsLoaded ) ? ? true )
2022-06-18 00:38:09 +12:00
{
watcherHistory . Add ( e ) ;
return ;
}
2022-06-21 03:39:39 +12:00
if ( e is RenamedEventArgs renamedEventArgs )
{
2023-05-08 10:14:23 +12:00
menu . Dispatcher . Invoke ( ( ) = > RenameItem ( menu , renamedEventArgs ) ) ;
2022-06-21 03:39:39 +12:00
}
2022-12-05 13:27:57 +13:00
else if ( e is FileSystemEventArgs fileSystemEventArgs )
2022-06-21 03:39:39 +12:00
{
if ( fileSystemEventArgs . ChangeType = = WatcherChangeTypes . Deleted )
{
2023-05-08 10:14:23 +12:00
menu . Dispatcher . Invoke ( ( ) = > DeleteItem ( menu , fileSystemEventArgs ) ) ;
2022-06-21 03:39:39 +12:00
}
else if ( fileSystemEventArgs . ChangeType = = WatcherChangeTypes . Created )
{
2023-05-08 10:14:23 +12:00
menu . Dispatcher . Invoke ( ( ) = > CreateItem ( menu , fileSystemEventArgs ) ) ;
2022-06-21 03:39:39 +12:00
}
}
}
2023-05-08 10:14:23 +12:00
private void RenameItem ( Menu menu , RenamedEventArgs e )
2022-06-21 03:39:39 +12:00
{
try
2022-06-18 00:38:09 +12:00
{
2022-06-21 03:39:39 +12:00
List < RowData > rowDatas = new ( ) ;
2023-05-08 10:14:23 +12:00
foreach ( ListViewItemData item in menu . GetDataGridView ( ) . Items )
2022-06-21 03:39:39 +12:00
{
2023-05-08 10:14:23 +12:00
RowData rowData = item . data ;
if ( rowData . Path . StartsWith ( $"{e.OldFullPath}" ) )
2022-06-21 03:39:39 +12:00
{
2023-05-08 10:14:23 +12:00
string path = rowData . Path . Replace ( e . OldFullPath , e . FullPath ) ;
FileAttributes attr = File . GetAttributes ( path ) ;
bool isFolder = ( attr & FileAttributes . Directory ) = = FileAttributes . Directory ;
if ( isFolder )
2022-06-21 03:39:39 +12:00
{
2023-05-08 10:14:23 +12:00
string? dirpath = Path . GetDirectoryName ( path ) ;
if ( string . IsNullOrEmpty ( dirpath ) )
2022-12-05 13:27:57 +13:00
{
continue ;
}
2022-06-21 03:39:39 +12:00
2023-05-08 10:14:23 +12:00
path = dirpath ;
2022-12-05 13:27:57 +13:00
}
2023-05-08 10:14:23 +12:00
RowData rowDataRenamed = new ( isFolder , rowData . IsAdditionalItem , 0 , path ) ;
FolderOptions . ReadHiddenAttributes ( rowDataRenamed . Path , out bool hasHiddenFlag , out bool isDirectoryToHide ) ;
if ( isDirectoryToHide )
2022-06-21 03:39:39 +12:00
{
2023-05-08 10:14:23 +12:00
continue ;
2022-06-21 03:39:39 +12:00
}
2023-05-08 10:14:23 +12:00
IconReader . RemoveIconFromCache ( rowData . Path ) ;
rowDataRenamed . HiddenEntry = hasHiddenFlag ;
rowDataRenamed . ReadIcon ( true ) ;
rowDatas . Add ( rowDataRenamed ) ;
}
else
{
rowDatas . Add ( rowData ) ;
2022-06-21 03:39:39 +12:00
}
}
2023-04-17 00:17:33 +12:00
rowDatas = DirectoryHelpers . SortItems ( rowDatas ) ;
2023-05-15 05:13:44 +12:00
menu . SelectedItem = null ;
2023-05-08 10:14:23 +12:00
menu . AddItemsToMenu ( rowDatas , null , true ) ;
menu . OnWatcherUpdate ( ) ;
2022-06-18 00:38:09 +12:00
}
2022-06-21 03:39:39 +12:00
catch ( Exception ex )
2022-06-18 00:38:09 +12:00
{
2022-06-21 03:39:39 +12:00
Log . Warn ( $"Failed to {nameof(RenameItem)}: {e.OldFullPath} {e.FullPath}" , ex ) ;
2022-06-18 00:38:09 +12:00
}
}
2023-05-08 10:14:23 +12:00
private void DeleteItem ( Menu menu , FileSystemEventArgs e )
2022-06-18 00:38:09 +12:00
{
try
{
2023-05-08 10:14:23 +12:00
ListView ? dgv = menu . GetDataGridView ( ) ;
List < ListViewItemData > rowsToRemove = new ( ) ;
2022-12-05 13:27:57 +13:00
2023-05-08 10:14:23 +12:00
foreach ( ListViewItemData item in dgv . ItemsSource )
{
RowData rowData = item . data ;
if ( rowData . Path = = e . FullPath | |
rowData . Path . StartsWith ( $"{e.FullPath}\\" ) )
2022-06-18 00:38:09 +12:00
{
2023-05-08 10:14:23 +12:00
IconReader . RemoveIconFromCache ( rowData . Path ) ;
rowsToRemove . Add ( item ) ;
2022-06-18 00:38:09 +12:00
}
2023-05-08 10:14:23 +12:00
}
2022-06-18 00:38:09 +12:00
2023-05-08 10:14:23 +12:00
foreach ( ListViewItemData rowToRemove in rowsToRemove )
{
( ( IEditableCollectionView ) dgv . Items ) . Remove ( rowToRemove ) ;
2022-06-18 00:38:09 +12:00
}
2023-05-15 05:13:44 +12:00
menu . SelectedItem = null ;
2023-05-08 10:14:23 +12:00
menu . OnWatcherUpdate ( ) ;
2022-06-18 00:38:09 +12:00
}
catch ( Exception ex )
{
Log . Warn ( $"Failed to {nameof(DeleteItem)}: {e.FullPath}" , ex ) ;
}
}
2023-05-08 10:14:23 +12:00
private void CreateItem ( Menu menu , FileSystemEventArgs e )
2022-06-18 00:38:09 +12:00
{
try
{
FileAttributes attr = File . GetAttributes ( e . FullPath ) ;
bool isFolder = ( attr & FileAttributes . Directory ) = = FileAttributes . Directory ;
bool isAddionalItem = Path . GetDirectoryName ( e . FullPath ) ! = Config . Path ;
2023-04-29 04:48:39 +12:00
RowData rowData = new ( isFolder , isAddionalItem , 0 , e . FullPath ) ;
2023-04-28 07:11:20 +12:00
FolderOptions . ReadHiddenAttributes ( rowData . Path , out bool hasHiddenFlag , out bool isDirectoryToHide ) ;
if ( isDirectoryToHide )
2022-06-18 00:38:09 +12:00
{
return ;
}
2023-04-28 07:11:20 +12:00
rowData . HiddenEntry = hasHiddenFlag ;
2022-06-18 00:38:09 +12:00
rowData . ReadIcon ( true ) ;
2023-05-08 10:14:23 +12:00
var items = menu . GetDataGridView ( ) . Items ;
List < RowData > rowDatas = new ( items . Count + 1 ) { rowData } ;
foreach ( ListViewItemData item in items )
2022-06-18 00:38:09 +12:00
{
2023-05-08 10:14:23 +12:00
rowDatas . Add ( item . data ) ;
2022-06-18 00:38:09 +12:00
}
2023-04-17 00:17:33 +12:00
rowDatas = DirectoryHelpers . SortItems ( rowDatas ) ;
2023-05-15 05:13:44 +12:00
menu . SelectedItem = null ;
2023-05-08 10:14:23 +12:00
menu . AddItemsToMenu ( rowDatas , null , true ) ;
menu . OnWatcherUpdate ( ) ;
2022-06-18 00:38:09 +12:00
}
catch ( Exception ex )
{
Log . Warn ( $"Failed to {nameof(CreateItem)}: {e.FullPath}" , ex ) ;
}
}
2020-09-27 01:54:24 +12:00
}
}