ui: each pane carries its own switch, the same one the header bar has
The trigger pane and the cursors pane now have a `GtkSwitch` in their heading, and it is *one control in
two places* with the header bar's button: both run the same action (`run_app_action()`), and
`sync_panel_switches()` re-reads each pair from the toolbar's state after any change, so clicking either
end moves both. A pane whose feature is off is disabled, which is exactly the state you need the switch
in, so it belongs *in* the pane rather than only in the toolbar.
Three things it needed:
- **The sensitive target moved to the pane's body.** A control inside an insensitive parent cannot be
clicked, so a switch that greyed out with its own pane could never turn it back on. `on_trigger_toggled`
and `on_cursors_toggled` now disable `trigger_body` / `cursors_body`, and so does the startup default;
the headings, with their switches, stay live.
- **The trigger's heading moved out of `trigger-panel.ui`** and is built in `build_trigger_panel()`,
which returns the heading row and the panel together. It has to be a sibling of the body for the switch
to stay usable, and keeping it in the file would have meant the file owning a widget the code must
arrange. The file's own panel is unchanged otherwise, so the `[trigger panel:]` counts still read.
- **A guard on the sync.** Setting a `GtkSwitch` emits `notify::active` for a programmatic write just as
it does for a click, so without `App::panel_switch_sync` the program's own write would come back as a
click and flip the feature again.
Readings, both directions in one line:
trigger panel sensitive before the probe toggled it: 0; … ; pane switches live while their panes are off=1
panel switches: trigger=1/1 cursors=1/1 drove_toolbar=1 (pane switch / header bar button)
`pane switches live …=1` is the design's point and is read before anything turns either pane on; `0` would
be a switch drawn, greyed and useless. The trigger pair is moved from the *toolbar* end and the cursors
pair from the *pane* end, so the line carries both halves of the sync, and `drove_toolbar=1` is the pane's
own switch having turned the cursors on with the button following. `trigger panel sensitive …=0` is the
body, not the panel.
Verified: the two lines above; `[side-tab-probe] … advanced=4`; `make test-ui` 14 files; zero
`Gtk-CRITICAL` and no missing icon. Docs: the new reading in `probe-readings.md` with what a `0` means,
the invariants bullet for the one-control-in-two-places rule and its two traps, the sidebar bullet's
sensitive target, and the README note on why the switch is in the pane.
This commit is contained in:
1 parent
d9287ea826
commit
cc8335c514
10 files changed
+201
-26
No files matched your search
@@ -2397,7 +2397,10 @@ now — they are listed so they are not mistaken for anything else:
|
||||
cursors and the ruler for Measure, a sine crossing its trigger level and the channel list
|
||||
for Signals (the same picture the toolbar's trigger button carries), drawn as a
|
||||
32x16 pair so both motifs are legible at a glance — with the words kept as each pill's tooltip and
|
||||
accessible name. The design spec had the switcher showing *titles* and neither page an
|
||||
accessible name. Each pane's heading also carries its **own switch**, the same control as the
|
||||
header bar's button — clicking either moves both — because a pane that is *disabled* while its
|
||||
feature is off is a pane whose controls you would otherwise have to leave to turn it back on. The
|
||||
design spec had the switcher showing *titles* and neither page an
|
||||
`icon-name`; `narrow` alone does not do it (libadwaita still draws a label beside the icon,
|
||||
measured), so the pages carry an empty title and the pills are named in code. With the math
|
||||
list folded into the signal list under a `MATH` divider whose `+` creates a channel — a derived channel already *is* a channel in
|
||||
|
||||
@@ -166,8 +166,9 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
heights, so one shared scroller would leave a switch scrolled where the new page never asked to
|
||||
be). The reason is the one this trap teaches: five sections competing for one 320 px column left
|
||||
every one of them short of room. A *feature's* on/off is no longer a visibility at all -- the
|
||||
trigger's and the cursors' toggles set their panel's **sensitivity**, so the panel stays where it
|
||||
is and the settings you are about to switch back on do not vanish while you look for them. The
|
||||
trigger's and the cursors' toggles set their pane's **body** insensitive, so the pane stays where it
|
||||
is and the settings you are about to switch back on do not vanish while you look for them. The *body*
|
||||
and not the pane, because each pane's heading carries its own switch (see the next bullet). The
|
||||
trap itself does not go away with the widget: a stack's hidden page measures 0x0 too and is still
|
||||
given the leftover width, so **ask about state, not about size** carries over unchanged. Every
|
||||
allocation read in the sidebar is a debug capture, and each reads 0 on the page that is *not*
|
||||
@@ -183,6 +184,16 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
**tooltip and accessible name** from the page's name, then hides the empty label that would otherwise
|
||||
leave the icon off-centre. An icon-only pill is still a named control for a screen reader, and
|
||||
`switcher: … pills=2 labels=0 names=2` is the reading for all three facts at once.
|
||||
- **A pane's heading switch and the header bar's button are one control in two places.** Each pane's
|
||||
heading (`build_trigger_panel()` builds the trigger's, `cursors-panel.ui` has the cursors' in its
|
||||
`head`) carries a `GtkSwitch` that runs the *same action* the toolbar button runs, and
|
||||
`sync_panel_switches()` re-reads each pair from the toolbar's state after any change — so clicking
|
||||
either end moves both, and `panel switches: trigger=1/1 cursors=1/1` is the reading. Two traps:
|
||||
a `GtkSwitch` emits `notify::active` for a *programmatic* write as well, so the sync holds a guard
|
||||
(`App::panel_switch_sync`) or the program's own write comes back as a click; and the switcher must
|
||||
stay *out* of what the pane disables, because a control inside an insensitive parent cannot be
|
||||
clicked — a switch that greyed out with its own pane could never turn it back on. That is why the
|
||||
sensitive target is the pane's **body** (`trigger_body`, `cursors_body`) and not the pane.
|
||||
- **A list box outlives the children of the pane it sits in, and the teardown order is not yours to
|
||||
choose.** Tearing the window down finalizes the events pane's buttons, then the list box, and the
|
||||
list's own `selected-rows-changed` then reaches a handler holding the freed pointers: four
|
||||
|
||||
@@ -164,7 +164,7 @@ Two lines of the self-check output are assertions in disguise:
|
||||
- `[cursors-probe] <action> mode=.. on=D pane_sensitive=D t0=.. t1=.. dt=.. T=[..] lines=N [..]`
|
||||
(stderr, `UDPSCOPE_PROBE_CURSORS_MS`) — walks `vertical` → `horizontal` → `both` →
|
||||
`mix`, then moves a marker *through the plot* (`Plot::set_cursor_x(..., Free)`, which is
|
||||
what a drag does) and reports again. `pane_sensitive` is the cursors *panel's* sensitivity, not a
|
||||
what a drag does) and reports again. `pane_sensitive` is the cursors *panel body's* sensitivity, not a
|
||||
revealer's child: with the cursors off the panel stays exactly where it is and its cells blank to `--`,
|
||||
because a readout whose instruments are off has no sample to quote. **The times must survive every mode change**: they
|
||||
are the `RowSet`'s one pair, and a mode that parks the markers must not read them back
|
||||
@@ -1139,6 +1139,29 @@ library measures and draws the rect in tests of its own.
|
||||
defect this reading exists for, and it was invisible from the frame alone: the file view pinning its
|
||||
axis from the ring each frame is what swallowed the gesture.
|
||||
|
||||
## `panel switches:` and the trigger panel's default sensitivity
|
||||
|
||||
```
|
||||
[udpscope] trigger panel sensitive before the probe toggled it: 0; signal list items=1 selected=0; column visible after sidebar toggle off=1; pane switches live while their panes are off=1
|
||||
[udpscope] panel switches: trigger=1/1 cursors=1/1 drove_toolbar=1 (pane switch / header bar button; `drove_toolbar` is the pane's own switch having moved the button)
|
||||
```
|
||||
|
||||
Each pane's heading carries its own switch, and it is the *same* control as the header bar's button:
|
||||
both run the same action and both are re-read from the toolbar's state afterwards, so they cannot
|
||||
disagree. The two lines are the claim, in both directions:
|
||||
|
||||
- **`pane switches live while their panes are off=1`** — read before anything turns either on, because
|
||||
it is the whole point of putting the switches in the headings: a control inside an insensitive parent
|
||||
cannot be clicked, so a switch that went insensitive *with* its own pane could never turn it back on.
|
||||
**`0` is that defect**: the switch is drawn, greyed, and useless.
|
||||
- **`trigger=` and `cursors=` are `pane switch / header bar button`**, and the pairs must agree. The
|
||||
trigger's is moved from the *toolbar* end (these steps toggle that button to arm the trigger) and the
|
||||
cursors' from the *pane* end, so one line carries both directions of the sync. **`drove_toolbar=1`**
|
||||
is the second direction's assertion: the pane's own switch turned the cursors on and the header bar's
|
||||
button followed.
|
||||
- `trigger panel sensitive …=0` is the *panel body*, not the panel: the heading row, with its switch,
|
||||
stays live. `1` here while trigger mode is off is the pane not being disabled at all.
|
||||
|
||||
## `[sidebar-probe]`
|
||||
|
||||
```
|
||||
|
||||
+49
-6
@@ -4355,6 +4355,39 @@ void on_window_popover_press(GtkGestureClick*, int, double x, double y, gpointer
|
||||
/// gtk_toggle_button_set_active(), which emits ::toggled and so runs exactly the handler a
|
||||
/// click would; the non-toggles call their click handlers with a null button, which they
|
||||
/// already ignore. Split out of the key handler so the probe can fire a row directly.
|
||||
/// The pane's switches and the header bar's buttons are one control with two places to click, so a
|
||||
/// change in either re-reads the other. `panel_switch_sync` is the guard: setting a `GtkSwitch` emits
|
||||
/// its own `notify::active`, and without it the program's write would come back as a click.
|
||||
void sync_panel_switches(App& app) {
|
||||
if (app.panel_switch_sync) return;
|
||||
app.panel_switch_sync = true;
|
||||
const auto mirror = [](GtkWidget* panel_switch, GtkWidget* toolbar_toggle) {
|
||||
if (panel_switch == nullptr || toolbar_toggle == nullptr) return;
|
||||
gtk_switch_set_active(GTK_SWITCH(panel_switch),
|
||||
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar_toggle)) == TRUE);
|
||||
};
|
||||
mirror(app.trigger_switch, app.trigger_toggle);
|
||||
mirror(app.cursors_switch, app.cursors_toggle);
|
||||
app.panel_switch_sync = false;
|
||||
}
|
||||
|
||||
/// A pane's own switch drives the *same action* as the header bar's button, so either place reaches
|
||||
/// the feature down one path, and the sync above is what keeps the two showing the same thing.
|
||||
void on_panel_switch_toggled(GObject* obj, GParamSpec*, gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
if (app->panel_switch_sync) return;
|
||||
const bool on = gtk_switch_get_active(GTK_SWITCH(obj)) != FALSE;
|
||||
const bool is_trigger = app->trigger_switch != nullptr && obj == G_OBJECT(app->trigger_switch);
|
||||
GtkWidget* toggle = is_trigger ? app->trigger_toggle : app->cursors_toggle;
|
||||
if (toggle == nullptr) return;
|
||||
// The action toggles the toolbar button, so it is run only when the two actually disagree -- which
|
||||
// is what makes this a *set* rather than a second toggle of something already in that state.
|
||||
if ((gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle)) != FALSE) != on) {
|
||||
run_app_action(*app, is_trigger ? AppAction::TriggerPanel : AppAction::CursorsPane);
|
||||
}
|
||||
sync_panel_switches(*app);
|
||||
}
|
||||
|
||||
void run_app_action(App& app, AppAction action) {
|
||||
const auto toggle = [](GtkWidget* button) {
|
||||
if (button == nullptr) return;
|
||||
@@ -4771,10 +4804,12 @@ void on_activate(GtkApplication* application, gpointer user) {
|
||||
app->signal_panel = build_signal_panel(*app);
|
||||
gtk_box_append(GTK_BOX(signals_page), app->signal_panel);
|
||||
|
||||
// Both instruments start off, so both panels start insensitive -- stated here by the one place
|
||||
// that knows the default, rather than left to whichever widget happened to be built first.
|
||||
gtk_widget_set_sensitive(app->trigger_panel, FALSE);
|
||||
gtk_widget_set_sensitive(app->cursors_panel, FALSE);
|
||||
// Both instruments start off, so both *bodies* start insensitive -- stated here by the one place
|
||||
// that knows the default, rather than left to whichever widget happened to be built first. The
|
||||
// bodies and not the panels: each panel's heading carries the switch that turns it on, and a
|
||||
// control inside an insensitive parent cannot be clicked.
|
||||
gtk_widget_set_sensitive(app->trigger_body, FALSE);
|
||||
gtk_widget_set_sensitive(app->cursors_body, FALSE);
|
||||
gtk_widget_set_sensitive(app->cursors_reset, FALSE);
|
||||
|
||||
if (app->side_stack != nullptr) {
|
||||
@@ -5685,6 +5720,12 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
app.dbg_trigger_sensitive, app.dbg_switcher_visible,
|
||||
rows.cursors_visible() ? 1 : 0, rows.time_window(),
|
||||
app.window_limit_s);
|
||||
std::printf("[udpscope] panel switches: trigger=%d/%d cursors=%d/%d drove_toolbar=%d "
|
||||
"(pane switch / header bar button; `drove_toolbar` is the pane's own switch having "
|
||||
"moved the button)\n",
|
||||
app.dbg_panel_switch_trigger, app.dbg_toolbar_switch_trigger,
|
||||
app.dbg_panel_switch_cursors, app.dbg_toolbar_switch_cursors,
|
||||
app.dbg_panel_drove_toolbar);
|
||||
std::printf("[udpscope] switcher: policy=%s pills=%d labels=%d names=%d icon=%dx%d "
|
||||
"size=%dx%d\n",
|
||||
app.dbg_switcher_policy == 0 ? "narrow" : (app.dbg_switcher_policy == 1 ? "wide" : "?"),
|
||||
@@ -5855,9 +5896,11 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
app.dbg_badge_frames[0], app.dbg_badge_frames[1], app.dbg_badge_frames[2],
|
||||
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\n",
|
||||
"signal list items=%d selected=%d; column visible after sidebar toggle off=%d; "
|
||||
"pane switches live while their panes are off=%d\n",
|
||||
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);
|
||||
{
|
||||
// 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
|
||||
|
||||
@@ -38,6 +38,15 @@
|
||||
<property name="css-classes">flat</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<!-- The pane's own switch, the same one the header bar carries: a feature whose pane is
|
||||
disabled while it is off needs its control *in* the pane, or turning it back on means
|
||||
finding the toolbar button that greyed it out. `body` is what goes insensitive, not the
|
||||
panel, so this stays live. -->
|
||||
<object class="GtkSwitch" id="cursors_switch">
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
<!-- SPDX-License-Identifier: MIT -->
|
||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||
<!--
|
||||
The trigger panel's frame: the heading, one labelled row per setting, the two buttons that run it, and
|
||||
the search block.
|
||||
The trigger panel's frame: one labelled row per setting, the two buttons that run it, and the search
|
||||
block. Its *heading* is not here: the heading row carries the switch that turns trigger mode on and
|
||||
off, and this panel's body is what goes insensitive when it is off, so the heading has to be its
|
||||
sibling rather than a child of it (`build_trigger_panel()` returns the two together).
|
||||
|
||||
Every row here is a label and a place for a control, and the control itself is built in code. A
|
||||
dropdown's model and a spin button's range are the *application's* (the condition list, the level
|
||||
@@ -24,12 +26,6 @@
|
||||
<property name="margin-bottom">8</property>
|
||||
<property name="margin-start">8</property>
|
||||
<property name="margin-end">8</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="trigger_heading">
|
||||
<property name="label" translatable="yes">Trigger</property>
|
||||
<property name="halign">start</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="signal_row">
|
||||
<property name="orientation">horizontal</property>
|
||||
|
||||
@@ -227,6 +227,18 @@ struct App {
|
||||
/// is what it costs.
|
||||
int dbg_switcher_w = -1;
|
||||
int dbg_switcher_h = -1;
|
||||
/// The panes' own switches and the header bar's buttons, read together by the exit probe: the two
|
||||
/// must agree, and `dbg_panel_drove_toolbar` is the pane's switch having moved the button.
|
||||
int dbg_panel_switch_trigger = -1;
|
||||
int dbg_toolbar_switch_trigger = -1;
|
||||
int dbg_panel_switch_cursors = -1;
|
||||
int dbg_toolbar_switch_cursors = -1;
|
||||
int dbg_panel_drove_toolbar = -1;
|
||||
/// Both panes' switches being *sensitive* while their panes are off -- read before anything turns
|
||||
/// one on. It is the whole point of putting them in the heading: a control inside an insensitive
|
||||
/// 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;
|
||||
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;
|
||||
@@ -236,6 +248,16 @@ 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 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
|
||||
/// change, so they cannot disagree.
|
||||
GtkWidget* trigger_switch = nullptr;
|
||||
GtkWidget* cursors_switch = nullptr;
|
||||
GtkWidget* trigger_body = nullptr;
|
||||
/// True while `sync_panel_switches()` is writing the switches: setting a `GtkSwitch` emits its own
|
||||
/// `notify::active`, and without this the program's own write would come back as a click.
|
||||
bool panel_switch_sync = false;
|
||||
/// The cursors pane: listing what the instruments read -- T0/T1/dT always, per row an Y1/Y2/dY
|
||||
/// where rulers are up, and in `mix` mode the value each cursor crosses each trace at.
|
||||
GtkWidget* cursors_mode_drop = nullptr;
|
||||
@@ -1388,6 +1410,10 @@ void on_right_click(GtkGestureClick* gesture, int, double x, double y, gpointer
|
||||
gboolean on_drop_channel(GtkDropTarget*, const GValue* value, double x, double y, gpointer user);
|
||||
void on_side_tab_changed(GObject*, GParamSpec*, gpointer user);
|
||||
void select_side_tab(App& app, const char* name);
|
||||
/// Reads each pane's own switch back off the header bar's button, and the other way round: the two are
|
||||
/// one control in two places, and `panel_switch_sync` keeps the write from looking like a click.
|
||||
void sync_panel_switches(App& app);
|
||||
void on_panel_switch_toggled(GObject* obj, GParamSpec*, gpointer user);
|
||||
void update_side_visibility(App& app);
|
||||
void on_sidebar_toggled(GtkToggleButton* button, gpointer user);
|
||||
void rebuild_signal_panels(App& app);
|
||||
|
||||
+12
-2
@@ -96,6 +96,12 @@ GtkWidget* build_cursors_panel(App& app) {
|
||||
app.cursors_mode_drop = GTK_WIDGET(gtk_builder_get_object(ui, "mode_drop"));
|
||||
app.cursors_reset = GTK_WIDGET(gtk_builder_get_object(ui, "reset"));
|
||||
app.cursors_body = GTK_WIDGET(gtk_builder_get_object(ui, "body"));
|
||||
app.cursors_switch = GTK_WIDGET(gtk_builder_get_object(ui, "cursors_switch"));
|
||||
if (app.cursors_switch != nullptr) {
|
||||
gtk_widget_set_tooltip_text(app.cursors_switch,
|
||||
"the cursors, the same switch the header bar carries");
|
||||
g_signal_connect(app.cursors_switch, "notify::active", G_CALLBACK(on_panel_switch_toggled), &app);
|
||||
}
|
||||
if (box == nullptr || app.cursors_mode_drop == nullptr || app.cursors_reset == nullptr ||
|
||||
app.cursors_body == nullptr) {
|
||||
// A missing id is a mistake in the `.ui`. The panel comes up empty rather than taking the window
|
||||
@@ -129,15 +135,19 @@ void on_cursors_toggled(GtkToggleButton* button, gpointer user) {
|
||||
const bool on = gtk_toggle_button_get_active(button);
|
||||
app->rows->set_cursors_visible(on);
|
||||
// Disabled, not hidden, and the same for the reset beside it: with the cursors off there is
|
||||
// nothing to reset, but the button must stay where the user will look for it.
|
||||
// nothing to reset, but the button must stay where the user will look for it. The *body* goes
|
||||
// insensitive and not the panel, because the panel's header carries the switch that turns the
|
||||
// cursors back on -- and a control inside an insensitive parent cannot be clicked.
|
||||
gtk_widget_set_sensitive(app->cursors_reset, on);
|
||||
if (app->cursors_panel != nullptr) gtk_widget_set_sensitive(app->cursors_panel, on);
|
||||
if (app->cursors_body != nullptr) gtk_widget_set_sensitive(app->cursors_body, on);
|
||||
if (!on) update_cursors_pane(*app);
|
||||
if (on) {
|
||||
app->rows->reset_cursors();
|
||||
rebuild_cursors_pane(*app);
|
||||
update_cursors_pane(*app);
|
||||
}
|
||||
// The pane's own switch shows the same state, whichever of the two was clicked.
|
||||
sync_panel_switches(*app);
|
||||
}
|
||||
|
||||
void on_cursors_reset(GtkButton*, gpointer user) {
|
||||
|
||||
+29
-3
@@ -85,6 +85,28 @@ gboolean exit_probe_step3(gpointer user) {
|
||||
a->dbg_switcher_w = gtk_widget_get_width(a->side_switcher);
|
||||
a->dbg_switcher_h = gtk_widget_get_height(a->side_switcher);
|
||||
}
|
||||
// The panes' own switches, and *both* directions of the sync: the header bar's trigger button is
|
||||
// flipped here and the pane's switch has to follow, then the cursors' switch is driven from the
|
||||
// pane and the button is what has to follow. Both are read back after the fact, so the two pairs in
|
||||
// the dump are the claim: they agree, having been moved from opposite ends.
|
||||
if (a->trigger_toggle != nullptr) {
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a->trigger_toggle), TRUE);
|
||||
}
|
||||
a->dbg_panel_switch_trigger =
|
||||
a->trigger_switch != nullptr && gtk_switch_get_active(GTK_SWITCH(a->trigger_switch)) ? 1 : 0;
|
||||
a->dbg_toolbar_switch_trigger =
|
||||
a->trigger_toggle != nullptr &&
|
||||
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(a->trigger_toggle))
|
||||
? 1 : 0;
|
||||
if (a->cursors_switch != nullptr) {
|
||||
gtk_switch_set_active(GTK_SWITCH(a->cursors_switch), TRUE);
|
||||
a->dbg_panel_switch_cursors = gtk_switch_get_active(GTK_SWITCH(a->cursors_switch)) ? 1 : 0;
|
||||
a->dbg_toolbar_switch_cursors =
|
||||
a->cursors_toggle != nullptr &&
|
||||
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(a->cursors_toggle))
|
||||
? 1 : 0;
|
||||
a->dbg_panel_drove_toolbar = a->dbg_toolbar_switch_cursors;
|
||||
}
|
||||
for (GtkWidget* pill = a->side_switcher != nullptr ? gtk_widget_get_first_child(a->side_switcher)
|
||||
: nullptr;
|
||||
pill != nullptr; pill = gtk_widget_get_next_sibling(pill)) {
|
||||
@@ -116,7 +138,7 @@ gboolean exit_probe_step3(gpointer user) {
|
||||
const int on_signals = page != nullptr && std::strcmp(page, "signals") == 0 ? 1 : 0;
|
||||
a->dbg_signal_page_current =
|
||||
on_signals && gtk_widget_get_first_child(a->signal_list) != nullptr ? 1 : 0;
|
||||
a->dbg_trigger_sensitive = on_signals && gtk_widget_get_sensitive(a->trigger_panel) ? 1 : 0;
|
||||
a->dbg_trigger_sensitive = on_signals && gtk_widget_get_sensitive(a->trigger_body) ? 1 : 0;
|
||||
// `gtk_widget_get_visible` on the sidebar's GtkBox is always 1: AdwOverlaySplitView hides its
|
||||
// sidebar child via `gtk_widget_set_child_visible()`, not `gtk_widget_set_visible()`. The useful
|
||||
// reading is whether the split view is currently showing the sidebar panel.
|
||||
@@ -2299,7 +2321,11 @@ gboolean export_probe(gpointer user) {
|
||||
gboolean exit_probe_step1(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
// Record the panel states before the toggles are exercised.
|
||||
a->dbg_trigger_sensitive_default = gtk_widget_get_sensitive(a->trigger_panel) ? 1 : 0;
|
||||
a->dbg_trigger_sensitive_default = gtk_widget_get_sensitive(a->trigger_body) ? 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)
|
||||
? 1 : 0;
|
||||
if (a->trigger_toggle != nullptr) {
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a->trigger_toggle), TRUE);
|
||||
}
|
||||
@@ -2342,7 +2368,7 @@ void cursors_probe_report(App& a, const char* what) {
|
||||
"[cursors-probe] %-14s mode=%-10s on=%d pane_sensitive=%d t0=%.6f t1=%.6f dt=%.6f "
|
||||
"T=[%s] lines=%d marks=%d [%s]\n",
|
||||
what, kModes[mode], a.rows->cursors_visible() ? 1 : 0,
|
||||
a.cursors_panel != nullptr && gtk_widget_get_sensitive(a.cursors_panel) ? 1 : 0,
|
||||
a.cursors_body != nullptr && gtk_widget_get_sensitive(a.cursors_body) ? 1 : 0,
|
||||
a.rows->cursor_a_x(), a.rows->cursor_b_x(),
|
||||
a.rows->cursor_b_x() - a.rows->cursor_a_x(), t_text.c_str(), n,
|
||||
a.rows->crossings_visible(), lines.c_str());
|
||||
|
||||
+31
-3
@@ -196,14 +196,18 @@ void on_trigger_toggled(GtkToggleButton* button, gpointer user) {
|
||||
app->model->set_signal_enabled(static_cast<std::size_t>(signal), true);
|
||||
}
|
||||
// Disabled, not hidden: a panel you can still see is a panel you can read, and the settings you
|
||||
// are about to switch back on must not vanish while you look for them.
|
||||
gtk_widget_set_sensitive(app->trigger_panel, on);
|
||||
// are about to switch back on must not vanish while you look for them. The *body* is what goes
|
||||
// insensitive and not the panel, because the panel's heading row carries the switch that turns
|
||||
// this back on -- and a control inside an insensitive parent cannot be clicked.
|
||||
gtk_widget_set_sensitive(app->trigger_body, on);
|
||||
// The event-history button is part of trigger mode and the pane it opens is *on demand*; leaving
|
||||
// trigger mode takes the button away and collapses the pane, so going back to streaming is not a
|
||||
// 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");
|
||||
// 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
|
||||
@@ -236,6 +240,8 @@ GtkWidget* build_trigger_panel(App& app) {
|
||||
GtkBuilder* ui = ui_load(app, "/org/udpscope/ui/trigger-panel.ui");
|
||||
GtkWidget* box = GTK_WIDGET(gtk_builder_get_object(ui, "trigger_panel"));
|
||||
if (box == nullptr) return gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
||||
// The *body* is what trigger-off disables; this panel's index into it is `trigger_body`.
|
||||
app.trigger_body = box;
|
||||
|
||||
app.trigger_signal_drop = gtk_drop_down_new(G_LIST_MODEL(gtk_string_list_new(nullptr)), nullptr);
|
||||
g_signal_connect(app.trigger_signal_drop, "notify::selected",
|
||||
@@ -422,7 +428,29 @@ GtkWidget* build_trigger_panel(App& app) {
|
||||
&app);
|
||||
}
|
||||
sync_trigger_rows(app);
|
||||
return box;
|
||||
|
||||
// The heading row, and the switch that is the *same* control as the header bar's button. Built here
|
||||
// rather than in the file because it must not go insensitive with the body: turning trigger mode
|
||||
// back on would otherwise mean finding the toolbar button that greyed the panel out.
|
||||
GtkWidget* head = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_widget_set_margin_top(head, 8);
|
||||
gtk_widget_set_margin_start(head, 8);
|
||||
gtk_widget_set_margin_end(head, 8);
|
||||
GtkWidget* title = gtk_label_new("Trigger");
|
||||
gtk_widget_set_halign(title, GTK_ALIGN_START);
|
||||
gtk_widget_set_hexpand(title, TRUE);
|
||||
gtk_box_append(GTK_BOX(head), title);
|
||||
app.trigger_switch = gtk_switch_new();
|
||||
gtk_widget_set_valign(app.trigger_switch, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_tooltip_text(app.trigger_switch,
|
||||
"trigger mode, the same switch the header bar carries");
|
||||
g_signal_connect(app.trigger_switch, "notify::active", G_CALLBACK(on_panel_switch_toggled), &app);
|
||||
gtk_box_append(GTK_BOX(head), app.trigger_switch);
|
||||
|
||||
GtkWidget* outer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_box_append(GTK_BOX(outer), head);
|
||||
gtk_box_append(GTK_BOX(outer), box);
|
||||
return outer;
|
||||
}
|
||||
|
||||
} // namespace ui_impl
|
||||
|
||||
Reference in new issue
Block a user