0% found this document useful (0 votes)
251 views27 pages

GTK Dragndrop

The document discusses the drag-and-drop (DND) mechanism in GTK+. It provides an overview of DND and how it allows applications to exchange data. It describes the key signals involved in transferring data from the source widget to the destination widget during a DND operation. These include the "drag-drop", "drag-data-get", and "drag-data-received" signals. It also discusses atoms, selections, and targets which provide the underlying support for DND operations.

Uploaded by

Luigi Maggio
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
251 views27 pages

GTK Dragndrop

The document discusses the drag-and-drop (DND) mechanism in GTK+. It provides an overview of DND and how it allows applications to exchange data. It describes the key signals involved in transferring data from the source widget to the destination widget during a DND operation. These include the "drag-drop", "drag-data-get", and "drag-data-received" signals. It also discusses atoms, selections, and targets which provide the underlying support for DND operations.

Uploaded by

Luigi Maggio
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 27

CSci493.

73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism

Prof. Stewart Weiss

The GTK+ Drag-and-Drop Mechanism


1 Overview
Drag-and-drop (

DND, for short) is an operation in applications with graphical user interfaces by which users source of the drag to the destination

can request, in a visual way, that running applications exchange data with each other. To the user, in a drag-and-drop operation, it appears that data is being dragged from the of the drag. Because applications are independent of each other and written without cognizance of who their partners will be in a DND operation, for DND to work, there must be underlying support by either the operating system or the windowing system. The Mac OS operating system has always had built-in support for drag-and-drop. Microsoft Windows did not have it; it was added on top of the operating system in Windows 95 and later. UNIX has no support for it at all because the graphical user interfaces found in UNIX systems are not part of the operating system. Support for DND is provided by the X Window system. Over the years, several dierent protocols were developed to support DND on X Windows. The two most common were protocols. In GTK+, for an application to be capable of DND, it must rst dene and set up the widgets that will participate in it. A widget can be a source and/or a destination for a drag-and-drop operation. A source widget is one that can provide drag data, so that the user can drag something o of it. A destination widget is one that can receive drag data. Destination widgets can limit from whom they will accept drag data., e.g. the same application or any application (including itself ). Destination widgets can also dene the types of data that they are willing to receive. In GTK+, DND is made possible through the use of the signals emitted by widgets, i.e., the signals dened for the

Xdnd and Motif DND. GTK+ can perform drag-and-drop on top of both the Xdnd and Motif

GtkWidget base class.

There are several signals that are emitted during the various stages of a DND Understanding

operation. destination.

The most fundamental ones are those involved in the transfer of data from the source to the This is, after all, the whole point of DND  to make this transfer happen.

how this transfer happens and the role that signals play in carrying it out is crucial to being able to write programs that use drag-and-drop. Therefore, we begin by describing the steps involved in the transfer. The actual transfer begins when the user, having started a drag and holding the mouse button down, releases it over a potential destination widget.

1. At this moment a drag-drop signal is emitted on this widget. 2. The drag-drop signal causes two simultaneous events: (a) If no errors occurred, a drag-data-get signal is emitted on the source widget. This signal is in essence a request for data from the source. (b) If a handler for the drag-drop signal was connected to the destination widget, then that handler runs. 3. In response to drag-drop signal, the destination widget has to indicate that it wishes to receive data from the source of the drag; it does this by calling

gtk_drag_get_data().

4. If a handler for the drag-data-get signal was connected to the source widget, when the source receives this signal, it must deliver the data. It does this by calling copies the data into a

GtkSelection

gtk_selection_data_set().

This function

object.

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


5. When the source widget has copied its data into a data-received signal to be emitted on the destination widget .

Prof. Stewart Weiss


GtkSelection object,
1
GTK arranges for a drag-

6. If a handler for the drag-data-received signal was connected to the destination widget, that handler's argument includes the selection object containing the data, and the destination can copy the data out of the object into its own variables. This may seem complicated on rst reading, but the basic idea is that the communication between the source and destination takes place through a third entity, GTK+'s destination know the others identity. GTK+ provides a way to do very basic DND without diving into the complexity of the topic, if you are willing to accept default behaviors and do not need to drop many dierent types of data. On the other hand, if you want to do things such as customizing the drag icon on the start of a drag, deciding whether or not to accept drag data depending no cursor position, or deciding what type of data to accept based on the cursor's position on the destination, highlighting the widget when it is a potential drop site, checking error conditions, and so on, then you need to learn how to use a larger portion of the API. These notes describe how to do many of these tasks. The remaining sections provide the background and detailed information required to implement drag-and-drop in elementary and more advanced ways. We begin by covering background material. detail below, and is made possible by the underlying DND protocol. Notice that neither the source nor the

selection mechanism, which will be explained in

2
2.1
A

Background
Atoms
is a fundamental type in GDK, its signicance arising from the fact that it is an ecient way to

GdkAtom

represent large chunks of data. Windows under X can have any number of associated properties attached to them. Properties in general are arbitrary chunks of data identied by atoms. In X, an atom is a numeric index into a string table on the X server. They are used to transfer strings eciently between clients without having to transfer the entire string. atom. Every property has an associated format, which is an integer describing how many bits are in each unit of data inside the property. It must be 8, 16, or 32. For example, if a property is a chunk of character data, then its format value would be 8, the number of bits in a character. If it is an integer, its format would be 32. GDK provides functions for manipulating atoms. These will be needed when implementing drag-and-drop in GTK+. Your application will need to string is A property has an associated type, which is also identied using an

intern various strings.

To

intern a string means to store it in an

internal table used by GDK and obtain an atom that identies it for later access. The function to intern a

GdkAtom

gdk_atom_intern

( const gchar *atom_name, gboolean only_if_exists);

This makes a copy of the string to be interned, i.e., the name of the atom, and returns an atom for that string. The second argument is ignored by GDK. If the string already exists, it returns its atom. If it does not, it creates a new atom. The inverse function is

1 Actually,

when the widget called

gtk_drag_get_data(),

that function caused emission of a signal on the selection object.

The selection object's signal handler for that signal was synchronized by the underlying DND protocol (e.g. X11) and when the data was actually made available in the selection object, the selection object emitted the drag-data-received signal on the destination widget.

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


gchar * gdk_atom_name g_free() (

Prof. Stewart Weiss


GdkAtom atom);
Your

Given an atom, this returns a newly-allocated string containing the string corresponding to atom. application must release the memory with when it is nished with it.

You will not need to use any of the other functions related to atoms for DND.

2.2
The

Selections

selection mechanism provides the basis for dierent types of communication between processes. In particGtkClipboard work because of the selection mechanism.
The

ular, drag-and-drop and the and-drop, the term

GtkSelectionData

object is used to store a chunk of data along with the data type and other associated information. In drap-

selection refers to the choice of data that is supplied and/or received by a widget. When

a drop is made on a widget, there may be several dierent types of data in the selection object that is provided to it; the widget has to decide which type of data it wants to accept. Therefore, one says that the widget The

selects a particular chunk of data.

The source will use one of various methods in the

GtkSelectionData object acts like the medium of transport between drag sources and drag destinations. GtkSelectionData class to describe the types of data that

it oers, and the destination widgets will use methods to search the selection for data types that interest them. When data is actually transferred, the selection object will be used as the intermediary between the two widgets.

2.3

Targets

The word target is a bit misleading in the context of DND. Although it sounds like it means the target of a drop, it does not. The word destination refers to this widget. To avoid any confusion, we will

never use

the word target to mean the destination. A target is a type of data to be used in a DND operation. For example, a widget can supply a string target, an image target, or a numeric target. Targets are represented by the The

GtkTargetEntry

structure.

GtkTargetEntry

structure represents a single type of data than can be supplied by a widget for a

selection or received by a destination widget in a drag-and-drop operation. It consists of three members: (1) target can be dropped, and

