feat: switching a signal off takes its traces off every plot
The switch in the signal list means "do not acquire this", and until now a trace of it
stayed on its plot drawing nothing: a slot that holds one of the four a plot has, reads
`--`, and blocks the drop that would fill it with something live.
`on_signal_switch()` now clears every slot that holds that signal, by the **signal name**
the slot remembers -- never the channel index, which is what the enable change renumbers --
and says what it did: `'sine' is off: 1 trace left the plots`. The follow-ups (a relayout,
the panels' chips, the layout save) happen once rather than per slot.
Re-enabling deliberately does not put the traces back: the arrangement is the user's, and
guessing where they were is worse than leaving them off. Say the word if you want that
direction too.
[enable-all-probe] reads it end to end -- it enables every signal, assigns the channels
across the rows, then switches one off *through its own acquire tick*:
[enable-all-probe] switched 'sine' off: traces_left=0 (want 0) notice=''sine' is off: 1 trace left the plots'
which is also why the notice shares the status line's warning styling from the previous
commit: the trace leaving the plots is something the user should see happen.
This commit is contained in:
1 parent
aaf544be1e
commit
8b3240cacb
6 files changed
+102
-1
No files matched your search
@@ -173,3 +173,12 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
because `relayout_areas()` is the one place that builds a figure and re-points the area at it.
|
||||
`UDPSCOPE_PROBE_COLOR_MS` reads the result back out of `plot::Plot::trace()` and out of the
|
||||
chip's own markup, since asking the resolver twice would assert nothing.
|
||||
|
||||
- **Switching a signal off takes its traces off every plot.** The switch means "do not acquire this", and a
|
||||
trace left behind draws nothing: it holds one of the four slots a plot has, reads `--`, and blocks the drop
|
||||
that would fill it with something live. `on_signal_switch()` clears every slot by the **signal name** the
|
||||
slot remembers -- never the channel index, which is exactly what the enable change renumbers -- and says what
|
||||
it did in a notice, because a trace that vanishes silently is its own kind of confusing. The follow-ups (a
|
||||
relayout, the panels' chips, the layout save) are done once by the caller rather than per slot. Re-enabling
|
||||
does **not** put the traces back: the arrangement is the user's, and guessing where they were is worse than
|
||||
leaving them off.
|
||||
@@ -1243,3 +1243,15 @@ second is that it *said so* -- the message and the stock `.warning` class the st
|
||||
notice is fresh; the third is the composed line the user reads, which is why the label is only checked here
|
||||
and not in the second: the status line folds the notice into itself on its own cadence, so a beat after the
|
||||
refusal it may still hold the previous text.
|
||||
|
||||
## `[enable-all-probe]` and switching a signal off
|
||||
|
||||
```
|
||||
[enable-all-probe] 4 channel(s) enabled and assigned to plots
|
||||
[enable-all-probe] switched 'sine' off: traces_left=0 (want 0) notice=''sine' is off: 1 trace left the plots'
|
||||
```
|
||||
|
||||
`UDPSCOPE_ENABLE_ALL_MS` enables every signal and assigns the acquired channels across the rows, then
|
||||
switches one off **through its own acquire tick in the signal list** -- the tick is a box inside the row, which
|
||||
is the shape `signal_tick_visible()` walks -- and reads the plots back. `traces_left=0` is the rule: a disabled
|
||||
signal leaves every plot rather than staying as a slot that draws nothing, and the notice is what says so.
|
||||
+21
@@ -2534,6 +2534,27 @@ void remove_trace(App& app, int row, int slot) {
|
||||
app.rows->assign(row, slot, std::string(), std::string(), 0);
|
||||
}
|
||||
|
||||
/// Takes a signal off every plot, and says how many traces went.
|
||||
///
|
||||
/// Switching a signal off means "do not acquire this", so a trace of it left on a plot draws nothing: a slot
|
||||
/// that reads `--`, takes the place a live signal could have had, and blocks the drop that would fill it. The
|
||||
/// slot is what is cleared, and the caller does the follow-ups once (a relayout, the panels, the layout save)
|
||||
/// rather than per slot.
|
||||
int remove_signal_from_plots(App& app, const std::string& name) {
|
||||
if (name.empty()) return 0;
|
||||
int removed = 0;
|
||||
for (int row = 0; row < app.rows->row_count(); ++row) {
|
||||
for (int slot = 0; slot < RowSet::kSlotsPerRow; ++slot) {
|
||||
// The slot remembers the *signal* (and its element), never the channel index -- the channel list is
|
||||
// renumbered on every enable change, which is what this is reacting to.
|
||||
if (app.rows->row(row).signal_name[static_cast<std::size_t>(slot)] != name) continue;
|
||||
remove_trace(app, row, slot);
|
||||
++removed;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
void on_remove_clicked(GtkButton* button, gpointer user) {
|
||||
(void)user;
|
||||
auto* target = static_cast<RemoveTarget*>(g_object_get_data(G_OBJECT(button), "udpscope-remove"));
|
||||
|
||||
@@ -1250,6 +1250,7 @@ void on_y_auto_clicked(GtkButton* button, gpointer user);
|
||||
void on_fixed_range_apply(GtkButton*, gpointer user);
|
||||
void on_y_fixed_clicked(GtkButton* button, gpointer user);
|
||||
void remove_trace(App& app, int row, int slot);
|
||||
int remove_signal_from_plots(App& app, const std::string& name);
|
||||
void on_remove_clicked(GtkButton* button, gpointer user);
|
||||
GtkWidget* menu_icon_button(const char* icon, const char* label);
|
||||
void on_row_menu_show_remove(GtkButton*, gpointer user);
|
||||
|
||||
@@ -1864,6 +1864,46 @@ gboolean enable_all_probe(gpointer user) {
|
||||
std::fprintf(stderr, "[enable-all-probe] %zu channel(s) enabled and assigned to plots\n",
|
||||
a->model->channel_count());
|
||||
std::fflush(stderr);
|
||||
// Then switch one off through its own switch in the signal list: a disabled signal must leave the plots,
|
||||
// not sit there drawing nothing in a slot a live one could take.
|
||||
g_timeout_add(250, +[](gpointer u) -> gboolean {
|
||||
App* app = static_cast<App*>(u);
|
||||
if (app->model->channels().empty() || app->signal_list == nullptr) return G_SOURCE_REMOVE;
|
||||
const std::size_t signal = app->model->channels().front().signal;
|
||||
const std::string name = app->model->channels().front().name;
|
||||
GtkWidget* tick = nullptr;
|
||||
for (GtkWidget* row_widget = gtk_widget_get_first_child(app->signal_list);
|
||||
row_widget != nullptr && tick == nullptr;
|
||||
row_widget = gtk_widget_get_next_sibling(row_widget)) {
|
||||
// The tick sits a box inside the row, which is the shape `signal_tick_visible()` walks too.
|
||||
for (GtkWidget* box = gtk_widget_get_first_child(row_widget); box != nullptr && tick == nullptr;
|
||||
box = gtk_widget_get_next_sibling(box)) {
|
||||
for (GtkWidget* w = gtk_widget_get_first_child(box); w != nullptr;
|
||||
w = gtk_widget_get_next_sibling(w)) {
|
||||
if (!GTK_IS_CHECK_BUTTON(w)) continue;
|
||||
if (GPOINTER_TO_SIZE(g_object_get_data(G_OBJECT(w), "udpscope-signal")) != signal) continue;
|
||||
tick = w;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tick == nullptr) {
|
||||
std::fprintf(stderr, "[enable-all-probe] no acquire tick found for signal %zu\n", signal);
|
||||
std::fflush(stderr);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(tick), FALSE);
|
||||
int left = 0;
|
||||
for (int r = 0; r < app->rows->row_count(); ++r) {
|
||||
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) {
|
||||
if (app->rows->row(r).signal_name[static_cast<std::size_t>(s)] == name) ++left;
|
||||
}
|
||||
}
|
||||
std::fprintf(stderr, "[enable-all-probe] switched '%s' off: traces_left=%d (want 0) notice='%s'\n",
|
||||
name.c_str(), left, app->notice.c_str());
|
||||
std::fflush(stderr);
|
||||
return G_SOURCE_REMOVE;
|
||||
}, a);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
|
||||
+19
-1
@@ -17,7 +17,25 @@ void on_signal_switch(GObject* obj, GParamSpec*, gpointer user) {
|
||||
if (app->updating_panels) return;
|
||||
const std::size_t index = static_cast<std::size_t>(
|
||||
GPOINTER_TO_SIZE(g_object_get_data(obj, "udpscope-signal")));
|
||||
app->model->set_signal_enabled(index, gtk_check_button_get_active(GTK_CHECK_BUTTON(obj)));
|
||||
const bool on = gtk_check_button_get_active(GTK_CHECK_BUTTON(obj)) != FALSE;
|
||||
// The name first: the switch may be the last reference to a signal the model is about to drop.
|
||||
std::string name;
|
||||
{
|
||||
const std::vector<SignalInfo>& signals = app->model->signals();
|
||||
if (index < signals.size()) name = signals[index].name;
|
||||
}
|
||||
app->model->set_signal_enabled(index, on);
|
||||
if (!on) {
|
||||
// A trace of a signal nobody acquires draws nothing, and holds the slot a live one could take.
|
||||
const int gone = remove_signal_from_plots(*app, name);
|
||||
if (gone > 0) {
|
||||
app->relayout_pending = true;
|
||||
rebuild_signal_panels(*app); // the chips of the signals that are still plotted
|
||||
set_notice(*app, "'" + name + "' is off: " + std::to_string(gone) +
|
||||
(gone == 1 ? " trace left the plots" : " traces left the plots"),
|
||||
4);
|
||||
}
|
||||
}
|
||||
// The enable state is part of the session (a signal can be on only as a math channel's source).
|
||||
schedule_layout_save(*app);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user