Feature/source plugins #1
No files matched your search
@@ -2915,9 +2915,11 @@ now — they are listed so they are not mistaken for anything else:
|
|||||||
unlocked, because configuring the host is what a first run is for. Each pane's heading also carries its **own switch**, the same control as the
|
unlocked, because configuring the host is what a first run is for. Each pane's heading also carries its **own switch**, the same control as the
|
||||||
header bar's button — clicking either moves both — because a pane that is *disabled* while its
|
header bar's button — clicking either moves both — because a pane that is *disabled* while its
|
||||||
feature is off is a pane whose controls you would otherwise have to leave to turn it back on. The
|
feature is off is a pane whose controls you would otherwise have to leave to turn it back on. The
|
||||||
switcher's policy is `wide` and each page has a title and no icon (the switcher's own placeholder icon is hidden). With the math
|
sidebar is one **dock** now, with no tab switcher: the signal list, the cursors and the measurements
|
||||||
|
in one scroller, shown from the start and insensitive until the stream has signals; `m` and `g`
|
||||||
|
scroll it to the cursors and back to the signal list. With the math
|
||||||
list folded into the signal list under a `MATH` divider whose `+` creates a channel — a derived channel already *is* a channel in
|
list folded into the signal list under a `MATH` divider whose `+` creates a channel — a derived channel already *is* a channel in
|
||||||
the model, so the two lists were the same list drawn twice. A tab is one-of-*N*, so no
|
the model, so the two lists were the same list drawn twice. No
|
||||||
toggle hides a pane any more: cursors-off **disables** its readout and leaves it in place, and
|
toggle hides a pane any more: cursors-off **disables** its readout and leaves it in place, and
|
||||||
trigger-off leaves the trigger panel fully editable — a trigger is configured *before* it is
|
trigger-off leaves the trigger panel fully editable — a trigger is configured *before* it is
|
||||||
switched on, and the locked view says `stream (trigger off)` instead of the settings — and the measurements' and math panes' toolbar toggles are gone with the panes.
|
switched on, and the locked view says `stream (trigger off)` instead of the settings — and the measurements' and math panes' toolbar toggles are gone with the panes.
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ What feeds `Acquisition` is a `DataSource`, one of the registered *kinds*.
|
|||||||
`src/source/udps_*.cpp` (`grep -rn "udps_" src` outside those files is a leak of the old coupling).
|
`src/source/udps_*.cpp` (`grep -rn "udps_" src` outside those files is a leak of the old coupling).
|
||||||
- The frame is **single-timebase**. A multi-timebase source (ABScope) will change the interface and
|
- The frame is **single-timebase**. A multi-timebase source (ABScope) will change the interface and
|
||||||
the data path together; do not bend one frame to carry two clocks.
|
the data path together; do not bend one frame to carry two clocks.
|
||||||
- A kind's capability panels are pages of `side_stack`, named `source-<id>`. A disconnected source
|
- A kind's capability panels are pages of the slide-over (`slideover_add_page`), named `source-<id>`. A disconnected source
|
||||||
leaves them **insensitive, not hidden** (`apply_source_panels` in `src/ui_source.cpp`).
|
leaves them **insensitive, not hidden** (`apply_source_panels` in `src/ui_source.cpp`).
|
||||||
- Session file naming for a non-UDPS kind: the pseudo-host is `<kind>-<identity>` (characters outside
|
- Session file naming for a non-UDPS kind: the pseudo-host is `<kind>-<identity>` (characters outside
|
||||||
`[A-Za-z0-9._-]` become `_`) on port **1** (0 is rejected by the session-file parser), the kind is
|
`[A-Za-z0-9._-]` become `_`) on port **1** (0 is rejected by the session-file parser), the kind is
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
|||||||
runs after the window is destroyed and asking GTK about a widget then is four criticals, which is
|
runs after the window is destroyed and asking GTK about a widget then is four criticals, which is
|
||||||
how the first version of that line earned them. What stays in the menu is what acts on the *data*
|
how the first version of that line earned them. What stays in the menu is what acts on the *data*
|
||||||
(Fit, Export, Configurations) and the one editor that is not a pane (the layout editor): the signal list
|
(Fit, Export, Configurations) and the one editor that is not a pane (the layout editor): the signal list
|
||||||
is a *tab* of the sidebar, so there is nothing left to reveal or hide about it. The Arm/Disarm button in the trigger panel still has text.
|
is a section of the sidebar's dock, so there is nothing left to reveal or hide about it. The Arm/Disarm button in the trigger panel still has text.
|
||||||
- **Five of this app's icons are its own, and they are a `GResource` compiled into the
|
- **Five of this app's icons are its own, and they are a `GResource` compiled into the
|
||||||
binary.** No icon theme has a glyph for an oscilloscope trigger, a pair of cursors or a
|
binary.** No icon theme has a glyph for an oscilloscope trigger, a pair of cursors or a
|
||||||
measurement ruler, and the stock names those buttons used to carry said something else
|
measurement ruler, and the stock names those buttons used to carry said something else
|
||||||
@@ -61,9 +61,9 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
|||||||
an icon with a live user is not an orphan, whatever the toggle that introduced it did -- and it is
|
an icon with a live user is not an orphan, whatever the toggle that introduced it did -- and it is
|
||||||
the Preferences window's icon.
|
the Preferences window's icon.
|
||||||
|
|
||||||
The sidebar's **tabs have no icons any more**: two glyphs (`udpscope-tab-measure-symbolic`,
|
The sidebar has **no tabs any more** (it is one dock): two tab glyphs (`udpscope-tab-measure-symbolic`,
|
||||||
`udpscope-tab-signals-symbolic`, each a 32x16 pair) were drawn for them and judged not understandable
|
`udpscope-tab-signals-symbolic`, each a 32x16 pair) were drawn for them and judged not understandable
|
||||||
enough, so the tabs are text and both SVGs were deleted. The *trigger's* own icon was redrawn to the
|
enough, so the tabs were text, and both SVGs were deleted. The *trigger's* own icon was redrawn to the
|
||||||
Signals glyph's picture at that time and kept it.
|
Signals glyph's picture at that time and kept it.
|
||||||
- **A shortcut table row's `accel` is GTK accelerator syntax, and a pointer gesture has
|
- **A shortcut table row's `accel` is GTK accelerator syntax, and a pointer gesture has
|
||||||
none.** `src/shortcuts.cpp`'s strings are handed to `AdwShortcutsItem`, which parses them
|
none.** `src/shortcuts.cpp`'s strings are handed to `AdwShortcutsItem`, which parses them
|
||||||
@@ -229,11 +229,13 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
|||||||
instead of across. `gtk_widget_set_visible()` is what a layout honours, so anything that must not
|
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
|
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.
|
*is* open. The allocated width is how this was found -- the revealer's own measure said 0.
|
||||||
**The sidebar is a tab stack now, and has no `GtkRevealer` left in it**: an `AdwViewSwitcher`
|
**The sidebar is one dock now, and has no `GtkRevealer` left in it**: a single `GtkScrolledWindow`
|
||||||
above an `AdwViewStack`, each page a `GtkScrolledWindow` of its own (the two pages are different
|
(`dock_scroller`) holding the signal list, the cursors and the measurements one under another. It
|
||||||
heights, so one shared scroller would leave a switch scrolled where the new page never asked to
|
replaced an `AdwViewSwitcher` over an `AdwViewStack`, whose four text tabs did not fit 320 px (and a source
|
||||||
be). The reason is the one this trap teaches: five sections competing for one 320 px column left
|
can add more); a source kind's own panels are slide-over pages, not dock sections. The reason is the one
|
||||||
every one of them short of room. A *feature's* on/off is no longer a visibility at all -- the
|
this trap teaches: sections competing for one 320 px column each left every one of them short of room, and
|
||||||
|
a scroller gives each its natural height. `m` and `g` scroll the dock to the cursors and to the signal list
|
||||||
|
(`dock_show_section()`). A *feature's* on/off is no longer a visibility at all -- the
|
||||||
**cursors'** toggle sets its pane's **body** insensitive -- the readout has nothing to show with the
|
**cursors'** toggle sets its pane's **body** insensitive -- the readout has nothing to show with the
|
||||||
instruments off -- and the *body* and not the pane, because the pane's heading carries its own switch
|
instruments off -- and the *body* and not the pane, because the pane's heading carries its own switch
|
||||||
(see the next bullet). The **trigger's body is deliberately never disabled**: a trigger is configured
|
(see the next bullet). The **trigger's body is deliberately never disabled**: a trigger is configured
|
||||||
@@ -241,23 +243,15 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
|||||||
ordinary way to work. It used to go insensitive with the mode, which left the settings unreachable
|
ordinary way to work. It used to go insensitive with the mode, which left the settings unreachable
|
||||||
exactly when a user wanted to change them; `trigger panel sensitive …=1` is the regression guard, and a
|
exactly when a user wanted to change them; `trigger panel sensitive …=1` is the regression guard, and a
|
||||||
`0` there means the configuration has been locked out again. The
|
`0` there means the configuration has been locked out again. The
|
||||||
trap itself does not go away with the widget: a stack's hidden page measures 0x0 too and is still
|
trap itself does not go away with the widget: a section scrolled out of view is still laid out, but
|
||||||
given the leftover width, so **ask about state, not about size** carries over unchanged. Every
|
**ask about state, not about size** still holds for anything a tick reads.
|
||||||
allocation read in the sidebar is a debug capture, and each reads 0 on the page that is *not*
|
- **The dock is shown but insensitive until the stream has signals ("disable, do not hide").**
|
||||||
current -- a change in what the reading means, not a defect, and no functional code may depend on
|
`update_side_visibility()` and the tick's per-frame block set the column's visibility from the user's wish
|
||||||
those numbers.
|
alone (`sidebar_wanted`) and the dock's *sensitivity* from `have_signals`; the toolbar's Dock button stays
|
||||||
- **The sidebar's tabs are text: `AdwViewSwitcherPolicy.wide`, a title on every page, no icon.** The
|
sensitive throughout. The wish is persisted (`dock = true|false` in the session file) and a change of it
|
||||||
built-in pages get their titles in the `.ui` and a source kind's pages from `SourcePanel::title`
|
schedules a save. There is no tab icon to keep clear any more: the `keep_tabs_icon_free()` workaround for
|
||||||
(`adw_view_stack_add_titled`); the page's *name* still identifies it to `select_side_tab()` and the
|
`AdwViewSwitcherButton`'s placeholder `[x]` image went with the switcher, and `dock:` in the dump
|
||||||
session file. An icon-only version existed (policy `narrow`, empty titles, the pills named in code by
|
replaces the `switcher:` line.
|
||||||
`name_side_tabs()`, because `narrow` still draws a label beside an icon); it was reversed because the
|
|
||||||
glyphs were not understandable. A page added with an empty title is a blank pill, and
|
|
||||||
`switcher: … pills=N labels=N images=0 pages=N` reads it as `labels` below `pills`. **No icon at all**
|
|
||||||
is not what "no `icon-name`" gives you: `AdwViewSwitcherButton` always builds a `GtkImage` and falls
|
|
||||||
back to `image-missing` (the `[x]` placeholder, which also squeezed the title to "Meas..."), and no
|
|
||||||
property turns it off. `keep_tabs_icon_free()` (`src/ui_source.cpp`) hides every image in the switcher,
|
|
||||||
re-hides one that is shown again, and re-runs on the stack's `items-changed` (connected *after* the
|
|
||||||
switcher's own, so the new button exists) and on the switcher's `map`; `images=0` is the reading.
|
|
||||||
- **An *overlaid* sidebar covers the plot, so a drag out of it has nowhere to land.** `collapsed` in an
|
- **An *overlaid* sidebar covers the plot, so a drag out of it has nowhere to land.** `collapsed` in an
|
||||||
`AdwOverlaySplitView` means the sidebar sits *on* the plot rather than beside it: the pointer is over the
|
`AdwOverlaySplitView` means the sidebar sits *on* the plot rather than beside it: the pointer is over the
|
||||||
list, never over a drop target, and a signal can be dragged but not dropped. `row_drag_sidebar()` hides
|
list, never over a drop target, and a signal can be dragged but not dropped. `row_drag_sidebar()` hides
|
||||||
@@ -278,7 +272,7 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
|||||||
`sync_panel_switches()` re-reads each pair from the toolbar's state after any change — so clicking
|
`sync_panel_switches()` re-reads each pair from the toolbar's state after any change — so clicking
|
||||||
either end moves both, and `panel switches: trigger=1/1 cursors=1/1` is the reading. Two traps:
|
either end moves both, and `panel switches: trigger=1/1 cursors=1/1` is the reading. Two traps:
|
||||||
a `GtkSwitch` emits `notify::active` for a *programmatic* write as well, so the sync holds a guard
|
a `GtkSwitch` emits `notify::active` for a *programmatic* write as well, so the sync holds a guard
|
||||||
(`App::panel_switch_sync`) or the program's own write comes back as a click; and the switcher must
|
(`App::panel_switch_sync`) or the program's own write comes back as a click; and the switch must
|
||||||
stay *out* of what the pane disables, because a control inside an insensitive parent cannot be
|
stay *out* of what the pane disables, because a control inside an insensitive parent cannot be
|
||||||
clicked — a switch that greyed out with its own pane could never turn it back on. That is why the
|
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.
|
sensitive target is the pane's **body** (`trigger_body`, `cursors_body`) and not the pane.
|
||||||
|
|||||||
@@ -23,10 +23,11 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
|||||||
toggle is the same mistake in one line, and it cannot be told from a real change by a "is this our own
|
toggle is the same mistake in one line, and it cannot be told from a real change by a "is this our own
|
||||||
write" flag either, since the toggle's signal may fire after the write returns. Instead `App::sidebar_wanted`
|
write" flag either, since the toggle's signal may fire after the write returns. Instead `App::sidebar_wanted`
|
||||||
holds the wish (written only by `on_sidebar_toggled`, guarded by `App::sidebar_syncing` around our own
|
holds the wish (written only by `on_sidebar_toggled`, guarded by `App::sidebar_syncing` around our own
|
||||||
`set_active`), `update_side_visibility()` is the single place that turns that wish plus "are there signals"
|
`set_active`), `update_side_visibility()` is the single place that turns that wish into the panel's visibility
|
||||||
into the panel's *and* the button's state together, and `tick()` re-asserts them when the widget disagrees —
|
and "are there signals" into the dock's *sensitivity* (the button stays sensitive), and `tick()` re-asserts both when the widget disagrees —
|
||||||
which is also why a hide *gesture* does not stick: the panel stays where the button says it is. The
|
which is also why a hide *gesture* does not stick: the panel stays where the button says it is. The
|
||||||
no-signals case must leave the wish alone, or a source connecting later finds a sidebar the user never closed.
|
no-signals case must leave the wish alone, or a source connecting later finds a sidebar the user never closed.
|
||||||
|
The wish is the session's `dock` key: a change of it (not a program's own write, and not a restore) schedules a save.
|
||||||
- **`AdwOverlaySplitView:sidebar-width-fraction` is a 0..1 fraction of the window, whatever
|
- **`AdwOverlaySplitView:sidebar-width-fraction` is a 0..1 fraction of the window, whatever
|
||||||
`sidebar-width-unit` says.** Setting a pixel count there (320) is a `GObject` critical, and it silently
|
`sidebar-width-unit` says.** Setting a pixel count there (320) is a `GObject` critical, and it silently
|
||||||
falls back to 0.25 — which reads as "the sidebar is mysteriously 560 px wide" on a wide window with no
|
falls back to 0.25 — which reads as "the sidebar is mysteriously 560 px wide" on a wide window with no
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
|||||||
not freeze a colour that is meant to follow the signal list), **the math channels with their
|
not freeze a colour that is meant to follow the signal list), **the math channels with their
|
||||||
graphs**, **the measurements pane**, and **`measure_stats`** — the measurements statistics
|
graphs**, **the measurements pane**, and **`measure_stats`** — the measurements statistics
|
||||||
toggle; written as a bare top-level boolean (`measure_stats = true/false`), defaults to `false`
|
toggle; written as a bare top-level boolean (`measure_stats = true/false`), defaults to `false`
|
||||||
when absent. Signal identity
|
when absent, and **`dock`** -- the Dock button's state (the user's last word on it, `sidebar_wanted`),
|
||||||
|
a bare top-level boolean written where the sidebar's `side_tab` used to be, default `true`. The parser
|
||||||
|
ignores unknown keys, so a file written before the tabs became a dock (`side_tab = "measure"`) still loads
|
||||||
|
and simply opens with the dock shown; nothing needs to read the old key. Signal identity
|
||||||
is the *name* throughout (a CONFIG renumbers channels), 'enabled' is read off the channels the
|
is the *name* throughout (a CONFIG renumbers channels), 'enabled' is read off the channels the
|
||||||
model built -- an enabled signal is exactly one that produced a channel -- and `apply_layout()`
|
model built -- an enabled signal is exactly one that produced a channel -- and `apply_layout()`
|
||||||
installs the graphs **before** assigning the rows, since a row may hold a math channel's name and
|
installs the graphs **before** assigning the rows, since a row may hold a math channel's name and
|
||||||
|
|||||||
@@ -479,22 +479,18 @@ Two lines of the self-check output are assertions in disguise:
|
|||||||
substitutes a missing-image placeholder, the button still works, and every other line of the
|
substitutes a missing-image placeholder, the button still works, and every other line of the
|
||||||
dump still passes. Changing an icon name is a one-line change in `build_toolbar()` and this
|
dump still passes. Changing an icon name is a one-line change in `build_toolbar()` and this
|
||||||
is the only check that it names something that exists.
|
is the only check that it names something that exists.
|
||||||
- `switcher: policy=wide pills=4 labels=4 images=0 pages=4 size=308x34` (in the exit dump, beside `panels:`) — the
|
- `dock: shown=1 sensitive=1 w=320 scroller_pages=1` (in the exit dump, beside `panels:`) -- the sidebar's dock
|
||||||
sidebar tabs as the switcher actually draws them, and they are **words**: `labels` counts *mapped labels
|
as the last tick found it. It replaces the old `switcher:` line: there is no tab switcher, the sidebar is
|
||||||
with text*, so `pills=labels=pages` says every page has a visible title. **`images` must be `0`**: it
|
one scroller (`dock_scroller`) holding the signal list, the cursors and the measurements. **`shown=1`
|
||||||
counts *mapped* `GtkImage`s inside the switcher. `AdwViewSwitcherButton` builds an icon in every tab, and
|
always** (the dock is on screen from the start; `shown=0` only when the user hid it with the Dock button,
|
||||||
a page with a title and no `icon-name` gets `image-missing` -- the `[x]` placeholder; the tabs hide it
|
which a probe does deliberately); **`sensitive=0` only before the stream has signals** ("disable, do not
|
||||||
(`keep_tabs_icon_free()`, which also covers pages `build_source_panels()` adds). A nonzero `images`
|
hide") -- a run against a port with no sender reads `shown=1 sensitive=0`, a normal run `sensitive=1`;
|
||||||
names a tab still drawing an icon or its placeholder (`images=pages` is the unfixed state), and
|
`w=` is the sidebar's own width (320); **`scroller_pages=1`** counts the scrollers directly under the dock
|
||||||
`pills`/`labels` never saw it. Read it on a source with extra pages (`--source probe` under
|
column and says there is one scroller and no stack. These are captured on the tick or at the exit probe:
|
||||||
`UDPSCOPE_PROBE_SOURCE_KINDS`) as well as on UDPS. `pages` is read from the stack
|
the dump runs after the window is destroyed. A source with extra panels adds *no* dock section: its panels
|
||||||
(2 for UDPS, 3 for the probe source, 4 for the E-1608 with its Analog out and Digital I/O tabs). A
|
are slide-over pages (`source-<id>`), so the line reads the same on the E-1608 and the probe kind.
|
||||||
`labels` below `pills` is a page added with an empty title; `pills=0` means nothing is mapped, which
|
|
||||||
makes the rest of the line meaningless rather than a pass. `policy=wide` is read rather than assumed.
|
|
||||||
`size=` is the bar's own size: a text tab costs about 34 px of height, and a width of 308 shows the
|
|
||||||
four tabs fit the 320 px column.
|
|
||||||
- `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=5 step=0 auto=1 requested=0` (in the exit
|
- `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=5 step=0 auto=1 requested=0` (in the exit
|
||||||
dump, beside `switcher:`) — the help affordance layer as the last tick found it. **`hints=` must equal
|
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
|
the number of ids**, and the ids are the surfaces the contextual help is on: eleven of the window
|
||||||
itself, and `math` only while the math editor is open (its heading lives in a builder that is rebuilt
|
itself, and `math` only while the math editor is open (its heading lives in a builder that is rebuilt
|
||||||
on every opening, so it is not one of the window's own). Four of the eleven are not a pane's *heading*
|
on every opening, so it is not one of the window's own). Four of the eleven are not a pane's *heading*
|
||||||
@@ -538,7 +534,7 @@ Two lines of the self-check output are assertions in disguise:
|
|||||||
[invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)). The writer chains to
|
[invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)). The writer chains to
|
||||||
`g_log_writer_default()`, so every message still reaches stderr exactly as before and the documented
|
`g_log_writer_default()`, so every message still reaches stderr exactly as before and the documented
|
||||||
baselines are read the same way.
|
baselines are read the same way.
|
||||||
- `help figures: trigger_head=X,Y,W,H trigger_summary=… trigger_panel=… signal_list=… side_switcher=… side_stack=… window_button=… status_label=… header_bar=… plot=…` (in the exit
|
- `help figures: trigger_head=X,Y,W,H trigger_summary=… trigger_panel=… signal_list=… side_stack=… window_button=… status_label=… header_bar=… plot=…` (in the exit
|
||||||
dump, beside `help layer:`) — the **window-coordinate** rectangle of each surface the help's annotated
|
dump, beside `help layer:`) — the **window-coordinate** rectangle of each surface the help's annotated
|
||||||
figures label, `x,y,width,height`, or `-1,-1,0,0` for a surface that is **not on screen**. It is
|
figures label, `x,y,width,height`, or `-1,-1,0,0` for a surface that is **not on screen**. It is
|
||||||
measured on the tick while the window is alive (nothing in the dump may call `gtk_widget_*` — see
|
measured on the tick while the window is alive (nothing in the dump may call `gtk_widget_*` — see
|
||||||
@@ -919,9 +915,9 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
|
|||||||
selectable into it (`gtk_widget_get_mapped()` -- a wrong *page* is kept, because selecting its
|
selectable into it (`gtk_widget_get_mapped()` -- a wrong *page* is kept, because selecting its
|
||||||
page reveals it, while a *collapsed pane* is still dropped; see the surface bullets in
|
page reveals it, while a *collapsed pane* is still dropped; see the surface bullets in
|
||||||
[invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)).
|
[invariants-gtk-and-chrome.md](invariants-gtk-and-chrome.md)).
|
||||||
- `[helpui] step=1/5 visible=1 side_tab=signals spot=[x,y WxH] target=[…] contains=1 card=[…] clear=C`
|
- `[helpui] step=1/5 visible=1 slideover=none spot=[x,y WxH] target=[…] contains=1 card=[…] clear=C`
|
||||||
— **in window coordinates**, the frame a screenshot is cropped in, with the sidebar's page beside
|
— **in window coordinates**, the frame a screenshot is cropped in, with the slide-over's open page
|
||||||
it (a stop the walk had to change the page for is readable as such; see `[toursurface]` below).
|
(`none`, or a page id) beside it; see `[toursurface]` below.
|
||||||
**`contains` must be 1** on every step: the spotlight is the target's own rectangle grown three
|
**`contains` must be 1** on every step: the spotlight is the target's own rectangle grown three
|
||||||
pixels. `clear` is the card missing the target, and it is 1 wherever the window has room for the
|
pixels. `clear` is the card missing the target, and it is 1 wherever the window has room for the
|
||||||
card beside or above/below the hole — at the documented run's window (see `help figures:` below)
|
card beside or above/below the hole — at the documented run's window (see `help figures:` below)
|
||||||
@@ -949,34 +945,30 @@ two also catch a row whose accelerator went missing. `settings=` must be a real
|
|||||||
read `mapped=0` (and `0x0`), which is exactly why the header box exists (`search_head` in
|
read `mapped=0` (and `0x0`), which is exactly why the header box exists (`search_head` in
|
||||||
`src/ui/trigger-panel.ui`) and why the hint joins it as its last child. `mapped_header=` is the
|
`src/ui/trigger-panel.ui`) and why the hint joins it as its last child. `mapped_header=` is the
|
||||||
header box beside it, and `hint=WxH` is the same 12x12 the layer's own line reads.
|
header box beside it, and `hint=WxH` is the same 12x12 the layer's own line reads.
|
||||||
- `[toursurface] …` (stderr, `UDPSCOPE_PROBE_TOUR_SURFACE_MS`) — a stop that stands on a page it is
|
- `[toursurface] …` (stderr, `UDPSCOPE_PROBE_TOUR_SURFACE_MS`) -- a stop that stands on a surface it is
|
||||||
not on, entered from the page it is not on. It puts the sidebar on **Measure**, starts the window's
|
not on, entered from the state it is not in. Since the sidebar's tabs became the dock the probe opens the
|
||||||
tour, and walks it a stop per tick (400 ms), because a stack allocates the new page's widgets on
|
`trigger` slide-over page instead of switching a tab, starts the window's tour and walks it a stop per tick
|
||||||
the frame *after* the switch — so each reading is of the page the step left standing, not of the
|
(400 ms). **The tour's own rework for the slide-over is Task 5's**: until its page hook exists the tour
|
||||||
one the switch has just left behind. The lines:
|
has no page stack, so a stop whose target is on a slide-over page is dropped, and the lines below read the
|
||||||
|
dock only. The lines:
|
||||||
|
|
||||||
- `[toursurface] before: side_tab=signals signal_list_mapped=1 running=1` — the state a first run
|
- `[toursurface] before: slideover=none signal_list_mapped=1 running=1` -- the state a first run starts in
|
||||||
starts in (a fresh config's sidebar is on Signals), then the auto-started walk is dismissed.
|
(the dock is on screen), then the auto-started walk is dismissed.
|
||||||
- `[toursurface] on measure: side_tab=measure signal_list_mapped=0 side_stack_mapped=1` — a frame
|
- `[toursurface] on trigger: slideover=trigger signal_list_mapped=1 side_box_mapped=1` -- a frame after the
|
||||||
after the switch: **`signal_list_mapped=0` beside `side_stack_mapped=1`** is the whole premise,
|
page was opened: the dock is still on screen under the slide-over (they are not alternatives).
|
||||||
the list is genuinely off screen while the stack that holds it is on screen.
|
- `[toursurface] tour: visible=1 steps=N step=0 signals_stop=1` -- **`signals_stop=1` is the filter's
|
||||||
- `[toursurface] tour: visible=1 steps=5 step=0 signals_stop=1` — **`signals_stop=1` is the filter's
|
reading**: the signal list is in the dock, which is mapped, so its stop is in the plan.
|
||||||
reading**: a stop whose target is *not mapped* is still in the plan because it names a page the
|
- `[toursurface] step=N/M slideover=<page> target_mapped=D[ target=signal_list]` -- one per stop;
|
||||||
stack can select. `steps=5` is the full table.
|
**the `signals` stop reads `target_mapped=1`**.
|
||||||
- `[toursurface] step=N/5 side_tab=<page> target_mapped=D[ target=signal_list]` — one per stop. The
|
- `[toursurface] signals stop=N/M visible=1 slideover=… spot=[…] target=[…] contains=1 card=[…] clear=1` --
|
||||||
page moves from `measure` to `signals` at the `trigger` stop (the first stop whose target is on
|
the `tour_geometry_line()` the helpui probe uses: **`contains=1` is the assertion** that the spotlight
|
||||||
the Signals page) and stays there; **the `signals` stop reads `target_mapped=1`**.
|
and the card are on the list.
|
||||||
- `[toursurface] signals stop=4/5 visible=1 side_tab=signals spot=[…] target=[…] contains=1
|
- `[toursurface] collapsed: side_box_mapped=0 steps=2 signals_stop=0 side_stop=0` -- the other half of
|
||||||
card=[…] clear=1` — the `tour_geometry_line()` the helpui probe uses, read on the frame *after*
|
the filter, read: with `sidebar_wanted` off (`update_side_visibility()` re-asserts it, so this is how a
|
||||||
the switch: **`side_tab=signals` with `contains=1` is the assertion** that the spotlight and the
|
probe puts the sidebar away for good) the split view unmaps the column and the stops that need the
|
||||||
card are on the list, not on the page the walk came from.
|
dock are dropped while the page-less ones stay. This is what says a *collapsed pane* is dropped.
|
||||||
- `[toursurface] collapsed: side_stack_mapped=0 steps=2 signals_stop=0 side_stop=0` — the other half
|
- `[keys] a: armed 0->1 | space: paused 0->1 | t: trigger panel: 0->1 | c: cursors: 0->1 | m: dock 0->243 (cursors_y=243 reach=2256 at_cursors=1) | g: dock 243->0 at_top=1 | … | editable focused: 'm' refused=1` (stderr, `UDPSCOPE_PROBE_KEYS_MS`) — **every `X->Y` pair must differ** (`g` returns, so its pair reads `243->0`) and **`refused` must be 1**.
|
||||||
of the filter, read: with `sidebar_wanted` off (`update_side_visibility()` re-asserts it, so this
|
The `m`/`g` readings are the dock scroller's vertical adjustment before and after (the probe pads the dock to 3000 px so there is something to scroll, and undoes it at the end): **`m` must land on `cursors_y`, the cursors panel's y in the dock (`at_cursors=1`), and `g` must return to 0 (`at_top=1`)**, which is what says those two keys scroll to a section rather than toggling a pane; a `reach` of 0 would mean the dock fit its viewport and the probe's padding was lost; `t`/`c` read the toolbar toggle's own state, because those two enable a feature and the button is where that state is visible. 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).
|
||||||
is how a probe puts the sidebar away for good) the split view unmaps the column, the stack with
|
|
||||||
it, and the two stops that need the sidebar are dropped while the page-less ones stay. This is
|
|
||||||
what says a *wrong page* and a *collapsed pane* are not the same case.
|
|
||||||
- `[keys] a: armed 0->1 | space: paused 0->1 | t: trigger panel: 0->1 | c: cursors: 0->1 | m: tab signals->measure | g: tab measure->signals | … | editable focused: 'm' refused=1` (stderr, `UDPSCOPE_PROBE_KEYS_MS`) — **every `X->Y` pair must differ** and **`refused` must be 1**.
|
|
||||||
The `m`/`g` pairs are the *tab name* before and after, which is what says those two keys select a tab rather than toggling a pane; `t`/`c` read the toolbar toggle's own state, because those two enable a feature and the button is where that state is visible. 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).
|
|
||||||
- `[color] '<name>' at row R slot S: trace=#RRGGBB chip_agrees=1 (want 1)`, then `[color] override: trace=#123456 chip_agrees=1`, `[color] session: entries=1 rgb=#123456` and `[color] default: trace=#RRGGBB chip_agrees=1` (stderr, `UDPSCOPE_PROBE_COLOR_MS`) — **all three `chip_agrees` must be 1**, the override line's `trace` must be `#123456`, the session line must read `entries=1 rgb=#123456`, and the default line's `trace` must be back to the first line's. Run it with `UDPSCOPE_ENABLE_ALL_MS` ahead of it, or there is no bound trace and the probe prints `no bound trace to recolour` instead of asserting anything; on the standard mock the first line reads `'sine' at row 0 slot 0: trace=#4FA3FF`. Two things make this an assertion rather than a tautology. The colours are read out of **`plot::Plot::trace().color`** — what the renderer will stroke, reached through `Row::plot` *after* `relayout_areas()`, since a new colour is a new figure — and out of the chip's own `<span foreground="…">` markup, not by asking `RowSet::signal_color()` twice, which would pass against a chip that never reached the trace. And the override is `#123456`, deliberately in no palette slot: an override equal to the entry it replaces passes against an implementation that ignores overrides entirely. The session leg round-trips `collect_layout()` → `save_layout()` → `load_layout()` on a temp path and deletes it, because a probe must not leave a colour in the real session file for a later run to inherit.
|
- `[color] '<name>' at row R slot S: trace=#RRGGBB chip_agrees=1 (want 1)`, then `[color] override: trace=#123456 chip_agrees=1`, `[color] session: entries=1 rgb=#123456` and `[color] default: trace=#RRGGBB chip_agrees=1` (stderr, `UDPSCOPE_PROBE_COLOR_MS`) — **all three `chip_agrees` must be 1**, the override line's `trace` must be `#123456`, the session line must read `entries=1 rgb=#123456`, and the default line's `trace` must be back to the first line's. Run it with `UDPSCOPE_ENABLE_ALL_MS` ahead of it, or there is no bound trace and the probe prints `no bound trace to recolour` instead of asserting anything; on the standard mock the first line reads `'sine' at row 0 slot 0: trace=#4FA3FF`. Two things make this an assertion rather than a tautology. The colours are read out of **`plot::Plot::trace().color`** — what the renderer will stroke, reached through `Row::plot` *after* `relayout_areas()`, since a new colour is a new figure — and out of the chip's own `<span foreground="…">` markup, not by asking `RowSet::signal_color()` twice, which would pass against a chip that never reached the trace. And the override is `#123456`, deliberately in no palette slot: an override equal to the entry it replaces passes against an implementation that ignores overrides entirely. The session leg round-trips `collect_layout()` → `save_layout()` → `load_layout()` on a temp path and deletes it, because a probe must not leave a colour in the real session file for a later run to inherit.
|
||||||
- `[reseq-probe] reseq=N late=N stale=N overflows=N holes=N hole_slots=N backfilled=N
|
- `[reseq-probe] reseq=N late=N stale=N overflows=N holes=N hole_slots=N backfilled=N
|
||||||
gaps_refused=N open_holes=N bands=N bands_repaired=N nan_slots=N scanned=N trigger_in_hole=D`
|
gaps_refused=N open_holes=N bands=N bands_repaired=N nan_slots=N scanned=N trigger_in_hole=D`
|
||||||
@@ -1796,18 +1788,16 @@ and `regained` reads 400 ms later.
|
|||||||
## `[side-tab-probe]`
|
## `[side-tab-probe]`
|
||||||
|
|
||||||
```
|
```
|
||||||
[side-tab-probe] page=signals accums=4 baseline recorded
|
[side-tab-probe] section=measure accums=4 baseline recorded (measure at scroll 243 of 2256)
|
||||||
[side-tab-probe] page=signals rows=4 accums=4 advanced=4 min_n=18
|
[side-tab-probe] section=signals rows=4 accums=4 advanced=4 min_n=18
|
||||||
```
|
```
|
||||||
|
|
||||||
`UDPSCOPE_PROBE_SIDE_TAB_MS` is the one reading for the rule that **the statistics accumulator keeps running
|
`UDPSCOPE_PROBE_SIDE_TAB_MS` (the variable and the tag keep their pre-dock names; the function is `dock_section_probe`) is the one reading for the rule that **the statistics accumulator keeps running
|
||||||
on the hidden page**. It switches *to* the Measure tab, forces one pass so there is a baseline, then switches
|
while the Measure section is out of view**. It scrolls the dock *to* the Measure section (`dock_show_section("measure")`), forces one pass so there is a baseline, then scrolls back to Signals and reads again two seconds later. To make the scroll observable on a short window the probe pads `dock_page` to 3000 px (probe only), and it re-arms once after 400 ms so the first reading has the layout.
|
||||||
away to Signals and reads again two seconds later -- with the measurements' page not current, so its widgets
|
|
||||||
are unrealized and measure 0x0.
|
|
||||||
|
|
||||||
- **`advanced=4` is the assertion**: every accumulator gained passes while its page was off screen, and
|
- **`advanced=4` is the assertion**: every accumulator gained passes while its section was out of view, and
|
||||||
`min_n` (the smallest `n` seen) must be non-zero too. **`advanced=0` is the defect**: something gated the
|
`min_n` (the smallest `n` seen) must be non-zero too. **`advanced=0` is the defect**: something gated the
|
||||||
accumulation on the visible tab, so the readout would gain a gap in its min/max/avg for exactly as long as
|
accumulation on the visible section, so the readout would gain a gap in its min/max/avg for exactly as long as
|
||||||
the trigger was on screen. A `Gtk-CRITICAL` here is the other half of the same rule -- a label or an
|
the trigger was on screen. A `Gtk-CRITICAL` here is the other half of the same rule -- a label or an
|
||||||
allocation read on the unrealized page.
|
allocation read on the unrealized page.
|
||||||
- The probe enables a signal **and assigns it to a plot** itself, both deliberately. A signal starts disabled
|
- The probe enables a signal **and assigns it to a plot** itself, both deliberately. A signal starts disabled
|
||||||
@@ -1849,7 +1839,7 @@ kind, so a run without it never shows that kind. The `form` line appears once pe
|
|||||||
`drained=0` is a queue that `poll()` does not drain on some path; `after` greater than `before + 1` is a
|
`drained=0` is a queue that `poll()` does not drain on some path; `after` greater than `before + 1` is a
|
||||||
command run twice.
|
command run twice.
|
||||||
|
|
||||||
The UDPS run has no `source-` page in `side_stack`, because the UDPS kind has no panels.
|
The UDPS run has no `source-` slide-over page, because the UDPS kind has no panels.
|
||||||
|
|
||||||
## `[e1608]`
|
## `[e1608]`
|
||||||
|
|
||||||
@@ -2591,3 +2581,19 @@ The same probe then puts five measurements on the same spiked `sine` as **two wi
|
|||||||
- **`measure windows=2`** -- the five legs ask for corners `0, 50, 0, 0, 50` on one channel, which is two
|
- **`measure windows=2`** -- the five legs ask for corners `0, 50, 0, 0, 50` on one channel, which is two
|
||||||
distinct windows. **`windows=1` means the corner is not part of the key**: the five collapsed onto one walk
|
distinct windows. **`windows=1` means the corner is not part of the key**: the five collapsed onto one walk
|
||||||
and two of them read a signal they did not ask for.
|
and two of them read a signal they did not ask for.
|
||||||
|
|
||||||
|
## `[dock-session]`
|
||||||
|
|
||||||
|
```
|
||||||
|
[dock-session] hidden: wish=0 save_scheduled=1 collected=0 saved=1 loaded=1 read=0 applied=1 split_follows=1 ok=1
|
||||||
|
[dock-session] shown: wish=1 save_scheduled=1 collected=1 saved=1 loaded=1 read=1 applied=1 split_follows=1 ok=1
|
||||||
|
[dock-session] old file (side_tab only): loaded=1 dock_shown=1 default=1 ok=1
|
||||||
|
```
|
||||||
|
|
||||||
|
`UDPSCOPE_PROBE_DOCK_SESSION_MS` reads the one thing the dock persists, the Dock button's state, as `dock = true|false` in the
|
||||||
|
session file (it replaced `side_tab`). Each of the first two lines drives the toggle, then follows the wish through: `save_scheduled=1` is
|
||||||
|
`on_sidebar_toggled` having scheduled a save (a user's wish that was not saved is lost at the next start), `collected` is
|
||||||
|
`collect_layout()`'s `dock_shown`, `saved`/`loaded` the file round trip on a temp path, `read` the loaded value, `applied`
|
||||||
|
the wish after `apply_layout()`, and `split_follows=1` the split view following it. **`ok=1` on all three lines is the assertion.** The third line
|
||||||
|
loads a file as the previous version wrote it (`side_tab = "measure"`): it must load with no error and leave `dock_shown` at its default
|
||||||
|
(`true`) -- the parser ignores unknown keys, and this is what says the old key still does not break a session. The probe writes only to a temp path.
|
||||||
@@ -412,12 +412,19 @@ precedent, not an exception.
|
|||||||
pauses, reports again with `dn=` per measurement, then resets and reports a third time. Three
|
pauses, reports again with `dn=` per measurement, then resets and reports a third time. Three
|
||||||
phases: `phase=0`, `phase=1` (after pause) and `phase=2` (after Reset). Needs
|
phases: `phase=0`, `phase=1` (after pause) and `phase=2` (after Reset). Needs
|
||||||
`UDPSCOPE_ENABLE_ALL_MS` ahead of it and `UDPSCOPE_PROBE_MEASURE_MS` to add measurements first.
|
`UDPSCOPE_ENABLE_ALL_MS` ahead of it and `UDPSCOPE_PROBE_MEASURE_MS` to add measurements first.
|
||||||
- `UDPSCOPE_PROBE_SIDE_TAB_MS=3000` with `UDPSCOPE_EXIT_AFTER_MS=11000` — the measurements on the
|
- `UDPSCOPE_PROBE_SIDE_TAB_MS=3000` with `UDPSCOPE_EXIT_AFTER_MS=11000` — the measurements in the
|
||||||
Measure tab, a forced pass, then the tab switched away and read again two seconds later. **`advanced=4`
|
dock's Measure section, a forced pass, then the dock scrolled back to the signal list (the Measure
|
||||||
is the assertion** (every accumulator gained passes while its page was off screen); `advanced=0` is the
|
section out of view) and read again two seconds later. The probe pads the dock to 3000 px so a scroll is
|
||||||
defect where the accumulation is gated on the visible tab, which would leave a gap in min/max/avg for
|
observable on a short window. **`advanced=4`
|
||||||
|
is the assertion** (every accumulator gained passes while its section was out of view); `advanced=0` is the
|
||||||
|
defect where the accumulation is gated on the visible section, which would leave a gap in min/max/avg for
|
||||||
exactly as long as the trigger was on screen. It enables *and plots* its own signal, so it needs no other
|
exactly as long as the trigger was on screen. It enables *and plots* its own signal, so it needs no other
|
||||||
probe ahead of it. The reading's own entry is in `probe-readings.md`.
|
probe ahead of it. 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.)
|
||||||
|
- `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`;
|
||||||
|
entries in `probe-readings.md`.
|
||||||
- `UDPSCOPE_PROBE_SLIDEOVER_MS=1500` with `UDPSCOPE_EXIT_AFTER_MS=5000` (and `UDPSCOPE_ENABLE_ALL_MS=500`) -- the
|
- `UDPSCOPE_PROBE_SLIDEOVER_MS=1500` with `UDPSCOPE_EXIT_AFTER_MS=5000` (and `UDPSCOPE_ENABLE_ALL_MS=500`) -- the
|
||||||
slide-over panel over the plot's right edge, opened on a throw-away `probe` page, read 0.5 s later. Two
|
slide-over panel over the plot's right edge, opened on a throw-away `probe` page, read 0.5 s later. Two
|
||||||
`[slideover]` lines. Line 1 must read `panel=360x<overlay height> ... edge_flush=1 full_height=1
|
`[slideover]` lines. Line 1 must read `panel=360x<overlay height> ... edge_flush=1 full_height=1
|
||||||
@@ -631,12 +638,12 @@ as distinct from the fault injection below, which exercises ingest:
|
|||||||
bullet) — so a probe run against a fresh config directory will have it on screen for the first
|
bullet) — so a probe run against a fresh config directory will have it on screen for the first
|
||||||
second and a half, and that start *writes* `tour_seen` to whichever config directory the run is
|
second and a half, and that start *writes* `tour_seen` to whichever config directory the run is
|
||||||
using, which is why the second run against the same one reads `auto=0`.
|
using, which is why the second run against the same one reads `auto=0`.
|
||||||
- `UDPSCOPE_PROBE_TOUR_SURFACE_MS` — the tour's *surface* switch: it puts the sidebar on **Measure**,
|
- `UDPSCOPE_PROBE_TOUR_SURFACE_MS` — the tour's *surface* switch: it opens a slide-over page (`trigger`),
|
||||||
starts the window's tour, and walks it a stop per tick to the signals stop, reading the sidebar's
|
starts the window's tour, and walks it a stop per tick to the signals stop, reading the sidebar's
|
||||||
page and the spotlight/card where the walk arrives. It then puts the sidebar *away* and starts the
|
page and the spotlight/card where the walk arrives. It then puts the sidebar *away* and starts the
|
||||||
tour again, which is the same filter's other case (a collapsed pane drops the stops a page switch
|
tour again, which is the same filter's other case (a collapsed pane drops the stops a page switch
|
||||||
could have revealed). `[toursurface] signals stop=4/5 … side_tab=signals … contains=1` and
|
could have revealed). `[toursurface] signals stop=4/5 … slideover=none … contains=1` and
|
||||||
`[toursurface] collapsed: side_stack_mapped=0 steps=2 signals_stop=0` are the two assertions; its
|
`[toursurface] collapsed: side_box_mapped=0 steps=2 signals_stop=0` are the two assertions; its
|
||||||
lines are in [probe-readings.md](probe-readings.md). Run it alone with
|
lines are in [probe-readings.md](probe-readings.md). Run it alone with
|
||||||
`UDPSCOPE_EXIT_AFTER_MS=9000 UDPSCOPE_EXIT_DUMP=1` against a live source.
|
`UDPSCOPE_EXIT_AFTER_MS=9000 UDPSCOPE_EXIT_DUMP=1` against a live source.
|
||||||
- `UDPSCOPE_PROBE_DEFAULTS_MS` — opens **Calibrate** and reads every spin button in it and in the
|
- `UDPSCOPE_PROBE_DEFAULTS_MS` — opens **Calibrate** and reads every spin button in it and in the
|
||||||
|
|||||||
@@ -568,8 +568,8 @@ bool load_layout(const std::string& path, LayoutState& out) {
|
|||||||
}
|
}
|
||||||
} else if (key == "measure_stats") {
|
} else if (key == "measure_stats") {
|
||||||
st.measure_stats = value == "true";
|
st.measure_stats = value == "true";
|
||||||
} else if (key == "side_tab") {
|
} else if (key == "dock") {
|
||||||
st.side_tab = strip_quote(value);
|
st.dock_shown = value == "true";
|
||||||
} else if (key.rfind("trigger_", 0) == 0) {
|
} else if (key.rfind("trigger_", 0) == 0) {
|
||||||
trigger_key(st.trigger, key.substr(8), strip_quote(value));
|
trigger_key(st.trigger, key.substr(8), strip_quote(value));
|
||||||
} else if (key == "multicast") {
|
} else if (key == "multicast") {
|
||||||
@@ -740,7 +740,7 @@ bool save_layout(const std::string& path, const LayoutState& state) {
|
|||||||
o << "]\n";
|
o << "]\n";
|
||||||
}
|
}
|
||||||
o << "measure_stats = " << (state.measure_stats ? "true" : "false") << "\n";
|
o << "measure_stats = " << (state.measure_stats ? "true" : "false") << "\n";
|
||||||
o << "side_tab = \"" << state.side_tab << "\"\n";
|
o << "dock = " << (state.dock_shown ? "true" : "false") << "\n";
|
||||||
// The trigger, settings and state both. Written even when it is off: that is the point of it.
|
// The trigger, settings and state both. Written even when it is off: that is the point of it.
|
||||||
o << "trigger_enabled = " << (state.trigger.enabled ? "true" : "false") << "\n";
|
o << "trigger_enabled = " << (state.trigger.enabled ? "true" : "false") << "\n";
|
||||||
if (!state.trigger.channel.empty()) {
|
if (!state.trigger.channel.empty()) {
|
||||||
|
|||||||
@@ -73,9 +73,10 @@ struct LayoutState {
|
|||||||
/// stream and mean nothing restored against another one.
|
/// stream and mean nothing restored against another one.
|
||||||
bool measure_stats = false;
|
bool measure_stats = false;
|
||||||
|
|
||||||
/// Which sidebar tab was showing: "measure" or "signals". The one thing the panel persists --
|
/// The dock's shown state (the user's last word on the toolbar's Dock button). The one thing the dock
|
||||||
/// no revealer states remain, and the 320 px width is the `.ui`'s.
|
/// persists -- its 320 px width is the `.ui`'s. An older file's `side_tab` key is not read: the parser
|
||||||
std::string side_tab = "signals";
|
/// ignores keys it does not know.
|
||||||
|
bool dock_shown = true;
|
||||||
|
|
||||||
/// The measurements pane, by signal *name* and with the type and options a row was built with.
|
/// The measurements pane, by signal *name* and with the type and options a row was built with.
|
||||||
std::vector<Measure> measures;
|
std::vector<Measure> measures;
|
||||||
|
|||||||
+2
-2
@@ -14,8 +14,8 @@ constexpr Accel kAccels[] = {
|
|||||||
{GDK_KEY_a, 0, AppAction::TriggerArm, "a", "Arm or disarm the trigger", "Acquisition"},
|
{GDK_KEY_a, 0, AppAction::TriggerArm, "a", "Arm or disarm the trigger", "Acquisition"},
|
||||||
{GDK_KEY_t, 0, AppAction::TriggerPanel, "t", "Enable or disable trigger mode", "Acquisition"},
|
{GDK_KEY_t, 0, AppAction::TriggerPanel, "t", "Enable or disable trigger mode", "Acquisition"},
|
||||||
{GDK_KEY_c, 0, AppAction::CursorsPane, "c", "Enable or disable the cursors", "Panes and editors"},
|
{GDK_KEY_c, 0, AppAction::CursorsPane, "c", "Enable or disable the cursors", "Panes and editors"},
|
||||||
{GDK_KEY_m, 0, AppAction::SideTabMeasure, "m", "Show the Measure tab", "Panes and editors"},
|
{GDK_KEY_m, 0, AppAction::ShowMeasure, "m", "Go to Measure", "Panes and editors"},
|
||||||
{GDK_KEY_g, 0, AppAction::SideTabSignals, "g", "Show the Signals tab", "Panes and editors"},
|
{GDK_KEY_g, 0, AppAction::ShowSignals, "g", "Go to Signals", "Panes and editors"},
|
||||||
{GDK_KEY_l, 0, AppAction::LayoutEditor, "l", "Show or hide the layout editor",
|
{GDK_KEY_l, 0, AppAction::LayoutEditor, "l", "Show or hide the layout editor",
|
||||||
"Panes and editors"},
|
"Panes and editors"},
|
||||||
{GDK_KEY_s, 0, AppAction::SidePanel, "s", "Show or hide the side panel",
|
{GDK_KEY_s, 0, AppAction::SidePanel, "s", "Show or hide the side panel",
|
||||||
|
|||||||
+2
-2
@@ -10,8 +10,8 @@ enum class AppAction {
|
|||||||
TriggerArm,
|
TriggerArm,
|
||||||
TriggerPanel,
|
TriggerPanel,
|
||||||
CursorsPane,
|
CursorsPane,
|
||||||
SideTabMeasure,
|
ShowMeasure,
|
||||||
SideTabSignals,
|
ShowSignals,
|
||||||
LayoutEditor,
|
LayoutEditor,
|
||||||
SidePanel,
|
SidePanel,
|
||||||
Export,
|
Export,
|
||||||
|
|||||||
+79
-109
@@ -2864,35 +2864,38 @@ void on_right_click(GtkGestureClick* gesture, int, double x, double y, gpointer
|
|||||||
open_row_menu(*app, row, x, y, widget);
|
open_row_menu(*app, row, x, y, widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_side_tab_changed(GObject* o, GParamSpec*, gpointer user) {
|
/// Scrolls the dock to a section. The dock is one scroller (`App::dock_scroller`) holding the signal list,
|
||||||
App* app = static_cast<App*>(user);
|
/// the cursors and the measurements top to bottom, so "measure" is the cursors panel's offset in the
|
||||||
const char* name = adw_view_stack_get_visible_child_name(ADW_VIEW_STACK(o));
|
/// page, and "signals" the top. Nothing is hidden or removed: both stay in the page. An unknown name is a
|
||||||
app->side_tab = name != nullptr ? name : "signals";
|
/// no-op, so a key bound to a section a later version drops cannot blank anything.
|
||||||
// Not while the layout is being applied: the restore would schedule a save of what it just read.
|
void dock_show_section(App& app, const char* section) {
|
||||||
if (!app->updating_panels) schedule_layout_save(*app);
|
if (app.dock_scroller == nullptr || app.dock_page == nullptr || section == nullptr) return;
|
||||||
|
GtkAdjustment* adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(app.dock_scroller));
|
||||||
|
if (std::strcmp(section, "signals") == 0) {
|
||||||
|
// The list is the first section: its own offset in the page is the page's margin, and "go to Signals"
|
||||||
|
// means the top of the dock.
|
||||||
|
gtk_adjustment_set_value(adj, 0.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (std::strcmp(section, "measure") != 0 || app.cursors_panel == nullptr) return;
|
||||||
|
graphene_point_t zero = GRAPHENE_POINT_INIT(0.f, 0.f);
|
||||||
|
graphene_point_t at;
|
||||||
|
if (!gtk_widget_compute_point(app.cursors_panel, app.dock_page, &zero, &at)) return;
|
||||||
|
gtk_adjustment_set_value(adj, at.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void select_side_tab(App& app, const char* name) {
|
/// The dock is on when the user's sidebar button is in -- from the start, with or without signals; the
|
||||||
if (app.side_stack == nullptr || name == nullptr) return;
|
/// two panel buttons appear as soon as a CONFIG describes any signal at all.
|
||||||
// An unknown name leaves the stack where it is -- a session file from a later version must not
|
|
||||||
// be able to blank the sidebar.
|
|
||||||
if (adw_view_stack_get_child_by_name(ADW_VIEW_STACK(app.side_stack), name) == nullptr) return;
|
|
||||||
adw_view_stack_set_visible_child_name(ADW_VIEW_STACK(app.side_stack), name);
|
|
||||||
app.side_tab = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The side column is on when it has something to show *and* the user's sidebar
|
|
||||||
/// button is in; the two panel buttons appear as soon as a CONFIG describes any
|
|
||||||
/// signal at all.
|
|
||||||
void update_side_visibility(App& app) {
|
void update_side_visibility(App& app) {
|
||||||
if (app.side_stack == nullptr) return;
|
if (app.side_box == nullptr) return;
|
||||||
const bool have_signals = !app.model->signals().empty();
|
const bool have_signals = !app.model->signals().empty();
|
||||||
// `have_signals` decides whether the sidebar *can* be on; `sidebar_wanted` is the user's last word,
|
// `sidebar_wanted` is the user's last word, which is why it is kept apart from the button's own state:
|
||||||
// which is why it is kept apart from the button's own state -- that state follows the panel now, and
|
// a swipe that hides the sidebar fires the button's handler (the binding is bidirectional), so a gesture
|
||||||
// the panel is off while there is nothing to show. A swipe that hides the sidebar fires the button's
|
// is a choice too. "Disable, do not hide": the dock is on screen from the start and does nothing until
|
||||||
// handler (the binding is bidirectional), so a gesture is a choice too.
|
// the stream has signals, so the signals decide its *sensitivity* and never whether it is there.
|
||||||
gtk_widget_set_sensitive(app.sidebar_toggle, have_signals);
|
gtk_widget_set_sensitive(app.side_box, have_signals);
|
||||||
const bool want_column = have_signals && app.sidebar_wanted;
|
gtk_widget_set_sensitive(app.sidebar_toggle, TRUE);
|
||||||
|
const bool want_column = app.sidebar_wanted;
|
||||||
// The split view owns the sidebar's visibility: it animates it, and when the window is too narrow it
|
// The split view owns the sidebar's visibility: it animates it, and when the window is too narrow it
|
||||||
// shows it as an overlay rather than letting the column be squeezed to nothing.
|
// shows it as an overlay rather than letting the column be squeezed to nothing.
|
||||||
if (app.side_view != nullptr) {
|
if (app.side_view != nullptr) {
|
||||||
@@ -2908,7 +2911,12 @@ void on_sidebar_toggled(GtkToggleButton* button, gpointer user) {
|
|||||||
App* app = static_cast<App*>(user);
|
App* app = static_cast<App*>(user);
|
||||||
// Only a state we did not write is a wish: `update_side_visibility` sets this button (and the panel)
|
// Only a state we did not write is a wish: `update_side_visibility` sets this button (and the panel)
|
||||||
// from `sidebar_wanted`, so an unguarded change here is the user's.
|
// from `sidebar_wanted`, so an unguarded change here is the user's.
|
||||||
if (!app->sidebar_syncing) app->sidebar_wanted = gtk_toggle_button_get_active(button) != FALSE;
|
if (!app->sidebar_syncing) {
|
||||||
|
app->sidebar_wanted = gtk_toggle_button_get_active(button) != FALSE;
|
||||||
|
// The wish is persisted (`SessionState::dock_shown`), so a change of it is a change of the session --
|
||||||
|
// but not one made while a layout is being applied, which would save what it just read.
|
||||||
|
if (!app->updating_panels) schedule_layout_save(*app);
|
||||||
|
}
|
||||||
update_side_visibility(*app);
|
update_side_visibility(*app);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2994,8 +3002,8 @@ void note_panel_controls(App& app) {
|
|||||||
// `HeaderBar::pack_end()` parents into an internal box, so the bar is a level up, not the parent.
|
// `HeaderBar::pack_end()` parents into an internal box, so the bar is a level up, not the parent.
|
||||||
char text[256];
|
char text[256];
|
||||||
std::snprintf(text, sizeof(text),
|
std::snprintf(text, sizeof(text),
|
||||||
"cursors=%d in the toolbar, switcher=%d, reset=%d in the cursors pane",
|
"cursors=%d in the toolbar, dock=%d, reset=%d in the cursors pane",
|
||||||
in_bar(app.cursors_toggle), app.side_switcher != nullptr ? 1 : 0,
|
in_bar(app.cursors_toggle), app.dock_page != nullptr ? 1 : 0,
|
||||||
in_cursors_pane(app.cursors_reset));
|
in_cursors_pane(app.cursors_reset));
|
||||||
// ... and the order the bar actually shows, which is what "the menu should be last" is about. A
|
// ... and the order the bar actually shows, which is what "the menu should be last" is about. A
|
||||||
// widget tree walked in child order *is* the layout order, so this is the reading order of the
|
// widget tree walked in child order *is* the layout order, so this is the reading order of the
|
||||||
@@ -3051,42 +3059,6 @@ void note_panel_controls(App& app) {
|
|||||||
" " +
|
" " +
|
||||||
icon_report(theme, "org.udpscope.app-symbolic",
|
icon_report(theme, "org.udpscope.app-symbolic",
|
||||||
"/org/udpscope/icons/scalable/apps/org.udpscope.app-symbolic.svg");
|
"/org/udpscope/icons/scalable/apps/org.udpscope.app-symbolic.svg");
|
||||||
|
|
||||||
// What the switcher is drawing: the sidebar's tabs are words (`wide` policy, a title per page, no
|
|
||||||
// icon). The pills are `AdwViewSwitcher`'s own buttons, built when the stack is mapped, so this
|
|
||||||
// reads on the first tick. `mapped`, not `visible`: visibility is per widget, so a label whose
|
|
||||||
// *parent* is hidden still reads visible; `mapped` requires every ancestor, which is what "on
|
|
||||||
// screen" means -- and it is why the pill count is read the same way: if nothing is mapped the
|
|
||||||
// pill count reads 0 and the label count means nothing, rather than passing vacuously.
|
|
||||||
int pills = 0;
|
|
||||||
int drawn_labels = 0;
|
|
||||||
int drawn_images = 0;
|
|
||||||
const std::function<void(GtkWidget*)> count_drawn = [&](GtkWidget* w) {
|
|
||||||
if (GTK_IS_TOGGLE_BUTTON(w) && gtk_widget_get_mapped(w)) ++pills;
|
|
||||||
// A *mapped* image is an icon (or its missing-icon placeholder) on a tab: tabs are words only.
|
|
||||||
if (GTK_IS_IMAGE(w) && gtk_widget_get_mapped(w)) ++drawn_images;
|
|
||||||
// A *mapped* label with text: an empty label would be counted by "mapped" alone.
|
|
||||||
if (GTK_IS_LABEL(w) && gtk_widget_get_mapped(w) && gtk_label_get_text(GTK_LABEL(w))[0] != '\0') {
|
|
||||||
++drawn_labels;
|
|
||||||
}
|
|
||||||
for (GtkWidget* c = gtk_widget_get_first_child(w); c != nullptr;
|
|
||||||
c = gtk_widget_get_next_sibling(c)) {
|
|
||||||
count_drawn(c);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (app.side_switcher != nullptr) count_drawn(app.side_switcher);
|
|
||||||
app.dbg_switcher_pills = pills;
|
|
||||||
app.dbg_switcher_labels = drawn_labels;
|
|
||||||
app.dbg_switcher_images = drawn_images;
|
|
||||||
// Walked through `get_pages()`, not the widget tree: an `AdwViewStackPage` is a plain `GObject`.
|
|
||||||
app.dbg_switcher_pages =
|
|
||||||
app.side_stack != nullptr
|
|
||||||
? static_cast<int>(g_list_model_get_n_items(
|
|
||||||
G_LIST_MODEL(adw_view_stack_get_pages(ADW_VIEW_STACK(app.side_stack)))))
|
|
||||||
: 0;
|
|
||||||
if (app.side_switcher != nullptr) {
|
|
||||||
g_object_get(G_OBJECT(app.side_switcher), "policy", &app.dbg_switcher_policy, nullptr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The event-zoom sweep's two halves: a gesture zoom, then the same walk through the window
|
/// The event-zoom sweep's two halves: a gesture zoom, then the same walk through the window
|
||||||
@@ -4022,7 +3994,7 @@ gboolean tick(gpointer user) {
|
|||||||
// follow a resize and the gutter settling without being told.
|
// follow a resize and the gutter settling without being told.
|
||||||
if (app->layout_editing) sync_layout_editor(*app);
|
if (app->layout_editing) sync_layout_editor(*app);
|
||||||
|
|
||||||
// The one thing that has to be decided *per frame* about the sidebar: whether there is room for it
|
// The one thing that has to be decided *per frame* about the dock: whether there is room for it
|
||||||
// beside the plot. Collapsed, the split view puts it *over* the plot rather than giving it a share of
|
// beside the plot. Collapsed, the split view puts it *over* the plot rather than giving it a share of
|
||||||
// a width that is not there -- so a small window shows fewer pixels of plot, never a panel squeezed
|
// a width that is not there -- so a small window shows fewer pixels of plot, never a panel squeezed
|
||||||
// to nothing. `kSidebarRoom` is the sidebar's 320 plus the width below which the plot stops being
|
// to nothing. `kSidebarRoom` is the sidebar's 320 plus the width below which the plot stops being
|
||||||
@@ -4030,8 +4002,12 @@ gboolean tick(gpointer user) {
|
|||||||
if (app->side_view != nullptr && app->window != nullptr) {
|
if (app->side_view != nullptr && app->window != nullptr) {
|
||||||
// The split view writes `show-sidebar` itself (at startup and when it collapses), so the wish is
|
// The split view writes `show-sidebar` itself (at startup and when it collapses), so the wish is
|
||||||
// re-asserted here -- but only when they disagree, so a settled frame touches nothing. This is also
|
// re-asserted here -- but only when they disagree, so a settled frame touches nothing. This is also
|
||||||
// why a hide *gesture* does not stick: the panel is where the button says it is.
|
// why a hide *gesture* does not stick: the panel is where the button says it is. The wish alone decides
|
||||||
const bool want = !app->model->signals().empty() && app->sidebar_wanted;
|
// whether the dock is there; having signals decides only whether it does anything ("disable, do not
|
||||||
|
// hide"): a dock that is on screen from the start does not jump in when the first CONFIG arrives.
|
||||||
|
const bool want = app->sidebar_wanted;
|
||||||
|
gtk_widget_set_sensitive(app->side_box, !app->model->signals().empty());
|
||||||
|
gtk_widget_set_sensitive(app->sidebar_toggle, TRUE);
|
||||||
if (adw_overlay_split_view_get_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(app->side_view)) != want) {
|
if (adw_overlay_split_view_get_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(app->side_view)) != want) {
|
||||||
adw_overlay_split_view_set_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(app->side_view), want);
|
adw_overlay_split_view_set_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(app->side_view), want);
|
||||||
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(app->sidebar_toggle)) != want) {
|
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(app->sidebar_toggle)) != want) {
|
||||||
@@ -4702,8 +4678,8 @@ void run_app_action(App& app, AppAction action) {
|
|||||||
case AppAction::CursorsPane:
|
case AppAction::CursorsPane:
|
||||||
toggle(app.cursors_toggle);
|
toggle(app.cursors_toggle);
|
||||||
break;
|
break;
|
||||||
case AppAction::SideTabMeasure: select_side_tab(app, "measure"); break;
|
case AppAction::ShowMeasure: dock_show_section(app, "measure"); break;
|
||||||
case AppAction::SideTabSignals: select_side_tab(app, "signals"); break;
|
case AppAction::ShowSignals: dock_show_section(app, "signals"); break;
|
||||||
case AppAction::LayoutEditor:
|
case AppAction::LayoutEditor:
|
||||||
toggle(app.layout_toggle);
|
toggle(app.layout_toggle);
|
||||||
break;
|
break;
|
||||||
@@ -4745,6 +4721,9 @@ gboolean on_app_key_pressed(GtkEventControllerKey*, guint keyval, guint, GdkModi
|
|||||||
tour_dismiss(*app);
|
tour_dismiss(*app);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
// ... and then the slide-over: Escape closes it, ahead of the editable check for the same reason -- the
|
||||||
|
// key that dismisses a panel must work from inside one of its own spin buttons.
|
||||||
|
if (keyval == GDK_KEY_Escape && slideover_escape(*app)) return TRUE;
|
||||||
GtkWidget* focus = gtk_window_get_focus(GTK_WINDOW(app->window));
|
GtkWidget* focus = gtk_window_get_focus(GTK_WINDOW(app->window));
|
||||||
if (focus != nullptr && GTK_IS_EDITABLE(focus)) return FALSE;
|
if (focus != nullptr && GTK_IS_EDITABLE(focus)) return FALSE;
|
||||||
|
|
||||||
@@ -4998,8 +4977,8 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
|
|
||||||
install_css();
|
install_css();
|
||||||
// The window's skeleton is a `.ui` file: the window, its two panes, the plot column with the status
|
// The window's skeleton is a `.ui` file: the window, its two panes, the plot column with the status
|
||||||
// line under it, and the sidebar's tab switcher and two-page stack. Everything that depends on the
|
// line under it, and the dock's scroller. Everything that depends on the
|
||||||
// stream -- the events pane, the two plot areas, the sidebar's panels -- is built below and put
|
// stream -- the events pane, the two plot areas, the dock's panels -- is built below and put
|
||||||
// into the slots by id.
|
// into the slots by id.
|
||||||
g_clear_object(&app->window_ui);
|
g_clear_object(&app->window_ui);
|
||||||
app->window_ui = gtk_builder_new_from_resource("/org/udpscope/ui/main-window.ui");
|
app->window_ui = gtk_builder_new_from_resource("/org/udpscope/ui/main-window.ui");
|
||||||
@@ -5018,10 +4997,8 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
// help's annotated figures label.
|
// help's annotated figures label.
|
||||||
app->status_row = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "status_row"));
|
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 = 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"));
|
app->dock_scroller = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "dock_scroller"));
|
||||||
GtkWidget* side_switcher = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "side_switcher"));
|
app->dock_page = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "dock_page"));
|
||||||
GtkWidget* measure_page = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "measure_page"));
|
|
||||||
GtkWidget* signals_page = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "signals_page"));
|
|
||||||
app->badge_bar = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_bar"));
|
app->badge_bar = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_bar"));
|
||||||
app->badge_signals = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_signals"));
|
app->badge_signals = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_signals"));
|
||||||
app->badge_system = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_system"));
|
app->badge_system = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_system"));
|
||||||
@@ -5033,8 +5010,7 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
app->status_label == nullptr || app->status_info_button == nullptr ||
|
app->status_label == nullptr || app->status_info_button == nullptr ||
|
||||||
app->status_row == nullptr || app->badge_bar == nullptr || app->badge_signals == nullptr ||
|
app->status_row == nullptr || app->badge_bar == nullptr || app->badge_signals == nullptr ||
|
||||||
app->badge_system == nullptr ||
|
app->badge_system == nullptr ||
|
||||||
side == nullptr || side_stack == nullptr || side_switcher == nullptr ||
|
side == nullptr || app->dock_scroller == nullptr || app->dock_page == nullptr) {
|
||||||
measure_page == nullptr || signals_page == nullptr) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The window's actions live on the application, so it has to know which one it belongs to.
|
// The window's actions live on the application, so it has to know which one it belongs to.
|
||||||
@@ -5177,28 +5153,28 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
gtk_popover_set_has_arrow(GTK_POPOVER(app->status_popover), TRUE);
|
gtk_popover_set_has_arrow(GTK_POPOVER(app->status_popover), TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The right-hand side is two tabs, not five stacked panels: one tab is on screen at a time, so
|
// The right-hand side is the dock: one scroller, the signal list first (it is what is dragged onto the
|
||||||
// no section competes with four others for height. Each page scrolls on its own -- the two are
|
// plot), then the cursors and the measurements. Every section is always there -- "go to Measure" scrolls
|
||||||
// different heights, and one shared scroller would leave a switch scrolled where the new page
|
// to it instead of switching a tab, so the measurements are as live as the list above them.
|
||||||
// never asked to be.
|
// `side` and `dock_page`/`dock_scroller` are resolved above with the rest of the skeleton and checked
|
||||||
// `side` and `side_stack`/`side_switcher`/`measure_page`/`signals_page` are resolved above with
|
// by the null guard; assign the member here.
|
||||||
// the rest of the skeleton and checked by the null guard; assign the members here.
|
|
||||||
app->side_box = side;
|
app->side_box = side;
|
||||||
app->side_stack = side_stack;
|
|
||||||
app->side_switcher = side_switcher;
|
|
||||||
|
|
||||||
// Measure: what the cursors bracket, and the figures derived from it.
|
// Built in the order the panels have always been (cursors, measurements, trigger, signals) and placed in
|
||||||
|
// the dock's: the trigger form goes to the slide-over (reached by the Trigger badge).
|
||||||
app->cursors_panel = build_cursors_panel(*app);
|
app->cursors_panel = build_cursors_panel(*app);
|
||||||
gtk_box_append(GTK_BOX(measure_page), app->cursors_panel);
|
GtkWidget* measures_panel = build_measures_panel(*app);
|
||||||
gtk_box_append(GTK_BOX(measure_page), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
|
||||||
gtk_box_append(GTK_BOX(measure_page), build_measures_panel(*app));
|
|
||||||
|
|
||||||
// The trigger form lives in the slide-over (reached by the Trigger badge); the signal list stays in the
|
|
||||||
// sidebar until the dock replaces it.
|
|
||||||
app->trigger_panel = build_trigger_panel(*app);
|
app->trigger_panel = build_trigger_panel(*app);
|
||||||
|
// Top-aligned: the page's scroller gives the child the page's height, and a form that stretched to it
|
||||||
|
// would spread its rows (or draw its frame) over empty space under the last one.
|
||||||
|
gtk_widget_set_valign(app->trigger_panel, GTK_ALIGN_START);
|
||||||
slideover_add_page(*app, "trigger", "Trigger", app->trigger_panel);
|
slideover_add_page(*app, "trigger", "Trigger", app->trigger_panel);
|
||||||
app->signal_panel = build_signal_panel(*app);
|
app->signal_panel = build_signal_panel(*app);
|
||||||
gtk_box_append(GTK_BOX(signals_page), app->signal_panel);
|
gtk_box_append(GTK_BOX(app->dock_page), app->signal_panel);
|
||||||
|
gtk_box_append(GTK_BOX(app->dock_page), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
||||||
|
gtk_box_append(GTK_BOX(app->dock_page), app->cursors_panel);
|
||||||
|
gtk_box_append(GTK_BOX(app->dock_page), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
||||||
|
gtk_box_append(GTK_BOX(app->dock_page), measures_panel);
|
||||||
|
|
||||||
// The cursors start off, so their readout starts insensitive -- stated here by the one place that
|
// The cursors start off, so their readout starts insensitive -- stated here by the one place that
|
||||||
// knows the default, and the *body* and not the panel, because the panel's heading carries the
|
// knows the default, and the *body* and not the panel, because the panel's heading carries the
|
||||||
@@ -5210,15 +5186,8 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
sync_arm_buttons(*app);
|
sync_arm_buttons(*app);
|
||||||
gtk_widget_set_sensitive(app->cursors_reset, FALSE);
|
gtk_widget_set_sensitive(app->cursors_reset, FALSE);
|
||||||
|
|
||||||
if (app->side_stack != nullptr) {
|
// The source kind's own panels (a generator, digital I/O), pages of the slide-over, added before the
|
||||||
adw_view_stack_set_visible_child_name(ADW_VIEW_STACK(app->side_stack), "signals");
|
// first tick.
|
||||||
g_signal_connect(app->side_stack, "notify::visible-child-name",
|
|
||||||
G_CALLBACK(on_side_tab_changed), app);
|
|
||||||
}
|
|
||||||
// The source kind's own tabs (a generator, digital I/O), added before the first tick so the
|
|
||||||
// pages-versus-pills reading below counts them too. The tabs are words, so the switcher's icons
|
|
||||||
// are hidden first and kept hidden for the pages this call adds.
|
|
||||||
keep_tabs_icon_free(*app);
|
|
||||||
build_source_panels(*app);
|
build_source_panels(*app);
|
||||||
// The badge bar reads `trigger_toggle` (made by `build_toolbar()` above) and the source's pages, which
|
// The badge bar reads `trigger_toggle` (made by `build_toolbar()` above) and the source's pages, which
|
||||||
// `build_source_panels()` has just registered: the Source badge opens the first of them.
|
// `build_source_panels()` has just registered: the Source badge opens the first of them.
|
||||||
@@ -5915,7 +5884,7 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
|||||||
g_timeout_add(static_cast<guint>(std::atoi(env)), stats_probe, &app);
|
g_timeout_add(static_cast<guint>(std::atoi(env)), stats_probe, &app);
|
||||||
}
|
}
|
||||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_SIDE_TAB_MS")) {
|
if (const char* env = g_getenv("UDPSCOPE_PROBE_SIDE_TAB_MS")) {
|
||||||
g_timeout_add(static_cast<guint>(std::atoi(env)), side_tab_probe, &app);
|
g_timeout_add(static_cast<guint>(std::atoi(env)), dock_section_probe, &app);
|
||||||
}
|
}
|
||||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_SLIDEOVER_MS")) {
|
if (const char* env = g_getenv("UDPSCOPE_PROBE_SLIDEOVER_MS")) {
|
||||||
g_timeout_add(static_cast<guint>(std::atoi(env)), slideover_probe, &app);
|
g_timeout_add(static_cast<guint>(std::atoi(env)), slideover_probe, &app);
|
||||||
@@ -6028,6 +5997,9 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
|||||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_MENUS_MS")) {
|
if (const char* env = g_getenv("UDPSCOPE_PROBE_MENUS_MS")) {
|
||||||
menus_probe_next(app, 0, static_cast<guint>(std::atoi(env)));
|
menus_probe_next(app, 0, static_cast<guint>(std::atoi(env)));
|
||||||
}
|
}
|
||||||
|
if (const char* env = g_getenv("UDPSCOPE_PROBE_DOCK_SESSION_MS")) {
|
||||||
|
g_timeout_add(static_cast<guint>(std::atoi(env)), dock_session_probe, &app);
|
||||||
|
}
|
||||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_KEYS_MS")) {
|
if (const char* env = g_getenv("UDPSCOPE_PROBE_KEYS_MS")) {
|
||||||
g_timeout_add(static_cast<guint>(std::atoi(env)), keys_probe, &app);
|
g_timeout_add(static_cast<guint>(std::atoi(env)), keys_probe, &app);
|
||||||
}
|
}
|
||||||
@@ -6237,12 +6209,12 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
|||||||
inside(app.events_list, app.events_revealer));
|
inside(app.events_list, app.events_revealer));
|
||||||
}
|
}
|
||||||
std::printf("[udpscope] panels: side=%dx%d (visible=%d) signals=%dx%d row_h=%d "
|
std::printf("[udpscope] panels: side=%dx%d (visible=%d) signals=%dx%d row_h=%d "
|
||||||
"(page_current=%d) trigger=%dx%d (sensitive=%d) switcher=%d "
|
"(page_current=%d) trigger=%dx%d (sensitive=%d) "
|
||||||
"cursors=%d window=%.3fs window_cap=%.1fs\n",
|
"cursors=%d window=%.3fs window_cap=%.1fs\n",
|
||||||
app.dbg_side_w, app.dbg_side_h, app.dbg_side_column_after_hide, app.dbg_signal_w,
|
app.dbg_side_w, app.dbg_side_h, app.dbg_side_column_after_hide, app.dbg_signal_w,
|
||||||
app.dbg_signal_h, app.dbg_signal_row_h, app.dbg_signal_page_current,
|
app.dbg_signal_h, app.dbg_signal_row_h, app.dbg_signal_page_current,
|
||||||
app.dbg_trigger_w, app.dbg_trigger_h,
|
app.dbg_trigger_w, app.dbg_trigger_h,
|
||||||
app.dbg_trigger_sensitive, app.dbg_switcher_visible,
|
app.dbg_trigger_sensitive,
|
||||||
rows.cursors_visible() ? 1 : 0, rows.time_window(),
|
rows.cursors_visible() ? 1 : 0, rows.time_window(),
|
||||||
app.window_limit_s);
|
app.window_limit_s);
|
||||||
{
|
{
|
||||||
@@ -6267,10 +6239,8 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
|||||||
app.dbg_panel_switch_trigger, app.dbg_toolbar_switch_trigger,
|
app.dbg_panel_switch_trigger, app.dbg_toolbar_switch_trigger,
|
||||||
app.dbg_panel_switch_cursors, app.dbg_toolbar_switch_cursors,
|
app.dbg_panel_switch_cursors, app.dbg_toolbar_switch_cursors,
|
||||||
app.dbg_panel_drove_toolbar);
|
app.dbg_panel_drove_toolbar);
|
||||||
std::printf("[udpscope] switcher: policy=%s pills=%d labels=%d images=%d pages=%d size=%dx%d\n",
|
std::printf("[udpscope] dock: shown=%d sensitive=%d w=%d scroller_pages=%d\n", app.dbg_dock_shown,
|
||||||
app.dbg_switcher_policy == 0 ? "narrow" : (app.dbg_switcher_policy == 1 ? "wide" : "?"),
|
app.dbg_dock_sensitive, app.dbg_dock_w, app.dbg_dock_scrollers);
|
||||||
app.dbg_switcher_pills, app.dbg_switcher_labels, app.dbg_switcher_images,
|
|
||||||
app.dbg_switcher_pages, app.dbg_switcher_w, app.dbg_switcher_h);
|
|
||||||
// The help affordance layer: what the contextual-help toggle left on the surfaces, and where the
|
// 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`),
|
// guided tour stood when the last capture ran. Both are captured on the tick (`capture_help_state`),
|
||||||
// so neither asks a destroyed widget anything.
|
// so neither asks a destroyed widget anything.
|
||||||
|
|||||||
+13
-56
@@ -4,7 +4,7 @@
|
|||||||
<!--
|
<!--
|
||||||
The main window's skeleton: the window, the split view that puts the sidebar beside the plot, the
|
The main window's skeleton: the window, the split view that puts the sidebar beside the plot, the
|
||||||
events pane's divider, the plot column (`plot_overlay` > `plot_box`, the slide-over panel being an overlay child added in code)
|
events pane's divider, the plot column (`plot_overlay` > `plot_box`, the slide-over panel being an overlay child added in code)
|
||||||
with the status line under it, and the sidebar's tab stack.
|
with the status line under it, and the sidebar, which is the dock.
|
||||||
|
|
||||||
The sidebar is an `AdwOverlaySplitView`, which is what a utility sidebar is in GNOME (as against
|
The sidebar is an `AdwOverlaySplitView`, which is what a utility sidebar is in GNOME (as against
|
||||||
`AdwNavigationSplitView`, which is for navigation lists). Three things follow from that, and they are
|
`AdwNavigationSplitView`, which is for navigation lists). Three things follow from that, and they are
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
What is *not* here is as deliberate as what is. Everything this file nests is fixed — the shape of
|
What is *not* here is as deliberate as what is. Everything this file nests is fixed — the shape of
|
||||||
the window does not depend on the stream, the trigger or the layout — while the subtrees that do are
|
the window does not depend on the stream, the trigger or the layout — while the subtrees that do are
|
||||||
built by code and put in place: the events pane, into `events_divider`'s start side; the two plot
|
built by code and put in place: the events pane, into `events_divider`'s start side; the two plot
|
||||||
areas, into the split inside `plot_box`; the sidebar's panels, appended to `measure_page` and `signals_page`; and the
|
areas, into the split inside `plot_box`; the dock's panels (signals, cursors, measurements), appended to `dock_page`; and the
|
||||||
status popover, attached to `status_info`.
|
status popover, attached to `status_info`.
|
||||||
|
|
||||||
The header bar is not here either: it is the window's own titlebar, built by `build_toolbar()`
|
The header bar is not here either: it is the window's own titlebar, built by `build_toolbar()`
|
||||||
@@ -49,66 +49,23 @@
|
|||||||
<property name="min-sidebar-width">320</property>
|
<property name="min-sidebar-width">320</property>
|
||||||
<property name="max-sidebar-width">320</property>
|
<property name="max-sidebar-width">320</property>
|
||||||
<property name="sidebar">
|
<property name="sidebar">
|
||||||
<!-- A switcher above a stack, and a scroller *per page*: the two pages are different
|
<!-- The dock: one scroller holding the signal list, then the cursors and the measurements.
|
||||||
heights, so one shared scroller around the stack would leave a tab switch scrolled
|
It replaces the tab switcher (four text tabs did not fit 320 px and a source can add
|
||||||
to somewhere the new page never asked for. The switcher is the sibling above, not a
|
more); it is the temporary home of Measure and Signals until steps 2 and 3 turn them
|
||||||
child of either scroller: the one control that chooses what you are looking at must
|
into the badge bar's signals and the compact readouts. -->
|
||||||
not be the first thing to scroll away.
|
|
||||||
|
|
||||||
The tabs are words: the policy is `wide` and every page has a title, no icon. (They
|
|
||||||
were icon-only glyphs once; the glyphs were not understandable enough.) The pages'
|
|
||||||
*names* are what identify them to `select_side_tab()` and the session file; a source
|
|
||||||
kind's own tabs are added in code with their `SourcePanel::title`. -->
|
|
||||||
<object class="GtkBox" id="side_column">
|
<object class="GtkBox" id="side_column">
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<property name="width-request">300</property>
|
<property name="width-request">300</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwViewSwitcher" id="side_switcher">
|
<object class="GtkScrolledWindow" id="dock_scroller">
|
||||||
<property name="stack">side_stack</property>
|
|
||||||
<property name="policy">wide</property>
|
|
||||||
<property name="margin-top">4</property>
|
|
||||||
<property name="margin-bottom">4</property>
|
|
||||||
<property name="margin-start">6</property>
|
|
||||||
<property name="margin-end">6</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="AdwViewStack" id="side_stack">
|
|
||||||
<property name="vexpand">1</property>
|
<property name="vexpand">1</property>
|
||||||
<child>
|
<property name="hscrollbar-policy">never</property>
|
||||||
<object class="AdwViewStackPage">
|
<property name="vscrollbar-policy">automatic</property>
|
||||||
<property name="name">measure</property>
|
<property name="child">
|
||||||
<property name="title">Measure</property>
|
<object class="GtkBox" id="dock_page">
|
||||||
<property name="child">
|
<property name="orientation">vertical</property>
|
||||||
<object class="GtkScrolledWindow">
|
|
||||||
<property name="hscrollbar-policy">never</property>
|
|
||||||
<property name="vscrollbar-policy">automatic</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkBox" id="measure_page">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</property>
|
||||||
<child>
|
|
||||||
<object class="AdwViewStackPage">
|
|
||||||
<property name="name">signals</property>
|
|
||||||
<property name="title">Signals</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkScrolledWindow">
|
|
||||||
<property name="hscrollbar-policy">never</property>
|
|
||||||
<property name="vscrollbar-policy">automatic</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkBox" id="signals_page">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|||||||
+2
-2
@@ -73,14 +73,14 @@
|
|||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="visible">1</property>
|
<property name="visible">1</property>
|
||||||
<property name="accelerator">m</property>
|
<property name="accelerator">m</property>
|
||||||
<property name="title" translatable="yes">Show the Measure tab</property>
|
<property name="title" translatable="yes">Go to Measure</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="visible">1</property>
|
<property name="visible">1</property>
|
||||||
<property name="accelerator">g</property>
|
<property name="accelerator">g</property>
|
||||||
<property name="title" translatable="yes">Show the Signals tab</property>
|
<property name="title" translatable="yes">Go to Signals</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
|||||||
+22
-34
@@ -241,13 +241,11 @@ struct App {
|
|||||||
GtkWidget* trigger_panel = nullptr;
|
GtkWidget* trigger_panel = nullptr;
|
||||||
GtkWidget* signal_panel = nullptr;
|
GtkWidget* signal_panel = nullptr;
|
||||||
GtkWidget* side_box = nullptr;
|
GtkWidget* side_box = nullptr;
|
||||||
/// The sidebar's tab stack and its switcher. The stack is what chooses which panels are on
|
/// The dock: the sidebar's one scroller and the box inside it, which holds the signal list, the
|
||||||
/// screen; a feature's own on/off never does (it sets its panel's sensitivity instead).
|
/// cursors and the measurements (`dock_show_section()` scrolls it). A feature's own on/off never
|
||||||
GtkWidget* side_stack = nullptr;
|
/// removes a section (it sets its panel's sensitivity instead).
|
||||||
GtkWidget* side_switcher = nullptr;
|
GtkWidget* dock_page = nullptr;
|
||||||
/// The page name the stack is showing, and the one value the session file persists about the
|
GtkWidget* dock_scroller = nullptr;
|
||||||
/// sidebar. "signals" on a first run.
|
|
||||||
std::string side_tab = "signals";
|
|
||||||
GtkWidget* arm_button = nullptr;
|
GtkWidget* arm_button = nullptr;
|
||||||
/// The Force button, beside the Arm/Disarm one: one acquisition now, with the configured pre/post
|
/// The Force button, beside the Arm/Disarm one: one acquisition now, with the configured pre/post
|
||||||
/// window. It carries no state of its own -- `sync_arm_buttons()` states the one it has
|
/// window. It carries no state of its own -- `sync_arm_buttons()` states the one it has
|
||||||
@@ -477,18 +475,6 @@ struct App {
|
|||||||
/// works, and every other line of the dump still passes.
|
/// works, and every other line of the dump still passes.
|
||||||
std::string dbg_toolbar_icons;
|
std::string dbg_toolbar_icons;
|
||||||
std::string dbg_app_icons;
|
std::string dbg_app_icons;
|
||||||
/// What the switcher is: how many pills it holds, and how many labels it is *drawing*. The tabs
|
|
||||||
/// are words (`wide` policy, a title per page), so the label count must equal the page count.
|
|
||||||
int dbg_switcher_pills = -1;
|
|
||||||
int dbg_switcher_labels = -1;
|
|
||||||
/// How many `GtkImage`s the switcher has *mapped*: a tab is its title and nothing else, so this
|
|
||||||
/// must be 0 -- an icon, or the missing-icon placeholder GTK draws for a page with no `icon-name`.
|
|
||||||
int dbg_switcher_images = -1;
|
|
||||||
/// How many pages the sidebar stack holds: `pills` and `labels` must both equal it.
|
|
||||||
int dbg_switcher_pages = -1;
|
|
||||||
/// The switcher's own size, beside it: what the tab bar costs the column.
|
|
||||||
int dbg_switcher_w = -1;
|
|
||||||
int dbg_switcher_h = -1;
|
|
||||||
/// The panes' own switches and the header bar's buttons, read together by the exit probe: the two
|
/// The panes' own switches and the header bar's buttons, read together by the exit probe: the two
|
||||||
/// must agree, and `dbg_panel_drove_toolbar` is the pane's switch having moved the button.
|
/// must agree, and `dbg_panel_drove_toolbar` is the pane's switch having moved the button.
|
||||||
int dbg_panel_switch_trigger = -1;
|
int dbg_panel_switch_trigger = -1;
|
||||||
@@ -504,7 +490,6 @@ struct App {
|
|||||||
/// The Arm button, read with the trigger off: it is the pane's one runtime control, so it is the one
|
/// The Arm button, read with the trigger off: it is the pane's one runtime control, so it is the one
|
||||||
/// thing in the body that *is* disabled with the mode.
|
/// thing in the body that *is* disabled with the mode.
|
||||||
int dbg_arm_sensitive = -1;
|
int dbg_arm_sensitive = -1;
|
||||||
int dbg_switcher_policy = -1;
|
|
||||||
/// The header bar, so the dump can report the order the controls are in: a header bar is a
|
/// The header bar, so the dump can report the order the controls are in: a header bar is a
|
||||||
/// *titlebar*, not a child of the window, so walking down from the window does not reach it.
|
/// *titlebar*, not a child of the window, so walking down from the window does not reach it.
|
||||||
GtkWidget* toolbar = nullptr;
|
GtkWidget* toolbar = nullptr;
|
||||||
@@ -881,7 +866,7 @@ struct App {
|
|||||||
std::string source_kind = "udps";
|
std::string source_kind = "udps";
|
||||||
SourceConfig source_opts;
|
SourceConfig source_opts;
|
||||||
std::string source_title;
|
std::string source_title;
|
||||||
/// The panels the source kind added to `side_stack`, in tab order. Empty for UDPS and in file mode.
|
/// The panels the source kind added to the slide-over, in page order. Empty for UDPS and in file mode.
|
||||||
std::vector<SourcePanelPage> source_panels;
|
std::vector<SourcePanelPage> source_panels;
|
||||||
bool source_has_datagrams = true; ///< the kind reports raw datagrams; false leaves the capture button disabled
|
bool source_has_datagrams = true; ///< the kind reports raw datagrams; false leaves the capture button disabled
|
||||||
guint layout_save_source = 0;
|
guint layout_save_source = 0;
|
||||||
@@ -1139,7 +1124,14 @@ struct App {
|
|||||||
int dbg_signal_row_h = 0;
|
int dbg_signal_row_h = 0;
|
||||||
int dbg_trigger_w = 0;
|
int dbg_trigger_w = 0;
|
||||||
int dbg_trigger_h = 0;
|
int dbg_trigger_h = 0;
|
||||||
|
/// The signal list has rows (the dock has no tab to be "current" on any more).
|
||||||
int dbg_signal_page_current = 0;
|
int dbg_signal_page_current = 0;
|
||||||
|
/// The dock as the exit probe found it: the split view showing the sidebar, the column sensitive (it is
|
||||||
|
/// not until the stream has signals), its width, and how many `GtkScrolledWindow`s it holds directly.
|
||||||
|
int dbg_dock_shown = -1;
|
||||||
|
int dbg_dock_sensitive = -1;
|
||||||
|
int dbg_dock_w = -1;
|
||||||
|
int dbg_dock_scrollers = -1;
|
||||||
int dbg_trigger_sensitive = 0;
|
int dbg_trigger_sensitive = 0;
|
||||||
int dbg_trigger_items = 0;
|
int dbg_trigger_items = 0;
|
||||||
int dbg_trigger_selected = -1;
|
int dbg_trigger_selected = -1;
|
||||||
@@ -1161,7 +1153,6 @@ struct App {
|
|||||||
double dbg_trigger_time = 0.0;
|
double dbg_trigger_time = 0.0;
|
||||||
int dbg_trigger_time_valid = -1;
|
int dbg_trigger_time_valid = -1;
|
||||||
std::string dbg_mode_live;
|
std::string dbg_mode_live;
|
||||||
int dbg_switcher_visible = 0;
|
|
||||||
int dbg_trigger_sensitive_default = 0;
|
int dbg_trigger_sensitive_default = 0;
|
||||||
|
|
||||||
/// The detail string the popover and the exit dump read. The dump used to read the label,
|
/// The detail string the popover and the exit dump read. The dump used to read the label,
|
||||||
@@ -1572,9 +1563,9 @@ constexpr int kEventThumbH = 46;
|
|||||||
constexpr int kEventThumbMaxH = 104;
|
constexpr int kEventThumbMaxH = 104;
|
||||||
inline int g_stats_phase = 0;
|
inline int g_stats_phase = 0;
|
||||||
inline std::vector<std::uint64_t> g_stats_n_before;
|
inline std::vector<std::uint64_t> g_stats_n_before;
|
||||||
/// The same for the tab-switch probe: the accumulator counts recorded while the Measure page was
|
/// The same for the dock-section probe: the accumulator counts recorded while the Measure section was
|
||||||
/// still current, so the report can say whether any of them advanced afterwards.
|
/// still scrolled into view, so the report can say whether any of them advanced after it scrolled out.
|
||||||
inline std::vector<std::uint64_t> g_side_tab_n_before;
|
inline std::vector<std::uint64_t> g_dock_section_n_before;
|
||||||
inline double g_trigrenum_edge = 0.0;
|
inline double g_trigrenum_edge = 0.0;
|
||||||
inline std::uint64_t g_trigrenum_latches = 0;
|
inline std::uint64_t g_trigrenum_latches = 0;
|
||||||
inline ConditionProbe g_condition;
|
inline ConditionProbe g_condition;
|
||||||
@@ -1600,11 +1591,6 @@ void config_dialog_refresh(ConfigDialog& d);
|
|||||||
void open_configurations(App& app);
|
void open_configurations(App& app);
|
||||||
void save_layout_now(App& app);
|
void save_layout_now(App& app);
|
||||||
|
|
||||||
/// The sidebar's tabs are words: hide the icon every `AdwViewSwitcherButton` draws (the
|
|
||||||
/// missing-icon placeholder, for a page with a title and no `icon-name`), for the buttons that exist
|
|
||||||
/// and for any page added later. Call once, before `build_source_panels()`.
|
|
||||||
void keep_tabs_icon_free(App& app);
|
|
||||||
|
|
||||||
/// Capability panels (`src/ui_source.cpp`). `build_source_panels` adds the source kind's panels as
|
/// Capability panels (`src/ui_source.cpp`). `build_source_panels` adds the source kind's panels as
|
||||||
/// pages of the slide-over (`source-<id>`), once, before the first tick. `apply_source_panels` sets every panel's
|
/// pages of the slide-over (`source-<id>`), once, before the first tick. `apply_source_panels` sets every panel's
|
||||||
/// sensitivity from `connected` -- disabled, never hidden.
|
/// sensitivity from `connected` -- disabled, never hidden.
|
||||||
@@ -1803,8 +1789,9 @@ void row_menu_fill(RowMenuState& st);
|
|||||||
GtkWidget* open_row_menu(App& app, int row, double x, double y, GtkWidget* parent, bool autohide = true);
|
GtkWidget* open_row_menu(App& app, int row, double x, double y, GtkWidget* parent, bool autohide = true);
|
||||||
void on_right_click(GtkGestureClick* gesture, int, double x, double y, gpointer user);
|
void on_right_click(GtkGestureClick* gesture, int, double x, double y, gpointer user);
|
||||||
gboolean on_drop_channel(GtkDropTarget*, const GValue* value, double x, double y, gpointer user);
|
gboolean on_drop_channel(GtkDropTarget*, const GValue* value, double x, double y, gpointer user);
|
||||||
void on_side_tab_changed(GObject*, GParamSpec*, gpointer user);
|
/// Scrolls the dock to a section: "signals" (the top) or "measure" (the cursors panel, which the
|
||||||
void select_side_tab(App& app, const char* name);
|
/// measurements follow). An unknown name is a no-op.
|
||||||
|
void dock_show_section(App& app, const char* section);
|
||||||
/// Reads each pane's own switch back off the header bar's button, and the other way round: the two are
|
/// Reads each pane's own switch back off the header bar's button, and the other way round: the two are
|
||||||
/// one control in two places, and `panel_switch_sync` keeps the write from looking like a click.
|
/// one control in two places, and `panel_switch_sync` keeps the write from looking like a click.
|
||||||
void sync_panel_switches(App& app);
|
void sync_panel_switches(App& app);
|
||||||
@@ -1907,8 +1894,9 @@ gboolean measurepin_live_wait(gpointer user);
|
|||||||
gboolean measure_trace(gpointer user);
|
gboolean measure_trace(gpointer user);
|
||||||
gboolean stats_probe_report(gpointer user);
|
gboolean stats_probe_report(gpointer user);
|
||||||
gboolean stats_probe(gpointer user);
|
gboolean stats_probe(gpointer user);
|
||||||
gboolean side_tab_probe_report(gpointer user);
|
gboolean dock_section_probe_report(gpointer user);
|
||||||
gboolean side_tab_probe(gpointer user);
|
gboolean dock_section_probe(gpointer user);
|
||||||
|
gboolean dock_session_probe(gpointer user); // UDPSCOPE_PROBE_DOCK_SESSION_MS
|
||||||
/// The slide-over panel (`src/ui_slideover.cpp`): one panel at a time over the plot's right edge, a page
|
/// The slide-over panel (`src/ui_slideover.cpp`): one panel at a time over the plot's right edge, a page
|
||||||
/// per setup surface, each page in a scroller of its own.
|
/// per setup surface, each page in a scroller of its own.
|
||||||
void build_slideover(App& app); // once, after the plot_overlay exists
|
void build_slideover(App& app); // once, after the plot_overlay exists
|
||||||
|
|||||||
+5
-2
@@ -64,7 +64,7 @@ LayoutState collect_layout(App& app) {
|
|||||||
st.math = app.math;
|
st.math = app.math;
|
||||||
st.measures = app.measures;
|
st.measures = app.measures;
|
||||||
st.measure_stats = app.measure_stats_on;
|
st.measure_stats = app.measure_stats_on;
|
||||||
st.side_tab = app.side_tab;
|
st.dock_shown = app.sidebar_wanted;
|
||||||
// The trigger, its settings *and* whether it was on. The channel goes by name, like every identity
|
// The trigger, its settings *and* whether it was on. The channel goes by name, like every identity
|
||||||
// in this file: every rebuild renumbers the channels.
|
// in this file: every rebuild renumbers the channels.
|
||||||
{
|
{
|
||||||
@@ -427,7 +427,10 @@ void apply_layout(App& app, const LayoutState& st) {
|
|||||||
app.measures = st.measures;
|
app.measures = st.measures;
|
||||||
app.measure_stats_on = st.measure_stats;
|
app.measure_stats_on = st.measure_stats;
|
||||||
app.updating_panels = true;
|
app.updating_panels = true;
|
||||||
select_side_tab(app, st.side_tab.c_str());
|
// The dock's shown state is the wish `update_side_visibility()` re-asserts every frame; setting the
|
||||||
|
// toggle goes through `sidebar_syncing`, so the restore does not schedule a save of what it just read.
|
||||||
|
app.sidebar_wanted = st.dock_shown;
|
||||||
|
update_side_visibility(app);
|
||||||
if (app.measures_stats_check != nullptr) {
|
if (app.measures_stats_check != nullptr) {
|
||||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(app.measures_stats_check),
|
gtk_check_button_set_active(GTK_CHECK_BUTTON(app.measures_stats_check),
|
||||||
app.measure_stats_on);
|
app.measure_stats_on);
|
||||||
|
|||||||
+177
-62
@@ -104,12 +104,6 @@ gboolean exit_probe_step3(gpointer user) {
|
|||||||
a->dbg_signal_h = gtk_widget_get_height(a->signal_panel);
|
a->dbg_signal_h = gtk_widget_get_height(a->signal_panel);
|
||||||
a->dbg_trigger_w = gtk_widget_get_width(a->trigger_panel);
|
a->dbg_trigger_w = gtk_widget_get_width(a->trigger_panel);
|
||||||
a->dbg_trigger_h = gtk_widget_get_height(a->trigger_panel);
|
a->dbg_trigger_h = gtk_widget_get_height(a->trigger_panel);
|
||||||
// The switcher's size as allocated: read here and not on the first tick, where the window has not
|
|
||||||
// been allocated yet and every widget still measures 0x0.
|
|
||||||
if (a->side_switcher != nullptr) {
|
|
||||||
a->dbg_switcher_w = gtk_widget_get_width(a->side_switcher);
|
|
||||||
a->dbg_switcher_h = gtk_widget_get_height(a->side_switcher);
|
|
||||||
}
|
|
||||||
// How many rows of the signal list carry a drag source: dragging a signal onto a plot is how a plot is
|
// How many rows of the signal list carry a drag source: dragging a signal onto a plot is how a plot is
|
||||||
// filled, so it has to survive both pane modes -- the locked list drops the tick, the menu and the `+`,
|
// filled, so it has to survive both pane modes -- the locked list drops the tick, the menu and the `+`,
|
||||||
// and this is the reading that says it did not drop the drag with them. One row is the `MATH` divider,
|
// and this is the reading that says it did not drop the drag with them. One row is the `MATH` divider,
|
||||||
@@ -187,14 +181,9 @@ gboolean exit_probe_step3(gpointer user) {
|
|||||||
}
|
}
|
||||||
if (a->area != nullptr) a->dbg_frames_painted = static_cast<int>(a->area->frames_painted());
|
if (a->area != nullptr) a->dbg_frames_painted = static_cast<int>(a->area->frames_painted());
|
||||||
a->dbg_reset_sensitive = a->cursors_reset != nullptr && gtk_widget_get_sensitive(a->cursors_reset) ? 1 : 0;
|
a->dbg_reset_sensitive = a->cursors_reset != nullptr && gtk_widget_get_sensitive(a->cursors_reset) ? 1 : 0;
|
||||||
// A page, not a revealer: a stack's hidden page measures 0x0 the way an unrevealed revealer
|
// The signal list has rows: a reading about *state* (the dock has no page to be current on), never
|
||||||
// did, so the reading asks about *state* -- is this page the current one -- and never about size.
|
// about size.
|
||||||
const char* page = a->side_stack != nullptr
|
a->dbg_signal_page_current = gtk_widget_get_first_child(a->signal_list) != nullptr ? 1 : 0;
|
||||||
? adw_view_stack_get_visible_child_name(ADW_VIEW_STACK(a->side_stack))
|
|
||||||
: nullptr;
|
|
||||||
const int on_signals = page != nullptr && std::strcmp(page, "signals") == 0 ? 1 : 0;
|
|
||||||
a->dbg_signal_page_current =
|
|
||||||
on_signals && gtk_widget_get_first_child(a->signal_list) != nullptr ? 1 : 0;
|
|
||||||
// The trigger form is no longer on a sidebar tab, so the reading is the body's own sensitivity.
|
// The trigger form is no longer on a sidebar tab, so the reading is the body's own sensitivity.
|
||||||
a->dbg_trigger_sensitive = gtk_widget_get_sensitive(a->trigger_body) ? 1 : 0;
|
a->dbg_trigger_sensitive = gtk_widget_get_sensitive(a->trigger_body) ? 1 : 0;
|
||||||
// `gtk_widget_get_visible` on the sidebar's GtkBox is always 1: AdwOverlaySplitView hides its
|
// `gtk_widget_get_visible` on the sidebar's GtkBox is always 1: AdwOverlaySplitView hides its
|
||||||
@@ -203,8 +192,17 @@ gboolean exit_probe_step3(gpointer user) {
|
|||||||
const int sidebar_shown = a->side_view != nullptr &&
|
const int sidebar_shown = a->side_view != nullptr &&
|
||||||
adw_overlay_split_view_get_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(a->side_view)) ? 1 : 0;
|
adw_overlay_split_view_get_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(a->side_view)) ? 1 : 0;
|
||||||
a->dbg_side_column_after_hide = sidebar_shown;
|
a->dbg_side_column_after_hide = sidebar_shown;
|
||||||
a->dbg_switcher_visible =
|
// The dock, read here with the other sizes (the dump runs after the window is gone). `sensitive` is the
|
||||||
a->side_switcher != nullptr && gtk_widget_get_visible(a->side_switcher) ? 1 : 0;
|
// availability: the column is on screen whether or not the stream has signals, and does nothing until
|
||||||
|
// it does.
|
||||||
|
a->dbg_dock_shown = sidebar_shown;
|
||||||
|
a->dbg_dock_sensitive = gtk_widget_get_sensitive(a->side_box) ? 1 : 0;
|
||||||
|
a->dbg_dock_w = a->dbg_side_w;
|
||||||
|
a->dbg_dock_scrollers = 0;
|
||||||
|
for (GtkWidget* c = gtk_widget_get_first_child(a->side_box); c != nullptr;
|
||||||
|
c = gtk_widget_get_next_sibling(c)) {
|
||||||
|
if (GTK_IS_SCROLLED_WINDOW(c)) ++a->dbg_dock_scrollers;
|
||||||
|
}
|
||||||
if (a->trigger_signal_drop != nullptr) {
|
if (a->trigger_signal_drop != nullptr) {
|
||||||
GListModel* model = gtk_drop_down_get_model(GTK_DROP_DOWN(a->trigger_signal_drop));
|
GListModel* model = gtk_drop_down_get_model(GTK_DROP_DOWN(a->trigger_signal_drop));
|
||||||
a->dbg_trigger_items = model != nullptr ? static_cast<int>(g_list_model_get_n_items(model)) : 0;
|
a->dbg_trigger_items = model != nullptr ? static_cast<int>(g_list_model_get_n_items(model)) : 0;
|
||||||
@@ -1795,11 +1793,24 @@ gboolean stats_probe(gpointer user) {
|
|||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phase two of the tab-switch probe: the hidden page's accumulator must have advanced. A statistics
|
/// Which section the dock's scroller is showing at its top: "measure" once it has been scrolled to the
|
||||||
/// readout is *history*, so min/max/avg would gain a gap for exactly as long as the trigger was on
|
/// cursors panel or past it, "signals" above that. Read from the adjustment, so it is the state the user
|
||||||
/// screen -- which is the one defect the design's "what runs on a hidden page" rule is there to
|
/// would see and not the last call made.
|
||||||
/// prevent, and the reason no frame-rate code may be gated on which tab is current.
|
static const char* dock_section_in_view(App& a) {
|
||||||
gboolean side_tab_probe_report(gpointer user) {
|
if (a.dock_scroller == nullptr || a.cursors_panel == nullptr || a.dock_page == nullptr) return "none";
|
||||||
|
graphene_point_t zero = GRAPHENE_POINT_INIT(0.f, 0.f);
|
||||||
|
graphene_point_t at;
|
||||||
|
if (!gtk_widget_compute_point(a.cursors_panel, a.dock_page, &zero, &at)) return "none";
|
||||||
|
GtkAdjustment* adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(a.dock_scroller));
|
||||||
|
return gtk_adjustment_get_value(adj) > 0.5 * at.y ? "measure" : "signals";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase two of the dock-section probe: the accumulators must have advanced while the Measure section was
|
||||||
|
/// scrolled out of view. A statistics readout is *history*, so min/max/avg would gain a gap for exactly as
|
||||||
|
/// long as the section was off screen -- which is the one defect the design's "what runs on a hidden
|
||||||
|
/// page" rule is there to prevent, and the reason no frame-rate code may be gated on which section is in
|
||||||
|
/// view. (It was a tab once; the dock is one scroller, and the rule outlived the tabs.)
|
||||||
|
gboolean dock_section_probe_report(gpointer user) {
|
||||||
App* a = static_cast<App*>(user);
|
App* a = static_cast<App*>(user);
|
||||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
||||||
std::uint64_t advanced = 0;
|
std::uint64_t advanced = 0;
|
||||||
@@ -1807,25 +1818,37 @@ gboolean side_tab_probe_report(gpointer user) {
|
|||||||
bool first = true;
|
bool first = true;
|
||||||
for (std::size_t i = 0; i < a->measure_stats.size(); ++i) {
|
for (std::size_t i = 0; i < a->measure_stats.size(); ++i) {
|
||||||
const std::uint64_t n = a->measure_stats[i].n;
|
const std::uint64_t n = a->measure_stats[i].n;
|
||||||
if (i < g_side_tab_n_before.size() && n > g_side_tab_n_before[i]) ++advanced;
|
if (i < g_dock_section_n_before.size() && n > g_dock_section_n_before[i]) ++advanced;
|
||||||
if (first || n < min_n) {
|
if (first || n < min_n) {
|
||||||
min_n = n;
|
min_n = n;
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (a->dock_page != nullptr) gtk_widget_set_size_request(a->dock_page, -1, -1);
|
||||||
std::fprintf(stderr,
|
std::fprintf(stderr,
|
||||||
"[side-tab-probe] page=%s rows=%zu accums=%zu advanced=%" G_GUINT64_FORMAT
|
"[side-tab-probe] section=%s rows=%zu accums=%zu advanced=%" G_GUINT64_FORMAT
|
||||||
" min_n=%" G_GUINT64_FORMAT "\n",
|
" min_n=%" G_GUINT64_FORMAT "\n",
|
||||||
a->side_tab.c_str(), a->measures.size(), a->measure_stats.size(), advanced, min_n);
|
dock_section_in_view(*a), a->measures.size(), a->measure_stats.size(), advanced, min_n);
|
||||||
std::fflush(stderr);
|
std::fflush(stderr);
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phase one: four measurements on the first channel, statistics on, then switch *away* from the
|
/// Phase one: four measurements on the first channel, statistics on, then scroll the dock *away* from the
|
||||||
/// Measure tab and come back in two seconds to read what ran while it was not on screen.
|
/// Measure section and come back in two seconds to read what ran while it was not in view. (The probe
|
||||||
gboolean side_tab_probe(gpointer user) {
|
/// keeps its old environment variable, `UDPSCOPE_PROBE_SIDE_TAB_MS`, and its `[side-tab-probe]` tag.)
|
||||||
|
gboolean dock_section_probe(gpointer user) {
|
||||||
App* a = static_cast<App*>(user);
|
App* a = static_cast<App*>(user);
|
||||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
||||||
|
// A dock whose content fits has nothing to scroll away from, and a tall window does that. The first
|
||||||
|
// pass makes the page taller than any window (a probe-only minimum height, undone in the report) and
|
||||||
|
// comes back once it has been laid out, so "scrolled out of view" is a state this run really reaches.
|
||||||
|
static bool padded = false;
|
||||||
|
if (!padded && a->dock_page != nullptr) {
|
||||||
|
padded = true;
|
||||||
|
gtk_widget_set_size_request(a->dock_page, -1, 3000);
|
||||||
|
g_timeout_add(400, dock_section_probe, a);
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
// One signal on, and on a plot, the way `enable_all_probe` does it and a restored session arrives.
|
// One signal on, and on a plot, the way `enable_all_probe` does it and a restored session arrives.
|
||||||
// Both halves matter here: a signal starts disabled (enabling is what starts acquisition), and a
|
// Both halves matter here: a signal starts disabled (enabling is what starts acquisition), and a
|
||||||
// channel with no trace leaves the live window *still* -- and a still window is exactly the state
|
// channel with no trace leaves the live window *still* -- and a still window is exactly the state
|
||||||
@@ -1863,18 +1886,23 @@ gboolean side_tab_probe(gpointer user) {
|
|||||||
if (a->measures_stats_check != nullptr) {
|
if (a->measures_stats_check != nullptr) {
|
||||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(a->measures_stats_check), TRUE);
|
gtk_check_button_set_active(GTK_CHECK_BUTTON(a->measures_stats_check), TRUE);
|
||||||
}
|
}
|
||||||
select_side_tab(*a, "measure");
|
dock_show_section(*a, "measure");
|
||||||
|
// Where the dock went: `scrolled` is its offset and `reach` the most it can scroll. A reach of 0 would
|
||||||
|
// mean the content fits and "away from the section" is not a state this run can reach.
|
||||||
|
GtkAdjustment* dock_adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(a->dock_scroller));
|
||||||
|
const double scrolled = gtk_adjustment_get_value(dock_adj);
|
||||||
|
const double reach = gtk_adjustment_get_upper(dock_adj) - gtk_adjustment_get_page_size(dock_adj);
|
||||||
update_measures(*a, /*force=*/true, /*accumulate=*/true);
|
update_measures(*a, /*force=*/true, /*accumulate=*/true);
|
||||||
g_side_tab_n_before.clear();
|
g_dock_section_n_before.clear();
|
||||||
for (const MeasureStatsAccum& s : a->measure_stats) g_side_tab_n_before.push_back(s.n);
|
for (const MeasureStatsAccum& s : a->measure_stats) g_dock_section_n_before.push_back(s.n);
|
||||||
// Away from the page the measurements are on: its widgets are now unrealized and measure 0x0, and
|
// Away from the section the measurements are in: they are scrolled out of the viewport (the widgets
|
||||||
// the tick keeps calling `update_measures()` regardless.
|
// stay mapped, only clipped), and the tick keeps calling `update_measures()` regardless.
|
||||||
select_side_tab(*a, "signals");
|
dock_show_section(*a, "signals");
|
||||||
std::fprintf(stderr, "[side-tab-probe] page=%s accums=%zu baseline recorded\n",
|
std::fprintf(stderr, "[side-tab-probe] section=%s accums=%zu baseline recorded (measure at scroll %.0f of %.0f)\n",
|
||||||
a->side_tab.c_str(), g_side_tab_n_before.size());
|
dock_section_in_view(*a), g_dock_section_n_before.size(), scrolled, reach);
|
||||||
std::fflush(stderr);
|
std::fflush(stderr);
|
||||||
// Long enough for several throttled recomputes (one per 100 ms while the window moves).
|
// Long enough for several throttled recomputes (one per 100 ms while the window moves).
|
||||||
g_timeout_add(2000, side_tab_probe_report, a);
|
g_timeout_add(2000, dock_section_probe_report, a);
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3573,6 +3601,63 @@ gboolean sidebar_probe(gpointer user) {
|
|||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `UDPSCOPE_PROBE_DOCK_SESSION_MS`: the dock's shown state through the session file, both ways.
|
||||||
|
///
|
||||||
|
/// The Dock button's state is the one thing the dock persists (`dock = true|false`, replacing `side_tab`).
|
||||||
|
/// Leg 1 hides the dock through the button (a wish, so it must schedule a save), collects the session and
|
||||||
|
/// writes it, reads the file back and applies it; leg 2 does the same shown. Leg 3 loads a file written by
|
||||||
|
/// the previous version (`side_tab = "measure"`, no `dock`): it must load without a complaint and leave
|
||||||
|
/// `dock_shown` at its default, true. Read as `ok=1` on every line.
|
||||||
|
gboolean dock_session_probe(gpointer user) {
|
||||||
|
App& a = *static_cast<App*>(user);
|
||||||
|
if (a.window == nullptr || a.sidebar_toggle == nullptr) return G_SOURCE_REMOVE;
|
||||||
|
const std::string dir = g_get_tmp_dir();
|
||||||
|
const std::string path = dir + "/udpscope-dock-session-probe.toml";
|
||||||
|
const auto leg = [&](const char* name, bool shown) {
|
||||||
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a.sidebar_toggle), shown);
|
||||||
|
const bool wish = a.sidebar_wanted;
|
||||||
|
const bool save_pending = a.layout_save_source != 0;
|
||||||
|
LayoutState st = collect_layout(a);
|
||||||
|
const bool collected = st.dock_shown;
|
||||||
|
const bool saved = save_layout(path, st);
|
||||||
|
LayoutState back;
|
||||||
|
const bool loaded = load_layout(path, back);
|
||||||
|
// Apply the *opposite* first so the restore has something to undo, then the loaded state.
|
||||||
|
a.sidebar_wanted = !shown;
|
||||||
|
apply_layout(a, back);
|
||||||
|
const bool applied = a.sidebar_wanted == shown;
|
||||||
|
const bool split_follows =
|
||||||
|
(adw_overlay_split_view_get_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(a.side_view)) != FALSE) == shown;
|
||||||
|
std::fprintf(stderr,
|
||||||
|
"[dock-session] %s: wish=%d save_scheduled=%d collected=%d saved=%d loaded=%d read=%d "
|
||||||
|
"applied=%d split_follows=%d ok=%d\n",
|
||||||
|
name, wish ? 1 : 0, save_pending ? 1 : 0, collected ? 1 : 0, saved ? 1 : 0,
|
||||||
|
loaded ? 1 : 0, back.dock_shown ? 1 : 0, applied ? 1 : 0, split_follows ? 1 : 0,
|
||||||
|
(wish == shown && save_pending && collected == shown && saved && loaded &&
|
||||||
|
back.dock_shown == shown && applied)
|
||||||
|
? 1 : 0);
|
||||||
|
};
|
||||||
|
leg("hidden", false);
|
||||||
|
leg("shown", true);
|
||||||
|
{
|
||||||
|
std::FILE* f = std::fopen(path.c_str(), "w");
|
||||||
|
if (f != nullptr) {
|
||||||
|
std::fputs("side_tab = \"measure\"\nmeasure_stats = false\n", f);
|
||||||
|
std::fclose(f);
|
||||||
|
}
|
||||||
|
LayoutState old_file;
|
||||||
|
old_file.dock_shown = false; // anything but the default, so "left at the default" is a reading
|
||||||
|
const bool loaded = load_layout(path, old_file);
|
||||||
|
LayoutState fresh;
|
||||||
|
std::fprintf(stderr, "[dock-session] old file (side_tab only): loaded=%d dock_shown=%d default=%d ok=%d\n",
|
||||||
|
loaded ? 1 : 0, old_file.dock_shown ? 1 : 0, fresh.dock_shown ? 1 : 0,
|
||||||
|
loaded && fresh.dock_shown ? 1 : 0);
|
||||||
|
}
|
||||||
|
std::remove(path.c_str());
|
||||||
|
std::fflush(stderr);
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean prefs_probe(gpointer user) {
|
gboolean prefs_probe(gpointer user) {
|
||||||
App& a = *static_cast<App*>(user);
|
App& a = *static_cast<App*>(user);
|
||||||
open_preferences(a);
|
open_preferences(a);
|
||||||
@@ -4524,7 +4609,7 @@ gboolean measures_probe_step(gpointer user) {
|
|||||||
m.options.show_on_plot = measure_can_show_on_plot(m.type);
|
m.options.show_on_plot = measure_can_show_on_plot(m.type);
|
||||||
a->measures.push_back(std::move(m));
|
a->measures.push_back(std::move(m));
|
||||||
}
|
}
|
||||||
select_side_tab(*a, "measure");
|
dock_show_section(*a, "measure");
|
||||||
rebuild_measures_pane(*a);
|
rebuild_measures_pane(*a);
|
||||||
std::fprintf(stderr, "[measures-probe] added %zu measurements on '%s'\n", a->measures.size(),
|
std::fprintf(stderr, "[measures-probe] added %zu measurements on '%s'\n", a->measures.size(),
|
||||||
signal.c_str());
|
signal.c_str());
|
||||||
@@ -4683,7 +4768,7 @@ gboolean bandavg_probe_step(gpointer user) {
|
|||||||
m.options.show_on_plot = measure_can_show_on_plot(m.type);
|
m.options.show_on_plot = measure_can_show_on_plot(m.type);
|
||||||
a->measures.push_back(std::move(m));
|
a->measures.push_back(std::move(m));
|
||||||
}
|
}
|
||||||
select_side_tab(*a, "measure");
|
dock_show_section(*a, "measure");
|
||||||
rebuild_measures_pane(*a);
|
rebuild_measures_pane(*a);
|
||||||
g_timeout_add(250, measure_trace, a);
|
g_timeout_add(250, measure_trace, a);
|
||||||
bandavg_probe_next(*a, 1, 1500);
|
bandavg_probe_next(*a, 1, 1500);
|
||||||
@@ -6247,6 +6332,16 @@ gboolean defaults_probe(gpointer user) {
|
|||||||
|
|
||||||
gboolean keys_probe(gpointer user) {
|
gboolean keys_probe(gpointer user) {
|
||||||
App* a = static_cast<App*>(user);
|
App* a = static_cast<App*>(user);
|
||||||
|
// `m` scrolls the dock, and a dock whose content fits has nothing to scroll: a run on a tall window
|
||||||
|
// would read 0->0 and prove nothing. So the first pass only makes the page taller than any window
|
||||||
|
// (a probe-only minimum height, undone below) and comes back once it has been laid out.
|
||||||
|
static bool padded = false;
|
||||||
|
if (!padded && a->dock_page != nullptr) {
|
||||||
|
padded = true;
|
||||||
|
gtk_widget_set_size_request(a->dock_page, -1, 3000);
|
||||||
|
g_timeout_add(400, keys_probe, a);
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
std::string line = "[keys]";
|
std::string line = "[keys]";
|
||||||
|
|
||||||
const auto before_after = [&](const char* name, GtkWidget* button, AppAction action) {
|
const auto before_after = [&](const char* name, GtkWidget* button, AppAction action) {
|
||||||
@@ -6282,38 +6377,56 @@ gboolean keys_probe(gpointer user) {
|
|||||||
|
|
||||||
before_after("t: trigger panel", a->trigger_toggle, AppAction::TriggerPanel);
|
before_after("t: trigger panel", a->trigger_toggle, AppAction::TriggerPanel);
|
||||||
before_after("c: cursors", a->cursors_toggle, AppAction::CursorsPane);
|
before_after("c: cursors", a->cursors_toggle, AppAction::CursorsPane);
|
||||||
|
// `m` and `g` scroll the dock: the reading is the scroller's offset before and after, and `m` must land
|
||||||
|
// on the cursors panel's own y (clamped to what the page can scroll, which is why the reachable maximum
|
||||||
|
// is printed beside it), `g` back on 0.
|
||||||
|
const auto dock_adjustment = [a]() {
|
||||||
|
return gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(a->dock_scroller));
|
||||||
|
};
|
||||||
|
const auto dock_value = [&]() { return gtk_adjustment_get_value(dock_adjustment()); };
|
||||||
{
|
{
|
||||||
const std::string before_tab = a->side_tab;
|
const double before = dock_value();
|
||||||
run_app_action(*a, AppAction::SideTabMeasure);
|
run_app_action(*a, AppAction::ShowMeasure);
|
||||||
const std::string after_tab = a->side_tab;
|
const double after = dock_value();
|
||||||
char buf[96];
|
graphene_point_t zero = GRAPHENE_POINT_INIT(0.f, 0.f);
|
||||||
std::snprintf(buf, sizeof(buf), " m: tab %s->%s |", before_tab.c_str(), after_tab.c_str());
|
graphene_point_t at = GRAPHENE_POINT_INIT(-1.f, -1.f);
|
||||||
|
if (!gtk_widget_compute_point(a->cursors_panel, a->dock_page, &zero, &at)) at.y = -1.f;
|
||||||
|
GtkAdjustment* adj = dock_adjustment();
|
||||||
|
const double reach = gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj);
|
||||||
|
char buf[160];
|
||||||
|
std::snprintf(buf, sizeof(buf), " m: dock %.0f->%.0f (cursors_y=%.0f reach=%.0f at_cursors=%d) |", before,
|
||||||
|
after, static_cast<double>(at.y), reach,
|
||||||
|
std::fabs(after - std::min(static_cast<double>(at.y), reach)) < 1.0 ? 1 : 0);
|
||||||
line += buf;
|
line += buf;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const std::string before_tab = a->side_tab;
|
const double before = dock_value();
|
||||||
run_app_action(*a, AppAction::SideTabSignals);
|
run_app_action(*a, AppAction::ShowSignals);
|
||||||
const std::string after_tab = a->side_tab;
|
const double after = dock_value();
|
||||||
char buf[96];
|
char buf[96];
|
||||||
std::snprintf(buf, sizeof(buf), " g: tab %s->%s |", before_tab.c_str(), after_tab.c_str());
|
std::snprintf(buf, sizeof(buf), " g: dock %.0f->%.0f at_top=%d |", before, after,
|
||||||
|
std::fabs(after) < 1.0 ? 1 : 0);
|
||||||
line += buf;
|
line += buf;
|
||||||
}
|
}
|
||||||
before_after("l: layout", a->layout_toggle, AppAction::LayoutEditor);
|
before_after("l: layout", a->layout_toggle, AppAction::LayoutEditor);
|
||||||
before_after("s: side panel", a->sidebar_toggle, AppAction::SidePanel);
|
before_after("s: side panel", a->sidebar_toggle, AppAction::SidePanel);
|
||||||
|
|
||||||
// An accel must be refused while text is being typed. Focus the window entry, send 'm'
|
// An accel must be refused while text is being typed. Focus the window entry, send 'm'
|
||||||
// through the real handler, and require the side tab not to change.
|
// through the real handler, and require the dock not to scroll.
|
||||||
int refused = -1;
|
int refused = -1;
|
||||||
if (a->window_entry != nullptr) {
|
if (a->window_entry != nullptr) {
|
||||||
gtk_menu_button_popup(GTK_MENU_BUTTON(a->window_button));
|
gtk_menu_button_popup(GTK_MENU_BUTTON(a->window_button));
|
||||||
gtk_widget_grab_focus(a->window_entry);
|
gtk_widget_grab_focus(a->window_entry);
|
||||||
const std::string before_tab = a->side_tab;
|
// The dock has to be somewhere other than where `m` would take it, or "did not scroll" proves nothing.
|
||||||
|
gtk_adjustment_set_value(dock_adjustment(), 0.0);
|
||||||
|
const double before_dock = dock_value();
|
||||||
const gboolean handled = on_app_key_pressed(nullptr, GDK_KEY_m, 0,
|
const gboolean handled = on_app_key_pressed(nullptr, GDK_KEY_m, 0,
|
||||||
static_cast<GdkModifierType>(0), a);
|
static_cast<GdkModifierType>(0), a);
|
||||||
const std::string after_tab = a->side_tab;
|
const double after_dock = dock_value();
|
||||||
refused = (handled == FALSE && before_tab == after_tab) ? 1 : 0;
|
refused = (handled == FALSE && before_dock == after_dock) ? 1 : 0;
|
||||||
gtk_menu_button_popdown(GTK_MENU_BUTTON(a->window_button));
|
gtk_menu_button_popdown(GTK_MENU_BUTTON(a->window_button));
|
||||||
}
|
}
|
||||||
|
if (a->dock_page != nullptr) gtk_widget_set_size_request(a->dock_page, -1, -1);
|
||||||
char tail[64];
|
char tail[64];
|
||||||
std::snprintf(tail, sizeof(tail), " editable focused: 'm' refused=%d\n", refused);
|
std::snprintf(tail, sizeof(tail), " editable focused: 'm' refused=%d\n", refused);
|
||||||
line += tail;
|
line += tail;
|
||||||
@@ -6570,10 +6683,10 @@ void tour_geometry_line(App& a, const char* probe, const char* what) {
|
|||||||
p.origin.y < q.origin.y + q.size.height && q.origin.y < p.origin.y + p.size.height;
|
p.origin.y < q.origin.y + q.size.height && q.origin.y < p.origin.y + p.size.height;
|
||||||
};
|
};
|
||||||
std::fprintf(stderr,
|
std::fprintf(stderr,
|
||||||
"[%s] %s=%d/%d visible=%d side_tab=%s spot=[%.0f,%.0f %.0fx%.0f] "
|
"[%s] %s=%d/%d visible=%d slideover=%s spot=[%.0f,%.0f %.0fx%.0f] "
|
||||||
"target=[%.0f,%.0f %.0fx%.0f] contains=%d card=[%.0f,%.0f %.0fx%.0f] clear=%d\n",
|
"target=[%.0f,%.0f %.0fx%.0f] contains=%d card=[%.0f,%.0f %.0fx%.0f] clear=%d\n",
|
||||||
probe, what, tour_step_index(a) + 1, tour_step_count(a), tour_active(a) ? 1 : 0,
|
probe, what, tour_step_index(a) + 1, tour_step_count(a), tour_active(a) ? 1 : 0,
|
||||||
a.side_tab.c_str(), spot.origin.x, spot.origin.y, spot.size.width, spot.size.height,
|
(slideover_page(a).empty() ? "none" : slideover_page(a).c_str()), spot.origin.x, spot.origin.y, spot.size.width, spot.size.height,
|
||||||
target.origin.x, target.origin.y, target.size.width, target.size.height,
|
target.origin.x, target.origin.y, target.size.width, target.size.height,
|
||||||
have_spot && have_target && contains(spot, target) ? 1 : 0, card.origin.x,
|
have_spot && have_target && contains(spot, target) ? 1 : 0, card.origin.x,
|
||||||
card.origin.y, card.size.width, card.size.height,
|
card.origin.y, card.size.width, card.size.height,
|
||||||
@@ -6818,10 +6931,10 @@ gboolean tour_surface_probe_step(gpointer user) {
|
|||||||
case 0: {
|
case 0: {
|
||||||
// Where the run starts: a fresh config's sidebar is on Signals, so this is a *first* run's
|
// Where the run starts: a fresh config's sidebar is on Signals, so this is a *first* run's
|
||||||
// state. The auto-start's walk (if one began) would only confuse the reading; it goes first.
|
// state. The auto-start's walk (if one began) would only confuse the reading; it goes first.
|
||||||
std::fprintf(stderr, "[toursurface] before: side_tab=%s signal_list_mapped=%d running=%d\n",
|
std::fprintf(stderr, "[toursurface] before: slideover=%s signal_list_mapped=%d running=%d\n",
|
||||||
a->side_tab.c_str(), mapped(a->signal_list), tour_active(*a) ? 1 : 0);
|
slideover_page(*a).empty() ? "none" : slideover_page(*a).c_str(), mapped(a->signal_list), tour_active(*a) ? 1 : 0);
|
||||||
tour_dismiss(*a);
|
tour_dismiss(*a);
|
||||||
select_side_tab(*a, "measure");
|
slideover_open(*a, "trigger");
|
||||||
a->dbg_tour_surface_step = 1;
|
a->dbg_tour_surface_step = 1;
|
||||||
tour_surface_probe_next(*a, 500);
|
tour_surface_probe_next(*a, 500);
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
@@ -6830,8 +6943,9 @@ gboolean tour_surface_probe_step(gpointer user) {
|
|||||||
// A frame after the switch, so the Measure page has been laid out and the list really is off
|
// A frame after the switch, so the Measure page has been laid out and the list really is off
|
||||||
// screen. The tour is started *here*, from this state.
|
// screen. The tour is started *here*, from this state.
|
||||||
std::fprintf(stderr,
|
std::fprintf(stderr,
|
||||||
"[toursurface] on measure: side_tab=%s signal_list_mapped=%d side_stack_mapped=%d\n",
|
"[toursurface] on trigger: slideover=%s signal_list_mapped=%d side_box_mapped=%d\n",
|
||||||
a->side_tab.c_str(), mapped(a->signal_list), mapped(a->side_stack));
|
slideover_page(*a).empty() ? "none" : slideover_page(*a).c_str(),
|
||||||
|
mapped(a->signal_list), mapped(a->side_box));
|
||||||
start_tour(*a);
|
start_tour(*a);
|
||||||
std::fprintf(stderr, "[toursurface] tour: visible=%d steps=%d step=%d signals_stop=%d\n",
|
std::fprintf(stderr, "[toursurface] tour: visible=%d steps=%d step=%d signals_stop=%d\n",
|
||||||
tour_active(*a) ? 1 : 0, tour_step_count(*a), tour_step_index(*a),
|
tour_active(*a) ? 1 : 0, tour_step_count(*a), tour_step_index(*a),
|
||||||
@@ -6848,8 +6962,9 @@ gboolean tour_surface_probe_step(gpointer user) {
|
|||||||
}
|
}
|
||||||
GtkWidget* target = tour_target(*a);
|
GtkWidget* target = tour_target(*a);
|
||||||
const bool is_list = target != nullptr && target == a->signal_list;
|
const bool is_list = target != nullptr && target == a->signal_list;
|
||||||
std::fprintf(stderr, "[toursurface] step=%d/%d side_tab=%s target_mapped=%d%s\n",
|
std::fprintf(stderr, "[toursurface] step=%d/%d slideover=%s target_mapped=%d%s\n",
|
||||||
tour_step_index(*a) + 1, tour_step_count(*a), a->side_tab.c_str(), mapped(target),
|
tour_step_index(*a) + 1, tour_step_count(*a),
|
||||||
|
slideover_page(*a).empty() ? "none" : slideover_page(*a).c_str(), mapped(target),
|
||||||
is_list ? " target=signal_list" : "");
|
is_list ? " target=signal_list" : "");
|
||||||
if (is_list) {
|
if (is_list) {
|
||||||
// The frame after the switch: the page has been laid out and `update_tour()` has placed the
|
// The frame after the switch: the page has been laid out and `update_tour()` has placed the
|
||||||
@@ -6876,9 +6991,9 @@ gboolean tour_surface_probe_step(gpointer user) {
|
|||||||
default: {
|
default: {
|
||||||
start_tour(*a);
|
start_tour(*a);
|
||||||
std::fprintf(stderr,
|
std::fprintf(stderr,
|
||||||
"[toursurface] collapsed: side_stack_mapped=%d steps=%d signals_stop=%d "
|
"[toursurface] collapsed: side_box_mapped=%d steps=%d signals_stop=%d "
|
||||||
"side_stop=%d\n",
|
"side_stop=%d\n",
|
||||||
mapped(a->side_stack), tour_step_count(*a),
|
mapped(a->side_box), tour_step_count(*a),
|
||||||
tour_plan_holds(*a, a->signal_list) ? 1 : 0,
|
tour_plan_holds(*a, a->signal_list) ? 1 : 0,
|
||||||
tour_plan_holds(*a, a->side_box) ? 1 : 0);
|
tour_plan_holds(*a, a->side_box) ? 1 : 0);
|
||||||
tour_dismiss(*a);
|
tour_dismiss(*a);
|
||||||
|
|||||||
@@ -11,48 +11,6 @@
|
|||||||
namespace udpscope {
|
namespace udpscope {
|
||||||
namespace ui_impl {
|
namespace ui_impl {
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
/// Hide every `GtkImage` inside `w`, and keep it hidden. `AdwViewSwitcherButton` always builds an
|
|
||||||
/// icon -- in both of its layouts -- and a page with no `icon-name` gets `image-missing`, which is
|
|
||||||
/// the placeholder the sidebar's text tabs used to draw. There is no property or policy that turns
|
|
||||||
/// the icon off, so the image is hidden (a hidden child takes no space: the label gets the pill) and
|
|
||||||
/// a `notify::visible` handler puts it back to hidden if the button ever shows it again.
|
|
||||||
void hide_tab_icons(GtkWidget* w) {
|
|
||||||
if (GTK_IS_IMAGE(w)) {
|
|
||||||
if (g_object_get_data(G_OBJECT(w), "udpscope-icon-hidden") == nullptr) {
|
|
||||||
g_object_set_data(G_OBJECT(w), "udpscope-icon-hidden", GINT_TO_POINTER(1));
|
|
||||||
g_signal_connect(w, "notify::visible", G_CALLBACK(+[](GObject* o, GParamSpec*, gpointer) {
|
|
||||||
GtkWidget* img = GTK_WIDGET(o);
|
|
||||||
if (gtk_widget_get_visible(img)) gtk_widget_set_visible(img, FALSE);
|
|
||||||
}),
|
|
||||||
nullptr);
|
|
||||||
}
|
|
||||||
gtk_widget_set_visible(w, FALSE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (GtkWidget* c = gtk_widget_get_first_child(w); c != nullptr; c = gtk_widget_get_next_sibling(c)) {
|
|
||||||
hide_tab_icons(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_tab_pages_changed(GListModel*, guint, guint, guint, gpointer sw) {
|
|
||||||
hide_tab_icons(GTK_WIDGET(sw));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void keep_tabs_icon_free(App& app) {
|
|
||||||
if (app.side_switcher == nullptr || app.side_stack == nullptr) return;
|
|
||||||
hide_tab_icons(app.side_switcher);
|
|
||||||
// A page added later gets a button when the model reports it; this handler is connected *after*
|
|
||||||
// the switcher's own, so the button already exists when it runs.
|
|
||||||
g_signal_connect_after(adw_view_stack_get_pages(ADW_VIEW_STACK(app.side_stack)), "items-changed",
|
|
||||||
G_CALLBACK(on_tab_pages_changed), app.side_switcher);
|
|
||||||
// And once more when the switcher is (re)mapped, which also covers a rebuild of the buttons.
|
|
||||||
g_signal_connect_swapped(app.side_switcher, "map", G_CALLBACK(hide_tab_icons), app.side_switcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
void build_source_panels(App& app) {
|
void build_source_panels(App& app) {
|
||||||
if (app.slideover.stack == nullptr || app.acq == nullptr || app.acq->source() == nullptr) return;
|
if (app.slideover.stack == nullptr || app.acq == nullptr || app.acq->source() == nullptr) return;
|
||||||
SourceKind* kind = find_kind(app.source_kind);
|
SourceKind* kind = find_kind(app.source_kind);
|
||||||
|
|||||||
+4
-7
@@ -422,11 +422,9 @@ void start_tour(App& app) {
|
|||||||
app.settings.tour_seen = true;
|
app.settings.tour_seen = true;
|
||||||
(void)save_settings(app.settings);
|
(void)save_settings(app.settings);
|
||||||
}
|
}
|
||||||
// The sidebar's own stack, so a stop can name the page it stands on. Set here and not in
|
// The main window has no page stack any more (the dock is one scroller): `page_stack` stays null, so
|
||||||
// `build_tour()`: the overlay is inserted above the window's content *before* `App::side_stack` is
|
// a stop's `page` selects nothing and a stop whose target is unmapped is dropped. The slide-over hook
|
||||||
// assigned, so the member does not exist yet at build time -- and the walk cannot start before the
|
// that lets a stop open the trigger page is the next step's.
|
||||||
// window is up anyway.
|
|
||||||
app.tour.page_stack = app.side_stack;
|
|
||||||
// The whole table, in walk order: `tour_begin()` drops the stops whose surface is not on screen.
|
// The whole table, in walk order: `tour_begin()` drops the stops whose surface is not on screen.
|
||||||
std::vector<TourStop> stops;
|
std::vector<TourStop> stops;
|
||||||
stops.reserve(kTourStepCount);
|
stops.reserve(kTourStepCount);
|
||||||
@@ -486,8 +484,7 @@ constexpr FigureSurface kFigureSurfaces[] = {
|
|||||||
{"trigger_summary", &App::trigger_summary},
|
{"trigger_summary", &App::trigger_summary},
|
||||||
{"trigger_panel", &App::trigger_panel},
|
{"trigger_panel", &App::trigger_panel},
|
||||||
{"signal_list", &App::signal_list},
|
{"signal_list", &App::signal_list},
|
||||||
{"side_switcher", &App::side_switcher},
|
{"side_stack", &App::side_box},
|
||||||
{"side_stack", &App::side_stack},
|
|
||||||
{"window_button", &App::window_button},
|
{"window_button", &App::window_button},
|
||||||
{"status_label", &App::status_label},
|
{"status_label", &App::status_label},
|
||||||
{"header_bar", &App::toolbar},
|
{"header_bar", &App::toolbar},
|
||||||
|
|||||||
Reference in new issue
Block a user