Skip to content

Add F11 Detach Mode - #749

Open
mfkeeq199458 wants to merge 5 commits into
LogExperts:Developmentfrom
mfkeeq199458:feature/f11-compact-mode
Open

mfkeeq199458 wants to merge 5 commits into
LogExperts:Developmentfrom
mfkeeq199458:feature/f11-compact-mode

Conversation

@mfkeeq199458

@mfkeeq199458 mfkeeq199458 commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

F11 detaches the active log window into its own top-level window and hides the main window; pressing F11 again returns to the normal layout. The View menu offers the same toggle as "Detach Mode" with F11 shown as a shortcut hint.

While detached

  • The float gets min/max buttons, a resizable border, a taskbar button and the main form's icon (the suite's shared log-window icon is disposed and replaced on every tail refresh). The dark title bar is re-applied after the style-driven handle recreation and the caption is repainted with a one-pixel nudge; the original chrome and window state are restored on exit, including for windows that were already floating.
  • Double-clicking the caption toggles maximize/restore instead of letting the suite dock the window back.
  • Ctrl+Tab / Ctrl+Shift+Tab move to the next / previous log window in the visible tab-strip order (TabController.GetNextWindow / GetPreviousWindow).
  • Opening or replacing a log file moves the mode to the newly shown window.
  • X on the float closes that log file. When the detached window closes, the mode ends and the main window comes back. This is intentional: re-docking a closing window would leave an empty tab or re-show a window that is shutting down.

Keyboard handling

A message filter serves the keys while the main form is hidden. It only reacts when the detached float is the active form, so modal dialogs and text boxes keep F11 and Ctrl+Tab for themselves; the auto-repeat burst of F11 is swallowed so the mode does not toggle on every repeat.

Restoring

Leaving the mode shows the main form first (assigning it as the owner of a still-hidden form would hide the float before its content is docked back), then releases the caption hook, re-docks the window into its original pane and tab index and restores the chrome and window state. The window state is applied last, so a window that was maximized before detach comes back maximized even when the suite's chrome has no maximize box.

If the suite refuses to float a window, the mode is not entered at all: the main window stays visible, and the same guard ends the mode cleanly when a switch or a newly opened window cannot be floated.

Naming

The menu item and its en/de/zh-CN resources are called "Detach Mode" everywhere (detachModeToolStripMenuItem with matching resource keys). Resources.Designer.cs is regenerated from Resources.resx instead of being edited by hand.

TabController also gains GetAllWindowsFromDockPanel and the tab-order-aware next/previous lookup. The detach mode controller, message filter and caption hook live in DetachModeService. Tests: 19 controller tests (8 of them drive the message filter) plus 4 new TabController tests.

F11 now detaches the active log window into its own top-level window and
hides the main window; F11 or ESC returns to the normal mode. The float
window gets min/max buttons, a resizable border, a taskbar button, its
own stable icon and a maximizable double-click-title behaviour; dark
title bars are re-applied after the style-driven handle recreation.

TabController gains GetPlacedWindows/GetNextWindow/GetPreviousWindow and
a guarded CloseAllExcept; the View menu offers the toggle as "Log Only
View" (en/de/zh-CN) with F11 as a display-only shortcut hint. DetachMode
service, message filter and a NativeWindow caption hook live in
DetachModeService; 17 controller tests plus 6 filter tests cover it.

@Hirogen Hirogen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mfkeeq199458 — a focus mode for a single log is a nice idea, and it's good that F11 doesn't clash with anything and that closing the app while detached leaves the saved main-window layout intact. Before this can go in, there are a few behavioural issues, plus some housekeeping.

ESC handling (blocking)
The filter catches ESC for the whole app, and the text-input guard doesn't work as intended:

  • Form.ActiveForm?.ActiveControl is TextBoxBase (DetachMessageFilter.cs:31) stops at the nearest nested container, so it sees the LogWindow, not the filter/search box or the in-grid edit control. ESC in those inputs therefore leaves detach mode instead of doing its normal job. A check like GetFocus() → Control.FromHandle(...) is TextBoxBase (or walking the ContainerControl chain) would find the real focused control.
  • ESC on the grid normally cancels a search and clears highlighting (LogWindow.cs:6989). The filter's own comment says that should keep working, but while detached it exits the mode instead.
  • If a modal dialog is open (e.g. from the float window), ESC is swallowed: the dialog stays open and the main window reappears behind it. Please only react when the active form is the detached float window.

