feat: the locked trigger pane carries Arm/Disarm, and one function states both arm buttons
The compact line -- the pane's locked, "what is it set to" mode -- showed the arm state in words and gave
no way to change it, so arming from there meant unfolding the panel first, which is the thing the lock
exists to avoid. The control is built into the pane's *heading*, the row that stays on screen when the
body goes, and it is wired to the same `on_arm_clicked` the body's button uses, so both do one thing.
Its label, sensitivity and visibility are stated by `sync_arm_buttons()`, called from the four places any of
its inputs can move: the click, the mode toggle, the curator's per-tick re-read (a single-shot latch disarms
by itself) and `apply_pane_lock()`. The label comes from the producer's `trigger_armed()` atomic, the other
two from the toolbar toggle that owns `enabled`, so no caller needs the model lock. Visibility is
`enabled && locked`: with trigger mode off the line beside it already reads "stream (trigger off)" and there
is no engine to arm, and with the pane open the body's own button is the one on screen. Never both -- two
arm buttons would be two controls for one state.
Verified with the arm probe, which locks the pane the way a user does (the [edit] click):
compact arm with the trigger off: visible=0; with the trigger on and the pane locked:
visible=1 sensitive=1 width=51 label=Disarm
[udpscope] compact arm probe: locked=1 enabled=1 own=1 effective=1 width=51 panel_width=-1 label=Disarm
`panel_width=-1` is the "not both" assertion: the body's button is not on screen while the compact one is.
The width cost a defect to read. The control is created hidden and shown for the first time inside the
handler, so a same-tick `gtk_widget_get_width()` reads 0 while `gtk_widget_is_visible()` already says 1 --
and the body button's *stale* allocation (270 px, from when the body was still up) reads as a live one,
because `gtk_widget_get_visible()` is the widget's own flag and stays true under a hidden parent. The
reading is therefore taken one frame later, by a step of its own, and the control is compared with
`is_visible()`. Both defects are in the invariants, because the next control read this way will meet them.
0 Gtk-CRITICAL; clean build with the documented four warnings (the help resource re-measured); test-ui
14/14. Docs: the rule and both readings in `invariants-trigger-and-pinning.md`, the probe's entry in
`probe-readings.md`, and the user-facing sentence in `src/help/08-trigger.txt`.
This commit is contained in:
1 parent
908286ee89
commit
620473a5be
8 files changed
+160
-17
No files matched your search
+40
-5
@@ -14,6 +14,31 @@
|
||||
namespace udpscope {
|
||||
namespace ui_impl {
|
||||
|
||||
/// Both arm buttons from the one state, so the panel's and the compact line's cannot disagree about what
|
||||
/// the engine is doing. Three properties, three sources, and they are deliberately not the same source:
|
||||
/// the *label* is the producer's atomic (no lock), the *sensitivity* and the compact button's
|
||||
/// *visibility* are the toolbar toggle's state -- the toggle is what owns `enabled`, and reading it
|
||||
/// instead of the model keeps this callable from a signal handler that does not hold, and must not take,
|
||||
/// the model lock. Visibility is `enabled && locked`: with trigger mode off the line's own text says
|
||||
/// "stream (trigger off)" and there is no engine to arm, and with the pane open the body's button is the
|
||||
/// one on screen -- never both at once.
|
||||
void sync_arm_buttons(App& app) {
|
||||
const bool armed = app.model->trigger_armed().load(std::memory_order_relaxed);
|
||||
const bool enabled =
|
||||
app.trigger_toggle != nullptr &&
|
||||
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(app.trigger_toggle)) != FALSE;
|
||||
const char* label = armed ? "Disarm" : "Arm";
|
||||
if (app.arm_button != nullptr) {
|
||||
gtk_button_set_label(GTK_BUTTON(app.arm_button), label);
|
||||
gtk_widget_set_sensitive(app.arm_button, enabled);
|
||||
}
|
||||
if (app.trigger_summary_arm != nullptr) {
|
||||
gtk_button_set_label(GTK_BUTTON(app.trigger_summary_arm), label);
|
||||
gtk_widget_set_sensitive(app.trigger_summary_arm, enabled);
|
||||
gtk_widget_set_visible(app.trigger_summary_arm, enabled && app.trigger_locked);
|
||||
}
|
||||
}
|
||||
|
||||
void on_arm_clicked(GtkButton* button, gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
(void)button;
|
||||
@@ -37,7 +62,7 @@ void on_arm_clicked(GtkButton* button, gpointer user) {
|
||||
// is switched on with it -- otherwise the trigger could never see a sample.
|
||||
// Outside the lock: set_signal_enabled() takes it.
|
||||
if (armed && signal >= 0) app->model->set_signal_enabled(static_cast<std::size_t>(signal), true);
|
||||
gtk_button_set_label(GTK_BUTTON(app->arm_button), armed ? "Disarm" : "Arm");
|
||||
sync_arm_buttons(*app);
|
||||
}
|
||||
|
||||
/// The trigger type dropdown's order, as a table rather than a cast: the dropdown lists
|
||||
@@ -409,10 +434,10 @@ void on_trigger_toggled(GtkToggleButton* button, gpointer user) {
|
||||
// column of stale events beside the plot. Recording is the other half of that answer, so this is
|
||||
// `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);
|
||||
// Arming is a *runtime* control: with trigger mode off there is no engine to arm, and the arm buttons
|
||||
// are the one part of this pane that is not a setting. (The body itself stays live -- see above.) Both
|
||||
// of them are stated from this switch's new state in one place, so they cannot disagree with each other.
|
||||
sync_arm_buttons(*app);
|
||||
// The pane's own switch shows the same state, whichever of the two was clicked, and the compact line
|
||||
// changes shape with it: with trigger mode off it says "stream" and nothing else.
|
||||
sync_panel_switches(*app);
|
||||
@@ -664,6 +689,16 @@ GtkWidget* build_trigger_panel(App& app) {
|
||||
gtk_widget_set_tooltip_text(app.trigger_edit, "configure the trigger");
|
||||
g_signal_connect(app.trigger_edit, "clicked", G_CALLBACK(on_pane_edit_clicked), &app);
|
||||
gtk_box_append(GTK_BOX(head), app.trigger_edit);
|
||||
// The compact line's own arm control, built here beside the [edit] and the switch for the same reason
|
||||
// the switch is: this row stays on screen when the body is hidden. A locked pane is exactly where
|
||||
// arming is decided -- the body's button left with the body -- and the heading is where it has to be.
|
||||
app.trigger_summary_arm = gtk_button_new_with_label("Arm");
|
||||
gtk_widget_set_valign(app.trigger_summary_arm, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_tooltip_text(app.trigger_summary_arm,
|
||||
"arm or disarm the trigger; disarming freezes the view where it stands");
|
||||
gtk_widget_set_visible(app.trigger_summary_arm, FALSE);
|
||||
g_signal_connect(app.trigger_summary_arm, "clicked", G_CALLBACK(on_arm_clicked), &app);
|
||||
gtk_box_append(GTK_BOX(head), app.trigger_summary_arm);
|
||||
app.trigger_switch = gtk_switch_new();
|
||||
gtk_widget_set_valign(app.trigger_switch, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_tooltip_text(app.trigger_switch,
|
||||
|
||||
Reference in new issue
Block a user