diff --git a/README.md b/README.md index dc86b7b..6b94772 100644 --- a/README.md +++ b/README.md @@ -2403,7 +2403,9 @@ now — they are listed so they are not mistaken for anything else: accessible name. Both panes also have a **locked** mode, which is what a returning user gets: the trigger pane shows one line saying what it is set to, the signal list shows only the signals that are on, and neither offers a control that changes the configuration — each heading has an `[edit]` button - to switch to the full pane, and back. A **first run** (no session file for that source) comes up + to switch to the full pane, and back. Dragging a signal onto a plot still works while locked — filling + plots is what the list is for, not configuration — and a locked row can be dragged from anywhere on it, + the colour chip included. A **first run** (no session file for that source) comes up 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 feature is off is a pane whose controls you would otherwise have to leave to turn it back on. The diff --git a/docs/claude/invariants-session-and-export.md b/docs/claude/invariants-session-and-export.md index f1ba76a..9d8b4e3 100644 --- a/docs/claude/invariants-session-and-export.md +++ b/docs/claude/invariants-session-and-export.md @@ -18,7 +18,11 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear a control that changes the configuration. A *first run* — no session file for that source — never reaches `apply_layout()` and stays in configuration mode, which is what a first run is for. The mode is a mode and not a setting: it is deliberately not persisted, so a scope reopened tomorrow starts - where a returning user expects to start. + where a returning user expects to start. **Dragging a row onto a plot is not configuration and survives + the lock**: filling plots is what the list is *for*, so a locked row keeps its `GtkDragSource` — from + anywhere on it, the colour chip included, which in configuration mode has a click gesture that claims + the sequence precisely so a drag cannot start there. `drag_sources` on the `signals panel:` line is the + reading, and it must be `rows - 1`. - **The session is the layout.** `LayoutState` persists what makes a reopened scope look like the one that was closed: the window, the rows and their slot signals, the calibration, **the enabled signals**, **the chosen trace colours** (`[[color]]`, `name` + `rgb = "#RRGGBB"` — only the diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md index cbd7f43..3245bcd 100644 --- a/docs/claude/probe-readings.md +++ b/docs/claude/probe-readings.md @@ -1378,7 +1378,11 @@ the two lines together read what the file gave and what the code put in it. The signals panel's frame read from the window, and in the same line the *shape* of the one channel list: the heading and the hint the `.ui` gives it, the list the rows go in, how many rows that list holds (`rows`), how many of them are streamed signals (`signals`), how many math channels the app has (`math`), -and `divider`, which is 1 when `rows == signals + 1 + math` and 0 otherwise. `labels=1` is a `.ui` that lost +and `divider`, which is 1 when `rows == signals + 1 + math` and 0 otherwise. **`drag_sources` must be +`rows - 1`** — one per signal row, the `MATH` divider not being draggable. 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 +`+`, and this is the reading that says the drag did not go with them. (`0` is a list that has become a thing +you can read but not use.) `labels=1` is a `.ui` that lost one of the two labels — counted over the panel's subtree *above* the list, because the rows' own name and unit labels are the code's and the heading sits in a heading row beside the pane's `[edit]` button — `list=0` one that lost the list -- both of which otherwise show as a panel quietly missing a piece -- and **`divider=0` is the merged list that lost its `+`**, the one control that can create diff --git a/src/ui.cpp b/src/ui.cpp index c941889..1a9e4d3 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -5747,12 +5747,13 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit // ones, and the `MATH` divider plus the math channels account for the rest -- so // `rows == signals + 1 + math` is the invariant, and `divider=0` is a list that lost its `+`. std::printf("[udpscope] signals panel: labels=%d list=%d rows=%d signals=%zu math=%zu " - "divider=%d\n", + "divider=%d drag_sources=%d\n", labels, lists, app.dbg_signal_rows, app.dbg_signal_plain, app.math.size(), app.dbg_signal_rows == static_cast(app.dbg_signal_plain + 1 + app.math.size()) ? 1 - : 0); + : 0, + app.dbg_signal_drag_sources); } { // The events pane's frame, read from the window: the four widgets the `.ui` gives it, and the list diff --git a/src/ui_app.hpp b/src/ui_app.hpp index 5086adb..38ad53e 100644 --- a/src/ui_app.hpp +++ b/src/ui_app.hpp @@ -820,6 +820,8 @@ struct App { /// of them are streamed signals. The `MATH` divider and the math channels account for the rest, and /// `rows == signals + 1 + math` is the invariant the dump checks -- a list that lost its `+`. int dbg_signal_rows = 0; + /// How many rows of the list carry a `GtkDragSource`: `rows - 1`, the divider not being draggable. + int dbg_signal_drag_sources = -1; std::size_t dbg_signal_plain = 0; int dbg_side_column_after_hide = 1; std::string dbg_mode_triggered; diff --git a/src/ui_probe.cpp b/src/ui_probe.cpp index f4a7079..aa0f852 100644 --- a/src/ui_probe.cpp +++ b/src/ui_probe.cpp @@ -85,6 +85,24 @@ gboolean exit_probe_step3(gpointer user) { 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 + // 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, + // which is not draggable: the count must be `rows - 1`. + if (a->signal_list != nullptr) { + int sources = 0; + for (GtkWidget* r = gtk_widget_get_first_child(a->signal_list); r != nullptr; + r = gtk_widget_get_next_sibling(r)) { + GListModel* controllers = gtk_widget_observe_controllers(r); + const guint n = controllers != nullptr ? g_list_model_get_n_items(controllers) : 0; + for (guint i = 0; i < n; ++i) { + GObject* c = G_OBJECT(g_list_model_get_item(controllers, i)); + if (GTK_IS_DRAG_SOURCE(c)) ++sources; + g_object_unref(c); + } + } + a->dbg_signal_drag_sources = sources; + } // The panes' own switches, and *both* directions of the sync: the header bar's trigger button is // flipped here and the pane's switch has to follow, then the cursors' switch is driven from the // pane and the button is what has to follow. Both are read back after the fact, so the two pairs in