Introduction

/!\ Notice:
This page was originally written for GTK 2.0 but most of it still applies for GTK 3.0. The sample code has already been updated for 3.0 but the text still needs to be reviewed. You can also find more information in the API reference. The signals for the widgets are found at GtkWidget and the functions necessary for drag and drop are found at Drag and Drop.

Conceptually, adding drag and drop to your GTK application is very simple. In practise however, it requires some effort and understanding. Also GTK+ is a powerful and flexible framework that will let you do many cool things with drag-and-drop, with multiple ways to get the same effect. Therefore we are going to concentrate on how to get the most simple, general, and correct drag-and-drop support, and let you add in the details.

GTK+ was first developed for the X11 system, and as such its drag and drop implementation is highly influenced by X's "selection" method of inter-client (inter-application) communication. In fact this is exactly what the Unix port of GTK+ uses to transport drag and drop information. If you find yourself helplessly confused during the course of implementing your application's drag and drop, you might consider searching for an online tutorial on X selections, or finding a copy of the X11 Xlib Programming Manual.

Overview

When the user initiates a DnD (drag-and-drop) operation, they are choosing a 'source' by picking and dragging a widget on the screen, and choosing a 'destination' by dragging and dropping onto another widget on the screen. The two widgets are then expected to agree on the meaning of this action, and communicate some sort of data.

As it is possible that widgets cannot agree on a meaning for any possible combination of drags and drops (such as a panel applet in a text editor window), or that they can't agree on the type of data to transfer (such as sending a string when an image is expected), we must ensure to let GTK+ know before-hand which widgets will accept DnDs and what data types they can use.

Let us take two widgets, Source and Destination, which communicate with each other via GTK+'s signal system. When an object is dropped onto the Destination, the handler for the "drop" signal chooses the type of data (such as integer, string, bytes, etc.) that it would like to make use of, and asks GTK+ to ask the Source for the data in that format. Then the handler for the "get data" signal tries to convert its data into a form suitable to the Destination as best it can, and sends it back via GTK+. Finally the handler for the "received data" signal checks that the data is acceptable and finishes the operation.

Drag and Drop with GTK+

Before a GTK+ widget can become either a source or destination for a DnD, it must be set up to let GTK+ know that it should allow DnD operations on it, to listen to certain signals, and to know what set of data types, called targets, it can accept.

A target is a name given to a type of data that is supported by a source or destination (hopefully both). GTK+ selections use the target name to negotiate a common data type or format. The Source then places a pointer to the formatted data as an array of bytes, which is then copied and given to the Destination. The formatting and target information should allow the Destination to properly decode the memory given to it.

Source and Destination are able to coordinate the types of data they support by drawing on a common list of GtkTargets. The list is an array of GtkTargetEntrys, and is the sole protocol for negotiating types between widgets or applications. Therefore the GtkTargetEntry for any application you wish to interact with should be carefully observed.

Unfortunately while researching this tutorial I ran across some information about how the DnD process works in GTK+ that at best is outdated, probably misleading, and at worst just plain wrong. Therefore I would like to describe in more detail what happens during a DnD with a simple schematic:

"signal-name"
[function_call]
(user action)

Source                        GTK+                         Destination
------                        ----                         -----------
                              (user begins drag)
[begin_cb]<-------------------"drag-begin"
                              (user drops)
                              "drag-leave"---------------->[leave_cb]
                              "drag-drop"----------------->[drop_cb]
                              [gtk_drag_get_data]<---------[drop_cb]
[data_cb]<--------------------"drag-data-get"
[data_cb]-------------------->[gtk_selection_data_set]
                              "drag-data-received"-------->[received_cb]
                              [gtk_drag_finish]<-----------[received_cb]
[end_cb]<---------------------"drag-end"

In addition to the above, Destination is capable of receiving the "drag-motion" signal, which is emitted whenever a draggable object is over the destination. Also, the Source is capable of receiving the "drag-data-delete" signal which is emitted by gtk_drag_finish, and is used to implement 'move operations' by letting the Source know it is safe to delete the data it just sent.

The signals we are most interested in, and are the minimum necessary for a basic DnD, are "drag-drop", "drag-data-get", and "drag-data-received".

The purpose of the handler for "drag-drop" is firstly to decide if it wants to accept the drop. For example a widget may have a complicated layout, and may decide that the drop was not on a valid part of the widget. If it wishes the operation to continue, it must return TRUE when finished. Next it must choose what type of data it prefers the source to deliver. Lastly it initiates the request by calling gtk_drag_get_data.

