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 ;
2023-05-08 10:14:23 +12:00
using System.Diagnostics.CodeAnalysis ;
2020-09-27 01:54:24 +12:00
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
{
2022-12-03 14:14:15 +13:00
private readonly Dispatcher dispatchter = Dispatcher . CurrentDispatcher ;
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 ( ) ;
2020-09-27 01:54:24 +12:00
private DateTime deactivatedTime = DateTime . MinValue ;
private OpenCloseState openCloseState = OpenCloseState . Default ;
private TaskbarPosition taskbarPosition = new WindowsTaskbar ( ) . Position ;
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-04-17 06:17:24 +12:00
keyboardInput . ClosePressed + = MenusFadeOut ;
keyboardInput . RowDeselected + = waitToOpenMenu . RowDeselected ;
keyboardInput . EnterPressed + = waitToOpenMenu . EnterOpensInstantly ;
2023-05-11 10:53:02 +12:00
keyboardInput . RowSelected + = waitToOpenMenu . RowSelectedByKey ;
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
}
waitToOpenMenu . StartLoadMenu + = StartLoadMenu ;
void StartLoadMenu ( RowData rowData )
{
2023-05-08 10:14:23 +12:00
if ( ! IsMainUsable )
{
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 )
2020-09-27 01:54:24 +12:00
{
2023-04-30 04:57:39 +12:00
Create ( new ( rowData ) , rowData . Path ) ; // Level 1+ Sub Menu (loading)
2021-10-04 07:24:22 +13:00
2022-12-06 09:46:53 +13:00
BackgroundWorker ? workerSubMenu = workersSubMenu .
2020-09-27 01:54:24 +12:00
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 ) ;
}
}
2023-05-08 10:14:23 +12:00
waitToOpenMenu . MouseEnterOk + = MouseEnterOk ;
waitToOpenMenu . CloseMenu + = ( menu ) = > HideOldMenu ( menu ) ;
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 ) = >
{
if ( IsMainUsable )
{
2023-05-08 10:14:23 +12:00
Menu ? menu = GetActiveMenu ( mainMenu ) ? ? mainMenu ;
2023-05-03 08:04:32 +12:00
menu ? . Dispatcher . Invoke ( keyboardInput . CmdKeyProcessed , new object [ ] { menu , key , modifiers } ) ;
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-08 10:14:23 +12:00
if ( ! IsActiveApp ( ) )
2020-09-27 01:54:24 +12:00
{
FadeHalfOrOutIfNeeded ( ) ;
}
}
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
private enum OpenCloseState
{
Default ,
Opening ,
Closing ,
}
2023-05-08 10:14:23 +12:00
[MemberNotNullWhen(true, nameof(mainMenu))]
private bool IsMainUsable = > mainMenu ? . IsUsable ? ? false ;
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
}
2022-12-04 13:24:30 +13:00
internal static void OpenFolder ( string? path = null )
{
if ( string . IsNullOrEmpty ( path ) )
{
path = Config . Path ;
}
Log . ProcessStart ( path ) ;
}
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-20 10:50:29 +12:00
internal void FirstStartInBackground ( )
2021-10-26 05:55:34 +13:00
{
2023-04-20 10:50:29 +12:00
dispatchter . Invoke ( ( ) = > SwitchOpenClose ( false , true ) ) ;
2021-10-26 05:55:34 +13:00
}
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 ;
2021-06-26 23:24:56 +12:00
if ( byClick & &
! Config . AlwaysOpenByPin & &
( DateTime . Now - deactivatedTime ) . TotalMilliseconds < 200 )
2020-09-27 01:54:24 +12:00
{
// By click on notifyicon the menu gets deactivated and closed
}
else if ( string . IsNullOrEmpty ( Config . Path ) )
{
// Case when Folder Dialog open
}
2023-04-17 06:17:24 +12:00
else
2020-09-27 01:54:24 +12:00
{
2023-04-17 06:17:24 +12:00
if ( openCloseState = = OpenCloseState . Opening | |
2023-05-08 10:14:23 +12:00
( ( mainMenu ? . Visibility ? ? Visibility . Collapsed ) = = Visibility . Visible & & openCloseState = = OpenCloseState . Default ) )
2020-09-27 01:54:24 +12:00
{
2023-04-17 06:17:24 +12:00
openCloseState = OpenCloseState . Closing ;
MenusFadeOut ( ) ;
StopWorker ( ) ;
2023-05-08 10:14:23 +12:00
if ( IsVisibleAnyMenu ( mainMenu ) = = null )
2023-04-17 06:17:24 +12:00
{
openCloseState = OpenCloseState . Default ;
}
}
else
{
openCloseState = OpenCloseState . Opening ;
StartWorker ( ) ;
2020-09-27 01:54:24 +12:00
}
}
deactivatedTime = DateTime . MinValue ;
}
internal void StartWorker ( )
{
2022-12-06 08:06:40 +13:00
if ( Settings . Default . GenerateShortcutsToDrives )
2022-07-10 21:40:48 +12:00
{
GenerateDriveShortcuts . Start ( ) ;
}
2020-09-27 01:54:24 +12:00
if ( ! workerMainMenu . IsBusy )
{
2022-12-06 09:46:53 +13:00
LoadStarted ? . Invoke ( ) ;
2023-04-20 10:50:29 +12:00
workerMainMenu . RunWorkerAsync ( null ) ;
2020-09-27 01:54:24 +12:00
}
}
internal void StopWorker ( )
{
if ( workerMainMenu . IsBusy )
{
workerMainMenu . CancelAsync ( ) ;
}
}
2023-05-08 10:14:23 +12:00
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 ;
}
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." ) ) ;
OpenFolder ( ) ;
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 . SetFolderByUser ( ) ;
AppRestart . ByConfigChange ( ) ;
break ;
case MenuDataDirectoryState . Undefined :
Log . Info ( $"{nameof(MenuDataDirectoryState)}.{nameof(MenuDataDirectoryState.Undefined)}" ) ;
break ;
default :
break ;
}
}
openCloseState = OpenCloseState . Default ;
}
2023-04-28 08:19:02 +12:00
private void LoadSubMenuCompleted ( object? senderCompleted , RunWorkerCompletedEventArgs e )
{
2023-05-08 10:14:23 +12:00
if ( e . Result = = null | | ! IsMainUsable )
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-05-08 10:14:23 +12:00
private bool IsActiveApp ( ) = > GetActiveMenu ( mainMenu ) ! = null | | ( App . TaskbarLogo ? . IsActive ? ? false ) ;
2022-02-28 04:44:08 +13: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
{
keyboardInput . SearchTextChanged ( menu , isSearchStringEmpty ) ;
AdjustMenusSizeAndLocation ( menu . Level + 1 ) ;
// if any open menu close
2023-05-08 10:14:23 +12:00
if ( ! causedByWatcherUpdate )
2023-04-17 06:17:24 +12:00
{
2023-05-08 10:14:23 +12:00
Menu ? menuToClose = menu . SubMenu ;
2023-04-28 09:24:25 +12:00
if ( menuToClose ! = null )
2023-04-17 06:17:24 +12:00
{
HideOldMenu ( menuToClose ) ;
}
}
}
2021-11-25 07:01:59 +13:00
menu . UserDragsMenu + = Menu_UserDragsMenu ;
2023-05-08 10:14:23 +12:00
void Menu_UserDragsMenu ( Menu mainMenu )
2021-11-25 07:01:59 +13:00
{
2023-05-08 10:14:23 +12:00
Menu ? menu = mainMenu . SubMenu ;
2023-04-17 06:17:24 +12:00
if ( menu ! = null )
2021-11-25 07:01:59 +13:00
{
2023-05-08 10:14:23 +12:00
// TODO: menus array not updated? Remove any way? (Call HideOldMenu within Menu_MouseDown direcly?)
2023-04-17 06:17:24 +12:00
HideOldMenu ( menu ) ;
2021-11-25 07:01:59 +13: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-04-20 10:50:29 +12:00
if ( openCloseState = = OpenCloseState . Opening )
2021-11-10 03:12:52 +13:00
{
Log . Info ( "Ignored Deactivate, because openCloseState == OpenCloseState.Opening" ) ;
}
2023-04-17 06:17:24 +12:00
else if ( ! Settings . Default . StaysOpenWhenFocusLostAfterEnterPressed )
2020-09-27 01:54:24 +12:00
{
2021-10-27 09:09:40 +13:00
FadeHalfOrOutIfNeeded ( ) ;
2023-05-08 10:14:23 +12:00
if ( ! IsActiveApp ( ) )
2021-10-27 09:09:40 +13:00
{
deactivatedTime = DateTime . Now ;
}
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-04-28 06:27:16 +12:00
menu . CellMouseEnter + = waitToOpenMenu . MouseEnter ;
menu . CellMouseLeave + = waitToOpenMenu . MouseLeave ;
2023-05-06 08:19:00 +12:00
menu . CellMouseDown + = ( menu , itemData ) = > MouseEnterOk ( menu , itemData ) ;
2023-05-06 04:52:05 +12:00
menu . CellOpenOnClick + = waitToOpenMenu . ClickOpensInstantly ;
2023-04-25 08:38:36 +12:00
menu . ClosePressed + = MenusFadeOut ;
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-04-17 06:17:24 +12:00
if ( IsMainUsable )
2023-04-16 08:47:29 +12:00
{
2023-05-08 10:14:23 +12:00
menu . ShowWithFade ( ! IsActiveApp ( ) , false ) ;
2023-05-09 10:19:29 +12:00
menu . RefreshSelection ( ) ;
2023-04-16 08:47:29 +12:00
}
}
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
{
if ( menu . IsUsable )
{
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
}
2022-10-23 11:02:31 +13:00
if ( menu . Visibility ! = Visibility . Visible & & menu . Level ! = 0 )
2020-09-27 01:54:24 +12:00
{
2022-11-26 11:31:20 +13:00
menu . Close ( ) ;
2020-09-27 01:54:24 +12:00
}
2023-05-08 10:14:23 +12:00
if ( IsVisibleAnyMenu ( mainMenu ) = = null )
2020-09-27 01:54:24 +12:00
{
2023-04-29 04:48:39 +12:00
IconReader . ClearCacheWhenLimitReached ( ) ;
2021-10-14 04:13:11 +13:00
openCloseState = OpenCloseState . Default ;
2020-09-27 01:54:24 +12:00
}
}
2023-05-06 08:19:00 +12:00
private void MouseEnterOk ( Menu menu , ListViewItemData itemData )
2022-06-18 00:38:09 +12:00
{
2023-04-17 06:17:24 +12:00
if ( IsMainUsable )
2022-06-18 00:38:09 +12:00
{
2023-05-06 08:19:00 +12:00
keyboardInput . MouseSelect ( menu , itemData ) ;
2021-11-24 11:08:19 +13:00
}
}
2020-09-27 01:54:24 +12:00
2022-12-06 09:46:53 +13:00
private void SystemEvents_DisplaySettingsChanged ( object? sender , EventArgs e )
2022-11-26 11:31:20 +13:00
{
2022-12-03 14:14:15 +13:00
dispatchter . Invoke ( ( ) = >
2022-11-26 11:31:20 +13:00
{
2023-04-17 06:17:24 +12:00
if ( IsMainUsable )
2022-12-03 14:14:15 +13:00
{
2023-05-08 10:14:23 +12:00
Menu ? menu = mainMenu ;
2023-04-17 06:17:24 +12:00
if ( menu ! = null )
{
2023-04-23 07:04:34 +12:00
menu . RelocateOnNextShow = true ;
2023-04-17 06:17:24 +12:00
}
2022-12-03 14:14:15 +13:00
}
} ) ;
2022-11-26 11:31:20 +13:00
}
2023-05-09 10:19:29 +12:00
private void HideOldMenu ( Menu menuToShow )
2020-09-27 01:54:24 +12:00
{
2023-05-08 10:14:23 +12:00
Menu ? menuPrevious = menuToShow . ParentMenu ;
2021-05-14 01:20:40 +12:00
if ( menuPrevious ! = null )
2020-09-27 01:54:24 +12:00
{
2023-05-08 10:14:23 +12:00
menuPrevious . SubMenu ? . HideWithFade ( true ) ;
2023-05-09 10:19:29 +12:00
menuPrevious . RefreshSelection ( ) ;
2020-09-27 01:54:24 +12:00
}
}
private void FadeHalfOrOutIfNeeded ( )
{
2023-04-17 06:17:24 +12:00
if ( IsMainUsable )
2020-09-27 01:54:24 +12:00
{
2023-05-08 10:14:23 +12:00
if ( ! IsActiveApp ( ) )
2020-09-27 01:54:24 +12:00
{
2023-05-08 10:14:23 +12:00
if ( Settings . Default . StaysOpenWhenFocusLost & & IsMouseOverAnyMenu ( mainMenu ) ! = null )
2020-09-27 01:54:24 +12:00
{
2023-05-10 10:55:18 +12:00
if ( ! keyboardInput . IsSelectedByKey )
2020-09-27 01:54:24 +12:00
{
2023-05-08 10:14:23 +12:00
mainMenu ? . ShowWithFade ( true , true ) ;
2020-09-27 01:54:24 +12:00
}
}
2021-06-26 23:24:56 +12:00
else if ( Config . AlwaysOpenByPin )
{
2023-05-08 10:14:23 +12:00
mainMenu ? . ShowWithFade ( true , true ) ;
2021-06-26 23:24:56 +12:00
}
2020-09-27 01:54:24 +12:00
else
{
MenusFadeOut ( ) ;
}
}
}
}
private void MenusFadeOut ( )
{
openCloseState = OpenCloseState . Closing ;
2023-05-08 10:14:23 +12:00
mainMenu ? . HideWithFade ( true ) ;
2021-06-26 23:24:56 +12:00
Config . AlwaysOpenByPin = false ;
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
// Only apply taskbar position change when no menu is currently open
2021-11-17 12:13:46 +13:00
WindowsTaskbar taskbar = new ( ) ;
2023-05-08 10:14:23 +12:00
if ( IsMainUsable & & mainMenu . SubMenu = = null )
2020-09-21 03:26:45 +12:00
{
taskbarPosition = taskbar . Position ;
2020-09-27 01:54:24 +12:00
}
// Shrink the usable space depending on taskbar location
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-10 10:55:18 +12:00
keyboardInput . DeselectFoccussedRow ( ) ;
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-10 10:55:18 +12:00
keyboardInput . DeselectFoccussedRow ( ) ;
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-10 10:55:18 +12:00
keyboardInput . DeselectFoccussedRow ( ) ;
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
}
}