scope windows take drops, and the signal page lists them
The second windows were read-only by accident: nothing accepted a drop on them, and the
signal page's *Plot on* buttons knew only the main window's rows.
- **Drop target per scope window**: the same string payload a badge drags (the signal index),
handled by `on_scope_drop_channel()` -- the target carries its own window, the row is that
window's cell under the pointer, and `assign_signal_to_scope()` lands the signal in the
window's own row set (enabling an unacquired signal first, exactly as the main drop does).
The rebuild and the area's re-point are the window's next frame in the tick: a drop arrives
from inside the very widget it rearranges, and a synchronous rebuild would reset the drag
controller under it.
- **Plot on, per window**: the signal page carries one named group per scope window ("In
\"plot 2\""), one button per plot of that window, sensitive on that window's own rows. A
window closing refreshes the pages in its destroy handler, before the deferred delete could
dangle a button's scope pointer.
- Probe: `[scope-probe]` drops through the wired handler (`drop=1 slot1='square'`) and counts
the page's groups (`page_groups=2`); the section documents that it must not run with
`UDPSCOPE_ENABLE_ALL_MS` -- the enable-all probe's second leg clears the front plot, which
is the slot this probe copies. README, help and the layout invariant follow.
This commit is contained in:
1 parent
706d247b58
commit
51b4fad386
9 files changed
+231
-21
No files matched your search
+86
-17
@@ -505,24 +505,26 @@ void rebuild_signal_list(App& app, const std::vector<SignalInfo>& signals) {
|
||||
app.updating_panels = false;
|
||||
}
|
||||
|
||||
bool assign_signal_to_row(App& app, std::size_t signal, int row) {
|
||||
if (signal >= app.model->signals().size()) return false;
|
||||
|
||||
const auto notice = [&](const std::string& what) {
|
||||
set_notice(app, what, 4);
|
||||
std::fprintf(stderr, "[udpscope] drop: %s\n", what.c_str());
|
||||
std::fflush(stderr);
|
||||
};
|
||||
/// The drop's one-line report, wherever the drop landed: the notice bar and the log, because a
|
||||
/// refusal a reader cannot act on is only half an answer.
|
||||
void drop_notice(App& app, const std::string& what) {
|
||||
set_notice(app, what, 4);
|
||||
std::fprintf(stderr, "[udpscope] drop: %s\n", what.c_str());
|
||||
std::fflush(stderr);
|
||||
}
|
||||
|
||||
/// Resolves a dropped signal to an acquired channel, enabling the signal when it has to be: the
|
||||
/// channel index and its display name, or -1 when the signal cannot be acquired (a multi-element
|
||||
/// signal has no channel to bind). Shared by the main window's drop and the scope windows'.
|
||||
int resolve_drop_channel(App& app, std::size_t signal, std::string* name) {
|
||||
// The dropped signal's channels are the ones the model built for it; take the
|
||||
// first, or the first element if the signal is a vector.
|
||||
const std::vector<ChannelInfo>& channels = app.model->channels();
|
||||
int channel = -1;
|
||||
std::string name;
|
||||
for (std::size_t i = 0; i < channels.size(); ++i) {
|
||||
if (channels[i].signal == signal) {
|
||||
channel = static_cast<int>(i);
|
||||
name = channels[i].name;
|
||||
*name = channels[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -533,14 +535,22 @@ bool assign_signal_to_row(App& app, std::size_t signal, int row) {
|
||||
for (std::size_t i = 0; i < again.size(); ++i) {
|
||||
if (again[i].signal == signal) {
|
||||
channel = static_cast<int>(i);
|
||||
name = again[i].name;
|
||||
*name = again[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
bool assign_signal_to_row(App& app, std::size_t signal, int row) {
|
||||
if (signal >= app.model->signals().size()) return false;
|
||||
|
||||
std::string name;
|
||||
const int channel = resolve_drop_channel(app, signal, &name);
|
||||
if (channel < 0) {
|
||||
notice("cannot acquire '" + app.model->signals()[signal].name +
|
||||
"' (multi-element signals are not acquired)");
|
||||
drop_notice(app, "cannot acquire '" + app.model->signals()[signal].name +
|
||||
"' (multi-element signals are not acquired)");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -554,9 +564,9 @@ 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 " + app.rows->plot_label(row) +
|
||||
" holds four signals already: drop it on another plot, or add a row with the layout "
|
||||
"editor (L)");
|
||||
drop_notice(app, "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;
|
||||
}
|
||||
// The slot remembers the *signal* (and element), never the channel index: the
|
||||
@@ -567,10 +577,69 @@ 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 " + app.rows->plot_label(row));
|
||||
drop_notice(app, "plotting '" + name + "' on plot " + app.rows->plot_label(row));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool assign_signal_to_scope(App& app, App::ScopeWindow& scope, std::size_t signal, int row) {
|
||||
if (scope.rows == nullptr || signal >= app.model->signals().size()) return false;
|
||||
if (row < 0 || row >= scope.rows->row_count()) return false;
|
||||
std::string name;
|
||||
const int channel = resolve_drop_channel(app, signal, &name);
|
||||
if (channel < 0) {
|
||||
drop_notice(app, "cannot acquire '" + app.model->signals()[signal].name +
|
||||
"' (multi-element signals are not acquired)");
|
||||
return false;
|
||||
}
|
||||
int slot = -1;
|
||||
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) {
|
||||
if (!scope.rows->slot_bound(row, s)) {
|
||||
slot = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot < 0) {
|
||||
drop_notice(app, scope.name + " plot " + scope.rows->plot_label(row) + " holds four signals "
|
||||
"already: drop it on another plot of that window");
|
||||
return false;
|
||||
}
|
||||
// The slot remembers the *signal* (and element), never the channel index -- the same rule the
|
||||
// main window's rows follow, for the same renumbering reason.
|
||||
const std::vector<SignalInfo>& signals = app.model->signals();
|
||||
const SignalInfo& sig = signals[signal];
|
||||
scope.rows->assign(row, slot, sig.name, name, signal_color_index(signals, signal));
|
||||
// The rebuild and the area's re-point are the window's own frame tick's (a drop arrives from
|
||||
// inside this widget, and a synchronous rebuild would reset the drag controller under it). The
|
||||
// signal page is refreshed now: its *Plot on* buttons read the scope's rows for "already on".
|
||||
signal_pages_sync(app);
|
||||
drop_notice(app, "plotting '" + name + "' on " + scope.name + " plot " +
|
||||
scope.rows->plot_label(row));
|
||||
return true;
|
||||
}
|
||||
|
||||
gboolean on_scope_drop_channel(GtkDropTarget* target, const GValue* value, double x, double y,
|
||||
gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
auto* scope =
|
||||
static_cast<App::ScopeWindow*>(g_object_get_data(G_OBJECT(target), "udpscope-scope"));
|
||||
if (scope == nullptr || scope->area == nullptr || scope->rows == nullptr) return FALSE;
|
||||
if (!G_VALUE_HOLDS_STRING(value)) return FALSE;
|
||||
const char* text = g_value_get_string(value);
|
||||
if (text == nullptr || *text == '\0') return FALSE;
|
||||
char* end = nullptr;
|
||||
const unsigned long long parsed = std::strtoull(text, &end, 10);
|
||||
if (end == nullptr || *end != '\0') return FALSE;
|
||||
const std::size_t signal = static_cast<std::size_t>(parsed);
|
||||
// Where the drop landed decides the row *of this window*: one area covers its figure's cells.
|
||||
const int row = std::clamp(static_cast<int>(scope->area->row_at(x, y)), 0,
|
||||
scope->rows->row_count() - 1);
|
||||
if (!assign_signal_to_scope(*app, *scope, signal, row)) return FALSE;
|
||||
if (plot::input::Controller* c = scope->area->row_controller(static_cast<plot::u32>(row))) {
|
||||
c->on_key(plot::input::KeyEvent{plot::input::kKeyEscape, 0, true});
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean on_drop_channel(GtkDropTarget*, const GValue* value, double x, double y,
|
||||
gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
|
||||
Reference in new issue
Block a user