diff --git a/src/ColumnizerLib/Extensions/Extensions.cs b/src/ColumnizerLib/Extensions/Extensions.cs
new file mode 100644
index 00000000..f0071ee6
--- /dev/null
+++ b/src/ColumnizerLib/Extensions/Extensions.cs
@@ -0,0 +1,17 @@
+namespace ColumnizerLib.Extensions;
+
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1708:Identifiers should differ by more than case", Justification = "Intentionally")]
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Intentionally")]
+public static class Extensions
+{
+ extension(ILogLine logLine)
+ {
+ public string ToClipBoardText () => logLine == null ? string.Empty : $"\t{logLine.LineNumber + 1}\t{logLine.FullLine}";
+ }
+
+ extension(ILogLineMemory logLine)
+ {
+ public string ToClipBoardText () => logLine == null ? string.Empty : $"\t{logLine.LineNumber + 1}\t{logLine.FullLine}";
+
+ }
+}
\ No newline at end of file
diff --git a/src/ColumnizerLib/Extensions/LogLineExtensions.cs b/src/ColumnizerLib/Extensions/LogLineExtensions.cs
deleted file mode 100644
index 698d9e5f..00000000
--- a/src/ColumnizerLib/Extensions/LogLineExtensions.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace ColumnizerLib.Extensions;
-
-//TODO: Move this to LogExpert.UI, change to internal and fix tests
-public static class LogLineExtensions
-{
- //TOOD: check if the callers are checking for null before calling
- public static string ToClipBoardText (this ILogLine logLine)
- {
- return logLine == null ? string.Empty : $"\t{logLine.LineNumber + 1}\t{logLine.FullLine}";
- }
-
- public static string ToClipBoardText (this ILogLineMemory logLine)
- {
- return logLine == null ? string.Empty : $"\t{logLine.LineNumber + 1}\t{logLine.FullLine}";
- }
-}
\ No newline at end of file
diff --git a/src/CsvColumnizer/Resources.de.resx b/src/CsvColumnizer/Resources.de.resx
index a0b3b097..0422a468 100644
--- a/src/CsvColumnizer/Resources.de.resx
+++ b/src/CsvColumnizer/Resources.de.resx
@@ -73,4 +73,16 @@
Abbrechen
-
+
+ Fehler
+
+
+ Fehler beim Deserialisieren der Konfigurationsdaten: {0}
+
+
+ Teilt die CSV-Dateien in Spalten auf.
+
+Credits:
+Dieser Columnizer verwendet den CsvHelper. https://github.com/JoshClose/CsvHelper.
+
+
\ No newline at end of file
diff --git a/src/LogExpert.Core/Classes/Log/LogfileReader.cs b/src/LogExpert.Core/Classes/Log/LogfileReader.cs
index f8d64146..1c373065 100644
--- a/src/LogExpert.Core/Classes/Log/LogfileReader.cs
+++ b/src/LogExpert.Core/Classes/Log/LogfileReader.cs
@@ -531,6 +531,7 @@ public ILogLine GetLogLine (int lineNum)
return GetLogLineInternal(lineNum).Result;
}
+ //TODO Make Task Based
public ILogLineMemory GetLogLineMemory (int lineNum)
{
return GetLogLineMemoryInternal(lineNum).Result;
diff --git a/src/LogExpert.Core/Interface/ILogView.cs b/src/LogExpert.Core/Interface/ILogView.cs
index 5dbc0e80..8427d0f1 100644
--- a/src/LogExpert.Core/Interface/ILogView.cs
+++ b/src/LogExpert.Core/Interface/ILogView.cs
@@ -10,16 +10,17 @@ public interface ILogView
#region Properties
ILogLineMemoryColumnizer CurrentColumnizer { get; }
+
string FileName { get; }
#endregion
#region Public methods
- void SelectLogLine(int lineNumber);
- void SelectAndEnsureVisible(int line, bool triggerSyncCall);
- void RefreshLogView();
- void DeleteBookmarks(List lineNumList);
+ void SelectLogLine (int lineNumber);
+ void SelectAndEnsureVisible (int line, bool triggerSyncCall);
+ void RefreshLogView ();
+ void DeleteBookmarks (List lineNumList);
#endregion
}
\ No newline at end of file
diff --git a/src/LogExpert.Resources/Resources.Designer.cs b/src/LogExpert.Resources/Resources.Designer.cs
index 06018dff..b98b1101 100644
--- a/src/LogExpert.Resources/Resources.Designer.cs
+++ b/src/LogExpert.Resources/Resources.Designer.cs
@@ -5916,6 +5916,33 @@ public static System.Drawing.Bitmap Star {
}
}
+ ///
+ /// Looks up a localized string similar to TabController is already initialized with a DockPanel.
+ ///
+ public static string TabController_Error_Message_AlreadInitialized {
+ get {
+ return ResourceManager.GetString("TabController_Error_Message_AlreadInitialized", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to TabController is not initialized. Call InitializeDockPanel first..
+ ///
+ public static string TabController_Error_Message_NotInitialized {
+ get {
+ return ResourceManager.GetString("TabController_Error_Message_NotInitialized", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Window already tracked.
+ ///
+ public static string TabController_Error_Message_WindowAlreadyTracked {
+ get {
+ return ResourceManager.GetString("TabController_Error_Message_WindowAlreadyTracked", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Name:.
///
diff --git a/src/LogExpert.Resources/Resources.de.resx b/src/LogExpert.Resources/Resources.de.resx
index c1ec0c3d..46a7a27a 100644
--- a/src/LogExpert.Resources/Resources.de.resx
+++ b/src/LogExpert.Resources/Resources.de.resx
@@ -2121,4 +2121,16 @@ LogExpert neu starten, um die Änderungen zu übernehmen?
{0} ist bereits initialisiert
+
+ {0} muss im UI-Thread erstellt werden
+
+
+ TabController ist nicht initialisiert. Rufen Sie zuerst InitializeDockPanel auf.
+
+
+ Fenster bereits verfolgt
+
+
+ TabController ist bereits mit einem DockPanel initialisiert
+
\ No newline at end of file
diff --git a/src/LogExpert.Resources/Resources.resx b/src/LogExpert.Resources/Resources.resx
index efeac89a..b71dc928 100644
--- a/src/LogExpert.Resources/Resources.resx
+++ b/src/LogExpert.Resources/Resources.resx
@@ -2133,4 +2133,13 @@ Restart LogExpert to apply changes?
{0} must be created on UI thread
+
+ TabController is not initialized. Call InitializeDockPanel first.
+
+
+ Window already tracked
+
+
+ TabController is already initialized with a DockPanel
+
\ No newline at end of file
diff --git a/src/LogExpert.Tests/Services/TabControllerTests.cs b/src/LogExpert.Tests/Services/TabControllerTests.cs
new file mode 100644
index 00000000..13e65d4b
--- /dev/null
+++ b/src/LogExpert.Tests/Services/TabControllerTests.cs
@@ -0,0 +1,436 @@
+using System.Runtime.Versioning;
+using System.Windows.Forms;
+
+using LogExpert.UI.Controls.LogWindow;
+using LogExpert.UI.Services;
+
+using NUnit.Framework;
+
+using WeifenLuo.WinFormsUI.Docking;
+
+namespace LogExpert.Tests.Services;
+
+///
+/// Unit tests for TabController.
+///
+/// Note: Many tests are limited because LogWindow is a complex WinForms control
+/// that cannot be easily mocked or subclassed. Tests that require actual LogWindow
+/// instances would need to be run as integration tests with full UI infrastructure.
+///
+/// These tests focus on the core TabController functionality that can be tested
+/// without instantiating LogWindow objects.
+///
+[TestFixture]
+[SupportedOSPlatform("windows")]
+[Apartment(ApartmentState.STA)] // Required for WinForms controls
+internal class TabControllerTests : IDisposable
+{
+ private Form _testForm;
+ private DockPanel _dockPanel;
+ private TabController _tabController;
+ private bool _disposed;
+
+ [SetUp]
+ public void Setup()
+ {
+ // Create a real Form and DockPanel for testing
+ // This is necessary because DockPanel requires WinForms infrastructure
+ _testForm = new Form();
+ _dockPanel = new DockPanel
+ {
+ Dock = DockStyle.Fill,
+ DocumentStyle = DocumentStyle.DockingMdi
+ };
+ _testForm.Controls.Add(_dockPanel);
+ _testForm.Show(); // Must show form for DockPanel to work
+
+ _tabController = new TabController(_dockPanel);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ _tabController?.Dispose();
+ _testForm?.Close();
+ _testForm?.Dispose();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ _tabController?.Dispose();
+ _testForm?.Dispose();
+ }
+
+ _disposed = true;
+ }
+
+ #region Constructor Tests
+
+ [Test]
+ public void Constructor_WithDockPanel_InitializesSuccessfully()
+ {
+ // Arrange & Act - already done in Setup
+
+ // Assert
+ Assert.That(_tabController, Is.Not.Null);
+ Assert.That(_tabController.GetWindowCount(), Is.EqualTo(0));
+ }
+
+ [Test]
+ public void Constructor_WithNullDockPanel_ThrowsArgumentNullException()
+ {
+ // Arrange & Act & Assert
+ Assert.Throws(() => new TabController(null));
+ }
+
+ [Test]
+ public void Constructor_WithoutDockPanel_CreatesUninitializedController()
+ {
+ // Arrange & Act
+ using var controller = new TabController();
+
+ // Assert - controller should be created but not initialized
+ // Calling GetWindowCount should still work (returns 0)
+ Assert.That(controller.GetWindowCount(), Is.EqualTo(0));
+ }
+
+ #endregion
+
+ #region InitializeDockPanel Tests
+
+ [Test]
+ public void InitializeDockPanel_WithValidDockPanel_Succeeds()
+ {
+ // Arrange
+ using var controller = new TabController();
+
+ // Act
+ controller.InitializeDockPanel(_dockPanel);
+
+ // Assert - no exception thrown, and controller should work
+ Assert.That(controller.GetWindowCount(), Is.EqualTo(0));
+ }
+
+ [Test]
+ public void InitializeDockPanel_WithNullDockPanel_ThrowsArgumentNullException()
+ {
+ // Arrange
+ using var controller = new TabController();
+
+ // Act & Assert
+ Assert.Throws(() => controller.InitializeDockPanel(null));
+ }
+
+ [Test]
+ public void InitializeDockPanel_WhenAlreadyInitialized_ThrowsInvalidOperationException()
+ {
+ // Arrange
+ using var controller = new TabController(_dockPanel);
+
+ // Create a new form and dock panel for the second initialization attempt
+ using var form2 = new Form();
+ using var dockPanel2 = new DockPanel();
+ form2.Controls.Add(dockPanel2);
+
+ // Act & Assert
+ Assert.Throws(() => controller.InitializeDockPanel(dockPanel2));
+ }
+
+ #endregion
+
+ #region GetAllWindowsFromDockPanel Tests
+
+ [Test]
+ public void GetAllWindowsFromDockPanel_WhenNotInitialized_ReturnsEmptyList()
+ {
+ // Arrange
+ using var controller = new TabController();
+
+ // Act
+ var result = controller.GetAllWindowsFromDockPanel();
+
+ // Assert
+ Assert.That(result, Is.Empty);
+ }
+
+ [Test]
+ public void GetAllWindowsFromDockPanel_WhenInitializedButEmpty_ReturnsEmptyList()
+ {
+ // Arrange - already done in Setup
+
+ // Act
+ var result = _tabController.GetAllWindowsFromDockPanel();
+
+ // Assert
+ Assert.That(result, Is.Empty);
+ }
+
+ [Test]
+ public void GetAllWindowsFromDockPanel_ReturnsReadOnlyList()
+ {
+ // Arrange - already done in Setup
+
+ // Act
+ var result = _tabController.GetAllWindowsFromDockPanel();
+
+ // Assert - ReadOnlyCollection implements IReadOnlyList
+ Assert.That(result, Is.InstanceOf>());
+ }
+
+ #endregion
+
+ #region GetAllWindows Tests
+
+ [Test]
+ public void GetAllWindows_WhenEmpty_ReturnsEmptyList()
+ {
+ // Arrange - already done in Setup
+
+ // Act
+ var result = _tabController.GetAllWindows();
+
+ // Assert
+ Assert.That(result, Is.Empty);
+ Assert.That(result, Is.InstanceOf>());
+ }
+
+ #endregion
+
+ #region GetWindowCount Tests
+
+ [Test]
+ public void GetWindowCount_WhenEmpty_ReturnsZero()
+ {
+ // Arrange - already done in Setup
+
+ // Act
+ var result = _tabController.GetWindowCount();
+
+ // Assert
+ Assert.That(result, Is.EqualTo(0));
+ }
+
+ #endregion
+
+ #region HasWindow Tests
+
+ [Test]
+ public void HasWindow_WithNullWindow_ReturnsFalse()
+ {
+ // Arrange - already done in Setup
+
+ // Act
+ var result = _tabController.HasWindow(null);
+
+ // Assert
+ Assert.That(result, Is.False);
+ }
+
+ #endregion
+
+ #region GetActiveWindow Tests
+
+ [Test]
+ public void GetActiveWindow_WhenNoWindowActive_ReturnsNull()
+ {
+ // Arrange - already done in Setup
+
+ // Act
+ var result = _tabController.GetActiveWindow();
+
+ // Assert
+ Assert.That(result, Is.Null);
+ }
+
+ #endregion
+
+ #region FindWindowByFileName Tests
+
+ [Test]
+ public void FindWindowByFileName_WithNullFileName_ReturnsNull()
+ {
+ // Arrange - already done in Setup
+
+ // Act
+ var result = _tabController.FindWindowByFileName(null);
+
+ // Assert
+ Assert.That(result, Is.Null);
+ }
+
+ [Test]
+ public void FindWindowByFileName_WithEmptyFileName_ReturnsNull()
+ {
+ // Arrange - already done in Setup
+
+ // Act
+ var result = _tabController.FindWindowByFileName(string.Empty);
+
+ // Assert
+ Assert.That(result, Is.Null);
+ }
+
+ [Test]
+ public void FindWindowByFileName_WhenNoWindowsExist_ReturnsNull()
+ {
+ // Arrange - already done in Setup
+
+ // Act
+ var result = _tabController.FindWindowByFileName("test.log");
+
+ // Assert
+ Assert.That(result, Is.Null);
+ }
+
+ #endregion
+
+ #region AddWindow Tests
+
+ [Test]
+ public void AddWindow_WithNullWindow_ThrowsArgumentNullException()
+ {
+ // Arrange - already done in Setup
+
+ // Act & Assert
+ Assert.Throws(() => _tabController.AddWindow(null, "Test Window"));
+ }
+
+ [Test]
+ public void AddWindow_WhenNotInitialized_ThrowsInvalidOperationException()
+ {
+ // Arrange
+ using var controller = new TabController();
+
+ // Create a mock-like object that's not null to avoid ArgumentNullException
+ // We need to test that the "not initialized" check happens
+ // Unfortunately, LogWindow cannot be instantiated without its dependencies
+ // So we can only verify the ArgumentNullException is thrown first for null
+ var ex = Assert.Throws(() => controller.AddWindow(null, "Test"));
+ Assert.That(ex.ParamName, Is.EqualTo("window"));
+ }
+
+ #endregion
+
+ #region RemoveWindow Tests
+
+ [Test]
+ public void RemoveWindow_WithNullWindow_DoesNotThrow()
+ {
+ // Arrange - already done in Setup
+
+ // Act & Assert - should not throw
+ Assert.DoesNotThrow(() => _tabController.RemoveWindow(null));
+ }
+
+ #endregion
+
+ #region CloseWindow Tests
+
+ [Test]
+ public void CloseWindow_WithNullWindow_DoesNotThrow()
+ {
+ // Arrange - already done in Setup
+
+ // Act & Assert - should not throw
+ Assert.DoesNotThrow(() => _tabController.CloseWindow(null));
+ }
+
+ #endregion
+
+ #region CloseAllWindows Tests
+
+ [Test]
+ public void CloseAllWindows_WhenEmpty_DoesNotThrow()
+ {
+ // Arrange - already done in Setup
+
+ // Act & Assert - should not throw
+ Assert.DoesNotThrow(() => _tabController.CloseAllWindows());
+ }
+
+ #endregion
+
+ #region CloseAllExcept Tests
+
+ [Test]
+ public void CloseAllExcept_WithNullWindow_DoesNotThrow()
+ {
+ // Arrange - already done in Setup
+
+ // Act & Assert - should not throw
+ Assert.DoesNotThrow(() => _tabController.CloseAllExcept(null));
+ }
+
+ #endregion
+
+ #region ActivateWindow Tests
+
+ [Test]
+ public void ActivateWindow_WithNullWindow_DoesNotThrow()
+ {
+ // Arrange - already done in Setup
+
+ // Act & Assert - should not throw
+ Assert.DoesNotThrow(() => _tabController.ActivateWindow(null));
+ }
+
+ #endregion
+
+ #region SwitchToNextWindow Tests
+
+ [Test]
+ public void SwitchToNextWindow_WhenEmpty_DoesNotThrow()
+ {
+ // Arrange - already done in Setup
+
+ // Act & Assert - should not throw
+ Assert.DoesNotThrow(() => _tabController.SwitchToNextWindow());
+ }
+
+ #endregion
+
+ #region SwitchToPreviousWindow Tests
+
+ [Test]
+ public void SwitchToPreviousWindow_WhenEmpty_DoesNotThrow()
+ {
+ // Arrange - already done in Setup
+
+ // Act & Assert - should not throw
+ Assert.DoesNotThrow(() => _tabController.SwitchToPreviousWindow());
+ }
+
+ #endregion
+
+ #region Dispose Tests
+
+ [Test]
+ public void Dispose_MultipleCallsDoNotThrow()
+ {
+ // Arrange
+ using var controller = new TabController(_dockPanel);
+
+ // Act & Assert - multiple dispose calls should not throw
+ Assert.DoesNotThrow(() =>
+ {
+ controller.Dispose();
+ controller.Dispose();
+ controller.Dispose();
+ });
+ }
+
+ #endregion
+}
diff --git a/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs b/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs
index 4752bc3b..1094b098 100644
--- a/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs
+++ b/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs
@@ -112,12 +112,14 @@ internal partial class LogWindow : DockContent, ILogPaintContextUI, ILogView, IL
private ILogLineMemoryColumnizer _forcedColumnizer;
private ILogLineMemoryColumnizer _forcedColumnizerForLoading;
+
private bool _isDeadFile;
private bool _isErrorShowing;
private bool _isLoadError;
private bool _isLoading;
private bool _isSearching;
private bool _isTimestampDisplaySyncing;
+
private List _lastFilterLinesList = [];
private int _lineHeight;
diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs
index 3285cd19..8e178b65 100644
--- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs
+++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs
@@ -32,7 +32,6 @@
namespace LogExpert.UI.Controls.LogTabWindow;
// Data shared over all LogTabWindow instances
-//TODO: Can we get rid of this class?
[SupportedOSPlatform("windows")]
internal partial class LogTabWindow : Form, ILogTabWindow
{
@@ -44,8 +43,9 @@ internal partial class LogTabWindow : Form, ILogTabWindow
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly Icon _deadIcon;
- private readonly ILedIndicatorService _ledService;
- private readonly Lock _windowListLock = new();
+ private readonly LedIndicatorService _ledService;
+
+ private readonly TabController _tabController;
private bool _disposed;
@@ -53,7 +53,6 @@ internal partial class LogTabWindow : Form, ILogTabWindow
private readonly int _instanceNumber;
- private readonly IList _logWindowList = [];
private readonly bool _showInstanceNumbers;
private readonly string[] _startupFileNames;
@@ -84,6 +83,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;
+
ApplyTextResources();
ConfigManager = configManager;
@@ -617,49 +622,146 @@ public ILogLineMemoryColumnizer GetColumnizerHistoryEntry (string fileName)
public void SwitchTab (bool shiftPressed)
{
- var index = dockPanel.Contents.IndexOf(dockPanel.ActiveContent);
if (shiftPressed)
{
- index--;
- if (index < 0)
- {
- index = dockPanel.Contents.Count - 1;
- }
-
- if (index < 0)
- {
- return;
- }
+ _tabController.SwitchToPreviousWindow();
}
else
{
- index++;
- if (index >= dockPanel.Contents.Count)
+ _tabController.SwitchToNextWindow();
+ }
+ }
+
+ public void ScrollAllTabsToTimestamp (DateTime timestamp, LogWindow.LogWindow senderWindow)
+ {
+ foreach (var logWindow in _tabController.GetAllWindows())
+ {
+ if (logWindow != senderWindow)
{
- index = 0;
+ if (logWindow.ScrollToTimestamp(timestamp, false, false))
+ {
+ _ledService.UpdateWindowActivity(logWindow, DIFF_MAX);
+ }
}
}
+ }
- if (index < dockPanel.Contents.Count)
+ ///
+ /// Handles the WindowActivated event from TabController.
+ /// Updates CurrentLogWindow and connects tool windows to the newly activated window.
+ ///
+ /// The TabController that raised the event
+ /// Event args containing the activated window and previous window
+ [SupportedOSPlatform("windows")]
+ private void OnTabControllerWindowActivated (object sender, WindowActivatedEventArgs e)
+ {
+ var newWindow = e.Window;
+ var previousWindow = e.PreviousWindow;
+
+ if (newWindow == _currentLogWindow)
+ {
+ return;
+ }
+
+ // Update CurrentLogWindow - this triggers ChangeCurrentLogWindow internally
+ // which handles disconnecting from previous window and connecting to new window
+ CurrentLogWindow = newWindow;
+
+ // Clear dirty state for the newly activated window
+ if (newWindow?.Tag is LogWindowData data)
{
- (dockPanel.Contents[index] as DockContent).Activate();
+ data.LedState.IsDirty = false;
+
+ // Update the tab icon to reflect cleared dirty state
+ var icon = GetLedIcon(data.LedState.DiffSum, data);
+ _ = BeginInvoke(new SetTabIconDelegate(SetTabIcon), newWindow, icon);
+ }
+
+ // Notify the window it has been activated
+ newWindow?.LogWindowActivated();
+
+ // Connect tool windows (bookmark window, etc.) to new window
+ if (newWindow != null)
+ {
+ ConnectToolWindows(newWindow);
}
}
- public void ScrollAllTabsToTimestamp (DateTime timestamp, LogWindow.LogWindow senderWindow)
+ ///
+ /// Handles the WindowAdded event from TabController.
+ /// Performs additional setup for newly added windows that LogTabWindow needs.
+ ///
+ /// The TabController that raised the event
+ /// Event args containing the added window and title
+ [SupportedOSPlatform("windows")]
+ private void OnTabControllerWindowAdded (object sender, WindowAddedEventArgs e)
{
- lock (_logWindowList)
+ var logWindow = e.Window;
+ var title = e.Title;
+
+ if (logWindow.Tag is not LogWindowData)
{
- foreach (var logWindow in _logWindowList)
+ LogWindowData data = new()
{
- if (logWindow != senderWindow)
- {
- if (logWindow.ScrollToTimestamp(timestamp, false, false))
- {
- _ledService.UpdateWindowActivity(logWindow, DIFF_MAX);
- }
- }
- }
+ LedState = new LedState(),
+ Color = _defaultTabColor
+ };
+
+ logWindow.Tag = data;
+ }
+
+ _ledService.RegisterWindow(logWindow);
+
+ ConnectEventHandlers(logWindow);
+ }
+
+ ///
+ /// Handles the WindowClosing event from TabController.
+ /// Performs pre-close validation and cleanup. Can cancel the close operation.
+ ///
+ /// The TabController that raised the event
+ /// Event args containing the window being closed and cancellation support
+ [SupportedOSPlatform("windows")]
+ private void OnTabControllerWindowClosing (object sender, WindowClosingEventArgs e)
+ {
+ var logWindow = e.Window;
+ var skipConfirmation = e.SkipConfirmation;
+
+ if (_tabController.GetWindowCount() == 1 && !skipConfirmation)
+ {
+ //TODO Add logic to confirm closing the last tab if desired
+ }
+
+ if (logWindow.Tag is LogWindowData data)
+ {
+ data.ToolTip?.Hide(logWindow);
+ }
+ }
+
+ ///
+ /// Handles the WindowRemoved event from TabController.
+ /// Cleans up resources and event subscriptions for the removed window.
+ ///
+ /// The TabController that raised the event
+ /// Event args containing the removed window
+ [SupportedOSPlatform("windows")]
+ private void OnTabControllerWindowRemoved (object sender, WindowRemovedEventArgs e)
+ {
+ var logWindow = e.Window;
+
+ _ledService.UnregisterWindow(logWindow);
+
+ DisconnectEventHandlers(logWindow);
+
+ if (logWindow.Tag is LogWindowData data)
+ {
+ data.ToolTip?.Dispose();
+ logWindow.Tag = null;
+ }
+
+ if (CurrentLogWindow == logWindow)
+ {
+ ChangeCurrentLogWindow(null);
}
}
@@ -713,7 +815,7 @@ public HighlightGroup FindHighlightGroupByFileMask (string fileName)
public void SelectTab (ILogWindow logWindow)
{
- logWindow.Activate();
+ _tabController.ActivateWindow(logWindow as LogWindow.LogWindow);
}
[SupportedOSPlatform("windows")]
@@ -762,12 +864,10 @@ public void NotifySettingsChanged (object sender, SettingsFlags flags)
public IList GetListOfOpenFiles ()
{
IList list = [];
- lock (_logWindowList)
+
+ foreach (var logWindow in _tabController.GetAllWindows())
{
- foreach (var logWindow in _logWindowList)
- {
- list.Add(new WindowFileEntry(logWindow));
- }
+ list.Add(new WindowFileEntry(logWindow));
}
return list;
@@ -867,14 +967,11 @@ private void DestroyBookmarkWindow ()
private void SaveLastOpenFilesList ()
{
- foreach (DockContent content in dockPanel.Contents.Cast())
+ foreach (var logWin in _tabController.GetAllWindowsFromDockPanel())
{
- if (content is LogWindow.LogWindow logWin)
+ if (!logWin.IsTempFile)
{
- if (!logWin.IsTempFile)
- {
- ConfigManager.Settings.LastOpenFilesList.Add(logWin.GivenFileName);
- }
+ ConfigManager.Settings.LastOpenFilesList.Add(logWin.GivenFileName);
}
}
}
@@ -942,6 +1039,13 @@ private void AddFileTabs (string[] fileNames)
Activate();
}
+ ///
+ /// Adds a LogWindow to the tab system.
+ /// Sets up window properties, delegates to TabController, and performs additional setup.
+ ///
+ /// The window to add
+ /// Tab title
+ /// Skip adding to DockPanel (for deferred loading)
[SupportedOSPlatform("windows")]
private void AddLogWindow (LogWindow.LogWindow logWindow, string title, bool doNotAddToPanel)
{
@@ -950,23 +1054,13 @@ private void AddLogWindow (LogWindow.LogWindow logWindow, string title, bool doN
SetTooltipText(logWindow, title);
logWindow.DockAreas = DockAreas.Document | DockAreas.Float;
- if (!doNotAddToPanel)
- {
- logWindow.Show(dockPanel);
- }
-
- LogWindowData data = new()
- {
- LedState = new LedState()
- };
-
- logWindow.Tag = data;
+ _tabController.AddWindow(logWindow, title, doNotAddToPanel);
- lock (_windowListLock)
- {
- _logWindowList.Add(logWindow);
- }
+ logWindow.Visible = true;
+ }
+ private void ConnectEventHandlers (LogWindow.LogWindow logWindow)
+ {
logWindow.FileSizeChanged += OnFileSizeChanged;
logWindow.TailFollowed += OnTailFollowed;
logWindow.Disposed += OnLogWindowDisposed;
@@ -975,10 +1069,6 @@ private void AddLogWindow (LogWindow.LogWindow logWindow, string title, bool doN
logWindow.FilterListChanged += OnLogWindowFilterListChanged;
logWindow.CurrentHighlightGroupChanged += OnLogWindowCurrentHighlightGroupChanged;
logWindow.SyncModeChanged += OnLogWindowSyncModeChanged;
-
- logWindow.Visible = true;
-
- _ledService.RegisterWindow(logWindow);
}
[SupportedOSPlatform("windows")]
@@ -993,7 +1083,7 @@ private void DisconnectEventHandlers (LogWindow.LogWindow logWindow)
logWindow.CurrentHighlightGroupChanged -= OnLogWindowCurrentHighlightGroupChanged;
logWindow.SyncModeChanged -= OnLogWindowSyncModeChanged;
- var data = logWindow.Tag as LogWindowData;
+ //var data = logWindow.Tag as LogWindowData;
//data.tabPage.MouseClick -= tabPage_MouseClick;
//data.tabPage.TabDoubleClick -= tabPage_TabDoubleClick;
//data.tabPage.ContextMenuStrip = null;
@@ -1007,21 +1097,15 @@ private void AddToFileHistory (string fileName)
FillHistoryMenu();
}
+ ///
+ /// Finds an existing window for a file.
+ ///
+ /// File name to search for
+ /// The LogWindow for the file, or null if not found
[SupportedOSPlatform("windows")]
private LogWindow.LogWindow FindWindowForFile (string fileName)
{
- lock (_logWindowList)
- {
- foreach (var logWindow in _logWindowList)
- {
- if (logWindow.FileName.ToUpperInvariant().Equals(fileName.ToUpperInvariant(), StringComparison.Ordinal))
- {
- return logWindow;
- }
- }
- }
-
- return null;
+ return _tabController.FindWindowByFileName(fileName);
}
[SupportedOSPlatform("windows")]
@@ -1041,30 +1125,21 @@ private void FillHistoryMenu ()
lastUsedToolStripMenuItem.DropDown = strip;
}
+ ///
+ /// Removes a LogWindow from the tab system.
+ /// Delegates to TabController for removal and cleanup.
+ ///
+ /// The window to remove
[SupportedOSPlatform("windows")]
private void RemoveLogWindow (LogWindow.LogWindow logWindow)
{
- lock (_windowListLock)
- {
- _ = _logWindowList.Remove(logWindow);
- _ledService.UnregisterWindow(logWindow);
- }
-
- DisconnectEventHandlers(logWindow);
+ _tabController.RemoveWindow(logWindow);
}
[SupportedOSPlatform("windows")]
private void RemoveAndDisposeLogWindow (LogWindow.LogWindow logWindow, bool dontAsk)
{
- if (CurrentLogWindow == logWindow)
- {
- ChangeCurrentLogWindow(null);
- }
-
- lock (_logWindowList)
- {
- _ = _logWindowList.Remove(logWindow);
- }
+ _tabController.RemoveWindow(logWindow);
logWindow.Close(dontAsk);
}
@@ -1536,13 +1611,13 @@ private void NotifyWindowsForChangedPrefs (SettingsFlags flags)
var fontName = ConfigManager.Settings.Preferences.FontName;
var fontSize = ConfigManager.Settings.Preferences.FontSize;
- lock (_logWindowList)
+ //lock (_logWindowList)
+ //{
+ foreach (var logWindow in _tabController.GetAllWindows())
{
- foreach (var logWindow in _logWindowList)
- {
- logWindow.PreferencesChanged(fontName, fontSize, setLastColumnWidth, lastColumnWidth, false, flags);
- }
+ logWindow.PreferencesChanged(fontName, fontSize, setLastColumnWidth, lastColumnWidth, false, flags);
}
+ //}
_bookmarkWindow.PreferencesChanged(fontName, fontSize, setLastColumnWidth, lastColumnWidth, flags);
@@ -1588,15 +1663,15 @@ private void ApplySettings (Settings settings, SettingsFlags flags)
private void SetTabIcons (Preferences preferences)
{
_ledService.RegenerateIcons(preferences.ShowTailColor);
- lock (_logWindowList)
+ //lock (_logWindowList)
+ //{
+ foreach (var logWindow in _tabController.GetAllWindows())
{
- foreach (var logWindow in _logWindowList)
- {
- var data = logWindow.Tag as LogWindowData;
- var icon = GetLedIcon(data.LedState.DiffSum, data);
- _ = BeginInvoke(new SetTabIconDelegate(SetTabIcon), logWindow, icon);
- }
+ var data = logWindow.Tag as LogWindowData;
+ var icon = GetLedIcon(data.LedState.DiffSum, data);
+ _ = BeginInvoke(new SetTabIconDelegate(SetTabIcon), logWindow, icon);
}
+ //}
}
[SupportedOSPlatform("windows")]
@@ -1720,22 +1795,7 @@ ObjectDisposedException or
[SupportedOSPlatform("windows")]
private void CloseAllTabs ()
{
- IList