diff --git a/src/help_window.cpp b/src/help_window.cpp index a9065f3..edb27ef 100644 --- a/src/help_window.cpp +++ b/src/help_window.cpp @@ -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(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); } diff --git a/src/help_window.hpp b/src/help_window.hpp index 8571e29..3eeb064 100644 --- a/src/help_window.hpp +++ b/src/help_window.hpp @@ -8,10 +8,16 @@ namespace udpscope { -/// The About dialog: version, commit, build timestamp and where the session files live. -/// Modal on `parent`. `session_path` is this run's own session file. +/// The About dialog: the app icon, version, commit, build timestamp and where the session +/// 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); +/// 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 scope runs. Already open: presented. 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. void show_shortcuts(GtkWindow* parent); -/// Closes whichever of the three windows are open. The About dialog and the help window are -/// GtkWindows and close with gtk_window_close(), so their close-request handlers run; the -/// shortcut overview is an AdwDialog and closes with adw_dialog_close(), which emits ::closed. -/// Either way the file-scope pointer is nulled by the handler. Used by the probe. +/// Closes whichever of the three windows are open. One of them is a GtkWindow -- the help +/// window, closed with gtk_window_close(), which emits close-request so its handler runs. The +/// other two are AdwDialogs, which are not GtkWindows: they close with adw_dialog_close(), +/// 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(); } // namespace udpscope diff --git a/src/ui.cpp b/src/ui.cpp index 5b3dbfe..33877a8 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -7568,6 +7568,9 @@ gboolean help_probe(gpointer user) { show_help(GTK_WINDOW(a->window)); show_about(GTK_WINDOW(a->window), session); 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(); int accel_count = 0; @@ -7576,10 +7579,11 @@ gboolean help_probe(gpointer user) { (void)plot_gestures(&gesture_count); std::fprintf(stderr, "[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.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); return G_SOURCE_REMOVE; }