feat: paged help window with the four Getting started pages

The pages are Pango markup in a GResource, bundled for the same reason the icons
are: udpscope runs straight out of the build tree with no install step, so a
runtime search path's failure mode would be a silently empty help window.
--c-name=udpscope_help keeps the two generated bundles from colliding.

Every page is validated with pango_parse_markup() at load and falls back to a
one-line stand-in rather than rendering raw markup or nothing. show_help() reuses
help_window.cpp's existing close-request handler, and closes via
gtk_window_close() so that handler runs and the pointer is nulled.

UDPSCOPE_PROBE_HELP_MS loads and validates every page, opens the help and About
windows, closes both and reports; parsed must equal pages.
This commit is contained in:
Martino Ferrari committed 2026-10-04 14:53:20 +02:00
1 parent 856f538681
commit 04b6181926
9 files changed
+373

No files matched your search

+41
View File
@@ -15,6 +15,7 @@
#include <gtk4/gtk_plot_area.hpp>
#include "acquisition.hpp"
#include "build_info.hpp"
#include "config.hpp"
#include "export.hpp"
#include "help_window.hpp"
@@ -4094,6 +4095,15 @@ void on_about_clicked(GtkButton*, gpointer user) {
layout_path(app->model->config().server, app->model->config().port));
}
void on_help_clicked(GtkButton*, gpointer user) {
App* app = static_cast<App*>(user);
if (app->menu_button != nullptr) {
GtkPopover* popover = gtk_menu_button_get_popover(GTK_MENU_BUTTON(app->menu_button));
if (popover != nullptr) gtk_popover_popdown(popover);
}
show_help(GTK_WINDOW(app->window));
}
// --- right-click menu -------------------------------------------------------
void on_y_auto_clicked(GtkButton* button, gpointer user) {
@@ -5555,6 +5565,11 @@ GtkWidget* build_toolbar(App& app) {
gtk_box_append(GTK_BOX(menu_box), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
GtkWidget* help_item = gtk_button_new_with_label("Help (F1)");
gtk_widget_set_tooltip_text(help_item, "the paged guide to every feature");
g_signal_connect(help_item, "clicked", G_CALLBACK(on_help_clicked), &app);
gtk_box_append(GTK_BOX(menu_box), help_item);
GtkWidget* about_item = gtk_button_new_with_label("About udpscope");
gtk_widget_set_tooltip_text(about_item, "version, build timestamp and settings paths");
g_signal_connect(about_item, "clicked", G_CALLBACK(on_about_clicked), &app);
@@ -7231,6 +7246,29 @@ gboolean zoomkeys_probe(gpointer user) {
return G_SOURCE_REMOVE;
}
/// Loads and validates every help page, opens all three windows, closes them, and reports.
/// `parsed` must equal `pages` and no page may be empty; `closed ok` with no GTK critical
/// is the crash check for three windows built and destroyed in one run.
gboolean help_probe(gpointer user) {
App* a = static_cast<App*>(user);
const HelpReport report = help_self_check();
const std::string session =
layout_path(a->model->config().server, a->model->config().port);
show_help(GTK_WINDOW(a->window));
show_about(GTK_WINDOW(a->window), session);
close_help_windows();
std::fprintf(stderr,
"[help] pages=%d parsed=%d shortest='%s' %zu B; "
"about version=%s commit=%s built=%s settings=%s; closed ok\n",
report.pages, report.parsed, report.shortest_title.c_str(),
report.shortest_bytes, build_info::kVersion, build_info::kCommit,
build_info::kTimestamp, config_dir().c_str());
std::fflush(stderr);
return G_SOURCE_REMOVE;
}
int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisition& acq,
RowSet& rows) {
(void)argc;
@@ -7314,6 +7352,9 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
if (const char* env = g_getenv("UDPSCOPE_PROBE_ZOOMKEYS_MS")) {
g_timeout_add(static_cast<guint>(std::atoi(env)), zoomkeys_probe, &app);
}
if (const char* env = g_getenv("UDPSCOPE_PROBE_HELP_MS")) {
g_timeout_add(static_cast<guint>(std::atoi(env)), help_probe, &app);
}
// Our own options were consumed by parse_args(); GTK would reject them, so only
// the program name is handed to g_application_run().