target, a string representing the type of data in a drag, (2) flags, a set of bits dening limits on where the info, an application assigned integer ID. typedef struct { gchar *target; guint flags; guint info; } GtkTargetEntry;

The

target

string is provides a human-understandable description of the data type. It is important to use

common sense target names, because if your application will accept drags or oer data to other applications, the names you choose should be those other applications might use also. The and comparisons. The the target in the functions that access and manipulate target data, because integers allow for faster look-ups

info member serves to identify

flags

value may be one of the following: The target will only be selected for drags within a single application. The target will only be selected for drags within a single widget.

GTK_TARGET_SAME_APP

GTK_TARGET_SAME_WIDGET GTK_TARGET_OTHER_APP

The target will not be selected for drags within a single application. 3

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


GTK_TARGET_OTHER_WIDGET
If

Prof. Stewart Weiss

The target will not be selected for drags withing a single widget.

flags

== 0, it means there are no constraints.

Usually you would create an enumeration within the application to provide meaningful names for the values, for example:

info

typedef enum { TEXT_HTML, STRING, IMAGE_JPEG, NUMBER, TEXT_URI, N_TARGETS } target_info;


Using this enumeration we could dene a few dierent targets as follows:

GtkTargetEntry string_target = {"string_data", 0, STRING}; GtkTargetEntry html_target = {"text/html", GTK_TARGET_SAME_APP, TEXT_HTML}; GtkTargetEntry image_target = {"image/jpeg", GTK_TARGET_SAME_WIDGET, IMAGE_JPEG};
The

string_target

and the

html_target

both represent text, but the latter would identify itself to a Such a

html_target rather than the string_target. The image_target could be used for JPEG image formats. The string target has no ags and therefore no limits on where it can be dropped. The html_target is only allowed to be dropped into the same application as the source widget, and the image_target is constrained to be dropped into the same widget.
widget would probably select the

destination widget was capable of parsing the HTML and preferred receiving it over plain text.

2.4
A

Target Tables and Target Lists


There is no object specically declared to be a target Target tables are useful in application

target table is an array of type GtkTargetEntry.


gtk_drag_source_set(),

table.

It is just understood that it is an array of target entries.

code for consolidating target entry denitions. a DND source widget,

More importantly, the function that sets up a widget as requires the set of targets to be passed to it as a table.

Target tables can also be passed as arguments to certain other functions related to following is an example of a target table:

GtkSelectionData.

The

GtkTargetEntry target_entries[] = { {"text/html", 0, TEXT_HTML }, {"STRING", 0, STRING}, {"number", 0, NUMBER}, {"image/jpeg", 0, IMAGE_JPEG}, {"text/uri-list", 0, TEXT_URI} };
A

target list is not a list of GtkTargetEntry structures, as you might expect. It is a list of GtkTargetPair
GtkTargetPair is a internal data structure

structures, and it serves a dierent purpose from target tables. A used by GTK+. It is dened by

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


struct GtkTargetPair { GdkAtom target; guint flags; guint info; };
Notice that it diers from a

Prof. Stewart Weiss

GtkTargetEntry

in a single respect: it uses a

string to identify the target. Recall from Section 2.1 above that a is interned.

GdkAtom

GdkAtom

instead of a character

is an integer that GDK uses to

represent a string internally; it is the index into an array of strings. An atom is only dened when a string

The functions that take a GtkTargetEntry and store that target for later use intern the character string and create an atom for it. Once this has been done, that target can be represented by a GtkTargetPair. In other words, the target atom in the

GtkTargetEntry.

GtkTargetPair

represents a target that has already been dened in some

Because atoms make for faster comparison and identication and save storage space, target lists are more ecient than target tables and are used more extensively than them by GTK+. There are methods in the

GtkSelectionData class for going back and forth between target table and target list representations of the
targets. For example:

gtk_target_list_new()

creates a target list from a target table prepends a target table to an existing target list creates a target table that contains the same targets as the given list.

gtk_target_list_add_table()

gtk_target_table_new_from_list()
Many of the methods provided by the

GtkSelectionData

class expect and manipulate target lists. They

are of fundamental importance in using drag-and-drop, and we will have more to say about them below.

Signals Involved in Drag-and-Drop

Various signals come into play during a DND operation. Some are essential to handle and others are not. All signals are emitted on GTK+ widgets and their descriptions can be found in the API documentation of the

GtkWidget

class. The following table lists all of these signals, indicating whether it is emitted on the

course or the destination, and what its purpose is.

Signal
drag-begin-event drag-motion drag-drop drag-data-get drag-data-received drag-data-delete drag-end-event drag-failed drag-leave

Widget
source destination destination source destination source source source destination

Purpose
noties source that drag started noties destination about drag pointer motion noties destination that data has been dropped request for drag data from source source has sent target the requested data source should/can delete data noties source that drag is done noties source that drag failed noties destination that cursor has left widget

3.1

The Typical Sequence of Events

The sequence of events that take place in a drag-and-drop is well-dened. The typical sequence is described below. Under certain conditions there will be slight deviations from it.

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


that moment, the drag-begin-event signal is emitted on the source.

Prof. Stewart Weiss

1. Everything begins when the user presses the mouse button over a source widget and starts a drag. At

2. When the mouse is on top of a destination widget, the drag-motion signal is emitted on that widget. This signal can be connected to the destination for various reasons. For one, you can use it to highlight the widget when the cursor is over it and the drag format and action are acceptable to the widget. For another, if only certain parts of the widget are drop zones, the handler is needed in order to determine whether the cursor is in a drop zone or not. If it is not in a drop zone, the handler should return

FALSE and take no other gdk_drag_status() and

action. Otherwise, it should display visual feedback to the user by calling return

TRUE.

Sometimes a drag-motion handler cannot decide whether the

oered data is acceptable from the cursor position and data type, and must actually examine the data to know. In this case it will do the work typically done in a drag-drop handler. The details about this and other issues to be handled in a drag-motion handler are explained below. If when you set up the destination widget using

gtk_drag_dest_set(), you set any GTK_DEST_DEFAULT_DROP, GTK_DEST_DEFAULT_MOTION or GTK_DEST_DEFAULT_ALL on the


functions instead.

of the ags widget, you

will not be able to use the drag-motion signal this way, because GTK+ will handle it with its internal

The drag-motion signal will be delivered to the widget each time that the cursor moves over the widget. If you want to detect when it enters and leaves the widget, you have to make use of the drag-leave signal, which is emitted on a destination widget whenever the cursor leaves it. An entry event takes place when it is the rst drag-motion signal to be received after a drag-leave or the rst one to be received. The handlers for the two signals can be coded to detect these conditions. 3. When the user releases the mouse button over the destination, the drag-drop signal is emitted on the destination widget. This signal should be connected to a signal handler whose primary objective is to determine whether the cursor position is in a drop zone or not, and if it is, to issue a request for the data from the source by calling zone, it should return

FALSE

gtk_drag_get_data()

and return

TRUE.

If the cursor is not in a drop

and take no other action.

4. When the destination issues a request for the source's data, whether in the drag-drop handler or the drag-motion handler, the drag-data-get signal will be emitted on the source. A handler for this signal should be connected to the signal. This handler is responsible for packing up the data and setting it into a selection object that will be available to the destination. 5. Once the source widget's drag-data-get handler has returned, the drag-data-received signal will be emitted on the destination. This signal should be connected to a signal handler on the destination widget. If the data was received in order to determine whether the drop will be accepted (as when the drag-motion handler requested the data), the handler has to call to retrieve the data from the selection object and then call move (the

gdk_drag_status()

and not nish

the drag. In most cases, if the data was received in response to a "drag-drop" signal, the handler has

gtk_drag_finish(). If the drag was a GdkDragAction was set to GDK_ACTION_MOVE in the source or destination), then in the call to gtk_drag_finish() it needs to pass a ag indicating that the data should be deleted in the source. gtk_drag_finish()
causes a drag-end signal to be emitted on the source widget. It can connect a handler to this signal to do any post-processing needed after the drag. It will also cause a drag-data-delete signal to be emitted on the source if the destination passed the ag when it called

