diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md
index 4914dd4..27de394 100644
--- a/docs/claude/probe-readings.md
+++ b/docs/claude/probe-readings.md
@@ -1190,3 +1190,15 @@ The signals panel's frame read from the window: the heading and the hint the `.u
the rows go in. `labels=1` is a `.ui` that lost one of the two, `list=0` one that lost the list -- both of
which otherwise show as a panel quietly missing a piece. `panels: … signals=WxH row_h=26` is the other half:
the list's own geometry and the height a row really takes, which is what the compact-row CSS rule is for.
+
+## `math panel:`
+
+```
+[udpscope] math panel: head=2 buttons=1 list=1 title='Math channels'
+```
+
+The math panel's frame read from the window: the head row the `.ui` gives it (the title and the add
+button), and the list the channel rows go in. `buttons=0` is a `.ui` that lost the `+` -- a panel that
+cannot add a channel, with nothing else complaining -- and `title=` is the file's own text read back, so a
+`.ui` that lost the label shows as `title='-'`. `[math-probe]` is the other half of the reading: it drives
+the add and the editor through the same code a user does.
diff --git a/src/ui.cpp b/src/ui.cpp
index 938c412..eb525da 100644
--- a/src/ui.cpp
+++ b/src/ui.cpp
@@ -5423,6 +5423,24 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
}
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)) : "-");
+ }
std::printf("[udpscope] panels: side=%dx%d (visible=%d) signals=%dx%d row_h=%d "
"(revealed=%d) trigger=%dx%d (revealed=%d) toggles=%d cursors=%d window=%.3fs "
"window_cap=%.1fs\n",
diff --git a/src/ui/math-panel.ui b/src/ui/math-panel.ui
new file mode 100644
index 0000000..9738111
--- /dev/null
+++ b/src/ui/math-panel.ui
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
diff --git a/src/ui/udpscope-ui.gresource.xml b/src/ui/udpscope-ui.gresource.xml
index f40c8e0..b55de40 100644
--- a/src/ui/udpscope-ui.gresource.xml
+++ b/src/ui/udpscope-ui.gresource.xml
@@ -17,6 +17,7 @@
main-window.uicursors-panel.uimeasures-panel.ui
+ math-panel.uitrigger-panel.uisignals-panel.ui
diff --git a/src/ui_math.cpp b/src/ui_math.cpp
index 87d9cbb..02e7422 100644
--- a/src/ui_math.cpp
+++ b/src/ui_math.cpp
@@ -1033,29 +1033,22 @@ GtkWidget* math_row(const char* label, GtkWidget* widget, GtkWidget** row_out) {
}
GtkWidget* build_math_panel(App& app) {
- GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
- gtk_widget_set_margin_top(box, 6);
- gtk_widget_set_margin_bottom(box, 6);
- gtk_widget_set_margin_start(box, 6);
- gtk_widget_set_margin_end(box, 6);
-
- GtkWidget* head = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
- GtkWidget* title = gtk_label_new("Math channels");
- gtk_widget_set_halign(title, GTK_ALIGN_START);
- gtk_widget_set_hexpand(title, true);
- gtk_box_append(GTK_BOX(head), title);
- GtkWidget* add = gtk_button_new_from_icon_name("list-add-symbolic");
- gtk_widget_add_css_class(add, "flat");
- gtk_widget_set_tooltip_text(add, "add a math channel");
+ // The panel's frame -- the heading row with its add button, and the list -- is a UI file; the rows are
+ // not, because each one is a channel and the whole list is rebuilt whenever the set changes. `make test-ui`
+ // checks the file's types against the GIR; the ids below are what keeps the code and the file agreeing.
+ GtkBuilder* ui = ui_load(app, "/org/udpscope/ui/math-panel.ui");
+ GtkWidget* box = GTK_WIDGET(gtk_builder_get_object(ui, "math_panel"));
+ GtkWidget* add = GTK_WIDGET(gtk_builder_get_object(ui, "math_add"));
+ app.math_list = GTK_WIDGET(gtk_builder_get_object(ui, "math_list"));
+ if (box == nullptr || add == nullptr || app.math_list == nullptr) {
+ // A missing id is a mistake in the `.ui`: the panel comes up empty rather than taking the window with
+ // it, and `[udpscope] math panel:` reads the difference.
+ return box != nullptr ? box : gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
+ }
g_signal_connect(add, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
add_math_channel(*static_cast(user));
}),
&app);
- gtk_box_append(GTK_BOX(head), add);
- gtk_box_append(GTK_BOX(box), head);
-
- app.math_list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
- gtk_box_append(GTK_BOX(box), app.math_list);
return box;
}