feat: the cursors panel is a UI file

src/ui/cursors-panel.ui holds the panel's shape -- the header with the mode dropdown and the reset
button, and the grid the two cursor times are read in. What is not in it is the grid's contents,
which follow the cursor mode and the rows and are rebuilt whenever either changes.

Panels need a builder that outlives them, unlike the dialogs, which are rebuilt on every opening: a
widget belongs to the builder that built it, so App::ui_builders holds each panel's for the life of
the window. ui_load() is that, in ui_widgets.cpp.

make test-ui also checks that udpscope-ui.gresource.xml lists every .ui file now. That was the third
trap of this refactor -- a file on disk but not in the resource is a Gtk-ERROR at load, which aborts
-- and it is a compile-time mistake that nothing else in the build could see. Verified by unlisting
the new file and watching the check fail, then restoring it.

[cursors-probe] reads as it did: all four modes, pane=1, the timings and the drag cases.
This commit is contained in:
Martino Ferrari committed 2026-10-12 10:47:34 +02:00
1 parent 7a982da6c4
commit ed82abd405
8 files changed
+118 -25

No files matched your search

+21 -24
View File
@@ -78,41 +78,38 @@ void update_cursors_pane(App& app) {
}
GtkWidget* build_cursors_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);
// The panel's shape is a UI file; the *contents* of its grid are not, because they follow the cursor
// mode and the rows and are rebuilt whenever either changes.
GtkBuilder* ui = ui_load(app, "/org/udpscope/ui/cursors-panel.ui");
GtkWidget* box = GTK_WIDGET(gtk_builder_get_object(ui, "cursors_panel"));
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"));
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
// with it, and `[cursors]` reads differently rather than 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("Cursors");
gtk_widget_set_halign(title, GTK_ALIGN_START);
gtk_widget_set_hexpand(title, true);
gtk_box_append(GTK_BOX(head), title);
// The mode names are the dropdown's model, and they are a C array, so they are set here.
const char* names[5] = {kCursorModeNames[0], kCursorModeNames[1], kCursorModeNames[2],
kCursorModeNames[3], nullptr};
app.cursors_mode_drop = gtk_drop_down_new_from_strings(names);
gtk_drop_down_set_model(GTK_DROP_DOWN(app.cursors_mode_drop),
G_LIST_MODEL(gtk_string_list_new(names)));
gtk_widget_set_tooltip_text(app.cursors_mode_drop,
"vertical: two times, shared by every row\n"
"horizontal: two rulers per row\n"
"both: the two together\n"
"horizontal: one value per row, at the two times\n"
"both: the two times and one value per row\n"
"mix: the two times, read off each trace where they cross");
g_signal_connect(app.cursors_mode_drop, "notify::selected",
G_CALLBACK(on_cursor_mode_changed), &app);
gtk_box_append(GTK_BOX(head), app.cursors_mode_drop);
// Reset lives here, beside the mode it resets, rather than in the overflow menu: it acts on this
// pane's instruments and is only visible while they are on, both of which are this pane's facts.
app.cursors_reset = gtk_button_new_from_icon_name("edit-clear-symbolic");
gtk_widget_add_css_class(app.cursors_reset, "flat");
// Reset lives beside the mode it resets rather than in the overflow menu: it acts on this pane's
// instruments and is only visible while they are on, both of which are this pane's facts.
gtk_widget_set_tooltip_text(app.cursors_reset, "re-place the cursors at the window's edges");
g_signal_connect(app.cursors_reset, "clicked", G_CALLBACK(on_cursors_reset), &app);
gtk_box_append(GTK_BOX(head), app.cursors_reset);
gtk_box_append(GTK_BOX(box), head);
app.cursors_body = gtk_grid_new();
gtk_grid_set_row_spacing(GTK_GRID(app.cursors_body), 2);
gtk_grid_set_column_spacing(GTK_GRID(app.cursors_body), 4);
gtk_box_append(GTK_BOX(box), app.cursors_body);
return box;
}