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 {
/// 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.
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;
/// 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_about = nullptr;
AdwDialog* g_shortcuts = nullptr;
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
}
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)user;
g_shortcuts = nullptr;
*static_cast<AdwDialog**>(slot) = nullptr;
}
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) {
if (g_about != nullptr) {
gtk_window_present(GTK_WINDOW(g_about));
adw_dialog_present(g_about, GTK_WIDGET(parent));
return;
}
// GtkAboutDialog is not deprecated in GTK 4 (unlike GtkShortcutsWindow), so the
// stock dialog is used here and the overview is hand-rolled.
GtkWidget* dialog = gtk_about_dialog_new();
// `AdwAboutDialog` rather than `GtkAboutDialog`: it shows the application icon as the page's
// own header, which is the whole reason the app has one, and `adw_about_window_new()` is
// 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;
GtkAboutDialog* about = GTK_ABOUT_DIALOG(dialog);
gtk_about_dialog_set_program_name(about, "udpscope");
gtk_about_dialog_set_version(about, build_info::kVersion);
gtk_about_dialog_set_comments(about, "An oscilloscope for UDPStreamer sources.");
gtk_about_dialog_set_license_type(about, GTK_LICENSE_MIT_X11);
AdwAboutDialog* about = ADW_ABOUT_DIALOG(dialog);
adw_about_dialog_set_application_name(about, "udpscope");
adw_about_dialog_set_application_icon(about, "org.udpscope.app");
adw_about_dialog_set_developer_name(about, "Martino Ferrari");
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];
std::snprintf(details, sizeof(details),
"Commit: %s\nBuilt: %s\nSettings: %s\nSession file: %s",
build_info::kCommit, build_info::kTimestamp, config_dir().c_str(),
session_path.c_str());
gtk_about_dialog_set_system_information(about, details);
// `debug_info` rather than `system_information`: it renders as a Troubleshooting page with a
// copy button, where the old field was inert text a user had to retype. A `std::string`
// rather than a fixed buffer, because a long settings path used to truncate it silently.
const std::string debug = "Commit: " + std::string(build_info::kCommit) +
"\nBuilt: " + build_info::kTimestamp +
"\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);
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
g_signal_connect(dialog, "close-request", G_CALLBACK(on_window_close), &g_about);
gtk_window_present(GTK_WINDOW(dialog));
g_signal_connect(dialog, "closed", G_CALLBACK(on_dialog_closed), &g_about);
adw_dialog_present(dialog, GTK_WIDGET(parent));
}
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() {
@@ -278,13 +290,13 @@ void show_shortcuts(GtkWindow* parent) {
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));
}
void close_help_windows() {
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);
}