From c462eafa7b47490b8ad76311d7fe6ffb728af939 Mon Sep 17 00:00:00 2001 From: mfkeeq <546357529@qq.com> Date: Fri, 23 Oct 2026 13:48:19 +0800 Subject: [PATCH 1/2] Add a clear recently used item to the file history menu The Last used dropdown now ends with a separator and a localized Clear recently used row that empties the file history list, persists the settings immediately, and rebuilds the menu. The row is disabled when the history is empty. Clearing only touches FileHistoryList; the search, filter, URI, regex and columnizer history lists are untouched. The click handler tells the new row apart from the file entries by reference, so the displayed text is never treated as a path. RemoveFromFileHistory and its HistoryItemRemoveRequested wiring are removed along with it: the right-click-to-remove handler subscribed to the dropdown's MouseUp, which a ToolStripDropDownMenu only raises for clicks outside its items, so the remove path never fired and is dead code. Tests: ClearFileHistoryTests covers clearing, persistence and that other history lists survive; MenuToolbarControllerTests covers the menu layout, the disabled state and that clicking the clear row does not raise HistoryItemClicked. --- src/LogExpert.Configuration/ConfigManager.cs | 12 +- .../Interfaces/IConfigManager.cs | 7 +- src/LogExpert.Resources/Resources.Designer.cs | 9 ++ src/LogExpert.Resources/Resources.de.resx | 3 + src/LogExpert.Resources/Resources.resx | 3 + src/LogExpert.Resources/Resources.zh-CN.resx | 3 + .../ClearFileHistoryTests.cs | 140 ++++++++++++++++++ .../Services/MenuToolbarControllerTests.cs | 39 ++++- .../Dialogs/LogTabWindow/LogTabWindow.cs | 6 +- .../Interface/IMenuToolbarController.cs | 2 +- .../MenuToolbarController.cs | 43 +++--- 11 files changed, 227 insertions(+), 40 deletions(-) create mode 100644 src/LogExpert.Tests/ConfigManagerTests/ClearFileHistoryTests.cs diff --git a/src/LogExpert.Configuration/ConfigManager.cs b/src/LogExpert.Configuration/ConfigManager.cs index 4b80b9e3b..85910330c 100644 --- a/src/LogExpert.Configuration/ConfigManager.cs +++ b/src/LogExpert.Configuration/ConfigManager.cs @@ -315,17 +315,9 @@ public void AddToFileHistory (string fileName) Save(SettingsFlags.FileHistory); } - public void RemoveFromFileHistory (string fileName) + public void ClearFileHistory () { - bool findName (string s) => s.ToUpperInvariant().Equals(fileName.ToUpperInvariant(), StringComparison.Ordinal); - - var index = Instance.Settings.FileHistoryList.FindIndex(findName); - - if (index != -1) - { - Instance.Settings.FileHistoryList.RemoveAt(index); - } - + Instance.Settings.FileHistoryList.Clear(); Save(SettingsFlags.FileHistory); } diff --git a/src/LogExpert.Core/Interfaces/IConfigManager.cs b/src/LogExpert.Core/Interfaces/IConfigManager.cs index ac523dafc..6b1b9fa0d 100644 --- a/src/LogExpert.Core/Interfaces/IConfigManager.cs +++ b/src/LogExpert.Core/Interfaces/IConfigManager.cs @@ -175,12 +175,11 @@ public interface IConfigManager /// This method is supported only on Windows platforms. /// The name of the file to add to the file history list. Comparison is case-insensitive. void AddToFileHistory (string fileName); - + /// - /// Removes the specified file name from the file history list. + /// Removes all entries from the file history list. /// - /// The name of the file to remove from the file history list. Comparison is case-insensitive. - void RemoveFromFileHistory (string fileName); + void ClearFileHistory (); /// /// Clears the list of recently opened files. diff --git a/src/LogExpert.Resources/Resources.Designer.cs b/src/LogExpert.Resources/Resources.Designer.cs index 6bf4905df..5e9f174d8 100644 --- a/src/LogExpert.Resources/Resources.Designer.cs +++ b/src/LogExpert.Resources/Resources.Designer.cs @@ -2383,6 +2383,15 @@ public static string LogTabWindow_UI_ToolStripMenuItem_cellSelectModeToolStripMe } } + /// + /// Looks up a localized string similar to Clear recently used. + /// + public static string LogTabWindow_UI_ToolStripMenuItem_clearFileHistoryToolStripMenuItem { + get { + return ResourceManager.GetString("LogTabWindow_UI_ToolStripMenuItem_clearFileHistoryToolStripMenuItem", resourceCulture); + } + } + /// /// Looks up a localized string similar to Close all tabs. /// diff --git a/src/LogExpert.Resources/Resources.de.resx b/src/LogExpert.Resources/Resources.de.resx index 67ef82bf3..736bc8a47 100644 --- a/src/LogExpert.Resources/Resources.de.resx +++ b/src/LogExpert.Resources/Resources.de.resx @@ -1240,6 +1240,9 @@ Ein ausgewähltes Tool erscheint in der Iconbar. Alle anderen verfügbaren Tools Zuletzt benutzt + + Zuletzt benutzte löschen + Exit diff --git a/src/LogExpert.Resources/Resources.resx b/src/LogExpert.Resources/Resources.resx index bd9b3a535..441e4b87d 100644 --- a/src/LogExpert.Resources/Resources.resx +++ b/src/LogExpert.Resources/Resources.resx @@ -1176,6 +1176,9 @@ Checked tools will appear in the icon bar. All other tools are available in the Last used + + Clear recently used + Exit diff --git a/src/LogExpert.Resources/Resources.zh-CN.resx b/src/LogExpert.Resources/Resources.zh-CN.resx index 9b875814b..58f62e03b 100644 --- a/src/LogExpert.Resources/Resources.zh-CN.resx +++ b/src/LogExpert.Resources/Resources.zh-CN.resx @@ -1046,6 +1046,9 @@ 最近使用 + + 清除最近使用 + 退出 diff --git a/src/LogExpert.Tests/ConfigManagerTests/ClearFileHistoryTests.cs b/src/LogExpert.Tests/ConfigManagerTests/ClearFileHistoryTests.cs new file mode 100644 index 000000000..088afe99d --- /dev/null +++ b/src/LogExpert.Tests/ConfigManagerTests/ClearFileHistoryTests.cs @@ -0,0 +1,140 @@ +using System.Reflection; + +using LogExpert.Core.Config; + +using Newtonsoft.Json; + +using NUnit.Framework; + +namespace LogExpert.Tests.ConfigManagerTests; + +[TestFixture] +public class ClearFileHistoryTests +{ + private string _testDir; + private Configuration.ConfigManager _configManager; + private object? _originalSettings; + private bool _originalIsInitialized; + private string _originalConfigDir; + + [SetUp] + public void SetUp () + { + _configManager = Configuration.ConfigManager.Instance; + _originalIsInitialized = GetPrivateField("_isInitialized") is true; + _originalSettings = GetPrivateField("_settings"); + _originalConfigDir = _configManager.ConfigDir; + + _testDir = Path.Join(Path.GetTempPath(), "LogExpert_ClearHistoryTest_" + Guid.NewGuid().ToString("N")); + _ = Directory.CreateDirectory(_testDir); + + ResetConfigManagerInitialization(); + _configManager.Initialize(_testDir, new Rectangle(0, 0, 1920, 1080)); + _configManager.ConfigDir = _testDir; + } + + [TearDown] + public void TearDown () + { + SetPrivateField("_isInitialized", _originalIsInitialized); + SetPrivateField("_settings", _originalSettings); + _configManager.ConfigDir = _originalConfigDir; + + try + { + if (Directory.Exists(_testDir)) + { + Directory.Delete(_testDir, recursive: true); + } + } + catch (IOException) + { + // Ignore IO errors during cleanup + } + catch (UnauthorizedAccessException) + { + // Ignore access errors during cleanup + } + } + + [Test] + public void ClearFileHistory_RemovesAllEntries () + { + _configManager.AddToFileHistory(@"C:\logs\one.log"); + _configManager.AddToFileHistory(@"C:\logs\two.log"); + Assert.That(_configManager.Settings.FileHistoryList, Has.Count.EqualTo(2)); + + _configManager.ClearFileHistory(); + + Assert.That(_configManager.Settings.FileHistoryList, Is.Empty); + } + + [Test] + public void ClearFileHistory_WritesEmptyListToSettingsFile () + { + _configManager.AddToFileHistory(@"C:\logs\one.log"); + + _configManager.ClearFileHistory(); + + var settingsFile = Path.Join(_configManager.ActiveConfigDir, "settings.json"); + Assert.That(File.Exists(settingsFile), Is.True); + + var saved = JsonConvert.DeserializeObject(File.ReadAllText(settingsFile)); + Assert.That(saved, Is.Not.Null); + Assert.That(saved.FileHistoryList, Is.Empty); + } + + [Test] + public void ClearFileHistory_LeavesOtherHistoryListsUntouched () + { + _configManager.AddToFileHistory(@"C:\logs\one.log"); + _configManager.Settings.LastOpenFilesList.Add(@"C:\logs\one.log"); + _configManager.Settings.SearchHistoryList.Add("error"); + _configManager.Settings.FilterHistoryList.Add("WARN"); + _configManager.Settings.FilterRangeHistoryList.Add("10"); + _configManager.Settings.UriHistoryList.Add("https://example.org/one.log"); + _configManager.Settings.RegexHistory.ExpressionHistoryList.Add(@"^\d+"); + _configManager.Settings.RegexHistory.TesttextHistoryList.Add("42"); + _configManager.Settings.ColumnizerHistoryList.Add(new ColumnizerHistoryEntry(@"C:\logs\one.log", "JsonColumnizer")); + + _configManager.ClearFileHistory(); + + Assert.Multiple(() => + { + Assert.That(_configManager.Settings.FileHistoryList, Is.Empty); + Assert.That(_configManager.Settings.LastOpenFilesList, Has.Count.EqualTo(1)); + Assert.That(_configManager.Settings.SearchHistoryList, Has.Count.EqualTo(1)); + Assert.That(_configManager.Settings.FilterHistoryList, Has.Count.EqualTo(1)); + Assert.That(_configManager.Settings.FilterRangeHistoryList, Has.Count.EqualTo(1)); + Assert.That(_configManager.Settings.UriHistoryList, Has.Count.EqualTo(1)); + Assert.That(_configManager.Settings.RegexHistory.ExpressionHistoryList, Has.Count.EqualTo(1)); + Assert.That(_configManager.Settings.RegexHistory.TesttextHistoryList, Has.Count.EqualTo(1)); + Assert.That(_configManager.Settings.ColumnizerHistoryList, Has.Count.EqualTo(1)); + }); + } + + /// + /// Resets the singleton so each test loads its settings from its own temp directory. + /// + private void ResetConfigManagerInitialization () + { + SetPrivateField("_isInitialized", false); + SetPrivateField("_settings", null); + } + + private object? GetPrivateField (string fieldName) + { + return Field(fieldName).GetValue(_configManager); + } + + private void SetPrivateField (string fieldName, object? value) + { + Field(fieldName).SetValue(_configManager, value); + } + + private static FieldInfo Field (string fieldName) + { + return typeof(Configuration.ConfigManager).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance) + ?? throw new InvalidOperationException($"ConfigManager.{fieldName} is gone; update this fixture's reset."); + } +} diff --git a/src/LogExpert.UI.Tests/Services/MenuToolbarControllerTests.cs b/src/LogExpert.UI.Tests/Services/MenuToolbarControllerTests.cs index f41d720df..69cb6f813 100644 --- a/src/LogExpert.UI.Tests/Services/MenuToolbarControllerTests.cs +++ b/src/LogExpert.UI.Tests/Services/MenuToolbarControllerTests.cs @@ -268,8 +268,45 @@ public void PopulateFileHistory_CreatesMenuItems () _controller.PopulateFileHistory(history); var lastUsed = FindMenuItem("lastUsedToolStripMenuItem"); - Assert.That(lastUsed.DropDownItems, Has.Count.EqualTo(2)); + Assert.That(lastUsed.DropDownItems, Has.Count.EqualTo(4)); Assert.That(lastUsed.DropDownItems[0].Text, Is.EqualTo(@"C:\log1.txt")); + Assert.That(lastUsed.DropDownItems[2], Is.TypeOf()); + Assert.That(lastUsed.DropDownItems[3].Name, Is.EqualTo("clearFileHistoryToolStripMenuItem")); + Assert.That(lastUsed.DropDownItems[3].Enabled, Is.True); + } + + [Test] + public void PopulateFileHistory_DisablesClearItem_WhenHistoryIsEmpty () + { + _controller.PopulateFileHistory([]); + + var lastUsed = FindMenuItem("lastUsedToolStripMenuItem"); + Assert.That(lastUsed.DropDownItems, Has.Count.EqualTo(1)); + Assert.That(lastUsed.DropDownItems[0].Name, Is.EqualTo("clearFileHistoryToolStripMenuItem")); + Assert.That(lastUsed.DropDownItems[0].Enabled, Is.False); + } + + [Test] + public void ClearHistoryItem_RaisesHistoryClearRequested_AndNotFileClicked () + { + var clearRequested = 0; + string clickedFile = null; + _controller.HistoryClearRequested += (_, _) => clearRequested++; + _controller.HistoryItemClicked += (_, e) => clickedFile = e.FileName; + + _controller.PopulateFileHistory(["test.log"]); + var lastUsed = FindMenuItem("lastUsedToolStripMenuItem"); + var clearItem = lastUsed.DropDownItems[^1]; + + var args = new ToolStripItemClickedEventArgs(clearItem); + var onItemClicked = typeof(ToolStrip).GetMethod("OnItemClicked", BindingFlags.Instance | BindingFlags.NonPublic); + _ = onItemClicked.Invoke(lastUsed.DropDown, [args]); + + Assert.Multiple(() => + { + Assert.That(clearRequested, Is.EqualTo(1)); + Assert.That(clickedFile, Is.Null); + }); } [Test] diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs index 5ec992ae2..1f122e5dc 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs @@ -235,7 +235,7 @@ private void OnFileOperationServiceFileOpened (object? sender, FileOpenedEventAr private void InitializeMenuToolbarControllerEvents () { _menuToolbarController.HistoryItemClicked += OnMenuControllerHistoryItemClicked; - _menuToolbarController.HistoryItemRemoveRequested += OnMenuControllerHistoryItemRemoveRequested; + _menuToolbarController.HistoryClearRequested += OnMenuControllerHistoryClearRequested; _menuToolbarController.HighlightGroupSelected += OnMenuControllerHighlightGroupSelected; } @@ -244,9 +244,9 @@ private void OnMenuControllerHighlightGroupSelected (object? sender, HighlightGr CurrentLogWindow?.SetCurrentHighlightGroup(e.GroupName); } - private void OnMenuControllerHistoryItemRemoveRequested (object? sender, HistoryItemClickedEventArgs e) + private void OnMenuControllerHistoryClearRequested (object? sender, EventArgs e) { - ConfigManager.RemoveFromFileHistory(e.FileName); + ConfigManager.ClearFileHistory(); FillHistoryMenu(); } diff --git a/src/LogExpert.UI/Interface/IMenuToolbarController.cs b/src/LogExpert.UI/Interface/IMenuToolbarController.cs index da3a921a3..5cf761a1f 100644 --- a/src/LogExpert.UI/Interface/IMenuToolbarController.cs +++ b/src/LogExpert.UI/Interface/IMenuToolbarController.cs @@ -52,6 +52,6 @@ void InitializeMenus (MenuStrip mainMenu, ToolStrip buttonToolbar, ToolStrip ext // Events event EventHandler HistoryItemClicked; - event EventHandler HistoryItemRemoveRequested; + event EventHandler HistoryClearRequested; event EventHandler HighlightGroupSelected; } diff --git a/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs b/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs index 5e1644f07..a2f5cae2c 100644 --- a/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs +++ b/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs @@ -49,13 +49,16 @@ internal sealed class MenuToolbarController : IMenuToolbarController // History menu private ToolStripMenuItem _lastUsedMenuItem; + // The "clear recently used" row of the history dropdown + private ToolStripMenuItem _clearHistoryItem; + private readonly SynchronizationContext _uiContext; private readonly int _uiThreadId = Environment.CurrentManagedThreadId; private bool _disposed; private bool _suppressEvents; public event EventHandler HistoryItemClicked; - public event EventHandler HistoryItemRemoveRequested; + public event EventHandler HistoryClearRequested; public event EventHandler HighlightGroupSelected; public MenuToolbarController () @@ -265,7 +268,6 @@ public void PopulateFileHistory (IEnumerable fileHistory) if (_lastUsedMenuItem.DropDown != null) { _lastUsedMenuItem.DropDown.ItemClicked -= OnHistoryMenuItemClicked; - _lastUsedMenuItem.DropDown.MouseUp -= OnHistoryStripMouseUp; } var strip = new ToolStripDropDownMenu(); @@ -275,8 +277,20 @@ public void PopulateFileHistory (IEnumerable fileHistory) _ = strip.Items.Add(new ToolStripMenuItem(file)); } + if (strip.Items.Count > 0) + { + _ = strip.Items.Add(new ToolStripSeparator()); + } + + _clearHistoryItem = new ToolStripMenuItem(Resources.LogTabWindow_UI_ToolStripMenuItem_clearFileHistoryToolStripMenuItem) + { + Name = "clearFileHistoryToolStripMenuItem", + Enabled = strip.Items.Count > 0 + }; + + _ = strip.Items.Add(_clearHistoryItem); + strip.ItemClicked += OnHistoryMenuItemClicked; - strip.MouseUp += OnHistoryStripMouseUp; _lastUsedMenuItem.DropDown = strip; } @@ -302,28 +316,16 @@ private void OnHighlightGroupComboSelectedIndexChanged (object sender, EventArgs private void OnHistoryMenuItemClicked (object sender, ToolStripItemClickedEventArgs e) { - var fileName = e.ClickedItem?.Text; - if (!string.IsNullOrEmpty(fileName)) - { - HistoryItemClicked?.Invoke(this, new HistoryItemClickedEventArgs(fileName)); - } - } - - private void OnHistoryStripMouseUp (object sender, MouseEventArgs e) - { - // Right-click to remove from history (preserves existing LogTabWindow behavior) - if (e.Button != MouseButtons.Right) + if (ReferenceEquals(e.ClickedItem, _clearHistoryItem)) { + HistoryClearRequested?.Invoke(this, EventArgs.Empty); return; } - if (sender is ToolStripDropDownMenu strip) + var fileName = e.ClickedItem?.Text; + if (!string.IsNullOrEmpty(fileName)) { - var item = strip.GetItemAt(e.Location); - if (item != null && !string.IsNullOrEmpty(item.Text)) - { - HistoryItemRemoveRequested?.Invoke(this, new HistoryItemClickedEventArgs(item.Text)); - } + HistoryItemClicked?.Invoke(this, new HistoryItemClickedEventArgs(fileName)); } } @@ -415,7 +417,6 @@ public void Dispose () if (_lastUsedMenuItem?.DropDown != null) { _lastUsedMenuItem.DropDown.ItemClicked -= OnHistoryMenuItemClicked; - _lastUsedMenuItem.DropDown.MouseUp -= OnHistoryStripMouseUp; } _disposed = true; From 664bb752d9cdc1986dba07f667d150bb1dc37f03 Mon Sep 17 00:00:00 2001 From: mfkeeq <546357529@qq.com> Date: Fri, 23 Oct 2026 15:28:59 +0800 Subject: [PATCH 2/2] Polish clear-file-history per review feedback - Rename _clearHistoryItem to _clearFileHistoryItem and HistoryClearRequested to FileHistoryClearRequested so the field, the event and the control name share one term - Drop the redundant comment on the clear item field - Take _loadSaveLock around the list clear in ClearFileHistory to match the neighbouring ClearLastOpenFilesList - Remove the explicit FillHistoryMenu call after ClearFileHistory; the Save -> ConfigChanged -> ApplySettings path already rebuilds the menu - Share the ConfigManager reset helper between test fixtures via ConfigManagerTestReflection (fail-fast variant) - Revert the whitespace-only change in IConfigManager.cs - Update the stale PopulateFileHistory doc comment: right-click removal was removed earlier in this branch --- src/LogExpert.Configuration/ConfigManager.cs | 6 ++- .../Interfaces/IConfigManager.cs | 2 +- .../ClearFileHistoryTests.cs | 37 +++-------------- .../ConfigManagerPortableModeTests.cs | 19 ++------- .../ConfigManagerTestReflection.cs | 40 +++++++++++++++++++ .../Services/MenuToolbarControllerTests.cs | 4 +- .../Dialogs/LogTabWindow/LogTabWindow.cs | 7 ++-- .../Interface/IMenuToolbarController.cs | 8 ++-- .../MenuToolbarController.cs | 13 +++--- 9 files changed, 71 insertions(+), 65 deletions(-) create mode 100644 src/LogExpert.Tests/ConfigManagerTests/ConfigManagerTestReflection.cs diff --git a/src/LogExpert.Configuration/ConfigManager.cs b/src/LogExpert.Configuration/ConfigManager.cs index 85910330c..f73c2c57f 100644 --- a/src/LogExpert.Configuration/ConfigManager.cs +++ b/src/LogExpert.Configuration/ConfigManager.cs @@ -317,7 +317,11 @@ public void AddToFileHistory (string fileName) public void ClearFileHistory () { - Instance.Settings.FileHistoryList.Clear(); + lock (_loadSaveLock) + { + Instance.Settings.FileHistoryList.Clear(); + } + Save(SettingsFlags.FileHistory); } diff --git a/src/LogExpert.Core/Interfaces/IConfigManager.cs b/src/LogExpert.Core/Interfaces/IConfigManager.cs index 6b1b9fa0d..8d430811a 100644 --- a/src/LogExpert.Core/Interfaces/IConfigManager.cs +++ b/src/LogExpert.Core/Interfaces/IConfigManager.cs @@ -175,7 +175,7 @@ public interface IConfigManager /// This method is supported only on Windows platforms. /// The name of the file to add to the file history list. Comparison is case-insensitive. void AddToFileHistory (string fileName); - + /// /// Removes all entries from the file history list. /// diff --git a/src/LogExpert.Tests/ConfigManagerTests/ClearFileHistoryTests.cs b/src/LogExpert.Tests/ConfigManagerTests/ClearFileHistoryTests.cs index 088afe99d..7b24ebb0d 100644 --- a/src/LogExpert.Tests/ConfigManagerTests/ClearFileHistoryTests.cs +++ b/src/LogExpert.Tests/ConfigManagerTests/ClearFileHistoryTests.cs @@ -1,5 +1,3 @@ -using System.Reflection; - using LogExpert.Core.Config; using Newtonsoft.Json; @@ -21,14 +19,14 @@ public class ClearFileHistoryTests public void SetUp () { _configManager = Configuration.ConfigManager.Instance; - _originalIsInitialized = GetPrivateField("_isInitialized") is true; - _originalSettings = GetPrivateField("_settings"); + _originalIsInitialized = ConfigManagerTestReflection.GetPrivateField(_configManager, "_isInitialized") is true; + _originalSettings = ConfigManagerTestReflection.GetPrivateField(_configManager, "_settings"); _originalConfigDir = _configManager.ConfigDir; _testDir = Path.Join(Path.GetTempPath(), "LogExpert_ClearHistoryTest_" + Guid.NewGuid().ToString("N")); _ = Directory.CreateDirectory(_testDir); - ResetConfigManagerInitialization(); + ConfigManagerTestReflection.ResetConfigManagerInitialization(_configManager); _configManager.Initialize(_testDir, new Rectangle(0, 0, 1920, 1080)); _configManager.ConfigDir = _testDir; } @@ -36,8 +34,8 @@ public void SetUp () [TearDown] public void TearDown () { - SetPrivateField("_isInitialized", _originalIsInitialized); - SetPrivateField("_settings", _originalSettings); + ConfigManagerTestReflection.SetPrivateField(_configManager, "_isInitialized", _originalIsInitialized); + ConfigManagerTestReflection.SetPrivateField(_configManager, "_settings", _originalSettings); _configManager.ConfigDir = _originalConfigDir; try @@ -112,29 +110,4 @@ public void ClearFileHistory_LeavesOtherHistoryListsUntouched () Assert.That(_configManager.Settings.ColumnizerHistoryList, Has.Count.EqualTo(1)); }); } - - /// - /// Resets the singleton so each test loads its settings from its own temp directory. - /// - private void ResetConfigManagerInitialization () - { - SetPrivateField("_isInitialized", false); - SetPrivateField("_settings", null); - } - - private object? GetPrivateField (string fieldName) - { - return Field(fieldName).GetValue(_configManager); - } - - private void SetPrivateField (string fieldName, object? value) - { - Field(fieldName).SetValue(_configManager, value); - } - - private static FieldInfo Field (string fieldName) - { - return typeof(Configuration.ConfigManager).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance) - ?? throw new InvalidOperationException($"ConfigManager.{fieldName} is gone; update this fixture's reset."); - } } diff --git a/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerPortableModeTests.cs b/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerPortableModeTests.cs index bb98741f1..117298347 100644 --- a/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerPortableModeTests.cs +++ b/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerPortableModeTests.cs @@ -26,7 +26,7 @@ public void SetUp () _configManager = Configuration.ConfigManager.Instance; // Reset the singleton's initialization state using reflection - ResetConfigManagerInitialization(); + ConfigManagerTestReflection.ResetConfigManagerInitialization(_configManager); _configManager.Initialize(_testDir, new Rectangle(0, 0, 1920, 1080)); } @@ -64,19 +64,6 @@ public void TearDown () #region Helper Methods - /// - /// Resets ConfigManager singleton initialization state via reflection. - /// - private void ResetConfigManagerInitialization () - { - var isInitializedField = typeof(Configuration.ConfigManager).GetField("_isInitialized", BindingFlags.NonPublic | BindingFlags.Instance); - isInitializedField?.SetValue(_configManager, false); - - // Reset settings so they reload from the new path - var settingsField = typeof(Configuration.ConfigManager).GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance); - settingsField?.SetValue(_configManager, null); - } - /// /// Invokes a private instance method using reflection. /// @@ -758,7 +745,7 @@ public void Load_NewPortableLayout_DetectedCorrectly () CreateSettingsFile(_configManager.PortableConfigDir); // Reset settings to force reload - ResetConfigManagerInitialization(); + ConfigManagerTestReflection.ResetConfigManagerInitialization(_configManager); _configManager.Initialize(_testDir, new Rectangle(0, 0, 1920, 1080)); // Act - Access Settings to trigger Load() @@ -783,7 +770,7 @@ public void Load_OldPortableLayout_TriggersMigration () """{ "Preferences": { "FontName": "OldLayout" }, "FilterList": [] }"""); // Reset settings to force reload - ResetConfigManagerInitialization(); + ConfigManagerTestReflection.ResetConfigManagerInitialization(_configManager); _configManager.Initialize(_testDir, new Rectangle(0, 0, 1920, 1080)); // Act - Access Settings to trigger Load() which should migrate diff --git a/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerTestReflection.cs b/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerTestReflection.cs new file mode 100644 index 000000000..5b2f8084e --- /dev/null +++ b/src/LogExpert.Tests/ConfigManagerTests/ConfigManagerTestReflection.cs @@ -0,0 +1,40 @@ +using System.Reflection; + +using LogExpert.Core.Config; + +namespace LogExpert.Tests.ConfigManagerTests; + +/// +/// Shared reflection helpers for fixtures that reset the ConfigManager singleton +/// between tests. +/// +internal static class ConfigManagerTestReflection +{ + /// + /// Resets the ConfigManager singleton's initialization state so the next + /// Initialize call reloads settings from its own directory. + /// + internal static void ResetConfigManagerInitialization (Configuration.ConfigManager configManager) + { + SetPrivateField(configManager, "_isInitialized", false); + + // Reset settings so they reload from the new path + SetPrivateField(configManager, "_settings", null); + } + + internal static object? GetPrivateField (Configuration.ConfigManager configManager, string fieldName) + { + return Field(fieldName).GetValue(configManager); + } + + internal static void SetPrivateField (Configuration.ConfigManager configManager, string fieldName, object? value) + { + Field(fieldName).SetValue(configManager, value); + } + + private static FieldInfo Field (string fieldName) + { + return typeof(Configuration.ConfigManager).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance) + ?? throw new InvalidOperationException($"ConfigManager.{fieldName} is gone; update the shared reset helper."); + } +} diff --git a/src/LogExpert.UI.Tests/Services/MenuToolbarControllerTests.cs b/src/LogExpert.UI.Tests/Services/MenuToolbarControllerTests.cs index 69cb6f813..c97bd2463 100644 --- a/src/LogExpert.UI.Tests/Services/MenuToolbarControllerTests.cs +++ b/src/LogExpert.UI.Tests/Services/MenuToolbarControllerTests.cs @@ -287,11 +287,11 @@ public void PopulateFileHistory_DisablesClearItem_WhenHistoryIsEmpty () } [Test] - public void ClearHistoryItem_RaisesHistoryClearRequested_AndNotFileClicked () + public void ClearHistoryItem_RaisesFileHistoryClearRequested_AndNotFileClicked () { var clearRequested = 0; string clickedFile = null; - _controller.HistoryClearRequested += (_, _) => clearRequested++; + _controller.FileHistoryClearRequested += (_, _) => clearRequested++; _controller.HistoryItemClicked += (_, e) => clickedFile = e.FileName; _controller.PopulateFileHistory(["test.log"]); diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs index 1f122e5dc..3b1218b35 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs @@ -235,7 +235,7 @@ private void OnFileOperationServiceFileOpened (object? sender, FileOpenedEventAr private void InitializeMenuToolbarControllerEvents () { _menuToolbarController.HistoryItemClicked += OnMenuControllerHistoryItemClicked; - _menuToolbarController.HistoryClearRequested += OnMenuControllerHistoryClearRequested; + _menuToolbarController.FileHistoryClearRequested += OnMenuControllerFileHistoryClearRequested; _menuToolbarController.HighlightGroupSelected += OnMenuControllerHighlightGroupSelected; } @@ -244,10 +244,11 @@ private void OnMenuControllerHighlightGroupSelected (object? sender, HighlightGr CurrentLogWindow?.SetCurrentHighlightGroup(e.GroupName); } - private void OnMenuControllerHistoryClearRequested (object? sender, EventArgs e) + private void OnMenuControllerFileHistoryClearRequested (object? sender, EventArgs e) { + // ClearFileHistory persists via Save, whose ConfigChanged event reaches + // ApplySettings and rebuilds the history menu from there. ConfigManager.ClearFileHistory(); - FillHistoryMenu(); } private void OnMenuControllerHistoryItemClicked (object? sender, HistoryItemClickedEventArgs e) diff --git a/src/LogExpert.UI/Interface/IMenuToolbarController.cs b/src/LogExpert.UI/Interface/IMenuToolbarController.cs index 5cf761a1f..8bd491d81 100644 --- a/src/LogExpert.UI/Interface/IMenuToolbarController.cs +++ b/src/LogExpert.UI/Interface/IMenuToolbarController.cs @@ -45,13 +45,15 @@ void InitializeMenus (MenuStrip mainMenu, ToolStrip buttonToolbar, ToolStrip ext void UpdateHighlightGroups (IEnumerable groups, string selectedGroup); /// - /// Populates file history menu with recent files. - /// Includes right-click removal support. + /// Populates the file history menu with recent files, followed by a + /// separator and a "clear recently used" row that raises + /// when clicked. + /// The clear row is disabled when the history is empty. /// void PopulateFileHistory (IEnumerable fileHistory); // Events event EventHandler HistoryItemClicked; - event EventHandler HistoryClearRequested; + event EventHandler FileHistoryClearRequested; event EventHandler HighlightGroupSelected; } diff --git a/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs b/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs index a2f5cae2c..55e6a30f8 100644 --- a/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs +++ b/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs @@ -49,8 +49,7 @@ internal sealed class MenuToolbarController : IMenuToolbarController // History menu private ToolStripMenuItem _lastUsedMenuItem; - // The "clear recently used" row of the history dropdown - private ToolStripMenuItem _clearHistoryItem; + private ToolStripMenuItem _clearFileHistoryItem; private readonly SynchronizationContext _uiContext; private readonly int _uiThreadId = Environment.CurrentManagedThreadId; @@ -58,7 +57,7 @@ internal sealed class MenuToolbarController : IMenuToolbarController private bool _suppressEvents; public event EventHandler HistoryItemClicked; - public event EventHandler HistoryClearRequested; + public event EventHandler FileHistoryClearRequested; public event EventHandler HighlightGroupSelected; public MenuToolbarController () @@ -282,13 +281,13 @@ public void PopulateFileHistory (IEnumerable fileHistory) _ = strip.Items.Add(new ToolStripSeparator()); } - _clearHistoryItem = new ToolStripMenuItem(Resources.LogTabWindow_UI_ToolStripMenuItem_clearFileHistoryToolStripMenuItem) + _clearFileHistoryItem = new ToolStripMenuItem(Resources.LogTabWindow_UI_ToolStripMenuItem_clearFileHistoryToolStripMenuItem) { Name = "clearFileHistoryToolStripMenuItem", Enabled = strip.Items.Count > 0 }; - _ = strip.Items.Add(_clearHistoryItem); + _ = strip.Items.Add(_clearFileHistoryItem); strip.ItemClicked += OnHistoryMenuItemClicked; _lastUsedMenuItem.DropDown = strip; @@ -316,9 +315,9 @@ private void OnHighlightGroupComboSelectedIndexChanged (object sender, EventArgs private void OnHistoryMenuItemClicked (object sender, ToolStripItemClickedEventArgs e) { - if (ReferenceEquals(e.ClickedItem, _clearHistoryItem)) + if (ReferenceEquals(e.ClickedItem, _clearFileHistoryItem)) { - HistoryClearRequested?.Invoke(this, EventArgs.Empty); + FileHistoryClearRequested?.Invoke(this, EventArgs.Empty); return; }