From d8705a369f54a2bb82ee4b1de9a364c8b2c5c712 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Wed, 14 Oct 2026 14:50:04 +0200 Subject: [PATCH] ui: Arm is disabled with trigger mode off, and the redundant Go-live button is gone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **`Arm` is now disabled while trigger mode is off**, and labelled `Arm` again: it is the one control in that pane that is *runtime* rather than setting, so with no engine running there is nothing to arm. It is also the only thing in the body that stays disabled — the settings themselves stay editable, which is the point of the previous commit. `arm sensitive with the trigger off=0` is the reading. **`Go live (trigger off)` is deleted.** Its handler was `trigger_toggle → FALSE`, i.e. exactly the toolbar's trigger switch and the pane's own switch, which is what it was for: "the way out of trigger mode, from inside the panel that froze the view: it is the toolbar's trigger switch, so there is exactly one piece of state." With that switch now in the pane's own heading, the button was the same control drawn a third time. The status line that told users about it says "or switch the trigger off to stream" instead. The events pane's own `events_live` button is a different button on a different path — the file view restore — and is untouched. **And one silent regression this work uncovered.** The `trigger panel:` frame reading had been walking the *wrapper* box the app puts around the `.ui`'s panel (heading row + locked summary + panel) ever since the heading moved out of the file in the lock commit, so it reported `boxes=2 … search_inside=0/4` against a panel that was entirely correct. It reads `trigger_body` — the file's own panel — again, and now says so in its comment, because a reading that can silently start describing the wrong widget is the failure it exists to catch. Verified: `trigger panel: boxes=15 controls_inside=15/15 search_widgets=4/4 search_inside=4/4` (the documented shape, restored); `arm sensitive with the trigger off=0`; `trigger panel sensitive …=1`; `panes:`/`panel switches:`/`pane edits:` unchanged; `make test-ui` 14 files; zero `Gtk-CRITICAL`. --- docs/claude/probe-readings.md | 9 ++++++- src/ui.cpp | 46 +++++++++++++++++++---------------- src/ui/trigger-panel.ui | 6 ----- src/ui_app.hpp | 4 ++- src/ui_probe.cpp | 1 + src/ui_trigger.cpp | 10 +++----- 6 files changed, 40 insertions(+), 36 deletions(-) diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md index d23280f..cbd7f43 100644 --- a/docs/claude/probe-readings.md +++ b/docs/claude/probe-readings.md @@ -1169,6 +1169,9 @@ its pane. a trigger is configured before it is switched on — so this reads `1` always, and a `0` means the settings have been made unreachable again. The *cursors'* body is disabled with the cursors, and that is the pane whose readout has nothing to show. +- **`arm sensitive with the trigger off=0`** is the one exception inside that body: `Arm` is a *runtime* + control, so with trigger mode off there is no engine to arm and it is disabled (and labelled `Arm` + again). `1` here is an Arm button that does nothing when clicked. - **`summary`** is the locked trigger pane's whole content, as `key: value` pairs separated by ` |`. The pane draws it as a small definition list — `signal`, `condition`, `level`, `mode` on four lines, and then only the figures that are *not* at their defaults (`noise band`, `HF reject`, `pre-trigger`, @@ -1348,7 +1351,11 @@ through one row type, for the reason in `invariants-gtk-and-chrome.md`. The form [udpscope] trigger panel: boxes=15 controls_inside=15/15 search_widgets=4/4 search_inside=4/4 ``` -The trigger panel's frame, read from the window: `boxes` is the row boxes the `.ui` holds, `controls_inside` +The trigger panel's frame, read from the window: **the `.ui`'s own panel** (`trigger_body`), not the box +this app wraps it in — the heading row and the locked summary are the code's arrangement, and reading the +*wrapper* made this line report `boxes=2 … search_inside=0/4` the moment the heading moved out of the file. +That is the failure this line is for, so it is worth knowing it can happen to itself. `boxes` is the row +boxes the `.ui` holds, `controls_inside` counts the fifteen controls the code built and then appended into those rows -- the last two being the noise band and the HF reject, whose rows arrived with the noise-rejection work, `search_widgets` is the four search buttons having resolved by id, and `search_inside` is those four being inside the block the expander diff --git a/src/ui.cpp b/src/ui.cpp index de56837..c941889 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -4082,7 +4082,7 @@ gboolean tick(gpointer user) { if (app->capturing) { mode = "trigger: CAPTURING (post-trigger window filling)"; } else if (app->event_held) { - mode = "trigger: EVENT HELD (Arm to re-arm, or 'Go live' for streaming)"; + mode = "trigger: EVENT HELD (Arm to re-arm, or switch the trigger off to stream)"; } else if (armed && have_trigger_value) { static char waiting[192]; std::snprintf(waiting, sizeof(waiting), @@ -4857,6 +4857,8 @@ void on_activate(GtkApplication* application, gpointer user) { // switch that turns it back on. The trigger's body is deliberately *not* disabled: a trigger is // configured before it is switched on, so its settings have to stay editable while it is off. gtk_widget_set_sensitive(app->cursors_body, FALSE); + // The trigger's Arm button is the one control in its pane that needs trigger mode running. + if (app->arm_button != nullptr) gtk_widget_set_sensitive(app->arm_button, FALSE); gtk_widget_set_sensitive(app->cursors_reset, FALSE); if (app->side_stack != nullptr) { @@ -5968,10 +5970,10 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit app.dbg_badge_frames[3]); std::printf("[udpscope] trigger panel sensitive before the probe toggled it: %d; " "signal list items=%d selected=%d; column visible after sidebar toggle off=%d; " - "pane switches live while their panes are off=%d\n", + "pane switches live while their panes are off=%d; arm sensitive with the trigger off=%d\n", app.dbg_trigger_sensitive_default, app.dbg_trigger_items, app.dbg_trigger_selected, app.dbg_side_column_after_hide, - app.dbg_panel_switch_live); + app.dbg_panel_switch_live, app.dbg_arm_sensitive); { // The panel's *frame* read back: the row boxes the `.ui` holds, and that every control the code built // ended up inside the panel rather than in a row that is not there. A control is created in code and @@ -5985,8 +5987,10 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit } return 0; }; + // The *file's* panel (`trigger_body`), not the wrapper this app puts it in: the heading row and the + // locked summary are the code's arrangement, and what these counts read is what the `.ui` holds. int boxes = 0; - for (GtkWidget* c = gtk_widget_get_first_child(app.trigger_panel); c != nullptr; + for (GtkWidget* c = gtk_widget_get_first_child(app.trigger_body); c != nullptr; c = gtk_widget_get_next_sibling(c)) { if (GTK_IS_BOX(c)) ++boxes; } @@ -5998,8 +6002,8 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit // a throwaway program, all three ways of setting it). So the walk the search widgets are read with has // to start at the expander's child and not at the panel -- walking to the panel reads 0/4 on a panel // that is entirely correct, which is what this line did first. - GtkWidget* search_root = app.trigger_panel; - for (GtkWidget* c = gtk_widget_get_first_child(app.trigger_panel); c != nullptr; + GtkWidget* search_root = app.trigger_body; + for (GtkWidget* c = gtk_widget_get_first_child(app.trigger_body); c != nullptr; c = gtk_widget_get_next_sibling(c)) { if (GTK_IS_EXPANDER(c)) { if (GtkWidget* inner = gtk_expander_get_child(GTK_EXPANDER(c))) search_root = inner; @@ -6013,21 +6017,21 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit "[udpscope] trigger panel: boxes=%d controls_inside=%d/15 search_widgets=%d/4 " "search_inside=%d/4\n", boxes, - inside(app.trigger_signal_drop, app.trigger_panel) + - inside(app.trigger_type_drop, app.trigger_panel) + - inside(app.trigger_edge_drop, app.trigger_panel) + - inside(app.trigger_cmp_drop, app.trigger_panel) + - inside(app.trigger_mode_drop, app.trigger_panel) + - inside(app.trigger_width_spin, app.trigger_panel) + - inside(app.trigger_width2_spin, app.trigger_panel) + - inside(app.trigger_level2_spin, app.trigger_panel) + - inside(app.trigger_timeout_spin, app.trigger_panel) + - inside(app.trigger_level_spin, app.trigger_panel) + - inside(app.trigger_pre_spin, app.trigger_panel) + - inside(app.trigger_holdoff_spin, app.trigger_panel) + - inside(app.trigger_band_spin, app.trigger_panel) + - inside(app.trigger_hf_spin, app.trigger_panel) + - inside(app.events_record_switch, app.trigger_panel), + inside(app.trigger_signal_drop, app.trigger_body) + + inside(app.trigger_type_drop, app.trigger_body) + + inside(app.trigger_edge_drop, app.trigger_body) + + inside(app.trigger_cmp_drop, app.trigger_body) + + inside(app.trigger_mode_drop, app.trigger_body) + + inside(app.trigger_width_spin, app.trigger_body) + + inside(app.trigger_width2_spin, app.trigger_body) + + inside(app.trigger_level2_spin, app.trigger_body) + + inside(app.trigger_timeout_spin, app.trigger_body) + + inside(app.trigger_level_spin, app.trigger_body) + + inside(app.trigger_pre_spin, app.trigger_body) + + inside(app.trigger_holdoff_spin, app.trigger_body) + + inside(app.trigger_band_spin, app.trigger_body) + + inside(app.trigger_hf_spin, app.trigger_body) + + inside(app.events_record_switch, app.trigger_body), search_present, inside(app.search_run_button, search_root) + inside(app.search_cancel_button, search_root) + inside(app.search_prev_button, search_root) + inside(app.search_next_button, search_root)); diff --git a/src/ui/trigger-panel.ui b/src/ui/trigger-panel.ui index 749c1ff..e1130f1 100644 --- a/src/ui/trigger-panel.ui +++ b/src/ui/trigger-panel.ui @@ -212,12 +212,6 @@ Arm - - - Go live (trigger off) - disable trigger mode and stream continuously - - Search the buffer diff --git a/src/ui_app.hpp b/src/ui_app.hpp index cd2ae8f..5086adb 100644 --- a/src/ui_app.hpp +++ b/src/ui_app.hpp @@ -239,6 +239,9 @@ struct App { /// parent cannot be clicked, so a switch that went insensitive with its own pane could never turn /// it back on. int dbg_panel_switch_live = -1; + /// The Arm button, read with the trigger off: it is the pane's one runtime control, so it is the one + /// thing in the body that *is* disabled with the mode. + int dbg_arm_sensitive = -1; int dbg_switcher_policy = -1; /// Whether `name_side_tabs()` has named the pills: it can only run once the switcher is mapped. bool side_tabs_named = false; @@ -1347,7 +1350,6 @@ void on_layout_drag_begin(GtkGestureDrag* gesture, double x, double y, gpointer void on_layout_drag_update(GtkGestureDrag* gesture, double offset_x, double offset_y, gpointer user); void on_layout_drag_end(GtkGestureDrag* gesture, double offset_x, double offset_y, gpointer user); void on_trigger_toggled(GtkToggleButton* button, gpointer user); -void on_go_live(GtkButton*, gpointer user); plot::u32 measure_color(std::size_t index); bool visible_samples(App& app, std::size_t channel, std::vector& time, std::vector& value); int measure_channel(App& app, const std::string& signal); diff --git a/src/ui_probe.cpp b/src/ui_probe.cpp index f0e8a44..f4a7079 100644 --- a/src/ui_probe.cpp +++ b/src/ui_probe.cpp @@ -2341,6 +2341,7 @@ gboolean exit_probe_step1(gpointer user) { // The summary's text comes from the sync that wrote it: the container is a grid of key/value rows, // and asking it for a label's text was itself a critical. a->dbg_trigger_summary_early = a->dbg_trigger_summary; // as found, before this probe touches it + a->dbg_arm_sensitive = a->arm_button != nullptr && gtk_widget_get_sensitive(a->arm_button) ? 1 : 0; a->dbg_panel_switch_live = a->trigger_switch != nullptr && gtk_widget_get_sensitive(a->trigger_switch) && a->cursors_switch != nullptr && gtk_widget_get_sensitive(a->cursors_switch) diff --git a/src/ui_trigger.cpp b/src/ui_trigger.cpp index 6bc2533..e6222b5 100644 --- a/src/ui_trigger.cpp +++ b/src/ui_trigger.cpp @@ -410,17 +410,15 @@ void on_trigger_toggled(GtkToggleButton* button, gpointer user) { // `sync_events_availability()`'s call and not the trigger's alone. sync_events_availability(*app); gtk_button_set_label(GTK_BUTTON(app->arm_button), on ? "Disarm" : "Arm"); + // Arming is a *runtime* control: with trigger mode off there is no engine to arm, and the button is the + // one part of this pane that is not a setting. (The body itself stays live -- see above.) + if (app->arm_button != nullptr) gtk_widget_set_sensitive(app->arm_button, on); // The pane's own switch shows the same state, whichever of the two was clicked. sync_panel_switches(*app); } /// The way out of trigger mode, from inside the panel that froze the view: it is /// the toolbar's trigger switch, so there is exactly one piece of state. -void on_go_live(GtkButton*, gpointer user) { - App* app = static_cast(user); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(app->trigger_toggle), FALSE); -} - /// The row the `.ui` declares for a control, with the control appended to it. /// /// The label is the file's, because it is text a translator owns; the control is code's, because a @@ -545,8 +543,6 @@ GtkWidget* build_trigger_panel(App& app) { if (app.arm_button != nullptr) { g_signal_connect(app.arm_button, "clicked", G_CALLBACK(on_arm_clicked), &app); } - GtkWidget* live = GTK_WIDGET(gtk_builder_get_object(ui, "trigger_live")); - if (live != nullptr) g_signal_connect(live, "clicked", G_CALLBACK(on_go_live), &app); for (GtkWidget* w : {app.trigger_level_spin, app.trigger_pre_spin, app.trigger_holdoff_spin, app.trigger_width_spin, app.trigger_width2_spin,