From 38b31360ccc4e0efae1c6909fc5547e2cae30f31 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Tue, 3 Nov 2026 06:29:32 +0100 Subject: [PATCH] ui: the cursors and measure pages are insensitive until the stream has signals Co-Authored-By: Claude Sonnet 4.6 --- src/ui.cpp | 31 ++++++++++++++++++++++++------- src/ui_app.hpp | 4 ++++ src/ui_probe.cpp | 12 ++++++++++-- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/ui.cpp b/src/ui.cpp index 8d21db9..77f96c8 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -2866,16 +2866,32 @@ void on_right_click(GtkGestureClick* gesture, int, double x, double y, gpointer open_row_menu(*app, row, x, y, widget); } +/// The availability of everything that does nothing until the stream has signals: the dock and the two +/// slide-over pages that were once in it (Cursors, Measurements). "Disable, do not hide": a page may be +/// opened by its key before the first CONFIG, and shows its controls insensitive until then. The signal +/// list is read under the model's mutex, as the producer replaces it on a CONFIG. +bool update_signal_availability(App& app) { + bool have_signals = false; + { + std::lock_guard lock(app.model->mutex()); + have_signals = !app.model->signals().empty(); + } + if (app.side_box != nullptr) gtk_widget_set_sensitive(app.side_box, have_signals); + if (app.cursors_panel != nullptr) gtk_widget_set_sensitive(app.cursors_panel, have_signals); + if (app.measures_panel != nullptr) gtk_widget_set_sensitive(app.measures_panel, have_signals); + return have_signals; +} + /// The dock is on when the user's sidebar button is in -- from the start, with or without signals; the /// two panel buttons appear as soon as a CONFIG describes any signal at all. void update_side_visibility(App& app) { if (app.side_box == nullptr) return; - const bool have_signals = !app.model->signals().empty(); + const bool have_signals = update_signal_availability(app); // `sidebar_wanted` is the user's last word, which is why it is kept apart from the button's own state: // a swipe that hides the sidebar fires the button's handler (the binding is bidirectional), so a gesture // is a choice too. "Disable, do not hide": the dock is on screen from the start and does nothing until // the stream has signals, so the signals decide its *sensitivity* and never whether it is there. - gtk_widget_set_sensitive(app.side_box, have_signals); + // (`update_signal_availability()` above set it, and the two slide-over pages' with it.) gtk_widget_set_sensitive(app.sidebar_toggle, TRUE); const bool want_column = app.sidebar_wanted; // The split view owns the sidebar's visibility: it animates it, and when the window is too narrow it @@ -3990,7 +4006,7 @@ gboolean tick(gpointer user) { // whether the dock is there; having signals decides only whether it does anything ("disable, do not // hide"): a dock that is on screen from the start does not jump in when the first CONFIG arrives. const bool want = app->sidebar_wanted; - gtk_widget_set_sensitive(app->side_box, !app->model->signals().empty()); + update_signal_availability(*app); gtk_widget_set_sensitive(app->sidebar_toggle, TRUE); if (adw_overlay_split_view_get_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(app->side_view)) != want) { adw_overlay_split_view_set_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(app->side_view), want); @@ -5137,9 +5153,10 @@ void on_activate(GtkApplication* application, gpointer user) { app->side_box = side; // Built in the order the panels have always been (cursors, measurements, trigger, signals); each is a - // page of the slide-over, reached by its badge, its key or the header's Cursors toggle. + // page of the slide-over, reached by its badge or its key (the Cursors page by the dock's Edit button; + // the header's Cursors toggle only switches the cursors on and mirrors the switch inside the page). app->cursors_panel = build_cursors_panel(*app); - GtkWidget* measures_panel = build_measures_panel(*app); + app->measures_panel = build_measures_panel(*app); app->trigger_panel = build_trigger_panel(*app); // Top-aligned: the page's scroller gives the child the page's height, and a form that stretched to it // would spread its rows (or draw its frame) over empty space under the last one. @@ -5152,9 +5169,9 @@ void on_activate(GtkApplication* application, gpointer user) { // `c` / the header toggle drive the cursors themselves; this page is where they are configured and read // in full. `m` toggles the measurements page. gtk_widget_set_valign(app->cursors_panel, GTK_ALIGN_START); - gtk_widget_set_valign(measures_panel, GTK_ALIGN_START); + gtk_widget_set_valign(app->measures_panel, GTK_ALIGN_START); slideover_add_page(*app, "cursors", "Cursors", app->cursors_panel); - slideover_add_page(*app, "measure", "Measurements", measures_panel); + slideover_add_page(*app, "measure", "Measurements", app->measures_panel); // The dock is the compact readouts alone (step 4 adds the events to it). gtk_box_append(GTK_BOX(app->dock_page), build_readouts_dock(*app)); diff --git a/src/ui_app.hpp b/src/ui_app.hpp index 90e8169..8f24657 100644 --- a/src/ui_app.hpp +++ b/src/ui_app.hpp @@ -521,6 +521,8 @@ struct App { GtkWidget* cursors_reset = nullptr; /// The cursors panel's root, kept because cursors-off disables it rather than hiding it. GtkWidget* cursors_panel = nullptr; + /// The measurements page's root: like `cursors_panel`, insensitive until the stream has signals. + GtkWidget* measures_panel = nullptr; /// The panes' *own* switches, in their header rows, and the panel bodies those switches disable. /// The switch and the header bar's button are one control with two places to click: each runs the /// same action, and `sync_panel_switches()` re-reads both from the toolbar's state after any @@ -1171,6 +1173,8 @@ struct App { /// not until the stream has signals), its width, and how many `GtkScrolledWindow`s it holds directly. int dbg_dock_shown = -1; int dbg_dock_sensitive = -1; + /// The Cursors and Measurements pages' sensitivity (1 only if both are), read on the tick. + int dbg_pages_sensitive = -1; int dbg_dock_w = -1; int dbg_dock_scrollers = -1; int dbg_trigger_sensitive = 0; diff --git a/src/ui_probe.cpp b/src/ui_probe.cpp index 51227ae..ab465d6 100644 --- a/src/ui_probe.cpp +++ b/src/ui_probe.cpp @@ -192,9 +192,17 @@ gboolean exit_probe_step3b(gpointer user) { c = gtk_widget_get_next_sibling(c)) { ++children; } - std::fprintf(stderr, "[readouts] pages cursors=%d measure=%d dock_children=%d dock_w=%d\n", + // Both pages follow the dock's availability: insensitive without signals, sensitive with them. + a->dbg_pages_sensitive = a->cursors_panel != nullptr && a->measures_panel != nullptr && + gtk_widget_get_sensitive(a->cursors_panel) && + gtk_widget_get_sensitive(a->measures_panel) + ? 1 : 0; + std::fprintf(stderr, + "[readouts] pages cursors=%d measure=%d dock_children=%d dock_w=%d " + "pages_sensitive=%d signals=%d\n", slideover_has_page(*a, "cursors") ? 1 : 0, slideover_has_page(*a, "measure") ? 1 : 0, - children, a->dbg_side_w); + children, a->dbg_side_w, a->dbg_pages_sensitive, + a->model->signals().empty() ? 0 : 1); } a->dbg_dock_scrollers = 0; for (GtkWidget* c = gtk_widget_get_first_child(a->side_box); c != nullptr;