6. The call to

gtk_drag_finish().

The source has to delete the data in its handler for this signal.

This normal sequence might not be followed if there was a failure at some point. In this case, the drag-failed signal will be emitted on the source. A handler can be attached to the source to deal with the failure, such as by logging a message.

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


4 Setting Up a Source Widget

Prof. Stewart Weiss

A widget is set up as a source widget for drag operations by calling the function on it. The prototype is

gtk_drag_source_set()

void

gtk_drag_source_set

( GtkWidget *widget, GdkModifierType start_button_mask, const GtkTargetEntry *targets, gint n_targets, GdkDragAction actions);
The remaining arguments have the following

The rst argument, meaning.

widget, is the widget to be the drag source.

start_button_mask targets n_targets actions

the bitmask of buttons that can start the drag, of type

GdkModifierType.

the table of targets that the drag will support, which may be the number of items in

NULL.

targets.

the bitmask of possible actions for a drag from this widget.

The values of the

GdkModifierType enumeration are listed in the API documentation for the GdkWindow. GDK_BUTTON1_MASK, GDK_BUTTON2_MASK, and so on. In addition, you can bitwise-or modiers such as GDK_CONTROL_MASK and GDK_SHIFT_MASK into the mask. Usually you should just set the mask to be GDK_BUTTON1_MASK.
The values have names such as Note. The API documentation for this function states that the widget must have a window. I have used no-window widgets with success, and the source code for the function in GTK+-2.24 does not check whether the widget has a window. You need to decide what types of data the widget will supply. Usually this is a simple matter; it has text, or perhaps images, or perhaps it has its own application-specic data chunks. The target table should be dened as described in Sections 2.3 and 2.4. follow. We will use the following target table in the examples that

GtkTargetEntry target_entries[] = { {"text/html", 0, TEXT_HTML }, {"STRING", 0, STRING}, {"number", 0, NUMBER}, {"image/jpeg", 0, IMAGE_JPEG}, {"text/uri-list", 0, TEXT_URI} };
The text/uri-list target is commonly used to drag links and lenames between applications. You also need to connect the signals that you want to handle to the source widget.

4.1

Example

The following boilerplate listing demonstrates the basic steps in setting up a source widget.

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


