From d1b8c0b279c2932d77ab2df9044e1257966bf60e Mon Sep 17 00:00:00 2001 From: Markus Hofknecht Date: Sat, 20 Jun 2020 17:38:21 +0200 Subject: [PATCH] #88, #69, #103, #104, #105, #97, #106 ,version 0.10.2.8 [Feature] Code Analyze and Refactor 0.11 (#88) [Feature] Mouse disturbing while using keys (#69) [Feature] Reload menu when reenter or click (#103) [BUG] Crash when fast mouse movings (#104) [BUG] Loading was shown too often (#105) [BUG] submenu wrong location in edgecase (#97) [BUG] Folder shown as open (green) but is not open (#106) --- Business/KeyboardInput.cs | 1058 ++++++++++---------- Business/Menus.cs | 420 ++++---- Business/WaitMenuOpen.cs | 76 -- Business/WaitToLoadMenu.cs | 139 +++ Config/Config.cs | 4 +- Config/MenuDefines.cs | 6 +- DataClasses/MenuData.cs | 10 +- DataClasses/RowData.cs | 64 +- Properties/AssemblyInfo.cs | 4 +- UserInterface/AppNotifyIcon.cs | 25 +- UserInterface/FolderDialog/FolderDialog.cs | 44 +- UserInterface/Menu.Designer.cs | 1 + UserInterface/Menu.cs | 33 +- Utilities/AppRestart.cs | 1 - Utilities/DataGridViewExtensions.cs | 4 + 15 files changed, 901 insertions(+), 988 deletions(-) delete mode 100644 Business/WaitMenuOpen.cs create mode 100644 Business/WaitToLoadMenu.cs diff --git a/Business/KeyboardInput.cs b/Business/KeyboardInput.cs index 2db6784..bf2a1e2 100644 --- a/Business/KeyboardInput.cs +++ b/Business/KeyboardInput.cs @@ -1,538 +1,538 @@ -using System; +using System; using System.Drawing; -using System.Globalization; -using System.Linq; -using System.Windows.Forms; -using SystemTrayMenu.DataClasses; -using SystemTrayMenu.Helper; -using SystemTrayMenu.Utilities; -using Menu = SystemTrayMenu.UserInterface.Menu; - -namespace SystemTrayMenu.Handler -{ - internal class KeyboardInput : IDisposable - { - public event EventHandlerEmpty HotKeyPressed; - public event EventHandlerEmpty ClosePressed; -#warning use event not action - public event Action KeyPressedSearching; - public event Action RowSelected; - public event Action RowDeselected; - public event EventHandlerEmpty Cleared; - - private readonly Menu[] menus; - private readonly KeyboardHook hook = new KeyboardHook(); - private readonly Timer timerKeySearch = new Timer(); - public int iRowKey = -1; - public int iMenuKey = 0; - private string KeySearchString = string.Empty; - - public bool InUse = false; - - public KeyboardInput(Menu[] menus) - { - this.menus = menus; - - timerKeySearch.Interval = MenuDefines.KeySearchInterval; - timerKeySearch.Tick += TimerKeySearch_Tick; - void TimerKeySearch_Tick(object sender, EventArgs e) - { - // this search has expired, reset search - timerKeySearch.Stop(); - KeySearchString = string.Empty; - } - } - - public void Dispose() - { - hook.Dispose(); - timerKeySearch.Dispose(); +using System.Globalization; +using System.Linq; +using System.Windows.Forms; +using SystemTrayMenu.DataClasses; +using SystemTrayMenu.Helper; +using SystemTrayMenu.Utilities; +using Menu = SystemTrayMenu.UserInterface.Menu; + +namespace SystemTrayMenu.Handler +{ + internal class KeyboardInput : IDisposable + { + public event EventHandlerEmpty HotKeyPressed; + public event EventHandlerEmpty ClosePressed; + public event Action RowSelected; + public event Action RowDeselected; + internal Action EnterPressed; + public event EventHandlerEmpty Cleared; + + private readonly Menu[] menus; + private readonly KeyboardHook hook = new KeyboardHook(); + private readonly Timer timerKeySearch = new Timer(); + public int iRowKey = -1; + public int iMenuKey = 0; + private string KeySearchString = string.Empty; + + public bool InUse = false; + + public KeyboardInput(Menu[] menus) + { + this.menus = menus; + + timerKeySearch.Interval = MenuDefines.KeySearchInterval; + timerKeySearch.Tick += TimerKeySearch_Tick; + void TimerKeySearch_Tick(object sender, EventArgs e) + { + // this search has expired, reset search + timerKeySearch.Stop(); + KeySearchString = string.Empty; + } + } + + public void Dispose() + { + hook.Dispose(); + timerKeySearch.Dispose(); } private int GetMenuIndex(in Menu currentMenu) - { - int index = 0; - foreach (Menu menuFindIndex in menus.Where(m => m != null)) - { - if (currentMenu == menuFindIndex) - { - break; - } - index++; + { + int index = 0; + foreach (Menu menuFindIndex in menus.Where(m => m != null)) + { + if (currentMenu == menuFindIndex) + { + break; + } + index++; } return index; - } - - internal void RegisterHotKey() - { - if (!string.IsNullOrEmpty(Properties.Settings.Default.HotKey)) - { - try - { - hook.RegisterHotKey(Properties.Settings.Default.HotKey); - hook.KeyPressed += hook_KeyPressed; - void hook_KeyPressed(object sender, KeyPressedEventArgs e) - { - HotKeyPressed?.Invoke(); - } - } - catch (InvalidOperationException ex) - { - //Log.Error($"key:'{key}'", ex); - Properties.Settings.Default.HotKey = string.Empty; - Properties.Settings.Default.Save(); - MessageBox.Show(ex.Message); - } - } - } - - internal void ResetSelectedByKey() - { - iRowKey = -1; - iMenuKey = 0; - } - - internal void CmdKeyProcessed(object sender, Keys keys) - { - switch (keys) - { - case Keys.Enter: - case Keys.Up: - case Keys.Down: - case Keys.Left: - case Keys.Right: - case Keys.Escape: - SelectByKey(keys); - break; - case Keys.Control | Keys.F: - Menu menu = menus[iMenuKey]; - menu.FocusTextBox(); - break; - case Keys.Tab: - { - Menu currentMenu = (Menu)sender; - int indexOfTheCurrentMenu = GetMenuIndex(currentMenu); - int indexMax = menus.Where(m => m != null).Count() - 1; - int indexNew = 0; - if (indexOfTheCurrentMenu > 0) - { - indexNew = indexOfTheCurrentMenu - 1; - } - else - { - indexNew = indexMax; - } - - menus[indexNew].FocusTextBox(); - } - break; - case Keys.Tab | Keys.Shift: - { - Menu currentMenu = (Menu)sender; - int indexOfTheCurrentMenu = GetMenuIndex(currentMenu); - int indexMax = menus.Where(m => m != null).Count() - 1; - int indexNew = 0; - if (indexOfTheCurrentMenu < indexMax) - { - indexNew = indexOfTheCurrentMenu + 1; - } - else - { - indexNew = 0; - } - - menus[indexNew].FocusTextBox(); - } + } + + internal void RegisterHotKey() + { + if (!string.IsNullOrEmpty(Properties.Settings.Default.HotKey)) + { + try + { + hook.RegisterHotKey(Properties.Settings.Default.HotKey); + hook.KeyPressed += hook_KeyPressed; + void hook_KeyPressed(object sender, KeyPressedEventArgs e) + { + HotKeyPressed?.Invoke(); + } + } + catch (InvalidOperationException ex) + { + //Log.Error($"key:'{key}'", ex); + Properties.Settings.Default.HotKey = string.Empty; + Properties.Settings.Default.Save(); + MessageBox.Show(ex.Message); + } + } + } + + internal void ResetSelectedByKey() + { + iRowKey = -1; + iMenuKey = 0; + } + + internal void CmdKeyProcessed(object sender, Keys keys) + { + switch (keys) + { + case Keys.Enter: + case Keys.Up: + case Keys.Down: + case Keys.Left: + case Keys.Right: + case Keys.Escape: + SelectByKey(keys); break; - case Keys.Apps: - { - DataGridView dgv = menus[iMenuKey].GetDataGridView(); - - if (iRowKey > -1 && - dgv.Rows.Count > iRowKey) - { - Point pt = dgv.GetCellDisplayRectangle(2, iRowKey, false).Location; - RowData trigger = (RowData)dgv.Rows[iRowKey].Cells[2].Value; - MouseEventArgs mea = new MouseEventArgs(MouseButtons.Right, 1, pt.X, pt.Y, 0); - trigger.MouseDown(dgv, mea); + case Keys.Control | Keys.F: + Menu menu = menus[iMenuKey]; + menu.FocusTextBox(); + break; + case Keys.Tab: + { + Menu currentMenu = (Menu)sender; + int indexOfTheCurrentMenu = GetMenuIndex(currentMenu); + int indexMax = menus.Where(m => m != null).Count() - 1; + int indexNew = 0; + if (indexOfTheCurrentMenu > 0) + { + indexNew = indexOfTheCurrentMenu - 1; } - } - break; - default: - break; - } - } - - /// - /// While menu is open user presses a key to search for specific entries. - /// - /// not used - /// Key data of the pressed key - internal void KeyPress(object sender, KeyPressEventArgs e) - { - if (char.IsLetterOrDigit(e.KeyChar) || - char.IsPunctuation(e.KeyChar) || - char.IsWhiteSpace(e.KeyChar) || - char.IsSeparator(e.KeyChar)) - { - string letter = e.KeyChar.ToString(CultureInfo.InvariantCulture); - - Menu menu = menus[iMenuKey]; - menu.KeyPressedSearch(letter); - -#warning remove if not more needed - // Old Search by letters - //timerKeySearch.Stop(); - //if (string.IsNullOrEmpty(KeySearchString)) - //{ - // // no search string set, start new search with initial letter - // KeySearchString += letter; - // SelectByKey(Keys.None, KeySearchString); - //} - //else if (KeySearchString.Length == 1 && KeySearchString.LastOrDefault().ToString(CultureInfo.InvariantCulture) == letter) - //{ - // // initial letter pressed again, jump to next element - // SelectByKey(Keys.None, letter); - //} - //else - //{ - // // new character for the search string, narrow down the search - // KeySearchString += letter; - // SelectByKey(Keys.None, KeySearchString, true); - //} - //// give user some time to continue with this search - //timerKeySearch.Start(); - - e.Handled = true; - } - } - - internal void SearchTextChanging() - { - ClearIsSelectedByKey(); - } - - internal void SearchTextChanged(object sender, EventArgs e) - { - Menu menu = (Menu)sender; - DataGridView dgv = menu.GetDataGridView(); - if (dgv.Rows.Count > 0) - { - Select(dgv, 0); - } - } - - private bool IsAnyMenuSelectedByKey( - ref DataGridView dgv, - ref Menu menuFromSelected, - ref string textselected) - { - Menu menu = menus[iMenuKey]; - bool isStillSelected = false; - if (menu != null && - iRowKey > -1) - { - dgv = menu.GetDataGridView(); - if (dgv.Rows.Count > iRowKey) - { - RowData rowData = (RowData)dgv. - Rows[iRowKey].Cells[2].Value; - if (rowData.IsSelectedByKeyboard) - { - isStillSelected = true; - menuFromSelected = rowData.SubMenu; - textselected = dgv.Rows[iRowKey]. - Cells[1].Value.ToString(); - } - } - } - - return isStillSelected; - } - - private void SelectByKey(Keys keys, string keyInput = "", bool KeepSelection = false) - { - int iRowBefore = iRowKey; - int iMenuBefore = iMenuKey; - - Menu menu = menus[iMenuKey]; - DataGridView dgv = null; - DataGridView dgvBefore = null; - Menu menuFromSelected = null; - string textselected = string.Empty; - bool isStillSelected = IsAnyMenuSelectedByKey(ref dgv, ref menuFromSelected, ref textselected); - if (isStillSelected) - { - if (KeepSelection) - { - // If current selection is still valid for this search then skip selecting different item - if (textselected.StartsWith(keyInput, true, CultureInfo.InvariantCulture)) - { - return; - } - } - - dgvBefore = dgv; - } - else - { - ResetSelectedByKey(); - menu = menus[iMenuKey]; - dgv = menu.GetDataGridView(); - } - - bool toClear = false; - switch (keys) - { - case Keys.Enter: - if (iRowKey > -1 && - dgv.Rows.Count > iRowKey) - { - RowData trigger = (RowData)dgv.Rows[iRowKey].Cells[2].Value; - if (trigger.IsSelected || !trigger.ContainsMenu) - { - trigger.MouseDown(dgv, null); - trigger.DoubleClick( - new MouseEventArgs(MouseButtons.Left, 0, 0, 0, 0)); - } - else - { - RowDeselected(iMenuBefore, iRowBefore, dgvBefore); - SelectRow(dgv, iRowKey); - } - } - break; - case Keys.Up: - if (SelectMatchedReverse(dgv, iRowKey) || - SelectMatchedReverse(dgv, dgv.Rows.Count - 1)) - { - RowDeselected(iMenuBefore, iRowBefore, dgvBefore); - SelectRow(dgv, iRowKey); - toClear = true; - } - break; - case Keys.Down: - if (SelectMatched(dgv, iRowKey) || - SelectMatched(dgv, 0)) - { - RowDeselected(iMenuBefore, iRowBefore, dgvBefore); - SelectRow(dgv, iRowKey); - toClear = true; - } - break; - case Keys.Left: - int iMenuKeyNext = iMenuKey + 1; - if (isStillSelected) - { - if (menuFromSelected != null && - menuFromSelected == menus[iMenuKeyNext]) - { - dgv = menuFromSelected.GetDataGridView(); - if (dgv.Rows.Count > 0) - { - iMenuKey += 1; - iRowKey = -1; - if (SelectMatched(dgv, iRowKey) || - SelectMatched(dgv, 0)) - { - RowDeselected(iMenuBefore, - iRowBefore, dgvBefore); - SelectRow(dgv, iRowKey); - toClear = true; - } - } - } - } - else - { - iRowKey = -1; - iMenuKey = menus.Where(m => m != null).Count() - 1; - if (menus[iMenuKey] != null) - { - dgv = menus[iMenuKey].GetDataGridView(); - if (SelectMatched(dgv, iRowKey) || - SelectMatched(dgv, 0)) - { - RowDeselected(iMenuBefore, iRowBefore, dgvBefore); - SelectRow(dgv, iRowKey); - toClear = true; - } - } - } - break; - case Keys.Right: - if (iMenuKey > 0) - { - if (menus[iMenuKey - 1] != null) - { - iMenuKey -= 1; - iRowKey = -1; - menu = menus[iMenuKey]; - dgv = menu.GetDataGridView(); - if (SelectMatched(dgv, dgv.SelectedRows[0].Index) || - SelectMatched(dgv, 0)) - { - RowDeselected(iMenuBefore, iRowBefore, dgvBefore); - SelectRow(dgv, iRowKey); - toClear = true; - } - } - } - else - { - RowDeselected(iMenuBefore, iRowBefore, dgvBefore); - iMenuKey = 0; - iRowKey = -1; - toClear = true; - Cleared?.Invoke(); - } - break; - case Keys.Escape: - RowDeselected(iMenuBefore, iRowBefore, dgvBefore); - iMenuKey = 0; - iRowKey = -1; - toClear = true; - ClosePressed?.Invoke(); - break; - default: - if (!string.IsNullOrEmpty(keyInput)) - { - if (SelectMatched(dgv, iRowKey, keyInput) || - SelectMatched(dgv, 0, keyInput)) - { - RowDeselected(iMenuBefore, iRowBefore, null); - SelectRow(dgv, iRowKey); - toClear = true; - } - else if (isStillSelected) - { - iRowKey = iRowBefore - 1; - if (SelectMatched(dgv, iRowKey, keyInput) || - SelectMatched(dgv, 0, keyInput)) - { - RowDeselected(iMenuBefore, iRowBefore, null); - SelectRow(dgv, iRowKey); - } - else - { - iRowKey = iRowBefore; - } - } - } - break; - } - if (isStillSelected && toClear) - { - ClearIsSelectedByKey(iMenuBefore, iRowBefore); - } - } - - private void SelectRow(DataGridView dgv, int iRowKey) - { - InUse = true; - RowSelected(dgv, iRowKey); - } - - private bool SelectMatched(DataGridView dgv, - int indexStart, string keyInput = "") - { - bool found = false; - for (int i = indexStart; i < dgv.Rows.Count; i++) - { - if (Select(dgv, i, keyInput)) - { - found = true; - break; - } - } - return found; - } - - private bool SelectMatchedReverse(DataGridView dgv, - int indexStart, string keyInput = "") - { - bool found = false; - for (int i = indexStart; i > -1; i--) - { - if (Select(dgv, i, keyInput)) - { - found = true; - break; - } - } - return found; - } - - public void Select(DataGridView dgv, int i) - { - int newiMenuKey = ((Menu)dgv.TopLevelControl).Level; - if (i != iRowKey || newiMenuKey != iMenuKey) - { - ClearIsSelectedByKey(); - } - iRowKey = i; - iMenuKey = newiMenuKey; - DataGridViewRow row = dgv.Rows[i]; - RowData rowData = (RowData)row.Cells[2].Value; - rowData.IsSelectedByKeyboard = true; - row.Selected = false; //event trigger - row.Selected = true; //event trigger - } - - private bool Select(DataGridView dgv, int i, - string keyInput = "") - { - bool found = false; - if (i > -1 && - i != iRowKey && - dgv.Rows.Count > i) - { - DataGridViewRow row = dgv.Rows[i]; - RowData rowData = (RowData)row.Cells[2].Value; - string text = row.Cells[1].Value.ToString(); - if (text.StartsWith(keyInput, true, CultureInfo.InvariantCulture)) - { - iRowKey = rowData.RowIndex; - rowData.IsSelectedByKeyboard = true; - row.Selected = false; //event trigger - row.Selected = true; //event trigger - if (row.Index < dgv.FirstDisplayedScrollingRowIndex) - { - dgv.FirstDisplayedScrollingRowIndex = row.Index; - } - else if (row.Index >= - dgv.FirstDisplayedScrollingRowIndex + - dgv.DisplayedRowCount(false)) - { - dgv.FirstDisplayedScrollingRowIndex = row.Index - - dgv.DisplayedRowCount(false) + 1; - } - - found = true; - } - } - return found; - } - - internal void ClearIsSelectedByKey() - { - ClearIsSelectedByKey(iMenuKey, iRowKey); - } - - private void ClearIsSelectedByKey(int menuIndex, int rowIndex) - { - Menu menu = menus[menuIndex]; - if (menu != null && rowIndex > -1) - { - DataGridView dgv = menu.GetDataGridView(); - if (dgv.Rows.Count > rowIndex) - { - DataGridViewRow row = dgv.Rows[rowIndex]; - RowData rowData = (RowData)row.Cells[2].Value; - rowData.IsSelectedByKeyboard = false; - row.Selected = false; //event trigger - } - } - } - } + else + { + indexNew = indexMax; + } + + menus[indexNew].FocusTextBox(); + } + break; + case Keys.Tab | Keys.Shift: + { + Menu currentMenu = (Menu)sender; + int indexOfTheCurrentMenu = GetMenuIndex(currentMenu); + int indexMax = menus.Where(m => m != null).Count() - 1; + int indexNew = 0; + if (indexOfTheCurrentMenu < indexMax) + { + indexNew = indexOfTheCurrentMenu + 1; + } + else + { + indexNew = 0; + } + + menus[indexNew].FocusTextBox(); + } + break; + case Keys.Apps: + { + DataGridView dgv = menus[iMenuKey].GetDataGridView(); + + if (iRowKey > -1 && + dgv.Rows.Count > iRowKey) + { + Point pt = dgv.GetCellDisplayRectangle(2, iRowKey, false).Location; + RowData trigger = (RowData)dgv.Rows[iRowKey].Cells[2].Value; + MouseEventArgs mea = new MouseEventArgs(MouseButtons.Right, 1, pt.X, pt.Y, 0); + trigger.MouseDown(dgv, mea); + } + } + break; + default: + break; + } + } + + /// + /// While menu is open user presses a key to search for specific entries. + /// + /// not used + /// Key data of the pressed key + internal void KeyPress(object sender, KeyPressEventArgs e) + { + if (char.IsLetterOrDigit(e.KeyChar) || + char.IsPunctuation(e.KeyChar) || + char.IsWhiteSpace(e.KeyChar) || + char.IsSeparator(e.KeyChar)) + { + string letter = e.KeyChar.ToString(CultureInfo.InvariantCulture); + + Menu menu = menus[iMenuKey]; + menu.KeyPressedSearch(letter); + +#warning remove if not more needed + // Old Search by letters + //timerKeySearch.Stop(); + //if (string.IsNullOrEmpty(KeySearchString)) + //{ + // // no search string set, start new search with initial letter + // KeySearchString += letter; + // SelectByKey(Keys.None, KeySearchString); + //} + //else if (KeySearchString.Length == 1 && KeySearchString.LastOrDefault().ToString(CultureInfo.InvariantCulture) == letter) + //{ + // // initial letter pressed again, jump to next element + // SelectByKey(Keys.None, letter); + //} + //else + //{ + // // new character for the search string, narrow down the search + // KeySearchString += letter; + // SelectByKey(Keys.None, KeySearchString, true); + //} + //// give user some time to continue with this search + //timerKeySearch.Start(); + + e.Handled = true; + } + } + + internal void SearchTextChanging() + { + ClearIsSelectedByKey(); + } + + internal void SearchTextChanged(object sender, EventArgs e) + { + Menu menu = (Menu)sender; + DataGridView dgv = menu.GetDataGridView(); + if (dgv.Rows.Count > 0) + { + Select(dgv, 0); + } + } + + private bool IsAnyMenuSelectedByKey( + ref DataGridView dgv, + ref Menu menuFromSelected, + ref string textselected) + { + Menu menu = menus[iMenuKey]; + bool isStillSelected = false; + if (menu != null && + iRowKey > -1) + { + dgv = menu.GetDataGridView(); + if (dgv.Rows.Count > iRowKey) + { + RowData rowData = (RowData)dgv. + Rows[iRowKey].Cells[2].Value; + if (rowData.IsSelected) + { + isStillSelected = true; + menuFromSelected = rowData.SubMenu; + textselected = dgv.Rows[iRowKey]. + Cells[1].Value.ToString(); + } + } + } + + return isStillSelected; + } + + private void SelectByKey(Keys keys, string keyInput = "", bool KeepSelection = false) + { + int iRowBefore = iRowKey; + int iMenuBefore = iMenuKey; + + Menu menu = menus[iMenuKey]; + DataGridView dgv = null; + DataGridView dgvBefore = null; + Menu menuFromSelected = null; + string textselected = string.Empty; + bool isStillSelected = IsAnyMenuSelectedByKey(ref dgv, ref menuFromSelected, ref textselected); + if (isStillSelected) + { + if (KeepSelection) + { + // If current selection is still valid for this search then skip selecting different item + if (textselected.StartsWith(keyInput, true, CultureInfo.InvariantCulture)) + { + return; + } + } + + dgvBefore = dgv; + } + else + { + ResetSelectedByKey(); + menu = menus[iMenuKey]; + dgv = menu.GetDataGridView(); + } + + bool toClear = false; + switch (keys) + { + case Keys.Enter: + if (iRowKey > -1 && + dgv.Rows.Count > iRowKey) + { + RowData trigger = (RowData)dgv.Rows[iRowKey].Cells[2].Value; + if (trigger.IsMenuOpen || !trigger.ContainsMenu) + { + trigger.MouseDown(dgv, null); + trigger.DoubleClick( + new MouseEventArgs(MouseButtons.Left, 0, 0, 0, 0)); + } + else + { + RowDeselected(iMenuBefore, iRowBefore, dgvBefore); + SelectRow(dgv, iRowKey); + EnterPressed.Invoke(dgv, iRowKey); + } + } + break; + case Keys.Up: + if (SelectMatchedReverse(dgv, iRowKey) || + SelectMatchedReverse(dgv, dgv.Rows.Count - 1)) + { + RowDeselected(iMenuBefore, iRowBefore, dgvBefore); + SelectRow(dgv, iRowKey); + toClear = true; + } + break; + case Keys.Down: + if (SelectMatched(dgv, iRowKey) || + SelectMatched(dgv, 0)) + { + RowDeselected(iMenuBefore, iRowBefore, dgvBefore); + SelectRow(dgv, iRowKey); + toClear = true; + } + break; + case Keys.Left: + int iMenuKeyNext = iMenuKey + 1; + if (isStillSelected) + { + if (menuFromSelected != null && + menuFromSelected == menus[iMenuKeyNext]) + { + dgv = menuFromSelected.GetDataGridView(); + if (dgv.Rows.Count > 0) + { + iMenuKey += 1; + iRowKey = -1; + if (SelectMatched(dgv, iRowKey) || + SelectMatched(dgv, 0)) + { + RowDeselected(iMenuBefore, + iRowBefore, dgvBefore); + SelectRow(dgv, iRowKey); + toClear = true; + } + } + } + } + else + { + iRowKey = -1; + iMenuKey = menus.Where(m => m != null).Count() - 1; + if (menus[iMenuKey] != null) + { + dgv = menus[iMenuKey].GetDataGridView(); + if (SelectMatched(dgv, iRowKey) || + SelectMatched(dgv, 0)) + { + RowDeselected(iMenuBefore, iRowBefore, dgvBefore); + SelectRow(dgv, iRowKey); + toClear = true; + } + } + } + break; + case Keys.Right: + if (iMenuKey > 0) + { + if (menus[iMenuKey - 1] != null) + { + iMenuKey -= 1; + iRowKey = -1; + menu = menus[iMenuKey]; + dgv = menu.GetDataGridView(); + if (SelectMatched(dgv, dgv.SelectedRows[0].Index) || + SelectMatched(dgv, 0)) + { + RowDeselected(iMenuBefore, iRowBefore, dgvBefore); + SelectRow(dgv, iRowKey); + toClear = true; + } + } + } + else + { + RowDeselected(iMenuBefore, iRowBefore, dgvBefore); + iMenuKey = 0; + iRowKey = -1; + toClear = true; + Cleared?.Invoke(); + } + break; + case Keys.Escape: + RowDeselected(iMenuBefore, iRowBefore, dgvBefore); + iMenuKey = 0; + iRowKey = -1; + toClear = true; + ClosePressed?.Invoke(); + break; + default: + if (!string.IsNullOrEmpty(keyInput)) + { + if (SelectMatched(dgv, iRowKey, keyInput) || + SelectMatched(dgv, 0, keyInput)) + { + RowDeselected(iMenuBefore, iRowBefore, null); + SelectRow(dgv, iRowKey); + toClear = true; + } + else if (isStillSelected) + { + iRowKey = iRowBefore - 1; + if (SelectMatched(dgv, iRowKey, keyInput) || + SelectMatched(dgv, 0, keyInput)) + { + RowDeselected(iMenuBefore, iRowBefore, null); + SelectRow(dgv, iRowKey); + } + else + { + iRowKey = iRowBefore; + } + } + } + break; + } + if (isStillSelected && toClear) + { + ClearIsSelectedByKey(iMenuBefore, iRowBefore); + } + } + + private void SelectRow(DataGridView dgv, int iRowKey) + { + InUse = true; + RowSelected(dgv, iRowKey); + } + + private bool SelectMatched(DataGridView dgv, + int indexStart, string keyInput = "") + { + bool found = false; + for (int i = indexStart; i < dgv.Rows.Count; i++) + { + if (Select(dgv, i, keyInput)) + { + found = true; + break; + } + } + return found; + } + + private bool SelectMatchedReverse(DataGridView dgv, + int indexStart, string keyInput = "") + { + bool found = false; + for (int i = indexStart; i > -1; i--) + { + if (Select(dgv, i, keyInput)) + { + found = true; + break; + } + } + return found; + } + + public void Select(DataGridView dgv, int i) + { + int newiMenuKey = ((Menu)dgv.TopLevelControl).Level; + if (i != iRowKey || newiMenuKey != iMenuKey) + { + ClearIsSelectedByKey(); + } + iRowKey = i; + iMenuKey = newiMenuKey; + DataGridViewRow row = dgv.Rows[i]; + RowData rowData = (RowData)row.Cells[2].Value; + rowData.IsSelected = true; + row.Selected = false; //event trigger + row.Selected = true; //event trigger + } + + private bool Select(DataGridView dgv, int i, + string keyInput = "") + { + bool found = false; + if (i > -1 && + i != iRowKey && + dgv.Rows.Count > i) + { + DataGridViewRow row = dgv.Rows[i]; + RowData rowData = (RowData)row.Cells[2].Value; + string text = row.Cells[1].Value.ToString(); + if (text.StartsWith(keyInput, true, CultureInfo.InvariantCulture)) + { + iRowKey = rowData.RowIndex; + rowData.IsSelected = true; + row.Selected = false; //event trigger + row.Selected = true; //event trigger + if (row.Index < dgv.FirstDisplayedScrollingRowIndex) + { + dgv.FirstDisplayedScrollingRowIndex = row.Index; + } + else if (row.Index >= + dgv.FirstDisplayedScrollingRowIndex + + dgv.DisplayedRowCount(false)) + { + dgv.FirstDisplayedScrollingRowIndex = row.Index - + dgv.DisplayedRowCount(false) + 1; + } + + found = true; + } + } + return found; + } + + internal void ClearIsSelectedByKey() + { + ClearIsSelectedByKey(iMenuKey, iRowKey); + } + + private void ClearIsSelectedByKey(int menuIndex, int rowIndex) + { + Menu menu = menus[menuIndex]; + if (menu != null && rowIndex > -1) + { + DataGridView dgv = menu.GetDataGridView(); + if (dgv.Rows.Count > rowIndex) + { + DataGridViewRow row = dgv.Rows[rowIndex]; + RowData rowData = (RowData)row.Cells[2].Value; + rowData.IsSelected = false; + row.Selected = false; + } + } + } + } } \ No newline at end of file diff --git a/Business/Menus.cs b/Business/Menus.cs index 8526867..bea75a9 100644 --- a/Business/Menus.cs +++ b/Business/Menus.cs @@ -18,13 +18,15 @@ namespace SystemTrayMenu.Business { internal class Menus : IDisposable { - internal event EventHandler LoadStarted; + internal event EventHandlerEmpty LoadStarted; internal event EventHandlerEmpty LoadStopped; private enum OpenCloseState { Default, Opening, Closing }; private OpenCloseState openCloseState = OpenCloseState.Default; private readonly Menu[] menus = new Menu[MenuDefines.MenusMax]; - private readonly BackgroundWorker worker = new BackgroundWorker(); + private readonly BackgroundWorker workerMainMenu = new BackgroundWorker(); + private readonly List workersSubMenu = new List(); + private readonly WaitToLoadMenu waitToOpenMenu = new WaitToLoadMenu(); private readonly KeyboardInput keyboardInput; private readonly Timer timerStillActiveCheck = new Timer(); private readonly WaitLeave waitLeave = new WaitLeave(MenuDefines.TimeUntilClose); @@ -35,27 +37,101 @@ namespace SystemTrayMenu.Business public Menus() { - worker.WorkerSupportsCancellation = true; - worker.DoWork += Load; - void Load(object sender, DoWorkEventArgs e) + workerMainMenu.WorkerSupportsCancellation = true; + workerMainMenu.DoWork += LoadMenu; + void LoadMenu(object senderDoWork, DoWorkEventArgs eDoWork) { - e.Result = GetData((BackgroundWorker)sender, Config.Path, 0); + 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; } - worker.RunWorkerCompleted += LoadCompleted; - void LoadCompleted(object sender, RunWorkerCompletedEventArgs e) + workerMainMenu.RunWorkerCompleted += LoadMainMenuCompleted; + void LoadMainMenuCompleted(object sender, RunWorkerCompletedEventArgs e) { keyboardInput.ResetSelectedByKey(); LoadStopped(); MenuData menuData = (MenuData)e.Result; if (menuData.Validity == MenuDataValidity.Valid) { - DisposeMenu(menus[0]); + DisposeMenu(menus[menuData.Level]); menus[0] = Create(menuData, Path.GetFileName(Config.Path)); AsEnumerable.ToList().ForEach(m => { m.ShowWithFade(); }); } } + waitToOpenMenu.StopLoadMenu += WaitToOpenMenu_StopLoadMenu; + void WaitToOpenMenu_StopLoadMenu() + { + foreach (BackgroundWorker workerSubMenu in workersSubMenu. + Where(w => w.IsBusy)) + { + workerSubMenu.CancelAsync(); + } + } + waitToOpenMenu.StartLoadMenu += StartLoadMenu; + void StartLoadMenu(RowData rowData) + { + if (menus[0].IsUsable && + menus[rowData.MenuLevel] == null || + menus[rowData.MenuLevel].Tag as RowData != 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); + } + } + } + } + keyboardInput = new KeyboardInput(menus); keyboardInput.RegisterHotKey(); keyboardInput.HotKeyPressed += KeyboardInput_HotKeyPressed; @@ -65,12 +141,18 @@ namespace SystemTrayMenu.Business } keyboardInput.ClosePressed += MenusFadeOut; - keyboardInput.RowDeselected += CheckMenuOpenerStop; - keyboardInput.RowSelected += KeyboardInputRowSelected; - void KeyboardInputRowSelected(DataGridView dgv, int rowIndex) + keyboardInput.RowDeselected += waitToOpenMenu.RowDeselected; + keyboardInput.RowSelected += waitToOpenMenu.RowSelected; + + timerStillActiveCheck.Interval = 1000; + timerStillActiveCheck.Tick += StillActiveTick; + void StillActiveTick(object senderTimer, EventArgs eTimer) { - FadeInIfNeeded(); - CheckMenuOpenerStart(dgv, rowIndex); + if (!IsActive()) + { + FadeHalfOrOutIfNeeded(); + timerStillActiveCheck.Stop(); + } } waitLeave.LeaveTriggered += LeaveTriggered; @@ -104,7 +186,6 @@ namespace SystemTrayMenu.Business else { openCloseState = OpenCloseState.Opening; - LoadStarted(this, true); StartWorker(); } deactivatedTime = DateTime.MinValue; @@ -112,10 +193,9 @@ namespace SystemTrayMenu.Business public void Dispose() { - worker.Dispose(); + workerMainMenu.Dispose(); keyboardInput.Dispose(); timerStillActiveCheck.Dispose(); - waitLeave.Dispose(); IconReader.Dispose(); DisposeMenu(menus[0]); } @@ -141,7 +221,7 @@ namespace SystemTrayMenu.Business MenuData menuData = new MenuData { RowDatas = new List(), - Validity = MenuDataValidity.Invalid, + Validity = MenuDataValidity.AbortedOrUnknown, Level = level }; if (!worker.CancellationPending) @@ -181,6 +261,7 @@ namespace SystemTrayMenu.Business rowData.HiddenEntry = hiddenEntry; string resolvedLnkPath = string.Empty; rowData.ReadIcon(true, ref resolvedLnkPath); + rowData.MenuLevel = level; menuData.RowDatas.Add(rowData); } } @@ -232,9 +313,16 @@ namespace SystemTrayMenu.Business if (!worker.CancellationPending) { - if (menuData.Validity == MenuDataValidity.Invalid) + if (menuData.Validity == MenuDataValidity.AbortedOrUnknown) { - menuData.Validity = MenuDataValidity.Valid; + if (menuData.RowDatas.Count == 0) + { + menuData.Validity = MenuDataValidity.Empty; + } + else + { + menuData.Validity = MenuDataValidity.Valid; + } } } @@ -243,7 +331,7 @@ namespace SystemTrayMenu.Business internal void MainPreload() { - menus[0] = Create(GetData(worker, Config.Path, 0), + menus[0] = Create(GetData(workerMainMenu, Config.Path, 0), Path.GetFileName(Config.Path)); menus[0].AdjustSizeAndLocation(); DisposeMenu(menus[0]); @@ -251,21 +339,19 @@ namespace SystemTrayMenu.Business internal void StartWorker() { - if (worker.IsBusy) + if (!workerMainMenu.IsBusy) { - LoadStopped(); - } - else - { - worker.RunWorkerAsync(); + LoadStarted(); + workerMainMenu.RunWorkerAsync( + new object[] { Config.Path, 0 }); } } internal void StopWorker() { - if (worker.IsBusy) + if (workerMainMenu.IsBusy) { - worker.CancelAsync(); + workerMainMenu.CancelAsync(); } } @@ -333,6 +419,7 @@ namespace SystemTrayMenu.Business menu.MouseEnter += waitLeave.Stop; menu.KeyPress += keyboardInput.KeyPress; menu.CmdKeyProcessed += keyboardInput.CmdKeyProcessed; + keyboardInput.EnterPressed += waitToOpenMenu.EnterOpensInstantly; menu.SearchTextChanging += keyboardInput.SearchTextChanging; menu.SearchTextChanged += Menu_SearchTextChanged; void Menu_SearchTextChanged(object sender, EventArgs e) @@ -358,33 +445,43 @@ namespace SystemTrayMenu.Business { menus[0].SetTitleColorActive(); AsList.ForEach(m => m.ShowWithFade()); - } - - CheckIfWindowsStartStoleFocusNoDeactivateInRareCase(); - void CheckIfWindowsStartStoleFocusNoDeactivateInRareCase() - { - timerStillActiveCheck.Interval = 1000; - timerStillActiveCheck.Tick += StillActiveTick; - void StillActiveTick(object senderTimer, EventArgs eTimer) - { - if (!waitLeave.IsRunning) - { - FadeHalfOrOutIfNeeded(); - if (!IsActive()) - { - timerStillActiveCheck.Stop(); - } - } - } timerStillActiveCheck.Start(); } } menu.VisibleChanged += MenuVisibleChanged; AddItemsToMenu(menuData.RowDatas, menu); + 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 += waitToOpenMenu.MouseEnter; dgv.CellMouseEnter += Dgv_CellMouseEnter; - dgv.CellMouseLeave += Dgv_CellMouseLeave; + void Dgv_CellMouseEnter(object sender, DataGridViewCellEventArgs e) + { + if (menus[0].IsUsable) + { + if (keyboardInput.InUse) + { + keyboardInput.ClearIsSelectedByKey(); + keyboardInput.InUse = false; + } + + keyboardInput.Select(dgv, e.RowIndex); + } + } + dgv.CellMouseLeave += waitToOpenMenu.MouseLeave; + dgv.MouseMove += waitToOpenMenu.MouseMove; dgv.MouseDown += Dgv_MouseDown; dgv.MouseDoubleClick += Dgv_MouseDoubleClick; dgv.SelectionChanged += Dgv_SelectionChanged; @@ -398,10 +495,6 @@ namespace SystemTrayMenu.Business if (menu.IsUsable) { AdjustMenusSizeAndLocation(); - if (menu.Level == 0) - { - menus[0].AdjustSizeAndLocation(); - } } if (!menu.Visible) { @@ -413,126 +506,6 @@ namespace SystemTrayMenu.Business } } - private 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) - { - CreateMenuRow(rowData, menu, dataTable); - } - dgv.DataSource = dataTable; - } - - private void Dgv_CellMouseEnter(object sender, DataGridViewCellEventArgs e) - { - if (menus[0].IsUsable) - { - if (keyboardInput.InUse) - { - CheckMenuOpenerStop(keyboardInput.iMenuKey, - keyboardInput.iRowKey); - keyboardInput.ClearIsSelectedByKey(); - keyboardInput.InUse = false; - } - - DataGridView dgv = (DataGridView)sender; - keyboardInput.Select(dgv, e.RowIndex); - CheckMenuOpenerStart(dgv, e.RowIndex); - } - } - - private void CheckMenuOpenerStart(DataGridView dgv, int rowIndex) - { - if (rowIndex > -1 && - dgv.Rows.Count > rowIndex) - { - RowData trigger = (RowData)dgv.Rows[rowIndex].Cells[2].Value; - trigger.IsSelected = true; - dgv.Rows[rowIndex].Selected = true; - Menu menuFromTrigger = (Menu)dgv.FindForm(); - Menu menuTriggered = trigger.SubMenu; - int level = menuFromTrigger.Level + 1; - - if (trigger.ContainsMenu && - level < MenuDefines.MenusMax && - menus[0].IsUsable && - (menus[level] == null || - menus[level] != menuTriggered)) - { - trigger.StopLoadMenuAndStartWaitToOpenIt(); - trigger.StartMenuOpener(); - - if (trigger.Reading.IsBusy) - { - trigger.RestartLoading = true; - } - else - { - LoadStarted(this, false); - trigger.Reading.RunWorkerAsync(level); - } - } - } - } - - private void CheckMenuOpenerStop(int menuIndex, int rowIndex, DataGridView dgv = null) - { - Menu menu = menus[menuIndex]; - if (menu != null && - rowIndex > -1) - { - if (dgv == null) - { - dgv = menu.GetDataGridView(); - } - if (dgv.Rows.Count > rowIndex) - { - RowData trigger = (RowData)dgv.Rows[rowIndex].Cells[2].Value; - if (trigger.Reading.IsBusy) - { - if (!trigger.IsContextMenuOpen) - { - trigger.IsSelected = false; - dgv.Rows[rowIndex].Selected = false; - } - trigger.Reading.CancelAsync(); - } - else if (trigger.ContainsMenu && !trigger.IsLoading) - { - trigger.IsSelected = true; - dgv.Rows[rowIndex].Selected = true; - } - else - { - if (!trigger.IsContextMenuOpen) - { - trigger.IsSelected = false; - dgv.Rows[rowIndex].Selected = false; - } - } - if (trigger.IsLoading) - { - trigger.StopLoadMenuAndStartWaitToOpenIt(); - trigger.IsLoading = false; - } - } - } - } - - private void Dgv_CellMouseLeave(object sender, DataGridViewCellEventArgs e) - { - if (!keyboardInput.InUse) - { - DataGridView dgv = (DataGridView)sender; - Menu menu = (Menu)dgv.FindForm(); - CheckMenuOpenerStop(menu.Level, e.RowIndex, dgv); - } - } - private void Dgv_MouseDown(object sender, MouseEventArgs e) { DataGridView dgv = (DataGridView)sender; @@ -541,8 +514,9 @@ namespace SystemTrayMenu.Business if (hitTestInfo.RowIndex > -1 && dgv.Rows.Count > hitTestInfo.RowIndex) { - RowData trigger = (RowData)dgv.Rows[hitTestInfo.RowIndex].Cells[2].Value; - trigger.MouseDown(dgv, e); + RowData rowData = (RowData)dgv.Rows[hitTestInfo.RowIndex].Cells[2].Value; + rowData.MouseDown(dgv, e); + waitToOpenMenu.ClickOpensInstantly(dgv, hitTestInfo.RowIndex); } } @@ -561,7 +535,11 @@ namespace SystemTrayMenu.Business private void Dgv_SelectionChanged(object sender, EventArgs e) { - DataGridView dgv = (DataGridView)sender; + RefreshSelection((DataGridView)sender); + } + + private void RefreshSelection(DataGridView dgv) + { foreach (DataGridViewRow row in dgv.Rows) { RowData rowData = (RowData)row.Cells[2].Value; @@ -573,14 +551,15 @@ namespace SystemTrayMenu.Business else if (!menus[0].IsUsable) { row.DefaultCellStyle.SelectionBackColor = Color.White; + row.Selected = false; } - else if (rowData.IsSelectedByKeyboard) + else if (rowData.IsSelected) { row.DefaultCellStyle.SelectionBackColor = MenuDefines.ColorSelectedItem; row.Selected = true; } - else if (rowData.IsSelected) + else if (rowData.IsMenuOpen) { row.DefaultCellStyle.SelectionBackColor = MenuDefines.ColorOpenFolder; @@ -588,93 +567,42 @@ namespace SystemTrayMenu.Business } else { - rowData.IsSelected = false; + row.DefaultCellStyle.SelectionBackColor = Color.White; row.Selected = false; } } } - private void CreateMenuRow(RowData rowData, Menu menu, DataTable dataTable) + private void ShowSubMenu(Menu menuToShow) { - rowData.SetData(rowData, dataTable); - rowData.OpenMenu += OpenSubMenu; - rowData.Reading.WorkerSupportsCancellation = true; - rowData.Reading.DoWork += ReadMenu_DoWork; - void ReadMenu_DoWork(object senderDoWork, - DoWorkEventArgs eDoWork) + //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) { - int level = (int)eDoWork.Argument; - BackgroundWorker worker = (BackgroundWorker)senderDoWork; - eDoWork.Result = Business.Menus.GetData(worker, rowData.TargetFilePath, level); - } - - rowData.Reading.RunWorkerCompleted += ReadMenu_RunWorkerCompleted; - void ReadMenu_RunWorkerCompleted(object senderCompleted, - RunWorkerCompletedEventArgs e) - { - MenuData menuData = (MenuData)e.Result; - if (rowData.RestartLoading) + RowData rowDataToClear = (RowData)row[2]; + if (rowDataToClear == (RowData)menuToShow.Tag) { - rowData.RestartLoading = false; - rowData.Reading.RunWorkerAsync(menuData.Level); + rowDataToClear.IsMenuOpen = true; } else { - LoadStopped(); - if (menuData.Validity != MenuDataValidity.Invalid) - { - menu = Create(menuData); - if (menuData.RowDatas.Count > 0) - { - menu.SetTypeSub(); - } - else if (menuData.Validity == MenuDataValidity.NoAccess) - { - menu.SetTypeNoAccess(); - } - else - { - menu.SetTypeEmpty(); - } - menu.Tag = rowData; - rowData.SubMenu = menu; - rowData.MenuLoaded(); - } + rowDataToClear.IsMenuOpen = false; } } - } + RefreshSelection(dgvPrevious); - private void OpenSubMenu(object sender, RowData trigger) - { - Menu menuTriggered = trigger.SubMenu; - Menu menuFromTrigger = menus[menuTriggered.Level - 1]; - - for (int level = menuTriggered.Level; - level < MenuDefines.MenusMax; level++) + foreach (Menu menuToClose in menus.Where( + m => m != null && m.Level > menuPrevious.Level)) { - if (menus[level] != null) - { - Menu menuToClose = menus[level]; - RowData oldTrigger = (RowData)menuToClose.Tag; - DataGridView dgv = menuFromTrigger.GetDataGridView(); - foreach (DataGridViewRow row in dgv.Rows) - { - RowData rowData = (RowData)row.Cells[2].Value; - rowData.IsSelected = false; - } - trigger.IsSelected = true; - dgv.ClearSelection(); - dgv.Rows[trigger.RowIndex].Selected = true; - menuToClose.HideWithFade(); - menuToClose.VisibleChanged += MenuVisibleChanged; - menus[level] = null; - } + menuToClose.VisibleChanged += MenuVisibleChanged; + menuToClose.HideWithFade(); + menus[menuToClose.Level] = null; } - DisposeMenu(menus[menuTriggered.Level]); - menus[menuTriggered.Level] = menuTriggered; + menus[menuToShow.Level] = menuToShow; AdjustMenusSizeAndLocation(); - menus[menuTriggered.Level].ShowWithFadeOrTransparent(IsActive()); + menus[menuToShow.Level].ShowWithFadeOrTransparent(IsActive()); } private void FadeInIfNeeded() diff --git a/Business/WaitMenuOpen.cs b/Business/WaitMenuOpen.cs deleted file mode 100644 index 2cc5f2c..0000000 --- a/Business/WaitMenuOpen.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using SystemTrayMenu.Utilities; -using Timer = System.Windows.Forms.Timer; - -namespace SystemTrayMenu.Handler -{ - internal class WaitMenuOpen : IDisposable - { - public event EventHandlerEmpty DoOpen; - - private readonly Timer waitOpen = new Timer(); - private bool waitedDone = false; - private bool clicked = false; - private bool menuLoaded = false; - - public WaitMenuOpen() - { - waitOpen.Interval = MenuDefines.WaitMenuOpen; - waitOpen.Tick += WaitOpen_Tick; - } - - private void WaitOpen_Tick(object sender, EventArgs e) - { - waitOpen.Stop(); - waitedDone = true; - CheckConditionsToOpenMenu(); - } - - private void CheckConditionsToOpenMenu() - { - if ((waitedDone || clicked) && - menuLoaded) - { - Stop(); - DoOpen?.Invoke(); - } - } - - // When mouse on menu, wait x ms until open it - // meanwhile load content, click opens when loaded - // (to not interrupt user when he moves into a submenu) - public void Start() - { - if (!waitedDone) - { - waitOpen.Start(); - } - } - - public void Stop() - { - clicked = false; - waitedDone = false; - menuLoaded = false; - waitOpen.Stop(); - } - - public void Click() - { - clicked = true; - CheckConditionsToOpenMenu(); - } - - public void MenuLoaded() - { - menuLoaded = true; - CheckConditionsToOpenMenu(); - } - - public void Dispose() - { - Stop(); - waitOpen.Dispose(); - } - } -} \ No newline at end of file diff --git a/Business/WaitToLoadMenu.cs b/Business/WaitToLoadMenu.cs new file mode 100644 index 0000000..66b28fd --- /dev/null +++ b/Business/WaitToLoadMenu.cs @@ -0,0 +1,139 @@ +using System; +using System.Windows.Forms; +using SystemTrayMenu.DataClasses; +using SystemTrayMenu.UserInterface; +using SystemTrayMenu.Utilities; +using Timer = System.Windows.Forms.Timer; + +namespace SystemTrayMenu.Handler +{ + internal class WaitToLoadMenu : IDisposable + { + internal event Action StartLoadMenu; + internal event EventHandlerEmpty StopLoadMenu; + + private readonly Timer timer = new Timer(); + private DataGridView dgv = null; + private int rowIndex = 0; + + private bool mouseActive = false; + private bool checkForMouseActive = false; + + internal WaitToLoadMenu() + { + timer.Interval = 200; + timer.Tick += WaitOpen_Tick; + } + + internal void MouseEnter(object sender, DataGridViewCellEventArgs e) + { + timer.Stop(); + StopLoadMenu.Invoke(); + SetData((DataGridView)sender, e.RowIndex); + checkForMouseActive = true; + timer.Start(); + } + + internal void RowSelected(DataGridView dgv, int rowIndex) + { + timer.Stop(); + StopLoadMenu.Invoke(); + SetData(dgv, rowIndex); + mouseActive = false; + checkForMouseActive = false; + timer.Start(); + } + + internal void MouseLeave(object sender, DataGridViewCellEventArgs e) + { + timer.Stop(); + StopLoadMenu.Invoke(); + ResetData((DataGridView)sender, e.RowIndex); + } + + internal void RowDeselected(int iMenuBefore, int rowIndex, DataGridView dgv) //iMenuBefore not needed + { + timer.Stop(); + StopLoadMenu.Invoke(); + ResetData(dgv, rowIndex); + mouseActive = false; + } + + internal void ClickOpensInstantly(DataGridView dgv, int rowIndex) + { + timer.Stop(); + StopLoadMenu.Invoke(); + SetData(dgv, rowIndex); + mouseActive = true; + checkForMouseActive = false; + CallOpenMenuNow(); + } + + internal void EnterOpensInstantly(DataGridView dgv, int rowIndex) + { + timer.Stop(); + StopLoadMenu.Invoke(); + SetData(dgv, rowIndex); + mouseActive = false; + checkForMouseActive = false; + CallOpenMenuNow(); + } + + internal void MouseMove(object sender, MouseEventArgs e) + { + mouseActive = true; + } + + private void WaitOpen_Tick(object sender, EventArgs e) + { + timer.Stop(); + if (!checkForMouseActive || mouseActive) + { + CallOpenMenuNow(); + } + } + + private void CallOpenMenuNow() + { + if (dgv.Rows.Count > rowIndex) + { + RowData rowData = (RowData)dgv.Rows[rowIndex].Cells[2].Value; + Menu menu = (Menu)dgv.FindForm(); + rowData.MenuLevel = menu.Level; + if (!rowData.IsContextMenuOpen && + rowData.ContainsMenu && + rowData.MenuLevel < MenuDefines.MenusMax) + { + StartLoadMenu.Invoke(rowData); + } + } + } + + private void SetData(DataGridView dgv, int rowIndex) + { + this.dgv = dgv; + this.rowIndex = rowIndex; + RowData rowData = (RowData)dgv.Rows[rowIndex].Cells[2].Value; + rowData.IsSelected = true; + dgv.Rows[rowIndex].Selected = true; + } + + private void ResetData(DataGridView dgv, int rowIndex) + { + if (dgv != null) + { + RowData rowData = (RowData)dgv.Rows[rowIndex].Cells[2].Value; + rowData.IsSelected = false; + dgv.Rows[rowIndex].Selected = false; + this.dgv = null; + this.rowIndex = 0; + } + } + + public void Dispose() + { + timer.Stop(); + timer.Dispose(); + } + } +} \ No newline at end of file diff --git a/Config/Config.cs b/Config/Config.cs index 1f339db..1cb70e6 100644 --- a/Config/Config.cs +++ b/Config/Config.cs @@ -1,6 +1,4 @@ -//using Microsoft.WindowsAPICodePack.Dialogs; -using System; -using System.Diagnostics; +using System.Diagnostics; using System.IO; using System.Reflection; using System.Windows.Forms; diff --git a/Config/MenuDefines.cs b/Config/MenuDefines.cs index 7428cbd..4734c03 100644 --- a/Config/MenuDefines.cs +++ b/Config/MenuDefines.cs @@ -14,11 +14,11 @@ namespace SystemTrayMenu internal static readonly Color ColorTitleSelected = AppColors.Yellow; internal static readonly Color ColorTitleBackground = AppColors.Azure; internal const int KeySearchInterval = 1000; - internal const int LengthMax = 37; internal const int Scrollspeed = 4; - internal const int WaitMenuOpen = 200; - internal const int MenusMax = 50; internal const int TimeUntilClose = 1000; + internal const int MenusMax = 50; + internal const int LengthMax = 37; + internal static float MaxMenuWidth = 300; } internal static class AppColors diff --git a/DataClasses/MenuData.cs b/DataClasses/MenuData.cs index ef9a562..978ddd0 100644 --- a/DataClasses/MenuData.cs +++ b/DataClasses/MenuData.cs @@ -4,15 +4,17 @@ namespace SystemTrayMenu.DataClasses { internal enum MenuDataValidity { + AbortedOrUnknown, Valid, - Invalid, + Empty, NoAccess } internal struct MenuData { - public List RowDatas; - public MenuDataValidity Validity; - public int Level; + internal List RowDatas; + internal MenuDataValidity Validity; + internal int Level; + internal RowData RowDataParent; }; } \ No newline at end of file diff --git a/DataClasses/RowData.cs b/DataClasses/RowData.cs index bcd85c2..04891f4 100644 --- a/DataClasses/RowData.cs +++ b/DataClasses/RowData.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Security; using System.Text; using System.Windows.Forms; -using SystemTrayMenu.Handler; using SystemTrayMenu.Utilities; using TAFactory.IconPack; using Menu = SystemTrayMenu.UserInterface.Menu; @@ -19,18 +18,14 @@ namespace SystemTrayMenu.DataClasses { internal class RowData : IDisposable { - internal event EventHandler OpenMenu; - internal BackgroundWorker Reading = new BackgroundWorker(); internal FileInfo FileInfo; internal Menu SubMenu; + internal bool IsMenuOpen; internal bool IsSelected; - internal bool IsSelectedByKeyboard; internal bool ContainsMenu; internal bool IsContextMenuOpen; private static DateTime ContextMenuClosed; internal bool IsResolvedLnk; - internal bool IsLoading = false; - internal bool RestartLoading = false; internal bool HiddenEntry; internal string TargetFilePath; internal string TargetFilePathOrig; @@ -38,23 +33,17 @@ namespace SystemTrayMenu.DataClasses private string WorkingDirectory; private string Arguments; private string Text; - private readonly WaitMenuOpen waitMenuOpen = new WaitMenuOpen(); private Icon Icon = null; private bool diposeIcon = true; private bool isDisposed = false; + internal int MenuLevel; internal RowData() { - Reading.WorkerSupportsCancellation = true; - waitMenuOpen.DoOpen += WaitMenuOpen_DoOpen; } internal void SetText(string text) { - if (text.Length > MenuDefines.LengthMax) - { - text = $"{text.Substring(0, MenuDefines.LengthMax)}..."; - } Text = text; } @@ -295,16 +284,6 @@ namespace SystemTrayMenu.DataClasses internal void MouseDown(DataGridView dgv, MouseEventArgs e) { - if (ContainsMenu) - { - TriggerMenuOpener(); // Touchscreen - } - - if (IsLoading) - { - waitMenuOpen.Click(); - } - if (e != null && e.Button == MouseButtons.Right && FileInfo != null && @@ -381,43 +360,6 @@ namespace SystemTrayMenu.DataClasses } } - internal void MenuLoaded() - { - waitMenuOpen.MenuLoaded(); - } - - internal void StartMenuOpener() - { - if (ContainsMenu) - { - IsLoading = true; - waitMenuOpen.Start(); - } - } - - private void TriggerMenuOpener() - { - if (ContainsMenu && IsLoading) - { - waitMenuOpen.Start(); - } - } - - internal void StopLoadMenuAndStartWaitToOpenIt() - { - if (ContainsMenu) - { - waitMenuOpen.Stop(); - //IsLoading = false; - } - } - - private void WaitMenuOpen_DoOpen() - { - IsLoading = false; - OpenMenu?.Invoke(this, this); - } - public void Dispose() { Dispose(true); @@ -428,8 +370,6 @@ namespace SystemTrayMenu.DataClasses { if (!isDisposed) { - waitMenuOpen.Dispose(); - Reading.Dispose(); if (diposeIcon) { Icon?.Dispose(); diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index ca0b96f..eb4600d 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -35,5 +35,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("0.10.2.7")] -[assembly: AssemblyFileVersion("0.10.2.7")] +[assembly: AssemblyVersion("0.10.2.8")] +[assembly: AssemblyFileVersion("0.10.2.8")] diff --git a/UserInterface/AppNotifyIcon.cs b/UserInterface/AppNotifyIcon.cs index 9619b98..3c128c9 100644 --- a/UserInterface/AppNotifyIcon.cs +++ b/UserInterface/AppNotifyIcon.cs @@ -20,7 +20,7 @@ namespace SystemTrayMenu.UserInterface private const int Interval60FPS = 16; //60fps=>1s/60fps=~16.6ms private readonly NotifyIcon notifyIcon = new NotifyIcon(); private DateTime timeLoadingStart; - private int threadsLoading = 0; + private bool threadsLoading = false; private readonly Timer load = new Timer(); private int loadCount = 0; private readonly int indexLoad = 0; @@ -85,36 +85,21 @@ namespace SystemTrayMenu.UserInterface load.Dispose(); } - public void LoadingStart(object sender = null, bool reset = false) + public void LoadingStart() { - if (reset) - { - threadsLoading = 0; - } - timeLoadingStart = DateTime.Now; - threadsLoading++; + threadsLoading = true; load.Start(); } public void LoadingStop() { - threadsLoading--; + threadsLoading = false; } - // Show a static icon when mainthread blocked - //public void LoadingStaticWait() - //{ - // notifyIcon.Icon = bitmapsLoading[loadCount++ % indexLoad]; - //} - //public void LoadingStaticStop() - //{ - // notifyIcon.Icon = R.SystemTrayMenu; - //} - private void Load_Tick(object sender, EventArgs e) { - if (threadsLoading > 0) + if (threadsLoading) { if (DateTime.Now - timeLoadingStart > new TimeSpan(0, 0, 0, 0, 500)) { diff --git a/UserInterface/FolderDialog/FolderDialog.cs b/UserInterface/FolderDialog/FolderDialog.cs index 6fbdc09..87e2e33 100644 --- a/UserInterface/FolderDialog/FolderDialog.cs +++ b/UserInterface/FolderDialog/FolderDialog.cs @@ -42,33 +42,30 @@ namespace SystemTrayMenu.UserInterface.FolderDialog } public DialogResult ShowVistaDialog(IWin32Window owner) { - var frm = (NativeMethods.IFileDialog)(new NativeMethods.FileOpenDialogRCW()); - uint options; - frm.GetOptions(out options); + NativeMethods.IFileDialog frm = (NativeMethods.IFileDialog)(new NativeMethods.FileOpenDialogRCW()); + frm.GetOptions(out uint options); options |= NativeMethods.FOS_PICKFOLDERS | NativeMethods.FOS_FORCEFILESYSTEM | NativeMethods.FOS_NOVALIDATE | NativeMethods.FOS_NOTESTFILECREATE | NativeMethods.FOS_DONTADDTORECENT; frm.SetOptions(options); - if (this.InitialFolder != null) + if (InitialFolder != null) { - NativeMethods.IShellItem directoryShellItem; - var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem + Guid riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem if (NativeMethods.SHCreateItemFromParsingName - (this.InitialFolder, IntPtr.Zero, ref riid, - out directoryShellItem) == NativeMethods.S_OK) + (InitialFolder, IntPtr.Zero, ref riid, + out NativeMethods.IShellItem directoryShellItem) == NativeMethods.S_OK) { frm.SetFolder(directoryShellItem); } } - if (this.DefaultFolder != null) + if (DefaultFolder != null) { - NativeMethods.IShellItem directoryShellItem; - var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem + Guid riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem if (NativeMethods.SHCreateItemFromParsingName - (this.DefaultFolder, IntPtr.Zero, ref riid, - out directoryShellItem) == NativeMethods.S_OK) + (DefaultFolder, IntPtr.Zero, ref riid, + out NativeMethods.IShellItem directoryShellItem) == NativeMethods.S_OK) { frm.SetDefaultFolder(directoryShellItem); } @@ -76,18 +73,16 @@ namespace SystemTrayMenu.UserInterface.FolderDialog if (frm.Show(owner.Handle) == NativeMethods.S_OK) { - NativeMethods.IShellItem shellItem; - if (frm.GetResult(out shellItem) == NativeMethods.S_OK) + if (frm.GetResult(out NativeMethods.IShellItem shellItem) == NativeMethods.S_OK) { - IntPtr pszString; if (shellItem.GetDisplayName(NativeMethods.SIGDN_FILESYSPATH, - out pszString) == NativeMethods.S_OK) + out IntPtr pszString) == NativeMethods.S_OK) { if (pszString != IntPtr.Zero) { try { - this.Folder = Marshal.PtrToStringAuto(pszString); + Folder = Marshal.PtrToStringAuto(pszString); return DialogResult.OK; } finally @@ -102,20 +97,20 @@ namespace SystemTrayMenu.UserInterface.FolderDialog } public DialogResult ShowLegacyDialog(IWin32Window owner) { - using (var frm = new SaveFileDialog()) + using (SaveFileDialog frm = new SaveFileDialog()) { frm.CheckFileExists = false; frm.CheckPathExists = true; frm.CreatePrompt = false; frm.Filter = "|" + Guid.Empty.ToString(); frm.FileName = "any"; - if (this.InitialFolder != null) { frm.InitialDirectory = this.InitialFolder; } + if (InitialFolder != null) { frm.InitialDirectory = InitialFolder; } frm.OverwritePrompt = false; frm.Title = "Select Folder"; frm.ValidateNames = false; if (frm.ShowDialog(owner) == DialogResult.OK) { - this.Folder = Path.GetDirectoryName(frm.FileName); + Folder = Path.GetDirectoryName(frm.FileName); return DialogResult.OK; } else @@ -154,12 +149,9 @@ namespace SystemTrayMenu.UserInterface.FolderDialog /// /// Original ptr /// - public IntPtr Handle - { - get { return _hwnd; } - } + public IntPtr Handle => _hwnd; - private IntPtr _hwnd; + private readonly IntPtr _hwnd; } internal static class NativeMethods { diff --git a/UserInterface/Menu.Designer.cs b/UserInterface/Menu.Designer.cs index 5c85a2c..fc4bad4 100644 --- a/UserInterface/Menu.Designer.cs +++ b/UserInterface/Menu.Designer.cs @@ -174,6 +174,7 @@ this.textBoxSearch.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold); this.textBoxSearch.Location = new System.Drawing.Point(25, 4); this.textBoxSearch.Margin = new System.Windows.Forms.Padding(3, 4, 3, 2); + this.textBoxSearch.MaxLength = 37; this.textBoxSearch.Name = "textBoxSearch"; this.textBoxSearch.ShortcutsEnabled = false; this.textBoxSearch.Size = new System.Drawing.Size(55, 15); diff --git a/UserInterface/Menu.cs b/UserInterface/Menu.cs index 576e94f..8d4e2d7 100644 --- a/UserInterface/Menu.cs +++ b/UserInterface/Menu.cs @@ -20,7 +20,6 @@ namespace SystemTrayMenu.UserInterface internal event EventHandler CmdKeyProcessed; internal event EventHandlerEmpty SearchTextChanging; internal event EventHandler SearchTextChanged; -#warning #68 => use event and not a action here? internal bool IsUsable => Visible && !fading.IsHiding && !IsDisposed && !Disposing; @@ -158,23 +157,23 @@ namespace SystemTrayMenu.UserInterface case MenuType.Empty: SetTitle(Translator.GetText("Folder empty")); labelTitle.BackColor = MenuDefines.ColorTitleWarning; - this.pictureBoxSearch.Visible = false; - this.textBoxSearch.Visible = false; - this.tableLayoutPanelSearch.Visible = false; + pictureBoxSearch.Visible = false; + textBoxSearch.Visible = false; + tableLayoutPanelSearch.Visible = false; break; case MenuType.NoAccess: SetTitle(Translator.GetText("Folder inaccessible")); labelTitle.BackColor = MenuDefines.ColorTitleWarning; - this.pictureBoxSearch.Visible = false; - this.textBoxSearch.Visible = false; - this.tableLayoutPanelSearch.Visible = false; + pictureBoxSearch.Visible = false; + textBoxSearch.Visible = false; + tableLayoutPanelSearch.Visible = false; break; case MenuType.MaxReached: SetTitle($"Max {MenuDefines.MenusMax - 1} Menus"); labelTitle.BackColor = MenuDefines.ColorTitleWarning; - this.pictureBoxSearch.Visible = false; - this.textBoxSearch.Visible = false; - this.tableLayoutPanelSearch.Visible = false; + pictureBoxSearch.Visible = false; + textBoxSearch.Visible = false; + tableLayoutPanelSearch.Visible = false; break; case MenuType.Main: break; @@ -275,13 +274,15 @@ namespace SystemTrayMenu.UserInterface { if (directionToRight) { - x = menuPredecessor.Location.X + Width - + x = menuPredecessor.Location.X + + menuPredecessor.Width - (int)Math.Round(Scaling.Factor, 0, MidpointRounding.AwayFromZero); } else { - x = menuPredecessor.Location.X - Width + + x = menuPredecessor.Location.X - + Width + (int)Math.Round(Scaling.Factor, 0, MidpointRounding.AwayFromZero); } @@ -302,17 +303,17 @@ namespace SystemTrayMenu.UserInterface { RowData trigger = (RowData)Tag; DataGridView dgv = menuPredecessor.GetDataGridView(); - int distanceToDgvTop = 0; + int distanceFromItemToDgvTop = 0; if (dgv.Rows.Count > trigger.RowIndex) { Rectangle cellRectangle = dgv.GetCellDisplayRectangle( 0, trigger.RowIndex, false); - distanceToDgvTop = cellRectangle.Top; + distanceFromItemToDgvTop = cellRectangle.Top; } y = menuPredecessor.Location.Y + menuPredecessor.dgv.Location.Y + - distanceToDgvTop; - if ((y + Height) > dgvHeightMax) + distanceFromItemToDgvTop; + if ((y + Height - tableLayoutPanelSearch.Height) > dgvHeightMax) { y = dgvHeightMax - Height + menuRestNeeded; } diff --git a/Utilities/AppRestart.cs b/Utilities/AppRestart.cs index f650db5..7523a75 100644 --- a/Utilities/AppRestart.cs +++ b/Utilities/AppRestart.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Reflection; using System.Runtime.CompilerServices; using System.Windows.Forms; diff --git a/Utilities/DataGridViewExtensions.cs b/Utilities/DataGridViewExtensions.cs index f4a5bdf..dfc63ba 100644 --- a/Utilities/DataGridViewExtensions.cs +++ b/Utilities/DataGridViewExtensions.cs @@ -31,6 +31,10 @@ namespace SystemTrayMenu.Utilities widthMax = checkWidth; } } + if (widthMax > MenuDefines.MaxMenuWidth) + { + widthMax = MenuDefines.MaxMenuWidth; + } dgv.Columns[i].Width = (int)(widthMax + 0.5); string stringWithWidthLikeIcon = "____";