signal page: the header names it, numbered; no duplicate title or back button

The slide-over's header already prints the open page's title beside its Close button, so the
signal page's body carried the name twice and a "Back to signals" button that only repeated
navigation the header owns. Both are gone.

The header's title is now the **numbered** name -- `<n>. <name>`, `measure_channel_number()`'s
number, the same one the signal badges and the measurements pane use -- so the panel says
*which acquired channel* it is and not only what it is called. `slideover_set_title()`
retitles a page in place (the open one included): the number follows the stream's channel
numbering, and the page is refilled whenever the model's signal list changes.

Probe: `[signalpage]` reads `title='1. sine' title_ok=1` (the header text against the numbered
name) and `no_dup=1` (no "Back to signals" button and no `title-3` label in the body); the old
`back_to_list` reading is gone with the button. Help, README and the probe doc follow.
This commit is contained in:
Martino Ferrari committed 2026-11-05 18:08:50 +01:00
1 parent bdf0ff7ddc
commit 706d247b58
7 files changed
+76 -22

No files matched your search

+4 -1
View File
@@ -149,7 +149,10 @@ divider's `+` for a new math channel; when every signal is acquired a dim "every
stands in for the list. While the bar has overflowed into the "N signals" badge the page lists the acquired
signals too, since their rows are then the only way to each signal's page. There is no signal lock: a
signal's page and the list always allow acquire, colour, calibration, the math editor and delete.
A signal badge opens that signal's own page in the slide-over -- the unit, gain and offset and the CONFIG's details directly under the name (aligned rows; a pencil beside the unit, gain and offset edits just that one -- a unit rename is stored per signal name, saved as `[[unit]]` and re-applied to every CONFIG), the colour and the *Plot on N*
A signal badge opens that signal's own page in the slide-over -- its title is the **numbered name**
(`2. sine`, the same number the badge and the measurements pane use) and the body carries the unit,
gain and offset and the CONFIG's details directly under it, with no second copy of the name and no
"back to the list" button (the header's Close is the way out) (aligned rows; a pencil beside the unit, gain and offset edits just that one -- a unit rename is stored per signal name, saved as `[[unit]]` and re-applied to every CONFIG), the colour and the *Plot on N*
buttons -- and is also the drag handle: drag it onto a plot to draw the signal there, or onto another
badge to reorder them. Past **six** signal badges the bar shows a single **"N signals"** badge in their
place (it opens the list, and `+` is hidden): the E-1608's 8 analog inputs and counter would not fit
+11
View File
@@ -746,3 +746,14 @@ The `Help` submenu, the contextual help's "?" and the guided tour, and the traps
each is a `GtkWindow` added to the application, and one left behind keeps the GApplication's
main loop alive around a window that is gone -- the hang where the exit run never reached its
dump.
- **A page's name lives in the slide-over header, once.** The header prints the open page's title
(from the `GtkStackPage`) beside its **Close** button; a page body that repeats the name is the
same word twice, and a body-level "back to the list" button duplicates a navigation the header
already owns. Signal pages therefore carry **no** name label and **no** back button
(`[signalpage] no_dup=1`), and their title is the **numbered** name -- `<channel number>. <name>`,
the same `measure_channel_number()` the badges and the measurements pane use -- so the panel says
*which acquired channel* this is rather than only what it is called. `slideover_set_title()`
retitles a page in place (the open one included) because that number changes when the stream
renumbers its channels; `fill()` sets it on every fill, and a page added by
`slideover_add_page()` gets its final title from the `fill()` that follows.
+6 -3
View File
@@ -2889,7 +2889,7 @@ left the caller's `false` alone would read `dock_shown=0 default=1 ok=0`. The pr
## `[signalpage]`
```
[signalpage] name=sine open=1 pages=1 enable_visible=1 plot_buttons=1 plot_on_row0=1 plot_on_row0_disabled=1 removed=1 btn_row0=1 back_to_list=1 row_opens=1 pruned=1 other_page_kept=1 rows=1
[signalpage] name=sine open=1 pages=1 enable_visible=1 plot_buttons=1 plot_on_row0=1 plot_on_row0_disabled=1 removed=1 btn_row0=1 title='1. sine' title_ok=1 no_dup=1 row_opens=1 pruned=1 other_page_kept=1 rows=1
```
`UDPSCOPE_PROBE_SIGNAL_PAGES_MS` builds and drives the page of the first streamed signal. `open=1` (the slide-over shows `signal:<name>`), `pages=1`
@@ -2897,8 +2897,11 @@ left the caller's `false` alone would read `dock_shown=0 default=1 ok=0`. The pr
`rows=` (one "Plot on" button per figure row), `plot_on_row0=1` (`assign_signal_to_row()` returned true and the signal is plotted, from a state where the probe first took it off every plot), `plot_on_row0_disabled=1`
(after the page refilled with the signal on plot 1, that plot's "Plot on" button is insensitive, "already on this plot"; `0` is a button that would put the signal in a second slot of
the same plot), `removed=1`
(the page's "Remove from plots" took it off), `btn_row0=1` (the "Plot on 1" button, the user's path, put it back), `back_to_list=1` ("Back to signals"
target), `row_opens=1` (the probe switches the signal off so it is addable, activates its list row and the page opens; the signal is then switched back on), and `pruned=1`: a math channel's page was opened, its **Delete** button clicked, and
(the page's "Remove from plots" took it off), `btn_row0=1` (the "Plot on 1" button, the user's path, put it back),
`title=` the slide-over **header's** title while the page is open, `title_ok=1` when it is `<channel number>. <name>` -- the
same numbering the badge carries (`measure_channel_number`), so a signal page says which acquired channel it is -- and
`no_dup=1` when the page **body** carries neither a "Back to signals" button (the header's Close is the way out) nor a
second copy of the name (a `title-3` label); `0` is either duplicate back, `row_opens=1` (the probe switches the signal off so it is addable, activates its list row and the page opens; the signal is then switched back on), and `pruned=1`: a math channel's page was opened, its **Delete** button clicked, and
the slide-over closed and the page was dropped by `signal_pages_sync()`. `pruned=-1` means no math channel could be added; `0` is a stale page. `other_page_kept=1`: with the `trigger` page open, a signal page that is not on show was pruned (a math channel added, its page built, the
channel removed) and the slide-over was still on `trigger`, both by `slideover_page()` and by the stack's visible child; `0` is `signal_pages_sync()` parking the stack on the
signals list while the title and the badge marker still name `trigger`; `-1` when there is no `trigger` page or no math channel could be added.
+2 -1
View File
@@ -30,7 +30,8 @@
</item>
<item>
<p>Once a signal is acquired its badge appears. Click it to open that signal's page: its
details directly under its name — <gui>Unit</gui>, <gui>Gain</gui> and <gui>Offset</gui>,
title carries the signal's name and its channel number (<code>2. sine</code>), and its
details follow — <gui>Unit</gui>, <gui>Gain</gui> and <gui>Offset</gui>,
each with a pencil button that edits that one value, then the type, size, range, sample
rate and timing the stream declared — its colour and <gui>Plot on</gui> buttons for the
rows.</p>
+3
View File
@@ -2161,6 +2161,9 @@ gboolean dock_session_probe(gpointer user); // UDPSCOPE_PROBE_DOCK_SESSION_MS
/// per setup surface, each page in a scroller of its own.
void build_slideover(App& app); // once, after the plot_overlay exists
bool slideover_add_page(App& app, const std::string& id, const std::string& title, GtkWidget* content);
/// Retitles a page in place (`slideover_add_page`'s title, for a page whose subject changed: a signal
/// page carries its channel's number, and the stream can renumber). False when the id is unknown.
bool slideover_set_title(App& app, const std::string& id, const std::string& title);
bool slideover_has_page(const App& app, const std::string& id);
/// The inverse of `slideover_add_page`: false when the id is unknown or is the open page.
bool slideover_remove_page(App& app, const std::string& id);
+33 -17
View File
@@ -194,20 +194,14 @@ void fill(App& app, const std::string& name, GtkWidget* page) {
const bool enabled = app.model->signal_enabled(index);
const bool plotted = signal_plotted(app, sig.name);
// Back to the list: a page reached from a list row has no other way home.
GtkWidget* back = text_button("Back to signals", page);
gtk_widget_set_halign(back, GTK_ALIGN_START);
gtk_widget_add_css_class(back, "flat");
g_signal_connect(back, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
slideover_open(*static_cast<App*>(user), "signals");
}),
&app);
GtkWidget* title = gtk_label_new(sig.name.c_str());
gtk_widget_add_css_class(title, "title-3");
gtk_widget_set_halign(title, GTK_ALIGN_START);
gtk_label_set_ellipsize(GTK_LABEL(title), PANGO_ELLIPSIZE_END);
gtk_box_append(GTK_BOX(page), title);
// The page's name is the panel header's, not the body's: the slide-over already prints it above the
// content (with its **Close** button), so a second copy in the body is the same word twice. The
// header's title carries the channel's number -- the same one the badge and the measurements pane
// use -- so the panel says which acquired channel this is, not just what it is called.
const int channel_number = measure_channel_number(app, sig.name);
slideover_set_title(app, page_id(name),
channel_number > 0 ? std::to_string(channel_number) + ". " + sig.name
: sig.name);
if (sig.math) {
if (!sig.unit.empty()) {
GtkWidget* unit = gtk_label_new(sig.unit.c_str());
@@ -489,8 +483,29 @@ gboolean signal_page_probe(gpointer user) {
: find_descendant(page_it->second, [](GtkWidget* w) { return is_button_labelled(w, "Remove from plots"); });
if (off != nullptr) g_signal_emit_by_name(off, "clicked");
}
// The page's *header* names it -- numbered like the badge -- and the body carries no second copy of
// the name and no "Back to signals" button: the header's Close is the way out, the badge bar and the
// list the ways in. Read before switching to the list.
const int channel_number = measure_channel_number(a, name);
const std::string want_title =
channel_number > 0 ? std::to_string(channel_number) + ". " + name : name;
// Copied: the label's text is replaced when the probe switches pages below, and a `const char*` into
// it would be read after the free (it printed as an empty title).
const std::string shown_title =
a.slideover.title != nullptr ? gtk_label_get_text(GTK_LABEL(a.slideover.title)) : "";
const int title_ok = a.slideover.page == ("signal:" + name) && want_title == shown_title ? 1 : 0;
// ...and the body really has lost the two duplicates: no "Back to signals" button (the header's
// Close is the way out) and no second copy of the name (a `title-3` label; the header carries it).
GtkWidget* body = page_it == a.signal_pages.end() ? nullptr : page_it->second;
const int no_dup =
body != nullptr &&
find_descendant(body, [](GtkWidget* w) { return is_button_labelled(w, "Back to signals"); }) ==
nullptr &&
find_descendant(body, [](GtkWidget* w) { return gtk_widget_has_css_class(w, "title-3"); }) ==
nullptr
? 1
: 0;
slideover_open(a, "signals");
const int back_to_list = slideover_page(a) == "signals" ? 1 : 0;
// Activating the signal's row in the list opens its page (the path a click takes). The list proposes what
// can be added, so the signal has a row only while it is not acquired: switch it off for the leg and back
@@ -660,9 +675,10 @@ gboolean signal_page_probe(gpointer user) {
std::fprintf(stderr,
"[signalpage] name=%s open=%d pages=%d enable_visible=%d plot_buttons=%d plot_on_row0=%d "
"plot_on_row0_disabled=%d "
"removed=%d btn_row0=%d back_to_list=%d row_opens=%d pruned=%d other_page_kept=%d rows=%d\n",
"removed=%d btn_row0=%d title='%s' title_ok=%d no_dup=%d row_opens=%d pruned=%d "
"other_page_kept=%d rows=%d\n",
name.c_str(), open, pages, enable_visible, plot_buttons, plot_on_row0, plot_on_row0_disabled,
removed, btn_row0, back_to_list, row_opens, pruned,
removed, btn_row0, shown_title.c_str(), title_ok, no_dup, row_opens, pruned,
other_page_kept, a.rows->row_count());
return G_SOURCE_REMOVE;
}
+17
View File
@@ -118,6 +118,23 @@ bool slideover_add_page(App& app, const std::string& id, const std::string& titl
return true;
}
bool slideover_set_title(App& app, const std::string& id, const std::string& title) {
SlideOver& s = app.slideover;
if (s.stack == nullptr || title.empty()) return false;
GtkWidget* child = gtk_stack_get_child_by_name(GTK_STACK(s.stack), id.c_str());
if (child == nullptr) return false;
GtkStackPage* page = gtk_stack_get_page(GTK_STACK(s.stack), child);
gtk_stack_page_set_title(page, title.c_str());
if (is_source_page(id)) {
AdwToggle* t = adw_toggle_group_get_toggle_by_name(ADW_TOGGLE_GROUP(s.source_switch), id.c_str());
if (t != nullptr) adw_toggle_set_label(t, title.c_str());
}
// The header shows the *open* page's title, so a retitle of the page on show is visible at once --
// a signal page's channel number can change under it when the stream renumbers its channels.
if (s.page == id) gtk_label_set_text(GTK_LABEL(s.title), title.c_str());
return true;
}
bool slideover_has_page(const App& app, const std::string& id) {
return app.slideover.stack != nullptr &&
gtk_stack_get_child_by_name(GTK_STACK(app.slideover.stack), id.c_str()) != nullptr;