ui: a refused drop says so, and looks like a message rather than the status

The refusal was never silent -- `on_drop_channel()` has always set a notice -- but the
status bar renders `notice + "   |   " + glance` in one label, so "plot 1 already holds
four signals" arrived looking like part of the routine text a reader skims, which is the
"no clear user feedback" report. The probe's own reading showed it plainly: the label held
`plotting 'sine' on plot 1   |   live · 4.44 kS/s · 2 s window`.

Three changes:

- While a notice is fresh the status label carries the stock `.warning` class, so a message
  looks like a message and the class goes when the notice does. Every notice benefits, not
  just this one.
- The refusal says what to *do*: "plot 1 holds four signals already: drop it on another
  plot, or add a row with the layout editor (L)".
- The drop's inline notice now goes through `set_notice()`, the one setter, instead of
  writing `app.notice` and its deadline by hand.

[drop-probe] reads the whole path, with the row filled first so the drop under test is the
refusal:

    full row refused the drop: 0 (want 0)
    refused feedback: notice='plot 1 holds four signals already: ...' warning=1
    the status line, composed: warning=1 status='plot 1 holds four signals already: ... | live · 9.97 kS/s · 2 s window'

The label is read in the third line and not the second on purpose: the status line composes
the notice into itself on its own cadence, so a beat after the refusal it may still hold the
previous text -- which the first attempt at this reading mistook for the fix not working.

The row limit is not silent in the same way: the layout editor's `+` is insensitive at
`kMaxRows`, which is a control that says "no" by being unusable rather than a message.
This commit is contained in:
Martino Ferrari committed 2026-10-12 20:11:36 +02:00
1 parent d34b97158e
commit aaf544be1e
5 files changed
+78 -3

No files matched your search

+43
View File
@@ -3367,6 +3367,49 @@ gboolean drop_probe(gpointer user) {
ok ? 1 : 0, a->rows->row(0).name[0].c_str(), a->rows->row(0).name[1].c_str(),
a->rows->row(0).name[2].c_str(), a->rows->row(0).name[3].c_str());
}
// Fill the row first, so that the drop below is the *refusal*: a drop can otherwise land in a slot
// whose signal is named but not acquired, which is a success rather than the case under test.
{
const std::vector<ChannelInfo>& channels = a->model->channels();
for (int slot = 0; slot < RowSet::kSlotsPerRow && !channels.empty(); ++slot) {
if (a->rows->slot_bound(0, slot)) continue;
const ChannelInfo& ch = channels[static_cast<std::size_t>(slot) % channels.size()];
a->rows->assign(0, slot, ch.name, ch.name, 0);
}
}
// The refusal's *feedback*, a tick later: the status line composes the notice into itself on the
// next tick, so this is where both the text and the class that makes it visible can be read.
g_timeout_add(500, +[](gpointer u) -> gboolean {
App* app = static_cast<App*>(u);
// And the drop that the full row has to refuse, fired here so its feedback is what the reading below
// sees rather than the last success's.
GValue refused_value = G_VALUE_INIT;
g_value_init(&refused_value, G_TYPE_STRING);
g_value_set_string(&refused_value, "1");
const gboolean refused = on_drop_channel(nullptr, &refused_value, 40.0, 20.0, app);
g_value_unset(&refused_value);
std::fprintf(stderr, "[drop-probe] full row refused the drop: %d (want 0)\n", refused ? 1 : 0);
// The message and its class, which are set the moment the drop is refused. The *label* is read in
// the later line instead: the status line composes the notice into itself on its own cadence, so
// here it may still hold the previous one.
std::fprintf(stderr, "[drop-probe] refused feedback: notice='%s' warning=%d\n",
app->notice.c_str(),
gtk_widget_has_css_class(app->status_label, "warning") ? 1 : 0);
std::fflush(stderr);
// Again a second and a half later: if the label is still the previous notice then the status line
// never recomposes it, which would be the whole of the "no clear feedback" report.
g_timeout_add(1500, +[](gpointer u) -> gboolean {
App* app = static_cast<App*>(u);
std::fprintf(stderr,
"[drop-probe] the status line, composed: warning=%d status='%s'\n",
gtk_widget_has_css_class(app->status_label, "warning") ? 1 : 0,
gtk_label_get_text(GTK_LABEL(app->status_label)));
std::fflush(stderr);
return G_SOURCE_REMOVE;
}, app);
return G_SOURCE_REMOVE;
}, a);
std::fflush(stderr);
return G_SOURCE_REMOVE;
}