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…
- …create a list view.
- …allow dragging list items.
- …allow dropping list items into other apps (like gnome-terminal).
- …allow dropping list items into other list items (same app, nothing happens afterwards).
I need help regarding these issues though:
- On drop (and during drag) it shows the text “GtkLabel” instead of the label text.
- I’m not sure how to reorder the lists on drag and drop.
a) Should I create a struct withGtkLabel *first_childand 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? - 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.