feat: the About dialog on AdwAboutDialog, showing the app icon

AdwAboutDialog puts the application icon in its own header, which is what the
app now has one for, and adw_about_window_new() is deprecated in favour of it.
It is an AdwDialog and not a GtkWindow, so it clears on ::closed and closes with
adw_dialog_close() -- the same trap the shortcut overview documents, and both
dialogs now share one handler keyed by the pointer to clear.

Two gains while mapping the fields: system_information becomes debug_info, which
renders as a Troubleshooting page with a copy button, and the string behind it
becomes a std::string rather than a char[1024] that truncated a long settings
path in silence.

The probe now reads the icon name back out of the open dialog, so it is asserted
to carry the icon rather than merely to open.
This commit is contained in:
Martino Ferrari committed 2026-10-04 20:40:05 +02:00
1 parent 6deb53c458
commit 33eee0c8d0
3 files changed
+60 -37

No files matched your search

+41 -29
View File
@@ -13,13 +13,14 @@
namespace udpscope { namespace udpscope {
namespace { namespace {
/// Nulled by the `close-request` handler so a second open cannot present a dead widget. /// Nulled by each window's own close handler, so a second open cannot present a dead widget.
/// One pointer per window, file-scope because there is one application. /// One pointer per window, file-scope because there is one application.
GtkWidget* g_about = nullptr; ///
/// The help window is a `GtkWindow`. The other two are `AdwDialog`s, which are **not**
/// `GtkWindow`s: they have no `::close-request`, so they clear on `::closed` and close with
/// `adw_dialog_close()`.
GtkWidget* g_help = nullptr; GtkWidget* g_help = nullptr;
AdwDialog* g_about = nullptr;
/// The shortcut overview is an `AdwDialog`, not a window, so it has its own pointer and
/// its own clearing signal (`::closed` rather than `::close-request`).
AdwDialog* g_shortcuts = nullptr; AdwDialog* g_shortcuts = nullptr;
gboolean on_window_close(GtkWindow* window, gpointer slot) { gboolean on_window_close(GtkWindow* window, gpointer slot) {
@@ -28,10 +29,11 @@ gboolean on_window_close(GtkWindow* window, gpointer slot) {
return FALSE; // let the default handler destroy it return FALSE; // let the default handler destroy it
} }
void on_shortcuts_closed(AdwDialog* dialog, gpointer user) { /// Shared by both `AdwDialog`s: the pointer to clear is the `user_data`, so there is one
/// handler rather than one per dialog.
void on_dialog_closed(AdwDialog* dialog, gpointer slot) {
(void)dialog; (void)dialog;
(void)user; *static_cast<AdwDialog**>(slot) = nullptr;
g_shortcuts = nullptr;
} }
struct HelpPage { struct HelpPage {
@@ -98,31 +100,41 @@ void on_help_row_selected(GtkListBox*, GtkListBoxRow* row, gpointer body_label)
void show_about(GtkWindow* parent, const std::string& session_path) { void show_about(GtkWindow* parent, const std::string& session_path) {
if (g_about != nullptr) { if (g_about != nullptr) {
gtk_window_present(GTK_WINDOW(g_about)); adw_dialog_present(g_about, GTK_WIDGET(parent));
return; return;
} }
// GtkAboutDialog is not deprecated in GTK 4 (unlike GtkShortcutsWindow), so the // `AdwAboutDialog` rather than `GtkAboutDialog`: it shows the application icon as the page's
// stock dialog is used here and the overview is hand-rolled. // own header, which is the whole reason the app has one, and `adw_about_window_new()` is
GtkWidget* dialog = gtk_about_dialog_new(); // deprecated in favour of it. It is an `AdwDialog`, so it is modal on what it is presented
// on and needs no transient-for or modal call of its own.
AdwDialog* dialog = adw_about_dialog_new();
g_about = dialog; g_about = dialog;
GtkAboutDialog* about = GTK_ABOUT_DIALOG(dialog); AdwAboutDialog* about = ADW_ABOUT_DIALOG(dialog);
gtk_about_dialog_set_program_name(about, "udpscope"); adw_about_dialog_set_application_name(about, "udpscope");
gtk_about_dialog_set_version(about, build_info::kVersion); adw_about_dialog_set_application_icon(about, "org.udpscope.app");
gtk_about_dialog_set_comments(about, "An oscilloscope for UDPStreamer sources."); adw_about_dialog_set_developer_name(about, "Martino Ferrari");
gtk_about_dialog_set_license_type(about, GTK_LICENSE_MIT_X11); adw_about_dialog_set_version(about, build_info::kVersion);
adw_about_dialog_set_comments(about, "An oscilloscope for UDPStreamer sources.");
adw_about_dialog_set_license_type(about, GTK_LICENSE_MIT_X11);
adw_about_dialog_set_website(about, "https://git.ferrarinas.it/martino/udpscope");
char details[1024]; // `debug_info` rather than `system_information`: it renders as a Troubleshooting page with a
std::snprintf(details, sizeof(details), // copy button, where the old field was inert text a user had to retype. A `std::string`
"Commit: %s\nBuilt: %s\nSettings: %s\nSession file: %s", // rather than a fixed buffer, because a long settings path used to truncate it silently.
build_info::kCommit, build_info::kTimestamp, config_dir().c_str(), const std::string debug = "Commit: " + std::string(build_info::kCommit) +
session_path.c_str()); "\nBuilt: " + build_info::kTimestamp +
gtk_about_dialog_set_system_information(about, details); "\nSettings: " + config_dir() +
"\nSession file: " + session_path;
adw_about_dialog_set_debug_info(about, debug.c_str());
gtk_window_set_transient_for(GTK_WINDOW(dialog), parent); g_signal_connect(dialog, "closed", G_CALLBACK(on_dialog_closed), &g_about);
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); adw_dialog_present(dialog, GTK_WIDGET(parent));
g_signal_connect(dialog, "close-request", G_CALLBACK(on_window_close), &g_about); }
gtk_window_present(GTK_WINDOW(dialog));
const char* about_icon_name() {
if (g_about == nullptr) return "";
return adw_about_dialog_get_application_icon(ADW_ABOUT_DIALOG(g_about));
} }
HelpReport help_self_check() { HelpReport help_self_check() {
@@ -278,13 +290,13 @@ void show_shortcuts(GtkWindow* parent) {
gestures[i].description); gestures[i].description);
} }
g_signal_connect(dialog, "closed", G_CALLBACK(on_shortcuts_closed), nullptr); g_signal_connect(dialog, "closed", G_CALLBACK(on_dialog_closed), &g_shortcuts);
adw_dialog_present(dialog, GTK_WIDGET(parent)); adw_dialog_present(dialog, GTK_WIDGET(parent));
} }
void close_help_windows() { void close_help_windows() {
if (g_help != nullptr) gtk_window_close(GTK_WINDOW(g_help)); if (g_help != nullptr) gtk_window_close(GTK_WINDOW(g_help));
if (g_about != nullptr) gtk_window_close(GTK_WINDOW(g_about)); if (g_about != nullptr) adw_dialog_close(g_about);
if (g_shortcuts != nullptr) adw_dialog_close(g_shortcuts); if (g_shortcuts != nullptr) adw_dialog_close(g_shortcuts);
} }
+13 -6
View File
@@ -8,10 +8,16 @@
namespace udpscope { namespace udpscope {
/// The About dialog: version, commit, build timestamp and where the session files live. /// The About dialog: the app icon, version, commit, build timestamp and where the session
/// Modal on `parent`. `session_path` is this run's own session file. /// files live. An `AdwAboutDialog`, so it is modal on `parent` by being presented on it, and
/// it is an `AdwDialog` rather than a `GtkWindow`. `session_path` is this run's own session
/// file; it goes on the Troubleshooting page, which has a copy button.
void show_about(GtkWindow* parent, const std::string& session_path); void show_about(GtkWindow* parent, const std::string& session_path);
/// The icon name the open About dialog carries, or "" when it is not open. The probe reads it
/// so the dialog is asserted to *show* the app icon rather than merely to open.
const char* about_icon_name();
/// The paged help window. Modal on nothing — it is a reference, so it stays usable while /// The paged help window. Modal on nothing — it is a reference, so it stays usable while
/// the scope runs. Already open: presented. /// the scope runs. Already open: presented.
void show_help(GtkWindow* parent); void show_help(GtkWindow* parent);
@@ -33,10 +39,11 @@ HelpReport help_self_check();
/// deprecated as of GTK 4.18 and this project builds warning-free. /// deprecated as of GTK 4.18 and this project builds warning-free.
void show_shortcuts(GtkWindow* parent); void show_shortcuts(GtkWindow* parent);
/// Closes whichever of the three windows are open. The About dialog and the help window are /// Closes whichever of the three windows are open. One of them is a GtkWindow -- the help
/// GtkWindows and close with gtk_window_close(), so their close-request handlers run; the /// window, closed with gtk_window_close(), which emits close-request so its handler runs. The
/// shortcut overview is an AdwDialog and closes with adw_dialog_close(), which emits ::closed. /// other two are AdwDialogs, which are not GtkWindows: they close with adw_dialog_close(),
/// Either way the file-scope pointer is nulled by the handler. Used by the probe. /// which emits ::closed. Either way the file-scope pointer is nulled by the handler, which is
/// what prevents a second open presenting a dead widget. Used by the probe.
void close_help_windows(); void close_help_windows();
} // namespace udpscope } // namespace udpscope
+6 -2
View File
@@ -7568,6 +7568,9 @@ gboolean help_probe(gpointer user) {
show_help(GTK_WINDOW(a->window)); show_help(GTK_WINDOW(a->window));
show_about(GTK_WINDOW(a->window), session); show_about(GTK_WINDOW(a->window), session);
show_shortcuts(GTK_WINDOW(a->window)); show_shortcuts(GTK_WINDOW(a->window));
// Read while it is open: the icon is the point of the dialog carrying one, and
// `close_help_windows()` is right below.
const std::string about_icon = about_icon_name();
close_help_windows(); close_help_windows();
int accel_count = 0; int accel_count = 0;
@@ -7576,10 +7579,11 @@ gboolean help_probe(gpointer user) {
(void)plot_gestures(&gesture_count); (void)plot_gestures(&gesture_count);
std::fprintf(stderr, std::fprintf(stderr,
"[help] pages=%d parsed=%d shortest='%s' %zu B; shortcuts: app=%d plot=%d; " "[help] pages=%d parsed=%d shortest='%s' %zu B; shortcuts: app=%d plot=%d; "
"about version=%s commit=%s built=%s settings=%s; closed ok\n", "about version=%s commit=%s built=%s settings=%s icon=%s; closed ok\n",
report.pages, report.parsed, report.shortest_title.c_str(), report.pages, report.parsed, report.shortest_title.c_str(),
report.shortest_bytes, accel_count, gesture_count, build_info::kVersion, report.shortest_bytes, accel_count, gesture_count, build_info::kVersion,
build_info::kCommit, build_info::kTimestamp, config_dir().c_str()); build_info::kCommit, build_info::kTimestamp, config_dir().c_str(),
about_icon.c_str());
std::fflush(stderr); std::fflush(stderr);
return G_SOURCE_REMOVE; return G_SOURCE_REMOVE;
} }