Looking for help to create Drag and Drop with a list view in C

Hello!

I’m trying to create an app that uses Adwaita and Gtk4. The app should have a list of items and allow reordering those items, including nesting, on 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.

Here’s the code that I already have (minimal version to remove code that is unnessary for this question):

// Save file as test_me.c, then run this command to build and execute binary:
// gcc test_me.c -o test_me $(pkg-config --cflags --libs gtk4 libadwaita-1) &&
// ./test_me


#include <adwaita.h>
#include <gtk/gtk.h>




static void
activate_cb(GtkApplication *application,
	    __attribute__((unused)) gpointer user_data);


GtkWidget *
create_application_window(GtkApplication *application);


static void
setup_list_item_factory_cb(GtkSignalListItemFactory *self,
			   GtkListItem *listitem,
			   gpointer user_data);


static void
bind_list_item_factory_cb(GtkSignalListItemFactory *self,
			  GtkListItem *listitem,
			  gpointer user_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); // NOLINT

	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;
	AdwNavigationPage *navigation_page;
	GtkStringList *string_list;
	GtkNoSelection *no_selection;
	GtkListItemFactory *factory;
	GtkWidget *list_view;

	const char *array[] = { "Dog",      "Cat",   "Elephant", "Hippo",
				"Dinosaur", "Whale", NULL };


	window = create_application_window(application);
	toolbar_view = adw_toolbar_view_new();
	header_bar = adw_header_bar_new();
	string_list = gtk_string_list_new((const char *const *)array);
	no_selection = gtk_no_selection_new(G_LIST_MODEL(string_list));
	factory = gtk_signal_list_item_factory_new();
	list_view =
	    gtk_list_view_new(GTK_SELECTION_MODEL(no_selection), factory);
	navigation_page = adw_navigation_page_new(list_view, "EXAMPLE");


	g_signal_connect(
	    factory, "setup", G_CALLBACK(setup_list_item_factory_cb), NULL);
	g_signal_connect(
	    factory, "bind", G_CALLBACK(bind_list_item_factory_cb), NULL);


	adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view),
				     header_bar);

	adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(toolbar_view),
				     GTK_WIDGET(navigation_page)); // NOLINT
	adw_application_window_set_content(ADW_APPLICATION_WINDOW(window),
					   toolbar_view);


	gtk_window_present(GTK_WINDOW(window)); // NOLINT
}


GtkWidget *
create_application_window(GtkApplication *application)
{
	GtkWidget *window;

	window = adw_application_window_new(application);

	g_signal_connect(
	    application, "shutdown", G_CALLBACK(g_application_quit), NULL);

	return window;
}


static void
setup_list_item_factory_cb(GtkSignalListItemFactory __attribute__((unused)) *
			       self,
			   GtkListItem *listitem,
			   gpointer __attribute__((unused)) user_data)
{
	GtkWidget *label;
	GtkDragSource *drag_source;
	GdkContentProvider *content_provider;
	GtkDropTarget *drop_target;

	label = gtk_label_new(NULL);
	drag_source = gtk_drag_source_new();

	gtk_list_item_set_child(listitem, label);
	content_provider = gdk_content_provider_new_typed(
	    G_TYPE_STRING,
	    gtk_widget_get_name(GTK_WIDGET(label))); // NOLINT
	gtk_drag_source_set_content(drag_source, content_provider);
	g_object_unref(content_provider);
	gtk_widget_add_controller(GTK_WIDGET(GTK_WIDGET(label)),      // NOLINT
				  GTK_EVENT_CONTROLLER(drag_source)); // NOLINT
	drop_target = gtk_drop_target_new(G_TYPE_STRING, GDK_ACTION_COPY);
	// I know I'll need this one to receive data but am not quite sure how.
	// g_signal_connect(drop_target, "drop", G_CALLBACK(on_drop_cb), NULL);
	gtk_widget_add_controller(GTK_WIDGET(label),                  // NOLINT
				  GTK_EVENT_CONTROLLER(drop_target)); // NOLINT
}


static void
bind_list_item_factory_cb(GtkSignalListItemFactory __attribute__((unused)) *
			      self,
			  GtkListItem *listitem,
			  gpointer __attribute__((unused)) user_data)
{
	GtkWidget *label;
	GtkStringObject *string_object;

	label = gtk_list_item_get_child(listitem);
	string_object = gtk_list_item_get_item(listitem);

	gtk_label_set_text(GTK_LABEL(label), // NOLINT
			   gtk_string_object_get_string(string_object));
}

The app currently successfully does…

  1. …create a list view.
  2. …allow dragging list items.
  3. …allow dropping list items into other apps (like gnome-terminal).
  4. …allow dropping list items into other list items (same app, nothing happens afterwards).

I need help regarding these issues though:

  1. On drop (and during drag) it shows the text “GtkLabel” instead of the label text.
  2. I’m not sure how to reorder the lists on drag and drop.
    a) Should I create a struct with GtkLabel *first_child and create a list, that is linked with *next_sibling, *previous_sibling, *parent_item?
    b) Would that be done in the “drop” signal of my drop_target?
  3. How can I get a nested effect on that list anyways? I.e. that a child item is indented a bit compared to its parent. Do I need to create several containers for that?

My code also has a few lines with the comment //NOLINT to silence clang-tidys bugprone-casting-through-void warnings like:

Do not cast ‘GTypeInstance *’ (aka ‘struct _GTypeInstance *’) to ‘GtkLabel *’ (aka ‘struct _GtkLabel *’) through ‘void *’

If there’s a better solution than turning off linting, please do share it!

Thank you in advance!

PS: I’m more of a beginner when it comes to programming, including libadwaita and gtk4.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.