feat(editor): 2D Editor pane, status instructions, generated shortcut map (#213)#261
Merged
Conversation
… map (#213) Closes out P1 on top of the merged p1-s1/s2/s3. The vertical profile moves into a new tabbed "2D Editor" pane, ready for the cross-section and Signal Phase editors (GW-4 step 4). The host is thin on purpose: a page keeps its own selection subscription and its own commands, so ProfilePanel moved without a line changing and test_profile_panel.cpp passes unmodified. The host only raises a relevant tab, and never yanks a tab the user is already using. The dock is dock.editor2d, NOT dock.profile: restoreState matches saved geometry by objectName, so reusing the name would let a returning user's stale entry capture a differently-shaped pane. An unknown name is ignored instead, and the default placement wins. Every binding now comes from one table (app/shortcut_registry.cpp): Actions binds from it via setShortcuts, and docs/user-guide/shortcuts.md is rendered from it, with a gtest gate that fails when the two drift — before this, key letters were hand-copied into tooltips and nothing noticed when they went stale. The page cannot be rendered from QKeySequence::StandardKey: keyBindings() returns a DIFFERENT list per platform (Redo is three sequences on Windows, one on macOS) and some are empty (Quit has none on macOS, where the platform menu owns it), so a committed page would pass the gate only on the machine that generated it. Standard rows therefore carry the one spelling the docs commit to, and the app still binds natively. The status bar gains a persistent instruction line fed from a new Tool::instruction(). Rendering the window showed why that needed more than a new label: every tool ALSO emitted the same guidance transiently from activate(), so the bar carried two wordings of one sentence, the transient one clipped. instruction() is now the single source — it feeds both the status line and the viewport corner hint (issue #103) — and the transient channel keeps only results and state-dependent guidance. The label is permanent, so showMessage can neither hide nor overlap it. Refs #213
4 tasks
Windows CI caught this; clang did not. `Qt::KeypadModifier | Qt::Key_8` yields a QKeyCombination, so storing it in an `int` field went through QKeyCombination::operator int() — deprecated since Qt 6.0. MSVC reports the deprecation (C4996) and CI builds with /WX, so the editor lib failed to compile on Windows while macOS and Linux were green. The field type was simply wrong, so fix the type rather than silence the warning. A plain Qt::Key still converts implicitly, so unmodified rows read as before; `alternate` now spells "none" as Qt::Key_unknown instead of 0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Final sprint of Pillar P1 — Interaction & Navigation (epic #250), on top of the merged p1-s1/s2/s3. Runs last so the shortcut registry captures the final bindings.
Closes #213
2D Editor pane
The vertical profile becomes the first page of a tabbed 2D Editor pane; the cross-section and Signal Phase Editor (GW-4 step 4, p4-s5) plug in behind the same
Editor2DPageinterface.The host is thin on purpose: a page keeps its own selection subscription and its own commands. That's why
ProfilePanelmoved without a line changing andtest_profile_panel.cpppasses unmodified — the strongest evidence the seam is in the right place. The host only raises a relevant tab, and won't yank a tab you're already using.The dock is
dock.editor2d, notdock.profile.restoreStatematches saved geometry to docks by objectName, so reusing the name would let a returning user's stale entry — placed and sized for the old panel — capture a differently-shaped pane. An unknown name is ignored, so the default placement wins.StaleProfileLayoutDoesNotCaptureTheRenamedDockpins the Qt behaviour that decision rests on (MainWindow needs GL and can't be built offscreen, so the test uses a bare QMainWindow).One table for every binding
app/shortcut_registry.cppis now the single source:Actionsbinds from it viasetShortcuts, anddocs/user-guide/shortcuts.mdis rendered from it, with a gtest gate that fails when they drift. Before this, key letters were hand-copied into tooltips and nothing noticed when they went stale.I verified the gate can fail, not just pass: changing Split's key to
Gmakes it fail with the regenerated table in the message. A gate that can't fail isn't one.The page cannot be rendered from
StandardKeyGenerating it exposed a real trap:
QKeySequence::keyBindings()returns a different list per platform — Redo is three sequences on Windows, one on macOS — and some are empty (Quithas none on macOS; the platform menu owns ⌘Q, so the page documented a blank). A committed page generated from it would pass the gate on the machine that made it and fail CI on the other two. Sostandardrows carry the one spelling the docs commit to, with platform variants in the Notes column, while the app still binds natively per OS. (Also:fromLatin1mangled the UTF-8 ellipsis intoSave as⦗ nowfromUtf8.)Status line — the render caught a real defect
Adding a
Tool::instruction()and a label wasn't enough. Rendering the window showed why: every tool also emitted the same guidance transiently fromactivate(), so the status bar carried two wordings of one sentence — first overlapping, then adjacent with the transient one clipped.instruction()is now the single source per tool. It feeds both the persistent status line and the viewport corner hint (issue #103 — during an interaction your eyes are on the viewport, and removing the activation message would otherwise have regressed that). The transient channel keeps only results and state-dependent guidance ("Merged", refusals). The label is permanent, soshowMessagecan neither hide nor paint over it.SelectTool::instruction()branches onmove_mode_, as itsactivate()message did — one tool, two modes.Status bar now, on the sample scene:
Tests
test_editor2d_host.cpp(7): tab registration/order, raise-the-relevant-page, keep-a-relevant-tab, no arbitrary pick, the ProfilePanel migration keeping its own subscription, selection-driven raise, the stale-layout guard.test_shortcut_registry.cpp(6): every Id has exactly one row, primary-first with the alternate kept,NoDuplicateActiveBindings,V-is-frame-cursor/Select-is-Q, Actions bind what the table documents, and the page gate (RM_DOCS_DIRcompile define).test_profile_panel.cppunmodified and green.Verification
ci-macos(-Werror); whole-treeclang-format --dry-run --Werrorclean.activate()emissions broke nothing — the tool tests spy afteractivate(), so they cover the state-dependent messages.--screenshot-ui) checked after each status-bar change; that's what surfaced the overlap.Acceptance
GW-2 step 7 (profile edits in the 2D Editor pane); the host API is sufficient for GW-4 step 4; GW-1's "documented shortcut map" pass criterion holds, and is now enforced.
Close-out
The GW-1 self-check follows as a comment on #250. It is not a GW-1 pass — every chord is a gesture, so steps 2–6, 10 and 14 need a hand on a real mouse. The results table stays empty until Armando runs it.