ui: one channel list, and the math pane is gone

`rebuild_signal_list()` is handed `model.signals()`, and `append_math_signals_locked()` already
appends one synthetic entry per math channel -- "they go through the ordinary signal path on
purpose: a derived channel is a channel" -- so **every math channel was already a row of the
signal list**, with its tick, chip, name, unit and menu. The math pane listed the same channels a
second time, so this is mostly a deletion: `build_math_panel()`, `rebuild_math_pane()`,
`src/ui/math-panel.ui` and its resource entry, `App::math_list`, and the `math panel:` dump line.

What the one list gains:

- a non-selectable `MATH` divider before the first math row, carrying the `+` that creates a
  channel -- appended even when there are no math channels at all, because a `+` conditional on
  one existing could never create the first one;
- a math row's own menu: *Edit graph…*, *Default colour*, *Delete*. No Information and no
  Calibrate (calibration is applied at ingest, and a derived channel has no stream to describe),
  and no rename, because the node editor owns a channel's name and unit -- a second place to
  change them would be a second answer to the same question. An item dismisses the row's popover
  before opening or destroying anything from it;
- `App::math` mirrored from a math row's tick: `enable_locked()` mirrors it into the model, but
  `App::math` is the copy the session file is written from. The mirror sits *before* the
  `apply_math_channels()` in the `!on` path, which would otherwise push a stale `enabled = true`
  straight back into the model;
- a tick visible in a file view too: a derived channel is computed, and a file view has no
  acquisition to start, which is the other tick's question.

`rebuild_signal_list()` also publishes the list's own shape -- `rows`, `signals`, `math` -- and
the dump prints `divider=1` when `rows == signals + 1 + math`: the reading for a list that lost
its `+`.

Verified: `make test-ui` 14 files (was 15); `signals panel: labels=2 list=1 rows=9 signals=4
math=4 divider=1`; `[math-probe] 3 math channels`, every other line unchanged; no `math panel:`
line, and zero `Gtk-CRITICAL`.
This commit is contained in:
Martino Ferrari committed 2026-10-14 12:56:31 +02:00
1 parent 132041f79e
commit a493737ca1
6 files changed
+186 -190

No files matched your search

