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

src/ui/signals-panel.ui holds the panel's shape: the box and its margins, the heading,
the hint that says what a signal is for, and the list. The rows stay in code -- one per
signal, carrying that signal's switch, colour, calibration and drop targets, rebuilt
whenever the stream's signals change -- so the file holds the frame, which is the shape
the plan gives this slice.

The list's width-only size request moved with it, comment and all: a height floor there
made the list occupy 120 px whatever it held, which hid the compact rows' height behind
it.

Read back by a new dump line, `signals panel: labels=2 list=1`, where a lost id reads as
`labels=1` or `list=0` instead of a panel quietly missing its hint; `panels: … row_h=26`
still reads the list's geometry and the height a row takes. check_ui is at 10 ok.
This commit is contained in:
Martino Ferrari committed 2026-10-12 15:50:52 +02:00
1 parent 1ccc70c5ac
commit c37fbdde21
6 files changed
+88 -22

No files matched your search

+7
View File
@@ -309,3 +309,10 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
Related: `gtk_expander_set_child()` asserts that its child has no parent yet, so a block the `.ui` has
already put in the panel cannot then be handed to an expander -- the file's `child` property is the right
way, and it works.
- **`--` is not allowed inside an XML comment, and a `.ui` comment is XML.** `<!-- ... -- ... -->` is a parse
error: `xml-stripblanks` fails the build, and `make test-ui` prints `FAIL <file>` with **no** message under
it, because blueprint-compiler's decompiler aborts on the malformed XML before it can report anything --
the bare `FAIL` line is that mistake and nothing else. The house style for a dash in prose is the em dash,
which is why no other `.ui` in `src/ui/` contains a double hyphen. It has cost two slices a debugging
round for one character.
+11
View File
@@ -1179,3 +1179,14 @@ The search walk starts at `gtk_expander_get_child()`, not at the panel: an expan
widget-parent chain at all, so walking up from a search button reaches no ancestor of the panel (see
`invariants-gtk-and-chrome.md`). `[defaults] trigger:` prints the same panel's seven spins in tree order, so
the two lines together read what the file gave and what the code put in it.
## `signals panel:`
```
[udpscope] signals panel: labels=2 list=1
```
The signals panel's frame read from the window: the heading and the hint the `.ui` gives it, and the list
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.
+14
View File
@@ -5409,6 +5409,20 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
static_cast<unsigned long long>(model.trigger_latches().load()),
static_cast<unsigned long long>(app.dbg_latches_at_disarm));
std::printf("[udpscope] window control: '%s'\n", app.dbg_window_text.c_str());
{
// The signals panel's frame, read from the window rather than from the `.ui`: the two labels and the
// list the file gives it. A `.ui` that lost one of them reads here -- `labels=1` or `list=0` -- rather
// than showing as a panel quietly missing its hint.
GtkWidget* panel = app.signal_list != nullptr ? gtk_widget_get_parent(app.signal_list) : nullptr;
int labels = 0;
int lists = 0;
for (GtkWidget* c = panel != nullptr ? gtk_widget_get_first_child(panel) : nullptr; c != nullptr;
c = gtk_widget_get_next_sibling(c)) {
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);
}
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",
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- SPDX-License-Identifier: MIT -->
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
<!--
The signals panel: a heading, the hint that says what a signal is for, and the list they go in.
The rows are not here. There is one per signal in the stream, it carries the switch that decides whether
that signal is acquired, its colour, its calibration and its drop targets, and the whole list is rebuilt
whenever the stream's signals change — so the file holds the frame and the code holds the rows.
The list's size request is **width only**, deliberately. A height floor here made the list occupy 120 px
whatever it held, which hid the compact rows' height behind it and wasted the space on an empty stream;
the scroller this panel sits in already caps how tall it can grow.
-->
<interface>
<object class="GtkBox" id="signal_panel">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="margin-top">8</property>
<property name="margin-bottom">8</property>
<property name="margin-start">8</property>
<property name="margin-end">8</property>
<child>
<object class="GtkLabel" id="signals_heading">
<property name="label" translatable="yes">Signals</property>
<property name="halign">start</property>
</object>
</child>
<child>
<object class="GtkLabel" id="signals_hint">
<property name="label" translatable="yes">drag a signal onto a plot</property>
<property name="halign">start</property>
<property name="css-classes">dim-label</property>
</object>
</child>
<child>
<object class="GtkListBox" id="signal_list">
<property name="selection-mode">none</property>
<property name="width-request">260</property>
</object>
</child>
</object>
</interface>
+1
View File
@@ -18,5 +18,6 @@
<file preprocess="xml-stripblanks">cursors-panel.ui</file>
<file preprocess="xml-stripblanks">measures-panel.ui</file>
<file preprocess="xml-stripblanks">trigger-panel.ui</file>
<file preprocess="xml-stripblanks">signals-panel.ui</file>
</gresource>
</gresources>
+12 -22
View File
@@ -382,28 +382,18 @@ void rebuild_signal_panels(App& app) {
}
GtkWidget* build_signal_panel(App& app) {
GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
gtk_widget_set_margin_top(box, 8);
gtk_widget_set_margin_bottom(box, 8);
gtk_widget_set_margin_start(box, 8);
gtk_widget_set_margin_end(box, 8);
GtkWidget* heading = gtk_label_new("Signals");
gtk_widget_set_halign(heading, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(box), heading);
GtkWidget* hint = gtk_label_new("drag a signal onto a plot");
gtk_widget_set_halign(hint, GTK_ALIGN_START);
gtk_widget_add_css_class(hint, "dim-label");
gtk_box_append(GTK_BOX(box), hint);
app.signal_list = gtk_list_box_new();
gtk_list_box_set_selection_mode(GTK_LIST_BOX(app.signal_list), GTK_SELECTION_NONE);
// Width only. A height floor here made the list occupy 120 px whatever it held,
// which hid the compact rows' height behind it and wasted the space on an empty
// stream; the scroller above it already caps how tall it can grow.
gtk_widget_set_size_request(app.signal_list, 260, -1);
gtk_box_append(GTK_BOX(box), app.signal_list);
// The panel's frame -- the heading, the hint under it, and the list -- is a UI file. The rows are not:
// there is one per signal, carrying that signal's switch, colour, calibration and drop targets, and the
// whole list is rebuilt whenever the stream's signals change. `make test-ui` checks the file's types
// against the GIR; the id below is what keeps the code and the file agreeing on the name.
GtkBuilder* ui = ui_load(app, "/org/udpscope/ui/signals-panel.ui");
GtkWidget* box = GTK_WIDGET(gtk_builder_get_object(ui, "signal_panel"));
app.signal_list = GTK_WIDGET(gtk_builder_get_object(ui, "signal_list"));
if (box == nullptr || app.signal_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] signals panel:` reads the difference.
return box != nullptr ? box : gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
}
return box;
}