Tutorial

Inti Logo

« Managing Selections
Scribble, A Simple Example Drawing Program  »

Drag and Drop

  1. Overview
  2. Properties
  3. Defining a Destination Widget
  4. Defining a Source Widget
  5. Drag and Drop Example


Overview

GTK+ has a high level set of functions for doing inter-process communication via the drag-and-drop system and can perform drag-and-drop on top of the low level Xdnd and Motif drag-and-drop protocols.

An application capable of drag-and-drop first defines and sets up the widget(s) for drag-and-drop. Each widget can be a source and/or destination for drag-and-drop. Note that these widgets must have an associated X Window, check using Gtk::Widget::has_no_window(). Source widgets can send out drag data, thus allowing the user to drag things off of them, while destination widgets can receive drag data. Drag-and-drop destinations can limit who they accept drag data from, e.g. the same application or any application (including itself).

Sending and receiving drop data makes use of signals. Dropping an item to a destination widget requires both a data request (for the source widget) and data received signal handler (for the target widget). Additional signal handers can be connected if you want to know when a drag begins (at the very instant it starts), when a drop is made, and when the entire drag-and-drop procedure has ended (successfully or not).

Your application will need to provide data for source widgets when requested, that involves having a drag data request signal handler. For destination widgets they will need a drop data received signal handler. So a typical drag-and-drop cycle would look as follows:
There are a few minor steps that go in between here and there, but we will get into detail about that later.


Properties

Drag data has the following properties:
The first thing you need to do when setting up a destination or source widget is construct a Gtk::TargetEntry (for a single target) or a vector of Gtk::TargetEntry (for multiple targets).

TargetEntry(const char *target_name, unsigned int unique_id, Gtk::TargetFlagsField drag_flags = 0);

The target_name is a string corresponding to the target atom and unique_id is the unique integer id. When constructing a Gtk::TargetEntry for a drag-and-drop the drag_flags argument is important. These flags are used to restrict the specified target to the same application or the same widget and can be one or more of the values defined in the Gtk::TargetFlags enumeration:
There are a number of possible drag actions that can be applied to the data exchanged through drag-and-drop, and are defined in the Gdk::DragAction enumeration:
Drag actions are quite obvious, they specify if the widget can drag with the specified action(s). A Gdk::ACTION_COPY would be a typical drag-and-drop without the source data being deleted while Gdk::ACTION_MOVE would be just like Gdk::ACTION_COPY but the source data will be 'suggested' to be deleted after the received signal handler is called. You may want to look into the additional drag actions including Gdk::ACTION_LINK when you get to more advanced levels of drag-and-drop.

Sent and received data format types (selection target) come into play only in your request and received data handler methods. The term selection target is somewhat misleading. It is a term adapted from GTK+ selection (cut/copy and paste). What selection target actually means is the data's format type (i.e. Gdk::Atom, integer, or string) that is being sent or received. Your request data handler method needs to specify the type (selection target) of data that it sends out and your received data handler needs to handle the type (selection target) of data received.


Defining a Destination Widget

To turn a widget into a drag-and-drop destination call the one of the following methods:

void drag_dest_set(Gtk::DestDefaultsField flags, const Gtk::TargetEntry& target, Gdk::DragActionField actions);

void drag_dest_set(Gtk::DestDefaultsField flags, const std::vector<Gtk::TargetEntry>& targets, Gdk::DragActionField actions);

The flags argument defines the automatic behaviour options of the destination widget and can be one or more values from the  Gtk::DestDefaults enumeration OR'd together:

DEST_DEFAULT_MOTION -
during a drag over this widget will check if the drag matches this widget's list of possible targets and actions.
DEST_DEFAULT_HIGHLIGHT -
will draw a highlight on this widget as long as a drag is over this widget and the widget drag format and action are acceptable.
DEST_DEFAULT_DROP -
checks if the drag matches this widget's list of possible targets and actions. If so, it calls Gtk::Widget::drag_data_get() for you.
DEST_DEFAULT_ALL -
specifies that all default actions should be taken. This is an appropriate choice for most applications.

