Files
udpscope/src/ui/shortcuts.ui
T
Martino Ferrari 1baa58b8fd 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.
2026-10-14 17:02:29 +02:00

348 lines
17 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<!-- SPDX-License-Identifier: MIT -->
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
<!--
The shortcut overview, as a UI file instead of a couple of hundred lines of widget construction, and
in gedit's arrangement: one GtkShortcutsWindow holding a section per page (the window draws the
sidebar and the search box over them), each section holding a group, each group holding rows.
The mouse rows are the reason this file exists. An accelerator cannot spell "Ctrl and drag", and
libadwaita's AdwShortcutsItem, the modern replacement for this widget, has no icon property at all,
so its cell reads "No Shortcut". GtkShortcutsShortcut has exactly one gesture type whose *icon* is
drawn (shortcut-type "gesture" plus the icon property, per its own documentation), and that is what
puts the mouse glyph where the keycaps would otherwise be.
Keep this file in step with the tables in shortcuts.cpp: those are what the application handles,
this is what it shows. The `[help]` probe's `ui=` against `tables=` is the guard against the two
drifting apart.
-->
<interface>
<object class="GtkShortcutsWindow" id="help_overlay">
<property name="modal">1</property>
<child>
<object class="GtkShortcutsSection">
<property name="visible">1</property>
<property name="section-name">Acquisition</property>
<property name="title" translatable="yes">Acquisition</property>
<property name="max-height">12</property>
<child>
<object class="GtkShortcutsGroup">
<property name="visible">1</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">space</property>
<property name="title" translatable="yes">Pause or resume the display</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">a</property>
<property name="title" translatable="yes">Arm or disarm the trigger</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">t</property>
<property name="title" translatable="yes">Enable or disable trigger mode</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkShortcutsSection">
<property name="visible">1</property>
<property name="section-name">Panes and editors</property>
<property name="title" translatable="yes">Panes and editors</property>
<property name="max-height">12</property>
<child>
<object class="GtkShortcutsGroup">
<property name="visible">1</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">c</property>
<property name="title" translatable="yes">Enable or disable the cursors</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">m</property>
<property name="title" translatable="yes">Show the Measure tab</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">g</property>
<property name="title" translatable="yes">Show the Signals tab</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">l</property>
<property name="title" translatable="yes">Show or hide the layout editor</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">s</property>
<property name="title" translatable="yes">Show or hide the side panel</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkShortcutsSection">
<property name="visible">1</property>
<property name="section-name">Files and windows</property>
<property name="title" translatable="yes">Files and windows</property>
<property name="max-height">12</property>
<child>
<object class="GtkShortcutsGroup">
<property name="visible">1</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;Control&gt;e</property>
<property name="title" translatable="yes">Export the visible range</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">F1</property>
<property name="title" translatable="yes">Open the documentation</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;Control&gt;slash</property>
<property name="title" translatable="yes">Open this shortcut overview</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;Control&gt;t</property>
<property name="title" translatable="yes">Start the guided tour</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;Control&gt;h</property>
<property name="title" translatable="yes">Turn the contextual help on or off</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;Control&gt;q</property>
<property name="title" translatable="yes">Quit udpscope</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkShortcutsSection">
<property name="visible">1</property>
<property name="section-name">Plot keyboard</property>
<property name="title" translatable="yes">Plot keyboard</property>
<property name="max-height">12</property>
<child>
<object class="GtkShortcutsGroup">
<property name="visible">1</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">plus minus</property>
<property name="title" translatable="yes">Zoom in or out, both axes</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;Control&gt;plus &lt;Control&gt;minus</property>
<property name="title" translatable="yes">Zoom the time axis only</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">&lt;Alt&gt;plus &lt;Alt&gt;minus</property>
<property name="title" translatable="yes">Zoom the value axis only</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">Left Right</property>
<property name="title" translatable="yes">Pan along time</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">Up Down</property>
<property name="title" translatable="yes">Pan along values</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">f</property>
<property name="title" translatable="yes">Fit the view to the data</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="accelerator">Escape</property>
<property name="title" translatable="yes">Cancel the gesture in progress</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkShortcutsSection">
<property name="visible">1</property>
<property name="section-name">Plot mouse</property>
<property name="title" translatable="yes">Plot mouse</property>
<property name="max-height">12</property>
<child>
<object class="GtkShortcutsGroup">
<property name="visible">1</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="shortcut-type">gesture</property>
<!-- A GIcon property takes an object, not a name: GtkBuilder reads a bare string as a
reference to another object and fails the whole file with "Object with ID … not
found" (a Gtk-ERROR, which aborts the process by default). -->
<property name="icon">
<object class="GThemedIcon">
<property name="names">input-mouse-symbolic</property>
</object>
</property>
<property name="title" translatable="yes">Zoom about the pointer, both axes</property>
<property name="subtitle" translatable="yes">Wheel</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="shortcut-type">gesture</property>
<!-- A GIcon property takes an object, not a name: GtkBuilder reads a bare string as a
reference to another object and fails the whole file with "Object with ID … not
found" (a Gtk-ERROR, which aborts the process by default). -->
<property name="icon">
<object class="GThemedIcon">
<property name="names">input-mouse-symbolic</property>
</object>
</property>
<property name="title" translatable="yes">Zoom the time axis only</property>
<property name="subtitle" translatable="yes">Shift and wheel</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="shortcut-type">gesture</property>
<!-- A GIcon property takes an object, not a name: GtkBuilder reads a bare string as a
reference to another object and fails the whole file with "Object with ID … not
found" (a Gtk-ERROR, which aborts the process by default). -->
<property name="icon">
<object class="GThemedIcon">
<property name="names">input-mouse-symbolic</property>
</object>
</property>
<property name="title" translatable="yes">Zoom the value axis only</property>
<property name="subtitle" translatable="yes">Ctrl and wheel</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="shortcut-type">gesture</property>
<!-- A GIcon property takes an object, not a name: GtkBuilder reads a bare string as a
reference to another object and fails the whole file with "Object with ID … not
found" (a Gtk-ERROR, which aborts the process by default). -->
<property name="icon">
<object class="GThemedIcon">
<property name="names">input-mouse-symbolic</property>
</object>
</property>
<property name="title" translatable="yes">Pan</property>
<property name="subtitle" translatable="yes">Drag</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="shortcut-type">gesture</property>
<!-- A GIcon property takes an object, not a name: GtkBuilder reads a bare string as a
reference to another object and fails the whole file with "Object with ID … not
found" (a Gtk-ERROR, which aborts the process by default). -->
<property name="icon">
<object class="GThemedIcon">
<property name="names">input-mouse-symbolic</property>
</object>
</property>
<property name="title" translatable="yes">Box zoom on time</property>
<property name="subtitle" translatable="yes">Ctrl and drag</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="shortcut-type">gesture</property>
<!-- A GIcon property takes an object, not a name: GtkBuilder reads a bare string as a
reference to another object and fails the whole file with "Object with ID … not
found" (a Gtk-ERROR, which aborts the process by default). -->
<property name="icon">
<object class="GThemedIcon">
<property name="names">input-mouse-symbolic</property>
</object>
</property>
<property name="title" translatable="yes">Box zoom on values</property>
<property name="subtitle" translatable="yes">Alt and drag</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">1</property>
<property name="shortcut-type">gesture</property>
<!-- A GIcon property takes an object, not a name: GtkBuilder reads a bare string as a
reference to another object and fails the whole file with "Object with ID … not
found" (a Gtk-ERROR, which aborts the process by default). -->
<property name="icon">
<object class="GThemedIcon">
<property name="names">input-mouse-symbolic</property>
</object>
</property>
<property name="title" translatable="yes">Box zoom on time and values</property>
<property name="subtitle" translatable="yes">Shift and drag</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>