feat: the help is a submenu, with a contextual-help layer and a guided tour
The hamburger menu's help rows were three flat entries and a window. They are a **`Help` submenu** now, which is where every GNOME menu keeps them, with the four things a first run cannot find: - Keyboard shortcuts Ctrl+/ (the existing overview, `show_shortcuts()`) - Documentation F1 (the existing manual, `show_help()`) - Guided tour Ctrl+T (new) - Enable contextual help (new, a stateful row: its check *is* the setting) The submenu *replaces* the menu's rows rather than floating beside them, the same shape and the same reason as the plot's right-click menu: a submenu torn out of the menu it belongs to is a second thing to dismiss, and the `GtkMenuButton` that owned one toggled itself out of sync the moment anything else closed its popover. It is the pages of a `GtkStack`, not a rebuild of the box and not a swap of the popover's child: the main view holds `app.layout_toggle`, which the tick's `sync_layout_editor()` and the `L` accel read, so rebuilding would leave that pointer on a destroyed widget, and a swapped child would leave the view that is not showing without an owner. **The contextual help is one table and a "?" per surface** (`src/ui_context_help.cpp`): the plot, the trigger, the cursors, the measurements, the math channels, the signals, the event history, the time window, the status line, and the math editor's identity row. Each row names the *heading box* its button joins -- a sibling of the title, never an overlay child on a corner, which is what would cover the switch or the [edit] button already there -- so adding a surface is a row and a widget pointer. Off means *gone*, not greyed: the buttons are removed, and they are found by a data key rather than a list of pointers because the math editor's heading is built afresh on every opening and a list would go stale one dialog later. `contextual_help` and `tour_seen` are keys of `settings.toml`, written through the same `Settings`/`load_settings()`/`save_settings()` the Preferences dialog uses, whose own rows start from `load_settings()` and so carry them through untouched. **The guided tour is five steps on a `GtkOverlay`** above the window's content: the plot, the side panel, the trigger, the signals and the status line. There is no popover in it and therefore no pointer grab to leak -- four dim rectangles around the hole are what make it modal -- and the probe asserts that as a reading (`controllers=` against the count taken before it opened) rather than as a claim. Everything is placed from `gtk_widget_compute_bounds()` re-read on every frame, because a widget's allocation is 0 on the frame it is first shown and a window resize moves it under a one-shot measurement; the hole is kept from the last frame that could measure the target, because a frame that lands between two layout passes would otherwise empty it and make the spotlight jump. `Skip`, `Finish` and `Escape` all hide every child and leave nothing behind. A first run gets the tour once, decided by `tour_seen` and not by "the settings file did not exist" (a user who changed the theme has a file and has still never seen it), and the flag is written when the tour *starts* rather than when it finishes: a user who skips it in the first second has still seen it. `UDPSCOPE_PROBE_HELPUI_MS` drives all of it -- the auto-start, the menu's seven rows and the submenu's five, the toggle's effect read as a count of affordances on either side of it, and the tour started, stepped forwards and back and dismissed -- and the exit dump gains `help layer:` and `help figures:`, the latter being the ten surfaces' window-coordinate rectangles that the manual's annotated screenshots crop their boxes from. Two of them are what the D-Bus-free headless run can still not decide, and both are limitations rather than defects: export carries no "?" (it is a menu action with no surface of its own, and a popover inside the menu's popover is the nested-popover trap the invariants already warn about), and the tour card cannot miss its target when the target is the whole content -- which is what the plot is at the window's 756 px minimum, where the split view overlays the side panel instead of putting it beside the plot. The probe prints both rectangles rather than asserting a placement, so that case reads as the geometry it is. Also fixed while it was found: `gtk_widget_measure()` includes the widget's own margins (measured: a 280 px box with `margin-start 453` reports 733), so a measure that is used to *place* the widget is a feedback loop -- that is what made the tour card walk between the corner and the target from frame to frame.
This commit is contained in:
1 parent
c1460a6977
commit
1baa58b8fd
20 files changed
+1558
-30
No files matched your search
+206
-24
@@ -2002,6 +2002,11 @@ void open_math_dialog(App& app, int edit) {
|
||||
}
|
||||
GtkWidget* window = d->window;
|
||||
gtk_window_set_title(GTK_WINDOW(window), edit >= 0 ? "Edit math channel" : "New math channel");
|
||||
// The contextual-help "?" for this editor. This is the one surface whose heading only exists while a
|
||||
// dialog is open -- the file is rebuilt on every opening -- so it cannot be a row the window's own
|
||||
// build visits: the row is still the table's, and this states its host and asks for the hint.
|
||||
app.math_head = head;
|
||||
context_help_add(app, "math", head);
|
||||
// The dialog's own state, freed with the window: a plain local here would leak the graph and every drag
|
||||
// it is halfway through on each opening.
|
||||
g_object_set_data_full(G_OBJECT(window), "udpscope-math-dialog", d,
|
||||
@@ -2010,7 +2015,13 @@ void open_math_dialog(App& app, int edit) {
|
||||
GtkWidget* dd = GTK_WIDGET(w);
|
||||
MathDialog* md = static_cast<MathDialog*>(
|
||||
g_object_get_data(G_OBJECT(dd), "udpscope-math-dialog"));
|
||||
if (md != nullptr && md->app != nullptr) md->app->math_dialog = nullptr;
|
||||
if (md != nullptr && md->app != nullptr) {
|
||||
// The host goes with the window, and the builder that owns it is dropped on the
|
||||
// next opening: clear the pointer here rather than leaving it aimed at widgets
|
||||
// that are about to be finalized.
|
||||
md->app->math_head = nullptr;
|
||||
md->app->math_dialog = nullptr;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
nullptr);
|
||||
@@ -2311,6 +2322,9 @@ GtkWidget* build_measures_panel(App& app) {
|
||||
update_measures(*a, /*force=*/true, /*accumulate=*/true);
|
||||
}),
|
||||
&app);
|
||||
// The heading row -- the .ui's anonymous first child, carrying the title and the `+`. It is where
|
||||
// the contextual help's "?" joins (see `ui_context_help.cpp`).
|
||||
app.measures_head = gtk_widget_get_first_child(box);
|
||||
return box;
|
||||
}
|
||||
|
||||
@@ -2430,32 +2444,70 @@ void on_export_clicked(GtkButton*, gpointer user) {
|
||||
g_object_unref(dialog);
|
||||
}
|
||||
|
||||
/// Dismisses the overflow menu. Every entry that opens something *else* -- a dialog, a window, the
|
||||
/// tour -- does this first, or the menu is still up behind what it opened.
|
||||
void close_overflow_menu(App& app) {
|
||||
if (app.menu_button == nullptr) return;
|
||||
GtkPopover* popover = gtk_menu_button_get_popover(GTK_MENU_BUTTON(app.menu_button));
|
||||
if (popover != nullptr) gtk_popover_popdown(popover);
|
||||
}
|
||||
|
||||
void on_about_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);
|
||||
}
|
||||
close_overflow_menu(*app);
|
||||
show_about(GTK_WINDOW(app->window),
|
||||
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);
|
||||
}
|
||||
close_overflow_menu(*app);
|
||||
show_help(GTK_WINDOW(app->window));
|
||||
}
|
||||
|
||||
void on_shortcuts_clicked(GtkButton*, gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
close_overflow_menu(*app);
|
||||
show_shortcuts(GTK_WINDOW(app->window));
|
||||
}
|
||||
|
||||
/// The Help submenu's two rows beyond the dialogs. The view swap is the plot menu's own shape: the
|
||||
/// second view *replaces* the first, with a Back row at its head, because a submenu torn out of the
|
||||
/// menu it belongs to is a second thing to dismiss and a `GtkMenuButton` that owned one toggled
|
||||
/// itself out of sync the moment anything else closed its popover.
|
||||
///
|
||||
/// What it does *not* do is rebuild the rows. The main view holds `app.layout_toggle`, which the tick
|
||||
/// reads (`sync_layout_editor()`) and which the `L` accel moves, so a rebuild would leave that pointer
|
||||
/// aimed at a destroyed widget; the two views are pages of a `GtkStack` instead, and a view is swapped
|
||||
/// by naming the page.
|
||||
void on_menu_show_help(GtkButton*, gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
if (app->menu_stack == nullptr) return;
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(app->menu_stack), "help");
|
||||
// The popover's content changed size, which is what `present()` re-allocates and re-positions: a
|
||||
// popover that is already up does not re-measure itself.
|
||||
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);
|
||||
if (popover != nullptr) gtk_popover_present(popover);
|
||||
}
|
||||
show_shortcuts(GTK_WINDOW(app->window));
|
||||
}
|
||||
|
||||
void on_menu_back(GtkButton*, gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
if (app->menu_stack == nullptr) return;
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(app->menu_stack), "main");
|
||||
if (app->menu_button != nullptr) {
|
||||
GtkPopover* popover = gtk_menu_button_get_popover(GTK_MENU_BUTTON(app->menu_button));
|
||||
if (popover != nullptr) gtk_popover_present(popover);
|
||||
}
|
||||
}
|
||||
|
||||
/// "Guided tour": the menu hands over to the tour module, and the tour is startable at any time --
|
||||
/// from here, from `<Control>t`, and by itself on a first run.
|
||||
void on_tour_clicked(GtkButton*, gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
close_overflow_menu(*app);
|
||||
start_tour(*app);
|
||||
}
|
||||
|
||||
// --- right-click menu -------------------------------------------------------
|
||||
@@ -3263,6 +3315,11 @@ gboolean tick(gpointer user) {
|
||||
// no text and no name either.
|
||||
name_side_tabs(*app);
|
||||
note_panel_controls(*app);
|
||||
// The tour, re-measured every frame: the target's allocation is 0 on the frame it is first shown
|
||||
// (see the allocation bullets in `docs/claude/invariants-trigger-and-pinning.md`) and the widget
|
||||
// moves under the spotlight on a window resize, so a one-shot measurement is wrong both ways.
|
||||
// Cheap when the tour is not running: one flag.
|
||||
update_tour(*app);
|
||||
ScopeModel& model = *app->model;
|
||||
const gint64 tick_start_us = g_get_monotonic_time();
|
||||
|
||||
@@ -3311,6 +3368,17 @@ gboolean tick(gpointer user) {
|
||||
std::fflush(stderr);
|
||||
}
|
||||
}
|
||||
// The help layer's own readings and the bounds the help's annotated figures crop, on the same
|
||||
// once-a-second cadence and for the same reason as the line above: the dump runs after the window
|
||||
// is destroyed and `verification.md` forbids a `gtk_widget_*` call there.
|
||||
{
|
||||
static gint64 next_help_log = 0;
|
||||
const gint64 now = g_get_monotonic_time();
|
||||
if (now >= next_help_log) {
|
||||
next_help_log = now + G_TIME_SPAN_SECOND;
|
||||
capture_help_state(*app);
|
||||
}
|
||||
}
|
||||
// Captured every frame so the dump has the last live value: see `dbg_measures_pane_w`.
|
||||
if (app->measures_list != nullptr) {
|
||||
app->dbg_measures_pane_w = gtk_widget_get_width(app->measures_list);
|
||||
@@ -4516,6 +4584,14 @@ void run_app_action(App& app, AppAction action) {
|
||||
case AppAction::Shortcuts:
|
||||
show_shortcuts(GTK_WINDOW(app.window));
|
||||
break;
|
||||
case AppAction::GuidedTour:
|
||||
start_tour(app);
|
||||
break;
|
||||
case AppAction::ContextualHelp:
|
||||
// Through the menu's own row rather than around it: the check *is* the setting, and the row's
|
||||
// handler is the one place that writes it.
|
||||
toggle(app.contextual_toggle);
|
||||
break;
|
||||
case AppAction::Quit:
|
||||
if (app.window != nullptr) gtk_window_close(GTK_WINDOW(app.window));
|
||||
break;
|
||||
@@ -4528,6 +4604,12 @@ gboolean on_app_key_pressed(GtkEventControllerKey*, guint keyval, guint, GdkModi
|
||||
gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
if (app->window == nullptr) return FALSE;
|
||||
// Escape ends the tour, and it does so ahead of the editable check: a tour that cannot be dismissed
|
||||
// with the key every one of them teaches is a tour with a trap in it.
|
||||
if (keyval == GDK_KEY_Escape && tour_active(*app)) {
|
||||
tour_dismiss(*app);
|
||||
return TRUE;
|
||||
}
|
||||
GtkWidget* focus = gtk_window_get_focus(GTK_WINDOW(app->window));
|
||||
if (focus != nullptr && GTK_IS_EDITABLE(focus)) return FALSE;
|
||||
|
||||
@@ -4589,6 +4671,9 @@ GtkWidget* build_toolbar(App& app) {
|
||||
// has already packed, so a control added here would land right of the whole group.
|
||||
if (GtkWidget* slot = GTK_WIDGET(gtk_builder_get_object(ui, "window_control_slot"))) {
|
||||
gtk_box_append(GTK_BOX(slot), build_window_control(app));
|
||||
// Where the contextual help's "?" for the span control joins: the slot, not the bar, because a
|
||||
// control added to a header bar lands right of everything the file packed.
|
||||
app.window_control_slot = slot;
|
||||
}
|
||||
refresh_window_limits(app);
|
||||
|
||||
@@ -4608,8 +4693,9 @@ GtkWidget* build_toolbar(App& app) {
|
||||
// because these act on the `RowSet` directly and two of them are toggles whose state the
|
||||
// rest of the UI reads, so an action model would only add a second copy of that state.
|
||||
//
|
||||
// The order is a GNOME menu's: what you do to the *view*, then the data, then the help
|
||||
// trio. Nothing here needs a separator of its own -- the groups are the separators.
|
||||
// The order is a GNOME menu's: what you do to the *view*, then the data, then Help, then the
|
||||
// two about-the-program entries. Nothing here needs a separator of its own -- the groups are
|
||||
// the separators.
|
||||
GtkWidget* menu_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_set_margin_top(menu_box, 6);
|
||||
gtk_widget_set_margin_bottom(menu_box, 6);
|
||||
@@ -4649,16 +4735,19 @@ GtkWidget* build_toolbar(App& app) {
|
||||
|
||||
gtk_box_append(GTK_BOX(menu_box), menu_separator());
|
||||
|
||||
GtkWidget* shortcuts_item = menu_button("Keyboard shortcuts", "Ctrl+/");
|
||||
gtk_widget_set_tooltip_text(shortcuts_item,
|
||||
"every key and mouse gesture, including the plot's own");
|
||||
g_signal_connect(shortcuts_item, "clicked", G_CALLBACK(on_shortcuts_clicked), &app);
|
||||
gtk_box_append(GTK_BOX(menu_box), shortcuts_item);
|
||||
|
||||
GtkWidget* help_item = menu_button("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);
|
||||
// The Help submenu's door. A row and not a `GtkMenuButton`: the submenu is the *next view of this
|
||||
// menu*, and the chevron is what says so -- the same shape, and the same reason, as the plot menu's
|
||||
// Remove-signal row.
|
||||
GtkWidget* help_row = menu_button("Help", nullptr);
|
||||
gtk_widget_set_tooltip_text(help_row,
|
||||
"the shortcut list, the documentation, a guided tour and the "
|
||||
"contextual help");
|
||||
g_signal_connect(help_row, "clicked", G_CALLBACK(on_menu_show_help), &app);
|
||||
if (GtkWidget* inner = gtk_button_get_child(GTK_BUTTON(help_row))) {
|
||||
gtk_box_append(GTK_BOX(inner), gtk_image_new_from_icon_name("pan-end-symbolic"));
|
||||
}
|
||||
app.menu_help_row = help_row;
|
||||
gtk_box_append(GTK_BOX(menu_box), help_row);
|
||||
|
||||
GtkWidget* prefs_item = menu_button("Preferences\u2026", nullptr);
|
||||
g_signal_connect(prefs_item, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
|
||||
@@ -4672,6 +4761,61 @@ GtkWidget* build_toolbar(App& app) {
|
||||
g_signal_connect(about_item, "clicked", G_CALLBACK(on_about_clicked), &app);
|
||||
gtk_box_append(GTK_BOX(menu_box), about_item);
|
||||
|
||||
// --- the Help view, the menu's second page --------------------------------
|
||||
// Exactly the four entries the menu promises: the two dialogs that already existed, the tour, and
|
||||
// the switch for the "?" layer. The two dialogs are the *same* entry points the keys reach --
|
||||
// `show_shortcuts()` and `show_help()` -- so the menu and the accelerator cannot drift apart.
|
||||
GtkWidget* help_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_set_margin_top(help_box, 6);
|
||||
gtk_widget_set_margin_bottom(help_box, 6);
|
||||
gtk_widget_set_margin_start(help_box, 6);
|
||||
gtk_widget_set_margin_end(help_box, 6);
|
||||
|
||||
GtkWidget* back_row = menu_icon_button("go-previous-symbolic", "Back");
|
||||
g_signal_connect(back_row, "clicked", G_CALLBACK(on_menu_back), &app);
|
||||
app.menu_back_row = back_row;
|
||||
gtk_box_append(GTK_BOX(help_box), back_row);
|
||||
gtk_box_append(GTK_BOX(help_box), menu_separator());
|
||||
|
||||
app.menu_shortcuts_row = menu_button("Keyboard shortcuts", "Ctrl+/");
|
||||
gtk_widget_set_tooltip_text(app.menu_shortcuts_row,
|
||||
"every key and mouse gesture, including the plot's own");
|
||||
g_signal_connect(app.menu_shortcuts_row, "clicked", G_CALLBACK(on_shortcuts_clicked), &app);
|
||||
gtk_box_append(GTK_BOX(help_box), app.menu_shortcuts_row);
|
||||
|
||||
app.menu_docs_row = menu_button("Documentation", "F1");
|
||||
gtk_widget_set_tooltip_text(app.menu_docs_row,
|
||||
"the udpscope manual, opened in the desktop help viewer");
|
||||
g_signal_connect(app.menu_docs_row, "clicked", G_CALLBACK(on_help_clicked), &app);
|
||||
gtk_box_append(GTK_BOX(help_box), app.menu_docs_row);
|
||||
|
||||
app.menu_tour_row = menu_button("Guided tour", "Ctrl+T");
|
||||
gtk_widget_set_tooltip_text(app.menu_tour_row,
|
||||
"walk over the plot, the side panel, the trigger, the signals and "
|
||||
"the status line, one step at a time");
|
||||
g_signal_connect(app.menu_tour_row, "clicked", G_CALLBACK(on_tour_clicked), &app);
|
||||
gtk_box_append(GTK_BOX(help_box), app.menu_tour_row);
|
||||
|
||||
// The check is the setting: `menu_toggle()` states it before connecting, so the row that starts
|
||||
// checked does not fire the handler while the surfaces it acts on are still being built.
|
||||
app.contextual_toggle = menu_toggle("Enable contextual help", nullptr,
|
||||
app.settings.contextual_help);
|
||||
gtk_widget_set_tooltip_text(app.contextual_toggle,
|
||||
"a small \u201c?\u201d beside each surface, saying what that surface "
|
||||
"is for");
|
||||
g_signal_connect(app.contextual_toggle, "toggled", G_CALLBACK(on_contextual_help_toggled), &app);
|
||||
gtk_box_append(GTK_BOX(help_box), app.contextual_toggle);
|
||||
|
||||
// The two views, as the pages of a stack: the popover's child is the stack and the visible child is
|
||||
// the view. Swapping the *page* rather than rebuilding the box is what keeps `app.layout_toggle` --
|
||||
// which points into the main view and which the tick reads -- a live widget.
|
||||
app.menu_main_view = menu_box;
|
||||
app.menu_help_view = help_box;
|
||||
app.menu_stack = gtk_stack_new();
|
||||
gtk_stack_set_transition_type(GTK_STACK(app.menu_stack), GTK_STACK_TRANSITION_TYPE_NONE);
|
||||
gtk_stack_add_named(GTK_STACK(app.menu_stack), menu_box, "main");
|
||||
gtk_stack_add_named(GTK_STACK(app.menu_stack), help_box, "help");
|
||||
|
||||
// --- the pane toggles, which live on the bar rather than in the menu ------
|
||||
// The three pane toggles, and the rest of the bar, are the file's now; their handlers are wired where
|
||||
// the bar's ids are resolved, below. The popover above still shares this function's styling helpers
|
||||
@@ -4679,7 +4823,7 @@ GtkWidget* build_toolbar(App& app) {
|
||||
|
||||
GtkWidget* popover = gtk_popover_new();
|
||||
gtk_widget_add_css_class(popover, "udpscope-menu");
|
||||
gtk_popover_set_child(GTK_POPOVER(popover), menu_box);
|
||||
gtk_popover_set_child(GTK_POPOVER(popover), app.menu_stack);
|
||||
app.menu_button = GTK_WIDGET(gtk_builder_get_object(ui, "menu_button"));
|
||||
app.sidebar_toggle = GTK_WIDGET(gtk_builder_get_object(ui, "sidebar_toggle"));
|
||||
app.trigger_toggle = GTK_WIDGET(gtk_builder_get_object(ui, "trigger_toggle"));
|
||||
@@ -4734,6 +4878,9 @@ void on_activate(GtkApplication* application, gpointer user) {
|
||||
app->status_badge = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "status_badge"));
|
||||
app->status_label = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "status_label"));
|
||||
app->status_info_button = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "status_info"));
|
||||
// The status line's row, which is where the status line's own "?" joins and one of the surfaces the
|
||||
// help's annotated figures label.
|
||||
app->status_row = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "status_row"));
|
||||
GtkWidget* side = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "side_column"));
|
||||
GtkWidget* side_stack = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "side_stack"));
|
||||
GtkWidget* side_switcher = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "side_switcher"));
|
||||
@@ -4744,6 +4891,7 @@ void on_activate(GtkApplication* application, gpointer user) {
|
||||
if (app->window == nullptr || side_view == nullptr || events_divider == nullptr ||
|
||||
app->plot_box == nullptr || app->plot_split == nullptr || app->status_badge == nullptr ||
|
||||
app->status_label == nullptr || app->status_info_button == nullptr ||
|
||||
app->status_row == nullptr ||
|
||||
side == nullptr || side_stack == nullptr || side_switcher == nullptr ||
|
||||
measure_page == nullptr || signals_page == nullptr) {
|
||||
return;
|
||||
@@ -4769,6 +4917,19 @@ void on_activate(GtkApplication* application, gpointer user) {
|
||||
// shows are the window's, and those are in the file.
|
||||
gtk_window_set_titlebar(GTK_WINDOW(app->window), build_toolbar(*app));
|
||||
|
||||
// The guided tour's overlay goes *above* the window's content, between the window and the split
|
||||
// view: its children -- the four dim rectangles, the spotlight's outline and the card -- have to
|
||||
// be able to cover the whole content, and a widget placed anywhere inside the split view could
|
||||
// only ever cover its own half of it. The split view keeps its reference from the builder, so
|
||||
// handing the window a new child and then adopting the old one costs no reference bookkeeping.
|
||||
{
|
||||
GtkWidget* overlay = gtk_overlay_new();
|
||||
gtk_window_set_child(GTK_WINDOW(app->window), overlay);
|
||||
gtk_overlay_set_child(GTK_OVERLAY(overlay), side_view);
|
||||
app->tour_overlay = overlay;
|
||||
build_tour(*app);
|
||||
}
|
||||
|
||||
// The size the window opens at, stated in code as well as in the `.ui`: `default-width` there is the
|
||||
// size it *asks* for, and a window whose content has a larger minimum opens at that minimum instead --
|
||||
// 756 px here, the plot column's own minimum plus the sidebar's 320. The old code set it explicitly;
|
||||
@@ -4974,6 +5135,16 @@ void on_activate(GtkApplication* application, gpointer user) {
|
||||
rebuild_signal_panels(*app);
|
||||
g_timeout_add(kTickMs, tick, app);
|
||||
|
||||
// The contextual-help layer, once every surface it names exists. It reads the setting, so a user
|
||||
// who turned it off meets a window with no "?" on it and a menu whose row is unchecked.
|
||||
context_help_sync(*app);
|
||||
|
||||
// A first run is the run whose settings file does not say the tour has been offered -- see
|
||||
// `tour_autostart()`. Delayed rather than fired on the first tick: the window has to be up and
|
||||
// laid out for the tour to have anything to point at, and a tour that opened on the frame the
|
||||
// window appeared would be a spotlight on a widget with no allocation yet.
|
||||
if (!app->settings.tour_seen) g_timeout_add(1500, tour_autostart, app);
|
||||
|
||||
// Bubble phase on purpose: a focused GtkEditable or GtkButton wins its own keys, so typing
|
||||
// 'm' into the window entry does not toggle the measurements pane and Space does not
|
||||
// double-fire on a focused button.
|
||||
@@ -5601,6 +5772,9 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_HELP_MS")) {
|
||||
g_timeout_add(static_cast<guint>(std::atoi(env)), help_probe, &app);
|
||||
}
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_HELPUI_MS")) {
|
||||
helpui_probe_next(app, 0, static_cast<guint>(std::atoi(env)));
|
||||
}
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_CONFIGS_MS")) {
|
||||
g_timeout_add(static_cast<guint>(std::atoi(env)), configs_probe, &app);
|
||||
}
|
||||
@@ -5855,6 +6029,14 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
app.dbg_switcher_pills, app.dbg_switcher_labels, app.dbg_switcher_names,
|
||||
app.dbg_switcher_icon_w, app.dbg_switcher_icon_h, app.dbg_switcher_w,
|
||||
app.dbg_switcher_h);
|
||||
// The help affordance layer: what the contextual-help toggle left on the surfaces, and where the
|
||||
// guided tour stood when the last capture ran. Both are captured on the tick (`capture_help_state`),
|
||||
// so neither asks a destroyed widget anything.
|
||||
std::printf("[udpscope] help layer: %s\n", app.dbg_context_help.c_str());
|
||||
// The bounds the help's annotated figures draw their crop boxes from: window coordinates, or
|
||||
// -1,-1,0,0 for a widget that is not on screen (`is_visible()`, not `get_visible()` -- a widget
|
||||
// under a hidden parent keeps its own flag true). Measured on the tick, after a frame.
|
||||
std::printf("[udpscope] help figures: %s\n", app.dbg_help_figures.c_str());
|
||||
std::printf("[udpscope] mode with trigger on: %s\n", app.dbg_mode_triggered.c_str());
|
||||
std::printf("[udpscope] trigger marker x=%.3f, view x_max=%.3f; model trigger_time=%.3f "
|
||||
"valid=%d\n",
|
||||
|
||||
Reference in new issue
Block a user