void { setup_source_widget ( GtkWidget

Prof. Stewart Weiss


source_widget
)

GtkTargetEntry

target_entries [ ] 0, 0, 0, 0, ", 0,

{ },

{" t e x t / html " , { "STRING " , { " number " , {" image / j p e g " , {" t e x t / u r i };

T E X T _ H T M L STRING } , N U M B E R} ,

IMAGE_JPEG} , TEXT_URI}

l i s t

Make

this

drag

source

offering

all

of

the

targets

listed

above

gtk_drag_source_set ( source_widget , G D K _ B U T T O N 1 _ M A S K, target_entries , G _ N _ E L E M E N T S ( target_entries ) , | G D K _ A C T I O N _ M O V E) ;

G D K _ A C T I O N _ C O P Y

Connect

it

to

all

signals

that

the

source

should

handle .

g _ s i g n a l _ c o n n e c t (G_OBJECT( s o u r c e _ w i d g e t ) , " drag_begin " , G _ C A L L B A C K NULL ) ; ( on_drag_begin ) ,

g _ s i g n a l _ c o n n e c t (G_OBJECT( s o u r c e _ w i d g e t ) , " drag_data_get " , G _ C A L L B A C K NULL ) ; ( on_drag_data_get ) ,

g _ s i g n a l _ c o n n e c t (G_OBJECT( s o u r c e _ w i d g e t ) , " drag_end " , G _ C A L L B A C K NULL ) ; } ( on_drag_end ) ,

This widget will oer all of the dierent target types listed in

target_entries.

It also supports copying

and moving of drag data. The only dierence is that, on a move, the source has to delete the original data. We will need to write the three handlers,

on_drag_begin(), on_drag_data_get(),

and

on_drag_end().

These handlers depend upon the specic widget and what types of data it supplies. First let us see how to set up a destination widget, after which we will describe and give examples of handlers for all of the relevant signals.

Setting Up a Destination Widget


gtk_drag_dest_set(),
whose prototype is

A widget is set up as a destination widget by calling

void

gtk_drag_dest_set

( GtkWidget *widget, GtkDestDefaults flags, const GtkTargetEntry *targets, gint n_targets, GdkDragAction actions);

The rst argument, following meaning.

widget,

is the widget to be the drag destination. The remaining arguments have the

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


flags targets n_targets actions
The the default drag behaviors to use the table of targets that the destination will accept, or the number of items in

Prof. Stewart Weiss

NULL

for none.

targets.

the bitmask of possible actions for a drop onto this widget.

flags

argument can be used to specify default behaviors for this widget. The values make it possible

to write a very simple destination widget if you are willing to accept default behaviors for it. If it is set to 0, there will be no defaults, and you will have to write handlers for all of the possible signals. The possible values for the

flags

argument as as follows:

GTK_DEST_DEFAULT_MOTION

If set for a widget, GTK+, during a drag over this widget will check if the drag

matches this widget's list of possible targets and actions, and will call

gdk_drag_status()

as

appropriate. If you set this ag, you will not need to write a handler for the drag-motion signal; GTK+ will supply a default handler. Conversely, you should not set this ag if you do connect your own handler, because unless you really know what you are doing, there will be unpredictable results.

GTK_DEST_DEFAULT_HIGHLIGHT

If set for a widget, GTK+ 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. If you set this ag, then you will not need to do the highlighting yourself in the drag-motion handler, and if you do the highlighting there, then you should not set this ag.

GTK_DEST_DEFAULT_DROP

If set for a widget, when a drop occurs, GTK+ will check if the drag matches this

widget's list of possible targets and actions. behalf of the widget and it will also the appropriate values to are handled correctly. signal.

If so, GTK+ will call gtk_drag_get_data() on gtk_drag_finish(). GTK+ will also take care of passing gtk_drag_finish() to make sure that move actions and copy actions

If you set this ag, you will have to know what you are doing in your

own custom drag-motion handler, and you will not need to write a handler for the drag-drop

GTK_DEST_DEFAULT_ALL

If set, all of the above default actions will be taken. In this case, you will only need

to write a handler for the drag-data-receive signal.

The destination does not have to accept the exact set of targets oered by the source. It might be a subset, or a superset, or it may even be unrelated, depending on the nature of your application. If DND is being used strictly to allow drags within your application, you may want to place the target table denition in a header le that can be included by all widgets, so that destination and source widgets share the same target names and info values.

5.1

Example

A listing of a set up of a very simple destination widget follows.


void { setup_dest_button ( GtkWidget

dest_widget

Allow

two

different

types

of

text = }, {

GtkTargetEntry

text_targets [ ] 0, 0, T E X T _ H T M L STRING}

{" t e x t / html " , { "STRING " , };

gtk_drag_dest_set (

dest_button ,

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


0, text_targets , 0, G D K _ A C T I O N _ C O P Y) ;

Prof. Stewart Weiss

Connect drop drag

this

widget might

to

all

of

the are , and

signals four drag of

that them : .

potential drag

widget ,

emit .

There

m o t i o n

d r o p

drag

d a t a r e c e i v e d

l e a v e

/
g _ s i g n a l _ c o n n e c t (G_OBJECT( d e s t _ w i d g e t ) , " drag_data_received " , G _ C A L L B A C K NULL ) ; ( on_drag_data_received ) ,

g _ s i g n a l _ c o n n e c t (G_OBJECT( d e s t _ w i d g e t ) , " drag_drop " , G _ C A L L B A C K NULL ) ; ( on_drag_drop ) ,

g _ s i g n a l _ c o n n e c t (G_OBJECT( d e s t _ w i d g e t ) , " drag_motion " , G _ C A L L B A C K NULL ) ; ( on_drag_motion ) ,

g _ s i g n a l _ c o n n e c t (G_OBJECT( d e s t _ w i d g e t ) , " drag_leave " , G _ C A L L B A C K NULL ) ; } ( on_drag_leave ) ,

5.2

Adding Targets to the Destination

NULL target table and then call gtk_drag_dest_set_target_list() to set the target list for the destination. The advantage of doing this is
ited use. It is often more convenient to create the destination with a add all image targets to a target list, or all text targets. The prototype of is

Although it is often sucient to use

gtk_drag_dest_set()

to set up a destination widget, it is of lim-

that there are several functions for adding classes of targets to target lists. For example, there is a function to

gtk_drag_dest_set_target_list()

void

gtk_drag_dest_set_target_list

( GtkWidget *widget, GtkTargetList *target_list);

which is given the widget and a target list, not a target table. You can create a target list from a target table with

GtkTargetList *gtk_target_list_new

( const GtkTargetEntry *targets, guint ntargets);

which is given the table and returns a newly-allocated target list. If you call it as

target_list = gtk_target_list_new(NULL, 0);


you will have an initially empty target list. You can prepend a target table to an existing target list with 10

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


void gtk_target_list_add_table

Prof. Stewart Weiss


( GtkTargetList *list, const GtkTargetEntry *targets, guint ntargets);

and you can append a single target to a target list with

void

gtk_target_list_add

( GtkTargetList *list, GdkAtom target, guint flags, guint info);

This function requires the target's atom.

gdk_atom_intern() to get an atom for it. The flags value is the same as you would use in the target table, and the info value is the integer by which you want to refer to this target elsewhere in the application.
To add all image targets that are supported by the system's

This implies that you must have rst interned the target with

GtkSelectionData

object, use

void

gtk_target_list_add_image_targets ( GtkTargetList *list, guint info, gboolean writable);


is the integer by which you will refer to all image targets, and

where

TRUE,

info

limits the image types to those that can be written, and when

writable is a ag that FALSE, allows all image types,

when those

can can be read only as well as written. You may refer to the API documentation for the other similar functions:

gtk_target_list_add_uri_targets(),

and

gtk_target_list_add_text_targets(), gtk_target_list_add_rich_text_targets().

The order in which the targets occur in the target list is important. When the drop occurs, the widget will need to decide which of the targets to accept. The simplest function for this purpose will traverse the target list from front to back looking for the rst target that satises the criteria. choosing that do not depend on order, but they will take more work. The following listing shows how to set up a it can load the image from the le. Listing 1: setup_drawing_area
void { / s e t u p _ d r a w i n g _ a r e a ( GtkWidget

There are other methods of

GtkDrawingArea

widget to accept drops of any image format

and the URIs. It might be interested in accepting the drop of a URI in case it is an image le, in which case

drawing_area
from an empty

Create

an

empty

target

list =

target

table 0);

GtkTargetList

target_list
image

g t k _ t a r g e t _ l i s t _ n e w (NULL,

Add

all

supported

targets is an

to

the

list defined this in same the TargetInfo value .

The I M A G E _ T A R G E T enumeration . All

argument image

integer will

formats

have

info

/
gtk_target_list_add_image_targets ( t a r g e t _ l i s t , IMAGE_TARGET, FALSE ) ;

Add so

supported that

text / uri is

targets . given to

These actual

are

appended

to

the

list

preference

image

formats .

/
gtk_target_list_add_uri_targets ( t a r g e t _ l i s t , TEXT_URI ) ;

gtk_drag_dest_set (

drawing_area , 0,

11

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


NULL, 0, G D K _ A C T I O N _ C O P Y) ; // empty target table

Prof. Stewart Weiss

Add

the

target

list

to

the

widget

/
target_list );

g t k _ d r a g _ d e s t _ s e t _ t a r g e t _ l i s t ( drawing_area ,

Initialize

pixbuf

pointer

to

NULL

to

indicate " pixbuf " ,

there

is

no

image

g _ o b j e c t _ s e t _ d a t a (G_OBJECT( d r a w i n g _ a r e a ) ,

NULL ) ;

Connect

expose

event

handler

g _ s i g n a l _ c o n n e c t (G_OBJECT( d r a w i n g _ a r e a ) , " expose

e v e n t " ,
( on_expose ) ,

G _ C A L L B A C K NULL ) ;

Connect

handlers

for

remaining

signals

g _ s i g n a l _ c o n n e c t (G_OBJECT( d r a w i n g _ a r e a ) , " drag_data_received " , G _ C A L L B A C K NULL ) ; g _ s i g n a l _ c o n n e c t (G_OBJECT( d r a w i n g _ a r e a ) , " drag_drop " , G _ C A L L B A C K NULL ) ; g _ s i g n a l _ c o n n e c t (G_OBJECT( d r a w i n g _ a r e a ) , " drag_motion " , G _ C A L L B A C K NULL ) ; g _ s i g n a l _ c o n n e c t (G_OBJECT( d r a w i n g _ a r e a ) , " drag_leave " , G _ C A L L B A C K NULL ) ; } ( on_da_drag_leave ) , ( on_da_drag_motion ) , ( on_da_drag_drop ) , ( on_da_drag_data_received ) ,

Signal Handlers in Depth

We will now show how to set up signal handlers for the source and destination widgets. To make things concrete, we will demonstrate this with a specic, but contrived example. We will create a single source widget: a button that can oer four types of target data: plain text, marked-up text, numbers, and an image. There will be four destination widgets: three buttons, one accepting numbers, one accepting plain text, and one accepting marked-up text, and a drawing area that can accept image data and text/uri-lists. The widgets will all get packed into a top-level window so that the user can drag from the source onto any of the other widgets. Because the only data that a button has is its label, we will attach dierent types of data to the button as a

GObject,

so that it can supply data to destination widgets when they request it.

The destination buttons will reect their receipt of data by changing their labels to show the new data. The drawing area will display the images that it receives.

6.1

The Source Widget

The application's source button is set up with a call to the work is in the function

setup_source_button(),

shown in Listing 2 . This

gets the current time and save it in a local variable. It passes its value to the drag-begin handler. Most of

make_drag_source(),

shown in Listing 3.

12

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


Listing 2: setup_source_button()
void { time_t start_time ; = t i m e (NULL ) ; setup_source_button ( GtkWidget

Prof. Stewart Weiss

source_button

start_time

Convert

the

button

into

drag

source

make_drag_source ( s o u r c e _ b u t t o n ) ;

g _ s i g n a l _ c o n n e c t (G_OBJECT( s o u r c e _ b u t t o n ) , " drag_begin " , G _ C A L L B A C K ( on_begin_drag ) ,

GUINT_TO_POINTER( s t a r t _ t i m e ) ) ;

g _ s i g n a l _ c o n n e c t (G_OBJECT( s o u r c e _ b u t t o n ) , " drag_data_get " , G _ C A L L B A C K NULL ) ; ( on_drag_data_get ) ,

g _ s i g n a l _ c o n n e c t (G_OBJECT( s o u r c e _ b u t t o n ) , " drag_end " , G _ C A L L B A C K NULL ) ; } ( on_end_drag ) ,

The function

make_drag_source() begins by dening the targets it will oer.

The number target is limited

to those widgets in the same application just to demonstrate what happens when it is dragged to another application when this ag is set. The function loads two pixbufs from hard-coded lenames and sets pointer to them as data in the button, so that it can deliver images when requested. The lines

g_object_set_data(G_OBJECT(source_button), "targetlist", (gpointer) target_entries); g_object_set_data(G_OBJECT(source_button), "ntargets", GUINT_TO_POINTER(G_N_ELEMENTS(target_entries)));


set the target table and its length as data in the button object so that these will be available to other callback functions that need access to the oered targets (the drag-begin handler). Listing 3: make_drag_source()
void { GError GdkPixbuf GdkPixbuf make_drag_source ( GtkWidget

source_button )

e r r o r = NULL ; p i x b u f = NULL ; p i x b u f 2 = NULL ;


the target this data is types that on this widget can deliver

/ /

Define Even set I

/
is deleted .

though on the

declared it is

the

stack , counted

when and

the does

pointer not get

object , by

reference

was

surprised

this .

/
= { T E X T _ H T M L STRING } , N U M B E R} , IMAGE_JPEG} , TEXT_URI} },

GtkTargetEntry

target_entries [ ] 0, 0,

{" t e x t / html " , { "STRING " , { " number " , {" image / j p e g " , {" t e x t / u r i };

G T K _ T A R G E T _ S A M E _ A P P, 0, ", 0,

l i s t

13

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


gtk_drag_source_set ( source_button , G D K _ B U T T O N 1 _ M A S K, target_entries , G _ N _ E L E M E N T S ( target_entries ) ,

Prof. Stewart Weiss

G D K _ A C T I O N _ C O P Y|G D K _ A C T I O N _ M O V E) ;

Create =

pixbuf

from

file

data

and (

error

c h e c k /
&e r r o r );

pixbuf if { (

gdk_pixbuf_new_from_file != NULL)

B U T T O N _ I M A G E _ P A T H,

error

g_print (" F a i l e d

to

load

image

file :

%s \ n " ,

error

>m e s s a g e ) ;

g_error_free ( e r r o r ) ; error } = NULL ;

Create =

second

pixbuf

from

file

data (

and

error

c h e c k /
&e r r o r );

pixbuf2 if { (

gdk_pixbuf_new_from_file != NULL)

BUTTON_IMAGE2_PATH,

error

g_print (" F a i l e d

to

load

image

file :

%s \ n " ,

error

>m e s s a g e ) ;

g_error_free ( e r r o r ) ; error } = NULL ;

Attach

the

images

to

the

widget

so

that

it

them

to

deliver

g _ o b j e c t _ s e t _ d a t a (G_OBJECT( s o u r c e _ b u t t o n ) , " image " , ( gpointer ) pixbuf ) ;

g _ o b j e c t _ s e t _ d a t a (G_OBJECT( s o u r c e _ b u t t o n ) , " image2 " , ( gpointer ) pixbuf2 ) ;

Attach so that

the the

targetlist callbacks

pointer can

and

the

length the

of

the

array

to

the

object

reconstruct

array .

g _ o b j e c t _ s e t _ d a t a (G_OBJECT( s o u r c e _ b u t t o n ) , ( gpointer )

" targetlist ",

target_entries ); " ntargets " ,

g _ o b j e c t _ s e t _ d a t a (G_OBJECT( s o u r c e _ b u t t o n ) ,

GUINT_TO_POINTER(G _ N _ E L E M E N T S( t a r g e t _ e n t r i e s ) ) ) ; }

The drag-begin signal handler must have the following prototype:

void

user_function

( GtkWidget *widget, GdkDragContext *drag_context, gpointer user_data);


structure. This structure will be passed to various

The

drag_context

is a pointer to a

GdkDragContext

callback functions during the drag operation. structure. It contains

Both the source and the destination have a drag context

struct GdkDragContext { GObject parent_instance; GdkDragProtocol GSEAL (protocol); gboolean GSEAL (is_source); GdkWindow *GSEAL (source_window); GdkWindow *GSEAL (dest_window); GList *GSEAL (targets); GdkDragAction GSEAL (actions);
14

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


GdkDragAction GSEAL (suggested_action); GdkDragAction GSEAL (action); guint32 GSEAL (start_time);

Prof. Stewart Weiss

};

Notice that it contains references to the source and destination windows, actions and suggested actions, a list of targets, the start time, and the protocol and parent. The details are not so important now. What matters is that you see that this

drag_context

is keeping track of the drag, and will be needed when it

comes time to get targets and to determine actions. The primary reason to have a handler for the drag-begin signal is to set up a custom drag icon. Our

drag-begin handler will do a bit more than this. It will get the current time and compute the time elapsed between when the application started up and the current time. It will save this as the number to be oered to any widget asking for number data. It will also construct more data based on the current time. current time, e.g.: It will create a plain text string stating the

It is now Sun Dec 11 20:57:46 2011


and it will create a Pango marked up string saying the same thing, but with formatting applied. indexed by the It will

create an array of four gpointers that will point to each of the four dierent data chunks that it can oer,

info

members of the target array. In other words, the array is

target_data[TEXT_HTML] target_data[STRING] target_data[NUMBER]

contains a pointer to the marked up text

contains a pointer to the plain text contains the number, cast to a gpointer contains a pointer to the pixbuf

target_data[IMAGE_JPEG]

The handler also creates the drag icon. When the user starts the drag, an image of the plain text string stating what time it is will be the icon. Because the code to create the icon is detailed and distracting, it is listed in the Appendix rather than here. The drag-begin handler is in Listing 4. Listing 4: drag-begin handler
void on_drag_begin ( GtkWidget GdkDragContext gpointer { GtkTargetEntry gpointer guint GdkPixbuf gchar gchar guint guint size_t

source_button dc ,
user_data )

target_entries t a r g e t _ d a t a = NULL ;
start_time

= GPOINTER_TO_UINT( u s e r _ d a t a ) ; = NULL ; = NULL ; = NULL ;

pixbuf html string


which ; length ;

elapsed_time ;

time_t gchar length =

now

t i m e (NULL ) ; = c t i m e (&now ) ;

/ /

now_string 1]

Get

the

current it to a

time

/ / /

Convert

string

s t r l e n ( now_string ) ; = '\0 '; / remove ending newline

now_string [ length

get

target

entry

data

/
15

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


target_entries = ( GtkTargetEntry

Prof. Stewart Weiss

g_object_get_data ( G_OBJECT( s o u r c e _ b u t t o n ) , " t a r g e t l i s t " ) ;

guint

ntargets

= GPOINTER_TO_UINT

( g_object_get_data ( G_OBJECT( s o u r c e _ b u t t o n ) , " n t a r g e t s " ) ) ;

pick

random

number

using

current

time

as

seed

s r a n d ( now ) ; which = rand ( ) ;

Get (

the

pixbuf

image )

data

pointer

/
" image " ) ;

if

which pixbuf

% 2 = = 0 =

g _ o b j e c t _ g e t _ d a t a (G_OBJECT( s o u r c e _ b u t t o n ) ,

else pixbuf = g _ o b j e c t _ g e t _ d a t a (G_OBJECT( s o u r c e _ b u t t o n ) , " image2 " ) ;

//

Compute

the

time

since

the

application

started

elapsed_time

= now

start_time ;

//

Create

and

draw

the

drag

icon dc , now_string ) ;

draw_drag_icon ( s o u r c e _ b u t t o n ,

// //

Allocate on the

the

array

of

pointers object

for

the

target table

data ,

which

is

stored

source

button ' s

properties

target_data

= g_new ( g p o i n t e r ,

ntargets ); " target_data " ,

g _ o b j e c t _ s e t _ d a t a (G_OBJECT( s o u r c e _ b u t t o n ) , target_data ) ;

//

Define =

the

content

of

the

target (

data .

html

g_markup_printf_escaped "< s p a n "< s p a n

f o n t _ f a m i l y =\" m o n o s p a c e \ " w e i g h t =\" b o l d \ "

style

=\" i t a l i c \">The

time

is

"

f o r e g r o u n d =\" b l u e \ " >%s . </ s p a n >"

"</ s p a n >" , now_string ) ; string = g_strconcat (" I t is now ", now_string , ".\n" , NULL ) ;

// //

Make that

a we

copy can

of

the

target it = =

data

and on

store

it

in

the

array

indexed type .

so

retrieve

based

the

TargetInfo

enumerated

t a r g e t _ d a t a [T E X T _ H T M L] t a r g e t _ d a t a [ STRING ] t a r g e t _ d a t a [N U M B E R] if ( pixbuf != NULL )

g_strdup ( html ) ; g_strdup ( s t r i n g ) ;

= GUINT_TO_POINTER( e l a p s e d _ t i m e ) ; { = ( gpointer ) pixbuf ;

t a r g e t _ d a t a [ IMAGE_JPEG ] }

We

have

to but

free

the

two

strings , the

since

we

copied we did

them not

into

the it

target here ,

array , but

cannot

free

pixbuf , to a

since

allocate

just

copied

the

pointer

local .

g _ f r e e ( html ) ; g_free ( s t r i n g ) ; }

The most important signal handler for the source is the drag-data-get handler. When the drop is made on a destination widget, the drag-data-get signal is emitted on the source. The prototype for this handler is

void

user_function

( GtkWidget *widget, GdkDragContext *drag_context, GtkSelectionData *data, guint info,


16

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


guint gpointer

Prof. Stewart Weiss


time, user_data);

The data argument is a pointer to a GtkSelectionData structure that was initialized by GTK+ before emitting the signal. This structure has a member named target which is a pointer to the target requested by the destination. The info member is the number in the target list that corresponds to this target. The responsibility of the handler is to determine the target type and deliver the requested data. Therefore, its rst task is to query the info member for the target data type. It can then copy data into the GtkSelectionData structure using the function

void

gtk_selection_data_set

( GtkSelectionData *selection_data, GdkAtom type, gint format, const guchar *data, gint length);
this

It needs the atom representing the target, the type),

data,

a pointer to the data to be copied, and

format (remember this is the number of bits in a unit of length, the number of units of this target type.

The exception is that pixbuf data is delivered in a dierent way. pixbuf data into a selection structure:

There is a special function that copies

gboolean TRUE

gtk_selection_data_set_pixbuf FALSE

( GtkSelectionData *selection_data, GdkPixbuf *pixbuf);

This returns

if it was successful, and

if it failed.

Our handler for the source button's drag-data-get handler is in Listing 5. The drag context is not used in this function. The important part of the code is the switch statement. It checks the type of data requested and in each case, copies the data into the selection structure. Although we use instructions like

gdk_atom_intern(text/html, TRUE);
to get the atom for the target, it would be simpler to replace these by

selection_data->target
which is the requested atom. This code just shows another way to do the same thing. Listing 5: drag-data-get handler for source button void on_drag_data_get ( GtkWidget

GdkDragContext guint guint gpointer { guint GdkPixbuf gpointer number_data ;

source_button , dc , GtkSelectionData selection_data


info , time , user_data )

p i x b u f = NULL ; target_data =
g _ o b j e c t _ g e t _ d a t a (G_OBJECT( s o u r c e _ b u t t o n ) , 17 " target_data " ) ;

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


/ Use the The the info and argument set up through array index , the to the was so determine which data

Prof. Stewart Weiss

the

destination to the be

has to

requested ,

GtkSelectionData filled we set in such

structure that info

passed value

destination target_data the array is

the

d r a g d a t a r e c e i v e d a way for the There data are

callback . info v a l u e X from functions for

equals The

target_data [X ] . exception image and data . separate supplying pixbuf , uri , u t f 8 d a t a .

/
switch ( i n f o ) { c a s e TEXT_HTML : gtk_selection_data_set ( selection_data , / We c o u l d instead use of getting the interned atom each time selection_data >t a r g e t

/
gdk_atom_intern ( " t e x t / h t m l " , TRUE) , / 8, t a r g e t _ d a t a [TEXT_HTML] , s t r l e n ( t a r g e t _ d a t a [TEXT_HTML ] ) ) ; break ; case STRING : / / / / type format data length gtk_selection_data_set ( selection_data , gdk_atom_intern ( "STRING" , TRUE) , 8, t a r g e t _ d a t a [ STRING ] , s t r l e n ( t a r g e t _ d a t a [ STRING ] ) ) ; break ; case N U M B E R : number_data = GPOINTER_TO_UINT( t a r g e t _ d a t a [N U M B E R] ) ; gtk_selection_data_set ( selection_data , gdk_atom_intern ( " number " , TRUE) , 32 , ( g u c h a r ) &number_data , 1); break ; c a s e IMAGE_JPEG : p i x b u f = t a r g e t _ d a t a [ IMAGE_JPEG ] ; gtk_selection_data_set_pixbuf ( selection_data , pixbuf ) ; break ; } } The source widget has two remaining signals that it should handle. One is drag-end and the other is / / / / type format data length / / / type format data length

/ / / /

/ / / /

/ / / /

drag-delete. In our case there is nothing to do for the drag-delete handler. If we wanted to do a real move instead of a copy, then we would have to delete data in the source widget when the drag nished, but in this example we do not do this. However, the drag-end signal should be handled because we need to de-allocate the strings that we created with the current time. We no longer need them. The prototype for the drag-end handler is

void

user_function

( GtkWidget *widget, GdkDragContext *drag_context,


18

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


gpointer

Prof. Stewart Weiss


user_data);

Our handler does not use the drag context. It does not need to free any resources used by the drag icon because GTK+ takes care of unref-ing the icon's pixmap when the drag nishes. Our handler is in Listing 6. Listing 6: drag-end handler void on_end_drag ( GtkWidget GdkDragContext gpointer { // Retrieve the array of = " target_data " ) ; was set as a pointers to source supplied data gpointer

source_button dc ,
user_data )

target_data

g _ o b j e c t _ g e t _ d a t a (G_OBJECT( s o u r c e _ b u t t o n ) , // // Release drag the memory that was allocated when widget

source

g _ f r e e ( t a r g e t _ d a t a [TEXT_HTML ] ) ; g _ f r e e ( t a r g e t _ d a t a [ STRING ] ) ; g_free ( target_data ) ; // } Set the key t o NULL " t a r g e t _ d a t a " , NULL ) ;

g _ o b j e c t _ s e t _ d a t a (G_OBJECT( s o u r c e _ b u t t o n ) ,

6.2

The Button Destination Widgets

There are four destination widgets: three buttons and one drawing area. The buttons all share the same functions. To make this possible, each button will have an ID number attached to it as user data, and this ID will tell the callbacks which button is the destination widget. There are also more signals to be handled on the destination side. The code to set up the destination buttons is in Listing 7. The button's id is passed as user data to the setup function, and it is then passed as user data to each of the callback functions. Separate target lists are created for the number and text buttons. Listing 7: setup_dest_button() void { GtkTargetList / One button setup_dest_button ( GtkWidget

dest_button

, )

DestinationType

button_type

target_list
will accept

; text targets ; = { N U M B E R} , the other , number data

GtkTargetEntry }; / Allow two

number_targets [ ]

{ " number " , GTK_TARGET_SAME_APP,

different 0, 0,

types

of

text = {

GtkTargetEntry { "STRING" , };

text_targets [ ] STRING}

{" t e x t / html " ,

TEXT_HTML } ,

19

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


gtk_drag_dest_set ( dest_button , 0, NULL, 0, GDK_ACTION_COPY) ; switch { case N U M B E R _ D R O P: ( button_type )

Prof. Stewart Weiss

t a r g e t _ l i s t = gtk_target_list_new ( number_targets , G_N_ELEMENTS( n u m b e r _ t a r g e t s ) ) break ; case case M A R K U P _ D R O P: TEXT_PLAIN_DROP: t a r g e t _ l i s t = gtk_target_list_new ( text_targets , G_N_ELEMENTS( t e x t _ t a r g e t s ) ) break ; default : g_print (" U n i d e n t i f i e d } / Now add the target list created above to the widget button . \ n " ) ; ; ;

gtk_drag_dest_set_target_list ( dest_button , / Connect this widget to all of the

target_list );

signals

g _ s i g n a l _ c o n n e c t (G_OBJECT( d e s t _ b u t t o n ) , " drag_data_received " , G_CALLBACK ( o n _ d b _ d r a g _ d a t a _ r e c e i v e d ) , GUINT_TO_POINTER( b u t t o n _ t y p e ) ) ; g _ s i g n a l _ c o n n e c t (G_OBJECT( d e s t _ b u t t o n ) , " drag_drop " , G_CALLBACK ( on_db_drag_drop ) , GUINT_TO_POINTER( b u t t o n _ t y p e ) ) ; g _ s i g n a l _ c o n n e c t (G_OBJECT( d e s t _ b u t t o n ) , " drag_motion " , G_CALLBACK ( on_db_drag_motion ) , GUINT_TO_POINTER( b u t t o n _ t y p e ) ) ; g _ s i g n a l _ c o n n e c t (G_OBJECT( d e s t _ b u t t o n ) , " drag_leave " , G_CALLBACK ( on_db_drag_leave ) , GUINT_TO_POINTER( b u t t o n _ t y p e ) ) ; } The rst handler that we need to create is the drag-motion signal handler. This signal is emitted on a destination when the cursor moves over it during a drag. The callback prototype is

gboolean

user_function

( GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, guint time,


20

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


gpointer

Prof. Stewart Weiss


user_data);

The two arguments of interest here besides the drag_context are the x and y coordinates. These are the cursor's position in the coordinates of the widgets GDK window. The handler may use these to determine whether a drop is possible in that particular place, or if there are dierent types of data that can be dropped depending on the position. This handler has several roles. One is to determine whether it can accept a drop. Another is to highlight the widget to give the user visual feedback that a drop is possible. To do either of these things, it needs to check the targets that are being oered by the drag and compare them to the targets in its list of acceptable targets. The function

GdkAtom

gtk_drag_dest_find_target

( GtkWidget *widget, GdkDragContext *context, GtkTargetList *target_list);

can be used for this purpose. It is given the destination widget and a pointer to the passed in drag context. The third argument is a target list. The function will compare the targets stored in the drag context against those in the target list, traversing the two lists from front to back, and stopping when it nds the rst target that the source is oering that is also in the target list. The proper way to use this function is to pass list returned by the call destination widget can accept. If a target is found, its atom is returned, otherwise GDK_NONE is returned. If no target was found, the drag motion handler needs to notify the source that it will not accept a drop. It does this by calling

NULL as the third argument. By doing this, GTK will use the gtk_drag_dest_get_target_list(widget), which is the list of targets that the

void

gdk_drag_status

( GdkDragContext *context, GdkDragAction action, guint32 time_); FALSE. It can

To indicate that a drop will not be accepted, it passes a 0 as the second argument and returns pass the time value argument given to the drag motion handler as the third argument. On the other hand, if an atom was found, then the handler should highlight the widget and call

gdk_drag_status()

to indicate the action that should be taken when the drop happens. This can be any of the values

GDK_ACTION_DEFAULT GDK_ACTION_COPY GDK_ACTION_MOVE GDK_ACTION_LINK

Means nothing, and should not be used.

Copy the data. Move the data, i.e. rst copy it, then delete it from the source using the DELETE target

of the X selection protocol. Add a link to the data. Note that this is only useful if source and destination agree on

what it means.

GDK_ACTION_PRIVATE GDK_ACTION_ASK

Special action which tells the source that the destination will do something that the

source doesn't understand. Ask the user what to do with the data.

Rather than making this decision itself, the handler can simply call

gdk_drag_status(context, context->suggested_action, time);


21

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism

Prof. Stewart Weiss

which will tell GTK+ to use the suggested action as the action to take. GTK+ will base this on default settings, such as whether the control key is pressed. Lastly, to highlight the widget, it should call

void

gtk_drag_highlight

( GtkWidget *widget);

which will highlight the widget and attach expose event and draw handlers to it. But if you do this, then you also have to handle the drag-leave signal, because if you highlight the widget when the cursor is over it, then you have to unhighlight it when the cursor leaves it. This requires keeping some state around to know what its state of highlighting is. Our example drag-motion and drag-leave handlers are in Listing 8. The drag-motion handler has to return

TRUE

if a drop is possible.

Listing 8: drag-motion and drag-leave handlers gboolean on_db_drag_motion ( GtkWidget GdkDragContext gint gint guint gpointer { GdkAtom t a r g e t _ a t o m ; static int counter = 0; dc , NULL ) ;

dest_button dc ,
x, y, time , user_data )

target_atom = gtk_drag_dest_find_target ( dest_button , if { g d k _ d r a g _ s t a t u s ( dc , // // if { g _ o b j e c t _ s e t _ d a t a (G_OBJECT( d e s t _ b u t t o n ) , " highlighted " , } / } g d k _ d r a g _ s t a t u s ( dc , return } FALSE ; 0, time ) ; The widget is a valid destination . ( g p o i n t e r )TRUE ) ; gtk_drag_highlight ( dest_button ) ; If has ( the dc >s u g g e s t e d _ a c t i o n , widget by GTK, isn ' t time ) ; ( target_atom != G D K _ N O N E)

destination suggested

highlighted this

yet

and

an

action

been

higlight

destination

widget .

! g _ o b j e c t _ g e t _ d a t a (G_OBJECT( d e s t _ b u t t o n ) , " h i g h l i g h t e d " ) && dc >s u g g e s t e d _ a c t i o n )

r e t u r n TRUE;

/ / void on_db_drag_leave ( GtkWidget GdkDragContext guint gpointer { gtk_drag_unhighlight ( dest_button ) ; g _ o b j e c t _ s e t _ d a t a (G_OBJECT( d e s t _ b u t t o n ) , } " highlighted " , ( g p o i n t e r )FALSE ) ;

dest_button dc ,
time , user_data )

22

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


mouse button over the widget. The prototype for this handler is

Prof. Stewart Weiss

The destination has to have a handler for the drag-drop signal, which occurs when the user releases the

gboolean

user_function

( GtkWidget GdkDragContext gint gint guint gpointer

*widget, *drag_context, x, y, time, user_data);


The

which is the same as that of the drag motion handler, and the arguments have the same meanings.

drag-drop handler has to request the data from the source widget. To do this it needs to know which data to request. If there were no drag-motion handler, this handler would have to check the targets being oered against those in the destination's target list, just as was done in the drag motion handler. But since that work was already done, we know that a drop is possible, and all that a handler has to do is to ask for the data by calling

void

gtk_drag_get_data

( GtkWidget *widget, GdkDragContext *context, GdkAtom target, guint32 time_);

passing the required target atom. How does it know which target to request? It could look in its target list. In our case, each destination button requests a specic type of data, so we just inspect the user data, which encodes the button's ID, and request that target that the button is designed to accept. Listing 9 claries this. Listing 9: drag-drop handler gboolean on_db_drag_drop ( GtkWidget GdkDragContext gint gint guint gpointer { GdkAtom t a r g e t _ a t o m = G D K _ N O N E; // Decide which widget emitted the signal , based on user_data

dest_button dc ,
x, y, time , user_data )

DestinationType // // // if The ask a (

b u t t o n _ t y p e = GPOINTER_TO_UINT( u s e r _ d a t a ) ; to as request markup ; text depends the has on no the widget . will at The Markup ask for present . button data will as

target_atom for the text The

Number

button widget

the

number .

plain

choice

b u t t o n _ t y p e == M A R K U P _ D R O P ) t a r g e t _ a t o m = gdk_atom_intern ( " t e x t / h t m l " , TRUE ) ; if if ( ( b u t t o n _ t y p e == TEXT_PLAIN_DROP ) b u t t o n _ t y p e == N U M B E R _ D R O P )

else else

t a r g e t _ a t o m = gdk_atom_intern ( "STRING" , TRUE ) ; t a r g e t _ a t o m = gdk_atom_intern ( " number " , TRUE ) ; // if This ( should always be true , unless the atoms got deleted somehow .

target_atom

!= G D K _ N O N E )

23

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


gtk_drag_get_data ( dest_button , r e t u r n TRUE; } dc , target_atom ,

Prof. Stewart Weiss


time ) ;

The destination widget then needs the handler for the drag-data-received signal, which is emitted on it when the source has supplied the data. The callback prototype is

void

user_function

( GtkWidget GdkDragContext gint gint GtkSelectionData guint guint gpointer

*widget, *drag_context, x, y, *data, info, time, user_data);


This is the

The parameters are at this point self-explanatory, except possibly for the (x,y) coordinates. position where the drop occurred. The responsibility of this handler is to get the data from the its own variables, and to call

gtk_drag_finish()

GtkSelectionData

structure and copy it into

to notify the source that the drag has been completed.

In the simple case, it does not need to look at the position of the drop.

drag_context->action

to see if it is a move or a copy, or whether it is

GDK_DRAG_ASK,

It should look at the value of in which case it

should display a dialog box asking the user what to do. In our application we just treat the action as a copy and forgo checking the action. as user data. It is in Listing 10. Listing 10: drag-data-received handler void on_db_drag_data_received ( GtkWidget GdkDragContext gint gint GtkSelectionData guint guint gpointer { gchar guint Again, because the

callback has to do dierent things for dierent buttons, its logic is based on the value of the button id passed

dest_button dc ,
x, y,

selection_data

info , time , user_data )

label

= NULL ;

elapsed_time ; b u t t o n _ t y p e = GPOINTER_TO_UINT( u s e r _ d a t a ) ; {

DestinationType switch

( button_type )

case M A R K U P _ D R O P: label = g_strconcat ( ( gchar

) s e l e c t i o n _ d a t a >d a t a

"

" , NULL ) ; label );

g t k _ l a b e l _ s e t _ m a r k u p (GTK_LABEL(GTK_BIN( d e s t _ b u t t o n )> c h i l d ) , g_free ( l a b e l ) ; break ; c a s e TEXT_PLAIN_DROP: label = g_strconcat ( ( gchar

) s e l e c t i o n _ d a t a >d a t a

"

" , NULL ) ; label );

g t k _ l a b e l _ s e t _ t e x t (GTK_LABEL(GTK_BIN( d e s t _ b u t t o n )> c h i l d ) , g_free ( l a b e l ) ; break ; 24

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


case N U M B E R _ D R O P: elapsed_time = ( guint ) ( selection_data >d a t a ) ; label = g_strdup_printf " the ( " The drag s t a r t e d %u

Prof. Stewart Weiss

seconds

after "

application

started .\n" ,

elapsed_time ) ; label );

g t k _ l a b e l _ s e t _ t e x t (GTK_LABEL(GTK_BIN( d e s t _ b u t t o n )> c h i l d ) , g_free ( l a b e l ) ; break ; default : g_print (" } // } Indicate that the dc , drag TRUE, is finished and free the target_name Got some other data . \ n " ) ;

string

gtk_drag_finish (

FALSE ,

time ) ;

6.3

The Drawing Area Widget

This widget is also a destination widget. Its setup was displayed in Listing 1. The signal handlers for it are conceptually the same as those for the buttons, but the technical details are dierent. We describe here how to extract image and text/uri-list data in the drag-data-received handler, and how to check for valid drop targets in the motion and drop handlers. (to be continued...)

25

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


Appendix
Listing 11: Drawing a Custom Drag Icon
void draw_drag_icon ( GtkWidget

Prof. Stewart Weiss

GdkDragContext gchar { GtkStyle GdkPixmap GdkPixmap GdkGC GdkGC GdkColor PangoFontDescription PangoLayout PangoContext gint

widget , drag_context

text )

s t y l e = NULL ; i c o n _ p i x m a p = NULL ; i c o n _ m a s k = NULL ; pixmap_gc = NULL ; mask_gc = NULL ;


mask_color ;

fd ; layout ; context ;
width , height ;

// // //

Get call

the

style

of

the

widget .

To

be

sure

it

actually tests

has that

style , the

we is

gtk_widget_ensure_style ,

which

basically

widget

realized .

gtk_widget_ensure_style ( widget ) ; style = gtk_widget_get_style ( widget ) ;

The

next

few

steps font

are

required

to from

draw the

text

into

pixmap .

// fd

Get

the

current

description

widget ' s

GtkStyle

widget

>s t y l e >f o n t _ d e s c ;
with for the this appropriate widget . font map , font description ,

// //

Get and

PangoContext direction

base =

context

gtk_widget_get_pango_context ( w i d g e t ) ;

// //

Create default =

new

PangoLayout for the

object

with

attributes

initialized

to

values

newly

created

PangoContext .

layout

pango_layout_new

( context ) ;

// // // //

We

have

to the

determine text . into We

the set

size the

of font

the

rectangle

that in the

will

be

used and

to then

render copy of

description can then

layout the !!

the

text

it .

The

layout in

calculate

width

and

height

the

rendered

text ,

measured

PANGO_distance fd ) ;

units

pango_layout_set_font_description pango_layout_set_text pango_layout_get_size ( layout , ( layout ,

( layout ,

text ,

1);
&h e i g h t ) ;

&w i d t h ,

// //

PANGO_SCALE width = and width

is

the

number

of

pango

units

per

pixel ,

so

we

scale

the

height

into

pixels

width height

/ PANGO_SCALE ; / PANGO_SCALE ;

height

//

Create

and

allocate

the

icon

pixmap

and

mask width , 1); height ,

icon_pixmap icon_mask

= gdk_pixmap_new ( w i d g e t

>window ,

1);

= gdk_pixmap_new (NULL,

width ,

height ,

//

Next

we

initialize

the

icon

pixmap

and

mask

graphical

contexts

pixmap_gc = gdk_gc_new ( i c o n _ p i x m a p ) ; g d k _ g c _ s e t _ b a c k g r o u n d ( pixmap_gc , &s t y l e

>w h i t e ) ;

26

CSci493.73 Graphical User Interface Programming The GTK+ Drag-and-Drop Mechanism


g d k _ g c _ s e t _ f o r e g r o u n d ( pixmap_gc , g d k _ g c _ s e t _ f u n c t i o n ( pixmap_gc , &s t y l e

Prof. Stewart Weiss


>b l a c k
);

G D K _ C O P Y) ;

mask_gc = gdk_gc_new ( i c o n _ m a s k ) ; mask_color . p i x e l = 1; &m a s k _ c o l o r ) ;

g d k _ g c _ s e t _ f o r e g r o u n d ( mask_gc , g d k _ g c _ s e t _ f u n c t i o n ( mask_gc ,

G D K _ C O P Y) ;

// //

Now

we

draw

white of

background the gc to

for black

the

icon

rectangle

and

set

the

foreground

color

g d k _ g c _ s e t _ f o r e g r o u n d ( pixmap_gc , g d k _ d r a w _ r e c t a n g l e ( icon_pixmap , g d k _ g c _ s e t _ f o r e g r o u n d ( pixmap_gc ,

&s t y l e

>w h i t e ) ;
TRUE, ); 0, 0, width , height ) ;

pixmap_gc , &s t y l e

>b l a c k

//

Make

the

icon

completely

opaque mask_gc , TRUE, 0, 0, width , height ) ;

g d k _ d r a w _ r e c t a n g l e ( icon_mask , mask_color . p i x e l = 0;

g d k _ g c _ s e t _ f o r e g r o u n d ( mask_gc ,

&m a s k _ c o l o r ) ;

//

Render

the

text

into

the

icon

pixmap ,

cast

as

GtkDrawable

gdk_draw_layout

(G D K _ D R A W A B L E( i c o n _ p i x m a p ) , pixmap_gc , 0, 0, layout ) ;

//

Set

the

icon

as

the

drag

icon .

gtk_drag_set_icon_pixmap ( d r a g _ c o n t e x t , gdk_colormap_get_system ( ) , width /2 , height /2); icon_pixmap , icon_mask ,

unref

the

layout

g_object_unref

( layout ) ;

27

You might also like