The purpose of the handler for "drag-data-get" is first to attempt to convert its data into the type requested by the Destination, or failing to do so, sending an error as a safe type such as a string. Finally it fills in a GtkSelectionData structure with the data and its formatting information using gtk_selection_data_set. GtkSelectionData is based on X's selection mechanism which passes data between X clients using named "properties". A property name is called an "atom", and in practise is simply a integer handle to describe some chunk of memory on the X server. X properties are only capable of storing data in blocks of 8, 16, or 32 bit units, which means that information other than integers, byte-strings, UTF-8 text, URIs, or PixBufs, must be preformatted into an array by your application.

The purpose of the handler for "drag-data-received" is first to check that the data is there and in a format acceptable, then decode the data pointer sent to it either manually or using a corresponding gtk_selection_data_get function. Finally it should call gtk_drag_finish indicating whether the DnD succeeded, and whether Source should delete the data (if the DnD was a GDK_ACTION_MOVE). This will cause GTK+ to emit the "drag-data-delete" signal if the delete parameter is set to TRUE, and finally "drag-end".

Caveat Window

Because GTK+ implements drag and drop on top of the native platform's windowing system, widgets must have a GdkWindow to be used as a drag source.

Step by Step

  1. Decide the types of targets your DnD will support, and fill in the entries in the GtkTargetEntry list. The first field of a GtkTargetEntry is a string representing the target name. The second is for GtkTargetFlags which allow you to restrict a DnD operation to the same application, or even the same widget. The third is a integer representing the target name, which is passed to various signal handlers to identify the target. This allows you to avoid a costly string operation, but increases the odds of accidentally colliding with a different target that has the same integer name.

  2. Decide which widgets will be source and destination, then call gtk_drag_dest/source_set respectively. For both functions the first parameter is the widget you wish to set as a destination or source -- however some restrictions apply to the widgets able to serve as such, for example all dest/source widgets must have an associated X window. The third and fourth parameters are for informing the widget about its list of GtkTargetEntrys as created in step 1. The fifth parameter lets your signal handlers know how they should treat the data that is being passed by a DnD. For example, during a GDK_ACTION_MOVE the Destination should emit a "drag-data-delete" signal after successfully receiving the data, which your Source should handle by deleting it's copy of the data. The second parameter of gtk_drag_dest_set allows you to customize the behaviour of the destination on a DnD somewhat. The second parameter of gtk_drag_source_set allows you to specify what keys or buttons will allow a DnD operation to begin.

  3. With appropriate handlers, connect the Destination to the "drag-drop" and "drag-data-received" singals, and the Source to the "drag-data-get" signal.
  4. Write those handlers. Exactly how to do so can be rather complicated, however I have attempted to sketch above the responsibilities of those handlers, and have written the sample code necessary to get basic DnD below. The comments for what each handler is doing and why, are placed above the handler's definition.

The Code

/* TestDnD - main.c : Simple tutorial for GTK+ Drag-N-Drop
 * Copyright (C) 2005 Ryan McDougall.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

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


/******************************************************************************/
#define _BYTE   8
#define _WORD   16
#define _DWORD  32


/******************************************************************************/
/* Define a list of data types called "targets" that a destination widget will
 * accept. The string type is arbitrary, and negotiated between DnD widgets by
 * the developer. An enum or GQuark can serve as the integer target id. */
enum {
        TARGET_INT32,
        TARGET_STRING,
        TARGET_ROOTWIN
};

/* datatype (string), restrictions on DnD (GtkTargetFlags), datatype (int) */
static GtkTargetEntry target_list[] = {
        { "INTEGER",    0, TARGET_INT32 },
        { "STRING",     0, TARGET_STRING },
        { "text/plain", 0, TARGET_STRING },
        { "application/x-rootwindow-drop", 0, TARGET_ROOTWIN }
};

static guint n_targets = G_N_ELEMENTS (target_list);


/******************************************************************************/
/* Signal receivable by destination */

/* Emitted when the data has been received from the source. It should check
 * the GtkSelectionData sent by the source, and do something with it. Finally
 * it needs to finish the operation by calling gtk_drag_finish, which will emit
 * the "data-delete" signal if told to. */
