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:
1 parent
0e256764d5
commit
4c56172005
11 files changed
+403
-247
No files matched your search
+19
-29
@@ -1633,17 +1633,18 @@ gboolean prefs_probe(gpointer user) {
|
||||
App& a = *static_cast<App*>(user);
|
||||
open_preferences(a);
|
||||
// `dir` is the directory the dialog is offering and `store` is the one the cache is really using:
|
||||
// `store` must be `dir` plus the source's folder name, which is what says the field shows the path in
|
||||
// force rather than a plausible-looking string.
|
||||
// `store` must be `dir` plus the source's folder name, which is what says the row shows the path in
|
||||
// force rather than a plausible-looking string. `rows=1` is the dialog's rows having resolved from
|
||||
// the `.ui` -- the one mistake neither the build nor `GtkBuilder` sees is a missing id.
|
||||
const char* shown =
|
||||
a.prefs.dir_entry != nullptr ? gtk_editable_get_text(GTK_EDITABLE(a.prefs.dir_entry)) : "-";
|
||||
std::fprintf(stderr, "[prefs-probe] opened=%d apply=%d dir=%s store=%s\n",
|
||||
a.prefs.window != nullptr ? 1 : 0, a.prefs.apply != nullptr ? 1 : 0,
|
||||
a.prefs.dir_entry != nullptr ? shown : "-",
|
||||
a.prefs.dir_row != nullptr ? gtk_editable_get_text(GTK_EDITABLE(a.prefs.dir_row)) : "-";
|
||||
std::fprintf(stderr, "[prefs-probe] opened=%d rows=%d dir=%s store=%s\n",
|
||||
a.prefs.dialog != nullptr ? 1 : 0, a.prefs.dir_row != nullptr ? 1 : 0,
|
||||
a.prefs.dir_row != nullptr ? shown : "-",
|
||||
a.event_store != nullptr ? a.event_store->dir().c_str() : "-");
|
||||
g_timeout_add(400, +[](gpointer u) -> gboolean {
|
||||
App& app = *static_cast<App*>(u);
|
||||
prefs_probe_fill_and_apply(app.prefs);
|
||||
prefs_probe_change(app.prefs);
|
||||
const Settings back = load_settings();
|
||||
std::fprintf(stderr, "[prefs-probe] dir=%s keep=%d rel=%d theme=%s live=%d\n",
|
||||
back.event_cache_dir.c_str(), back.event_cache_max, back.relative_time ? 1 : 0,
|
||||
@@ -1652,8 +1653,8 @@ gboolean prefs_probe(gpointer user) {
|
||||
// And typing the default back must not *pin* it: the file keeps "unset", so the default still
|
||||
// follows whatever cache directory the next run has. An empty value here is the whole point --
|
||||
// a path written into the file is a user's choice, and this one was not a choice.
|
||||
gtk_editable_set_text(GTK_EDITABLE(app.prefs.dir_entry), default_events_dir().c_str());
|
||||
g_signal_emit_by_name(app.prefs.apply, "clicked");
|
||||
// Setting the row *is* applying it: there is no Apply button to press.
|
||||
gtk_editable_set_text(GTK_EDITABLE(app.prefs.dir_row), default_events_dir().c_str());
|
||||
std::fprintf(stderr, "[prefs-probe] default restored: file='%s'\n",
|
||||
load_settings().event_cache_dir.c_str());
|
||||
// The folder chooser, opened from its button and then *cancelled from outside*: a callback that
|
||||
@@ -1665,31 +1666,20 @@ gboolean prefs_probe(gpointer user) {
|
||||
g_signal_emit_by_name(app.prefs.browse, "clicked");
|
||||
g_timeout_add(600, +[](gpointer u) -> gboolean {
|
||||
App& app = *static_cast<App*>(u);
|
||||
GListModel* tops = gtk_window_get_toplevels();
|
||||
const guint n = g_list_model_get_n_items(tops);
|
||||
guint extra = 0;
|
||||
for (guint i = 0; i < n; ++i) {
|
||||
GtkWidget* w = GTK_WIDGET(g_list_model_get_item(tops, i));
|
||||
if (w != app.window && w != app.prefs.window) {
|
||||
++extra;
|
||||
gtk_window_close(GTK_WINDOW(w));
|
||||
}
|
||||
g_object_unref(w);
|
||||
}
|
||||
// The button is an icon and nothing else, so "the theme has this icon" is the difference
|
||||
// between a control and a blank square: a name the icon theme does not know draws a
|
||||
// missing-image placeholder and says nothing anywhere else.
|
||||
std::fprintf(stderr, "[prefs-probe] browse: toplevels=%u extra=%u icon_ok=%d field='%s'\n", n,
|
||||
extra,
|
||||
// Cancel through the chooser's own cancellable: a user's Cancel button lands in the same
|
||||
// callback with the same null answer, and it is the *cancel* path a picker invites -- a
|
||||
// callback after the thing it belongs to is gone.
|
||||
if (app.prefs.browse_cancel != nullptr) g_cancellable_cancel(app.prefs.browse_cancel);
|
||||
std::fprintf(stderr, "[prefs-probe] browse: cancelled=%d icon_ok=%d field='%s'\n",
|
||||
app.prefs.browse_cancel != nullptr ? 1 : 0,
|
||||
app.prefs.browse != nullptr &&
|
||||
gtk_icon_theme_has_icon(
|
||||
gtk_icon_theme_get_for_display(
|
||||
gtk_widget_get_display(app.prefs.browse)),
|
||||
gtk_icon_theme_get_for_display(gtk_widget_get_display(app.prefs.browse)),
|
||||
"folder-open-symbolic")
|
||||
? 1
|
||||
: 0,
|
||||
app.prefs.dir_entry != nullptr
|
||||
? gtk_editable_get_text(GTK_EDITABLE(app.prefs.dir_entry))
|
||||
app.prefs.dir_row != nullptr
|
||||
? gtk_editable_get_text(GTK_EDITABLE(app.prefs.dir_row))
|
||||
: "-");
|
||||
g_timeout_add(600, +[](gpointer u2) -> gboolean {
|
||||
prefs_close(static_cast<App*>(u2)->prefs);
|
||||
|
||||
Reference in new issue
Block a user