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 serialisable SessionOptions, 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_session writes result.h5, options.toml and retrieval.pdf; result_mismatch says whether a saved result was fitted to the trace a replay produced).

  • croak.plotting.screen — what an on-screen view shows, as plain data: MapView for an image and LineView for curves, built by functions such as trace_map, temporal_view, filter_views and uncertainty_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

window

MainWindow: top bar, stage stack, drawer, menus, and the session actions (new, open, replay, retarget, save)

workflow

the route as pure rules: Stage, Progress, blocked_reason, next_stage, primary_action, secondary_action, remember_recent

home

Home: the entry tiles and Open a session (each with its drawing from assets/tiles), About croak and the recent sessions

about

About: the version, links, authors and copyright, from the package metadata

pages

default_pages (the page factory), DataPage (the entry’s loader, and a reopened session’s restore) and AnalysePage (the rail)

stage

PageBase, the hooks the window reads, and StagePage, a page that is a sheet

base

Stage, the interface the stage modules are written against (controls, set_plot_area, set_status, advance), and RunControls

sheet

the sheet’s parts: Form, Section, ReadyLine, Segments, AdviceBox, DerivedGrid and Sheet itself

stage_load, stage_simulated, stage_synthetic

the three loaders (Data)

stage_marginal_check, stage_preprocess, stage_retrieve, stage_dispersion, stage_uncertainty

the other stage pages

view_panel

ViewPanel, a titled plot of one screen view, drawn as the display settings show it, with its right-click menu, and the figure export of a plot or a page (page_figure, export_figure)

display

the display settings every plot follows (spectral axis, logarithmic range): current, set_display, the hub that tells the plots, and DisplayBar at the foot of the window

retrieval_views

the retrieval’s panels on screen (RetrievalPanels) and Analyse’s result pages (ResultPage, with Export and Pop out)

plots

the pyqtgraph views: Plot, MapPlot, LinePlot, the colour maps and the hover readers

theme

the palette, type scale, style sheet and the role helpers (label, button, frame, set_flag)

controls, widgets, readout, mirror_stack

form builders, slider/spin-box composites, Retrieve’s numbers, Dispersion’s mirror stack

help

per-stage help: the dialog and the tooltips, from one HELP list

state

WizardState: the parameters and pipeline outputs, with cascading invalidation

worker

the QThread workers for preprocessing, retrieval and uncertainty

settings

remembered preferences in a QSettings store

branding

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.