The actions argument can be one or more values from the Gdk::DragAction enumeration above, OR'd together. The first method is used to define a single target. The second method is used to define multiple targets.

To remove drag-and-drop functionality from a destination widget call:

void drag_dest_unset();

To be able to receive data from a drag-and-drop source you must either connect the destination widget to the drag_data_received signal or derive a destination widget from an
existing widget and override its virtual on_drag_data_received() handler.

You can connect to the drag_data_received signal like this:

widget->sig_drag_data_received().connect(slot(this, &MyClass::drag_data_received_handler));

where the drag_data_received_handler has the following prototype:

void MyClass::drag_data_received_handler(GdkDragContext *context, int x, int y, GtkSelectionData *data, unsigned int info, unsigned int time);

The virtual on_drag_data_received() handler has the same prototype, except it passes context as a reference to a Gdk::DragContext wrapper and data as a reference to a Gtk::SelectionData wrapper. x and y are the position of the mouse pointer relative to the destination widget. info is the unique id that was set when the target was constructed and time is the time the request occurred.

During a drag-and-drop operation the destination widget is sent the following signals:

Defining a Source Widget

To define a widget as a drag-and-drop source call one of the following methods:

void drag_source_set(Gdk::ModifierTypeField start_button_mask, const TargetEntry& target, Gdk::DragActionField actions);

void drag_source_set(Gdk::ModifierTypeField start_button_mask, const std::vector<TargetEntry>& targets, Gdk::DragActionField actions);

The start_button_mask defines which mouse button has to be pressed to start the drag action. It can be one of the values in the Gdk::ModifierType enumeration but only the following values are useful:
The action argument can be one or more values from the Gdk::DragAction enumeration above, OR'd together. The first method is used to define a single target. The second method is used to define multiple targets.

To remove drag-and-drop functionality from a source widget call:

void drag_source_unset();

A custom drag icon can be used to represent the data being dragged. To set the drag icon for the source widget call one of the following Gtk::Widget methods:

void drag_source_set_icon(Gdk::Colormap *colormap, Gdk::Pixmap *pixmap, Gdk::Bitmap *mask);

void drag_source_set_icon_pixbuf(Gdk::Pixbuf& pixbuf);

void drag_source_set_icon_stock(const char *stock_id);

To receive notification that a drag-and-drop operation has started and ended you can either connect to the drag_begin and drag_end signals or derive your source widget from an existing widget and override its virtual on_drag_begin() and on_drag_end() handlers.

You can connect to the drag_begin and drag_end signals like this:

widget->sig_drag_begin().connect(slot(this, &MyClass::drag_begin_handler));

widget->sig_drag_end().connect(slot(this, &MyClass::drag_end_handler));

where the drag_begin_handler and the drag_end_handler have the following prototypes:

void MyClass::drag_begin_handler(GdkDragContext *context);

void MyClass::drag_end_handler(GdkDragContext *context);

The virtual on_drag_begin() and on_drag_end() handlers have the same prototypes, except context is passed as a reference to a Gdk::DragContext wrapper.

After a valid drop the data of the first target supported by the destination widget is requested from the source widget. To provide the data that corresponds to the requested target you will either have to connect to the drag_data_get signal or derive your source widget from an existing widget and override its virtual on_drag_data_get() handler.

You can connect to the drag_data_get signal like this:

widget->sig_drag_data_get().connect(slot(this, &MyClass::drag_data_get_handler));

where the drag_data_get_handler has the following prototype:

void MyClass::drag_data_get_handler(GdkDragContext *context, GtkSelectionData *data, unsigned int info, unsigned int time);

The virtual on_drag_data_get() handler has the same prototype, except it passes context as a reference to a Gdk::DragContext wrapper and data as a reference to a Gtk::SelectionData wrapper. info is the unique id that was set when the target was constructed and time is the time the request occurred.

