From 49c6ef05f0a413f89611ce6af64232e92b155ef3 Mon Sep 17 00:00:00 2001 From: Markus Hofknecht Date: Sat, 26 Sep 2020 15:54:24 +0200 Subject: [PATCH] [BUG] Resolve network root sometimes not working with WARN ArgumentOutOfRangeException: Length cannot be less than zero. (Parameter 'length') #129 --- Business/Menus.cs | 1787 ++++++++++++++++---------------- Packaging/Package.appxmanifest | 2 +- Properties/AssemblyInfo.cs | 4 +- Utilities/FolderOptions.cs | 25 +- Utilities/Log.cs | 16 +- 5 files changed, 931 insertions(+), 903 deletions(-) diff --git a/Business/Menus.cs b/Business/Menus.cs index 13fb48a..2584016 100644 --- a/Business/Menus.cs +++ b/Business/Menus.cs @@ -1,874 +1,879 @@ -// -// Copyright (c) PlaceholderCompany. All rights reserved. -// - -namespace SystemTrayMenu.Business -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Data; - using System.Diagnostics; - using System.Drawing; - using System.IO; - using System.Linq; - using System.Security; - using System.Windows.Forms; - using SystemTrayMenu.DataClasses; - using SystemTrayMenu.Handler; - using SystemTrayMenu.Helper; - using SystemTrayMenu.Utilities; - using Menu = SystemTrayMenu.UserInterface.Menu; - using Timer = System.Windows.Forms.Timer; - - internal class Menus : IDisposable - { - private readonly Menu[] menus = new Menu[MenuDefines.MenusMax]; - private readonly BackgroundWorker workerMainMenu = new BackgroundWorker(); - private readonly List workersSubMenu = new List(); - - private readonly DgvMouseRow dgvMouseRow = new DgvMouseRow(); - private readonly WaitToLoadMenu waitToOpenMenu = new WaitToLoadMenu(); - private readonly KeyboardInput keyboardInput = null; - private readonly Timer timerStillActiveCheck = new Timer(); - private readonly WaitLeave waitLeave = new WaitLeave(MenuDefines.TimeUntilClose); - private DateTime deactivatedTime = DateTime.MinValue; - private OpenCloseState openCloseState = OpenCloseState.Default; - private RowData loadingRowData = null; - private bool showingMessageBox = false; - private TaskbarPosition taskbarPosition = new WindowsTaskbar().Position; - - public Menus() - { - workerMainMenu.WorkerSupportsCancellation = true; - workerMainMenu.DoWork += LoadMenu; - static void LoadMenu(object senderDoWork, DoWorkEventArgs eDoWork) - { - string path = Config.Path; - int level = 0; - RowData rowData = eDoWork.Argument as RowData; - if (rowData != null) - { - path = rowData.TargetFilePath; - level = rowData.MenuLevel + 1; - } - - MenuData menuData = GetData((BackgroundWorker)senderDoWork, path, level); - menuData.RowDataParent = rowData; - eDoWork.Result = menuData; - } - - workerMainMenu.RunWorkerCompleted += LoadMainMenuCompleted; - void LoadMainMenuCompleted(object sender, RunWorkerCompletedEventArgs e) - { - keyboardInput.ResetSelectedByKey(); - LoadStopped(); - MenuData menuData = (MenuData)e.Result; - switch (menuData.Validity) - { - case MenuDataValidity.Valid: - DisposeMenu(menus[menuData.Level]); - menus[0] = Create(menuData, Path.GetFileName(Config.Path)); - AsEnumerable.ToList().ForEach(m => { m.ShowWithFade(); }); - break; - case MenuDataValidity.Empty: - if (!showingMessageBox) - { - showingMessageBox = true; - MessageBox.Show(Translator.GetText( - "MessageRootFolderEmpty")); - OpenFolder(); - showingMessageBox = false; - } - - break; - case MenuDataValidity.NoAccess: - if (!showingMessageBox) - { - showingMessageBox = true; - MessageBox.Show(Translator.GetText( - "MessageRootFolderNoAccess")); - OpenFolder(); - showingMessageBox = false; - } - - break; - case MenuDataValidity.AbortedOrUnknown: - Log.Info("MenuDataValidity.AbortedOrUnknown"); - break; - default: - break; - } - } - - waitToOpenMenu.StopLoadMenu += WaitToOpenMenu_StopLoadMenu; - void WaitToOpenMenu_StopLoadMenu() - { - foreach (BackgroundWorker workerSubMenu in workersSubMenu. - Where(w => w.IsBusy)) - { - workerSubMenu.CancelAsync(); - } - - LoadStopped(); - } - - waitToOpenMenu.StartLoadMenu += StartLoadMenu; - void StartLoadMenu(RowData rowData) - { - if (menus[0].IsUsable && - loadingRowData != rowData && - (menus[rowData.MenuLevel + 1] == null || - menus[rowData.MenuLevel + 1].Tag as RowData != rowData)) - { - loadingRowData = rowData; - LoadStarted(); - 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); - } - - void LoadSubMenuCompleted(object senderCompleted, RunWorkerCompletedEventArgs e) - { - LoadStopped(); - MenuData menuData = (MenuData)e.Result; - if (menus[0].IsUsable && - menuData.Validity != MenuDataValidity.AbortedOrUnknown) - { - Menu menu = Create(menuData); - switch (menuData.Validity) - { - case MenuDataValidity.Valid: - menu.SetTypeSub(); - break; - case MenuDataValidity.Empty: - menu.SetTypeEmpty(); - break; - case MenuDataValidity.NoAccess: - menu.SetTypeNoAccess(); - break; - } - - menu.Tag = menuData.RowDataParent; - menuData.RowDataParent.SubMenu = menu; - if (menus[0].IsUsable) - { - ShowSubMenu(menu); - } - } - - loadingRowData = null; - } - } - - waitToOpenMenu.CloseMenu += CloseMenu; - void CloseMenu(int level) - { - if (menus[level] != null) - { - menus[level - 1].FocusTextBox(); - HideOldMenu(menus[level]); - } - } - - waitToOpenMenu.MouseEnterOk += MouseEnterOk; - void MouseEnterOk(DataGridView dgv, int rowIndex) - { - if (menus[0].IsUsable) - { - if (keyboardInput.InUse) - { - keyboardInput.ClearIsSelectedByKey(); - keyboardInput.InUse = false; - } - - keyboardInput.Select(dgv, rowIndex, false); - } - } - - dgvMouseRow.RowMouseEnter += waitToOpenMenu.MouseEnter; - dgvMouseRow.RowMouseLeave += waitToOpenMenu.MouseLeave; - - keyboardInput = new KeyboardInput(menus); - keyboardInput.RegisterHotKey(); - keyboardInput.HotKeyPressed += KeyboardInput_HotKeyPressed; - void KeyboardInput_HotKeyPressed() - { - SwitchOpenClose(false); - } - - keyboardInput.ClosePressed += MenusFadeOut; - keyboardInput.RowDeselected += waitToOpenMenu.RowDeselected; - keyboardInput.RowSelected += waitToOpenMenu.RowSelected; - keyboardInput.EnterPressed += waitToOpenMenu.EnterOpensInstantly; - - timerStillActiveCheck.Interval = 1000; - timerStillActiveCheck.Tick += StillActiveTick; - void StillActiveTick(object senderTimer, EventArgs eTimer) - { - if (!IsActive()) - { - FadeHalfOrOutIfNeeded(); - timerStillActiveCheck.Stop(); - } - } - - waitLeave.LeaveTriggered += LeaveTriggered; - void LeaveTriggered() - { - FadeHalfOrOutIfNeeded(); - } - } - - internal event EventHandlerEmpty LoadStarted; - - internal event EventHandlerEmpty LoadStopped; - - private enum OpenCloseState - { - Default, - Opening, - Closing, - } - - private IEnumerable AsEnumerable => menus.Where(m => m != null && !m.IsDisposed); - - private List AsList => AsEnumerable.ToList(); - - public void Dispose() - { - workerMainMenu.Dispose(); - foreach (BackgroundWorker worker in workersSubMenu) - { - worker.Dispose(); - } - - waitToOpenMenu.Dispose(); - keyboardInput.Dispose(); - timerStillActiveCheck.Dispose(); - waitLeave.Dispose(); - IconReader.Dispose(); - DisposeMenu(menus[0]); - loadingRowData?.Dispose(); - dgvMouseRow.Dispose(); - } - - internal static MenuData GetData(BackgroundWorker worker, string path, int level) - { - MenuData menuData = new MenuData - { - RowDatas = new List(), - Validity = MenuDataValidity.AbortedOrUnknown, - Level = level, - }; - if (!worker.CancellationPending) - { - string[] directories = Array.Empty(); - - try - { - if (FileLnk.IsNetworkRoot(path)) - { - directories = GetDirectoriesInNetworkLocation(path); - static string[] GetDirectoriesInNetworkLocation(string networkLocationRootPath) - { - List directories = new List(); - try - { - Process cmd = new Process(); - cmd.StartInfo.FileName = "cmd.exe"; - cmd.StartInfo.RedirectStandardInput = true; - cmd.StartInfo.RedirectStandardOutput = true; - cmd.StartInfo.CreateNoWindow = true; - cmd.StartInfo.UseShellExecute = false; - cmd.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; - cmd.Start(); - cmd.StandardInput.WriteLine($"net view {networkLocationRootPath}"); - cmd.StandardInput.Flush(); - cmd.StandardInput.Close(); - - string output = cmd.StandardOutput.ReadToEnd(); - - cmd.WaitForExit(); - cmd.Close(); - - output = output.Substring(output.LastIndexOf('-') + 2); - output = output.Substring(0, output.IndexOf("The command completed successfully.", StringComparison.InvariantCulture)); - - foreach (string line in output - .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) - { - int indexOfSecondColumnTypeDisk = line.IndexOf("Disk", StringComparison.InvariantCulture); - if (indexOfSecondColumnTypeDisk > 0) - { - string directory = Path.Combine( - networkLocationRootPath, - line.Substring(0, indexOfSecondColumnTypeDisk)); - directories.Add(directory); - } - } - } - catch (ArgumentOutOfRangeException ex) - { - Log.Warn($"Could not resolve network root folder: {networkLocationRootPath}", ex); - } - - return directories.ToArray(); - } - } - else - { - directories = Directory.GetDirectories(path); - } - - Array.Sort(directories, new WindowsExplorerSort()); - } - catch (UnauthorizedAccessException ex) - { - Log.Warn($"path:'{path}'", ex); - menuData.Validity = MenuDataValidity.NoAccess; - } - catch (IOException ex) - { - Log.Warn($"path:'{path}'", ex); - } - - foreach (string directory in directories) - { - if (worker != null && worker.CancellationPending) - { - break; - } - - bool hiddenEntry = false; - if (FolderOptions.IsHidden(directory, ref hiddenEntry)) - { - continue; - } - - RowData rowData = ReadRowData(directory, false); - rowData.ContainsMenu = true; - rowData.HiddenEntry = hiddenEntry; - string resolvedLnkPath = string.Empty; - rowData.ReadIcon(true, ref resolvedLnkPath); - rowData.MenuLevel = level; - menuData.RowDatas.Add(rowData); - } - } - - if (!worker.CancellationPending) - { - string[] files = Array.Empty(); - - try - { - if (!FileLnk.IsNetworkRoot(path)) - { - files = Directory.GetFiles(path); - } - - Array.Sort(files, new WindowsExplorerSort()); - } - catch (UnauthorizedAccessException ex) - { - Log.Warn($"path:'{path}'", ex); - menuData.Validity = MenuDataValidity.NoAccess; - } - catch (IOException ex) - { - Log.Warn($"path:'{path}'", ex); - } - - foreach (string file in files) - { - if (worker != null && worker.CancellationPending) - { - break; - } - - bool hiddenEntry = false; - if (FolderOptions.IsHidden(file, ref hiddenEntry)) - { - continue; - } - - RowData rowData = ReadRowData(file, false); - string resolvedLnkPath = string.Empty; - if (rowData.ReadIcon(false, ref resolvedLnkPath)) - { - rowData = ReadRowData(resolvedLnkPath, true, rowData); - rowData.ContainsMenu = true; - rowData.HiddenEntry = hiddenEntry; - } - - menuData.RowDatas.Add(rowData); - } - } - - if (!worker.CancellationPending) - { - if (menuData.Validity == MenuDataValidity.AbortedOrUnknown) - { - if (menuData.RowDatas.Count == 0) - { - menuData.Validity = MenuDataValidity.Empty; - } - else - { - menuData.Validity = MenuDataValidity.Valid; - } - } - } - - return menuData; - } - - internal void SwitchOpenCloseByTaskbarItem() - { - SwitchOpenClose(true); - timerStillActiveCheck.Start(); - } - - internal void SwitchOpenClose(bool byClick) - { - waitToOpenMenu.MouseActive = byClick; - if (byClick && (DateTime.Now - deactivatedTime).TotalMilliseconds < 200) - { - // By click on notifyicon the menu gets deactivated and closed - } - else if (string.IsNullOrEmpty(Config.Path)) - { - // Case when Folder Dialog open - } - else if (openCloseState == OpenCloseState.Opening || - (menus[0] != null && menus[0].Visible && openCloseState == OpenCloseState.Default)) - { - openCloseState = OpenCloseState.Closing; - MenusFadeOut(); - StopWorker(); - if (!AsEnumerable.Any(m => m.Visible)) - { - openCloseState = OpenCloseState.Default; - } - } - else - { - openCloseState = OpenCloseState.Opening; - StartWorker(); - } - - deactivatedTime = DateTime.MinValue; - } - - internal void DisposeMenu(Menu menuToDispose) - { - if (menuToDispose != null) - { - menuToDispose.MouseWheel -= AdjustMenusSizeAndLocation; - menuToDispose.MouseLeave -= waitLeave.Start; - menuToDispose.MouseEnter -= waitLeave.Stop; - menuToDispose.KeyPress -= keyboardInput.KeyPress; - menuToDispose.CmdKeyProcessed -= keyboardInput.CmdKeyProcessed; - menuToDispose.SearchTextChanging -= keyboardInput.SearchTextChanging; - menuToDispose.SearchTextChanged -= Menu_SearchTextChanged; - DataGridView dgv = menuToDispose.GetDataGridView(); - dgv.CellMouseEnter -= dgvMouseRow.CellMouseEnter; - dgv.CellMouseLeave -= dgvMouseRow.CellMouseLeave; - dgv.MouseLeave -= dgvMouseRow.MouseLeave; - dgv.MouseMove -= waitToOpenMenu.MouseMove; - dgv.MouseDown -= Dgv_MouseDown; - dgv.MouseDoubleClick -= Dgv_MouseDoubleClick; - dgv.SelectionChanged -= Dgv_SelectionChanged; - dgv.RowPostPaint -= Dgv_RowPostPaint; - dgv.ClearSelection(); - - foreach (DataGridViewRow row in dgv.Rows) - { - RowData rowData = (RowData)row.Cells[2].Value; - rowData.Dispose(); - DisposeMenu(rowData.SubMenu); - } - - menuToDispose.Dispose(); - } - } - - internal void MainPreload() - { - menus[0] = Create( - GetData(workerMainMenu, Config.Path, 0), - Path.GetFileName(Config.Path)); - AdjustMenusSizeAndLocation(); - DisposeMenu(menus[0]); - } - - internal void StartWorker() - { - if (!workerMainMenu.IsBusy) - { - LoadStarted(); - workerMainMenu.RunWorkerAsync( - new object[] { Config.Path, 0 }); - } - } - - internal void StopWorker() - { - if (workerMainMenu.IsBusy) - { - workerMainMenu.CancelAsync(); - } - } - - private static RowData ReadRowData(string fileName, bool isResolvedLnk, RowData rowData = null) - { - if (rowData == null) - { - rowData = new RowData(); - } - - rowData.IsResolvedLnk = isResolvedLnk; - - try - { - rowData.FileInfo = new FileInfo(fileName); - rowData.TargetFilePath = rowData.FileInfo.FullName; - if (!isResolvedLnk) - { - if (string.IsNullOrEmpty(rowData.FileInfo.Name)) - { - string path = rowData.FileInfo.FullName; - int directoryNameBegin = path.LastIndexOf(@"\", StringComparison.InvariantCulture) + 1; - rowData.SetText(path.Substring(directoryNameBegin)); - } - else - { - rowData.SetText(rowData.FileInfo.Name); - } - - rowData.TargetFilePathOrig = rowData.FileInfo.FullName; - } - } - catch (Exception ex) - { - if (ex is SecurityException || - ex is ArgumentException || - ex is UnauthorizedAccessException || - ex is PathTooLongException || - ex is NotSupportedException) - { - Log.Warn($"fileName:'{fileName}'", ex); - } - else - { - throw; - } - } - - return rowData; - } - - private static bool IsActive() - { - return Form.ActiveForm is Menu || - Form.ActiveForm is UserInterface.TaskbarForm; - } - - private static void OpenFolder() - { - Log.ProcessStart(Config.Path, null, true); - } - - private Menu Create(MenuData menuData, string title = null) - { - Menu menu = new Menu(); - - if (title != null) - { - if (string.IsNullOrEmpty(title)) - { - title = Path.GetPathRoot(Config.Path); - } - - menu.SetTitle(title); - menu.UserClickedOpenFolder += OpenFolder; - } - - menu.Level = menuData.Level; - menu.MouseWheel += AdjustMenusSizeAndLocation; - menu.MouseLeave += waitLeave.Start; - menu.MouseEnter += waitLeave.Stop; - menu.KeyPress += keyboardInput.KeyPress; - menu.CmdKeyProcessed += keyboardInput.CmdKeyProcessed; - menu.SearchTextChanging += keyboardInput.SearchTextChanging; - menu.SearchTextChanged += Menu_SearchTextChanged; - menu.Deactivate += Deactivate; - void Deactivate(object sender, EventArgs e) - { - FadeHalfOrOutIfNeeded(); - if (!IsActive()) - { - deactivatedTime = DateTime.Now; - } - } - - menu.Activated += Activated; - void Activated(object sender, EventArgs e) - { - if (IsActive() && - menus[0].IsUsable) - { - AsList.ForEach(m => m.ShowWithFade()); - timerStillActiveCheck.Start(); - } - } - - menu.VisibleChanged += MenuVisibleChanged; - AddItemsToMenu(menuData.RowDatas, menu); - static void AddItemsToMenu(List data, Menu menu) - { - DataGridView dgv = menu.GetDataGridView(); - DataTable dataTable = new DataTable(); - dataTable.Columns.Add(dgv.Columns[0].Name, typeof(Icon)); - dataTable.Columns.Add(dgv.Columns[1].Name, typeof(string)); - dataTable.Columns.Add("data", typeof(RowData)); - foreach (RowData rowData in data) - { - rowData.SetData(rowData, dataTable); - } - - dgv.DataSource = dataTable; - } - - DataGridView dgv = menu.GetDataGridView(); - dgv.CellMouseEnter += dgvMouseRow.CellMouseEnter; - dgv.CellMouseLeave += dgvMouseRow.CellMouseLeave; - dgv.MouseLeave += dgvMouseRow.MouseLeave; - dgv.MouseMove += waitToOpenMenu.MouseMove; - dgv.MouseDown += Dgv_MouseDown; - dgv.MouseDoubleClick += Dgv_MouseDoubleClick; - dgv.SelectionChanged += Dgv_SelectionChanged; - dgv.RowPostPaint += Dgv_RowPostPaint; - - return menu; - } - - private void MenuVisibleChanged(object sender, EventArgs e) - { - Menu menu = (Menu)sender; - if (menu.IsUsable) - { - AdjustMenusSizeAndLocation(); - } - - if (!menu.Visible) - { - DisposeMenu(menu); - } - - if (!AsEnumerable.Any(m => m.Visible)) - { - openCloseState = OpenCloseState.Default; - } - } - - private void Dgv_MouseDown(object sender, MouseEventArgs e) - { - DataGridView dgv = (DataGridView)sender; - DataGridView.HitTestInfo hitTestInfo; - hitTestInfo = dgv.HitTest(e.X, e.Y); - if (hitTestInfo.RowIndex > -1 && - dgv.Rows.Count > hitTestInfo.RowIndex) - { - RowData rowData = (RowData)dgv.Rows[hitTestInfo.RowIndex].Cells[2].Value; - rowData.MouseDown(dgv, e); - waitToOpenMenu.ClickOpensInstantly(dgv, hitTestInfo.RowIndex); - } - } - - private void Dgv_MouseDoubleClick(object sender, MouseEventArgs e) - { - DataGridView dgv = (DataGridView)sender; - DataGridView.HitTestInfo hitTestInfo; - hitTestInfo = dgv.HitTest(e.X, e.Y); - if (hitTestInfo.RowIndex > -1 && - dgv.Rows.Count > hitTestInfo.RowIndex) - { - RowData trigger = (RowData)dgv.Rows[hitTestInfo.RowIndex].Cells[2].Value; - trigger.DoubleClick(e); - } - } - - private void Dgv_SelectionChanged(object sender, EventArgs e) - { - RefreshSelection((DataGridView)sender); - } - - private void RefreshSelection(DataGridView dgv) - { - foreach (DataGridViewRow row in dgv.Rows) - { - RowData rowData = (RowData)row.Cells[2].Value; - - if (rowData == null) - { - // Case when filtering a previous menu - } - else if (!menus[0].IsUsable) - { - row.DefaultCellStyle.SelectionBackColor = Color.White; - row.Selected = false; - } - else if (rowData.IsContextMenuOpen || (rowData.IsMenuOpen && rowData.IsSelected)) - { - row.Selected = true; - } - else if (rowData.IsMenuOpen) - { - row.Selected = true; - } - else if (rowData.IsSelected) - { - row.DefaultCellStyle.SelectionBackColor = MenuDefines.ColorSelectedItem; - row.Selected = true; - } - else - { - row.DefaultCellStyle.SelectionBackColor = Color.White; - row.Selected = false; - } - } - - dgv.Refresh(); - } - - private void Dgv_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e) - { - DataGridView dgv = (DataGridView)sender; - DataGridViewRow row = dgv.Rows[e.RowIndex]; - - if (row.Selected) - { - RowData rowData = (RowData)row.Cells[2].Value; - - int width = dgv.Columns[0].Width + dgv.Columns[1].Width; - Rectangle rowBounds = new Rectangle(0, e.RowBounds.Top, width, e.RowBounds.Height); - - if (rowData.IsContextMenuOpen || (rowData.IsMenuOpen && rowData.IsSelected)) - { - ControlPaint.DrawBorder(e.Graphics, rowBounds, MenuDefines.ColorSelectedItemBorder, ButtonBorderStyle.Solid); - row.DefaultCellStyle.SelectionBackColor = MenuDefines.ColorSelectedItem; - } - else if (rowData.IsMenuOpen) - { - ControlPaint.DrawBorder(e.Graphics, rowBounds, MenuDefines.ColorOpenFolderBorder, ButtonBorderStyle.Solid); - row.DefaultCellStyle.SelectionBackColor = MenuDefines.ColorOpenFolder; - } - } - } - - private void ShowSubMenu(Menu menuToShow) - { - HideOldMenu(menuToShow, true); - - menus[menuToShow.Level] = menuToShow; - AdjustMenusSizeAndLocation(); - menus[menuToShow.Level].ShowWithFadeOrTransparent(IsActive()); - } - - private void HideOldMenu(Menu menuToShow, bool keepOrSetIsMenuOpen = false) - { - // Clean up menu status IsMenuOpen for previous one - Menu menuPrevious = menus[menuToShow.Level - 1]; - DataGridView dgvPrevious = menuPrevious.GetDataGridView(); - foreach (DataRow row in ((DataTable)dgvPrevious.DataSource).Rows) - { - RowData rowDataToClear = (RowData)row[2]; - if (rowDataToClear == (RowData)menuToShow.Tag) - { - rowDataToClear.IsMenuOpen = keepOrSetIsMenuOpen; - } - else - { - rowDataToClear.IsMenuOpen = false; - } - } - - RefreshSelection(dgvPrevious); - - // Hide old menu - foreach (Menu menuToClose in menus.Where( - m => m != null && m.Level > menuPrevious.Level)) - { - menuToClose.VisibleChanged += MenuVisibleChanged; - menuToClose.HideWithFade(); - menus[menuToClose.Level] = null; - } - } - - private void FadeHalfOrOutIfNeeded() - { - if (menus[0].IsUsable) - { - if (!IsActive()) - { - Point position = Control.MousePosition; - if (AsList.Any(m => m.IsMouseOn(position))) - { - if (!keyboardInput.InUse) - { - AsList.ForEach(menu => menu.ShowTransparent()); - } - } - else - { - MenusFadeOut(); - } - } - } - } - - private void MenusFadeOut() - { - openCloseState = OpenCloseState.Closing; - AsList.ForEach(menu => - { - if (menu.Level > 0) - { - menus[menu.Level] = null; - } - - menu.HideWithFade(); - }); - } - - private void AdjustMenusSizeAndLocation() - { - WindowsTaskbar taskbar = new WindowsTaskbar(); - Menu menuPredecessor = null; - List list = AsList; - Menu menu; - Rectangle screenBounds = Screen.PrimaryScreen.Bounds; - Menu.StartLocation startLocation; - - // Only apply taskbar position change when no menu is currently open +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace SystemTrayMenu.Business +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Data; + using System.Diagnostics; + using System.Drawing; + using System.IO; + using System.Linq; + using System.Security; + using System.Windows.Forms; + using SystemTrayMenu.DataClasses; + using SystemTrayMenu.Handler; + using SystemTrayMenu.Helper; + using SystemTrayMenu.Utilities; + using Menu = SystemTrayMenu.UserInterface.Menu; + using Timer = System.Windows.Forms.Timer; + + internal class Menus : IDisposable + { + private readonly Menu[] menus = new Menu[MenuDefines.MenusMax]; + private readonly BackgroundWorker workerMainMenu = new BackgroundWorker(); + private readonly List workersSubMenu = new List(); + + private readonly DgvMouseRow dgvMouseRow = new DgvMouseRow(); + private readonly WaitToLoadMenu waitToOpenMenu = new WaitToLoadMenu(); + private readonly KeyboardInput keyboardInput = null; + private readonly Timer timerStillActiveCheck = new Timer(); + private readonly WaitLeave waitLeave = new WaitLeave(MenuDefines.TimeUntilClose); + private DateTime deactivatedTime = DateTime.MinValue; + private OpenCloseState openCloseState = OpenCloseState.Default; + private RowData loadingRowData = null; + private bool showingMessageBox = false; + private TaskbarPosition taskbarPosition = new WindowsTaskbar().Position; + + public Menus() + { + workerMainMenu.WorkerSupportsCancellation = true; + workerMainMenu.DoWork += LoadMenu; + static void LoadMenu(object senderDoWork, DoWorkEventArgs eDoWork) + { + string path = Config.Path; + int level = 0; + RowData rowData = eDoWork.Argument as RowData; + if (rowData != null) + { + path = rowData.TargetFilePath; + level = rowData.MenuLevel + 1; + } + + MenuData menuData = GetData((BackgroundWorker)senderDoWork, path, level); + menuData.RowDataParent = rowData; + eDoWork.Result = menuData; + } + + workerMainMenu.RunWorkerCompleted += LoadMainMenuCompleted; + void LoadMainMenuCompleted(object sender, RunWorkerCompletedEventArgs e) + { + keyboardInput.ResetSelectedByKey(); + LoadStopped(); + MenuData menuData = (MenuData)e.Result; + switch (menuData.Validity) + { + case MenuDataValidity.Valid: + DisposeMenu(menus[menuData.Level]); + menus[0] = Create(menuData, Path.GetFileName(Config.Path)); + AsEnumerable.ToList().ForEach(m => { m.ShowWithFade(); }); + break; + case MenuDataValidity.Empty: + if (!showingMessageBox) + { + showingMessageBox = true; + MessageBox.Show(Translator.GetText( + "MessageRootFolderEmpty")); + OpenFolder(); + showingMessageBox = false; + } + + break; + case MenuDataValidity.NoAccess: + if (!showingMessageBox) + { + showingMessageBox = true; + MessageBox.Show(Translator.GetText( + "MessageRootFolderNoAccess")); + OpenFolder(); + showingMessageBox = false; + } + + break; + case MenuDataValidity.AbortedOrUnknown: + Log.Info("MenuDataValidity.AbortedOrUnknown"); + break; + default: + break; + } + } + + waitToOpenMenu.StopLoadMenu += WaitToOpenMenu_StopLoadMenu; + void WaitToOpenMenu_StopLoadMenu() + { + foreach (BackgroundWorker workerSubMenu in workersSubMenu. + Where(w => w.IsBusy)) + { + workerSubMenu.CancelAsync(); + } + + LoadStopped(); + } + + waitToOpenMenu.StartLoadMenu += StartLoadMenu; + void StartLoadMenu(RowData rowData) + { + if (menus[0].IsUsable && + loadingRowData != rowData && + (menus[rowData.MenuLevel + 1] == null || + menus[rowData.MenuLevel + 1].Tag as RowData != rowData)) + { + loadingRowData = rowData; + LoadStarted(); + 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); + } + + void LoadSubMenuCompleted(object senderCompleted, RunWorkerCompletedEventArgs e) + { + LoadStopped(); + MenuData menuData = (MenuData)e.Result; + if (menus[0].IsUsable && + menuData.Validity != MenuDataValidity.AbortedOrUnknown) + { + Menu menu = Create(menuData); + switch (menuData.Validity) + { + case MenuDataValidity.Valid: + menu.SetTypeSub(); + break; + case MenuDataValidity.Empty: + menu.SetTypeEmpty(); + break; + case MenuDataValidity.NoAccess: + menu.SetTypeNoAccess(); + break; + } + + menu.Tag = menuData.RowDataParent; + menuData.RowDataParent.SubMenu = menu; + if (menus[0].IsUsable) + { + ShowSubMenu(menu); + } + } + + loadingRowData = null; + } + } + + waitToOpenMenu.CloseMenu += CloseMenu; + void CloseMenu(int level) + { + if (menus[level] != null) + { + menus[level - 1].FocusTextBox(); + HideOldMenu(menus[level]); + } + } + + waitToOpenMenu.MouseEnterOk += MouseEnterOk; + void MouseEnterOk(DataGridView dgv, int rowIndex) + { + if (menus[0].IsUsable) + { + if (keyboardInput.InUse) + { + keyboardInput.ClearIsSelectedByKey(); + keyboardInput.InUse = false; + } + + keyboardInput.Select(dgv, rowIndex, false); + } + } + + dgvMouseRow.RowMouseEnter += waitToOpenMenu.MouseEnter; + dgvMouseRow.RowMouseLeave += waitToOpenMenu.MouseLeave; + + keyboardInput = new KeyboardInput(menus); + keyboardInput.RegisterHotKey(); + keyboardInput.HotKeyPressed += KeyboardInput_HotKeyPressed; + void KeyboardInput_HotKeyPressed() + { + SwitchOpenClose(false); + } + + keyboardInput.ClosePressed += MenusFadeOut; + keyboardInput.RowDeselected += waitToOpenMenu.RowDeselected; + keyboardInput.RowSelected += waitToOpenMenu.RowSelected; + keyboardInput.EnterPressed += waitToOpenMenu.EnterOpensInstantly; + + timerStillActiveCheck.Interval = 1000; + timerStillActiveCheck.Tick += StillActiveTick; + void StillActiveTick(object senderTimer, EventArgs eTimer) + { + if (!IsActive()) + { + FadeHalfOrOutIfNeeded(); + timerStillActiveCheck.Stop(); + } + } + + waitLeave.LeaveTriggered += LeaveTriggered; + void LeaveTriggered() + { + FadeHalfOrOutIfNeeded(); + } + } + + internal event EventHandlerEmpty LoadStarted; + + internal event EventHandlerEmpty LoadStopped; + + private enum OpenCloseState + { + Default, + Opening, + Closing, + } + + private IEnumerable AsEnumerable => menus.Where(m => m != null && !m.IsDisposed); + + private List AsList => AsEnumerable.ToList(); + + public void Dispose() + { + workerMainMenu.Dispose(); + foreach (BackgroundWorker worker in workersSubMenu) + { + worker.Dispose(); + } + + waitToOpenMenu.Dispose(); + keyboardInput.Dispose(); + timerStillActiveCheck.Dispose(); + waitLeave.Dispose(); + IconReader.Dispose(); + DisposeMenu(menus[0]); + loadingRowData?.Dispose(); + dgvMouseRow.Dispose(); + } + + internal static MenuData GetData(BackgroundWorker worker, string path, int level) + { + MenuData menuData = new MenuData + { + RowDatas = new List(), + Validity = MenuDataValidity.AbortedOrUnknown, + Level = level, + }; + if (!worker.CancellationPending) + { + string[] directories = Array.Empty(); + + try + { + if (FileLnk.IsNetworkRoot(path)) + { + directories = GetDirectoriesInNetworkLocation(path); + static string[] GetDirectoriesInNetworkLocation(string networkLocationRootPath) + { + List directories = new List(); + Process cmd = new Process(); + cmd.StartInfo.FileName = "cmd.exe"; + cmd.StartInfo.RedirectStandardInput = true; + cmd.StartInfo.RedirectStandardOutput = true; + cmd.StartInfo.CreateNoWindow = true; + cmd.StartInfo.UseShellExecute = false; + cmd.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + cmd.Start(); + cmd.StandardInput.WriteLine($"net view {networkLocationRootPath}"); + cmd.StandardInput.Flush(); + cmd.StandardInput.Close(); + + string output = cmd.StandardOutput.ReadToEnd(); + + cmd.WaitForExit(); + cmd.Close(); + + bool resolvedSomething = false; + + List lines = output + .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + if (lines.Count > 8) + { + foreach (string line in lines.Skip(6).SkipLast(2)) + { + int indexOfFirstSpace = line.IndexOf(" ", StringComparison.InvariantCulture); + if (indexOfFirstSpace > 0) + { + string directory = Path.Combine( + networkLocationRootPath, + line.Substring(0, indexOfFirstSpace)); + if (Directory.Exists(directory)) + { + directories.Add(directory); + resolvedSomething = true; + } + } + } + } + + if (!resolvedSomething) + { + Log.Info($"Could not resolve network root folder: {networkLocationRootPath} , output:{output}"); + } + + return directories.ToArray(); + } + } + else + { + directories = Directory.GetDirectories(path); + } + + Array.Sort(directories, new WindowsExplorerSort()); + } + catch (UnauthorizedAccessException ex) + { + Log.Warn($"path:'{path}'", ex); + menuData.Validity = MenuDataValidity.NoAccess; + } + catch (IOException ex) + { + Log.Warn($"path:'{path}'", ex); + } + + foreach (string directory in directories) + { + if (worker != null && worker.CancellationPending) + { + break; + } + + bool hiddenEntry = false; + if (FolderOptions.IsHidden(directory, ref hiddenEntry)) + { + continue; + } + + RowData rowData = ReadRowData(directory, false); + rowData.ContainsMenu = true; + rowData.HiddenEntry = hiddenEntry; + string resolvedLnkPath = string.Empty; + rowData.ReadIcon(true, ref resolvedLnkPath); + rowData.MenuLevel = level; + menuData.RowDatas.Add(rowData); + } + } + + if (!worker.CancellationPending) + { + string[] files = Array.Empty(); + + try + { + if (!FileLnk.IsNetworkRoot(path)) + { + files = Directory.GetFiles(path); + } + + Array.Sort(files, new WindowsExplorerSort()); + } + catch (UnauthorizedAccessException ex) + { + Log.Warn($"path:'{path}'", ex); + menuData.Validity = MenuDataValidity.NoAccess; + } + catch (IOException ex) + { + Log.Warn($"path:'{path}'", ex); + } + + foreach (string file in files) + { + if (worker != null && worker.CancellationPending) + { + break; + } + + bool hiddenEntry = false; + if (FolderOptions.IsHidden(file, ref hiddenEntry)) + { + continue; + } + + RowData rowData = ReadRowData(file, false); + string resolvedLnkPath = string.Empty; + if (rowData.ReadIcon(false, ref resolvedLnkPath)) + { + rowData = ReadRowData(resolvedLnkPath, true, rowData); + rowData.ContainsMenu = true; + rowData.HiddenEntry = hiddenEntry; + } + + menuData.RowDatas.Add(rowData); + } + } + + if (!worker.CancellationPending) + { + if (menuData.Validity == MenuDataValidity.AbortedOrUnknown) + { + if (menuData.RowDatas.Count == 0) + { + menuData.Validity = MenuDataValidity.Empty; + } + else + { + menuData.Validity = MenuDataValidity.Valid; + } + } + } + + return menuData; + } + + internal void SwitchOpenCloseByTaskbarItem() + { + SwitchOpenClose(true); + timerStillActiveCheck.Start(); + } + + internal void SwitchOpenClose(bool byClick) + { + waitToOpenMenu.MouseActive = byClick; + if (byClick && (DateTime.Now - deactivatedTime).TotalMilliseconds < 200) + { + // By click on notifyicon the menu gets deactivated and closed + } + else if (string.IsNullOrEmpty(Config.Path)) + { + // Case when Folder Dialog open + } + else if (openCloseState == OpenCloseState.Opening || + (menus[0] != null && menus[0].Visible && openCloseState == OpenCloseState.Default)) + { + openCloseState = OpenCloseState.Closing; + MenusFadeOut(); + StopWorker(); + if (!AsEnumerable.Any(m => m.Visible)) + { + openCloseState = OpenCloseState.Default; + } + } + else + { + openCloseState = OpenCloseState.Opening; + StartWorker(); + } + + deactivatedTime = DateTime.MinValue; + } + + internal void DisposeMenu(Menu menuToDispose) + { + if (menuToDispose != null) + { + menuToDispose.MouseWheel -= AdjustMenusSizeAndLocation; + menuToDispose.MouseLeave -= waitLeave.Start; + menuToDispose.MouseEnter -= waitLeave.Stop; + menuToDispose.KeyPress -= keyboardInput.KeyPress; + menuToDispose.CmdKeyProcessed -= keyboardInput.CmdKeyProcessed; + menuToDispose.SearchTextChanging -= keyboardInput.SearchTextChanging; + menuToDispose.SearchTextChanged -= Menu_SearchTextChanged; + DataGridView dgv = menuToDispose.GetDataGridView(); + dgv.CellMouseEnter -= dgvMouseRow.CellMouseEnter; + dgv.CellMouseLeave -= dgvMouseRow.CellMouseLeave; + dgv.MouseLeave -= dgvMouseRow.MouseLeave; + dgv.MouseMove -= waitToOpenMenu.MouseMove; + dgv.MouseDown -= Dgv_MouseDown; + dgv.MouseDoubleClick -= Dgv_MouseDoubleClick; + dgv.SelectionChanged -= Dgv_SelectionChanged; + dgv.RowPostPaint -= Dgv_RowPostPaint; + dgv.ClearSelection(); + + foreach (DataGridViewRow row in dgv.Rows) + { + RowData rowData = (RowData)row.Cells[2].Value; + rowData.Dispose(); + DisposeMenu(rowData.SubMenu); + } + + menuToDispose.Dispose(); + } + } + + internal void MainPreload() + { + menus[0] = Create( + GetData(workerMainMenu, Config.Path, 0), + Path.GetFileName(Config.Path)); + AdjustMenusSizeAndLocation(); + DisposeMenu(menus[0]); + } + + internal void StartWorker() + { + if (!workerMainMenu.IsBusy) + { + LoadStarted(); + workerMainMenu.RunWorkerAsync( + new object[] { Config.Path, 0 }); + } + } + + internal void StopWorker() + { + if (workerMainMenu.IsBusy) + { + workerMainMenu.CancelAsync(); + } + } + + private static RowData ReadRowData(string fileName, bool isResolvedLnk, RowData rowData = null) + { + if (rowData == null) + { + rowData = new RowData(); + } + + rowData.IsResolvedLnk = isResolvedLnk; + + try + { + rowData.FileInfo = new FileInfo(fileName); + rowData.TargetFilePath = rowData.FileInfo.FullName; + if (!isResolvedLnk) + { + if (string.IsNullOrEmpty(rowData.FileInfo.Name)) + { + string path = rowData.FileInfo.FullName; + int directoryNameBegin = path.LastIndexOf(@"\", StringComparison.InvariantCulture) + 1; + rowData.SetText(path.Substring(directoryNameBegin)); + } + else + { + rowData.SetText(rowData.FileInfo.Name); + } + + rowData.TargetFilePathOrig = rowData.FileInfo.FullName; + } + } + catch (Exception ex) + { + if (ex is SecurityException || + ex is ArgumentException || + ex is UnauthorizedAccessException || + ex is PathTooLongException || + ex is NotSupportedException) + { + Log.Warn($"fileName:'{fileName}'", ex); + } + else + { + throw; + } + } + + return rowData; + } + + private static bool IsActive() + { + return Form.ActiveForm is Menu || + Form.ActiveForm is UserInterface.TaskbarForm; + } + + private static void OpenFolder() + { + Log.ProcessStart(Config.Path, null, true); + } + + private Menu Create(MenuData menuData, string title = null) + { + Menu menu = new Menu(); + + if (title != null) + { + if (string.IsNullOrEmpty(title)) + { + title = Path.GetPathRoot(Config.Path); + } + + menu.SetTitle(title); + menu.UserClickedOpenFolder += OpenFolder; + } + + menu.Level = menuData.Level; + menu.MouseWheel += AdjustMenusSizeAndLocation; + menu.MouseLeave += waitLeave.Start; + menu.MouseEnter += waitLeave.Stop; + menu.KeyPress += keyboardInput.KeyPress; + menu.CmdKeyProcessed += keyboardInput.CmdKeyProcessed; + menu.SearchTextChanging += keyboardInput.SearchTextChanging; + menu.SearchTextChanged += Menu_SearchTextChanged; + menu.Deactivate += Deactivate; + void Deactivate(object sender, EventArgs e) + { + FadeHalfOrOutIfNeeded(); + if (!IsActive()) + { + deactivatedTime = DateTime.Now; + } + } + + menu.Activated += Activated; + void Activated(object sender, EventArgs e) + { + if (IsActive() && + menus[0].IsUsable) + { + AsList.ForEach(m => m.ShowWithFade()); + timerStillActiveCheck.Start(); + } + } + + menu.VisibleChanged += MenuVisibleChanged; + AddItemsToMenu(menuData.RowDatas, menu); + static void AddItemsToMenu(List data, Menu menu) + { + DataGridView dgv = menu.GetDataGridView(); + DataTable dataTable = new DataTable(); + dataTable.Columns.Add(dgv.Columns[0].Name, typeof(Icon)); + dataTable.Columns.Add(dgv.Columns[1].Name, typeof(string)); + dataTable.Columns.Add("data", typeof(RowData)); + foreach (RowData rowData in data) + { + rowData.SetData(rowData, dataTable); + } + + dgv.DataSource = dataTable; + } + + DataGridView dgv = menu.GetDataGridView(); + dgv.CellMouseEnter += dgvMouseRow.CellMouseEnter; + dgv.CellMouseLeave += dgvMouseRow.CellMouseLeave; + dgv.MouseLeave += dgvMouseRow.MouseLeave; + dgv.MouseMove += waitToOpenMenu.MouseMove; + dgv.MouseDown += Dgv_MouseDown; + dgv.MouseDoubleClick += Dgv_MouseDoubleClick; + dgv.SelectionChanged += Dgv_SelectionChanged; + dgv.RowPostPaint += Dgv_RowPostPaint; + + return menu; + } + + private void MenuVisibleChanged(object sender, EventArgs e) + { + Menu menu = (Menu)sender; + if (menu.IsUsable) + { + AdjustMenusSizeAndLocation(); + } + + if (!menu.Visible) + { + DisposeMenu(menu); + } + + if (!AsEnumerable.Any(m => m.Visible)) + { + openCloseState = OpenCloseState.Default; + } + } + + private void Dgv_MouseDown(object sender, MouseEventArgs e) + { + DataGridView dgv = (DataGridView)sender; + DataGridView.HitTestInfo hitTestInfo; + hitTestInfo = dgv.HitTest(e.X, e.Y); + if (hitTestInfo.RowIndex > -1 && + dgv.Rows.Count > hitTestInfo.RowIndex) + { + RowData rowData = (RowData)dgv.Rows[hitTestInfo.RowIndex].Cells[2].Value; + rowData.MouseDown(dgv, e); + waitToOpenMenu.ClickOpensInstantly(dgv, hitTestInfo.RowIndex); + } + } + + private void Dgv_MouseDoubleClick(object sender, MouseEventArgs e) + { + DataGridView dgv = (DataGridView)sender; + DataGridView.HitTestInfo hitTestInfo; + hitTestInfo = dgv.HitTest(e.X, e.Y); + if (hitTestInfo.RowIndex > -1 && + dgv.Rows.Count > hitTestInfo.RowIndex) + { + RowData trigger = (RowData)dgv.Rows[hitTestInfo.RowIndex].Cells[2].Value; + trigger.DoubleClick(e); + } + } + + private void Dgv_SelectionChanged(object sender, EventArgs e) + { + RefreshSelection((DataGridView)sender); + } + + private void RefreshSelection(DataGridView dgv) + { + foreach (DataGridViewRow row in dgv.Rows) + { + RowData rowData = (RowData)row.Cells[2].Value; + + if (rowData == null) + { + // Case when filtering a previous menu + } + else if (!menus[0].IsUsable) + { + row.DefaultCellStyle.SelectionBackColor = Color.White; + row.Selected = false; + } + else if (rowData.IsContextMenuOpen || (rowData.IsMenuOpen && rowData.IsSelected)) + { + row.Selected = true; + } + else if (rowData.IsMenuOpen) + { + row.Selected = true; + } + else if (rowData.IsSelected) + { + row.DefaultCellStyle.SelectionBackColor = MenuDefines.ColorSelectedItem; + row.Selected = true; + } + else + { + row.DefaultCellStyle.SelectionBackColor = Color.White; + row.Selected = false; + } + } + + dgv.Refresh(); + } + + private void Dgv_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e) + { + DataGridView dgv = (DataGridView)sender; + DataGridViewRow row = dgv.Rows[e.RowIndex]; + + if (row.Selected) + { + RowData rowData = (RowData)row.Cells[2].Value; + + int width = dgv.Columns[0].Width + dgv.Columns[1].Width; + Rectangle rowBounds = new Rectangle(0, e.RowBounds.Top, width, e.RowBounds.Height); + + if (rowData.IsContextMenuOpen || (rowData.IsMenuOpen && rowData.IsSelected)) + { + ControlPaint.DrawBorder(e.Graphics, rowBounds, MenuDefines.ColorSelectedItemBorder, ButtonBorderStyle.Solid); + row.DefaultCellStyle.SelectionBackColor = MenuDefines.ColorSelectedItem; + } + else if (rowData.IsMenuOpen) + { + ControlPaint.DrawBorder(e.Graphics, rowBounds, MenuDefines.ColorOpenFolderBorder, ButtonBorderStyle.Solid); + row.DefaultCellStyle.SelectionBackColor = MenuDefines.ColorOpenFolder; + } + } + } + + private void ShowSubMenu(Menu menuToShow) + { + HideOldMenu(menuToShow, true); + + menus[menuToShow.Level] = menuToShow; + AdjustMenusSizeAndLocation(); + menus[menuToShow.Level].ShowWithFadeOrTransparent(IsActive()); + } + + private void HideOldMenu(Menu menuToShow, bool keepOrSetIsMenuOpen = false) + { + // Clean up menu status IsMenuOpen for previous one + Menu menuPrevious = menus[menuToShow.Level - 1]; + DataGridView dgvPrevious = menuPrevious.GetDataGridView(); + foreach (DataRow row in ((DataTable)dgvPrevious.DataSource).Rows) + { + RowData rowDataToClear = (RowData)row[2]; + if (rowDataToClear == (RowData)menuToShow.Tag) + { + rowDataToClear.IsMenuOpen = keepOrSetIsMenuOpen; + } + else + { + rowDataToClear.IsMenuOpen = false; + } + } + + RefreshSelection(dgvPrevious); + + // Hide old menu + foreach (Menu menuToClose in menus.Where( + m => m != null && m.Level > menuPrevious.Level)) + { + menuToClose.VisibleChanged += MenuVisibleChanged; + menuToClose.HideWithFade(); + menus[menuToClose.Level] = null; + } + } + + private void FadeHalfOrOutIfNeeded() + { + if (menus[0].IsUsable) + { + if (!IsActive()) + { + Point position = Control.MousePosition; + if (AsList.Any(m => m.IsMouseOn(position))) + { + if (!keyboardInput.InUse) + { + AsList.ForEach(menu => menu.ShowTransparent()); + } + } + else + { + MenusFadeOut(); + } + } + } + } + + private void MenusFadeOut() + { + openCloseState = OpenCloseState.Closing; + AsList.ForEach(menu => + { + if (menu.Level > 0) + { + menus[menu.Level] = null; + } + + menu.HideWithFade(); + }); + } + + private void AdjustMenusSizeAndLocation() + { + WindowsTaskbar taskbar = new WindowsTaskbar(); + Menu menuPredecessor = null; + List list = AsList; + Menu menu; + Rectangle screenBounds = Screen.PrimaryScreen.Bounds; + Menu.StartLocation startLocation; + + // Only apply taskbar position change when no menu is currently open if (list.Count == 1) { taskbarPosition = taskbar.Position; - } - - // Shrink the usable space depending on taskbar location + } + + // Shrink the usable space depending on taskbar location switch (taskbarPosition) { case TaskbarPosition.Left: @@ -890,18 +895,18 @@ namespace SystemTrayMenu.Business screenBounds.Height -= taskbar.Size.Height; startLocation = Menu.StartLocation.BottomRight; break; - } - - for (int i = 0; i < list.Count; i++) - { - menu = list[i]; - - // Only last one has to be updated as all previous one were already updated in the past + } + + for (int i = 0; i < list.Count; i++) + { + menu = list[i]; + + // Only last one has to be updated as all previous one were already updated in the past if (list.Count - 1 == i) - { + { menu.AdjustSizeAndLocation(screenBounds, menuPredecessor, startLocation); - } - + } + if (i == 0) { const int overlapTolerance = 4; @@ -913,16 +918,16 @@ namespace SystemTrayMenu.Business } screenBounds.Width -= menu.Width - overlapTolerance; - } - - menuPredecessor = menu; - } - } - - private void Menu_SearchTextChanged(object sender, EventArgs e) - { - keyboardInput.SearchTextChanged(sender, e); - AdjustMenusSizeAndLocation(); - } - } -} + } + + menuPredecessor = menu; + } + } + + private void Menu_SearchTextChanged(object sender, EventArgs e) + { + keyboardInput.SearchTextChanged(sender, e); + AdjustMenusSizeAndLocation(); + } + } +} diff --git a/Packaging/Package.appxmanifest b/Packaging/Package.appxmanifest index d1a2096..c453792 100644 --- a/Packaging/Package.appxmanifest +++ b/Packaging/Package.appxmanifest @@ -9,7 +9,7 @@ + Version="1.0.16.0" /> SystemTrayMenu diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index d0f4349..1304cf4 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -39,5 +39,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.16.0")] -[assembly: AssemblyFileVersion("1.0.16.0")] +[assembly: AssemblyVersion("1.0.16.1")] +[assembly: AssemblyFileVersion("1.0.16.1")] diff --git a/Utilities/FolderOptions.cs b/Utilities/FolderOptions.cs index 7f1ed45..a983a2c 100644 --- a/Utilities/FolderOptions.cs +++ b/Utilities/FolderOptions.cs @@ -62,15 +62,26 @@ namespace SystemTrayMenu.Utilities bool isDirectoryToHide = false; if (path.Length < 260) { - FileAttributes attributes = File.GetAttributes(path); - hiddenEntry = attributes.HasFlag(FileAttributes.Hidden); - bool systemEntry = attributes.HasFlag( - FileAttributes.Hidden | FileAttributes.System); - if ((hideHiddenEntries && hiddenEntry) || - (hideSystemEntries && systemEntry)) + try { - isDirectoryToHide = true; + FileAttributes attributes = File.GetAttributes(path); + hiddenEntry = attributes.HasFlag(FileAttributes.Hidden); + bool systemEntry = attributes.HasFlag( + FileAttributes.Hidden | FileAttributes.System); + if ((hideHiddenEntries && hiddenEntry) || + (hideSystemEntries && systemEntry)) + { + isDirectoryToHide = true; + } } + catch (UnauthorizedAccessException ex) + { + Log.Warn($"path:'{path}'", ex); + } + } + else + { + Log.Info($"path too long (>=260):'{path}'"); } return isDirectoryToHide; diff --git a/Utilities/Log.cs b/Utilities/Log.cs index 8a92d49..88e7d58 100644 --- a/Utilities/Log.cs +++ b/Utilities/Log.cs @@ -5,6 +5,7 @@ namespace SystemTrayMenu.Utilities { using System; + using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; @@ -15,6 +16,8 @@ namespace SystemTrayMenu.Utilities internal static class Log { private static readonly Logger LogValue = new Logger(string.Empty); + private static List warnings = new List(); + private static List infos = new List(); internal static void Initialize() { @@ -23,12 +26,21 @@ namespace SystemTrayMenu.Utilities internal static void Info(string message) { - LogValue.Info(message); + if (!infos.Contains(message)) + { + LogValue.Info(message); + infos.Add(message); + } } internal static void Warn(string message, Exception ex) { - LogValue.Warn($"{message}{Environment.NewLine}{ex}"); + string warning = $"{message} {ex.ToString().Replace(Environment.NewLine, " ", StringComparison.InvariantCulture)}"; + if (!warnings.Contains(warning)) + { + LogValue.Warn(warning); + warnings.Add(warning); + } } internal static void Error(string message, Exception ex)