Goal
I want to create an app that uses Adwaita and Gtk4. I want to have a list of items that can be reordered via drag and drop. If you remember the old 3.X gnome-todo app: I am trying to create something like this for my DND list behavior.
Issue
I have several questions. I already got a lot of advice in https://app.element.io/#/room/#gtk:gnome.org.
To name two:
- I have been told that one can get the drop location with
Gtk.ListBox.get_row_at_y, after which I’d have to find the drop location index, remove the dragged widget from the list and then re-add it at that index location that I got. - I have also been made aware that gnome-control-center has an example of something similar I’m trying (e.g. keyboard panel).
I still have troubles, though, probably because I’m still a beginner and the gnome-control-center code was to complex for me.
My issue is that I’m not really sure if the things I did are correct and how to proceed from here.
My Questions (see code below)
- Do I need to connect to the
drag-beginanddrag-endsignals in mysetup_drag_source()? - In
on_drag_prepare_cb()I set up thecontent_provider. The label text is hidden below the cursor icon though. How to fix? - In
on_drag_prepare_cb()I return thecontent_providerand thus cannotg_object_unref()it. Is that an issue? - In
setup_drop_target(), should I really useGDK_ACTION_COPY? - In
setup_drop_target(), is the GType really suitable for my needs? I don’t plan on moving strings after all, but labels.
Help is much appreciated!
My Code
You can find my questions from above in the comments in the code as well.
#include <adwaita.h>
#include <gtk/gtk.h>
static void
activate_cb(GtkApplication *application,
__attribute__((unused)) gpointer user_data);
GtkWidget *
gui_list_box_create(GtkWidget *list_box);
void
setup_drag_source(GtkWidget *label);
static GdkContentProvider *
on_drag_prepare_cb(GtkDragSource *source, double x, double y, GtkWidget *self);
void
setup_drop_target(GtkWidget *label);
static gboolean
on_drop_cb(GtkDropTarget *target,
const GValue *value,
double x,
double y,
gpointer data);
int
main(void)
{
AdwApplication *application;
int status;
application =
adw_application_new("org.test.me", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect(
application, "activate", G_CALLBACK(activate_cb), NULL);
status = g_application_run(G_APPLICATION(application), 0, NULL);
g_object_unref(application);
return status;
}
static void
activate_cb(GtkApplication *application,
__attribute__((unused)) gpointer user_data)
{
GtkWidget *window;
GtkWidget *toolbar_view;
GtkWidget *header_bar;
GtkWidget *list_box = NULL;
window = adw_application_window_new(application);
toolbar_view = adw_toolbar_view_new();
header_bar = adw_header_bar_new();
list_box = gui_list_box_create(list_box);
adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view),
header_bar);
adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(toolbar_view), list_box);
adw_application_window_set_content(ADW_APPLICATION_WINDOW(window),
toolbar_view);
gtk_window_present(GTK_WINDOW(window));
}
/*
* Create a listbox with 3 children: A, B and C.
* Also add Drag and Drop functionality here.
*/
GtkWidget *
gui_list_box_create(GtkWidget *list_box)
{
GtkWidget *label;
const char *array[] = { "A", "B", "C", NULL };
list_box = gtk_list_box_new();
for (int i = 0; array[i] != NULL; i++) {
label = gtk_label_new(array[i]);
// I can see that a drag source is added to my label in the
// inspector (which can be called via Ctrl-Shift-d). Dragging
// now also changes the cursor.
setup_drag_source(label);
// I can see that the drop target has been added to my label in
// the inspector. Dropping now changes the cursor (to an "I
// accept drops" icon) and the drop signal successfully prints
// "DROP STRING".
setup_drop_target(label);
gtk_list_box_append(GTK_LIST_BOX(list_box), label);
}
return list_box;
}
void
setup_drag_source(GtkWidget *label)
{
GtkDragSource *drag_source;
drag_source = gtk_drag_source_new();
// I want to move label A below label B, so that A->B->C becomes
// B->A->C. I want to use Drag and Drop for that.
// Do I need GDK_ACTION_MOVE and thus connect to the drag-begin,
// drag-end signals? If yes, what do I do in their callbacks?
// Or do I need GDK_ACTION_COPY? Why?
g_signal_connect(
drag_source, "prepare", G_CALLBACK(on_drag_prepare_cb), label);
gtk_widget_add_controller(label, GTK_EVENT_CONTROLLER(drag_source));
}
static GdkContentProvider *
on_drag_prepare_cb(GtkDragSource *drag_source,
double x,
double y,
GtkWidget *self)
{
GdkContentProvider *content_provider;
// Not sure if this is even correct. As far as a understand this is only
// used to show text during drag – meaning I do want a string here? In
// other words: This is unrelated to my wish of moving the widget label
// "A" below the label "B" via Drag and Drop?
//
// Besides: It does have the issue that the text (e.g. "A") is hidden
// beneath my cursor which isn't ideal. Is that a bug withhin my
// program? How to solve?
content_provider = gdk_content_provider_new_typed(
G_TYPE_STRING, gtk_label_get_text(GTK_LABEL(self)));
gtk_drag_source_set_content(drag_source, content_provider);
// I cannot unref here since I need to return it. Will that cause
// issues?
// g_object_unref(content_provider);
return content_provider;
}
void
setup_drop_target(GtkWidget *label)
{
GtkDropTarget *drop_target;
// I want to move my labels.
// 01. I'm guessing GDK_ACTION_COPY is wrong here and I should use
// GDK_ACTION_MOVE instead?
// 02. I'm having a hard time believing that my GType is correct here.
// What should I use as a GType?
drop_target = gtk_drop_target_new(G_TYPE_STRING, GDK_ACTION_COPY);
g_signal_connect(drop_target, "drop", G_CALLBACK(on_drop_cb), label);
gtk_widget_add_controller(label, GTK_EVENT_CONTROLLER(drop_target));
}
static gboolean
on_drop_cb(GtkDropTarget *target,
const GValue *value,
double x,
double y,
gpointer data)
{
GtkWidget *self;
self = data;
// Call the appropriate setter depending on the type of data that we
// received
if (G_VALUE_HOLDS(value, G_TYPE_STRING)) {
// Do something like this function which I took from the docs
// my_widget_set_file(self, g_value_get_object(value));
puts("DROP STRING");
} else {
puts("DROP NOT STRING");
return FALSE;
}
return TRUE;
}
You can build this with
gcc test_me.c -o test_me $(pkg-config --cflags --libs gtk4 libadwaita-1)
PS
This is a continuation from Looking for help to create Drag and Drop with a list view in C. I can not edit that post, however, nor close it. Since it’s apparently really complicated to implement DND with a GtkListView (which is why I decided to use GtkListBox instead), I’m assuming it’s a better idea to create a new topic for this.