To provide the data you will need to fill in the fields of the GtkSelectionData structure by calling gtk_selection_data_set(). Alternatively, you can set the Gtk::SelectionData fields by calling the following Gtk::SelectionData method:

void set(Gdk::Atom type, int format, const void *data, int length);

The type is an atom that describes the data provided and the format is the number of bits required to store one element of the target type. Usually it will be 8 for a character or 32 for an integer. The most commonly used target types are: ATOM, COMPOUND_TEXT, INTEGER and STRING. This method takes care of properly making a copy of the data so that you don't have to worry about keeping it around.

During a drag-and-drop operation the source widget is sent the following signals:

Drag and Drop Example

The drag-and-drop example creates a window with three buttons, one drag source button and two drag destination buttons. The example is based on the drag-and-drop example in Donna Martin's book "GTK+ Programming in 21 Days".

Drag and Drop Example

Run the example from a shell. The drag source button registers two target types "text/html" and "STRING". When the source button receives the drag_data_get signal it fills the Gtk::SelectionData data field with the string "It's now" followed by the current date and time. Depending on the requested target the string is either formatted as html text or plain text.

Each drag destination button registers only one target type. The first button registers the "text/html" target type and the second button registers the "STRING" target type. When a drag-and-drop action occurs over the first button it requests the string data in html format. When a drag-and-drop action occurs over the second button it requests the string data in plain text format. If a successful drop occurs the button label changes to show the time the drop occurred.

You can also try a drag-and-drop action on the gnome-terminal or one of the many html or text editors to see what happens.

The header file for Drag and Drop Example is dnd.h:

#include<inti/main.h>
#include <inti/core.h>
#include <inti/gtk/button.h>


using namespace Inti;

class DestinationButton : public Gtk::Button
{
protected:
    virtual void on_drag_data_received(Gtk::DragContext& context, int x, int y, const Gtk::SelectionData& data,
                                      unsigned int info, unsigned int event_time);
public:
    DestinationButton(const String& label, const Gtk::TargetEntry& entry);
    virtual ~DestinationButton();
};

class SourceButton : public Gtk::Button
{
    static std::vector<Gtk::TargetEntry> target_entries;

    String target_html;
    String target_string;

protected:
    virtual void on_drag_begin(Gtk::DragContext& context);
    virtual void on_drag_end(Gtk::DragContext& context);
    virtual void on_drag_data_get(Gtk::DragContext& context, Gtk::SelectionData& data,
                                 unsigned int info, unsigned int time);

public:
    SourceButton(const String& label);
    virtual ~SourceButton();
};

class Window : public Gtk::Window
{
public:
    Window();
    virtual ~Window();
};

and the source file is dnd.cc:

#include"dnd.h"
#include <inti/gdk/bitmap.h>
#include <inti/gdk/color.h>
#include <time.h>
#include <iostream>


std::vector<Gtk::TargetEntry> SourceButton::target_entries;

enum
{
    //  Define the info fields for the supported targets.
    TEXT_HTML,
    STRING
};

DestinationButton::DestinationButton(const String& label, const Gtk::TargetEntry& entry)
: Gtk::Button(label)
{
    // Set up this button as a drag destination.
    drag_dest_set(Gtk::DEST_DEFAULT_ALL, entry, Gdk::ACTION_COPY);
}

DestinationButton::~DestinationButton()
{
}