static void
drag_data_received_handl
(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
        GtkSelectionData *selection_data, guint target_type, guint time,
        gpointer data)
{
        glong   *_idata;
        gchar   *_sdata;

        gboolean dnd_success = FALSE;
        gboolean delete_selection_data = FALSE;

        const gchar *name = gtk_widget_get_name (widget);
        g_print ("%s: drag_data_received_handl\n", name);


        /* Deal with what we are given from source */
        if((selection_data != NULL) && (gtk_selection_data_get_length(selection_data) >= 0))
        {
                if (gdk_drag_context_get_suggested_action(context) == GDK_ACTION_ASK)
                {
                /* Ask the user to move or copy, then set the context action. */
                }

                if (gdk_drag_context_get_suggested_action(context) == GDK_ACTION_MOVE)
                        delete_selection_data = TRUE;

                /* Check that we got the format we can use */
                g_print (" Receiving ");
                switch (target_type)
                {
                        case TARGET_INT32:
                                _idata = (glong*)gtk_selection_data_get_data(selection_data);
                                g_print ("integer: %ld", *_idata);
                                dnd_success = TRUE;
                                break;

                        case TARGET_STRING:
                                _sdata = (gchar*)gtk_selection_data_get_data(selection_data);
                                g_print ("string: %s", _sdata);
                                dnd_success = TRUE;
                                break;

                        default:
                                g_print ("nothing good");
                }

                g_print (".\n");
        }

        if (dnd_success == FALSE)
        {
                g_print ("DnD data transfer failed!\n");
        }

        gtk_drag_finish (context, dnd_success, delete_selection_data, time);
}

/* Emitted when a drag is over the destination */
static gboolean
drag_motion_handl
(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint t,
        gpointer user_data)
{
        // Fancy stuff here. This signal spams the console something horrible.
        //const gchar *name = gtk_widget_get_name (widget);
        //g_print ("%s: drag_motion_handl\n", name);
        return  FALSE;
}

/* Emitted when a drag leaves the destination */
static void
drag_leave_handl
(GtkWidget *widget, GdkDragContext *context, guint time, gpointer user_data)
{
        const gchar *name = gtk_widget_get_name (widget);
        g_print ("%s: drag_leave_handl\n", name);
}

/* Emitted when the user releases (drops) the selection. It should check that
 * the drop is over a valid part of the widget (if its a complex widget), and
 * itself to return true if the operation should continue. Next choose the
 * target type it wishes to ask the source for. Finally call gtk_drag_get_data
 * which will emit "drag-data-get" on the source. */
static gboolean
drag_drop_handl
(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time,
        gpointer user_data)
{
        gboolean        is_valid_drop_site;
        GdkAtom         target_type;

        const gchar *name = gtk_widget_get_name (widget);
        g_print ("%s: drag_drop_handl\n", name);

        /* Check to see if (x,y) is a valid drop site within widget */
        is_valid_drop_site = TRUE;

        /* If the source offers a target */
        if (gdk_drag_context_list_targets (context))
        {
                /* Choose the best target type */
                target_type = GDK_POINTER_TO_ATOM
                        (g_list_nth_data (gdk_drag_context_list_targets(context), TARGET_INT32));

                /* Request the data from the source. */
                gtk_drag_get_data
                (
                        widget,         /* will receive 'drag-data-received' signal */
                        context,        /* represents the current state of the DnD */
                        target_type,    /* the target type we want */
                        time            /* time stamp */
                );
        }
        /* No target offered by source => error */
        else
        {
                is_valid_drop_site = FALSE;
        }

        return  is_valid_drop_site;
}


/******************************************************************************/
/* Signals receivable by source */

/* Emitted after "drag-data-received" is handled, and gtk_drag_finish is called
 * with the "delete" parameter set to TRUE (when DnD is GDK_ACTION_MOVE). */
