diff --git a/src/LogExpert.Configuration/ConfigManager.cs b/src/LogExpert.Configuration/ConfigManager.cs
index d3fa4903..63d918c4 100644
--- a/src/LogExpert.Configuration/ConfigManager.cs
+++ b/src/LogExpert.Configuration/ConfigManager.cs
@@ -281,6 +281,22 @@ public void AddToFileHistory (string fileName)
Save(SettingsFlags.FileHistory);
}
+ [SupportedOSPlatform("windows")]
+ public void RemoveFromFileHistory (string fileName)
+ {
+ 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);
+ }
+
+ Save(SettingsFlags.FileHistory);
+ }
+
+
public void ClearLastOpenFilesList ()
{
lock (_loadSaveLock)
diff --git a/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderBase.cs b/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderBase.cs
index ba09f0fc..bfb53aa0 100644
--- a/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderBase.cs
+++ b/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderBase.cs
@@ -47,7 +47,7 @@ protected PositionAwareStreamReaderBase (Stream stream, EncodingOptions encoding
MaximumLineLength = maximumLineLength;
- _preambleLength = DetectPreambleLengthAndEncoding(out var detectedEncoding);
+ (_preambleLength, Encoding? detectedEncoding) = DetectPreambleLength(_stream);
var usedEncoding = DetermineEncoding(encodingOptions, detectedEncoding);
_posIncPrecomputed = GetPosIncPrecomputed(usedEncoding);
@@ -165,11 +165,19 @@ protected void MovePosition (int offset)
#region Private Methods
+ public static Encoding DetermineEncoding (EncodingOptions options, Encoding detectedEncoding)
+ {
+ return options?.Encoding != null
+ ? options.Encoding
+ : detectedEncoding ?? options?.DefaultEncoding ?? Encoding.Default;
+ }
+
///
- /// Determines the actual number of preamble bytes in the file.
+ /// Determines the actual number of preamble bytes in the file and the Encoding.
///
- /// Number of preamble bytes in the file
- private int DetectPreambleLengthAndEncoding (out Encoding detectedEncoding)
+ ///
+ /// Number of preamble bytes in the file and the Encoding if there is one
+ public static (int length, Encoding? detectedEncoding) DetectPreambleLength (Stream stream)
{
/*
UTF-8: EF BB BF
@@ -179,31 +187,15 @@ private int DetectPreambleLengthAndEncoding (out Encoding detectedEncoding)
UTF-32-Little-Endian-Byteorder: FF FE 00 00
*/
- var (length, encoding) = DetectPreambleLength(_stream);
- // not found or less than 2 byte read
- detectedEncoding = encoding;
-
- return length;
- }
-
- public static Encoding DetermineEncoding (EncodingOptions options, Encoding detectedEncoding)
- {
- return options?.Encoding != null
- ? options.Encoding
- : detectedEncoding ?? options?.DefaultEncoding ?? Encoding.Default;
- }
-
- public static (int length, Encoding? detectedEncoding) DetectPreambleLength (Stream stream)
- {
if (!stream.CanSeek)
{
return (0, null);
}
var originalPos = stream.Position;
- var buffer = new byte[4];
+ Span buffer = stackalloc byte[4];
_ = stream.Seek(0, SeekOrigin.Begin);
- var readBytes = stream.Read(buffer, 0, buffer.Length);
+ var readBytes = stream.Read(buffer);
_ = stream.Seek(originalPos, SeekOrigin.Begin);
if (readBytes >= 2)
diff --git a/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderSystem.cs b/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderSystem.cs
index 0f3cc994..a1b4e95b 100644
--- a/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderSystem.cs
+++ b/src/LogExpert.Core/Classes/Log/PositionAwareStreamReaderSystem.cs
@@ -125,11 +125,15 @@ private int GuessNewLineSequenceLength (StreamReader reader)
var secondChar = reader.Read();
if (secondChar == CHAR_LF) // check \n
{
- return Encoding.GetByteCount("\r\n");
+ // Use stackalloc or SpanOwner instead of string
+ Span newline = ['\r', '\n'];
+ return Encoding.GetByteCount(newline);
+ //return Encoding.GetByteCount("\r\n");
}
}
- return Encoding.GetByteCount(((char)firstChar).ToString());
+ Span singleChar = [(char)firstChar];
+ return Encoding.GetByteCount(singleChar);
}
return 0;
diff --git a/src/LogExpert.Core/Interface/IConfigManager.cs b/src/LogExpert.Core/Interface/IConfigManager.cs
index a436cb23..9e1d34fa 100644
--- a/src/LogExpert.Core/Interface/IConfigManager.cs
+++ b/src/LogExpert.Core/Interface/IConfigManager.cs
@@ -151,6 +151,12 @@ 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.
+ ///
+ /// The name of the file to remove from the file history list. Comparison is case-insensitive.
+ void RemoveFromFileHistory (string fileName);
///
/// Clears the list of recently opened files.
diff --git a/src/LogExpert.Tests/Services/LedIndicatorServiceTests.cs b/src/LogExpert.Tests/Services/LedIndicatorServiceTests.cs
index 9153e21a..c44f3741 100644
--- a/src/LogExpert.Tests/Services/LedIndicatorServiceTests.cs
+++ b/src/LogExpert.Tests/Services/LedIndicatorServiceTests.cs
@@ -1,6 +1,7 @@
using System.Runtime.Versioning;
-using LogExpert.UI.Services;
+using LogExpert.UI.Interface.Services;
+using LogExpert.UI.Services.LedService;
using NUnit.Framework;
@@ -9,11 +10,12 @@ namespace LogExpert.Tests.Services;
[TestFixture]
[Apartment(ApartmentState.STA)] // Required for UI components
[SupportedOSPlatform("windows")]
-public class LedIndicatorServiceTests
+public class LedIndicatorServiceTests : IDisposable
{
private LedIndicatorService? _service;
private ApplicationContext? _appContext;
private WindowsFormsSynchronizationContext? _syncContext;
+ private bool _disposed;
[SetUp]
public void Setup ()
@@ -140,9 +142,9 @@ public void StartStop_DoesNotThrowException ()
_service!.Initialize(Color.Blue);
// Act
- _service.Start();
+ _service.StartService();
Thread.Sleep(500); // Let timer tick a few times
- _service.Stop();
+ _service.StopService();
// Assert - no exception
Assert.That(true, Is.True, "Service started and stopped without exceptions");
@@ -192,7 +194,7 @@ public void Dispose_DisposesAllResources ()
{
// Arrange
_service!.Initialize(Color.Blue);
- _service.Start();
+ _service.StartService();
// Act
_service.Dispose();
@@ -217,7 +219,7 @@ public void Start_WithoutInitialize_ThrowsException ()
// Arrange - don't initialize
// Act & Assert
- _ = Assert.Throws(() => _service!.Start());
+ _ = Assert.Throws(() => _service!.StartService());
}
[Test]
@@ -316,4 +318,25 @@ public void GetIcon_WithSyncedState_ReturnsSyncedIcon ()
// The icons should be different (synced has blue indicator on left side)
Assert.That(iconSynced, Is.Not.EqualTo(iconNotSynced));
}
+
+ public void Dispose ()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose (bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ _service?.Dispose();
+ }
+
+ _disposed = true;
+ }
}
\ No newline at end of file
diff --git a/src/LogExpert.Tests/Services/MenuToolbarControllerTests.cs b/src/LogExpert.Tests/Services/MenuToolbarControllerTests.cs
new file mode 100644
index 00000000..1a60cc6d
--- /dev/null
+++ b/src/LogExpert.Tests/Services/MenuToolbarControllerTests.cs
@@ -0,0 +1,426 @@
+using System.Reflection;
+using System.Runtime.Versioning;
+using System.Text;
+using System.Windows.Forms;
+
+using LogExpert.Core.EventArguments;
+using LogExpert.Dialogs;
+using LogExpert.UI.Controls;
+using LogExpert.UI.Services;
+using LogExpert.UI.Services.MenuToolbarService;
+
+using NUnit.Framework;
+
+namespace LogExpert.Tests.Services;
+
+[TestFixture]
+[SupportedOSPlatform("windows")]
+[Apartment(ApartmentState.STA)]
+internal class MenuToolbarControllerTests : IDisposable
+{
+ private MenuToolbarController _controller;
+ private MenuStrip _mainMenu;
+ private ToolStrip _toolbar;
+ private DateTimeDragControl _dragControl;
+ private CheckBox _followTailCheckBox;
+ private ToolStripMenuItem _fileMenu;
+ private ToolStripMenuItem _viewMenu;
+ private ToolStripMenuItem _optionMenu;
+ private ToolStripMenuItem _encodingMenu;
+ private bool _disposed;
+ private ApplicationContext? _appContext;
+ private WindowsFormsSynchronizationContext? _syncContext;
+
+ [SetUp]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Tests")]
+ public void Setup ()
+ {
+ // Ensure we have a WindowsFormsSynchronizationContext for the UI thread
+ if (SynchronizationContext.Current == null)
+ {
+ _syncContext = new WindowsFormsSynchronizationContext();
+ SynchronizationContext.SetSynchronizationContext(_syncContext);
+ }
+
+ // Create an application context to ensure we have a proper UI context
+ _appContext = new ApplicationContext();
+
+ _controller = new MenuToolbarController();
+
+ _mainMenu = new MenuStrip();
+ _toolbar = new ToolStrip();
+ _dragControl = new DateTimeDragControl();
+ _followTailCheckBox = new CheckBox { Name = "checkBoxFollowTail" };
+
+ // Build realistic menu structure matching designer
+ _fileMenu = new ToolStripMenuItem("File") { Name = "fileToolStripMenuItem" };
+ _ = _fileMenu.DropDownItems.Add(new ToolStripMenuItem("Close") { Name = "closeFileToolStripMenuItem" });
+ _ = _fileMenu.DropDownItems.Add(new ToolStripMenuItem("Last Used") { Name = "lastUsedToolStripMenuItem" });
+
+ var multiFile = new ToolStripMenuItem("Multi-File") { Name = "multiFileToolStripMenuItem" };
+ _ = multiFile.DropDownItems.Add(new ToolStripMenuItem("Enabled") { Name = "multiFileEnabledStripMenuItem" });
+ _ = _fileMenu.DropDownItems.Add(multiFile);
+
+ _viewMenu = new ToolStripMenuItem("View") { Name = "viewNavigateToolStripMenuItem" };
+ _ = _viewMenu.DropDownItems.Add(new ToolStripMenuItem("Search") { Name = "searchToolStripMenuItem" });
+ _ = _viewMenu.DropDownItems.Add(new ToolStripMenuItem("Filter") { Name = "filterToolStripMenuItem" });
+ _ = _viewMenu.DropDownItems.Add(new ToolStripMenuItem("Column Finder") { Name = "columnFinderToolStripMenuItem" });
+
+ _encodingMenu = new ToolStripMenuItem("Encoding") { Name = "encodingToolStripMenuItem" };
+ _ = _encodingMenu.DropDownItems.Add(new ToolStripMenuItem("ASCII") { Name = "encodingASCIIToolStripMenuItem" });
+ _ = _encodingMenu.DropDownItems.Add(new ToolStripMenuItem("ANSI") { Name = "encodingANSIToolStripMenuItem" });
+ _ = _encodingMenu.DropDownItems.Add(new ToolStripMenuItem("UTF-8") { Name = "encodingUTF8toolStripMenuItem" });
+ _ = _encodingMenu.DropDownItems.Add(new ToolStripMenuItem("UTF-16") { Name = "encodingUTF16toolStripMenuItem" });
+ _ = _encodingMenu.DropDownItems.Add(new ToolStripMenuItem("ISO-8859-1") { Name = "encodingISO88591toolStripMenuItem" });
+ _ = _viewMenu.DropDownItems.Add(_encodingMenu);
+
+ var timeshiftMenu = new ToolStripMenuItem("Timeshift") { Name = "timeshiftToolStripMenuItem" };
+ _ = timeshiftMenu.DropDownItems.Add(new ToolStripTextBox { Name = "timeshiftToolStripTextBox" });
+ _ = _viewMenu.DropDownItems.Add(timeshiftMenu);
+
+ _optionMenu = new ToolStripMenuItem("Options") { Name = "optionToolStripMenuItem" };
+ _ = _optionMenu.DropDownItems.Add(new ToolStripMenuItem("Cell Select") { Name = "cellSelectModeToolStripMenuItem" });
+
+ _mainMenu.Items.AddRange([_fileMenu, _viewMenu, _optionMenu]);
+
+ // Toolbar
+ _ = _toolbar.Items.Add(new ToolStripButton("Bubbles") { Name = "toolStripButtonBubbles" });
+ _ = _toolbar.Items.Add(new ToolStripComboBox { Name = "highlightGroupsToolStripComboBox" });
+
+ _controller.InitializeMenus(_mainMenu, _toolbar, null, _dragControl, _followTailCheckBox);
+ }
+
+ [TearDown]
+ public void TearDown ()
+ {
+ _controller?.Dispose();
+ _mainMenu?.Dispose();
+ _toolbar?.Dispose();
+ _dragControl?.Dispose();
+ _followTailCheckBox?.Dispose();
+ _appContext?.Dispose();
+ _syncContext?.Dispose();
+ }
+
+ [Test]
+ public void UpdateGuiState_SetsFollowTailChecked ()
+ {
+ var state = new GuiStateEventArgs { FollowTail = true, MenuEnabled = true };
+ _controller.UpdateGuiState(state, false);
+ Assert.That(_followTailCheckBox.Checked, Is.True);
+ }
+
+ [Test]
+ public void UpdateGuiState_SetsTimeshiftMenuState ()
+ {
+ var state = new GuiStateEventArgs
+ {
+ TimeshiftPossible = true,
+ TimeshiftEnabled = true,
+ TimeshiftText = "500",
+ MenuEnabled = true
+ };
+ _controller.UpdateGuiState(state, false);
+
+ var timeshiftItem = FindMenuItem("timeshiftToolStripMenuItem");
+ Assert.That(timeshiftItem.Enabled, Is.True);
+ Assert.That(timeshiftItem.Checked, Is.True);
+
+ var timeshiftTextBox = FindItemRecursive(_mainMenu.Items, "timeshiftToolStripTextBox");
+ Assert.That(timeshiftTextBox.Text, Is.EqualTo("500"));
+ Assert.That(timeshiftTextBox.Enabled, Is.True);
+ }
+
+ [Test]
+ public void UpdateGuiState_ShowsTimestampControl_WhenTimeshiftAndConfigEnabled ()
+ {
+ var state = new GuiStateEventArgs
+ {
+ TimeshiftPossible = true,
+ MinTimestamp = new DateTime(2025, 1, 1),
+ MaxTimestamp = new DateTime(2025, 12, 31),
+ Timestamp = new DateTime(2025, 6, 15),
+ MenuEnabled = true
+ };
+
+ _controller.UpdateGuiState(state, timestampControlEnabled: true);
+ Assert.That(_dragControl.Visible, Is.True);
+ Assert.That(_dragControl.Enabled, Is.True);
+ }
+
+ [Test]
+ public void UpdateGuiState_HidesTimestampControl_WhenTimeshiftNotPossible ()
+ {
+ var state = new GuiStateEventArgs { TimeshiftPossible = false, MenuEnabled = true };
+ _controller.UpdateGuiState(state, timestampControlEnabled: true);
+ Assert.That(_dragControl.Visible, Is.False);
+ }
+
+ [Test]
+ public void UpdateEncodingMenu_Utf8_ChecksCorrectItem ()
+ {
+ _controller.UpdateEncodingMenu(Encoding.UTF8);
+
+ var utf8Item = FindMenuItem("encodingUTF8toolStripMenuItem");
+ var asciiItem = FindMenuItem("encodingASCIIToolStripMenuItem");
+ Assert.That(utf8Item.Checked, Is.True);
+ Assert.That(asciiItem.Checked, Is.False);
+ }
+
+ [Test]
+ public void UpdateEncodingMenu_NullEncoding_UnchecksAll ()
+ {
+ // First set one
+ _controller.UpdateEncodingMenu(Encoding.UTF8);
+ // Then clear
+ _controller.UpdateEncodingMenu(null);
+
+ var utf8Item = FindMenuItem("encodingUTF8toolStripMenuItem");
+ Assert.That(utf8Item.Checked, Is.False);
+ }
+
+ [Test]
+ public void UpdateHighlightGroups_PopulatesComboBox ()
+ {
+ var groups = new[] { "Default", "Errors", "Warnings" };
+ _controller.UpdateHighlightGroups(groups, "Errors");
+
+ var combo = _toolbar.Items["highlightGroupsToolStripComboBox"] as ToolStripComboBox;
+ Assert.That(combo, Is.Not.Null);
+ Assert.That(combo.Items, Has.Count.EqualTo(3));
+ Assert.That(combo.Text, Is.EqualTo("Errors"));
+ }
+
+ [Test]
+ public void PopulateFileHistory_CreatesMenuItems ()
+ {
+ var history = new[] { @"C:\log1.txt", @"C:\log2.txt" };
+ _controller.PopulateFileHistory(history);
+
+ var lastUsed = FindMenuItem("lastUsedToolStripMenuItem");
+ Assert.That(lastUsed.DropDownItems, Has.Count.EqualTo(2));
+ Assert.That(lastUsed.DropDownItems[0].Text, Is.EqualTo(@"C:\log1.txt"));
+ }
+
+ [Test]
+ public void HistoryItemClicked_RaisesEvent ()
+ {
+ string clickedFile = null;
+ _controller.HistoryItemClicked += (_, e) => clickedFile = e.FileName;
+
+ _controller.PopulateFileHistory(["test.log"]);
+ var lastUsed = FindMenuItem("lastUsedToolStripMenuItem");
+
+ // Simulate click via reflection — OnItemClicked is protected on ToolStrip
+ var args = new ToolStripItemClickedEventArgs(lastUsed.DropDownItems[0]);
+ var onItemClicked = typeof(ToolStrip).GetMethod("OnItemClicked", BindingFlags.Instance | BindingFlags.NonPublic);
+ _ = onItemClicked.Invoke(lastUsed.DropDown, [args]);
+
+ Assert.That(clickedFile, Is.EqualTo("test.log"));
+ }
+
+ [Test]
+ public void HighlightGroupSelected_RaisesEvent_OnComboChange ()
+ {
+ string selectedGroup = null;
+ _controller.HighlightGroupSelected += (_, e) => selectedGroup = e.GroupName;
+
+ // Populate first
+ _controller.UpdateHighlightGroups(["Default", "Errors"], "Default");
+
+ // Simulate user selecting "Errors"
+ var combo = _toolbar.Items["highlightGroupsToolStripComboBox"] as ToolStripComboBox;
+ Assert.That(combo, Is.Not.Null, "Expected highlightGroupsToolStripComboBox to be a ToolStripComboBox.");
+ combo.SelectedIndex = 1; // This triggers SelectedIndexChanged
+
+ Assert.That(selectedGroup, Is.EqualTo("Errors"));
+ }
+
+ [Test]
+ public void Dispose_UnsubscribesEvents ()
+ {
+ string selectedGroup = null;
+ _controller.HighlightGroupSelected += (_, e) => selectedGroup = e.GroupName;
+
+ // Populate combo so we can change selection
+ _controller.UpdateHighlightGroups(["Default", "Errors"], "Errors");
+
+ _controller.Dispose();
+
+ // Changing selection after dispose should not raise HighlightGroupSelected
+ var combo = _toolbar.Items["highlightGroupsToolStripComboBox"] as ToolStripComboBox;
+ Assert.That(combo, Is.Not.Null, "Expected highlightGroupsToolStripComboBox to be a ToolStripComboBox.");
+ combo.SelectedIndex = 0;
+
+ Assert.That(selectedGroup, Is.Null);
+ }
+
+ [Test]
+ public void InitializeMenus_NullMainMenu_ThrowsArgumentNullException ()
+ {
+ var controller = new MenuToolbarController();
+ _ = Assert.Throws(() =>
+ controller.InitializeMenus(null, _toolbar, null, _dragControl, _followTailCheckBox));
+ }
+
+ [Test]
+ public void InitializeMenus_NullButtonToolbar_ThrowsArgumentNullException ()
+ {
+ var controller = new MenuToolbarController();
+ _ = Assert.Throws(() =>
+ controller.InitializeMenus(_mainMenu, null, null, _dragControl, _followTailCheckBox));
+ }
+
+ [Test]
+ public void InitializeMenus_NullDragControl_ThrowsArgumentNullException ()
+ {
+ var controller = new MenuToolbarController();
+ _ = Assert.Throws(() =>
+ controller.InitializeMenus(_mainMenu, _toolbar, null, null, _followTailCheckBox));
+ }
+
+ [Test]
+ public void InitializeMenus_NullCheckBox_ThrowsArgumentNullException ()
+ {
+ var controller = new MenuToolbarController();
+ _ = Assert.Throws(() =>
+ controller.InitializeMenus(_mainMenu, _toolbar, null, _dragControl, null));
+ }
+
+ [Test]
+ public void UpdateGuiState_NullState_ThrowsArgumentNullException ()
+ {
+ _ = Assert.Throws(() => _controller.UpdateGuiState(null, false));
+ }
+
+ [Test]
+ public void UpdateGuiState_SetsMultiFileState ()
+ {
+ var state = new GuiStateEventArgs
+ {
+ MultiFileEnabled = true,
+ IsMultiFileActive = true,
+ MenuEnabled = true
+ };
+ _controller.UpdateGuiState(state, false);
+
+ var multiFileItem = FindMenuItem("multiFileToolStripMenuItem");
+ var multiFileEnabledItem = FindMenuItem("multiFileEnabledStripMenuItem");
+ Assert.That(multiFileItem.Enabled, Is.True);
+ Assert.That(multiFileItem.Checked, Is.True);
+ Assert.That(multiFileEnabledItem.Checked, Is.True);
+ }
+
+ [Test]
+ public void UpdateGuiState_SetsCellSelectMode ()
+ {
+ var state = new GuiStateEventArgs { CellSelectMode = true, MenuEnabled = true };
+ _controller.UpdateGuiState(state, false);
+
+ var cellSelectItem = FindMenuItem("cellSelectModeToolStripMenuItem");
+ Assert.That(cellSelectItem.Checked, Is.True);
+ }
+
+ [Test]
+ public void UpdateGuiState_SetsBookmarkBubblesButton ()
+ {
+ var state = new GuiStateEventArgs { ShowBookmarkBubbles = true, MenuEnabled = true };
+ _controller.UpdateGuiState(state, false);
+
+ var bubblesButton = _toolbar.Items["toolStripButtonBubbles"] as ToolStripButton;
+ Assert.That(bubblesButton, Is.Not.Null, "Expected toolStripButtonBubbles to be a ToolStripButton.");
+ Assert.That(bubblesButton.Checked, Is.True);
+ }
+
+ [Test]
+ public void UpdateGuiState_SetsColumnFinderVisible ()
+ {
+ var state = new GuiStateEventArgs { ColumnFinderVisible = true, MenuEnabled = true };
+ _controller.UpdateGuiState(state, false);
+
+ var columnFinderItem = FindMenuItem("columnFinderToolStripMenuItem");
+ Assert.That(columnFinderItem.Checked, Is.True);
+ }
+
+ [Test]
+ public void UpdateGuiState_SetsHighlightGroupName ()
+ {
+ var state = new GuiStateEventArgs { HighlightGroupName = "Errors", MenuEnabled = true };
+ _controller.UpdateGuiState(state, false);
+
+ var combo = _toolbar.Items["highlightGroupsToolStripComboBox"] as ToolStripComboBox;
+ Assert.That(combo.Text, Is.EqualTo("Errors"));
+ }
+
+ [Test]
+ public void UpdateGuiState_SetsMenuEnabled ()
+ {
+ var state = new GuiStateEventArgs { MenuEnabled = false };
+ _controller.UpdateGuiState(state, false);
+
+ Assert.That(_mainMenu.Enabled, Is.False);
+ }
+
+ [Test]
+ public void UpdateHighlightGroups_DoesNotRaiseEvent_DuringProgrammaticUpdate ()
+ {
+ string selectedGroup = null;
+ _controller.HighlightGroupSelected += (_, e) => selectedGroup = e.GroupName;
+
+ _controller.UpdateHighlightGroups(["Default", "Errors"], "Errors");
+
+ Assert.That(selectedGroup, Is.Null);
+ }
+
+ private ToolStripMenuItem FindMenuItem (string name)
+ {
+ return FindItemRecursive(_mainMenu.Items, name);
+ }
+
+ private static T FindItemRecursive (ToolStripItemCollection items, string name) where T : ToolStripItem
+ {
+ foreach (ToolStripItem item in items)
+ {
+ if (item.Name == name && item is T typed)
+ {
+ return typed;
+ }
+
+ if (item is ToolStripDropDownItem dropDown)
+ {
+ var found = FindItemRecursive(dropDown.DropDownItems, name);
+ if (found != null)
+ {
+ return found;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public void Dispose ()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose (bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ _controller?.Dispose();
+ _mainMenu?.Dispose();
+ _toolbar?.Dispose();
+ _dragControl?.Dispose();
+ _followTailCheckBox?.Dispose();
+ }
+
+ _disposed = true;
+ }
+}
\ No newline at end of file
diff --git a/src/LogExpert.Tests/Services/TabControllerTests.cs b/src/LogExpert.Tests/Services/TabControllerTests.cs
index 13e65d4b..8c64d8ec 100644
--- a/src/LogExpert.Tests/Services/TabControllerTests.cs
+++ b/src/LogExpert.Tests/Services/TabControllerTests.cs
@@ -2,7 +2,7 @@
using System.Windows.Forms;
using LogExpert.UI.Controls.LogWindow;
-using LogExpert.UI.Services;
+using LogExpert.UI.Services.TabControllerService;
using NUnit.Framework;
@@ -93,7 +93,7 @@ public void Constructor_WithDockPanel_InitializesSuccessfully()
public void Constructor_WithNullDockPanel_ThrowsArgumentNullException()
{
// Arrange & Act & Assert
- Assert.Throws(() => new TabController(null));
+ _ = Assert.Throws(() => new TabController(null));
}
[Test]
@@ -131,7 +131,7 @@ public void InitializeDockPanel_WithNullDockPanel_ThrowsArgumentNullException()
using var controller = new TabController();
// Act & Assert
- Assert.Throws(() => controller.InitializeDockPanel(null));
+ _ = Assert.Throws(() => controller.InitializeDockPanel(null));
}
[Test]
@@ -146,7 +146,7 @@ public void InitializeDockPanel_WhenAlreadyInitialized_ThrowsInvalidOperationExc
form2.Controls.Add(dockPanel2);
// Act & Assert
- Assert.Throws(() => controller.InitializeDockPanel(dockPanel2));
+ _ = Assert.Throws(() => controller.InitializeDockPanel(dockPanel2));
}
#endregion
@@ -305,7 +305,7 @@ public void AddWindow_WithNullWindow_ThrowsArgumentNullException()
// Arrange - already done in Setup
// Act & Assert
- Assert.Throws(() => _tabController.AddWindow(null, "Test Window"));
+ _ = Assert.Throws(() => _tabController.AddWindow(null, "Test Window"));
}
[Test]
@@ -358,7 +358,7 @@ public void CloseAllWindows_WhenEmpty_DoesNotThrow()
// Arrange - already done in Setup
// Act & Assert - should not throw
- Assert.DoesNotThrow(() => _tabController.CloseAllWindows());
+ Assert.DoesNotThrow(_tabController.CloseAllWindows);
}
#endregion
@@ -397,7 +397,7 @@ public void SwitchToNextWindow_WhenEmpty_DoesNotThrow()
// Arrange - already done in Setup
// Act & Assert - should not throw
- Assert.DoesNotThrow(() => _tabController.SwitchToNextWindow());
+ Assert.DoesNotThrow(_tabController.SwitchToNextWindow);
}
#endregion
@@ -410,7 +410,7 @@ public void SwitchToPreviousWindow_WhenEmpty_DoesNotThrow()
// Arrange - already done in Setup
// Act & Assert - should not throw
- Assert.DoesNotThrow(() => _tabController.SwitchToPreviousWindow());
+ Assert.DoesNotThrow(_tabController.SwitchToPreviousWindow);
}
#endregion
diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs
index a888e269..e2fc4513 100644
--- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs
+++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs
@@ -23,7 +23,10 @@
using LogExpert.UI.Entities;
using LogExpert.UI.Extensions;
using LogExpert.UI.Extensions.LogWindow;
-using LogExpert.UI.Services;
+using LogExpert.UI.Interface.Services;
+using LogExpert.UI.Services.LedService;
+using LogExpert.UI.Services.MenuToolbarService;
+using LogExpert.UI.Services.TabControllerService;
using NLog;
@@ -46,6 +49,7 @@ internal partial class LogTabWindow : Form, ILogTabWindow
private readonly LedIndicatorService _ledService;
private readonly TabController _tabController;
+ private readonly MenuToolbarController _menuToolbarController;
private bool _disposed;
@@ -84,11 +88,12 @@ public LogTabWindow (string[] fileNames, int instanceNumber, bool showInstanceNu
ConfigureDockPanel();
_tabController = new TabController(dockPanel);
- _tabController.WindowAdded += OnTabControllerWindowAdded;
- _tabController.WindowRemoved += OnTabControllerWindowRemoved;
- _tabController.WindowActivated += OnTabControllerWindowActivated;
- _tabController.WindowClosing += OnTabControllerWindowClosing;
+ InitializeTabControllerEvents();
+ _menuToolbarController = new MenuToolbarController();
+ _menuToolbarController.InitializeMenus(mainMenuStrip, buttonToolStrip, externalToolsToolStrip, dragControlDateTime, checkBoxFollowTail);
+ InitializeMenuToolbarControllerEvents();
+
ApplyTextResources();
ConfigManager = configManager;
@@ -111,7 +116,7 @@ public LogTabWindow (string[] fileNames, int instanceNumber, bool showInstanceNu
_ledService = new LedIndicatorService();
_ledService.Initialize(ConfigManager.Settings.Preferences.ShowTailColor);
_ledService.IconChanged += OnLedIconChanged;
- _ledService.Start();
+ _ledService.StartService();
_deadIcon = _ledService.GetDeadIcon();
@@ -146,6 +151,37 @@ public LogTabWindow (string[] fileNames, int instanceNumber, bool showInstanceNu
InitToolWindows();
}
+ private void InitializeMenuToolbarControllerEvents ()
+ {
+ _menuToolbarController.HistoryItemClicked += OnMenuControllerHistoryItemClicked;
+ _menuToolbarController.HistoryItemRemoveRequested += OnMenuControllerHistoryItemRemoveRequested;
+ _menuToolbarController.HighlightGroupSelected += OnMenuControllerHighlightGroupSelected;
+ }
+
+ private void OnMenuControllerHighlightGroupSelected (object? sender, HighlightGroupSelectedEventArgs e)
+ {
+ CurrentLogWindow?.SetCurrentHighlightGroup(e.GroupName);
+ }
+
+ private void OnMenuControllerHistoryItemRemoveRequested (object? sender, HistoryItemClickedEventArgs e)
+ {
+ ConfigManager.RemoveFromFileHistory(e.FileName);
+ FillHistoryMenu();
+ }
+
+ private void OnMenuControllerHistoryItemClicked (object? sender, HistoryItemClickedEventArgs e)
+ {
+ _ = AddFileTab(e.FileName, false, null, false, null);
+ }
+
+ private void InitializeTabControllerEvents ()
+ {
+ _tabController.WindowAdded += OnTabControllerWindowAdded;
+ _tabController.WindowRemoved += OnTabControllerWindowRemoved;
+ _tabController.WindowActivated += OnTabControllerWindowActivated;
+ _tabController.WindowClosing += OnTabControllerWindowClosing;
+ }
+
#endregion
#region Delegates
@@ -190,11 +226,6 @@ public LogWindow.LogWindow CurrentLogWindow
public List HighlightGroupList { get; private set; } = [];
- //public Settings Settings
- //{
- // get { return ConfigManager.Settings; }
- //}
-
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public ILogExpertProxy LogExpertProxy { get; set; }
@@ -299,7 +330,6 @@ private void ConfigureDockPanel ()
private void ApplyTextResources ()
{
-
mainMenuStrip.Text = Resources.LogTabWindow_UI_MenuStrip_MainMenu;
Text = Resources.LogExpert_Common_UI_Title_LogExpert;
checkBoxHost.AccessibleName = Resources.LogTabWindow_UI_CheckBox_ToolTip_checkBoxHost;
@@ -888,10 +918,11 @@ protected override void Dispose (bool disposing)
if (disposing && (components != null))
{
- _ledService?.Stop();
+ _ledService?.StopService();
_ledService?.Dispose();
components.Dispose();
_tabStringFormat?.Dispose();
+ _menuToolbarController?.Dispose();
}
_disposed = true;
@@ -1080,12 +1111,6 @@ private void DisconnectEventHandlers (LogWindow.LogWindow logWindow)
logWindow.FilterListChanged -= OnLogWindowFilterListChanged;
logWindow.CurrentHighlightGroupChanged -= OnLogWindowCurrentHighlightGroupChanged;
logWindow.SyncModeChanged -= OnLogWindowSyncModeChanged;
-
- //var data = logWindow.Tag as LogWindowData;
- //data.tabPage.MouseClick -= tabPage_MouseClick;
- //data.tabPage.TabDoubleClick -= tabPage_TabDoubleClick;
- //data.tabPage.ContextMenuStrip = null;
- //data.tabPage = null;
}
[SupportedOSPlatform("windows")]
@@ -1109,18 +1134,7 @@ private LogWindow.LogWindow FindWindowForFile (string fileName)
[SupportedOSPlatform("windows")]
private void FillHistoryMenu ()
{
- ToolStripDropDown strip = new ToolStripDropDownMenu();
-
- foreach (var file in ConfigManager.Settings.FileHistoryList)
- {
- ToolStripItem item = new ToolStripMenuItem(file);
- _ = strip.Items.Add(item);
-
- }
-
- strip.ItemClicked += OnHistoryItemClicked;
- strip.MouseUp += OnStripMouseUp;
- lastUsedToolStripMenuItem.DropDown = strip;
+ _menuToolbarController.PopulateFileHistory(ConfigManager.Settings.FileHistoryList);
}
///
@@ -1169,16 +1183,9 @@ private void ShowHighlightSettingsDialog ()
[SupportedOSPlatform("windows")]
private void FillHighlightComboBox ()
{
- var currentGroupName = highlightGroupsToolStripComboBox.Text;
- highlightGroupsToolStripComboBox.Items.Clear();
- foreach (var group in HighlightGroupList)
- {
- _ = highlightGroupsToolStripComboBox.Items.Add(group.GroupName);
- if (group.GroupName.Equals(currentGroupName, StringComparison.Ordinal))
- {
- highlightGroupsToolStripComboBox.Text = group.GroupName;
- }
- }
+ var groups = HighlightGroupList.Select(g => g.GroupName);
+ var selected = highlightGroupsToolStripComboBox.Text;
+ _menuToolbarController.UpdateHighlightGroups(groups, selected);
}
[SupportedOSPlatform("windows")]
@@ -1424,38 +1431,7 @@ private void DisconnectBookmarkWindow ()
private void GuiStateUpdateWorker (GuiStateEventArgs e)
{
_skipEvents = true;
- checkBoxFollowTail.Checked = e.FollowTail;
- mainMenuStrip.Enabled = e.MenuEnabled;
- timeshiftToolStripMenuItem.Enabled = e.TimeshiftPossible;
- timeshiftToolStripMenuItem.Checked = e.TimeshiftEnabled;
- timeshiftToolStripTextBox.Text = e.TimeshiftText;
- timeshiftToolStripTextBox.Enabled = e.TimeshiftEnabled;
- multiFileToolStripMenuItem.Enabled = e.MultiFileEnabled; // disabled for temp files
- multiFileToolStripMenuItem.Checked = e.IsMultiFileActive;
- multiFileEnabledStripMenuItem.Checked = e.IsMultiFileActive;
- cellSelectModeToolStripMenuItem.Checked = e.CellSelectMode;
-
- RefreshEncodingMenuBar(e.CurrentEncoding);
-
- if (e.TimeshiftPossible && ConfigManager.Settings.Preferences.TimestampControl)
- {
- dragControlDateTime.MinDateTime = e.MinTimestamp;
- dragControlDateTime.MaxDateTime = e.MaxTimestamp;
- dragControlDateTime.DateTime = e.Timestamp;
- dragControlDateTime.Visible = true;
- dragControlDateTime.Enabled = true;
- dragControlDateTime.Refresh();
- }
- else
- {
- dragControlDateTime.Visible = false;
- dragControlDateTime.Enabled = false;
- }
-
- toolStripButtonBubbles.Checked = e.ShowBookmarkBubbles;
- highlightGroupsToolStripComboBox.Text = e.HighlightGroupName;
- columnFinderToolStripMenuItem.Checked = e.ColumnFinderVisible;
-
+ _menuToolbarController.UpdateGuiState(e, ConfigManager.Settings.Preferences.TimestampControl);
_skipEvents = false;
}
@@ -1579,39 +1555,7 @@ private Icon GetLedIcon (int diffSum, LogWindowData data)
[SupportedOSPlatform("windows")]
private void RefreshEncodingMenuBar (Encoding encoding)
{
- encodingASCIIToolStripMenuItem.Checked = false;
- encodingANSIToolStripMenuItem.Checked = false;
- encodingUTF8toolStripMenuItem.Checked = false;
- encodingUTF16toolStripMenuItem.Checked = false;
- encodingISO88591toolStripMenuItem.Checked = false;
-
- if (encoding == null)
- {
- return;
- }
-
- if (encoding is ASCIIEncoding)
- {
- encodingASCIIToolStripMenuItem.Checked = true;
- }
- else if (encoding.Equals(Encoding.Default))
- {
- encodingANSIToolStripMenuItem.Checked = true;
- }
- else if (encoding is UTF8Encoding)
- {
- encodingUTF8toolStripMenuItem.Checked = true;
- }
- else if (encoding is UnicodeEncoding)
- {
- encodingUTF16toolStripMenuItem.Checked = true;
- }
- else if (encoding.Equals(Encoding.GetEncoding("iso-8859-1")))
- {
- encodingISO88591toolStripMenuItem.Checked = true;
- }
-
- encodingANSIToolStripMenuItem.Text = Encoding.Default.HeaderName;
+ _menuToolbarController.UpdateEncodingMenu(encoding);
}
[SupportedOSPlatform("windows")]
@@ -2176,22 +2120,6 @@ private void OnLogTabWindowFormClosing (object sender, CancelEventArgs e)
}
}
- private void OnStripMouseUp (object sender, MouseEventArgs e)
- {
- if (sender is ToolStripDropDown dropDown)
- {
- _ = AddFileTab(dropDown.Text, false, null, false, null);
- }
- }
-
- private void OnHistoryItemClicked (object sender, ToolStripItemClickedEventArgs e)
- {
- if (!string.IsNullOrEmpty(e.ClickedItem.Text))
- {
- _ = AddFileTab(e.ClickedItem.Text, false, null, false, null);
- }
- }
-
private void OnLogWindowDisposed (object sender, EventArgs e)
{
var logWindow = sender as LogWindow.LogWindow;
@@ -2266,7 +2194,6 @@ private void OnSelectFilterToolStripMenuItemClick (object sender, EventArgs e)
logWindow.ColumnizerConfigChanged();
}
}
-
}
}
}
@@ -2491,7 +2418,7 @@ private void OnFileSizeChanged (object sender, LogEventArgs e)
return;
}
- if (logWindow.Tag is not LogWindowData data)
+ if (logWindow.Tag is not LogWindowData)
{
return;
}
@@ -2931,12 +2858,6 @@ private void OnHighlightGroupsComboBoxDropDownClosed (object sender, EventArgs e
ApplySelectedHighlightGroup();
}
- [SupportedOSPlatform("windows")]
- private void OnHighlightGroupsComboBoxSelectedIndexChanged (object sender, EventArgs e)
- {
- ApplySelectedHighlightGroup();
- }
-
[SupportedOSPlatform("windows")]
private void OnHighlightGroupsComboBoxMouseUp (object sender, MouseEventArgs e)
{
diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs
index f173f5c7..e5f9c152 100644
--- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs
+++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs
@@ -965,7 +965,6 @@ private void InitializeComponent ()
highlightGroupsToolStripComboBox.Size = new Size(150, 23);
highlightGroupsToolStripComboBox.ToolTipText = "Select the current highlight settings for the log file (right-click to open highlight settings)";
highlightGroupsToolStripComboBox.DropDownClosed += OnHighlightGroupsComboBoxDropDownClosed;
- highlightGroupsToolStripComboBox.SelectedIndexChanged += OnHighlightGroupsComboBoxSelectedIndexChanged;
highlightGroupsToolStripComboBox.MouseUp += OnHighlightGroupsComboBoxMouseUp;
//
// checkBoxFollowTail
diff --git a/src/LogExpert.UI/Services/ILedIndicatorService.cs b/src/LogExpert.UI/Interface/ILedIndicatorService.cs
similarity index 95%
rename from src/LogExpert.UI/Services/ILedIndicatorService.cs
rename to src/LogExpert.UI/Interface/ILedIndicatorService.cs
index d6520a09..82b0c45c 100644
--- a/src/LogExpert.UI/Services/ILedIndicatorService.cs
+++ b/src/LogExpert.UI/Interface/ILedIndicatorService.cs
@@ -1,6 +1,7 @@
using LogExpert.UI.Controls.LogWindow;
+using LogExpert.UI.Services.LedService;
-namespace LogExpert.UI.Services;
+namespace LogExpert.UI.Interface;
///
/// Service for managing LED indicator icons on log window tabs
@@ -35,12 +36,12 @@ internal interface ILedIndicatorService : IDisposable
///
/// Starts the LED animation thread
///
- void Start ();
+ void StartService ();
///
/// Stops the LED animation thread
///
- void Stop ();
+ void StopService ();
///
/// Registers a window for LED state tracking
diff --git a/src/LogExpert.UI/Interface/IMenuToolbarController.cs b/src/LogExpert.UI/Interface/IMenuToolbarController.cs
new file mode 100644
index 00000000..9bd7c3a8
--- /dev/null
+++ b/src/LogExpert.UI/Interface/IMenuToolbarController.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using System.Runtime.Versioning;
+
+using LogExpert.Core.EventArguments;
+using LogExpert.Dialogs;
+using LogExpert.UI.Services.MenuToolbarService;
+
+namespace LogExpert.UI.Interface;
+
+///
+/// Controls menu and toolbar state based on application state.
+/// Thread-safe UI updates via SynchronizationContext.
+///
+[SupportedOSPlatform("windows")]
+internal interface IMenuToolbarController : IDisposable
+{
+ ///
+ /// Initializes controller with menu, toolbar, and timestamp control references.
+ /// Must be called on UI thread.
+ ///
+ void InitializeMenus (MenuStrip mainMenu, ToolStrip buttonToolbar, ToolStrip externalToolsToolStrip,
+ DateTimeDragControl dragControlDateTime, CheckBox checkBoxFollowTail);
+
+ ///
+ /// Updates all menus, toolbars, encoding, highlight group, and timestamp control
+ /// based on the GUI state event args from a LogWindow.
+ ///
+ ///
+ /// Consumes directly — no intermediate mapping needed.
+ ///
+ void UpdateGuiState (GuiStateEventArgs state, bool timestampControlEnabled);
+
+ ///
+ /// Updates encoding menu to show current encoding.
+ /// Also updates the ANSI menu item header text.
+ ///
+ void UpdateEncodingMenu (Encoding currentEncoding);
+
+ ///
+ /// Updates highlight groups combo box.
+ ///
+ void UpdateHighlightGroups (IEnumerable groups, string selectedGroup);
+
+ ///
+ /// Populates file history menu with recent files.
+ /// Includes right-click removal support.
+ ///
+ void PopulateFileHistory (IEnumerable fileHistory);
+
+ // Events
+ event EventHandler HistoryItemClicked;
+ event EventHandler HistoryItemRemoveRequested;
+ event EventHandler HighlightGroupSelected;
+}
diff --git a/src/LogExpert.UI/Services/ITabController.cs b/src/LogExpert.UI/Interface/ITabController.cs
similarity index 97%
rename from src/LogExpert.UI/Services/ITabController.cs
rename to src/LogExpert.UI/Interface/ITabController.cs
index 319cfe06..3f59ffe1 100644
--- a/src/LogExpert.UI/Services/ITabController.cs
+++ b/src/LogExpert.UI/Interface/ITabController.cs
@@ -1,8 +1,10 @@
using LogExpert.UI.Controls.LogWindow;
+using LogExpert.UI.Interface.Services;
+using LogExpert.UI.Services.TabControllerService;
using WeifenLuo.WinFormsUI.Docking;
-namespace LogExpert.UI.Services;
+namespace LogExpert.UI.Interface;
///
/// Controls the management of LogWindow tabs in the application.
diff --git a/src/LogExpert.UI/Services/IconChangedEventArgs.cs b/src/LogExpert.UI/Services/LedService/IconChangedEventArgs.cs
similarity index 87%
rename from src/LogExpert.UI/Services/IconChangedEventArgs.cs
rename to src/LogExpert.UI/Services/LedService/IconChangedEventArgs.cs
index 79962397..08b72785 100644
--- a/src/LogExpert.UI/Services/IconChangedEventArgs.cs
+++ b/src/LogExpert.UI/Services/LedService/IconChangedEventArgs.cs
@@ -1,6 +1,6 @@
using LogExpert.UI.Controls.LogWindow;
-namespace LogExpert.UI.Services;
+namespace LogExpert.UI.Services.LedService;
///
/// Event arguments for icon change notifications
diff --git a/src/LogExpert.UI/Services/LedIndicatorService.cs b/src/LogExpert.UI/Services/LedService/LedIndicatorService.cs
similarity index 98%
rename from src/LogExpert.UI/Services/LedIndicatorService.cs
rename to src/LogExpert.UI/Services/LedService/LedIndicatorService.cs
index 392bd8ab..0a1f4270 100644
--- a/src/LogExpert.UI/Services/LedIndicatorService.cs
+++ b/src/LogExpert.UI/Services/LedService/LedIndicatorService.cs
@@ -4,10 +4,11 @@
using System.Runtime.Versioning;
using LogExpert.UI.Controls.LogWindow;
+using LogExpert.UI.Interface;
using NLog;
-namespace LogExpert.UI.Services;
+namespace LogExpert.UI.Services.LedService;
[SupportedOSPlatform("windows")]
internal sealed class LedIndicatorService : ILedIndicatorService, IDisposable
@@ -96,7 +97,7 @@ public void Dispose ()
}
_disposed = true;
- Stop();
+ StopService();
Thread.Sleep(ANIMATION_INTERVAL_MS * 2);
@@ -425,7 +426,7 @@ public void RegenerateIcons (Color tailColor)
if (wasRunning)
{
- Stop();
+ StopService();
Thread.Sleep(ANIMATION_INTERVAL_MS * 2); // Wait for pending ticks
}
@@ -459,7 +460,7 @@ public void RegenerateIcons (Color tailColor)
if (wasRunning)
{
- Start();
+ StartService();
}
}
@@ -483,7 +484,7 @@ public void RegisterWindow (LogWindow window)
///
/// Starts the LED animation timer
///
- public void Start ()
+ public void StartService ()
{
if (!_isInitialized)
{
@@ -510,7 +511,7 @@ public void Start ()
///
/// Stops the LED animation timer
///
- public void Stop ()
+ public void StopService ()
{
if (_animationTimer == null)
{
diff --git a/src/LogExpert.UI/Services/LedState.cs b/src/LogExpert.UI/Services/LedService/LedState.cs
similarity index 95%
rename from src/LogExpert.UI/Services/LedState.cs
rename to src/LogExpert.UI/Services/LedService/LedState.cs
index 14a648eb..2d624a1d 100644
--- a/src/LogExpert.UI/Services/LedState.cs
+++ b/src/LogExpert.UI/Services/LedService/LedState.cs
@@ -1,4 +1,4 @@
-namespace LogExpert.UI.Services;
+namespace LogExpert.UI.Services.LedService;
///
/// LED state information
diff --git a/src/LogExpert.UI/Services/TailFollowState.cs b/src/LogExpert.UI/Services/LedService/TailFollowState.cs
similarity index 91%
rename from src/LogExpert.UI/Services/TailFollowState.cs
rename to src/LogExpert.UI/Services/LedService/TailFollowState.cs
index 080d59c1..b6c5f358 100644
--- a/src/LogExpert.UI/Services/TailFollowState.cs
+++ b/src/LogExpert.UI/Services/LedService/TailFollowState.cs
@@ -1,4 +1,4 @@
-namespace LogExpert.UI.Services;
+namespace LogExpert.UI.Services.LedService;
///
/// Represents the tail follow state for a log window
diff --git a/src/LogExpert.UI/Services/TimeSyncState.cs b/src/LogExpert.UI/Services/LedService/TimeSyncState.cs
similarity index 89%
rename from src/LogExpert.UI/Services/TimeSyncState.cs
rename to src/LogExpert.UI/Services/LedService/TimeSyncState.cs
index 1ebdc147..f5d832e0 100644
--- a/src/LogExpert.UI/Services/TimeSyncState.cs
+++ b/src/LogExpert.UI/Services/LedService/TimeSyncState.cs
@@ -1,4 +1,4 @@
-namespace LogExpert.UI.Services;
+namespace LogExpert.UI.Services.LedService;
///
/// Represents the time synchronization state for a log window
diff --git a/src/LogExpert.UI/Services/MenuToolbarService/HighlightGroupSelectedEventArgs.cs b/src/LogExpert.UI/Services/MenuToolbarService/HighlightGroupSelectedEventArgs.cs
new file mode 100644
index 00000000..61e83a78
--- /dev/null
+++ b/src/LogExpert.UI/Services/MenuToolbarService/HighlightGroupSelectedEventArgs.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace LogExpert.UI.Services.MenuToolbarService;
+
+/// Event args for highlight group combo box selection.
+internal class HighlightGroupSelectedEventArgs (string groupName) : EventArgs
+{
+ public string GroupName { get; } = groupName;
+}
diff --git a/src/LogExpert.UI/Services/MenuToolbarService/HistoryItemClickedEventArgs.cs b/src/LogExpert.UI/Services/MenuToolbarService/HistoryItemClickedEventArgs.cs
new file mode 100644
index 00000000..4ae700b8
--- /dev/null
+++ b/src/LogExpert.UI/Services/MenuToolbarService/HistoryItemClickedEventArgs.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace LogExpert.UI.Services.MenuToolbarService;
+
+/// Event args for file history item interactions.
+internal class HistoryItemClickedEventArgs (string fileName) : EventArgs
+{
+ public string FileName { get; } = fileName;
+}
diff --git a/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs b/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs
new file mode 100644
index 00000000..04cb3083
--- /dev/null
+++ b/src/LogExpert.UI/Services/MenuToolbarService/MenuToolbarController.cs
@@ -0,0 +1,444 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.Versioning;
+using System.Text;
+
+using LogExpert.Core.EventArguments;
+using LogExpert.Dialogs;
+using LogExpert.UI.Interface;
+
+using NLog;
+
+namespace LogExpert.UI.Services.MenuToolbarService;
+
+[SupportedOSPlatform("windows")]
+internal sealed class MenuToolbarController : IMenuToolbarController
+{
+ private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
+
+ private MenuStrip _mainMenu;
+ private ToolStrip _buttonToolbar;
+ private ToolStrip _externalToolsToolStrip;
+
+ // Controls passed in from LogTabWindow (not owned by this controller)
+ private DateTimeDragControl _dragControlDateTime;
+ private CheckBox _checkBoxFollowTail;
+
+ // Menu items (cached during initialization for performance)
+ private ToolStripMenuItem _closeFileMenuItem;
+ private ToolStripMenuItem _searchMenuItem;
+ private ToolStripMenuItem _filterMenuItem;
+ private ToolStripMenuItem _goToLineMenuItem;
+ private ToolStripMenuItem _multiFileMenuItem;
+ private ToolStripMenuItem _multiFileEnabledMenuItem;
+ private ToolStripMenuItem _timeshiftMenuItem;
+ private ToolStripTextBox _timeshiftTextBox;
+ private ToolStripMenuItem _cellSelectMenuItem;
+ private ToolStripMenuItem _columnFinderMenuItem;
+
+ // Encoding menu items
+ private ToolStripMenuItem _encodingAsciiMenuItem;
+ private ToolStripMenuItem _encodingAnsiMenuItem;
+ private ToolStripMenuItem _encodingUtf8MenuItem;
+ private ToolStripMenuItem _encodingUtf16MenuItem;
+ private ToolStripMenuItem _encodingIso88591MenuItem;
+
+ // Toolbar items
+ private ToolStripButton _bubblesButton;
+
+ // Highlight group combo
+ private ToolStripComboBox _highlightGroupCombo;
+
+ // History menu
+ private ToolStripMenuItem _lastUsedMenuItem;
+
+ private readonly SynchronizationContext _uiContext;
+ private bool _disposed;
+ private bool _suppressEvents;
+
+ public event EventHandler HistoryItemClicked;
+ public event EventHandler HistoryItemRemoveRequested;
+ public event EventHandler HighlightGroupSelected;
+
+ public MenuToolbarController ()
+ {
+ _uiContext = SynchronizationContext.Current
+ ?? throw new InvalidOperationException("Must be created on UI thread");
+ }
+
+ public void InitializeMenus (MenuStrip mainMenu, ToolStrip buttonToolbar,
+ ToolStrip externalToolsToolStrip, DateTimeDragControl dragControlDateTime,
+ CheckBox checkBoxFollowTail)
+ {
+ ArgumentNullException.ThrowIfNull(mainMenu);
+ ArgumentNullException.ThrowIfNull(buttonToolbar);
+ ArgumentNullException.ThrowIfNull(dragControlDateTime);
+ ArgumentNullException.ThrowIfNull(checkBoxFollowTail);
+
+ _mainMenu = mainMenu;
+ _buttonToolbar = buttonToolbar;
+ _externalToolsToolStrip = externalToolsToolStrip;
+ _dragControlDateTime = dragControlDateTime;
+ _checkBoxFollowTail = checkBoxFollowTail;
+
+ // Cache menu items by designer name (recursive search into dropdowns)
+ _closeFileMenuItem = FindMenuItem("closeFileToolStripMenuItem");
+ _searchMenuItem = FindMenuItem("searchToolStripMenuItem");
+ _filterMenuItem = FindMenuItem("filterToolStripMenuItem");
+ _goToLineMenuItem = FindMenuItem("goToLineToolStripMenuItem");
+ _multiFileMenuItem = FindMenuItem("multiFileToolStripMenuItem");
+ _multiFileEnabledMenuItem = FindMenuItem("multiFileEnabledStripMenuItem");
+ _timeshiftMenuItem = FindMenuItem("timeshiftToolStripMenuItem");
+ _timeshiftTextBox = FindToolStripItem(_mainMenu, "timeshiftToolStripTextBox");
+ _cellSelectMenuItem = FindMenuItem("cellSelectModeToolStripMenuItem");
+ _columnFinderMenuItem = FindMenuItem("columnFinderToolStripMenuItem");
+
+ // Encoding menu items
+ _encodingAsciiMenuItem = FindMenuItem("encodingASCIIToolStripMenuItem");
+ _encodingAnsiMenuItem = FindMenuItem("encodingANSIToolStripMenuItem");
+ _encodingUtf8MenuItem = FindMenuItem("encodingUTF8toolStripMenuItem");
+ _encodingUtf16MenuItem = FindMenuItem("encodingUTF16toolStripMenuItem");
+ _encodingIso88591MenuItem = FindMenuItem("encodingISO88591toolStripMenuItem");
+
+ // Toolbar items
+ _bubblesButton = FindToolStripItem(_buttonToolbar, "toolStripButtonBubbles");
+
+ // Highlight group combo (may be on buttonToolbar or externalToolsToolStrip)
+ _highlightGroupCombo = FindToolStripItem(_buttonToolbar, "highlightGroupsToolStripComboBox") ?? FindToolStripItem(_externalToolsToolStrip, "highlightGroupsToolStripComboBox");
+
+ _highlightGroupCombo?.SelectedIndexChanged += OnHighlightGroupComboSelectedIndexChanged;
+
+ // History menu
+ _lastUsedMenuItem = FindMenuItem("lastUsedToolStripMenuItem");
+
+ LogMissingItems();
+ }
+
+ public void UpdateGuiState (GuiStateEventArgs state, bool timestampControlEnabled)
+ {
+ ArgumentNullException.ThrowIfNull(state);
+
+ if (_uiContext != SynchronizationContext.Current)
+ {
+ _uiContext.Post(_ => UpdateGuiState(state, timestampControlEnabled), null);
+ return;
+ }
+
+ _suppressEvents = true;
+
+ try
+ {
+ _checkBoxFollowTail.Checked = state.FollowTail;
+ _mainMenu.Enabled = state.MenuEnabled;
+
+ // Timeshift
+ if (_timeshiftMenuItem != null)
+ {
+ _timeshiftMenuItem.Enabled = state.TimeshiftPossible;
+ _timeshiftMenuItem.Checked = state.TimeshiftEnabled;
+ }
+
+ if (_timeshiftTextBox != null)
+ {
+ _timeshiftTextBox.Text = state.TimeshiftText;
+ _timeshiftTextBox.Enabled = state.TimeshiftEnabled;
+ }
+
+ // Multi-file
+ if (_multiFileMenuItem != null)
+ {
+ _multiFileMenuItem.Enabled = state.MultiFileEnabled;
+ _multiFileMenuItem.Checked = state.IsMultiFileActive;
+ }
+
+ _ = (_multiFileEnabledMenuItem?.Checked = state.IsMultiFileActive);
+
+ // Cell select
+ _ = (_cellSelectMenuItem?.Checked = state.CellSelectMode);
+
+ // Encoding
+ UpdateEncodingMenu(state.CurrentEncoding);
+
+ // Timestamp drag control
+ if (state.TimeshiftPossible && timestampControlEnabled)
+ {
+ _dragControlDateTime.MinDateTime = state.MinTimestamp;
+ _dragControlDateTime.MaxDateTime = state.MaxTimestamp;
+ _dragControlDateTime.DateTime = state.Timestamp;
+ _dragControlDateTime.Visible = true;
+ _dragControlDateTime.Enabled = true;
+ _dragControlDateTime.Refresh();
+ }
+ else
+ {
+ _dragControlDateTime.Visible = false;
+ _dragControlDateTime.Enabled = false;
+ }
+
+ // Toolbar
+ _ = (_bubblesButton?.Checked = state.ShowBookmarkBubbles);
+
+ // Highlight group
+ _ = (_highlightGroupCombo?.Text = state.HighlightGroupName);
+
+ // Column finder
+ _ = (_columnFinderMenuItem?.Checked = state.ColumnFinderVisible);
+ }
+ finally
+ {
+ _suppressEvents = false;
+ }
+ }
+
+ public void UpdateEncodingMenu (Encoding currentEncoding)
+ {
+ if (_uiContext != SynchronizationContext.Current)
+ {
+ _uiContext.Post(_ => UpdateEncodingMenu(currentEncoding), null);
+ return;
+ }
+
+ // Clear all checks
+ SetCheckedSafe(_encodingAsciiMenuItem, false);
+ SetCheckedSafe(_encodingAnsiMenuItem, false);
+ SetCheckedSafe(_encodingUtf8MenuItem, false);
+ SetCheckedSafe(_encodingUtf16MenuItem, false);
+ SetCheckedSafe(_encodingIso88591MenuItem, false);
+
+ if (currentEncoding == null)
+ {
+ return;
+ }
+
+ if (currentEncoding is ASCIIEncoding)
+ {
+ SetCheckedSafe(_encodingAsciiMenuItem, true);
+ }
+ else if (currentEncoding.Equals(Encoding.Default))
+ {
+ SetCheckedSafe(_encodingAnsiMenuItem, true);
+ }
+ else if (currentEncoding is UTF8Encoding)
+ {
+ SetCheckedSafe(_encodingUtf8MenuItem, true);
+ }
+ else if (currentEncoding is UnicodeEncoding)
+ {
+ SetCheckedSafe(_encodingUtf16MenuItem, true);
+ }
+ else if (currentEncoding.Equals(Encoding.GetEncoding("iso-8859-1")))
+ {
+ SetCheckedSafe(_encodingIso88591MenuItem, true);
+ }
+
+ // Preserve existing behavior: update ANSI display name
+ _ = (_encodingAnsiMenuItem?.Text = Encoding.Default.HeaderName);
+ }
+
+ public void UpdateHighlightGroups (IEnumerable groups, string selectedGroup)
+ {
+ if (_highlightGroupCombo == null)
+ {
+ return;
+ }
+
+ if (_uiContext != SynchronizationContext.Current)
+ {
+ _uiContext.Post(_ => UpdateHighlightGroups(groups, selectedGroup), null);
+ return;
+ }
+
+ _suppressEvents = true;
+ try
+ {
+ _highlightGroupCombo.Items.Clear();
+
+ foreach (var group in groups)
+ {
+ _ = _highlightGroupCombo.Items.Add(group);
+
+ if (group.Equals(selectedGroup, StringComparison.Ordinal))
+ {
+ _highlightGroupCombo.Text = group;
+ }
+ }
+ }
+ finally
+ {
+ _suppressEvents = false;
+ }
+ }
+
+ public void PopulateFileHistory (IEnumerable fileHistory)
+ {
+ if (_lastUsedMenuItem == null)
+ {
+ return;
+ }
+
+ if (_uiContext != SynchronizationContext.Current)
+ {
+ _uiContext.Post(_ => PopulateFileHistory(fileHistory), null);
+ return;
+ }
+
+ // Unsubscribe from previous dropdown events
+ if (_lastUsedMenuItem.DropDown != null)
+ {
+ _lastUsedMenuItem.DropDown.ItemClicked -= OnHistoryMenuItemClicked;
+ _lastUsedMenuItem.DropDown.MouseUp -= OnHistoryStripMouseUp;
+ }
+
+ var strip = new ToolStripDropDownMenu();
+
+ foreach (var file in fileHistory)
+ {
+ _ = strip.Items.Add(new ToolStripMenuItem(file));
+ }
+
+ strip.ItemClicked += OnHistoryMenuItemClicked;
+ strip.MouseUp += OnHistoryStripMouseUp;
+ _lastUsedMenuItem.DropDown = strip;
+ }
+
+ #region Private Helpers
+
+ private static void SetCheckedSafe (ToolStripMenuItem item, bool value)
+ {
+ _ = (item?.Checked = value);
+ }
+
+ private void OnHighlightGroupComboSelectedIndexChanged (object sender, EventArgs e)
+ {
+ if (_suppressEvents)
+ {
+ return;
+ }
+
+ if (_highlightGroupCombo.SelectedItem is string groupName && !string.IsNullOrEmpty(groupName))
+ {
+ HighlightGroupSelected?.Invoke(this, new HighlightGroupSelectedEventArgs(groupName));
+ }
+ }
+
+ 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)
+ {
+ return;
+ }
+
+ if (sender is ToolStripDropDownMenu strip)
+ {
+ var item = strip.GetItemAt(e.Location);
+ if (item != null && !string.IsNullOrEmpty(item.Text))
+ {
+ HistoryItemRemoveRequested?.Invoke(this, new HistoryItemClickedEventArgs(item.Text));
+ }
+ }
+ }
+
+ private ToolStripMenuItem FindMenuItem (string name)
+ {
+ return FindToolStripItem(_mainMenu, name);
+ }
+
+ private static T FindToolStripItem (ToolStrip strip, string name) where T : ToolStripItem
+ {
+ if (strip == null)
+ {
+ return null;
+ }
+
+ foreach (ToolStripItem item in strip.Items)
+ {
+ if (item.Name == name && item is T typedItem)
+ {
+ return typedItem;
+ }
+
+ if (item is ToolStripDropDownItem dropDown)
+ {
+ var found = FindToolStripItemRecursive(dropDown.DropDownItems, name);
+ if (found != null)
+ {
+ return found;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static T FindToolStripItemRecursive (ToolStripItemCollection items, string name) where T : ToolStripItem
+ {
+ foreach (ToolStripItem item in items)
+ {
+ if (item.Name == name && item is T typedItem)
+ {
+ return typedItem;
+ }
+
+ if (item is ToolStripDropDownItem dropDown)
+ {
+ var found = FindToolStripItemRecursive(dropDown.DropDownItems, name);
+ if (found != null)
+ {
+ return found;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void LogMissingItems ()
+ {
+ // Log warnings for any menu items that couldn't be found during initialization
+ LogIfNull(_closeFileMenuItem, "closeFileToolStripMenuItem");
+ LogIfNull(_searchMenuItem, "searchToolStripMenuItem");
+ LogIfNull(_filterMenuItem, "filterToolStripMenuItem");
+ LogIfNull(_timeshiftMenuItem, "timeshiftToolStripMenuItem");
+ LogIfNull(_encodingAsciiMenuItem, "encodingASCIIToolStripMenuItem");
+ LogIfNull(_highlightGroupCombo, "highlightGroupsToolStripComboBox");
+ LogIfNull(_lastUsedMenuItem, "lastUsedToolStripMenuItem");
+ }
+
+ private static void LogIfNull (object item, string name)
+ {
+ if (item == null)
+ {
+ _logger.Warn("MenuToolbarController: menu item '{0}' not found during initialization", name);
+ }
+ }
+
+ #endregion
+
+ public void Dispose ()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _highlightGroupCombo?.SelectedIndexChanged -= OnHighlightGroupComboSelectedIndexChanged;
+
+ if (_lastUsedMenuItem?.DropDown != null)
+ {
+ _lastUsedMenuItem.DropDown.ItemClicked -= OnHistoryMenuItemClicked;
+ _lastUsedMenuItem.DropDown.MouseUp -= OnHistoryStripMouseUp;
+ }
+
+ _disposed = true;
+ }
+}
diff --git a/src/LogExpert.UI/Services/TabController.cs b/src/LogExpert.UI/Services/TabControllerService/TabController.cs
similarity index 99%
rename from src/LogExpert.UI/Services/TabController.cs
rename to src/LogExpert.UI/Services/TabControllerService/TabController.cs
index 21441e58..1065ca08 100644
--- a/src/LogExpert.UI/Services/TabController.cs
+++ b/src/LogExpert.UI/Services/TabControllerService/TabController.cs
@@ -2,10 +2,12 @@
using LogExpert.UI.Controls.LogWindow;
using LogExpert.UI.Entities;
+using LogExpert.UI.Interface;
+using LogExpert.UI.Interface.Services;
using WeifenLuo.WinFormsUI.Docking;
-namespace LogExpert.UI.Services;
+namespace LogExpert.UI.Services.TabControllerService;
[SupportedOSPlatform("windows")]
internal class TabController : ITabController
diff --git a/src/LogExpert.UI/Services/WindowActivatedEventArgs.cs b/src/LogExpert.UI/Services/TabControllerService/WindowActivatedEventArgs.cs
similarity index 82%
rename from src/LogExpert.UI/Services/WindowActivatedEventArgs.cs
rename to src/LogExpert.UI/Services/TabControllerService/WindowActivatedEventArgs.cs
index 806f99cb..df248829 100644
--- a/src/LogExpert.UI/Services/WindowActivatedEventArgs.cs
+++ b/src/LogExpert.UI/Services/TabControllerService/WindowActivatedEventArgs.cs
@@ -1,6 +1,6 @@
using LogExpert.UI.Controls.LogWindow;
-namespace LogExpert.UI.Services;
+namespace LogExpert.UI.Services.TabControllerService;
internal class WindowActivatedEventArgs (LogWindow window, LogWindow previousWindow) : EventArgs
{
diff --git a/src/LogExpert.UI/Services/WindowAddedEventArgs.cs b/src/LogExpert.UI/Services/TabControllerService/WindowAddedEventArgs.cs
similarity index 74%
rename from src/LogExpert.UI/Services/WindowAddedEventArgs.cs
rename to src/LogExpert.UI/Services/TabControllerService/WindowAddedEventArgs.cs
index f518ac79..d28e95aa 100644
--- a/src/LogExpert.UI/Services/WindowAddedEventArgs.cs
+++ b/src/LogExpert.UI/Services/TabControllerService/WindowAddedEventArgs.cs
@@ -1,6 +1,6 @@
using LogExpert.UI.Controls.LogWindow;
-namespace LogExpert.UI.Services;
+namespace LogExpert.UI.Services.TabControllerService;
internal class WindowAddedEventArgs (LogWindow window) : EventArgs
{
diff --git a/src/LogExpert.UI/Services/WindowClosingEventArgs.cs b/src/LogExpert.UI/Services/TabControllerService/WindowClosingEventArgs.cs
similarity index 86%
rename from src/LogExpert.UI/Services/WindowClosingEventArgs.cs
rename to src/LogExpert.UI/Services/TabControllerService/WindowClosingEventArgs.cs
index df073838..e5707cf0 100644
--- a/src/LogExpert.UI/Services/WindowClosingEventArgs.cs
+++ b/src/LogExpert.UI/Services/TabControllerService/WindowClosingEventArgs.cs
@@ -3,7 +3,7 @@
using LogExpert.UI.Controls.LogWindow;
-namespace LogExpert.UI.Services;
+namespace LogExpert.UI.Interface.Services;
internal class WindowClosingEventArgs (LogWindow window, bool skipConfirmation) : CancelEventArgs
{
diff --git a/src/LogExpert.UI/Services/WindowRemovedEventArgs.cs b/src/LogExpert.UI/Services/TabControllerService/WindowRemovedEventArgs.cs
similarity index 74%
rename from src/LogExpert.UI/Services/WindowRemovedEventArgs.cs
rename to src/LogExpert.UI/Services/TabControllerService/WindowRemovedEventArgs.cs
index 305d4a8d..3e264dec 100644
--- a/src/LogExpert.UI/Services/WindowRemovedEventArgs.cs
+++ b/src/LogExpert.UI/Services/TabControllerService/WindowRemovedEventArgs.cs
@@ -1,6 +1,6 @@
using LogExpert.UI.Controls.LogWindow;
-namespace LogExpert.UI.Services;
+namespace LogExpert.UI.Services.TabControllerService;
internal class WindowRemovedEventArgs (LogWindow window) : EventArgs
{
diff --git a/src/LogExpert.sln b/src/LogExpert.sln
index 66c80e97..eded07ab 100644
--- a/src/LogExpert.sln
+++ b/src/LogExpert.sln
@@ -26,6 +26,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
Solution Items\AssemblyInfo.cs = Solution Items\AssemblyInfo.cs
..\CHANGELOG.md = ..\CHANGELOG.md
+ ..\.github\copilot-instructions.md = ..\.github\copilot-instructions.md
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
..\GitVersion.yml = ..\GitVersion.yml
diff --git a/src/PluginRegistry/PluginHashGenerator.Generated.cs b/src/PluginRegistry/PluginHashGenerator.Generated.cs
index c748f387..2fbd3781 100644
--- a/src/PluginRegistry/PluginHashGenerator.Generated.cs
+++ b/src/PluginRegistry/PluginHashGenerator.Generated.cs
@@ -10,7 +10,7 @@ public static partial class PluginValidator
{
///
/// Gets pre-calculated SHA256 hashes for built-in plugins.
- /// Generated: 2026-01-23 15:56:33 UTC
+ /// Generated: 2026-02-27 11:51:33 UTC
/// Configuration: Release
/// Plugin count: 22
///
@@ -18,28 +18,28 @@ public static Dictionary GetBuiltInPluginHashes()
{
return new Dictionary(StringComparer.OrdinalIgnoreCase)
{
- ["AutoColumnizer.dll"] = "1D07B11AE3270F10155D1D6AA54FE70ED0080415E43A1F2B2A0EF72875DA84AF",
+ ["AutoColumnizer.dll"] = "7077A562ABAAD963BEB2A6CFC5FA63CC1D19C76452995F4668728C0DB6D65826",
["BouncyCastle.Cryptography.dll"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6",
["BouncyCastle.Cryptography.dll (x86)"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6",
- ["CsvColumnizer.dll"] = "4DDCDD5E767C265AE31FF09DE7C6A093DC66979CB8EC2D9A8CBCB9A79096ED51",
- ["CsvColumnizer.dll (x86)"] = "4DDCDD5E767C265AE31FF09DE7C6A093DC66979CB8EC2D9A8CBCB9A79096ED51",
- ["DefaultPlugins.dll"] = "9F2745A5376897CA4436C6A90B9B2BB0A29F4225F81BF95C0B868E66DFBB8D3C",
- ["FlashIconHighlighter.dll"] = "D7D008CF7945D67C790BCC17649F01810B1B2F243483DBBAE08B653A9B62FBA4",
- ["GlassfishColumnizer.dll"] = "E25C288D6C13B1B80A58EC25906D9BEBAC5C5B46AB98F28B6C4C9E90401F9E40",
- ["JsonColumnizer.dll"] = "A74907FDB01478D84F89C8AEB1DD83B4A6F2FDF884760296EDCBD2BA3D46FF1B",
- ["JsonCompactColumnizer.dll"] = "AEF65E2C43EE39EE7DC80FBD8D8F499E6E215D6839FF6BDA7CED98C6312DB5BA",
- ["Log4jXmlColumnizer.dll"] = "F85079FDA7BB9F8837AA6F5A6449B5F222552B77886F78F3A16A125E4EDD288C",
- ["LogExpert.Core.dll"] = "CACF8255B06CB4AF43595196419192CF8BC2CB630456F15E6BDBC7292AC8A86E",
- ["LogExpert.Resources.dll"] = "ABA39DD2544E6F8D357008649500B8AEDC88BD233EC3ADEE9304035EB9FD1853",
+ ["CsvColumnizer.dll"] = "ACABF8FE90E75E88B58653253256F523673856C42BD313DC176B9BF89032FDB0",
+ ["CsvColumnizer.dll (x86)"] = "ACABF8FE90E75E88B58653253256F523673856C42BD313DC176B9BF89032FDB0",
+ ["DefaultPlugins.dll"] = "C25831E431B6902BDB48A5E2896365189BCC650EEB3632FC1999F1DC4C5C93D3",
+ ["FlashIconHighlighter.dll"] = "C7C20498D7C5F79EFF42BA8F9A5291C337A56DD36E5E3B4A6832113E44EAC95D",
+ ["GlassfishColumnizer.dll"] = "A51BA6FD40FCEBF495ACDC93990E310C42A54CC82F7AC89C0739AB5480F95D9D",
+ ["JsonColumnizer.dll"] = "4BF75A8E1060DD96AEDDA578AC9ADD46684EAB68E658F7A6929F01F7C2BCDF4C",
+ ["JsonCompactColumnizer.dll"] = "744BB326E206139929581046E2E5D827BE571A1645BB33545CAD8B6A982625D0",
+ ["Log4jXmlColumnizer.dll"] = "E578036CF907C018E0A94D001F5A15EC2C369F5F14331CB927FB8CA0606DAFEB",
+ ["LogExpert.Core.dll"] = "B2B7F38E5BDD3BCD45405EE635944E9661B0AE42D00F7DD48D87F7E7516B63FB",
+ ["LogExpert.Resources.dll"] = "D52BB5FF1F1EE3ED04D5516E3170C41EB5BC6F46960FB8807A0D106DADA2E216",
["Microsoft.Extensions.DependencyInjection.Abstractions.dll"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93",
["Microsoft.Extensions.DependencyInjection.Abstractions.dll (x86)"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93",
["Microsoft.Extensions.Logging.Abstractions.dll"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D",
["Microsoft.Extensions.Logging.Abstractions.dll (x86)"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D",
- ["RegexColumnizer.dll"] = "F44BF1FFF92E4D96C66E0B518D3E276BFE92E7DDB9EB0F93479ABC8EBE072CE2",
- ["SftpFileSystem.dll"] = "1971EE85E9CEDDD0A38E846A3EC78F42D823B1DD1CA1A02B0A132B436FC1B32D",
- ["SftpFileSystem.dll (x86)"] = "46D0701DB009188D5FADF47FDB54A5ADC85DB39D73CCE40C7823059CF9F19872",
- ["SftpFileSystem.Resources.dll"] = "92C1963B793F646215C33E7B3E459564E0EDB6193D305739AEB871092572F318",
- ["SftpFileSystem.Resources.dll (x86)"] = "92C1963B793F646215C33E7B3E459564E0EDB6193D305739AEB871092572F318",
+ ["RegexColumnizer.dll"] = "1564B50ABCF3B343C6E1A3247B6C94D3299D5185FC36DE8EB7DBBC8A29CE4DB1",
+ ["SftpFileSystem.dll"] = "EA142CB3F6C2B83008A3723ECC0C424374A297859E99B7DA7A6CE9DE2BBEB72C",
+ ["SftpFileSystem.dll (x86)"] = "0E57E9EED2F1637C8A6ABC32A1EEEA8AE8A7E855351CF832500DDE06C7382937",
+ ["SftpFileSystem.Resources.dll"] = "481373B967EF74F38A3A92BCE54D22635BFC1817218832AE3BC89EE20A858AAA",
+ ["SftpFileSystem.Resources.dll (x86)"] = "481373B967EF74F38A3A92BCE54D22635BFC1817218832AE3BC89EE20A858AAA",
};
}