The layer asked the modal's focusWidget() what to focus, but hosting a
modal reparents it into a scroll area and a reparent clears the focus its
constructor set, so the answer was always nothing and the dialog itself
took the keyboard. Modals now name their initial focus widget outright,
which survives the hosting.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
One tooltip mechanism for the whole UI: a Tooltip popup plus a
TooltipTrigger event filter, replacing every setToolTip call. Qt's own
tooltip cannot do what REQ-UI-TOOLTIP-TRIGGER and REQ-UI-TOOLTIP-DISMISS
ask for -- it times out, hides on the first mouse move, cannot be hovered
and cannot be brought up by a click.
A tooltip now appears the moment an element with no click action of its
own is clicked, and it is placed with its top-left corner on the pointer,
so the pointer can move onto the tooltip and hold it open. The pointer is
polled while a tooltip is up rather than tracked through enter and leave
events, whose order depends on which widget the pointer crosses first.
The item chip's children become transparent to the mouse. They were
taking the chip's enter and leave events, so its tooltip only ever
appeared when the pointer rested on the chip's padding.
The modal header's close button loses its "Close" tooltip: a close button
in a header says that by being one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
Every dialog was an OS window with a dim widget behind it in the game
window. That is why a click beside one could not reach it: Qt drops mouse
events for a window a modal blocks, so the dim -- a child of the blocked
window -- never saw the press.
ModalLayer is that dim grown up: still a child of the main window covering
its rect, but it now hosts the open modal, places it, and runs its event
loop, so it is the widget the clicks beside a modal land on. It keeps a
stack, paints one dim however many modals are open, and a hold keeps it up
while one modal hands over to the next. ModalDimOverlay and ModalDimScope
are gone; ModalLayerHold replaces the scope at the sites that still open a
system message box.
ModalDialog is what a dialog inherits in place of the window it lost: the
panel background, the drawn header with its optional close button, and the
dismissal gestures. isDismissible() governs Q and, next, the click outside
-- one predicate because both gestures reach the same dialogs. Its default
refuses, so a dialog opts in. requestDismiss() is what a gesture asks for,
and ShipLayoutDialog overrides it with the one-step ladder its Q handler
used to spell out, which is now reached by both. DialogDismiss.h folded into
the base.
The four dialogs the player meets keep their contents unchanged and lose
their title bars: the recipe/schematic selection, blueprint selection, ship
layout, and schematic choice dialogs. Placement moved with them --
placeOnSelectionPanel became getSelectionPanelAnchor, and the layer centers
on that rectangle in window coordinates, with no global mapping and no
frame height to guess at.
Two things followed from dropping OS modality: the focus guard that hands
focus back to the game world now also asks the layer whether a modal is
open, and closeEvent refuses to close the window while one is, since its
nested loop runs over widgets the window owns.
The escape menu, the game-over and win screens, and the two name dialogs are
still system dialogs; they are dimmed by a layer hold until they are
converted next.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x