ui: the math panel's frame is a .ui file

The first half of this plan row. src/ui/math-panel.ui holds the panel: the box and its
margins, the heading row with the title and the add button, and the list the channel rows
go in. The rows stay in code -- each is a channel with its name, unit, expression, colour
and its own edit and remove buttons -- so the file holds the frame, as in the measurements
and signals panels beside it.

Read back by a dump line that also reads the file's own text:
`math panel: head=2 buttons=1 list=1 title='Math channels'`, where buttons=0 is a lost
`+` and title='-' a lost label. `[math-probe]` still drives the add and opens the editor,
so the rows landing in the file's list is read as well as the frame.

The other half is the node editor's chrome, which is where least of that file is template:
the canvas is a GtkDrawingArea that is drawn rather than declared, and the gestures, the
parameter fields and the palette are code. That is its own change.
This commit is contained in:
Martino Ferrari committed 2026-10-12 15:53:35 +02:00
1 parent c37fbdde21
commit 90a17eb186
5 files changed
+92 -19

No files matched your search

+18
View File
@@ -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",
+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- SPDX-License-Identifier: MIT -->
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
<!--
The math panel: a heading row with the button that adds a channel, and the list they go in.
The rows are not here. A math channel's row carries its name, its unit, its expression, its colour and its
own edit and remove buttons, all of which follow the channel — so the file holds the frame and the code
holds the rows, as in the measurements and signals panels beside it.
The heading takes the slack instead of the button, which is what keeps the `+` at the row's end however
long the title or the panel is.
-->
<interface>
<object class="GtkBox" id="math_panel">
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<property name="margin-top">6</property>
<property name="margin-bottom">6</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<child>
<object class="GtkBox" id="math_head">
<property name="orientation">horizontal</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel" id="math_title">
<property name="label" translatable="yes">Math channels</property>
<property name="halign">start</property>
<property name="hexpand">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="math_add">
<property name="icon-name">list-add-symbolic</property>
<property name="css-classes">flat</property>
<property name="tooltip-text" translatable="yes">add a math channel</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="math_list">
<property name="orientation">vertical</property>
<property name="spacing">2</property>
</object>
</child>
</object>
</interface>
+1
View File
@@ -17,6 +17,7 @@
<file preprocess="xml-stripblanks">main-window.ui</file>
<file preprocess="xml-stripblanks">cursors-panel.ui</file>
<file preprocess="xml-stripblanks">measures-panel.ui</file>
<file preprocess="xml-stripblanks">math-panel.ui</file>
<file preprocess="xml-stripblanks">trigger-panel.ui</file>
<file preprocess="xml-stripblanks">signals-panel.ui</file>
</gresource>
+12 -19
View File
@@ -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<App*>(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;
}