ui: one way to open the events page, and stale wording after the pane's removal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Sonnet 4.6 committed 2026-11-03 07:11:34 +01:00
1 parent 8c866300d8
commit 794df9b4d0
5 files changed
+39 -29

No files matched your search

+2 -3
View File
@@ -4187,7 +4187,7 @@ gboolean tick(gpointer user) {
next_channels = now_c + G_TIME_SPAN_SECOND;
app->last_channels_meas = now_c;
// The cache's own question, on the same cadence as the channel dump: a directory read per second,
// and the button's availability re-decided only when the answer changed.
// and the dock block's and the events page's availability re-decided only when the answer changed.
if (app->event_store != nullptr) {
const bool cached = !app->event_store->list().empty();
if (cached != app->events_cached) {
@@ -5708,8 +5708,7 @@ void report_events_availability(App& a, const char* what) {
}
/// Walks the whole event-cache loop the way a user does: trigger mode on (the real toggle, which is
/// what reveals the pane), a latch recorded automatically, the cache listed, the newest event
/// Walks the whole event-cache loop the way a user does: trigger mode on (the real toggle), a latch recorded automatically, the cache listed, the newest event
/// replayed through the loader `--open` uses, and *Live* taken back. What it reports is what each
/// step must be able to do -- a cache nothing can replay is a directory of files, and a replay with
/// no way back is a one-way door.
+6
View File
@@ -1945,6 +1945,12 @@ GdkTexture* scale_texture(GdkTexture* source, int width, int height);
EventThumbCache* event_thumb_cache(App& app);
GdkTexture* event_preview_texture(App& app, const std::string& path);
void refresh_events_pane(App& app);
/// Opens the `events` page. **Every route that opens that page must use this** and never `slideover_open()`
/// directly: the page's list is rebuilt only while the page is open (see `record_event()`), and the
/// slide-over has no on-open hook, so the list is refreshed here, once, before the page shows. The refresh
/// is skipped when the page is already open -- it is current then, and a rebuild would drop the
/// Compare/Accumulate selection and the scroll position. Returns what `slideover_open()` returns.
bool open_events_page(App& app);
/// Re-marks which row is the event currently on screen, **without rebuilding the list**.
///
/// The mark is the only thing about a row that a replay changes -- the picture and the numbers
+3 -6
View File
@@ -249,13 +249,10 @@ const char* dock_event_marker(const App& app, const EventRecord& r) {
return " ";
}
/// Opens the `events` page. The page's list is rebuilt only while the page is open (see `record_event()`),
/// so it is refreshed here, once, before the page shows: the slide-over has no on-open hook, and the list
/// is small. The button is not a child of either list, so the rebuild cannot free the widget it runs from.
/// Opens the `events` page through `open_events_page()` (which refreshes the list only if it is not
/// already open). The button is not a child of either list, so the rebuild cannot free the widget it runs from.
void on_manage_events(GtkButton*, gpointer user) {
App& app = *static_cast<App*>(user);
refresh_events_pane(app);
slideover_open(app, "events");
open_events_page(*static_cast<App*>(user));
}
/// One click on a row replays it, as the page list's activation does: `replay_event()` and then the in-place
+19 -12
View File
@@ -622,7 +622,7 @@ EventThumbCache* event_thumb_cache(App& app) {
// Created on first use and owned by the list. Nothing used to create it, and a missing cache is
// not a slow path here but a *blank* one: `event_preview_texture()` answers from the cache or not
// at all, so every row came up without a picture -- which is what the previews were reported for.
// Owned by the list because nothing else has the pane's lifetime: textures of a list that no
// Owned by the list because nothing else has the events page's lifetime: textures of a list that no
// longer exists are so much held memory.
cache = new EventThumbCache();
g_object_set_data_full(G_OBJECT(app.events_list), "udpscope-event-thumbs", cache,
@@ -707,7 +707,7 @@ GdkTexture* event_preview_texture(App& app, const std::string& path) {
///
/// Through GTK's own tooltip rather than a popover of ours, for lifetime: a tooltip is shown, placed,
/// dismissed and destroyed by GTK, and it takes no pointer grab -- a popover per row would be a
/// parented widget to unparent on every rebuild of the pane, and a grab to lose (see the GTK
/// parented widget to unparent on every rebuild of the page, and a grab to lose (see the GTK
/// invariants on both). The texture is decoded in the handler and dropped with the tooltip's picture,
/// so a hover costs nothing once it is over and the row cache keeps holding only the small one.
gboolean on_preview_query_tooltip(GtkWidget*, int, int, gboolean, GtkTooltip* tooltip, gpointer user) {
@@ -842,7 +842,7 @@ static void rebuild_events_pane_list(App& app, std::vector<EventRecord>& records
// to grow and the text keeps the rest of the width.
gtk_widget_set_size_request(frame, kEventThumbW, kEventThumbH);
// `halign` START, not the default FILL: a filling widget is given whatever the row has left over,
// which made the picture as wide as the pane and cropped by the height it was pinned to.
// which made the picture as wide as the page and cropped by the height it was pinned to.
gtk_widget_set_halign(frame, GTK_ALIGN_START);
gtk_widget_set_valign(frame, GTK_ALIGN_CENTER);
gtk_widget_add_css_class(frame, "udpscope-event-thumb");
@@ -870,7 +870,7 @@ static void rebuild_events_pane_list(App& app, std::vector<EventRecord>& records
++unreadable;
}
gtk_box_append(GTK_BOX(line), frame);
// The newest event is the first row: what the probe measures, and nothing the pane treats
// The newest event is the first row: what the probe measures, and nothing the page treats
// specially.
if (app.events_thumb == nullptr) app.events_thumb = shot;
@@ -926,10 +926,11 @@ static void rebuild_events_pane_list(App& app, std::vector<EventRecord>& records
}
}
/// Both views of the cache, rebuilt from the same list: the pane's rows (with their pictures) and the
/// dock's compact ones. The dock follows even when the pane's list does not exist.
/// Both views of the cache, rebuilt from the same list: the events page's rows (with their pictures) and the
/// dock block's compact ones. The dock follows even when the page's list does not exist. To *open* the page
/// use `open_events_page()`, which calls this only when the page is not already open.
void refresh_events_pane(App& app) {
// One read of the cache directory for both views (none without a store, which the pane's rebuild
// One read of the cache directory for both views (none without a store, which the page's rebuild
// and the dock's both treat as nothing to list).
std::vector<EventRecord> records;
if (app.event_store != nullptr) records = app.event_store->list();
@@ -937,9 +938,14 @@ void refresh_events_pane(App& app) {
if (app.event_store != nullptr) dock_rebuild_events(app, records);
}
bool open_events_page(App& app) {
if (slideover_page(app) != "events") refresh_events_pane(app);
return slideover_open(app, "events");
}
/// Clicking a row replays it: the same loader `--open` uses, on the event's file. The acquisition is
/// stopped by `replay_event()` -- a live producer would overwrite the samples within a frame -- so the
/// pane's own *Live* button is the way back.
/// page's own *Live* button is the way back.
void on_event_row_activated(GtkListBox*, GtkListBoxRow* row, gpointer user) {
App& app = *static_cast<App*>(user);
if (row == nullptr) return;
@@ -976,7 +982,7 @@ GtkWidget* build_events_pane(App& app) {
return app.events_box != nullptr ? app.events_box : gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
}
// What the pane *does*, which a file cannot hold: the two group actions read the selection, the list
// What the page *does*, which a file cannot hold: the two group actions read the selection, the list
// replays an event on a double click, and the header's button goes back to the live stream.
g_signal_connect(app.events_compare, "clicked", G_CALLBACK(on_group_clicked), &app);
g_signal_connect(app.events_accumulate, "clicked", G_CALLBACK(on_group_clicked), &app);
@@ -993,9 +999,10 @@ GtkWidget* build_events_pane(App& app) {
return app.events_box;
}
/// The recording switch, which lives here because it is a trigger option and not a pane option: it
/// decides whether latches are written at all, and the pane that lists them must not be the only
/// place to reach the control that stops them. Flipping it re-decides the pane's availability.
/// The recording switch, which lives here because it is a trigger option and not an events-page option: it
/// decides whether latches are written at all, and the page that lists them must not be the only
/// place to reach the control that stops them. Flipping it re-decides the availability of the dock's
/// Events block and of the page.
void on_events_record_toggled(GObject* o, GParamSpec*, gpointer user) {
App& app = *static_cast<App*>(user);
app.event_recording = gtk_switch_get_active(GTK_SWITCH(o));
+9 -8
View File
@@ -5674,7 +5674,7 @@ gboolean events_probe_step(gpointer user) {
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a->trigger_toggle), TRUE);
}
report_events_availability(*a, "after trigger on");
slideover_open(*a, "events");
open_events_page(*a);
// Read straight after the open, before anything else can call the availability rule.
report_events_availability(*a, "after events page opened");
// ... and again a few frames later, still with no replay in the way: this is the *open alone*
@@ -5747,11 +5747,15 @@ gboolean events_probe_step(gpointer user) {
r.stem.c_str(), r.duration_s, r.pre_s, r.post_s, r.edge, r.signals, r.samples,
r.rate_hz, static_cast<unsigned long long>(r.seq), thumb_bytes,
a->event_recording ? 1 : 0, slideover_page(*a) == "events" ? 1 : 0);
}
// The first latch must not have moved the plot: the list is a slide-over page over the plot's
// edge, and the dock is a fixed column, so nothing about an event changes the plot's width.
// edge, and the dock is a fixed column, so nothing about an event changes the plot's width. Both
// widths must be real (> 0): two unrealised -1s are equal and would read as stable.
{
const int plot_w_now = a->area_widget != nullptr ? gtk_widget_get_width(a->area_widget) : -1;
std::fprintf(stderr, " plot_w_stable=%d plot_w=%d/%d", plot_w_now == a->dbg_events_plot_w ? 1 : 0,
a->dbg_events_plot_w, plot_w_now);
std::fprintf(stderr, " plot_w_stable=%d plot_w=%d/%d",
(plot_w_now > 0 && plot_w_now == a->dbg_events_plot_w) ? 1 : 0, a->dbg_events_plot_w,
plot_w_now);
}
std::fprintf(stderr, "\n");
std::fflush(stderr);
@@ -5898,10 +5902,7 @@ gboolean events_group_probe_step(gpointer user) {
a->dbg_events_wait = 0;
// Open the page first: its list is only rebuilt while the page is *open* (or by the Manage button), so
// a selection made against a closed page selects rows that are not current.
if (slideover_page(*a) != "events") {
refresh_events_pane(*a);
slideover_open(*a, "events");
}
open_events_page(*a);
// The selection *is* the input, so select the two newest rows the way a user does and press the
// button: `on_group_clicked()` reads the list, not a parameter.
GtkListBox* list = GTK_LIST_BOX(a->events_list);