static void
drag_data_delete_handl
(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
{
        // We aren't moving or deleting anything here
        const gchar *name = gtk_widget_get_name (widget);
        g_print ("%s: drag_data_delete_handl\n", name);
}

/* Emitted when the destination requests data from the source via
 * gtk_drag_get_data. It should attempt to provide its data in the form
 * requested in the target_type passed to it from the destination. If it cannot,
 * it should default to a "safe" type such as a string or text, even if only to
 * print an error. Then use gtk_selection_data_set to put the source data into
 * the allocated selection_data object, which will then be passed to the
 * destination. This will cause "drag-data-received" to be emitted on the
 * destination. GdkSelectionData is based on X's selection mechanism which,
 * via X properties, is only capable of storing data in blocks of 8, 16, or
 * 32 bit units. */
static void
drag_data_get_handl
(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
        guint target_type, guint time, gpointer user_data)
{
        const gchar *name = gtk_widget_get_name (widget);
        const gchar *string_data = "This is data from the source.";
        const glong integer_data = 42;

        g_print ("%s: drag_data_get_handl\n", name);
        g_assert (selection_data != NULL);

        g_print (" Sending ");
        switch (target_type)
        {
                /* case TARGET_SOME_OBJECT:
                 * Serialize the object and send as a string of bytes.
                 * Pixbufs, (UTF-8) text, and URIs have their own convenience
                 * setter functions */

        case TARGET_INT32:
                g_print ("integer: %ld", integer_data);
                gtk_selection_data_set
                (
                        selection_data,         /* Allocated GdkSelectionData object */
                        gtk_selection_data_get_target(selection_data), /* target type */
                        _DWORD,                 /* number of bits per 'unit' */
                        (guchar*) &integer_data,/* pointer to data to be sent */
                        sizeof (integer_data)   /* length of data in units */
                );
                break;

        case TARGET_STRING:
                g_print ("string: %s", string_data);
                gtk_selection_data_set
                (
                        selection_data,
                        gtk_selection_data_get_target(selection_data),
                        _BYTE,
                        (guchar*) string_data,
                        strlen (string_data)
                );
                break;

        case TARGET_ROOTWIN:
                g_print ("Dropped on the root window!\n");
                break;

        default:
                /* Default to some a safe target instead of fail. */
                g_assert_not_reached ();
        }

        g_print (".\n");
}

/* Emitted when DnD begins. This is often used to present custom graphics. */
static void
drag_begin_handl
(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
{
        const gchar *name = gtk_widget_get_name (widget);
        g_print ("%s: drag_begin_handl\n", name);
}

/* Emitted when DnD ends. This is used to clean up any leftover data. */
static void
drag_end_handl
(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
{
        const gchar *name = gtk_widget_get_name (widget);
        g_print ("%s: drag_end_handl\n", name);
}


/******************************************************************************/
int
main (int argc, char **argv)
{
        GtkWidget       *window;
        GtkWidget       *hbox;
        GtkWidget       *coin_source;
        GtkWidget       *well_dest;
        GtkWidget       *directions_label;
        guint           win_xsize       = 450;
        guint           win_ysize       = 50;
        guint           spacing         = 5;


        /* Always start GTK+ first! */
        gtk_init (&argc, &argv);


        /* Create the widgets */
        window  = gtk_window_new (GTK_WINDOW_TOPLEVEL);
        hbox    = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, spacing);

        coin_source     = gtk_button_new_with_label ("[coins]");
        well_dest       = gtk_label_new ("[a well]");

        directions_label = gtk_label_new ("drag a coin and drop it in the well");


        /* Pack the widgets */
        gtk_container_add (GTK_CONTAINER (window), hbox);

        gtk_container_add (GTK_CONTAINER (hbox), coin_source);
        gtk_container_add (GTK_CONTAINER (hbox), directions_label);
        gtk_container_add (GTK_CONTAINER (hbox), well_dest);


        /* Make the window big enough for some DnD action */
        gtk_window_set_default_size (GTK_WINDOW(window), win_xsize, win_ysize);


        /* Make the "well label" a DnD destination. */
        gtk_drag_dest_set
        (
                well_dest,              /* widget that will accept a drop */
                GTK_DEST_DEFAULT_MOTION /* default actions for dest on DnD */
                | GTK_DEST_DEFAULT_HIGHLIGHT,
                target_list,            /* lists of target to support */
                n_targets,              /* size of list */
                GDK_ACTION_COPY         /* what to do with data after dropped */
        );

        /* Make the "coin button" a DnD source. */
        /* Why doesn't GtkLabel work here? 
         * See Caveat Window above
         */
        gtk_drag_source_set
        (
                coin_source,            /* widget will be drag-able */
                GDK_BUTTON1_MASK,       /* modifier that will start a drag */
                target_list,            /* lists of target to support */
                n_targets,              /* size of list */
                GDK_ACTION_COPY         /* what to do with data after dropped */
        );


        /* Connect the signals */
        g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);

        /* All possible destination signals */
        g_signal_connect (well_dest, "drag-data-received",
                G_CALLBACK(drag_data_received_handl), NULL);

        g_signal_connect (well_dest, "drag-leave",
                G_CALLBACK (drag_leave_handl), NULL);

        g_signal_connect (well_dest, "drag-motion",
                G_CALLBACK (drag_motion_handl), NULL);

        g_signal_connect (well_dest, "drag-drop",
                G_CALLBACK (drag_drop_handl), NULL);

        /* All possible source signals */
        g_signal_connect (coin_source, "drag-data-get",
                G_CALLBACK (drag_data_get_handl), NULL);

        g_signal_connect (coin_source, "drag-data-delete",
                G_CALLBACK (drag_data_delete_handl), NULL);

        g_signal_connect (coin_source, "drag-begin",
                G_CALLBACK (drag_begin_handl), NULL);

        g_signal_connect (coin_source, "drag-end",
                G_CALLBACK (drag_end_handl), NULL);


        /* Show the widgets */
        gtk_widget_show_all (window);

        /* Start the even loop */
        gtk_main ();

        return 0;
}

To compile the example use the following command:

cc main.c `pkg-config --cflags --libs gtk+-3.0` -o main

Copyright RyanMcDougall (2005)

Newcomers/OldDragNDropTutorial (last edited 2020-12-21 11:42:55 by EmmanueleBassi)