Drag and drop is also written as “Drag-and-Drop”, or “DND” in short. DND is like “copy and paste” or “cut and paste”. If a user drags a UI element, which is a widget, selected part or something, data is transferred from the source to the destination.
You probably have experience that you moved a file with DND.
When the DND starts, the file sample_file.txt
is given
to the system. When the DND ends, the system gives
sample_file.txt
to the directory sample_folder
in the file manager. Therefore, it is like “cut and paste”. The actual
behavior may be different from the explanation here, but the concept is
similar.
This tutorial provides a simple example in the src/dnd
directory. It has three labels for the source and one label for the
destination. The source labels have “red”, “green” or “blue” labels. If
a user drags the label to the destination label, the font color will be
changed.
The widgets are defined in the XML file dnd.ui
.
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="default-width">800</property>
<property name="default-height">600</property>
<property name="resizable">FALSE</property>
<property name="title">Drag and Drop</property>
<child>
<object class="GtkBox">
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="homogeneous">TRUE</property>
<child>
<object class="GtkLabel" id="red">
<property name="label">RED</property>
<property name="justify">GTK_JUSTIFY_CENTER</property>
<property name="name">red</property>
</object>
</child>
<child>
<object class="GtkLabel" id="green">
<property name="label">GREEN</property>
<property name="justify">GTK_JUSTIFY_CENTER</property>
<property name="name">green</property>
</object>
</child>
<child>
<object class="GtkLabel" id="blue">
<property name="label">BLUE</property>
<property name="justify">GTK_JUSTIFY_CENTER</property>
<property name="name">blue</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="canvas">
<property name="label">CANVAS</property>
<property name="justify">GTK_JUSTIFY_CENTER</property>
<property name="name">canvas</property>
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
</object>
</child>
</object>
</child>
</object>
</interface>
It is converted to a resource file by
glib-compile-resources
. The compiler uses an XML file
dnd.gresource.xml
.
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/github/ToshioCP/dnd">
<file>dnd.ui</file>
</gresource>
</gresources>
The C file dnd.c
isn’t a big file. The number of the
lines is less than a hundred. A GtkApplication object is created in the
function main
.
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}
The application ID is defined as:
#define APPLICATION_ID "com.github.ToshioCP.dnd"
Most of the work is done in the “startup” signal handler.
Two objects GtkDragSource and GtkDropTarget is used for DND implementation.
The example below uses these objects in a very simple way. You can use number of features that the two objects have. See the following links for more information.
static void
app_startup (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkBuilder *build;
GtkWindow *win;
GtkLabel *src_labels[3];
int i;
GtkLabel *canvas;
GtkDragSource *src;
GdkContentProvider* content;
GtkDropTarget *tgt;
GdkDisplay *display;
char *s;
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/dnd/dnd.ui");
win = GTK_WINDOW (gtk_builder_get_object (build, "win"));
src_labels[0] = GTK_LABEL (gtk_builder_get_object (build, "red"));
src_labels[1] = GTK_LABEL (gtk_builder_get_object (build, "green"));
src_labels[2] = GTK_LABEL (gtk_builder_get_object (build, "blue"));
canvas = GTK_LABEL (gtk_builder_get_object (build, "canvas"));
gtk_window_set_application (win, app);
g_object_unref (build);
for (i=0; i<3; ++i) {
src = gtk_drag_source_new ();
content = gdk_content_provider_new_typed (G_TYPE_STRING, gtk_widget_get_name (GTK_WIDGET (src_labels[i])));
gtk_drag_source_set_content (src, content);
g_object_unref (content);
gtk_widget_add_controller (GTK_WIDGET (src_labels[i]), GTK_EVENT_CONTROLLER (src)); // The ownership of src is taken by the instance.
}
tgt = gtk_drop_target_new (G_TYPE_STRING, GDK_ACTION_COPY);
g_signal_connect (tgt, "drop", G_CALLBACK (drop_cb), NULL);
gtk_widget_add_controller (GTK_WIDGET (canvas), GTK_EVENT_CONTROLLER (tgt)); // The ownership of tgt is taken by the instance.
provider = gtk_css_provider_new ();
s = g_strdup_printf (format, "black");
gtk_css_provider_load_from_data (provider, s, -1);
g_free (s);
display = gdk_display_get_default ();
gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref (provider); // The provider is still alive because the display owns it.
}
source_labels[]
points the source labels red, green and blue in the ui file. The
variable canvas
points the destination label.src_labels[]
each of which points the source
widget, red, green or blue label.drop_cb
.format
is static and defined at the top
of the program. Static variables are shown below.static GtkCssProvider *provider = NULL;
static const char *format = "label {padding: 20px;} label#red {background: red;} "
"label#green {background: green;} label#blue {background: blue;} "
"label#canvas {color: %s; font-weight: bold; font-size: 72pt;}";
static void
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWindow *win;
win = gtk_application_get_active_window (app);
gtk_window_present (win);
}
This handler just shows the window.
static gboolean
drop_cb (GtkDropTarget* self, const GValue* value, gdouble x, gdouble y, gpointer user_data) {
char *s;
s = g_strdup_printf (format, g_value_get_string (value));
gtk_css_provider_load_from_data (provider, s, -1);
g_free (s);
return TRUE;
}
The “drop” signal handler has five parameters.
x
and y
are the coordinate
of the mouse when released.The string from the GValue is “red”, “green” or “blue”. It replaces
“%s” in the variable format
. That means the font color of
the label canvas
will turn to the color.
The file meson.build
controls the building process.
project('dnd', 'c')
gtkdep = dependency('gtk4')
gnome = import('gnome')
resources = gnome.compile_resources('resources','dnd.gresource.xml')
executable(meson.project_name(), 'dnd.c', resources, dependencies: gtkdep, export_dynamic: true, install: false)
You can build it from the command line.
$ cd src/dnd
$ meson setup _build
$ ninja -C _build
$ _build/dnd
The source files are under the directory src/dnd
of the
repository.
Download it and see the directory.