docs: help, shortcuts, About and the two-tier status bar
Adds the four new features to README.md (About dialog, shortcut overview with Ctrl/Alt key table and deviation note, 14-page help window, two-tier status bar), adds the four new probes and six new invariants to CLAUDE.md, and fixes the `10.0 kS/s` rate-format error in src/help/14-status.txt (format_rate() uses %.3g, which prints `10 kS/s`). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
8242812338
commit
ccb4964182
3 files changed
+116
-2
No files matched your search
@@ -564,6 +564,10 @@ Two lines of the self-check output are assertions in disguise:
|
|||||||
so the panes the tick rewrites are measured directly. The `[width-probe]` lines are the
|
so the panes the tick rewrites are measured directly. The `[width-probe]` lines are the
|
||||||
per-change detail behind the `axes.w changes` count, and a step over 100 px dumps the widget
|
per-change detail behind the `axes.w changes` count, and a step over 100 px dumps the widget
|
||||||
tree's wide subtrees — that is what the documented, intermittent ~320 px step wants read.
|
tree's wide subtrees — that is what the documented, intermittent ~320 px step wants read.
|
||||||
|
- `[help] pages=14 parsed=14 shortest='Overview' 1381 B; shortcuts: app=12 plot=13; about version=… commit=… built=… settings=…; closed ok` (stderr, `UDPSCOPE_PROBE_HELP_MS`) — **`parsed` must equal `pages`** and no page may be empty: a missing resource or markup Pango rejects renders a one-line stand-in instead of failing, so the window *looks* fine and only this count notices. `settings=` must be a real path (the same one the About dialog shows). `closed ok` with **no `Gtk-CRITICAL`** is as much a crash test of three windows built, presented and closed in one run as it is a state check. Two of the three are `GtkWindow`s and close with `gtk_window_close()`; the shortcut overview is an `AdwDialog` and closes with `adw_dialog_close()`, cleared on `::closed`. `shortest=` is the page title — a page whose body was never compiled into the binary is absent rather than empty, and a title alone is the stand-in's shape.
|
||||||
|
- `[keys] a: armed 0->1 | space: paused 0->1 | t: trigger panel: 0->1 | … | editable focused: 'm' refused=1` (stderr, `UDPSCOPE_PROBE_KEYS_MS`) — **every `X->Y` pair must differ** and **`refused` must be 1**. The refusal is the whole reason the controller is in the bubble phase: every bare-letter accel is also a character, so an accel that fires while a `GtkEditable` has focus makes the window entry unusable. A pair reading `0->0` is an action whose dispatch does not reach the handler a click reaches. Note the format is `<key>: <widget-description>: <before>-><after>` — the name passed to `before_after()` includes the widget's description, so `t: trigger panel: 0->1` is the correct form (not `t: trigger panel 0->1` without the colon before the values).
|
||||||
|
- `[zoomkeys] ctrl+plus x=[..]->[..] y_held=1 | alt+plus y=[..]->[..] x_held=1` (stderr, `UDPSCOPE_PROBE_ZOOMKEYS_MS`) — **both `_held` must be 1 and both zooms must still happen.** Run it inside a pinned state, where a frame cannot re-frame the other axis for its own reasons: `UDPSCOPE_PROBE_PAUSE_MS=1500 UDPSCOPE_PROBE_ZOOMKEYS_MS=2200`. A `_held=0` is the modifier not reaching the plotter's controller; an unchanged range on the *zoomed* axis is the opposite failure, the key doing nothing. Ctrl restricts to x and Alt to y; Ctrl+Alt zooms neither (no special case needed — neither flag alone fires).
|
||||||
|
- `status bar: glance=[● live · 1.09 MS/s · 2 s window] max_glance_chars=53 detail_fields=11 popover_rebuilds=0` (exit dump) — **`max_glance_chars` must stay small** (under about 60) and **`popover_rebuilds` must be 0**. The status row is directly above the plot, so this is the "layout must not depend on content" rule read from the bar's own side: the state field is one word from a fixed set, the rate field is `%.3g` (so `10 kS/s`, never `10.0 kS/s`), and the only field trigger mode adds is the elapsed time, which steps units rather than growing digits. `detail_fields` is the popover's label count, a literal, so adding a label without thinking about layout shows up here. The glance rate format matters: `%.3g` drops the trailing zero that a `%.4g` or a fixed-precision format would keep — a rate that reads differently in the dump and in the help page is a format disagreement, and `14-status.txt`'s example must match it.
|
||||||
|
|
||||||
The probes are the regression net — when changing UI or plot-host behaviour, extend
|
The probes are the regression net — when changing UI or plot-host behaviour, extend
|
||||||
the probe/dump in `src/ui.cpp` rather than adding a separate harness.
|
the probe/dump in `src/ui.cpp` rather than adding a separate harness.
|
||||||
@@ -1301,4 +1305,48 @@ far the largest file.
|
|||||||
out — a source whose streamer was merely down during one run would silently vanish from the
|
out — a source whose streamer was merely down during one run would silently vanish from the
|
||||||
only list it can be picked from, for ever. Keeping the old file loses nothing: a run that
|
only list it can be picked from, for ever. Keeping the old file loses nothing: a run that
|
||||||
configured nothing had nothing worth persisting.
|
configured nothing had nothing worth persisting.
|
||||||
|
- **The status bar is two tiers, and the dump reads the detail one.** `format_glance()` writes
|
||||||
|
the bar and `format_detail()` writes both the popover's prose and `App::status_detail`, which
|
||||||
|
is what `exit_probe_step3`/`exit_probe_step2` copy into `dbg_mode_live`/`dbg_mode_triggered`.
|
||||||
|
Those two used to read the *label*, and the label no longer carries `packets`, `gaps`,
|
||||||
|
`plot 1:` or the buffer figures — so a `gtk_label_get_text()` there would silently reduce
|
||||||
|
every documented `mode …:` assertion to four fields. The glance line's state field is **one
|
||||||
|
word from a fixed set** (`live`, `paused`, `armed`, `capturing`, `event held`, `disarmed`) for
|
||||||
|
the layout reason above; a seventh word, or a field whose width tracks the stream, breaks the
|
||||||
|
row the plot is laid out under.
|
||||||
|
- **The ⓘ popover's widgets are built once and only their text is set.** It is visible when the
|
||||||
|
tick updates it, so a rebuild is a per-frame relayout of a widget on screen.
|
||||||
|
`update_status_popover()` only ever calls `gtk_label_set_text()`, and `popover_rebuilds` in
|
||||||
|
the dump is the assertion.
|
||||||
|
- **The About dialog, the help window and the shortcut overview are opened by `show_about()`,
|
||||||
|
`show_help()` and `show_shortcuts()`, and each closes with the matching call.** The help window
|
||||||
|
and the About dialog are `GtkWindow`s: they close with `gtk_window_close()`, which emits
|
||||||
|
`close-request` so the handler that nulls the file-scope pointer runs. `gtk_window_destroy()`
|
||||||
|
does not emit that signal and must not be used — the math dialog fell into exactly this trap,
|
||||||
|
and the pointer then points at a dead widget until the next attempt to open the same window.
|
||||||
|
The shortcut overview is an `AdwDialog` — it is **not** a `GtkWindow` — closed with
|
||||||
|
`adw_dialog_close()` and cleared on `::closed` (there is no `::close-request` on `AdwDialog`).
|
||||||
|
One `GtkWidget*` (or `AdwDialog*`) per window, nulled in its close handler, is the shape that
|
||||||
|
prevents the double-open defect across all three.
|
||||||
|
- **`build_info.cpp` is generated, never committed.** `cmake/write_build_info.cmake` runs at
|
||||||
|
configure time (so `add_executable` can name the file) and again on every build through
|
||||||
|
`udpscope_build_info` (so the commit and the timestamp belong to the binary that is running).
|
||||||
|
`git` absent leaves `kCommit` at `"unknown"` and the build succeeds. The About dialog and the
|
||||||
|
`[help]` probe both read `kVersion`, `kCommit` and `kTimestamp` from it; `settings=` in the
|
||||||
|
probe reads `config_dir()`, which is the path the session file is written to.
|
||||||
|
- **The keyboard zoom's modifiers are not the wheel's.** `+`/`-` with **Ctrl** restricts to x
|
||||||
|
and with **Alt** to y, while the wheel keeps upstream's Shift = x / Ctrl = y convention. The
|
||||||
|
two cannot agree: Shift is unusable on the keyboard path because `key_from()` folds `'+'` and
|
||||||
|
`'='` onto `kKeyZoomIn` — `+` *is* Shift+`=` on common layouts, so a bare zoom-in already
|
||||||
|
arrives with `Mod::Shift` set. Ctrl+Alt together zooms neither axis, which needs no special
|
||||||
|
case. The guards are `deps/plotter`'s own `ctrl_plus_zooms_only_x` and `alt_plus_zooms_only_y`
|
||||||
|
tests, plus `UDPSCOPE_PROBE_ZOOMKEYS_MS`.
|
||||||
|
- **A help page is one `.txt`, one `<file>` line in the GResource manifest, and one `kPages`
|
||||||
|
row.** The bodies are Pango markup in a second GResource (`--c-name=udpscope_help`, so its
|
||||||
|
generated symbols cannot collide with the icons'), validated with `pango_parse_markup()` at
|
||||||
|
load; a page that fails either lookup or parse renders a one-line stand-in and is excluded from
|
||||||
|
`parsed`. `UDPSCOPE_PROBE_HELP_MS`'s `parsed` must equal `pages`.
|
||||||
|
- **`find_accel()` masks the modifiers before comparing.** Lock, Num-Lock and the pointer buttons
|
||||||
|
all arrive in `GdkModifierType`, so an unmasked compare matches no table row at all once
|
||||||
|
Caps Lock is on.
|
||||||
- Every source file carries an SPDX MIT header; match it in new files.
|
- Every source file carries an SPDX MIT header; match it in new files.
|
||||||
@@ -17,6 +17,11 @@ cmake --build build
|
|||||||
--multicast 239.1.2.3 --interface 10.0.0.9 # multicast
|
--multicast 239.1.2.3 --interface 10.0.0.9 # multicast
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**System dependency: libadwaita ≥ 1.8** (`pkg_check_modules(ADWAITA ... libadwaita-1>=1.8)`).
|
||||||
|
It is used for exactly one widget — the shortcut overview is an `AdwShortcutsDialog` — because
|
||||||
|
GTK4's own `GtkShortcutsWindow` is deprecated as of 4.18 and `AdwShortcutsDialog` is its
|
||||||
|
replacement.
|
||||||
|
|
||||||
Options: `--server`, `--port`, `--multicast`, `--interface`, `--buffer-mb`
|
Options: `--server`, `--port`, `--multicast`, `--interface`, `--buffer-mb`
|
||||||
(size of the sample buffer, default 200), `--max-window` (largest time
|
(size of the sample buffer, default 200), `--max-window` (largest time
|
||||||
window the toolbar offers, default 600), `--holdoff` (default trigger hold-off),
|
window the toolbar offers, default 600), `--holdoff` (default trigger hold-off),
|
||||||
@@ -1209,6 +1214,67 @@ on the live ring too. So exporting during a capture writes the partly filled win
|
|||||||
is on screen (`time` from `-pre` up to wherever the post side has reached), not the
|
is on screen (`time` from `-pre` up to wherever the post side has reached), not the
|
||||||
previous event.
|
previous event.
|
||||||
|
|
||||||
|
**The About dialog** (overflow menu) reports the running binary's version, the git commit it was
|
||||||
|
built from, the build timestamp, the settings directory and this session's layout file. Version,
|
||||||
|
commit and timestamp come from `src/build_info.cpp`, generated by CMake at configure time and
|
||||||
|
regenerated on every build — so the commit and the timestamp always belong to the binary that is
|
||||||
|
running, and `git` absent leaves the commit as `"unknown"` without breaking the build.
|
||||||
|
|
||||||
|
**The shortcut overview** (toolbar **?** button, or Ctrl+?) lists all keyboard and mouse controls
|
||||||
|
in two groups — application actions and plot controls — and is an `AdwShortcutsDialog` rather
|
||||||
|
than GTK4's deprecated `GtkShortcutsWindow`. libadwaita ≥ 1.8 is therefore a hard dependency;
|
||||||
|
`CLAUDE.md` records the one consequence worth knowing before editing (the stylesheet it installs
|
||||||
|
reaches every widget). The full key table:
|
||||||
|
|
||||||
|
| key | action |
|
||||||
|
|---|---|
|
||||||
|
| `a` | arm / disarm the trigger |
|
||||||
|
| `Space` | pause / resume |
|
||||||
|
| `t` | show / hide the trigger panel |
|
||||||
|
| `c` | show / hide cursors |
|
||||||
|
| `m` | show / hide measurements |
|
||||||
|
| `g` | show / hide math channels |
|
||||||
|
| `l` | show / hide the layout editor |
|
||||||
|
| `s` | show / hide the side panel |
|
||||||
|
| `f` | fit to data |
|
||||||
|
| `e` | export visible range |
|
||||||
|
| `+` / `-` | zoom in / out (both axes) |
|
||||||
|
| `Ctrl+` / `Ctrl-` | zoom in / out (x axis only) |
|
||||||
|
| `Alt+` / `Alt-` | zoom in / out (y axis only) |
|
||||||
|
|
||||||
|
The wheel uses Shift = x / Ctrl = y, while the keyboard uses Ctrl = x / Alt = y. The two
|
||||||
|
cannot agree: `+` *is* Shift+`=` on common layouts, so a bare zoom-in already arrives with
|
||||||
|
`Mod::Shift` set, and using Shift for axis restriction would make x-only zoom unreachable from
|
||||||
|
the keyboard. This is a **deliberate deviation from the wheel's convention**, documented in the
|
||||||
|
shortcut overview itself.
|
||||||
|
|
||||||
|
The six "Plot mouse" rows (wheel, drag, right-click) carry no accelerator key — GTK's shortcut
|
||||||
|
syntax has no member for pointer gestures — and put the prose in the `subtitle` field instead,
|
||||||
|
which `AdwShortcutsItem` renders as free text.
|
||||||
|
|
||||||
|
**The 14-page help window** (overflow menu, F1, or the toolbar **?** button long-press) is a
|
||||||
|
scrollable text view that opens on the page nearest to what the user was doing: the help pages
|
||||||
|
are compiled into the binary as a GResource (`src/help/udpscope_help.gresource.xml`,
|
||||||
|
`--c-name=udpscope_help`) for the same reason the icons are — udpscope is run straight out of the
|
||||||
|
build tree with no install step, so a search path would have to be found at runtime and a missing
|
||||||
|
file fails silently. The pages are Pango markup (`.txt` bodies), validated at load; a page that
|
||||||
|
fails validation renders a one-line stand-in so the window always opens rather than crashing.
|
||||||
|
`UDPSCOPE_PROBE_HELP_MS` verifies that `parsed == pages` and that the `closed ok` line carries
|
||||||
|
no `Gtk-CRITICAL`.
|
||||||
|
|
||||||
|
**The status bar is two tiers.** The bar itself shows one glance line: the activity badge, the
|
||||||
|
display state (one word from a fixed set), the stored rate and the window. Everything else —
|
||||||
|
packet and frame counts, gaps, buffer size and span, per-plot trace counts — is behind the ⓘ
|
||||||
|
button at the bar's right-hand end, opening a popover. The two moved there because a bar that
|
||||||
|
carried all of them was too wide to read while watching a signal, and because the bar sits
|
||||||
|
directly above the plot: a field whose width tracked the stream would relayout the plot on every
|
||||||
|
update. The rate field uses `%.3g` (`10 kS/s`, never `10.0 kS/s`), and the elapsed-time field
|
||||||
|
steps to a coarser unit rather than growing digits.
|
||||||
|
|
||||||
|
The exit dump still prints the full detail string (`mode with trigger on:` / `mode after
|
||||||
|
go-live:`) because those strings come from `format_detail()` — the same path the popover
|
||||||
|
reads — not from the label, which no longer carries those fields.
|
||||||
|
|
||||||
## Review findings not (yet) implemented
|
## Review findings not (yet) implemented
|
||||||
|
|
||||||
These are known deviations, found by a spec/code review, that are deliberate for
|
These are known deviations, found by a spec/code review, that are deliberate for
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ The bar is always visible under the plot. Click <b>ⓘ</b> for the detail popove
|
|||||||
|
|
||||||
<b>Example</b>
|
<b>Example</b>
|
||||||
|
|
||||||
The bar reads <tt>● live · 10.0 kS/s · 2 s window</tt>. Behind ⓘ:
|
The bar reads <tt>● live · 10 kS/s · 2 s window</tt>. Behind ⓘ:
|
||||||
<tt>Stream connected, configured / packets 717 · frames 716 · gaps 0 / 3 signals · 3 channels</tt>,
|
<tt>Stream connected, configured / packets 717 · frames 716 · gaps 0 / 3 signals · 3 channels</tt>,
|
||||||
<tt>Buffer 200.0 MB · 1677 s span · time x1 / stored 10.0 kS/s (wire 10.0 kS/s)</tt>,
|
<tt>Buffer 200.0 MB · 1677 s span · time x1 / stored 10 kS/s (wire 10 kS/s)</tt>,
|
||||||
<tt>Plot 1 3 traces · 195000 samples</tt>, <tt>Reconnects 0</tt>.
|
<tt>Plot 1 3 traces · 195000 samples</tt>, <tt>Reconnects 0</tt>.
|
||||||
Reference in new issue
Block a user