session: remember extra windows, and reopen them from the menu

A scope or detached window was never written to the session, and a detached figure vanished from
the layout at the next start -- the figure had been removed from the main rows, and nothing kept
the window that held it.

Each window is now a `[[window]]` block in the session -- `name`, `detached`, and its own
`[[window.plot]]` rows with the same keys the main `[[plot]]` rows carry (signals, row weight,
cell width). `collect_layout()` writes the open windows' present arrangement plus the ones already
closed; loading puts **all** of them in `App::closed_windows` and never opens one: a window is a
view the user closed, and a launch that tiled windows onto the desktop would take that decision
for them (a tiling WM would tile them straight away). The overflow menu gains **Reopen closed
windows (N)**, which opens every remembered window through `open_scope_window_from_state()` -- the
one window constructor, then its rows, weights and signals dealt into its row set.

Closing a window records it, by name (a close/reopen cycle replaces its entry rather than
multiplying them), and schedules the save; **attaching** records nothing, because that window's
figure is on the main window again. A detached figure therefore survives a quit through its
window's entry.

Probe: `[scope-probe] loaded closed=N scopes=0` (remembered, none opened) and `reopen
scopes=..->.. remembered=.. left=0` (close, remember, the menu item's own click reopens).
Docs: README, the Mallard layout page, the probe readings and the session invariant.
This commit is contained in:
Martino Ferrari committed 2026-11-06 12:06:21 +01:00
1 parent 0c9333858c
commit a29f38eaa9
9 files changed
+326 -12

No files matched your search

+20
View File
@@ -225,6 +225,10 @@ struct App {
/// carries the attach control and can be put back. A copied or empty window is a plain second
/// view, and has no attach control.
bool detached = false;
/// False while attaching: the window is closing *because* its figure went back to the main
/// window, so there is nothing to remember (remembering it would offer to reopen a window
/// whose figure is on screen again).
bool remember_on_close = true;
/// The overlay wrapping the GL widget: the corner controls (attach, the layout toggle, the
/// layout editor's own buttons) all live on it. Its motion controller keeps the pointer state
/// they are revealed by.
@@ -252,6 +256,12 @@ struct App {
std::unique_ptr<RowSet> rows;
};
std::vector<std::unique_ptr<ScopeWindow>> scopes;
/// The **closed** extra windows, as the session remembers them: a window's arrangement is kept
/// when it is closed (and across a restart), and the *Reopen closed windows* entry opens them --
/// a launch never does, because a window is a view the user closed on purpose.
std::vector<LayoutState::Window> closed_windows;
/// The menu entry that reopens them, so its label can carry the count.
GtkWidget* reopen_scopes_item = nullptr;
/// Every empty scope window is numbered from this, so two of them never share a name.
int scope_seq = 0;
@@ -2090,6 +2100,16 @@ bool assign_signal_to_scope(App& app, App::ScopeWindow& scope, std::size_t signa
/// the application through `run_app_action()`.
gboolean on_scope_key(GtkEventControllerKey* controller, guint keyval, guint keycode,
GdkModifierType state, gpointer user);
/// The session record of a window: its name, whether it was detached, and its plot rows.
LayoutState::Window scope_window_state(const App::ScopeWindow& scope);
/// Remembers a window as closed (`closed_windows`, replaced by name) -- what its destroy handler
/// does, and what keeps a closed window reopenable.
void remember_closed_window(App& app, const App::ScopeWindow& scope);
/// Opens a window from a remembered state: an empty scope window, then its rows, weights and
/// signals. Returns the opened window (nullptr when it could not be made).
App::ScopeWindow* open_scope_window_from_state(App& app, const LayoutState::Window& state);
/// Refreshes the *Reopen closed windows* entry: its count and whether it is sensitive.
void sync_reopen_scopes_item(App& app);
/// Turns a scope window's own layout editor on or off, building or taking down its buttons.
void set_scope_layout_editing(App& app, App::ScopeWindow& scope, bool on);
/// The scope window's own right-click menu: the same row menu's actions, aimed at **that