The ESC tests stub _isTextInputFocused, so they can't catch this. It needs a manual check, or a test that exercises the real focus lookup.

Restoring state on exit

  • Exit docks the window again with Show(_dockPanel, DockState.Document) (DetachModeController.cs:107), so the original tab position and any split pane are lost. Please restore the original pane and index.
  • If the window was already floating before F11, Exit leaves the patched chrome on it (no owner, taskbar button, sizable border). ReleaseFloatWindow only removes the hook.
  • _detachModeController is never disposed in LogTabWindow.Dispose, and the message filter is only removed when Exit() runs. Please make the controller IDisposable and dispose it next to _tabController.

Scope

  • "Close other tabs" now goes through CloseAllExcept and asks for confirmation, where before it closed directly. That's a user-visible change detach mode doesn't need. Please split it into its own PR (or drop it).
  • Ctrl+Tab cycling, "opening a file replaces the detached window" and "X on the float detaches the next hidden window" are reasonable, but they aren't in the description. Please document them. I'd also argue the X button should simply return to the main window; that's what most users will expect.

Housekeeping

  • The PR adds 10 new analyzer warnings (CA1060, CA5392, CA1051, CA1303, CA1001, CA1031). Please get the build back to the base warning count.
  • LogExpert.UI already uses Vanara: please use Vanara.PInvoke.User32 for GetWindowLongPtr / CallWindowProc, and put DwmSetWindowAttribute in NativeMethods (or use Vanara.DwmApi) instead of new DllImports.
  • WM_KEYDOWN and the autorepeat bit are defined in both the filter and LogTabWindow. The restore-dock-state block is repeated three times in the controller. Please extract both.
  • ToggleDetachMode() only calls Enter(). Please rename it, or make it actually toggle.
  • Comments: we keep them minimal in this repo. DetachModeController is about 20% comments, many of them multi-paragraph. Please trim them to the non-obvious "why" lines.
  • Naming: the code says "detach mode", the UI says "Log Only View". Please pick one concept name and use it throughout.
  • Test seams: prefer constructor injection over mutable internal fields.
  • The new blocks in LogTabWindow.designer.cs look hand-edited (// without the trailing space). Please let the designer generate them.

Description

  • The title says "Replace compact mode", but there's no compact mode on Development. Please retitle to something like "Add F11 Log Only View".
  • The test count is off: the code has 9 controller + 8 filter + 6 TabController tests.

Thanks — the core idea is solid. ESC handling and restoring the original state on exit are the two I need fixed; the rest is cleanup.

mfkeeq199458 and others added 2 commits October 24, 2026 09:55
Adds F11 detach mode: F11 hides the main window and keeps the active log
window floating as a stand-alone viewer. F11 brings the main window back and
restores the window to its original pane and tab index.

Behaviour:
- F11 enters and leaves the mode. ESC is not part of detach mode and keeps its
  normal behaviour (cancelling a search on the grid, editing in text boxes,
  dismissing dialogs).
- Ctrl+Tab / Ctrl+Shift+Tab cycle through log windows while detached.
- Opening a file while detached replaces the detached window.
- Closing the detached float (X) ends the mode and returns to the main window.
- A window that was already floating keeps its original chrome on exit; a
  docked window is restored to its original pane and tab position.
- The keyboard filter only reacts while the detached float is the active form,
  so modal dialogs keep F11 and Ctrl+Tab for themselves.

Review follow-up:
- Removed the ESC exit path and the old text-input guard; the mode is left
  with F11 only.
- Restore the original dock pane and content index instead of docking to
  DockState.Document.
- Restore the patched chrome for windows that were already floating on entry.
- Make DetachModeController IDisposable and dispose it next to TabController;
  the message filter is removed in Dispose as well.
- Revert the "Close other tabs" change: it was not part of this feature and is
  now identical to Development.
- Clear the new analyzer warnings: remove the unused GetFocus/SetFocus
  P/Invokes, add DefaultDllImportSearchPaths for the DWM call, and suppress
  CA1303, CA1031 and CA1001 with justifications.
- Share the WM_KEYDOWN and autorepeat constants through NativeMethods and keep
  a single restore-dock-state block.
- Rename ToggleDetachMode to EnterDetachMode, trim comments to the non-obvious
  "why", and use the concept name "Detach Mode" in code, designer text and all
  three resource files.
- Use constructor injection for the keyboard filter test seams.

Tests: 9 controller, 8 filter and 3 TabController tests. Full
LogExpert.UI.Tests suite: 391 passed, 0 failed.

Note: the previous description said "Replace compact mode", but Development has
no compact mode; this change adds F11 Detach Mode. The earlier test count and
the Ctrl+Tab, file-replacement and X-button behaviour were missing from the
description and are documented above.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com>
@mfkeeq199458
mfkeeq199458 requested a review from Hirogen October 24, 2026 06:23
Two detach mode regressions:

- Closing the detached float through its X left an empty, unclosable tab in the main
  window. OnWindowDisposed fired while LogWindow.IsDisposed was still false, so Exit()
  re-showed the closing window into its original pane. The close path no longer calls
  Exit(): it releases the hook without touching the float chrome, clears the captured
  placement and shows the main window on the message loop after the suite finished
  closing, which keeps the document panel empty.
- In dark mode a detached float kept a light title bar until it was moved or resized.
  DWM only repaints the caption on a real move/resize, so the immersive dark flag is
  applied after the float is shown and a one pixel height nudge forces the repaint. A
  maximized float only gets the flag because the nudge would overwrite its restore
  bounds.

ApplyDarkTitleBar replaces SetImmersiveDarkTitleBar, Exit keeps restoring chrome for the
F11 path, and ClearOriginalPlacement is shared by both exit paths.

Tests: 18 DetachModeController tests pass, including a regression test that closing the
float leaves no document in the panel; LogExpert.UI and LogExpert build without errors.

@Hirogen Hirogen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mfkeeq199458 — both blocking items from last round are addressed. ESC is no longer touched, F11/Ctrl+Tab only react while the float is the active form (modal dialogs and text boxes are safe now), the original pane and index are restored, and the controller is IDisposable and disposed next to _tabController. Vanara, the NativeMethods move, the EnterDetachMode rename, the single RestoreWindow and the constructor-injected seams all look good too. "Close other tabs" now behaves exactly as on Development.

Behaviour (please fix)

  • Dark title bar on exit: RestoreChrome (DetachModeController.cs:~247) sets FormBorderStyle, which recreates the handle, and then only re-applies the dark flag, without the one-pixel nudge from 204c456. A window that was already floating before F11 will probably get a light caption again on exit. Also, a float that was maximized while detached stays Maximized after its MaximizeBox is removed.
  • Release order: SwitchWindow and OnWindowAdded call RestoreWindow before Detach, and Detach is what calls ReleaseFloatWindow. So the hook gets released from a float that may already be disposed. Exit does it in the right order: release first, then restore.
  • Ctrl+Tab order: GetNextWindow/GetPreviousWindow walk the _windows dictionary, not the tab strip, but the doc comment says "tab order". Please use the visible tab order.
  • X button: X on the float now closes the log file. I said "return to the main window", which could also mean re-docking it. Was closing intentional? If so, please say so in the description.

Housekeeping

  • Resources.Designer.cs:2664-2672 looks hand-edited: logOnlyView sits before lockInstance, and the blank lines are missing the generated indent. Please regenerate it from the resx.
  • LogTabWindow.cs:2460-2461: please revert the whitespace-only re-indent.
  • One name: the UI says "Detach Mode", but the menu item and resx keys are logOnlyView…. Either name is fine, as long as it's the same everywhere. Please also use it in the PR title.
  • DetachFloatWindowHook: after AssignHandle, base.WndProc(ref m) already forwards to the previous window procedure, so _originalProc, GetWindowProc and the CallWindowProc call can go.
  • _originalDockState/_originalPane/_originalContentIndex are reset in ClearOriginalPlacement and again by hand at :172-173, which skips the index. Please use one path, e.g. a small record like ChromeSnapshot.
  • DetachModeControllerTests.cs:27: please implement IDisposable like the neighbouring fixtures instead of suppressing CA1001.
  • Comments: still a bit above our minimum. Examples: the XML doc on a private field (LogTabWindow.cs:84-87), comments that restate the code (:855, :1497, :1830), the <remarks> added in TabController, and the "Arrange —" essays in the tests.

Tests

  • Exit_RestoresTheWindowAndTheMainForm only checks DockState.Document. Please also assert the original pane and the content index, because that was the fix.
  • Exit_KeepsAnAlreadyFloatingWindowFloating should assert that Owner, ShowInTaskbar and the border style are restored.

Description
The title and description still describe the first version: "Replace compact mode", "F11 or ESC", GetPlacedWindows, CloseAllExcept, and 17+6 tests (it's 18 controller + 3 TabController now). Please update both to match e78c47f/204c4561, including Ctrl+Tab, file replacement and the X behaviour.

Getting close. The title bar on exit and the release order are the two I'd like fixed. The rest is polish. Thanks!

@mfkeeq199458 mfkeeq199458 changed the title Replace compact mode with F11 detach mode (Log Only View) Add F11 Detach Mode Oct 28, 2026
- Restore the window state and dark title bar when leaving detach mode; show
  the main form before the float gets its owner back and re-dock before
  walking the tab order on Ctrl+Tab
- Keep the main window visible when the suite cannot float a window
- Simplify the caption hook to base.WndProc and drop the unused import
- Regenerate Resources.Designer.cs from Resources.resx
- Rename logOnlyView members and resources to detachMode
@mfkeeq199458
mfkeeq199458 requested a review from Hirogen October 28, 2026 05:26

@Hirogen Hirogen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mfkeeq199458 — round 3 fixes both behaviour items. The hook is released before restore in SwitchWindow/OnWindowAdded, and RestoreChrome re-applies the dark flag and then nudges, with WindowState set last. The snapshot records, the slimmer DetachFloatWindowHook, the stronger Exit tests, the reverted re-indent, the documented X behaviour and the consistent "Detach Mode" naming all look good. Build adds no warnings compared to Development (the 10 from round 1 are gone), and the 51 Detach Mode/TabController tests pass.

Before merge

  1. Resources.Designer.cs locale. The file was regenerated, but by a Chinese-language VS 17: the header and ~760 doc comments are now in Chinese, and GeneratedCodeAttribute went from 18.0.0.0 to 17.0.0.0. Please regenerate it with an English VS UI (Tools → Options → Environment → International Settings), ideally VS 2026/18. Heads-up: #746 has the same issue, and both PRs regenerate this file, so the second one to merge will need one more Run Custom Tool after rebasing.

  2. Exceptions halfway through a switch. If Detach throws inside SwitchWindow or OnWindowAdded, the main form stays hidden while _detachedWindow is still set, and the user can end up with no visible window. The same applies to Enter if something throws after PatchFloatWindow. A try/catch (or finally) that falls back to LeaveDetachedState() and shows the main form would make this safe.

Small things

  • Comments are close but not quite there. Please remove the XML docs on the private IsAutoRepeat/LeaveDetachedState, the 3-line comment in ProcessCmdKey that repeats its doc, the extra sentence on GetNextWindow, the XML docs on the NativeMethods constants and the paragraph-long summary on the test class. In the tests, NativeMethods.WM_KEYDOWN is better than 0x0100, // WM_KEYDOWN.
  • Ctrl+Tab in the normal main window now also follows tab-strip order (SwitchToNextWindow → GetNextWindow). I think that's an improvement, but please mention it in the description, since it changes behaviour outside detach mode.
  • Description: GetAllWindowsFromDockPanel already exists on Development. And the icon that gets disposed and replaced comes from LogExpert's own SetTabIcon, not the suite.
  • Dispose → Exit() → _mainForm.Show() runs while the form is being disposed. It might be cleaner to skip the show path when disposing.

Optional

  • The auto-repeat check exists both in LogTabWindow.IsAutoRepeat and inline in DetachMessageFilter, and the "release → restore → detach-or-leave" sequence is repeated in SwitchWindow/OnWindowAdded. Small helpers would remove both duplicates.
  • DwmSetWindowAttribute is now the only DllImport in src. Vanara.PInvoke.DwmApi would remove it, together with the local WM_NCLBUTTONDBLCLK/VK_* ints (Vanara has enums for those).
  • Nullable annotations: GetNextWindow(LogWindow current = null), _captionHook and DetachedFloat should be nullable.

Please also do a quick manual check that a window which was already floating before F11 keeps its dark title bar after leaving detach mode. There's no test for that path.

With 1 and 2 done, I'm happy to merge. Thanks — this turned out really well!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants