GUI and session integration#
The GUI, saved-session replay, and generated scripts should express the same workflow. Session parameters and the shared stage operations provide the link between interactive controls and headless calculations.
When adding a setting, trace it from the session parameter definition through the stage operation to the calculation it controls. Check loading and saving, GUI state, replay, and script generation where the setting is exposed. Keep Qt-specific behaviour at the interface boundary.
The session API documents the serialisable settings and execution interfaces. The command-line and scripting API covers headless use and generated scripts. User-facing instructions belong in saving, sessions, and reproducible workflows and the GUI guide; the look and layout rules are in GUI design.
Three layers#
The GUI is the thinnest of three layers, and only it imports Qt:
croak.session— the per-stage parameter dataclasses (croak.session.params), the serialisableSessionOptions, the mapping of each stage onto the library (croak.session.pipeline), headless replay (croak.session.engine), retargeting (croak.session.retarget) and a saved session’s files (croak.session.files:save_sessionwritesresult.h5,options.tomlandretrieval.pdf;result_mismatchsays whether a saved result was fitted to the trace a replay produced).croak.plotting.screen— what an on-screen view shows, as plain data:MapViewfor an image andLineViewfor curves, built by functions such astrace_map,temporal_view,filter_viewsanduncertainty_views. They use the same helpers as the matplotlib panels, so a curve on screen is the curve in an exported figure.croak.gui— Qt glue: widgets bound to the session’s parameters, worker threads, and pyqtgraph views that draw the screen data.
Importing croak or croak.gui does not import Qt; croak.gui.run_gui()
does. A change that needs numerics belongs in the first two layers, where it is
tested without a window.
The window#
Module |
Holds |
|---|---|
|
|
|
the route as pure rules: |
|
Home: the entry tiles and Open a session (each with its drawing from |
|
About: the version, links, authors and copyright, from the package metadata |
|
|
|
|
|
|
|
the sheet’s parts: |
|
the three loaders (Data) |
|
the other stage pages |
|
|
|
the display settings every plot follows (spectral axis, logarithmic range): |
|
the retrieval’s panels on screen ( |
|
the pyqtgraph views: |
|
the palette, type scale, style sheet and the role helpers ( |
|
form builders, slider/spin-box composites, Retrieve’s numbers, Dispersion’s mirror stack |
|
per-stage help: the dialog and the tooltips, from one |
|
|
|
the |
|
remembered preferences in a |
|
the application’s name and icon |
The route#
Which stage can open and what the primary action does depend only on
workflow.Progress, a frozen snapshot of what the session holds. The rules are
pure functions with doctests and their own tests (tests/gui/test_workflow.py);
MainWindow.refresh re-applies them to the top bar whenever the state or a page
says something changed. Put a new navigation rule there, not in a widget’s slot.
Pages#
A page tells the window what it can do through the PageBase hooks:
on_enter, can_advance, busy, page_key (which sub-page is shown, for Data
and Analyse), act(kind) for the primary action, overview (the retrieval
behind a saved session’s PDF) and show_help. It emits changed when the
answers change and message for a status-bar notice.
MainWindow builds a session’s four pages with a factory
(pages.default_pages), and rebuilds them for every new or reopened session,
because their controls bind to the parameter objects on the shared
WizardState. A test can pass its own factory to MainWindow.
Most stage pages subclass base.Stage, a StagePage that keeps the calls the
stage modules were first written against: sections go into self.controls
(from controls.group(title, folded=…, settings_key=…)), plots into
set_plot_area(plot, *strips), and messages onto the ready line with
set_status(text, "ok" | "warn" | "error") (Retrieve overrides it: a notice at
the foot of the window, and a failure in its advice box). A loader implements advance and
emits advanced("check") or advanced("retrieve"); DataPage calls it on
Continue. A page that runs a worker mixes in RunControls, whose busy turns
the primary action into Stop.
Help is declared once per page as HELP = [(section, [(label, text), ...])].
apply_help attaches each text as the tooltip of the row with that
(section title, row label), the form shows the focused control’s tooltip as
its one-line help, and HelpDialog lists them all. A row label spells a literal
ampersand && (Qt’s mnemonic escape); HELP keys are written as the user reads
them.
Screen views (GUI support)#
What a view shows is decided in croak.plotting.screen; how it is drawn is
decided in croak.gui.plots. The screen functions take the same inputs as the
matplotlib panels — for the retrieval, a RetrievalPlotData from
retrieval_plot_data() — and return arrays, labels, ranges
and curve roles. Two display choices differ from the figures: an image is
resampled onto evenly spaced axes at its finest spacing (an interactive image is
placed by its corners, a pcolormesh by every edge), and a logarithmic trace
shows only the positive part, leaving negative samples to the signed linear maps.
retrieval_views.PANELS maps each key of
RETRIEVAL_PANELS to its screen function, so the GUI’s
pages and the exported figures share the panel names and layouts
(RETRIEVAL_PAGES). A live retrieval calls
RetrievalPanels.set_data on the plots it already has, which replaces their data
without rebuilding the widgets. The figures of Analyse’s result pages, the
dispersion and uncertainty figures a saved session holds and its
retrieval.pdf go through the
matplotlib draw_* and plot_* functions. Every other figure — a plot’s
Export this plot as a figure…, or Export this page as a figure… on a page
without a figure of its own — is drawn from the screen views themselves by
croak.plotting.export, with the same role colours (screen.ROLE_STYLE), so a
new view can be exported without writing its figure.
The screen functions are Qt-free and tested in tests/test_plotting_screen.py;
a new view starts there.
Threads#
The workers in croak.gui.worker add only threading, progress signals and
cancellation to the stage operations in croak.session.pipeline, so the GUI and
the headless engine run identical computations. RetrievalWorker emits live
previews at most every PREVIEW_MIN_INTERVAL (0.25 s). Retrieve reports what
presenting each one cost, and the worker stretches the interval to
cost / PREVIEW_DUTY (a duty of 0.25), so presenting never takes more than about
a quarter of the wall-clock time on a slow machine.
Remembered preferences#
croak.gui.settings keeps the window geometry, each folding section’s state
(sections with a settings_key), the display settings (display/spectral_axis
and display/db_range, read and written by croak.gui.display) and the recent
sessions in a QSettings INI store, opening a short-lived store
for every read or write. It always names the organisation and application
explicitly, which is what lets the test suite redirect the store to a per-test
directory with QSettings.setPath (tests/conftest.py).
Testing the GUI#
The GUI tests use pytest-qt with qt_api = "pyside6" and run offscreen
(QT_QPA_PLATFORM=offscreen, set in tests/conftest.py and the pytest
environment), driving the real widgets through qtbot. They count towards the
100 % coverage gate like everything else.
Many stage tests were written for the old six-stage wizard.
tests/gui/gui_session.py keeps them working: its Driver builds a
MainWindow, maps the old stage numbers onto the window (1 the loader on Data,
2 Check, 3 Prepare, 4 Retrieve, 5 Dispersion and 6 Uncertainty on
Analyse) and hands back the stage pages, which keep their behaviour. goto
bypasses the route rules, as the old stage setter did, so a test can look at a
page before its input exists. Tests of navigation itself use the window and the
route rules directly (test_window.py, test_window_session.py,
test_workflow.py).
When testing saved window geometry, expose both the original and restored
window with qtbot.waitExposed before comparing their displayed sizes. Qt
clamps saved geometry to the available screen before applying layout minimums
on show. Windows font metrics can make those minimums wider than the offscreen
test display, so comparing a shown window to a hidden one tests different
layout states.