From 9f3e3597aff7362c0183fb40042e0925976f8d9d Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Tue, 3 Nov 2026 18:04:58 +0100 Subject: [PATCH] signals: label plots by row and cell, drop onto a cell --- src/ui.cpp | 6 ++-- src/ui_probe.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++ src/ui_signal_page.cpp | 15 ++++++++-- src/ui_signals.cpp | 4 +-- 4 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/ui.cpp b/src/ui.cpp index e00aba8..90147c1 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -2908,11 +2908,11 @@ void on_remove_clicked(GtkButton* button, gpointer user) { ++removed; } what = "removed " + std::to_string(removed) + (removed == 1 ? " trace" : " traces") + - " from plot " + std::to_string(target->row + 1); + " from plot " + app.rows->plot_label(target->row); } else { const std::string name = r.name[static_cast(target->slot)]; remove_trace(app, target->row, target->slot); - what = "removed '" + name + "' from plot " + std::to_string(target->row + 1); + what = "removed '" + name + "' from plot " + app.rows->plot_label(target->row); } app.relayout_pending = true; @@ -2990,7 +2990,7 @@ void row_menu_fill(RowMenuState& st) { if (st.view == RowMenuView::Main) { char markup[64]; - std::snprintf(markup, sizeof(markup), "Plot %d", row + 1); + std::snprintf(markup, sizeof(markup), "Plot %s", app.rows->plot_label(row).c_str()); add_heading(markup); add_row("Y axis: auto scale", nullptr, G_CALLBACK(on_y_auto_clicked), &app); add_row("Y axis: fixed range...", nullptr, G_CALLBACK(on_y_fixed_clicked), &app); diff --git a/src/ui_probe.cpp b/src/ui_probe.cpp index c9dc881..81de1a7 100644 --- a/src/ui_probe.cpp +++ b/src/ui_probe.cpp @@ -4259,8 +4259,73 @@ gboolean split_probe_step(gpointer user) { cell_plus_sensitive, row_plus_sensitive, rs.row_count()); // Close editor. gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a->layout_toggle), false); + split_probe_next(*a); break; } + // --- Steps 16..17: signals page and drop onto a cell (task 6) --- + case 16: { + // Two split rows (plots 0,1 on row 1; plots 2,3 on row 2), all empty; the next step reads a frame + // that was laid out with them. + rs.set_cells_per_row({2, 2}); + split_probe_clear_all(*a); + split_probe_restructured(*a); + split_probe_next(*a); + break; + } + case 17: { + split_probe_report(*a, "two-split-rows"); + // Which plot the pointer resolves to at the centre of every cell, through the app's own wrapper + // (`row_at(app, x, y)`), in the area's logical coordinates: the cell rects are device pixels. + const plot::FigureLayout& fl = rs.figure().layout(); + const double scale = (a->area != nullptr && a->area->device_scale() > 0.0f) + ? static_cast(a->area->device_scale()) : 1.0; + std::string rowat; + int rowat_ok = fl.cells.size() == static_cast(rs.row_count()) ? 1 : 0; + for (std::size_t i = 0; i < fl.cells.size() && rowat_ok; ++i) { + const plot::Rect& c = fl.cells[i]; + const int got = row_at(*a, (static_cast(c.x) + c.w * 0.5) / scale, + (static_cast(c.y) + c.h * 0.5) / scale); + rowat += (i == 0 ? "" : ",") + rs.plot_label(static_cast(i)) + "=" + std::to_string(got); + if (got != static_cast(i)) rowat_ok = 0; + } + std::fprintf(stderr, "[split-probe] drop-rowat ok=%d rowat=[%s] (label=plot index at each cell's centre)\n", + rowat_ok, rowat.c_str()); + // The signals page's "Plot on" buttons for signal 1: label and tooltip of each, read off the widgets. + const std::string sig0 = a->model->signals()[1].name; + signal_page_ensure(*a, sig0); + signal_pages_sync(*a); + std::string buttons; + const auto page_it = a->signal_pages.find(sig0); + GtkWidget* box = page_it == a->signal_pages.end() ? nullptr + : find_named(page_it->second, "udpscope-plot-on"); + for (GtkWidget* b = box != nullptr ? gtk_widget_get_first_child(box) : nullptr; b != nullptr; + b = gtk_widget_get_next_sibling(b)) { + const char* tip = gtk_widget_get_tooltip_text(b); + buttons += std::string(buttons.empty() ? "" : "|") + gtk_button_get_label(GTK_BUTTON(b)) + ":" + + (tip != nullptr ? tip : "-"); + } + std::fprintf(stderr, "[split-probe] plot-on-buttons [%s]\n", buttons.c_str()); + // The "Plot on 1b" path: the button's own function, onto plot 1 (the right cell of row 1). + const bool assigned = assign_signal_to_row(*a, 1, 1); + std::fprintf(stderr, "[split-probe] assign-1b assigned=%d notice='%s'\n", assigned ? 1 : 0, + a->notice.c_str()); + // And the drop handler itself, at the centre of cell 3 ("2b"), with signal 2's index as the payload. + GValue payload = G_VALUE_INIT; + g_value_init(&payload, G_TYPE_STRING); + g_value_set_string(&payload, "2"); + const plot::Rect& c3 = fl.cells[3]; + const gboolean dropped = on_drop_channel(nullptr, &payload, (static_cast(c3.x) + c3.w * 0.5) / scale, + (static_cast(c3.y) + c3.h * 0.5) / scale, a); + g_value_unset(&payload); + std::fprintf(stderr, "[split-probe] drop-2b dropped=%d notice='%s'\n", dropped ? 1 : 0, + a->notice.c_str()); + split_probe_restructured(*a); + split_probe_next(*a); + break; + } + case 18: + split_probe_report(*a, "after-assign"); + break; default: break; } diff --git a/src/ui_signal_page.cpp b/src/ui_signal_page.cpp index 3523dbe..db38dd8 100644 --- a/src/ui_signal_page.cpp +++ b/src/ui_signal_page.cpp @@ -265,7 +265,9 @@ void fill(App& app, const std::string& name, GtkWidget* page) { GtkWidget* rows_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); gtk_widget_set_name(rows_box, "udpscope-plot-on"); // the probe counts its buttons for (int r = 0; r < app.rows->row_count(); ++r) { - GtkWidget* b = text_button(std::to_string(r + 1).c_str(), rows_box); + // The plot's label is its row's number, then a letter when the row is split ("2", "2a"); the tooltip + // of a split row's cell says the same thing in words. + GtkWidget* b = text_button(app.rows->plot_label(r).c_str(), rows_box); tag(b, index); g_object_set_data(G_OBJECT(b), "udpscope-row", GINT_TO_POINTER(r)); // A button for a plot the signal is already on would add it to a second slot there. @@ -274,7 +276,16 @@ void fill(App& app, const std::string& name, GtkWidget* page) { if (app.rows->row(r).signal_name[static_cast(slot)] == sig.name) on_this_row = true; } gtk_widget_set_sensitive(b, !on_this_row); - gtk_widget_set_tooltip_text(b, on_this_row ? "already on this plot" : "draw this signal on this plot"); + const int figure_row = app.rows->figure_row_of(r); + const int in_row = figure_row >= 0 ? app.rows->cells_in_row(figure_row) : 1; + if (in_row > 1) { + char where[64]; + std::snprintf(where, sizeof(where), "row %d, cell %d of %d", figure_row + 1, + app.rows->cell_of(r) + 1, in_row); + gtk_widget_set_tooltip_text(b, on_this_row ? "already on this plot" : where); + } else { + gtk_widget_set_tooltip_text(b, on_this_row ? "already on this plot" : "draw this signal on this plot"); + } g_signal_connect(b, "clicked", G_CALLBACK(+[](GtkButton* b, gpointer user) { outlive_the_rebuild(GTK_WIDGET(b)); assign_signal_to_row(*static_cast(user), tagged(b), diff --git a/src/ui_signals.cpp b/src/ui_signals.cpp index c2bf88c..d87a8b9 100644 --- a/src/ui_signals.cpp +++ b/src/ui_signals.cpp @@ -554,7 +554,7 @@ bool assign_signal_to_row(App& app, std::size_t signal, int row) { if (slot < 0) { // Say what to do about it. A refusal a reader cannot act on is only half an answer: the two ways // out are another plot or another row. - notice("plot " + std::to_string(row + 1) + + notice("plot " + app.rows->plot_label(row) + " holds four signals already: drop it on another plot, or add a row with the layout " "editor (L)"); return false; @@ -567,7 +567,7 @@ bool assign_signal_to_row(App& app, std::size_t signal, int row) { schedule_layout_save(app); app.relayout_pending = true; rebuild_signal_panels(app); // the row's colour chip now has a trace to name - notice("plotting '" + name + "' on plot " + std::to_string(row + 1)); + notice("plotting '" + name + "' on plot " + app.rows->plot_label(row)); return true; }