From 706d247b58048d865020d4f778ac32acf3015144 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Thu, 5 Nov 2026 18:08:50 +0100 Subject: [PATCH] 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 -- `. `, `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. --- README.md | 5 ++- docs/claude/invariants-gtk-and-chrome.md | 11 ++++++ docs/claude/probe-readings.md | 9 +++-- help/C/org.udpscope.app/signals.page | 3 +- src/ui_app.hpp | 3 ++ src/ui_signal_page.cpp | 50 ++++++++++++++++-------- src/ui_slideover.cpp | 17 ++++++++ 7 files changed, 76 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index e937d15..ba0bf65 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/claude/invariants-gtk-and-chrome.md b/docs/claude/invariants-gtk-and-chrome.md index f409c69..dd1fa98 100644 --- a/docs/claude/invariants-gtk-and-chrome.md +++ b/docs/claude/invariants-gtk-and-chrome.md @@ -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 -- `. `, + 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. diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md index 1647f17..e778b3c 100644 --- a/docs/claude/probe-readings.md +++ b/docs/claude/probe-readings.md @@ -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:`), `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 `. ` -- 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. diff --git a/help/C/org.udpscope.app/signals.page b/help/C/org.udpscope.app/signals.page index a642169..f688483 100644 --- a/help/C/org.udpscope.app/signals.page +++ b/help/C/org.udpscope.app/signals.page @@ -30,7 +30,8 @@

Once a signal is acquired its badge appears. Click it to open that signal's page: its - details directly under its name — Unit, Gain and Offset, + title carries the signal's name and its channel number (2. sine), and its + details follow — Unit, Gain and Offset, 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 Plot on buttons for the rows.

diff --git a/src/ui_app.hpp b/src/ui_app.hpp index b3a26a1..73c6ec0 100644 --- a/src/ui_app.hpp +++ b/src/ui_app.hpp @@ -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); diff --git a/src/ui_signal_page.cpp b/src/ui_signal_page.cpp index db38dd8..f1a78e3 100644 --- a/src/ui_signal_page.cpp +++ b/src/ui_signal_page.cpp @@ -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(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; } diff --git a/src/ui_slideover.cpp b/src/ui_slideover.cpp index ee3330a..1aee9d7 100644 --- a/src/ui_slideover.cpp +++ b/src/ui_slideover.cpp @@ -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;