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
+57
-5
@@ -206,6 +206,44 @@ gboolean exit_probe_step3(gpointer user) {
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
/// The frame after the pane was locked: read the compact arm control's *allocation*, then leave the
|
||||
/// configuration mode and trigger mode as step 2 used to, and hand the chain back its 400 ms. The button
|
||||
/// is created hidden and this is the first tick it is shown, so its width in the same tick would always
|
||||
/// read 0 -- a number that says "not yet laid out" rather than "not on the surface".
|
||||
gboolean compact_arm_step(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
GtkWidget* b = a->trigger_summary_arm;
|
||||
a->dbg_summary_arm_width = b != nullptr ? gtk_widget_get_width(b) : -1;
|
||||
std::fprintf(stderr,
|
||||
"[udpscope] compact arm probe: locked=%d enabled=%d own=%d effective=%d "
|
||||
"width=%d panel_width=%d label=%s\n",
|
||||
a->trigger_locked ? 1 : 0,
|
||||
a->trigger_toggle != nullptr &&
|
||||
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(a->trigger_toggle)) != FALSE
|
||||
? 1 : 0,
|
||||
b != nullptr && gtk_widget_get_visible(b) ? 1 : 0,
|
||||
b != nullptr && gtk_widget_is_visible(b) ? 1 : 0, a->dbg_summary_arm_width,
|
||||
// The body's own Arm button, read only if it is itself on screen: with the pane locked it
|
||||
// is not, and its stale allocation would read as if it were. -1 is "not the one on screen".
|
||||
// `is_visible`, not `get_visible`: the flag is the widget's own and stays true while its
|
||||
// *body* is hidden, which reported the body button's stale allocation as a live one.
|
||||
a->arm_button != nullptr && gtk_widget_is_visible(a->arm_button)
|
||||
? gtk_widget_get_width(a->arm_button)
|
||||
: -1,
|
||||
a->dbg_summary_arm_label.c_str());
|
||||
std::fflush(stderr);
|
||||
if (a->trigger_edit != nullptr) {
|
||||
g_signal_emit_by_name(a->trigger_edit, "clicked"); // and back to the view only mode
|
||||
}
|
||||
if (a->trigger_toggle != nullptr) {
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a->trigger_toggle), FALSE);
|
||||
}
|
||||
// The tick keeps running for these 400 ms, so by step 3 the status line reads
|
||||
// the state the app is actually in after leaving trigger mode.
|
||||
g_timeout_add(400, exit_probe_step3, a);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean exit_probe_step2(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
a->dbg_mode_triggered = a->status_detail;
|
||||
@@ -215,12 +253,21 @@ gboolean exit_probe_step2(gpointer user) {
|
||||
a->dbg_trigger_time = a->model->trigger_time().load();
|
||||
a->dbg_trigger_time_valid = a->model->trigger_time_valid().load() ? 1 : 0;
|
||||
}
|
||||
if (a->trigger_toggle != nullptr) {
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a->trigger_toggle), FALSE);
|
||||
// The combination the compact control exists for: the trigger *on*, the pane *locked*, so the body's
|
||||
// own Arm button has gone with the body. Locking is the [edit] button's click, because that is what a
|
||||
// user does, and the two properties read are the two the user sees. The label is checked against the
|
||||
// engine's armed state rather than against a word: a single-shot latch can have disarmed it by now.
|
||||
if (a->trigger_edit != nullptr) {
|
||||
g_signal_emit_by_name(a->trigger_edit, "clicked"); // into configuration mode
|
||||
GtkWidget* b = a->trigger_summary_arm;
|
||||
a->dbg_summary_arm_visible = b != nullptr && gtk_widget_get_visible(b) ? 1 : 0;
|
||||
a->dbg_summary_arm_sensitive = b != nullptr && gtk_widget_get_sensitive(b) ? 1 : 0;
|
||||
const char* text = b != nullptr && GTK_IS_BUTTON(b) ? gtk_button_get_label(GTK_BUTTON(b)) : nullptr;
|
||||
a->dbg_summary_arm_label = text != nullptr ? text : "-";
|
||||
}
|
||||
// The tick keeps running for these 400 ms, so by step 3 the status line reads
|
||||
// the state the app is actually in after leaving trigger mode.
|
||||
g_timeout_add(400, exit_probe_step3, a);
|
||||
// The pane stays locked for one more frame: the allocation the next step reads is only meaningful once
|
||||
// a frame has run with the control shown. Leaving trigger mode is that step's job now.
|
||||
g_timeout_add(400, compact_arm_step, a);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
@@ -2400,6 +2447,11 @@ gboolean exit_probe_step1(gpointer user) {
|
||||
// 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;
|
||||
// The compact line's arm control must be *absent* here: the trigger is off, the line beside it reads
|
||||
// "stream (trigger off)", and a trigger that is off has no engine to arm. Read before the toggle, which
|
||||
// is where that absence is the assertion -- and it is the pair to the reading in step 2.
|
||||
a->dbg_summary_arm_off =
|
||||
a->trigger_summary_arm != nullptr && gtk_widget_get_visible(a->trigger_summary_arm) ? 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)
|
||||
|
||||
Reference in new issue
Block a user