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

The panel's shape -- the title row with its add button, the one statistics switch
the whole panel shares, and the box the readings go into -- now lives in
src/ui/measures-panel.ui. The readings stay in code: each row is a measurement a
user placed, rebuilt whenever the set or the mode changes.

A faithful move, not a redesign: the same margins, spacing, labels, tooltips, flat
classes and insensitive Reset button, the same two lambdas on the same two signals.

The frame is read back through the dump line, which grew the two fields that can
disagree with the code that drives them -- `measures pane: n=4 revealed=0
marks_shown=6 pane_rows=4 pane_w=0 pane_h=0 switch_on=1 reset_sensitive=1`. In that
reading pane_rows=4 is the four readout rows having landed in the frame's
`measures_list`, and switch_on/reset_sensitive are the switch widget and the Reset
button following measure_stats_on, which the probe set through the same signal a user
would. `check_ui` type-checks the file (8 ok, 0 failed) and the build stays at four
warnings.
This commit is contained in:
Martino Ferrari committed 2026-10-12 15:35:39 +02:00
1 parent 0a64e9e854
commit d344361165
3 files changed
+97 -34

No files matched your search

+24 -34
View File
@@ -2280,36 +2280,30 @@ void rebuild_math_pane(App& app) {
} }
GtkWidget* build_measures_panel(App& app) { GtkWidget* build_measures_panel(App& app) {
GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4); // The panel's frame is a UI file; the readings are not, because each row is one measurement and they are
gtk_widget_set_margin_top(box, 6); // rebuilt whenever the set or the mode changes. `make test-ui` checks the file's types against the GIR;
gtk_widget_set_margin_bottom(box, 6); // the ids below are the only thing that keeps the code and the file agreeing on the names.
gtk_widget_set_margin_start(box, 6); GtkBuilder* ui = ui_load(app, "/org/udpscope/ui/measures-panel.ui");
gtk_widget_set_margin_end(box, 6); GtkWidget* box = GTK_WIDGET(gtk_builder_get_object(ui, "measures_panel"));
GtkWidget* add = GTK_WIDGET(gtk_builder_get_object(ui, "measures_add"));
app.measures_stats_check = GTK_WIDGET(gtk_builder_get_object(ui, "measures_stats_check"));
app.measures_stats_reset = GTK_WIDGET(gtk_builder_get_object(ui, "measures_stats_reset"));
app.measures_list = GTK_WIDGET(gtk_builder_get_object(ui, "measures_list"));
if (box == nullptr || add == nullptr || app.measures_stats_check == nullptr ||
app.measures_stats_reset == nullptr || app.measures_list == nullptr) {
// A missing id is a mistake in the `.ui`. The pane comes up empty rather than taking the window with
// it, and `[udpscope] measures pane:` reads differently instead of the application failing to start.
return box != nullptr ? box : gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
}
GtkWidget* head = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
GtkWidget* title = gtk_label_new("Measurements");
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 measurement");
g_signal_connect(add, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) { g_signal_connect(add, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
open_measure_dialog(*static_cast<App*>(user), -1); open_measure_dialog(*static_cast<App*>(user), -1);
}), }),
&app); &app);
gtk_box_append(GTK_BOX(head), add);
gtk_box_append(GTK_BOX(box), head);
// One toggle for the whole pane, not one per measurement: statistics are either what the // One toggle for the whole pane, not one per measurement: statistics are either what the
// user is looking at or they are not, and a per-row switch is a column of chrome paid for // user is looking at or they are not, and a per-row switch is a column of chrome paid for
// on every row to answer a question asked once. // on every row to answer a question asked once.
GtkWidget* stats_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
app.measures_stats_check = gtk_check_button_new_with_label("Statistics");
gtk_widget_set_hexpand(app.measures_stats_check, true);
gtk_widget_set_tooltip_text(app.measures_stats_check,
"show how each reading has varied: passes, mean, standard "
"deviation and extremes, under the reading itself");
g_signal_connect(app.measures_stats_check, "toggled", g_signal_connect(app.measures_stats_check, "toggled",
G_CALLBACK(+[](GtkCheckButton* b, gpointer user) { G_CALLBACK(+[](GtkCheckButton* b, gpointer user) {
App* a = static_cast<App*>(user); App* a = static_cast<App*>(user);
@@ -2328,13 +2322,7 @@ GtkWidget* build_measures_panel(App& app) {
schedule_layout_save(*a); schedule_layout_save(*a);
}), }),
&app); &app);
gtk_box_append(GTK_BOX(stats_row), app.measures_stats_check);
app.measures_stats_reset = gtk_button_new_with_label("Reset");
gtk_widget_add_css_class(app.measures_stats_reset, "flat");
gtk_widget_set_tooltip_text(app.measures_stats_reset,
"start every measurement's statistics again from the next pass");
gtk_widget_set_sensitive(app.measures_stats_reset, false);
g_signal_connect(app.measures_stats_reset, "clicked", g_signal_connect(app.measures_stats_reset, "clicked",
G_CALLBACK(+[](GtkButton*, gpointer user) { G_CALLBACK(+[](GtkButton*, gpointer user) {
App* a = static_cast<App*>(user); App* a = static_cast<App*>(user);
@@ -2342,11 +2330,6 @@ GtkWidget* build_measures_panel(App& app) {
update_measures(*a, /*force=*/true, /*accumulate=*/true); update_measures(*a, /*force=*/true, /*accumulate=*/true);
}), }),
&app); &app);
gtk_box_append(GTK_BOX(stats_row), app.measures_stats_reset);
gtk_box_append(GTK_BOX(box), stats_row);
app.measures_list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
gtk_box_append(GTK_BOX(box), app.measures_list);
return box; return box;
} }
@@ -5775,10 +5758,17 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
{ {
std::lock_guard<std::mutex> lock(model.mutex()); std::lock_guard<std::mutex> lock(model.mutex());
std::printf("[udpscope] measures pane: n=%zu revealed=%d marks_shown=%d pane_rows=%zu " std::printf("[udpscope] measures pane: n=%zu revealed=%d marks_shown=%d pane_rows=%zu "
"pane_w=%d pane_h=%d\n", "pane_w=%d pane_h=%d switch_on=%d reset_sensitive=%d\n",
app.measures.size(), app.measures_revealed ? 1 : 0, app.measures.size(), app.measures_revealed ? 1 : 0,
rows.measure_marks_placed(), app.measure_labels.size(), rows.measure_marks_placed(), app.measure_labels.size(),
app.dbg_measures_pane_w, app.dbg_measures_pane_h); app.dbg_measures_pane_w, app.dbg_measures_pane_h,
// The two widgets the panel's *frame* is read by: the code drives `measure_stats_on`,
// so the switch and the Reset button are where a `.ui` that lost them -- or lost the
// wiring to them -- shows up. `switch_on` disagreeing with `[stats-probe] on=` is that.
app.measures_stats_check != nullptr &&
gtk_check_button_get_active(GTK_CHECK_BUTTON(app.measures_stats_check)) ? 1 : 0,
app.measures_stats_reset != nullptr &&
gtk_widget_get_sensitive(app.measures_stats_reset) ? 1 : 0);
} }
// `locked` is re-reported here rather than only in the readouts line: this task added a // `locked` is re-reported here rather than only in the readouts line: this task added a
// whole new cursor pool, and a pool created selectable is exactly the regression that a // whole new cursor pool, and a pool created selectable is exactly the regression that a
+72
View File
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- SPDX-License-Identifier: MIT -->
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
<!--
The measurements panel: a title row with the button that adds one, the one statistics switch the whole
panel shares, and the box the readings go into.
The readings are not here. Each one is a measurement the user placed — its name, its markers and its
readout row — so the rows are built and rebuilt in code, and this file holds the frame they land in
(`measures_list`) plus the three widgets whose state outlives them.
One statistics switch for the panel rather than one per reading: statistics are either what a user is
looking at or they are not, and a per-row switch is a column of chrome paid for on every row to answer a
question asked once.
-->
<interface>
<object class="GtkBox" id="measures_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">
<property name="orientation">horizontal</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel" id="measures_title">
<property name="label" translatable="yes">Measurements</property>
<property name="halign">start</property>
<property name="hexpand">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="measures_add">
<property name="icon-name">list-add-symbolic</property>
<property name="css-classes">flat</property>
<property name="tooltip-text" translatable="yes">add a measurement</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">horizontal</property>
<property name="spacing">8</property>
<child>
<object class="GtkCheckButton" id="measures_stats_check">
<property name="label" translatable="yes">Statistics</property>
<property name="hexpand">1</property>
<property name="tooltip-text" translatable="yes">show how each reading has varied: passes, mean, standard deviation and extremes, under the reading itself</property>
</object>
</child>
<child>
<object class="GtkButton" id="measures_stats_reset">
<property name="label" translatable="yes">Reset</property>
<property name="css-classes">flat</property>
<property name="sensitive">0</property>
<property name="tooltip-text" translatable="yes">start every measurement's statistics again from the next pass</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="measures_list">
<property name="orientation">vertical</property>
<property name="spacing">2</property>
</object>
</child>
</object>
</interface>
+1
View File
@@ -16,5 +16,6 @@
<file preprocess="xml-stripblanks">source-chooser.ui</file> <file preprocess="xml-stripblanks">source-chooser.ui</file>
<file preprocess="xml-stripblanks">main-window.ui</file> <file preprocess="xml-stripblanks">main-window.ui</file>
<file preprocess="xml-stripblanks">cursors-panel.ui</file> <file preprocess="xml-stripblanks">cursors-panel.ui</file>
<file preprocess="xml-stripblanks">measures-panel.ui</file>
</gresource> </gresource>
</gresources> </gresources>