void
DestinationButton::on_drag_data_received(Gtk::DragContext& context, int x, int y, const Gtk::SelectionData& data,
                                        unsigned int info, unsigned int event_time)
{
    using namespace std;

    // Get current time
    time_t now = ::time(0);
    struct tm *now_tm = localtime(&now);
    char now_string[10];
    strftime(now_string, sizeof(now_string), "%T", now_tm);

    // Get button id from enum info
    int id = info + 1;
   
    // Set button label to reflect the dropped data
    String label = String::format(" Drag DESTINATION %i received % s data at %s ", id, data.get_target().c_str(), now_string);
    set_label(label);
    cout << "Destination " << id << ": Received data for the '" << data.get_target() << "' target." << endl;

    // Print the dropped data to the standard output
    switch(info)
    {
    case TEXT_HTML:
        cout << "text/html data = '" << data.data() << "'" <<endl;
        break;

    case STRING:
        cout << "STRING data = '" << data.data() << "'" << endl;
        break;
    }
}

SourceButton::SourceButton(const String& label)
: Gtk::Button(label)
{
    // Add text/html and STRING targets to the primary selection.
    target_entries.push_back(Gtk::TargetEntry("text/html", TEXT_HTML, Gtk::TARGET_SAME_APP));
    target_entries.push_back(Gtk::TargetEntry("STRING", STRING));

    // Set up this button as a source for a drag operation
    drag_source_set(Gdk::BUTTON1_MASK, target_entries, Gdk::ACTION_COPY);

    // Create a custom icon to be used for the drags
    Pointer<Gdk::Bitmap> drag_mask;
    Gdk::Pixmap *drag_icon(new Gdk::Pixmap(Gdk::Colormap::get_system(), "gtk.xpm", &drag_mask));
    drag_source_set_icon(Gdk::Colormap::get_system(), drag_icon, drag_mask);
}

SourceButton::~SourceButton()
{
}

void
SourceButton::on_drag_begin(Gtk::DragContext& context)
{
    // Get the current time
    time_t now = time(0);
    char *now_string = ctime(&now);
   
    // Remove terminating new line character.
    now_string[strlen(now_string) - 1] = '\0';

    std::cout << "Source: Drag begun..." << std::endl;

    // Set data strings    for the supported targets
    target_html = String::format("<P>It's now <B>%s</B>.</P>", now_string);
    target_string = String::format("It's now %s.", now_string);
}

void
SourceButton::on_drag_data_get(Gtk::DragContext& context, Gtk::SelectionData& data,
                              unsigned int info, unsigned int time)
{
    std::cout << "Source: Got request for the '" << data.get_target() << "' target." << std::endl;

    // Fill the Gtk::SelectionData with the values to be passed to the requesting destination.
    switch(info)
    {
    case TEXT_HTML :
        data.set(gdk_atom_intern("text/html", TRUE), 8, target_html.c_str(), target_html.size());
        break;

    case STRING :
        data.set(gdk_atom_intern("STRING", TRUE), 8, target_string.c_str(), target_string.size());
        break;
    }
}

void
SourceButton::on_drag_end(Gtk::DragContext& context)
{
    std::cout << "Source: Drag ended!\n" << std::endl;
}

Window::Window()
{
    set_title("Drag and Drop Example");

    Gtk::VBox *vbox = new Gtk::VBox(true, 5);
    vbox->set_border_width(5);
    add(*vbox);

    // Create drag destination button 1
    Gtk::TargetEntry entry("text/html", TEXT_HTML);
    Gtk::Button *button = new DestinationButton(" Drag DESTINATION 1 received nothing ", entry);
    vbox->pack_start(*button);

    // Create drag destination button 2
    entry.set("STRING", STRING);
    button = new DestinationButton(" Drag DESTINATION 2 received nothing ", entry);
    vbox->pack_start(*button);
   
    // Create drag source button
    button = new SourceButton(" Drag SOURCE providing current time ");
    vbox->pack_start(*button);
    vbox->show_all();
}

Window::~Window()
{
}

int main (int argc, char *argv[])
{
    using namespace Main;

    init(&argc, &argv);

    Window window;
    window.sig_destroy().connect(slot(&Inti::Main::quit));
    window.show();

    run();
    return 0;
}




« Managing Selections
Index
Top
Scribble, A Simple Example Drawing Program  »