feat: the preferences are a UI file, applying as they change

src/ui/preferences.ui is an AdwPreferencesDialog whose rows are libadwaita's own -- AdwEntryRow,
AdwSpinRow, AdwSwitchRow, AdwComboRow, AdwButtonRow -- instead of a label and a bare GtkEntry in a
GtkBox. There is no Apply button: a row applies as it changes, which is what those widgets are
designed around, and [prefs-probe] reads the settings file back after every change.

The folder chooser is cancellable now, so a picker still open when the dialog closes cannot write
into a row that is gone; the cancellable is also how the probe takes the cancel path without a
mouse.

Two traps cost a run each and are recorded in docs/claude/invariants-gtk-and-chrome.md: an
AdwDialog is not a GtkWindow (it cannot parent a GtkFileDialog, and in floating mode it adds a
native surface to gtk_window_get_toplevels() that is neither a widget nor a window), and a .ui
that is not listed in the GResource is a Gtk-ERROR at runtime rather than a build failure -- which
is exactly what the probe opening every window is for.
This commit is contained in:
Martino Ferrari committed 2026-10-11 15:30:10 +02:00
1 parent 0e256764d5
commit 4c56172005
11 files changed
+403 -247

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- SPDX-License-Identifier: MIT -->
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
<!--
The preferences, as an AdwPreferencesDialog: pages of groups of rows, which is what every GNOME
application shows and what libadwaita's own widgets are for. The rows are AdwEntryRow, AdwSpinRow,
AdwSwitchRow, AdwComboRow and AdwButtonRow rather than a label and a bare GtkEntry in a GtkBox, so
they behave the way a reader expects — each one is a whole row, with its own title, an optional
subtitle underneath, and the editing widget on the right where a finger expects it.
**There is no Apply button, deliberately.** A preferences row applies as it changes, which is the
practice these widgets are designed around and why they carry an `apply` of their own (AdwEntryRow's
show-apply-button is off here: the write is a few bytes to one file, so a keystroke is not worth
batching). The two settings that cannot take effect immediately — the cache directory and relative
time, both of which are planned before any source is known — say so in their subtitles instead of
being held back by a button.
Nothing here carries a value: `ui_prefs.cpp` fills the rows from `Settings` when the dialog opens,
and writes it back on every change. This file is the shape, that is the state.
-->
<interface>
<object class="AdwPreferencesDialog" id="preferences">
<property name="title" translatable="yes">Preferences</property>
<property name="search-enabled">0</property>
<child>
<object class="AdwPreferencesPage">
<property name="title" translatable="yes">Event cache</property>
<property name="icon-name">udpscope-events-symbolic</property>
<property name="description" translatable="yes">Latches are written here as they happen; whether anything is written at all is the trigger panel's own switch.</property>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwEntryRow" id="dir_row">
<property name="title" translatable="yes">_Directory</property>
<property name="tooltip-text" translatable="yes">One directory in which every source gets a folder of its own, never one directory between two sources</property>
</object>
</child>
<child>
<object class="AdwSpinRow" id="keep_row">
<property name="title" translatable="yes">Keep _events</property>
<property name="subtitle" translatable="yes">Per source; 0 keeps every event, which is a decision about the disk</property>
<property name="adjustment">
<object class="GtkAdjustment">
<property name="lower">0</property>
<property name="upper">100000</property>
<property name="step-increment">1</property>
<property name="value">32</property>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesPage">
<property name="title" translatable="yes">Plot</property>
<property name="icon-name">udpscope-measure-symbolic</property>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwSwitchRow" id="relative_row">
<property name="title" translatable="yes">_Relative time</property>
<property name="subtitle" translatable="yes">Take the plot's time from the sample rate and the packet counter rather than from the stream's own clock, so arrival and clock jitter cannot move a sample; absolute time is still kept for export. Read at start-up, because the buffers are planned before any source is known.</property>
</object>
</child>
<child>
<object class="AdwComboRow" id="theme_row">
<property name="title" translatable="yes">_Theme</property>
<property name="subtitle" translatable="yes">What the interface looks like; auto follows the system</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesPage">
<property name="title" translatable="yes">Reset</property>
<property name="icon-name">view-refresh-symbolic</property>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwButtonRow" id="reset_row">
<property name="title" translatable="yes">_Reset to defaults</property>
<property name="start-icon-name">view-refresh-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
+1
View File
@@ -10,5 +10,6 @@
<gresources>
<gresource prefix="/org/udpscope/ui">
<file preprocess="xml-stripblanks">shortcuts.ui</file>
<file preprocess="xml-stripblanks">preferences.ui</file>
</gresource>
</gresources>