+13 -93
View File
@@ -2248,76 +2248,6 @@ void open_math_dialog(App& app, int edit) {
}
void rebuild_math_pane(App& app) {
if (app.math_list == nullptr) return;
while (GtkWidget* child = gtk_widget_get_first_child(app.math_list)) {
gtk_box_remove(GTK_BOX(app.math_list), child);
}
if (app.math.empty()) {
GtkWidget* none = gtk_label_new("— none —");
gtk_widget_add_css_class(none, "dim-label");
gtk_widget_set_halign(none, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(app.math_list), none);
return;
}
for (std::size_t i = 0; i < app.math.size(); ++i) {
const MathChannel& m = app.math[i];
GtkWidget* row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
GtkWidget* on = gtk_check_button_new();
gtk_check_button_set_active(GTK_CHECK_BUTTON(on), m.enabled);
gtk_widget_set_tooltip_text(on, "enable this math channel: off means it is not acquired, not\n"
"plotted and not measured, exactly like a signal's switch");
const std::size_t index = i;
g_signal_connect(on, "toggled", G_CALLBACK(+[](GtkToggleButton* b, gpointer user) {
App* a = static_cast<App*>(user);
const std::size_t k = GPOINTER_TO_SIZE(
g_object_get_data(G_OBJECT(b), "udpscope-math-index"));
if (k >= a->math.size()) return;
a->math[k].enabled = gtk_toggle_button_get_active(b);
apply_math_channels(*a);
}),
&app);
g_object_set_data(G_OBJECT(on), "udpscope-math-index", GSIZE_TO_POINTER(index));
gtk_box_append(GTK_BOX(row), on);
std::string text = m.name;
if (!m.unit.empty()) text += " [" + m.unit + "]";
GtkWidget* label = gtk_label_new(text.c_str());
gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
gtk_widget_set_hexpand(label, true);
gtk_widget_set_halign(label, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(row), label);
GtkWidget* edit = gtk_button_new_from_icon_name("document-edit-symbolic");
gtk_widget_add_css_class(edit, "flat");
gtk_widget_set_tooltip_text(edit, "edit this math channel's graph");
g_object_set_data(G_OBJECT(edit), "udpscope-math-index", GSIZE_TO_POINTER(index));
g_signal_connect(edit, "clicked", G_CALLBACK(+[](GtkButton* b, gpointer user) {
App* a = static_cast<App*>(user);
const std::size_t k = GPOINTER_TO_SIZE(
g_object_get_data(G_OBJECT(b), "udpscope-math-index"));
open_math_dialog(*a, static_cast<int>(k));
}),
&app);
gtk_box_append(GTK_BOX(row), edit);
GtkWidget* del = gtk_button_new_from_icon_name("window-close-symbolic");
gtk_widget_add_css_class(del, "flat");
gtk_widget_set_tooltip_text(del, "delete this math channel");
g_object_set_data(G_OBJECT(del), "udpscope-math-index", GSIZE_TO_POINTER(index));
g_signal_connect(del, "clicked", G_CALLBACK(+[](GtkButton* b, gpointer user) {
App* a = static_cast<App*>(user);
const std::size_t k = GPOINTER_TO_SIZE(
g_object_get_data(G_OBJECT(b), "udpscope-math-index"));
if (k < a->math.size()) a->math.erase(a->math.begin() + k);
apply_math_channels(*a);
}),
&app);
gtk_box_append(GTK_BOX(row), del);
gtk_box_append(GTK_BOX(app.math_list), row);
}
}
GtkWidget* build_measures_panel(App& app) {
// The panel's frame is a UI file; the readings are not, because each row is one measurement and they are
// rebuilt whenever the set or the mode changes. `make test-ui` checks the file's types against the GIR;
@@ -4724,10 +4654,6 @@ void on_activate(GtkApplication* application, gpointer user) {
app->trigger_panel = build_trigger_panel(*app);
gtk_box_append(GTK_BOX(signals_page), app->trigger_panel);
gtk_box_append(GTK_BOX(signals_page), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
// Still on the signals page until the list grows the `+` and the per-row menu that replace it.
gtk_box_append(GTK_BOX(signals_page), build_math_panel(*app));
gtk_box_append(GTK_BOX(signals_page), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
rebuild_math_pane(*app);
app->signal_panel = build_signal_panel(*app);
gtk_box_append(GTK_BOX(signals_page), app->signal_panel);
@@ -5376,6 +5302,9 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
if (const char* env = g_getenv("UDPSCOPE_PROBE_STATS_MS")) {
g_timeout_add(static_cast<guint>(std::atoi(env)), stats_probe, &app);
}
if (const char* env = g_getenv("UDPSCOPE_PROBE_SIDE_TAB_MS")) {
g_timeout_add(static_cast<guint>(std::atoi(env)), side_tab_probe, &app);
}
if (const char* env = g_getenv("UDPSCOPE_PROBE_SEARCH_MS")) {
g_timeout_add(static_cast<guint>(std::atoi(env)), search_probe, &app);
}
@@ -5606,25 +5535,16 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
if (GTK_IS_LABEL(c)) ++labels;
if (GTK_IS_LIST_BOX(c)) ++lists;
}
std::printf("[udpscope] signals panel: labels=%d list=%d\n", labels, lists);
}
{
// The math panel's frame, read from the window: the head row with the title and the add button, and the
// list. `buttons=0` is a `.ui` that lost the `+` -- otherwise a panel that cannot add anything, with
// nothing else complaining -- and the title is read out to show the file's own text arrived.
GtkWidget* panel = app.math_list != nullptr ? gtk_widget_get_parent(app.math_list) : nullptr;
GtkWidget* head = panel != nullptr ? gtk_widget_get_first_child(panel) : nullptr;
GtkWidget* title = head != nullptr ? gtk_widget_get_first_child(head) : nullptr;
int head_children = 0;
int buttons = 0;
for (GtkWidget* c = head != nullptr ? gtk_widget_get_first_child(head) : nullptr; c != nullptr;
c = gtk_widget_get_next_sibling(c)) {
++head_children;
if (GTK_IS_BUTTON(c)) ++buttons;
}
std::printf("[udpscope] math panel: head=%d buttons=%d list=%d title='%s'\n", head_children,
buttons, app.math_list != nullptr ? 1 : 0,
GTK_IS_LABEL(title) ? gtk_label_get_text(GTK_LABEL(title)) : "-");
// The list's shape, beside the frame: `rows` is what the list holds, `signals` the streamed
// ones, and the `MATH` divider plus the math channels account for the rest -- so
// `rows == signals + 1 + math` is the invariant, and `divider=0` is a list that lost its `+`.
std::printf("[udpscope] signals panel: labels=%d list=%d rows=%d signals=%zu math=%zu "
"divider=%d\n",
labels, lists, app.dbg_signal_rows, app.dbg_signal_plain, app.math.size(),
app.dbg_signal_rows ==
static_cast<int>(app.dbg_signal_plain + 1 + app.math.size())
? 1
: 0);
}
{
// The events pane's frame, read from the window: the four widgets the `.ui` gives it, and the list