ui: Arm is disabled with trigger mode off, and the redundant Go-live button is gone

**`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`.
This commit is contained in:
Martino Ferrari committed 2026-10-14 14:50:04 +02:00
1 parent 796dda4ad5
commit d8705a369f
6 files changed
+40 -36

No files matched your search

+8 -1
View File
@@ -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 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 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. 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 - **`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 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`, 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 [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 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 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 search buttons having resolved by id, and `search_inside` is those four being inside the block the expander
+25 -21
View File
@@ -4082,7 +4082,7 @@ gboolean tick(gpointer user) {
if (app->capturing) { if (app->capturing) {
mode = "trigger: CAPTURING (post-trigger window filling)"; mode = "trigger: CAPTURING (post-trigger window filling)";
} else if (app->event_held) { } 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) { } else if (armed && have_trigger_value) {
static char waiting[192]; static char waiting[192];
std::snprintf(waiting, sizeof(waiting), 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 // 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. // 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); 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); gtk_widget_set_sensitive(app->cursors_reset, FALSE);
if (app->side_stack != nullptr) { 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]); app.dbg_badge_frames[3]);
std::printf("[udpscope] trigger panel sensitive before the probe toggled it: %d; " 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; " "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_sensitive_default, app.dbg_trigger_items,
app.dbg_trigger_selected, app.dbg_side_column_after_hide, 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 // 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 // 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; 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; 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)) { c = gtk_widget_get_next_sibling(c)) {
if (GTK_IS_BOX(c)) ++boxes; 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 // 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 // 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. // that is entirely correct, which is what this line did first.
GtkWidget* search_root = app.trigger_panel; GtkWidget* search_root = app.trigger_body;
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)) { c = gtk_widget_get_next_sibling(c)) {
if (GTK_IS_EXPANDER(c)) { if (GTK_IS_EXPANDER(c)) {
if (GtkWidget* inner = gtk_expander_get_child(GTK_EXPANDER(c))) search_root = inner; 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 " "[udpscope] trigger panel: boxes=%d controls_inside=%d/15 search_widgets=%d/4 "
"search_inside=%d/4\n", "search_inside=%d/4\n",
boxes, boxes,
inside(app.trigger_signal_drop, app.trigger_panel) + inside(app.trigger_signal_drop, app.trigger_body) +
inside(app.trigger_type_drop, app.trigger_panel) + inside(app.trigger_type_drop, app.trigger_body) +
inside(app.trigger_edge_drop, app.trigger_panel) + inside(app.trigger_edge_drop, app.trigger_body) +
inside(app.trigger_cmp_drop, app.trigger_panel) + inside(app.trigger_cmp_drop, app.trigger_body) +
inside(app.trigger_mode_drop, app.trigger_panel) + inside(app.trigger_mode_drop, app.trigger_body) +
inside(app.trigger_width_spin, app.trigger_panel) + inside(app.trigger_width_spin, app.trigger_body) +
inside(app.trigger_width2_spin, app.trigger_panel) + inside(app.trigger_width2_spin, app.trigger_body) +
inside(app.trigger_level2_spin, app.trigger_panel) + inside(app.trigger_level2_spin, app.trigger_body) +
inside(app.trigger_timeout_spin, app.trigger_panel) + inside(app.trigger_timeout_spin, app.trigger_body) +
inside(app.trigger_level_spin, app.trigger_panel) + inside(app.trigger_level_spin, app.trigger_body) +
inside(app.trigger_pre_spin, app.trigger_panel) + inside(app.trigger_pre_spin, app.trigger_body) +
inside(app.trigger_holdoff_spin, app.trigger_panel) + inside(app.trigger_holdoff_spin, app.trigger_body) +
inside(app.trigger_band_spin, app.trigger_panel) + inside(app.trigger_band_spin, app.trigger_body) +
inside(app.trigger_hf_spin, app.trigger_panel) + inside(app.trigger_hf_spin, app.trigger_body) +
inside(app.events_record_switch, app.trigger_panel), inside(app.events_record_switch, app.trigger_body),
search_present, search_present,
inside(app.search_run_button, search_root) + inside(app.search_cancel_button, search_root) + 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)); inside(app.search_prev_button, search_root) + inside(app.search_next_button, search_root));
-6
View File
@@ -212,12 +212,6 @@
<property name="label" translatable="yes">Arm</property> <property name="label" translatable="yes">Arm</property>
</object> </object>
</child> </child>
<child>
<object class="GtkButton" id="trigger_live">
<property name="label" translatable="yes">Go live (trigger off)</property>
<property name="tooltip-text" translatable="yes">disable trigger mode and stream continuously</property>
</object>
</child>
<child> <child>
<object class="GtkExpander" id="search_exp"> <object class="GtkExpander" id="search_exp">
<property name="label" translatable="yes">Search the buffer</property> <property name="label" translatable="yes">Search the buffer</property>
+3 -1
View File
@@ -239,6 +239,9 @@ struct App {
/// parent cannot be clicked, so a switch that went insensitive with its own pane could never turn /// parent cannot be clicked, so a switch that went insensitive with its own pane could never turn
/// it back on. /// it back on.
int dbg_panel_switch_live = -1; 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; int dbg_switcher_policy = -1;
/// Whether `name_side_tabs()` has named the pills: it can only run once the switcher is mapped. /// Whether `name_side_tabs()` has named the pills: it can only run once the switcher is mapped.
bool side_tabs_named = false; 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_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_layout_drag_end(GtkGestureDrag* gesture, double offset_x, double offset_y, gpointer user);
void on_trigger_toggled(GtkToggleButton* button, 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); plot::u32 measure_color(std::size_t index);
bool visible_samples(App& app, std::size_t channel, std::vector<double>& time, std::vector<double>& value); bool visible_samples(App& app, std::size_t channel, std::vector<double>& time, std::vector<double>& value);
int measure_channel(App& app, const std::string& signal); int measure_channel(App& app, const std::string& signal);
+1
View File
@@ -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, // 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. // 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_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->dbg_panel_switch_live =
a->trigger_switch != nullptr && gtk_widget_get_sensitive(a->trigger_switch) && a->trigger_switch != nullptr && gtk_widget_get_sensitive(a->trigger_switch) &&
a->cursors_switch != nullptr && gtk_widget_get_sensitive(a->cursors_switch) a->cursors_switch != nullptr && gtk_widget_get_sensitive(a->cursors_switch)
+3 -7
View File
@@ -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()`'s call and not the trigger's alone.
sync_events_availability(*app); sync_events_availability(*app);
gtk_button_set_label(GTK_BUTTON(app->arm_button), on ? "Disarm" : "Arm"); 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. // The pane's own switch shows the same state, whichever of the two was clicked.
sync_panel_switches(*app); sync_panel_switches(*app);
} }
/// The way out of trigger mode, from inside the panel that froze the view: it is /// 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. /// the toolbar's trigger switch, so there is exactly one piece of state.
void on_go_live(GtkButton*, gpointer user) {
App* app = static_cast<App*>(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 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 /// 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) { if (app.arm_button != nullptr) {
g_signal_connect(app.arm_button, "clicked", G_CALLBACK(on_arm_clicked), &app); 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, for (GtkWidget* w : {app.trigger_level_spin, app.trigger_pre_spin, app.trigger_holdoff_spin,
app.trigger_width_spin, app.trigger_width2_spin, app.trigger_width_spin, app.trigger_width2_spin,