docs: events in the dock and on its page
The dock's Events block, the events slide-over page, the availability-rule deviation, the probe lines and the removal of the left pane are described; the false remembered-width claim is gone. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
9db5e4a49c
commit
c4b79da057
7 files changed
+206
-105
No files matched your search
@@ -102,10 +102,12 @@ touching the data path.
|
||||
own and registered under its id (`trigger`, `source-<id>`, `signals` (the signal list) and
|
||||
`signal:<name>` (one signal's page, `src/ui_signal_page.cpp`, created on demand by `signal_page_ensure()`
|
||||
and pruned by `signal_pages_sync()`, which `rebuild_signal_panels()` ends with), `cursors` (the cursors
|
||||
panel, unchanged) and `measure` (the measurements panel, unchanged; `m` toggles it); `events` is reserved
|
||||
for a later step). Both of those are registered whether or not they are open (a closed page's widgets
|
||||
exist, unmapped), and both are insensitive until the stream has signals, by the same
|
||||
`update_signal_availability()` call that sets the dock's sensitivity. One page is open at a time and `App::slideover.page`
|
||||
panel, unchanged) and `measure` (the measurements panel, unchanged; `m` toggles it); `events` (the full events list, `events-pane.ui`'s `events_box`: the Live, Compare and Accumulate
|
||||
controls and the multi-select list with its thumbnails; open it only through `open_events_page()`, which
|
||||
refreshes the list first, because the slide-over has no on-open hook). They are registered whether or not
|
||||
they are open (a closed page's widgets exist, unmapped); `cursors` and `measure` are insensitive until the
|
||||
stream has signals, by the same `update_signal_availability()` call that sets the dock's sensitivity, and
|
||||
`events` follows `sync_events_availability()` (below). One page is open at a time and `App::slideover.page`
|
||||
is its id, empty when closed. The open page is **not** a session value. Esc, *Close*, a second click on the
|
||||
badge and a click on the plot close it; the click-away is a capture-phase observer (see
|
||||
[invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)). With two or more `source-*` pages, an
|
||||
@@ -120,7 +122,7 @@ touching the data path.
|
||||
pick and focus from `slideover.source_pages` being non-empty every tick, so anything that indexes the badge
|
||||
(the tour's `badges` step targets the whole bar, `[badges]`) must tolerate it being absent.
|
||||
- **The dock** is the `AdwOverlaySplitView` sidebar (`side_box` > `dock_scroller` > `dock_page`), **280 px**
|
||||
and fixed. It holds one thing, the **Readouts** box (`src/ui_dock.cpp`, `build_readouts_dock()`): a
|
||||
and fixed. It holds two things, the **Readouts** box and, under a separator, the **Events** block (below). The Readouts box (`src/ui_dock.cpp`, `build_readouts_dock()`): a
|
||||
**Cursors** block (heading, an **Edit** button that opens the `cursors` page, a `GtkGrid` of a header row
|
||||
`A B delta` and one line per page line) and a **Measurements** block (heading, **Edit** opening the
|
||||
`measure` page, one line per measurement: chip, type, value; a click on a line opens that measurement's
|
||||
@@ -132,6 +134,22 @@ touching the data path.
|
||||
kind's panels (those are slide-over pages). The window's measurement editor is a dialog
|
||||
(`open_measure_dialog()`), not part of the `measure` page.
|
||||
|
||||
The **Events** block (`build_events_dock()`, also `ui_dock.cpp`) is a heading with a **Manage** button
|
||||
(`open_events_page()`) over a short list (`dock_events_list`, 112 px, scrolled, no thumbnails): one compact
|
||||
row per `EventRecord`, newest first, capped at `kDockEventsMax` (24) with a dim "... N more, see Manage"
|
||||
row after them. A row's cells are a one-character marker (filled = the event on screen; hollow = a member
|
||||
of the group on screen -- dock only, the page's rows do not mark members; blank otherwise), the title
|
||||
(`event_row_title`, ellipsized, full text in the tooltip) and `#<seq>`; **one click replays** it
|
||||
(`replay_event()`, the body of the page's double-click handler). Both views are rebuilt from one
|
||||
`EventStore::list()` by `refresh_events_pane()`, but on different cadences: **the dock follows every
|
||||
latch** (`record_event()` refreshes it unconditionally), while the page's list is refreshed per latch only
|
||||
while the page is open and by `open_events_page()` otherwise. `mark_events_on_screen()` updates titles and
|
||||
markers in place and never rebuilds either list. The block, and the page, are insensitive (never hidden)
|
||||
unless `sync_events_availability()` says `event_recording || replaying || events_cached` -- a different
|
||||
rule from the spec's (see [invariants-trigger-and-pinning.md](invariants-trigger-and-pinning.md)).
|
||||
There is no events pane, divider or header toggle any more: the page is reached from Manage (or by a
|
||||
caller of `open_events_page()`), and the plot is never squeezed by events.
|
||||
|
||||
**When each is built and when it is read.** `build_slideover()` runs in `on_activate` right after the tour
|
||||
overlay is built (the tour's overlay wraps the whole window, so its dim rectangles cover the slide-over
|
||||
too); the trigger form is registered as
|
||||
|
||||
@@ -210,9 +210,10 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
entry points in `source_dialog.cpp` (`chooser_tour_begin`, `chooser_tour_autostart`), and *that* walk
|
||||
is what a first run meets first -- see the tour bullets in the help-affordance section below.
|
||||
- **A `GtkPaned` hands its start child at least its minimum unless the position says otherwise.**
|
||||
Putting the events pane in a paned is what makes its width the user's to drag -- but with the
|
||||
position left alone, `shrink-start-child` or not, the pane's minimum is reserved as an empty column
|
||||
while it is *closed*, and the plot pays for a pane nobody opened. The position is the only knob that
|
||||
(The events pane that taught this is gone -- its list is a slide-over page now, with no divider -- but
|
||||
the rule holds for any paned.) With the
|
||||
position left alone, `shrink-start-child` or not, a closed side child's minimum is reserved as an empty
|
||||
column, and the plot pays for a pane nobody opened. The position is the only knob that
|
||||
says "no room": store the width when it is closed (reading `gtk_paned_get_position()` *before*
|
||||
zeroing it) and set 0, restoring it when the pane opens. `gtk_paned_set_shrink_start_child(TRUE)` is
|
||||
still needed, or the minimum would clamp even a zero position.
|
||||
@@ -220,7 +221,8 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
= FALSE` makes both the revealer's minimum and its natural size zero -- and its box then hands it
|
||||
everything the expanding sibling beside it did not claim, so a *collapsed* pane reserved a
|
||||
310-pixel empty column between the window edge and a plot drawn 630 pixels wide in a 1270-pixel
|
||||
window. Every vertical revealer in the side panel hides this, because there the leftover goes down
|
||||
window. (That pane was a revealer beside the plot; it is a slide-over page now, so no revealer sits in
|
||||
a horizontal box any more.) Every vertical revealer in the side panel hid this, because there the leftover goes down
|
||||
instead of across. `gtk_widget_set_visible()` is what a layout honours, so anything that must not
|
||||
take room has to be hidden and not merely unrevealed; the revealer keeps the slide while the pane
|
||||
*is* open. The allocated width is how this was found -- the revealer's own measure said 0.
|
||||
@@ -278,8 +280,9 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
clicked — a switch that greyed out with its own pane could never turn it back on. That is why the
|
||||
sensitive target is the pane's **body** (`trigger_body`, `cursors_body`) and not the pane.
|
||||
- **A list box outlives the children of the pane it sits in, and the teardown order is not yours to
|
||||
choose.** Tearing the window down finalizes the events pane's buttons, then the list box, and the
|
||||
list's own `selected-rows-changed` then reaches a handler holding the freed pointers: four
|
||||
choose.** Tearing the window down finalizes the events page's buttons, then its list box, and the
|
||||
list's own `selected-rows-changed` then reaches a handler holding the freed pointers (`update_group_buttons()`
|
||||
therefore keeps its `app.window == nullptr` guard): four
|
||||
`Gtk-CRITICAL`s from `gtk_widget_set_sensitive` and a read of memory nobody owns. `close-request` is
|
||||
*not* the hook -- `UDPSCOPE_EXIT_AFTER_MS` ends the self-check with `gtk_window_destroy()`, which does
|
||||
not emit it. The hook that does work is `App::window`: every teardown path clears it *before* the
|
||||
@@ -296,22 +299,26 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
- **A pointer to a widget inside a list survives the list being rebuilt.** `events_thumb` named a row's
|
||||
picture across `refresh_events_pane()`, which removes every row first: the next reader of that
|
||||
pointer got a `Gtk-CRITICAL` from `gtk_picture_get_paintable` on freed memory (the four criticals
|
||||
read as "the pane is empty"). A rebuild has to null what it is about to destroy, not only rebuild it.
|
||||
read as "the page is empty"). A rebuild has to null what it is about to destroy, not only rebuild it
|
||||
(`rebuild_events_pane_list()`, the page half of `refresh_events_pane()`, nulls `events_thumb` before it removes the page list's children). A handler on a
|
||||
row that rebuilds the list destroys the widget it was emitted from: `outlive_the_rebuild(widget)`. The dock's
|
||||
rows are a second list built from the same records; the dock row's click handler is safe only because
|
||||
`replay_event()` never rebuilds the dock list.
|
||||
- **`gtk_picture_set_filename()` can render nothing, silently.** It hands the file to the image
|
||||
loader's sandboxed, asynchronous decoder, which on this machine fails without a message: the
|
||||
picture keeps a *paint-able* and takes an allocation, so a probe that checks
|
||||
`gtk_picture_get_paintable()` reads success while the pane shows an empty rectangle (the event
|
||||
pane's preview did exactly that). `gdk_texture_new_from_filename()` decodes the same file on this
|
||||
`gtk_picture_get_paintable()` reads success while the page shows an empty rectangle (the event
|
||||
page's preview did exactly that). `gdk_texture_new_from_filename()` decodes the same file on this
|
||||
thread and `gtk_picture_set_paintable()` hands over the result -- and a NULL texture at least says
|
||||
so. A picture that must be on screen is also a picture with a **height request**: `can_shrink`
|
||||
alone asks for no height at all in a box.
|
||||
- **A cache that is the *only* source of a widget's content must be created by the thing that reads
|
||||
it.** The event pane's thumbnails come from an `EventThumbCache` hung off the list box with
|
||||
it.** The events page's thumbnails (the dock's rows have none) come from an `EventThumbCache` hung off the page's list box with
|
||||
`g_object_set_data_full`, and `event_preview_texture()` answers out of that cache or not at all.
|
||||
Nothing ever created it — so for as long as the pane existed, **every** row came up without a
|
||||
Nothing ever created it — so for as long as the list existed, **every** row came up without a
|
||||
picture, which is what "event screenshot are unusable and sometime missing" was. A missing cache
|
||||
there is not a slow path, it is a blank one; `event_thumb_cache()` therefore creates one on first
|
||||
use. Owned by the **list**, because nothing else has the pane's lifetime: textures of a list that
|
||||
use. Owned by the **list**, because nothing else has the page's lifetime: textures of a list that
|
||||
no longer exists are so much held memory, and the destroy notify is where they are unref'd.
|
||||
- **Hover magnification is a tooltip, not a popover of ours.** A row's thumbnail is 138 px wide and
|
||||
the stored preview is 480: the small one says *which* event this is, and the zoom says what is in
|
||||
|
||||
@@ -40,6 +40,14 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
moved the plot column by about 180 px, which the layout probe read as a different `done_box` and a
|
||||
taller figure.
|
||||
|
||||
- **Events never take the plot's width.** The events list is a slide-over page (an overlay child over the plot) and
|
||||
the dock's Events block lives in the fixed 280 px column, so there is no events divider, revealer or pane beside the
|
||||
plot to squeeze it; the first latch must not move the plot area (`[events] ... plot_w_stable=1`, which compares the
|
||||
allocated width before and after). A dock row is a fixed-width ellipsized label (title at 18 characters, `#seq`), the
|
||||
list is a fixed 112 px scroller capped at `kDockEventsMax` rows plus one "N more" row, and it has no thumbnails: the
|
||||
dock's width and height do not depend on how many events there are or what they say ("layout must not depend on
|
||||
content"). The thumbnails are the *page's* (138 px wide).
|
||||
|
||||
- **`plot::Plot` cannot remove or retarget a trace.** Hence fixed trace slots created
|
||||
up front and rebinding via `SlotSeries`; a *name* change (legend text) is the one
|
||||
thing that forces a rebuild. Cursors are parked outside the axis range instead of
|
||||
@@ -131,7 +139,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
the Source badge is on screen for the E-1608 and hidden for UDPS, and its text differs by kind). A change that makes it grow with the signal count has
|
||||
taken the scroller out of the measurement.
|
||||
- **The dock's width is fixed too, and it is the readouts' width that holds it there** (`side_column`
|
||||
`width-request` 260, the split view's 280 px floor and cap). The dock holds only the compact readouts, so a
|
||||
`width-request` 260, the split view's 280 px floor and cap). The dock holds only the compact readouts and the Events block (a fixed-height list, below), so a
|
||||
section's visibility is never what makes it taller; the scroller absorbs the rest. **The readouts' minimum
|
||||
width is the sum of their cells' character widths, and it must stay under 280** (the Readouts box is 268:
|
||||
280 less its 6 px margins): a cursor line is the `who` cell (5 characters) plus three value cells (9 each),
|
||||
@@ -139,7 +147,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
tooltip carries the text and the layout never depends on what a value reads. The `who` cell was 8
|
||||
characters, which made the readouts' minimum width 304 px; that pushed the sidebar to 304 px
|
||||
(`dock_w=304`) and defeated the 280 pin. Widening any cell is the same
|
||||
trap -- read `[readouts] ... dock_w=` (must be 280) and the `dock cursors:` / `dock measures:` `fits=`.
|
||||
trap -- read `[readouts] ... dock_w=` (must be 280) and the `dock cursors:` / `dock measures:` `fits=`, and for the Events block `dock events: ... dock_w=` (268).
|
||||
- **Trace slots are identified by signal name, not channel index.** Enabling a signal
|
||||
or a new CONFIG renumbers the channel list, so an index would silently re-point a
|
||||
trace at another signal.
|
||||
|
||||
@@ -440,15 +440,24 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
|
||||
## A latch is also a file
|
||||
|
||||
The pane that lists the cache is **on demand**: the toolbar's event-history button and the pane are
|
||||
collapsed until it is asked for, because a column of stale events beside a live plot is what a user
|
||||
who has stopped looking at events does not want. What makes it *available* -- (recording is on) or
|
||||
(an event is on screen) -- is decided in one place, `sync_events_availability()`, so the button and
|
||||
the pane cannot disagree; a disabled cache with nothing being examined takes the button away *and*
|
||||
collapses the pane, because a visible empty column is what "off" must not look like. Trigger mode is
|
||||
deliberately **not** one of the conditions: a stored event being examined is a file, which has no
|
||||
trigger, and tying the pane to the trigger would close the list under the click that opened an event.
|
||||
The recording switch lives in the trigger panel, not in the pane it empties.
|
||||
The cache is listed twice, from one `EventStore::list()`: the dock's compact **Events block** and the full list on the
|
||||
`events` slide-over page (the left pane, its divider and the header toggle are gone, so events no longer take
|
||||
any of the plot's width). What makes it *available* -- recording is on, **or** a replay is on screen, **or** the
|
||||
cache already holds something (`event_recording || replaying || events_cached`) -- is decided in one place,
|
||||
`sync_events_availability()`, and it sets the block's sensitivity (`dock_events_set_sensitive()`) and the page's
|
||||
(`events_box`) from the one `available`, so they cannot disagree. Unavailable means **insensitive, never hidden**
|
||||
("disable, do not hide"): the block stays on screen greyed, and turning recording off with the page open leaves it up
|
||||
and greyed rather than stale and live.
|
||||
|
||||
**This deliberately differs from the redesign spec**, which has the block insensitive "outside trigger mode". Trigger
|
||||
mode is *not* one of the conditions: a stored event being examined is a file, which has no trigger, and a replay or a
|
||||
group *leaves* trigger mode, so a block that went grey outside trigger mode would grey itself under the very click
|
||||
that started the replay -- the one-click replay in the dock would disable the dock it was clicked in. Do not "fix" this
|
||||
to match the spec. The recording switch lives in the trigger panel, not in the block it empties.
|
||||
|
||||
The two views follow the latch on different cadences: the dock is rebuilt on **every** latch (`record_event()`), the
|
||||
page's list only while the page is open, and `open_events_page()` refreshes it when it was closed; a caller that
|
||||
opens the page any other way shows a list that is as stale as the last time it was open.
|
||||
|
||||
## Opening a file is a detour, so *Go live* is a restore
|
||||
|
||||
|
||||
@@ -491,7 +491,7 @@ Two lines of the self-check output are assertions in disguise:
|
||||
column and says there is one scroller and no stack. These are captured on the tick or at the exit probe:
|
||||
the dump runs after the window is destroyed. A source with extra panels adds *no* dock section: its panels
|
||||
are slide-over pages (`source-<id>`), so the line reads the same on the E-1608 and the probe kind.
|
||||
- `[readouts] pages cursors=1 measure=1 dock_children=1 dock_w=280 pages_sensitive=1 signals=1` (in the exit
|
||||
- `[readouts] pages cursors=1 measure=1 dock_children=3 dock_w=280 pages_sensitive=1 signals=1` (in the exit
|
||||
dump; `dock_children` counts the direct children of the dock page) -- the two pages and the dock against the
|
||||
stream. **`cursors=1 measure=1`**: both slide-over pages are registered, always and from the start (not when
|
||||
the stream has signals); `0` is a page that was never added, and then the dock's Edit buttons and the `m` key
|
||||
@@ -500,9 +500,10 @@ Two lines of the self-check output are assertions in disguise:
|
||||
`update_signal_availability()` writes them with the dock), so a run against a port with no sender reads
|
||||
`pages_sensitive=0 signals=0`, a normal run `pages_sensitive=1 signals=1`. `pages_sensitive=1` with `signals=0`
|
||||
is a page that can be opened and read with nothing to read; `pages_sensitive=0` with `signals=1` is a
|
||||
stream whose pages never came back after the signal list arrived. **`dock_children=1`**: the dock
|
||||
page holds the readouts box and nothing else (a second child is a section that crept back into the dock; 0 is a
|
||||
dock that was built empty).
|
||||
stream whose pages never came back after the signal list arrived. **`dock_children=3`**: the dock
|
||||
page holds the readouts box, a separator and the Events block (`build_events_dock()`) and nothing else (a fourth
|
||||
child is a section that crept back into the dock; 1 is a dock without its Events block, 0 a dock that was built
|
||||
empty).
|
||||
- `help layer: contextual=on hints=12 ids={events plot status cursors measures trigger trigger_advanced events_record trigger_search signals signals_config window} icon=dialog-information-symbolic(ok) hint=12x12; tour=idle steps=6 step=0 auto=1 requested=0` (in the exit
|
||||
dump, beside `dock:`) — the help affordance layer as the last tick found it. **`hints=` must equal
|
||||
the number of ids**, and the ids are the surfaces the contextual help is on: eleven of the window
|
||||
@@ -1601,24 +1602,33 @@ run they report:
|
||||
## `[events]`
|
||||
|
||||
```
|
||||
[events] live, recording off: button_visible=0 pane=0 pane_w=0 tick=1 recording=0
|
||||
[events] recording on, no trigger: button_visible=1 pane=0 pane_w=0 tick=1 recording=1
|
||||
[events] after trigger on: button_visible=1 pane=0 pane_w=0 tick=1 recording=1
|
||||
[events] live, recording off: available=0 dock_sensitive=0 page_sensitive=0 page=none
|
||||
[events] recording on, no trigger: available=1 dock_sensitive=1 page_sensitive=1 page=none
|
||||
[events] after trigger on: available=1 dock_sensitive=1 page_sensitive=1 page=none
|
||||
[events] after events page opened: available=1 dock_sensitive=1 page_sensitive=1 page=events
|
||||
[events] open alone: page=events page_box=347
|
||||
[events] preview redrawn: signals=3 stored=480x132 row_drawn=1
|
||||
[events] dir=/home/u/.cache/udpscope/events/127.0.0.1-21023 n=3 newest: stem=20261013-184140-0003 dur=2.000000 pre=1.600000 post=0.400000 edge=5.660100 signals=3 samples=20001 rate=10000 seq=3 thumb_bytes=190134 recorded=1 pane=1
|
||||
[events] activated row0: replaying=1 pane=1 pane_w=240 pane_box=228 tick=0 window=2.000 axis=[-1.600,0.400] marker=0.000 thumbnail=1 thumb_size=138x46 thumb_visible=1 rows_kept=1 selected=2/2
|
||||
[events] dir=/home/u/.cache/udpscope/events/127.0.0.1-21023 n=3 newest: stem=20261013-184140-0003 dur=2.000000 pre=1.600000 post=0.400000 edge=5.660100 signals=3 samples=20001 rate=10000 seq=3 thumb_bytes=190134 recorded=1 page=1 plot_w_stable=1 plot_w=756/756
|
||||
[events] activated row0: replaying=1 page=events page_box=347 tick=0 window=2.000 axis=[-1.600,0.400] marker=0.000 thumbnail=1 thumb_size=138x46 thumb_visible=1 rows_kept=1 selected=2/2
|
||||
[events] dock events: rows=3 pane_rows=3 same_titles=1 sensitive=1 dock_w=268 on_screen=1 marked=0 members=0 max_ok=1 expected_marked=1 at=after pane replay
|
||||
[events] dock replay: clicked=1 replaying=1 waveform_changed=1
|
||||
[events] dock events: rows=3 pane_rows=3 same_titles=1 sensitive=1 dock_w=268 on_screen=1 marked=1 members=0 max_ok=1 expected_marked=1 at=after dock click
|
||||
[events] replayed: channels=3 samples=20001 replaying=1 waveform=1
|
||||
[events] live again: replaying=0 waveform=0
|
||||
[events] live restored (after replay): window=0.500000 (want 0.500000) trigger=1 (want 1) channel='square' (want 'square') level=0 (want 0) enabled=1 armed=1 pinned=0 held=0 mode=2 pending=0 gen=10/5
|
||||
[events] recording off, trigger off: button_visible=1 pane=1 pane_w=240 tick=1 recording=0
|
||||
[events] recording off, trigger on: button_visible=1 pane=1 pane_w=240 tick=1 recording=0
|
||||
[events] recording off, trigger off: available=1 dock_sensitive=1 page_sensitive=1 page=events
|
||||
[events] recording off, trigger on: available=1 dock_sensitive=1 page_sensitive=1 page=events
|
||||
[events] compare buttons: cached=2 selected=2 compare=1 accumulate=1
|
||||
[events] compare: kind=1 rows=1 channels=8 samples0=32964 traces0=4 traces1=-1 window=2.000 recording=1 pane=1
|
||||
[events] dock events: rows=2 pane_rows=2 same_titles=1 sensitive=1 dock_w=268 on_screen=0 marked=-1 members=0 max_ok=1 expected_marked=-1 at=two events latched
|
||||
[events] compare: kind=1 rows=1 channels=8 samples0=32964 traces0=4 traces1=-1 window=2.000 recording=1 page=events
|
||||
[events] dock events: rows=2 pane_rows=2 same_titles=1 sensitive=1 dock_w=268 on_screen=0 marked=-1 members=2 max_ok=1 expected_marked=-1 at=comparison up
|
||||
[events] compare plots: area_b=1 rows_a=1 rows_b=1 list=4 x_a=[-2.000,0.000] x_b=[-2.000,0.000] same_x=1
|
||||
[events] compare sync: y_a=-3.500 y_b=-3.500 mirrored=1
|
||||
[events] accumulate: kind=2 rows=1 channels=8 samples0=32964 selected=2 traces0=8 window=2.000
|
||||
[events] group left: replaying=0 kind=0 waveform=0
|
||||
[events] live restored (after a group): window=0.500000 (want 0.500000) trigger=1 (want 1) channel='square' (want 'square') level=0 (want 0) enabled=1 armed=1 pinned=0 held=0 mode=2 pending=0 gen=22/16
|
||||
[events] dock replay: clicked=1 replaying=1 (second click, live before)
|
||||
[events] dock events: rows=2 pane_rows=2 same_titles=1 sensitive=1 dock_w=268 on_screen=1 marked=1 members=0 max_ok=1 expected_marked=1 at=second dock click
|
||||
```
|
||||
|
||||
`thumb_size` is the built size of the row's picture: `kEventThumbW` wide, and as tall as the *stored*
|
||||
@@ -1628,8 +1638,18 @@ reads `138x75`) as well as with the CSS border and the display's scale factor. W
|
||||
`0x0`, and a height pinned to the floor for *every* event says the aspect fit was dropped and tall
|
||||
previews are being squashed again.
|
||||
|
||||
**The E-1608 never latches an event** (its stream carries no trigger edge to record): on `--source e1608` the probe reads
|
||||
`n=0`, the `recording on` lines read `1 1 1` (recording is on, so the block is available), and the two
|
||||
`recording off` lines read `available=0 dock_sensitive=0 page_sensitive=0 page=events` -- the page is open and
|
||||
greyed, the "disabled, not hidden" path -- while `activated row0:` reads `replaying=0 ... marker=-1000000000000.000
|
||||
thumbnail=0 rows_kept=0` and the `dock events:` / `dock replay:` lines read `rows=0`, `clicked=0 replaying=0`. The
|
||||
`page=` of the first availability lines is whichever `source-*` page the kind opened at start-up, not `none`.
|
||||
A UDPS run against a **fresh cache** reads the same `n=0` and `rows=0` on the opening `dir=` / `dock` lines (nothing
|
||||
has latched when they are printed) and fills in from the group leg on; a UDPS run with a populated cache is the one
|
||||
that proves `same_titles` and the dock click on real rows.
|
||||
|
||||
`[events] preview redrawn:` is the repair path, and the probe forces it: step 1 deletes the newest
|
||||
`.bmp`, drops the thumbnail cache and refreshes the pane, so this line is `EventStore::redraw_preview()`
|
||||
`.bmp`, drops the thumbnail cache and refreshes the page's list, so this line is `EventStore::redraw_preview()`
|
||||
drawing the picture again from the `.npy`. `stored` is the file it wrote, read back out of the BMP
|
||||
header — `480` wide and `44 * signals` tall — and `row_drawn=1` says the row got a paintable from it.
|
||||
The probe therefore needs a cache a **previous** run populated: it fires ~4.5 s in, before this run's
|
||||
@@ -1668,23 +1688,25 @@ session that then disagrees with it takes the pair apart.
|
||||
| `signals`, `samples` | the event's channels and rows | a table built from the *rows* instead of the snapshot: only what happened to be plotted |
|
||||
| `thumb_bytes` | a real 24-bit BMP, `54 + 480 * (44 * signals) * 3` rounded up per row to a multiple of four -- so it *grows with the signal count* (190134 at three signals, 380214 at six) | a preview that is empty or missing -- the widget snapshot of a `GtkGLArea`, which is NULL -- or a picture back on a fixed total height, which gave a six-signal event 26 px a signal and an eight-signal one 20: a smear, not a waveform |
|
||||
| `recorded` | 1 while the trigger panel's switch is on | the switch not wired, or recording defaulting off (it must be on: the cache is for the event you did not expect) |
|
||||
| `pane` | 1 once the history button has been clicked | the button's handler not revealing the pane |
|
||||
| `live` | `button_visible=0 pane=0** | a pane or a button on screen with no trigger mode: a column of nothing beside a live plot |
|
||||
| `after trigger on` | `button_visible=1 pane=0` | the pane revealed by trigger mode alone (it must be on demand from the toolbar button), or the button missing in trigger mode |
|
||||
| `recording off, trigger off` and `recording off, trigger on` | `button_visible=1 pane=1 recording=0` in both **once the cache holds anything** (the probe's own run does by this point) | the button going away with the switch when events are already written -- an event on disk outlives the switch that wrote it -- or `pane_w` not being 240 with the pane open. With an *empty* cache the same two lines read `button_visible=0 pane=0`, which is the empty column the item exists to prevent |
|
||||
| `page` (on the `dir=` line) | 1 once the `events` page is open (the probe opens it with `open_events_page()`) | a page that did not open, or one that opened before its list was refreshed |
|
||||
| `plot_w_stable`, `plot_w=a/b` | 1, with the plot area's width before and after the first latch equal and both > 0 (756/756 on the documented window) | events squeezing the plot again (the old pane did), or two unrealised `-1` widths read as equal (the probe requires > 0) |
|
||||
| `available`, `dock_sensitive`, `page_sensitive` (the lines headed `live, recording off`, `recording on, no trigger`, `after trigger on`, `after events page opened`, `recording off, trigger off/on`) | the three equal on every line, because both views follow `sync_events_availability()`: `0 0 0` live with recording off and an empty cache; `1 1 1` once recording is on, **and it stays `1 1 1` in trigger mode and with the page open** (a replay or a group leaves trigger mode, and a block that greyed itself under that click is the trap) | the dock block and the page disagreeing (one of the two not wired to the sync), or the block going `0` under trigger mode. After recording is switched off the lines read `1 1 1` **once the cache holds anything** (the probe's own run does by this point): an event on disk outlives the switch that wrote it. With an *empty* cache they read `0 0 0` -- insensitive, **never hidden** ("disable, do not hide") |
|
||||
| `page` (on the availability lines) | `none` until the probe opens the page, then `events` | the page not registered (`open_events_page()` returning false), or a page left open by an earlier leg |
|
||||
| `open alone` | `page=events page_box=` a width near the slide-over's (347 on the documented window) | the page's box measuring 0 (an unrevealed `GtkRevealer` as the content instead of `events_box`), or a page not opened at all |
|
||||
| `dock events` | `same_titles=1`, `rows` equal `pane_rows` (both capped at 24: `pane_rows` is cut to the dock's `kDockEventsMax`), `sensitive=1`, **`dock_w` below 280** (268: the block fits the fixed dock; more means a row that no longer ellipsizes), `max_ok=1` (dock children <= 24 + the "N more" row). `on_screen` counts filled markers: **1 after a replay, 0 while live or in a group**; `marked` is the index of the first filled row (-1 when none), `expected_marked=1` when the filled row is the replayed record's file (-1 where no replay is named); `members` counts hollow markers: **2 with a comparison up**, 0 otherwise. The `at=` text names the moment | `same_titles=0` is the two lists rebuilt from different data or one of them stale (the dock follows every latch, the page only while open or on Manage -- a latch landing between the page's refresh and the read can show `rows=9 pane_rows=8` once, which is a probe-time race, not a defect, if it does not persist); `on_screen` above 1 is two filled markers (`event_on_screen` not shared with the title); `members=0` in a comparison is the group not marking its members; `max_ok=0` is the cap lost |
|
||||
| `dock replay` | `clicked=1 replaying=1 waveform_changed=1`; the second line, from live, reads `clicked=1 replaying=1` | the dock row's click not reaching `replay_event()` (`clicked=0` with a non-empty list), or the click rebuilding the dock list under its own handler. With an empty cache (`rows=0`) it reads `clicked=0 replaying=0`, which is not a defect |
|
||||
| `thumbnail`, `thumb_size`, `thumb_visible` | 1, a non-zero size, and 1 | the picture loaded through `gtk_picture_set_filename()`, which fails silently (the paint-able is there, the allocation is there, nothing is drawn), or a `GtkPicture` given a size request and no cap -- its natural size is its content, so the row's text is squeezed off. `thumb_size` is the reading that catches both. It also catches the cache never being *created*: `event_preview_texture()` answers from the cache or not at all, so a reader that does not create one leaves **every** row blank, which is what `thumbnail=0` meant for as long as it read 0 |
|
||||
| `preview redrawn` | `stored=480x<44*signals>` and `row_drawn=1` | a preview that cannot be repaired. The `.npy` is the event and the `.bmp` is only a picture of it, so a write that failed, a truncated file or a cache from an older build must be redrawn rather than shown as a blank row -- `row_drawn=0` is a row the user reported as "sometimes missing" |
|
||||
| `axis`, `marker`, `pane_box` (in `activated row0`) | `[-pre, +post]` of the event just opened, `marker=0.000`, and a `pane_box` close to `pane_w` | an opened event framed on something other than its own pin; a trigger line left **parked** (a large negative) rather than on the edge, which is a file opened without its marker; or a pane whose *rendered* width is 0 while the divider says 240 -- the position is what was asked for, `pane_box` is what the pane actually got, and a divider at 240 with a zero-width pane is still no pane |
|
||||
| `axis`, `marker` (in `activated row0`) | `[-pre, +post]` of the event just opened, `marker=0.000` | an opened event framed on something other than its own pin; a trigger line left **parked** (a large negative) rather than on the edge, which is a file opened without its marker. `page=events` and `page_box` (the page's width, as in `open alone`) say the page is still up and has a width: a replay from the page must not close it |
|
||||
| `rows_kept`, `selected` (in `activated row0`) | `rows_kept=1` and `selected=N/N` -- the counts either side of the activation equal | the list **rebuilt** by the activation. It is the row *object* that is compared, not its index: a rebuilt list has a row 0 too, and only identity tells a list that was left alone from one that was torn down under the user's scroll position and their comparison selection. `mark_events_on_screen()` must rewrite the titles in place |
|
||||
| `replaying` (last line) | 0 | a replay with no way back: *Live* must start a fresh client and thread |
|
||||
| `window`, `trigger`, `channel`, `level` (in `live restored`) | each equal to its `(want …)` | the way back out of a file view discarding what the live view was set to. One wrong half names which writer won: a `window` that reads the **session file's** value is the deferred `apply_layout()` beating the restore; `trigger=0` is the toggle the file view switched off and nobody switched back; an empty or shifted `channel` is an index restored instead of a name, after a rebuild renumbered (or a group renamed) the channels; and a `level` that moved on its own is `rebuild_trigger_signals()` re-defaulting it because `trigger_level_touched` was not restored with the configuration |
|
||||
| `pending`, `gen` (in `live restored`) | `pending=0`, and the two generations different | the restore still waiting. It is gated on the stream's first CONFIG, so `pending=1` says the channel list the stored trigger name needs never arrived -- with `channels=0` behind it, a session file that enabled nothing |
|
||||
| `pinned`, `held`, `mode` (in `live restored`) | `pinned=0`, **or** `pinned=1` with `held=1` | a file's pin surviving the way back, which is a live view that cannot be navigated. `apply_time_window()` drops it; the second reading is the legitimate case -- a restored trigger that was *on* can latch again before the probe reads, and a held capture is pinned, correctly |
|
||||
| `pane_w` | the divider's position: 0 while the pane is closed, its remembered width (240 by default) while it is open | the pane reserving a column while closed (a paned hands its start child its minimum unless the position says otherwise), or a width that is not remembered |
|
||||
| `tick` | 1 in live mode, **0** once a file is on screen | the signal list still offering "acquire this signal" for samples that are already stored |
|
||||
| `window` (in `activated row0`) | the **event's** span -- the probe sets 0.5 s just before the click, so reading the event's span says opening it framed it | a replay that leaves the previous window alone, or one that restates it every frame instead of once |
|
||||
| `compare buttons` | `selected=2` and both buttons sensitive | the buttons reading something other than the selection, or staying insensitive with a valid pair selected |
|
||||
| `compare` | `kind=1 rows=1 channels=8 samples0=33045 traces0=4`, `pane=1` | `samples0` above the stamps pushed -- a live producer left running, pushing four stream signals into the group's eight channels; `rows=1` because the layout is the user's and must not be re-derived; `traces0=4` because the four slots the layout holds are renamed, not replaced |
|
||||
| `compare` | `kind=1 rows=1 channels=8 samples0=33045 traces0=4`, `page=events` | `samples0` above the stamps pushed -- a live producer left running, pushing four stream signals into the group's eight channels; `rows=1` because the layout is the user's and must not be re-derived; `traces0=4` because the four slots the layout holds are renamed, not replaced |
|
||||
| `compare plots` | `area_b=1 rows_a=1 rows_b=1 list=4 same_x=1` | no second plot on screen, a second plot with a different row count, two plots not on one x window, or `list` above the signal count -- the panel listing each event's copy of a signal as a signal of its own |
|
||||
| `compare sync` | `mirrored=1`, with `y_a` and `y_b` equal to the range the probe set on the first plot | the mirror not running, or running the wrong way: a y (or x span) change in one plot that the other never sees |
|
||||
| `accumulate` | `kind=2 rows=1 channels=8 samples0=33045 traces0=8 window=2.000` | the second event's layers not landing: `traces0` stays at the four signals the layout holds when the slots were not free, or the row named after a previous group's suffix so nothing binds |
|
||||
@@ -2360,14 +2382,15 @@ is what the compact-row CSS rule is for.
|
||||
## `events pane:`
|
||||
|
||||
```
|
||||
[udpscope] events pane: compare=1 accumulate=1 list=1 revealer=1 nested=1
|
||||
[udpscope] events pane: compare=1 accumulate=1 list=1 nested=1
|
||||
```
|
||||
|
||||
The events pane's frame read from the window: the two group actions, the list, and the revealer the `.ui`
|
||||
builds, plus `nested` — the list being *inside* that revealer, which is the one thing a lost id cannot show
|
||||
(a list parented elsewhere reads 1 everywhere else). `[events]` is the other half, and it drives the pane end
|
||||
to end: recording off, recording on, the trigger, the history button that reveals it, a row click and the
|
||||
replay, then Compare and Accumulate — so the file's buttons are read by being used.
|
||||
The `events` page's frame read from the window (the page's content is `events_box` of `events-pane.ui`; there is
|
||||
no revealer, divider or header toggle any more, so none is read): the two group actions, the list, plus `nested`
|
||||
— the list being *inside* `events_box`, which is the one thing a lost id cannot show (a list parented elsewhere
|
||||
reads 1 everywhere else). **All four must read 1.** `[events]` is the other half, and it drives the page end
|
||||
to end: recording off, recording on, the trigger, the page opened with `open_events_page()`, a row activation
|
||||
and the replay, then Compare and Accumulate — so the file's buttons are read by being used.
|
||||
|
||||
## `[windowpop]`
|
||||
|
||||
|
||||
+34
-25
@@ -433,14 +433,14 @@ precedent, not an exception.
|
||||
config that has seen the first-run tour, which would close the page it opens (see the next-but-one bullet).
|
||||
The reading's own entry is in `probe-readings.md`. (Probe name `dock_section_probe`; the variable and the
|
||||
`[side-tab-probe]` tag keep their old names so existing scripts keep working. The measurements used to be a
|
||||
dock section the probe scrolled out of view, with padding; the dock now holds only readouts, so there is
|
||||
dock section the probe scrolled out of view, with padding; the dock now holds the readouts and the Events block, so there is
|
||||
no scroll and no padding.)
|
||||
- `UDPSCOPE_PROBE_CURSORS_MS` and `UDPSCOPE_PROBE_MEASURES_MS` also print the dock's copy of what they read:
|
||||
a `dock cursors:` line after every `[cursors-probe]` report and a `dock measures:` line at the
|
||||
measures probe's added / settled / final stops. **`same_text=1` on every one** is the assertion that the
|
||||
dock and the page show one string; `fits=1` that the Readouts box is no wider than the dock. Entries in
|
||||
`probe-readings.md`. The exit chain also prints one `[readouts]` line (pages registered, the dock's one
|
||||
child, the dock's width, the pages' sensitivity, whether the stream has signals) on every exit-dump run.
|
||||
`probe-readings.md`. The exit chain also prints one `[readouts]` line (pages registered, the dock page's three
|
||||
children, the dock's width, the pages' sensitivity, whether the stream has signals) on every exit-dump run.
|
||||
- `UDPSCOPE_PROBE_DOCK_SESSION_MS=2500` with `UDPSCOPE_EXIT_AFTER_MS=8000` -- the session file's `dock` key,
|
||||
end to end: the dock hidden, saved and reloaded; shown again; and a file from the previous version
|
||||
(`side_tab = "measure"`, no `dock`) loaded without an error. Three `[dock-session]` lines, each `ok=1`;
|
||||
@@ -721,41 +721,50 @@ as distinct from the fault injection below, which exercises ingest:
|
||||
leg of it (`title='Opening <basename>' detail='reading the recording'`, before `waveform loaded:`).
|
||||
A run that names its source with `--server` prints **no** `loading:` line at all: every other probe
|
||||
here is on that path and must stay on it.
|
||||
- **The event cache round-trips, and only where it is available.** `UDPSCOPE_PROBE_EVENTS_MS` reads
|
||||
the pane's availability first, in the state the app starts in -- `[events] live: button_visible=0
|
||||
pane=0 recording=1`, **no pane and no button without trigger mode** -- then turns trigger mode on
|
||||
through the real toggle (`[events] after trigger on: button_visible=1 pane=0`: the button appears,
|
||||
the pane stays collapsed) and clicks the button, waits for a latch, then reads the cache and replays
|
||||
the newest event through the pane's own row:
|
||||
- **The event cache round-trips, and only where it is available.** `UDPSCOPE_PROBE_EVENTS_MS` needs a
|
||||
config that has seen the tour (it opens a slide-over page) and a throwaway `XDG_CACHE_HOME`. It reads the
|
||||
availability first, in the state the app starts in -- `[events] live, recording off: available=0
|
||||
dock_sensitive=0 page_sensitive=0 page=none` (the dock block and the page agree, both insensitive and neither
|
||||
hidden) -- then switches recording on (`available=1 dock_sensitive=1 page_sensitive=1`; **trigger mode is not
|
||||
a condition**, and the line after the trigger is turned on reads the same), opens the `events` page with
|
||||
`open_events_page()` (`page=events`, `[events] open alone: page=events page_box=347`), waits for a latch, then
|
||||
reads the cache and replays the newest event through the page's own row:
|
||||
`[events] dir=… n=1 newest: dur=2.000000 pre=1.000000 post=1.000000 edge=… signals=4 samples=20001
|
||||
rate=10000 seq=1 thumb_bytes=230454 recorded=1 pane=1`, then
|
||||
`[events] activated row0: replaying=1 thumbnail=1 thumb_size=108x36 thumb_visible=1` (the size is
|
||||
what says the picture is *on screen*: a paint-able alone is not), `[events] replayed: channels=4
|
||||
samples=20001 replaying=1 waveform=1` and `[events] live again: replaying=0 waveform=0`. It ends on
|
||||
the pane's availability, through the trigger panel's switch: `[events] recording off, trigger off` and
|
||||
`[events] recording off, trigger on` -- once the cache holds events (the probe's own run has written
|
||||
some by this point) both read `button_visible=1 pane=1 recording=0`, because a button that shows the
|
||||
cache must not go away with the switch that wrote it; against an *empty* cache the same two lines read
|
||||
`button_visible=0 pane=0`, which is the empty column the item exists to prevent. **`samples` must equal the
|
||||
export's row count for the same window** (`20001` for a 2 s event at 10 kS/s), `thumb_bytes` must be
|
||||
a real BMP (`54 + 480*160*3`-ish, i.e. hundreds of kB, not 0), `thumbnail` must be 1 (the row loaded
|
||||
the picture) and the last line must read `replaying=0` -- a replay with no way back is a one-way
|
||||
rate=10000 seq=1 thumb_bytes=230454 recorded=1 page=1 plot_w_stable=1 plot_w=756/756` (**`plot_w_stable=1`**: the
|
||||
first latch did not move the plot, which the old pane did), then
|
||||
`[events] activated row0: replaying=1 page=events … thumbnail=1 thumb_size=138x46 thumb_visible=1` (the size is
|
||||
what says the picture is *on screen*: a paint-able alone is not), the dock's reading of the same cache --
|
||||
`[events] dock events: rows=N pane_rows=N same_titles=1 sensitive=1 dock_w=268 on_screen=1 … at=after pane
|
||||
replay` -- then **one click on a dock row**, `[events] dock replay: clicked=1 replaying=1 waveform_changed=1`,
|
||||
and the dock read again (`marked=1`, `expected_marked=1`: exactly one filled marker, on the replayed record),
|
||||
`[events] replayed: channels=4 samples=20001 replaying=1 waveform=1` and `[events] live again: replaying=0
|
||||
waveform=0`. It ends on the availability, through the trigger panel's switch: `[events] recording off,
|
||||
trigger off` and `[events] recording off, trigger on` -- once the cache holds events (the probe's own run has
|
||||
written some by this point) both read `available=1 dock_sensitive=1 page_sensitive=1`, because a block that
|
||||
shows the cache must not go away with the switch that wrote it; against an *empty* cache (and on the E-1608,
|
||||
which never latches) the same two lines read `0 0 0` with the page still open -- insensitive, never hidden.
|
||||
**`samples` must equal the export's row count for the same window** (`20001` for a 2 s event at 10 kS/s),
|
||||
`thumb_bytes` must be a real BMP (`54 + 480*160*3`-ish, i.e. hundreds of kB, not 0), `thumbnail` must be 1 (the
|
||||
row loaded the picture) and the last line must read `replaying=0` -- a replay with no way back is a one-way
|
||||
door. It then **waits for a second latch** (bounded: twelve retries) and drives the two group layouts
|
||||
through the buttons: `[events] compare buttons: cached=2 selected=2 compare=1 accumulate=1` (the
|
||||
buttons read the selection), `[events] halve`… `[events] halves a_first=20001 a_second=0 b_first=0
|
||||
b_second=20001` -- **the two halves of the ring, and the reading that says a comparison's gap is a
|
||||
gap: the first event's channel is finite only in the first half and the second's only in the second**
|
||||
-- `[events] compare: kind=1 rows=4 channels=8 samples0=40002 traces0=2 traces1=2 window=4.000
|
||||
recording=1 pane=1` (`samples0` must be exactly the stamps pushed, **not more**: a live producer left
|
||||
running pushes its own four signals into the group's eight channels), `[events] compare plots:
|
||||
recording=1 page=events` (`samples0` must be exactly the stamps pushed, **not more**: a live producer left
|
||||
running pushes its own four signals into the group's eight channels), the dock line `members=2 … at=comparison
|
||||
up` (the hollow markers on the group's members, which only the dock shows), `[events] compare plots:
|
||||
area_b=1 rows_a=1 rows_b=1 x_a=[-2.000,0.000] x_b=[-2.000,0.000] same_x=1` (the second plot exists, is
|
||||
on screen, mirrors the row count, and both are on one x window), then the sync -- the probe sets a y
|
||||
range nobody would arrive at by accident on the *first* plot and reads the second a few frames later:
|
||||
`[events] compare sync: y_a=-3.500 y_b=-3.500 mirrored=1` -- and `[events] accumulate: kind=2 rows=1
|
||||
channels=8 samples0=33045 selected=2 traces0=8 window=2.000` (`traces0=8`: the four signals the layout
|
||||
holds plus one layer each for the second event, **in the same row**, which is what an accumulation
|
||||
means). Run it with a throwaway
|
||||
`XDG_CACHE_HOME` and `UDPSCOPE_EXIT_AFTER_MS` long enough for the second latch (20 s).
|
||||
means). The exit dump adds `events pane: compare=1 accumulate=1 list=1 nested=1` and the readouts line's
|
||||
`dock_children=3`. Run it with a throwaway
|
||||
`XDG_CACHE_HOME` and `UDPSCOPE_EXIT_AFTER_MS` long enough for the second latch (20 s). The dock row cap
|
||||
(24) is only exercised by a cache that has filled past it: `max_ok=1`.
|
||||
- **A waveform round-trips.** Export from a live run (`UDPSCOPE_EXPORT_MS` with
|
||||
`UDPSCOPE_EXPORT_PATH`, against the mock at 10 kS/s), then open that file with `--open` and an
|
||||
exit dump. The load must print
|
||||
|
||||
Reference in new issue
Block a user