ui: the signal badge is the drag handle (to a plot row, or to reorder); the list rows stop dragging
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
f27925da59
commit
ca10dfb323
9 files changed
+230
-109
No files matched your search
+2
-21
@@ -4634,25 +4634,6 @@ void on_pane_edit_clicked(GtkButton* button, gpointer user) {
|
||||
apply_pane_lock(*app);
|
||||
}
|
||||
|
||||
/// Hides the sidebar for the length of a drag, when it is *overlaid*, and restores it after.
|
||||
///
|
||||
/// With the split view collapsed the sidebar covers the plot -- that is what collapsed means -- so a drag
|
||||
/// that starts in the signal list has nowhere to land: the pointer is over the sidebar and never over a
|
||||
/// drop target, and the drag is impossible rather than merely awkward. Side by side nothing is done: the
|
||||
/// plot is already visible, and hiding the list the drag started in would be startling.
|
||||
void row_drag_sidebar(App& app, bool dragging) {
|
||||
if (app.side_view == nullptr) return;
|
||||
AdwOverlaySplitView* split = ADW_OVERLAY_SPLIT_VIEW(app.side_view);
|
||||
if (dragging) {
|
||||
if (!adw_overlay_split_view_get_collapsed(split)) return;
|
||||
app.sidebar_hidden_for_drag = adw_overlay_split_view_get_show_sidebar(split) != FALSE;
|
||||
if (app.sidebar_hidden_for_drag) adw_overlay_split_view_set_show_sidebar(split, FALSE);
|
||||
} else if (app.sidebar_hidden_for_drag) {
|
||||
app.sidebar_hidden_for_drag = false;
|
||||
adw_overlay_split_view_set_show_sidebar(split, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
void run_app_action(App& app, AppAction action) {
|
||||
const auto toggle = [](GtkWidget* button) {
|
||||
if (button == nullptr) return;
|
||||
@@ -6223,8 +6204,8 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
app.window_limit_s);
|
||||
{
|
||||
GtkIconTheme* theme = gtk_icon_theme_get_for_display(gdk_display_get_default());
|
||||
std::printf("[udpscope] pane drag: sidebar hidden for the drag=%d restored after it=%d\n",
|
||||
app.dbg_drag_hid_sidebar, app.dbg_drag_restored_sidebar);
|
||||
std::printf("[udpscope] pane drag: slide-over closed for the drag=%d\n",
|
||||
app.dbg_drag_closed_slideover);
|
||||
std::printf("[udpscope] pane edits: locked=%s(%s) config=%s(%s)\n",
|
||||
app.dbg_pane_edit_icon.c_str(),
|
||||
gtk_icon_theme_has_icon(theme, app.dbg_pane_edit_icon.c_str()) ? "ok" : "MISSING",
|
||||
|
||||
+4
-11
@@ -531,9 +531,6 @@ struct App {
|
||||
/// True while `sync_panel_switches()` is writing the switches: setting a `GtkSwitch` emits its own
|
||||
/// `notify::active`, and without this the program's own write would come back as a click.
|
||||
bool panel_switch_sync = false;
|
||||
/// True while a drag has the overlaid sidebar hidden: what it was showing before, so `drag-end` puts it
|
||||
/// back rather than guessing.
|
||||
bool sidebar_hidden_for_drag = false;
|
||||
/// The channel a session's trigger names, until `rebuild_trigger_signals()` can resolve it: the
|
||||
/// file stores a *name* and the trigger needs an index into the acquired channels, which do not
|
||||
/// exist until the stream has been described. Empty means "nothing pending".
|
||||
@@ -1166,11 +1163,11 @@ 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.
|
||||
/// How many rows of the list carry a `GtkDragSource`: 0 -- the signal badges are the drag handles now.
|
||||
int dbg_signal_drag_sources = -1;
|
||||
/// The drag-with-an-overlaid-sidebar pair: the sidebar hid for the drag, and came back after it.
|
||||
int dbg_drag_hid_sidebar = -1;
|
||||
int dbg_drag_restored_sidebar = -1;
|
||||
/// A drag begun through the first signal badge's source closed the open slide-over (1), or did not (0);
|
||||
/// -1 when there is no badge to drag.
|
||||
int dbg_drag_closed_slideover = -1;
|
||||
std::size_t dbg_signal_plain = 0;
|
||||
int dbg_side_column_after_hide = 1;
|
||||
std::string dbg_mode_triggered;
|
||||
@@ -1852,10 +1849,6 @@ const char* trigger_mode_text(TriggerMode m);
|
||||
TriggerMode trigger_mode_from_text(const std::string& s);
|
||||
/// The panel's controls from the model, and the session's trigger into both.
|
||||
void write_trigger_widgets(App& app);
|
||||
/// Hides the sidebar for the length of a drag when it is *overlaid*, and puts it back after. With the
|
||||
/// split view collapsed the sidebar covers the plot, so a drag that starts in the signal list has nowhere
|
||||
/// to land: the pointer is over the sidebar, not over a drop target.
|
||||
void row_drag_sidebar(App& app, bool dragging);
|
||||
/// The compact line the trigger pane shows while it is locked, and the lock itself: the panes'
|
||||
/// bodies, their [edit] buttons and the signal list's rows all follow from `trigger_locked` and
|
||||
/// `signals_locked`.
|
||||
|
||||
+161
-1
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -78,6 +79,40 @@ void make_badge(App& app, Badge& b, GtkWidget* parent, const char* name, const s
|
||||
gtk_box_append(GTK_BOX(parent), b.button);
|
||||
}
|
||||
|
||||
/// The reorder: widgets are moved, never rebuilt, so neither the dragged badge nor this target (whose "drop"
|
||||
/// emission is running) is destroyed under the drag. The payload is the signal index as a decimal string (the
|
||||
/// plot area's drop target reads the same one); anything else a foreign drag might carry is refused.
|
||||
gboolean on_badge_drop(GtkDropTarget*, const GValue* value, double, double, gpointer user) {
|
||||
Badge* target = static_cast<Badge*>(user);
|
||||
App* app = static_cast<App*>(g_object_get_data(G_OBJECT(target->button), "udpscope-app"));
|
||||
if (app == nullptr || value == nullptr || !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);
|
||||
Badge* moved = nullptr;
|
||||
for (auto& b : app->badges.signal_badges) {
|
||||
if (b->signal == signal) moved = b.get();
|
||||
}
|
||||
if (moved == nullptr) return FALSE;
|
||||
if (moved == target) return TRUE;
|
||||
GtkWidget* prev = gtk_widget_get_prev_sibling(target->button);
|
||||
if (prev != moved->button) { // already directly in front: nothing to move
|
||||
gtk_box_reorder_child_after(GTK_BOX(app->badges.signals_row), moved->button, prev);
|
||||
}
|
||||
// The order, as the widgets now stand.
|
||||
app->badge_order.clear();
|
||||
for (GtkWidget* c = gtk_widget_get_first_child(app->badges.signals_row); c != nullptr;
|
||||
c = gtk_widget_get_next_sibling(c)) {
|
||||
for (auto& b : app->badges.signal_badges) {
|
||||
if (b->button == c) app->badge_order.push_back(b->signal_name);
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/// A signal's badge: its colour dot (grey when it has no trace) and its name. Words and a dot, no icon.
|
||||
void make_signal_badge(App& app, Badge& b, const std::vector<SignalInfo>& signals, std::size_t index) {
|
||||
b.signal = index;
|
||||
@@ -98,6 +133,27 @@ void make_signal_badge(App& app, Badge& b, const std::vector<SignalInfo>& signal
|
||||
gtk_widget_set_tooltip_text(b.button, b.signal_name.c_str());
|
||||
g_object_set_data(G_OBJECT(b.button), "udpscope-app", &app);
|
||||
g_signal_connect(b.button, "clicked", G_CALLBACK(on_badge_clicked), &b);
|
||||
|
||||
// A drag handle: the same payload the list's rows used to carry, so the plot area's drop target takes it
|
||||
// unchanged. The slide-over steps aside for the drag, since it covers the plot's right edge. (A click
|
||||
// without movement never reaches the drag threshold, so it still opens the page; past the threshold the
|
||||
// source claims the sequence and the button's click is cancelled.)
|
||||
DragContext* ctx = new DragContext{&app, index};
|
||||
GtkDragSource* source = gtk_drag_source_new();
|
||||
gtk_drag_source_set_actions(source, GDK_ACTION_COPY);
|
||||
g_signal_connect_data(source, "prepare", G_CALLBACK(on_drag_prepare), ctx,
|
||||
[](gpointer data, GClosure*) { delete static_cast<DragContext*>(data); },
|
||||
G_CONNECT_DEFAULT);
|
||||
g_signal_connect(source, "drag-begin", G_CALLBACK(+[](GtkDragSource*, GdkDrag*, gpointer user) {
|
||||
slideover_close(*static_cast<App*>(user));
|
||||
}),
|
||||
&app);
|
||||
gtk_widget_add_controller(b.button, GTK_EVENT_CONTROLLER(source));
|
||||
|
||||
// Dropping another signal's badge on this one moves it in front of this one.
|
||||
GtkDropTarget* drop = gtk_drop_target_new(G_TYPE_STRING, GDK_ACTION_COPY);
|
||||
g_signal_connect(drop, "drop", G_CALLBACK(on_badge_drop), &b);
|
||||
gtk_widget_add_controller(b.button, GTK_EVENT_CONTROLLER(drop));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -197,7 +253,9 @@ void sync_badge_bar(App& app) {
|
||||
}
|
||||
if (bb.overflow.button != nullptr) {
|
||||
gtk_widget_set_sensitive(bb.overflow.button, !app.model->signals().empty());
|
||||
set_open(bb.overflow.button, slideover_page(app) == bb.overflow.page);
|
||||
// The overflow stands for every signal's badge too: a signal page opened from a list row marks it.
|
||||
set_open(bb.overflow.button,
|
||||
slideover_page(app) == bb.overflow.page || slideover_page(app).rfind("signal:", 0) == 0);
|
||||
}
|
||||
for (const std::unique_ptr<Badge>& sb : bb.signal_badges) {
|
||||
set_open(sb->button, slideover_page(app) == sb->page);
|
||||
@@ -360,6 +418,108 @@ gboolean signal_badges_probe(gpointer user) {
|
||||
"bar_h=%d bar_h_fixed=%d bar_min_w=%d opens=%d plus_opens=%d\n",
|
||||
enabled, badges, overflowed ? 1 : 0, overflow_text.c_str(), plus_visible, dot_plotted, before.first,
|
||||
bar_h_fixed, bar_min_w, opens, plus_opens);
|
||||
|
||||
// The drag handles. Controllers are enumerated through the widget's own model.
|
||||
const auto count_controllers = [](GtkWidget* w, bool (*is)(GObject*)) {
|
||||
int n = 0;
|
||||
GListModel* controllers = gtk_widget_observe_controllers(w);
|
||||
const guint items = controllers != nullptr ? g_list_model_get_n_items(controllers) : 0;
|
||||
for (guint i = 0; i < items; ++i) {
|
||||
GObject* c = G_OBJECT(g_list_model_get_item(controllers, i));
|
||||
if (is(c)) ++n;
|
||||
g_object_unref(c);
|
||||
}
|
||||
return n;
|
||||
};
|
||||
const auto is_source = [](GObject* c) { return GTK_IS_DRAG_SOURCE(c) != FALSE; };
|
||||
const auto is_target = [](GObject* c) { return GTK_IS_DROP_TARGET(c) != FALSE; };
|
||||
int drag_sources = 0;
|
||||
int drop_targets = 0;
|
||||
for (const std::unique_ptr<Badge>& sb : bb.signal_badges) {
|
||||
drag_sources += count_controllers(sb->button, is_source);
|
||||
drop_targets += count_controllers(sb->button, is_target);
|
||||
}
|
||||
// The payload: what a drag from the first badge carries, read back from the provider `prepare` returns.
|
||||
int prepare_ok = -1;
|
||||
if (!bb.signal_badges.empty()) {
|
||||
prepare_ok = 0;
|
||||
GListModel* controllers = gtk_widget_observe_controllers(bb.signal_badges.front()->button);
|
||||
const guint items = controllers != nullptr ? g_list_model_get_n_items(controllers) : 0;
|
||||
for (guint i = 0; i < items; ++i) {
|
||||
GObject* c = G_OBJECT(g_list_model_get_item(controllers, i));
|
||||
if (GTK_IS_DRAG_SOURCE(c)) {
|
||||
GdkContentProvider* provider = nullptr;
|
||||
g_signal_emit_by_name(c, "prepare", 0.0, 0.0, &provider);
|
||||
if (provider != nullptr) {
|
||||
GValue v = G_VALUE_INIT;
|
||||
g_value_init(&v, G_TYPE_STRING);
|
||||
if (gdk_content_provider_get_value(provider, &v, nullptr) && g_value_get_string(&v) != nullptr &&
|
||||
std::to_string(bb.signal_badges.front()->signal) == g_value_get_string(&v)) {
|
||||
prepare_ok = 1;
|
||||
}
|
||||
g_value_unset(&v);
|
||||
g_object_unref(provider);
|
||||
}
|
||||
}
|
||||
g_object_unref(c);
|
||||
}
|
||||
}
|
||||
|
||||
// The reorder, by the drop handler the targets run: the last badge dropped on the first goes in front of it,
|
||||
// and the remembered order follows the widgets. Then the order is put back as it was.
|
||||
const auto widget_names = [&] {
|
||||
std::string names;
|
||||
for (GtkWidget* c = gtk_widget_get_first_child(bb.signals_row); c != nullptr; c = gtk_widget_get_next_sibling(c)) {
|
||||
for (const std::unique_ptr<Badge>& sb : bb.signal_badges) {
|
||||
if (sb->button == c) names += (names.empty() ? "" : ",") + sb->signal_name;
|
||||
}
|
||||
}
|
||||
return names;
|
||||
};
|
||||
std::string reorder_before = "-";
|
||||
std::string reorder_after = "-";
|
||||
int reorder_ok = -1;
|
||||
if (bb.signal_badges.size() >= 2) {
|
||||
const std::vector<std::string> saved_order = a->badge_order;
|
||||
reorder_before = widget_names();
|
||||
Badge& first = *bb.signal_badges.front();
|
||||
Badge& last = *bb.signal_badges.back();
|
||||
GValue v = G_VALUE_INIT;
|
||||
g_value_init(&v, G_TYPE_STRING);
|
||||
g_value_set_string(&v, std::to_string(last.signal).c_str());
|
||||
const gboolean handled = on_badge_drop(nullptr, &v, 0.0, 0.0, &first);
|
||||
g_value_unset(&v);
|
||||
reorder_after = widget_names();
|
||||
std::string order;
|
||||
for (const std::string& n : a->badge_order) order += (order.empty() ? "" : ",") + n;
|
||||
GtkWidget* head = gtk_widget_get_first_child(bb.signals_row);
|
||||
reorder_ok = handled && head == last.button && order == reorder_after && reorder_after != reorder_before ? 1 : 0;
|
||||
// Back to the original place: the moved badge goes after the one that was last, and the order is restored.
|
||||
gtk_box_reorder_child_after(GTK_BOX(bb.signals_row), last.button, bb.signal_badges[bb.signal_badges.size() - 2]->button);
|
||||
a->badge_order = saved_order;
|
||||
if (widget_names() != reorder_before) reorder_ok = 0;
|
||||
}
|
||||
|
||||
// The drop path itself: a badge's payload lands through `assign_signal_to_row`, as the plot area's drop does.
|
||||
// (The assignment rebuilds the panels, and the badges with them, so the first one is named beforehand.)
|
||||
int payload_drop = -1;
|
||||
int list_drag = -1;
|
||||
if (!bb.signal_badges.empty()) {
|
||||
const std::size_t signal = bb.signal_badges.front()->signal;
|
||||
const std::string name = bb.signal_badges.front()->signal_name;
|
||||
remove_signal_from_plots(*a, name);
|
||||
const bool assigned = assign_signal_to_row(*a, signal, 0);
|
||||
payload_drop = assigned && signal_plotted(*a, name) ? 1 : 0;
|
||||
}
|
||||
if (a->signal_list != nullptr) {
|
||||
GtkWidget* row = gtk_widget_get_first_child(a->signal_list);
|
||||
list_drag = row != nullptr ? count_controllers(row, is_source) : -1;
|
||||
}
|
||||
std::fprintf(stderr,
|
||||
"[sigbadges] drag_sources=%d drop_targets=%d prepare_ok=%d reorder_before=%s reorder_after=%s "
|
||||
"reorder_ok=%d payload_drop=%d list_drag=%d\n",
|
||||
drag_sources, drop_targets, prepare_ok, reorder_before.c_str(), reorder_after.c_str(), reorder_ok,
|
||||
payload_drop, list_drag);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
|
||||
+18
-30
@@ -101,10 +101,9 @@ gboolean exit_probe_step3b(gpointer user) {
|
||||
// the page is open and the reveal has run.
|
||||
a->dbg_signal_w = gtk_widget_get_width(a->signal_panel);
|
||||
a->dbg_signal_h = gtk_widget_get_height(a->signal_panel);
|
||||
// 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`.
|
||||
// How many rows of the signal list carry a drag source: none. Dragging a signal onto a plot is the signal
|
||||
// badge's job (and survives the pane lock, since the badges do not follow it); the list's rows only open
|
||||
// the signal's page. The reading says the list did not grow a second, competing handle.
|
||||
if (a->signal_list != nullptr) {
|
||||
int sources = 0;
|
||||
for (GtkWidget* r = gtk_widget_get_first_child(a->signal_list); r != nullptr;
|
||||
@@ -119,34 +118,23 @@ gboolean exit_probe_step3b(gpointer user) {
|
||||
}
|
||||
a->dbg_signal_drag_sources = sources;
|
||||
}
|
||||
// A drag out of an *overlaid* sidebar: the sidebar has to step aside or the drop has nowhere to land.
|
||||
// The state is forced the way the sidebar probe forces it -- collapsed is what a window with no room
|
||||
// beside the plot produces -- and the drag is begun through the *source*, so the handler is the one a
|
||||
// real drag reaches.
|
||||
if (a->side_view != nullptr && a->signal_list != nullptr) {
|
||||
AdwOverlaySplitView* split = ADW_OVERLAY_SPLIT_VIEW(a->side_view);
|
||||
GtkWidget* row = gtk_widget_get_first_child(a->signal_list);
|
||||
adw_overlay_split_view_set_collapsed(split, TRUE);
|
||||
adw_overlay_split_view_set_show_sidebar(split, TRUE);
|
||||
if (row != nullptr) {
|
||||
GListModel* controllers = gtk_widget_observe_controllers(row);
|
||||
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)) {
|
||||
g_signal_emit_by_name(c, "drag-begin", nullptr);
|
||||
a->dbg_drag_hid_sidebar =
|
||||
adw_overlay_split_view_get_show_sidebar(split) == FALSE ? 1 : 0;
|
||||
row_drag_sidebar(*a, false);
|
||||
a->dbg_drag_restored_sidebar =
|
||||
adw_overlay_split_view_get_show_sidebar(split) != FALSE ? 1 : 0;
|
||||
}
|
||||
g_object_unref(c);
|
||||
// A drag out of a badge with a slide-over open: the panel covers the plot's right edge, so it has to step
|
||||
// aside or part of the plot is not a place to drop. The drag is begun through the *source*, so the handler
|
||||
// is the one a real drag reaches; the page that was open is put back so the readings after it stand.
|
||||
if (!a->badges.signal_badges.empty() && slideover_has_page(*a, "signals")) {
|
||||
const std::string was = slideover_page(*a);
|
||||
slideover_open(*a, "signals");
|
||||
GListModel* controllers = gtk_widget_observe_controllers(a->badges.signal_badges.front()->button);
|
||||
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)) {
|
||||
g_signal_emit_by_name(c, "drag-begin", nullptr);
|
||||
a->dbg_drag_closed_slideover = slideover_page(*a).empty() ? 1 : 0;
|
||||
}
|
||||
g_object_unref(c);
|
||||
}
|
||||
// Left as the window's own width decides, so the readings taken before this are the ones that stand.
|
||||
adw_overlay_split_view_set_collapsed(split, FALSE);
|
||||
adw_overlay_split_view_set_show_sidebar(split, TRUE);
|
||||
if (!was.empty()) slideover_open(*a, was);
|
||||
}
|
||||
// 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
|
||||
|
||||
+4
-22
@@ -302,7 +302,7 @@ void rebuild_signal_list(App& app, const std::vector<SignalInfo>& signals) {
|
||||
|
||||
// One line per *signal*, not per channel. A group's model holds each signal once per event; a list
|
||||
// that showed both would name one "sine (A)" and the other "sine (B)" as if they were different
|
||||
// things, and give them different colours. What the row toggles, colours and drags is the signal.
|
||||
// things, and give them different colours. What the row toggles and colours is the signal.
|
||||
std::vector<std::size_t> rows;
|
||||
// The `MATH` divider goes in before the first math row, and once only: `append_math_signals_locked()`
|
||||
// has already put every math channel in `signals()`, so the boundary is the one thing the deleted
|
||||
@@ -350,8 +350,8 @@ void rebuild_signal_list(App& app, const std::vector<SignalInfo>& signals) {
|
||||
g_signal_connect(pick, "pressed",
|
||||
G_CALLBACK(+[](GtkGestureClick* g, int, double, double, gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
// Claimed, so the row's `GtkDragSource` cannot take the same sequence
|
||||
// and turn a click on the chip into the start of a drag.
|
||||
// Claimed, so the same click does not also activate the row (which would open
|
||||
// the signal's page).
|
||||
gtk_gesture_set_state(GTK_GESTURE(g), GTK_EVENT_SEQUENCE_CLAIMED);
|
||||
open_signal_color(*a, static_cast<std::size_t>(GPOINTER_TO_SIZE(
|
||||
g_object_get_data(G_OBJECT(g),
|
||||
@@ -466,25 +466,7 @@ void rebuild_signal_list(App& app, const std::vector<SignalInfo>& signals) {
|
||||
gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), box);
|
||||
// Activating the row opens the signal's page; the row resolves its identity the way its widgets do.
|
||||
g_object_set_data(G_OBJECT(row), "udpscope-signal", GSIZE_TO_POINTER(i));
|
||||
|
||||
DragContext* ctx = new DragContext{&app, i};
|
||||
GtkDragSource* source = gtk_drag_source_new();
|
||||
gtk_drag_source_set_actions(source, GDK_ACTION_COPY);
|
||||
g_signal_connect_data(source, "prepare", G_CALLBACK(on_drag_prepare), ctx,
|
||||
[](gpointer data, GClosure*) { delete static_cast<DragContext*>(data); },
|
||||
G_CONNECT_DEFAULT);
|
||||
// A drag out of an *overlaid* sidebar would have nowhere to land (see `row_drag_sidebar()`), so the
|
||||
// sidebar steps out of the way for as long as the drag lasts.
|
||||
g_signal_connect(source, "drag-begin", G_CALLBACK(+[](GtkDragSource*, GdkDrag*, gpointer user) {
|
||||
row_drag_sidebar(*static_cast<App*>(user), true);
|
||||
}),
|
||||
&app);
|
||||
g_signal_connect(source, "drag-end",
|
||||
G_CALLBACK(+[](GtkDragSource*, GdkDrag*, gboolean, gpointer user) {
|
||||
row_drag_sidebar(*static_cast<App*>(user), false);
|
||||
}),
|
||||
&app);
|
||||
gtk_widget_add_controller(row, GTK_EVENT_CONTROLLER(source));
|
||||
// Dragging a signal onto a plot is the signal badge's job (`make_signal_badge`): the row opens the page.
|
||||
|
||||
gtk_list_box_append(GTK_LIST_BOX(list), row);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user