2 GTK Drag & Drop Tutorial - Example Source Code
4 Copyright (C) 2000-2002 WolfPack Entertainment
5 http://wolfpack.twu.net/
10 cc `gtk-config --cflags --libs` dnd.c -o dnd
13 Important: DND does not appear to work properly with GTK+ 1.2.9
14 or older!! Please try to obtain GTK+ 1.2.10 or newer before
15 attempting to run this demo.
18 Order of GTK DND events are as follows (for one complete drag
21 1. "drag_begin_event" notifies source that drag started
22 2. "drag_motion" notifies target about drag pointer motion
23 3. "drag_data_get" request for drag data from source
24 4. "drag_data_received" source has sent target the requested data
25 5. "drag_data_delete" source should/can delete data
26 6. "drag_end_event" notifies source that drag is done
28 Sequences 1, 3, 5, and 6 are sent to the source widget, while
29 sequences 2 and 4 are sent to the target widget.
33 #include <gdk/gdkx.h> /* Needed for pixmap loading. */
36 /* Data for the pixmaps that we will be using in the GtkCList cells
45 * DND data format type idenficaition, it identifies what format
46 * the drag data is in for internal parsing by this program.
47 * Remember that this is the data format type (not to be confused
48 * with data type, like string, binary, etc).
50 * These values are passed to gtk_drag_dest_set() and
51 * gtk_drag_source_set() and will be given as inputs in DND signal
54 * In each callback, we have the choice of using either the name
55 * (a string) or the info (an int). In this example we will use
56 * the info (an int) since it is easier and more commonly practiced.
58 * Below we define the name and info pairs that are commonly used
59 * in most GTK+ and GNOME applications:
61 #define DRAG_TAR_NAME_0 "text/plain"
62 #define DRAG_TAR_INFO_0 0
64 #define DRAG_TAR_NAME_1 "text/uri-list" /* not url-list */
65 #define DRAG_TAR_INFO_1 1
67 #define DRAG_TAR_NAME_2 "STRING"
68 #define DRAG_TAR_INFO_2 2
72 * Our window structure, it's just a toplevel GtkWindow and a
73 * GtkCList. The GtkCList will not be set reorderable, so that
74 * we can demostrate the (more advanced) DND abilities of this
82 } my_prog_window_struct;
85 /* General event/signal callbacks. */
86 static gint HandleCloseCB(
87 GtkWidget *widget, gpointer *event, gpointer data
89 static void CListSelectRowCB(
91 gint row, gint column,
92 GdkEventButton *event, gpointer data
94 static void CListUnselectRowCB(
96 gint row, gint column,
97 GdkEventButton *event, gpointer data
100 /* DND specific event/signal callbacks. */
101 static void DNDBeginCB(
102 GtkWidget *widget, GdkDragContext *dc, gpointer data
104 static void DNDEndCB(
105 GtkWidget *widget, GdkDragContext *dc, gpointer data
107 static gboolean DNDDragMotionCB(
108 GtkWidget *widget, GdkDragContext *dc,
109 gint x, gint y, guint t,
112 static void DNDDataRequestCB(
113 GtkWidget *widget, GdkDragContext *dc,
114 GtkSelectionData *selection_data, guint info, guint t,
117 static void DNDDataRecievedCB(
118 GtkWidget *widget, GdkDragContext *dc,
119 gint x, gint y, GtkSelectionData *selection_data,
120 guint info, guint t, gpointer data
122 static void DNDDataDeleteCB(
123 GtkWidget *widget, GdkDragContext *dc, gpointer data
126 /* Some convience functions for our example. */
127 static my_prog_window_struct *WindowNew(const gchar *title);
128 static void WindowDelete(my_prog_window_struct *win);
131 /* Pixmap and mask pairs used for GtkCList cell pixmaps and
134 static GdkPixmap *sphere_pixmap,
137 static GdkBitmap *sphere_mask,
143 * "delete_event" signal callback.
145 * Called when a GtkWindow closes.
147 static gint HandleCloseCB(
148 GtkWidget *widget, gpointer *event, gpointer data
151 /* Make it simple, when one window closes we just effectively
152 * close them all by breaking out of the last GTK+ main loop
153 * so that they will all be destroyed.
161 * "select_row" signal callback.
163 * Called when a row is selected on a GtkCList.
165 static void CListSelectRowCB(
167 gint row, gint column,
168 GdkEventButton *event, gpointer data
172 my_prog_window_struct *win = (my_prog_window_struct *)data;
173 if((widget == NULL) || (win == NULL))
176 clist = GTK_CLIST(widget);
178 /* Selected row in bounds? */
179 if((row >= 0) && (row < clist->rows))
184 GdkPixmap *pixmap = NULL;
185 GdkBitmap *mask = NULL;
188 /* Obtain the pixmap and mask pair of the icon of the
189 * selected row and update the DND icon.
191 cell_type = gtk_clist_get_cell_type(clist, row, 0);
194 case GTK_CELL_PIXMAP:
195 gtk_clist_get_pixmap(
196 clist, row, 0, &pixmap, &mask
198 case GTK_CELL_PIXTEXT:
199 gtk_clist_get_pixtext(
201 &text, &spacing, &pixmap, &mask
204 /* Selected row has pixmap? */
209 /* Get size of pixmap. */
211 (GdkWindow *)pixmap, &w, &h
213 /* Update the DND icon to be used on the next
214 * drag based on this pixmap. Set the hot spot
215 * to be at the center of this pixmap.
217 gtk_drag_set_default_icon(
218 gdk_colormap_get_system(),
224 /* If the selected row is not fully visible, attempt to
225 * scroll and make it visible in the center of the
228 if(gtk_clist_row_is_visible(clist, row) !=
233 row, 0, /* Row, column. */
234 0.5, 0.0 /* Row, column. */
240 * "unselect_row" signal callback.
242 * Called when a row is selected on a GtkCList.
244 static void CListUnselectRowCB(
246 gint row, gint column,
247 GdkEventButton *event, gpointer data
255 * DND "drag_begin" handler, this is called whenever a drag starts.
257 static void DNDBeginCB(
258 GtkWidget *widget, GdkDragContext *dc, gpointer data
261 my_prog_window_struct *win = (my_prog_window_struct *)data;
262 if((widget == NULL) || (win == NULL) || (dc == NULL))
265 /* Put any needed drag begin setup code here. */
269 * DND "drag_end" handler, this is called when a drag and drop has
270 * completed. So this function is the last one to be called in
271 * any given DND operation.
273 static void DNDEndCB(
274 GtkWidget *widget, GdkDragContext *dc, gpointer data
277 my_prog_window_struct *win = (my_prog_window_struct *)data;
278 if((win == NULL) || (dc == NULL))
281 /* Put any needed drag end cleanup code here. */
285 * DND "drag_motion" handler, this is called whenever the
286 * pointer is dragging over the target widget.
288 static gboolean DNDDragMotionCB(
289 GtkWidget *widget, GdkDragContext *dc,
290 gint x, gint y, guint t,
294 gboolean same_widget;
295 GdkDragAction suggested_action;
296 GtkWidget *src_widget, *tar_widget;
297 my_prog_window_struct *win = (my_prog_window_struct *)data;
298 if((win == NULL) || (dc == NULL))
301 /* Get source widget and target widget. */
302 src_widget = gtk_drag_get_source_widget(dc);
305 /* Note if source widget is the same as the target. */
306 same_widget = (src_widget == tar_widget) ? TRUE : FALSE;
309 /* If this is the same widget, our suggested action should be
310 * move. For all other case we assume copy.
313 suggested_action = GDK_ACTION_MOVE;
315 suggested_action = GDK_ACTION_COPY;
317 /* Respond with default drag action (status). First we check
318 * the dc's list of actions. If the list only contains
319 * move, copy, or link then we select just that, otherwise we
320 * return with our default suggested action.
321 * If no valid actions are listed then we respond with 0.
325 if(dc->actions == GDK_ACTION_MOVE)
326 gdk_drag_status(dc, GDK_ACTION_MOVE, t);
328 else if(dc->actions == GDK_ACTION_COPY)
329 gdk_drag_status(dc, GDK_ACTION_COPY, t);
331 else if(dc->actions == GDK_ACTION_LINK)
332 gdk_drag_status(dc, GDK_ACTION_LINK, t);
333 /* Other action, check if listed in our actions list? */
334 else if(dc->actions & suggested_action)
335 gdk_drag_status(dc, suggested_action, t);
336 /* All else respond with 0. */
338 gdk_drag_status(dc, 0, t);
344 * DND "drag_data_get" handler, for handling requests for DND
345 * data on the specified widget. This function is called when
346 * there is need for DND data on the source, so this function is
347 * responsable for setting up the dynamic data exchange buffer
348 * (DDE as sometimes it is called) and sending it out.
350 static void DNDDataRequestCB(
351 GtkWidget *widget, GdkDragContext *dc,
352 GtkSelectionData *selection_data, guint info, guint t,
356 gboolean data_sent = FALSE;
360 my_prog_window_struct *win = (my_prog_window_struct *)data;
361 if((widget == NULL) || (win == NULL) || (dc == NULL))
364 clist = GTK_CLIST(widget);
366 /* Get last selected row on the clist. */
367 glist = clist->selection_end;
368 row = (glist != NULL) ? (gint)glist->data : -1;
370 /* Selected row in bounds? */
371 if((row >= 0) && (row < clist->rows))
376 GdkPixmap *pixmap = NULL;
377 GdkBitmap *mask = NULL;
380 /* Obtain the text of the first cell of the selected
381 * row, then use this text and send it out to the DDE.
383 cell_type = gtk_clist_get_cell_type(clist, row, 0);
390 case GTK_CELL_PIXTEXT:
391 gtk_clist_get_pixtext(
393 &text, &spacing, &pixmap, &mask
396 /* Selected row has text? */
399 /* Send out the data using the selection system,
400 * this is also used for cut and paste but
401 * GTK uses it for drag and drop as well. When
402 * sending a string, GTK will ensure that a null
403 * terminating byte is added to the end so we
404 * do not need to add it. GTK also coppies the
405 * data so the original will never be modified.
407 gtk_selection_data_set(
409 GDK_SELECTION_TYPE_STRING,
410 8, /* 8 bits per character. */
417 /* If we did not send out any data (for whatever reason),
418 * then send out an error response since this function
419 * needs to gaurantee a response when reasonably possible.
423 const gchar *cstrptr = "Error";
425 gtk_selection_data_set(
427 GDK_SELECTION_TYPE_STRING,
428 8, /* 8 bits per character. */
429 cstrptr, strlen(cstrptr)
436 * DND "drag_data_received" handler. When DNDDataRequestCB()
437 * calls gtk_selection_data_set() to send out the data, this function
438 * recieves it and is responsible for handling it.
440 * This is also the only DND callback function where the given
441 * inputs may reflect those of the drop target so we need to check
442 * if this is the same structure or not.
444 static void DNDDataRecievedCB(
445 GtkWidget *widget, GdkDragContext *dc,
446 gint x, gint y, GtkSelectionData *selection_data,
447 guint info, guint t, gpointer data
452 GtkWidget *source_widget;
454 my_prog_window_struct *win = (my_prog_window_struct *)data;
455 if((widget == NULL) || (win == NULL) || (dc == NULL))
458 /* Important, check if we actually got data. Sometimes errors
459 * occure and selection_data will be NULL.
461 if(selection_data == NULL)
463 if(selection_data->length < 0)
466 /* Source and target widgets are the same? */
467 source_widget = gtk_drag_get_source_widget(dc);
468 same = (source_widget == widget) ? TRUE : FALSE;
470 clist = GTK_CLIST(widget);
472 /* Calculate the row and column at which the drop has occured
473 * over on the clist based on the given x and y coordinates.
475 if(!gtk_clist_get_selection_info(
478 y - ((clist->flags & GTK_CLIST_SHOW_TITLES) ?
479 clist->column_title_area.height +
480 clist->column_title_area.y : 0),
488 /* Now check if the data format type is one that we support
489 * (remember, data format type, not data type).
491 * We check this by testing if info matches one of the info
492 * values that we have defined.
494 * Note that we can also iterate through the atoms in:
495 * GList *glist = dc->targets;
497 * while(glist != NULL)
499 * gchar *name = gdk_atom_name((GdkAtom)glist->data);
500 * * strcmp the name to see if it matches
501 * * one that we support
503 * glist = glist->next;
506 if((info == DRAG_TAR_INFO_0) ||
507 (info == DRAG_TAR_INFO_1) ||
508 (info == DRAG_TAR_INFO_2)
512 gchar *new_text = selection_data->data;
514 GdkPixmap *pixmap = NULL;
515 GdkBitmap *mask = NULL;
518 /* Reset cells text array for adding a new row, we'll
519 * set the actual text after the row has been created.
523 /* Append or insert the new row? */
524 if((row >= 0) && (row < clist->rows))
525 new_row = gtk_clist_insert(clist, row, text);
527 new_row = gtk_clist_append(clist, text);
529 /* Check the name of the cell obtained from the received
530 * data and decipher which pixmap and mask pair to
531 * use for the new cell (if any).
533 if(!strcmp(new_text, "Sphere"))
535 pixmap = sphere_pixmap;
538 else if(!strcmp(new_text, "Cube"))
540 pixmap = cube_pixmap;
543 else if(!strcmp(new_text, "Cone"))
545 pixmap = cone_pixmap;
548 /* Update the new row's cell with the pixmap (if
549 * available) and the received data as the text.
552 gtk_clist_set_pixtext(
554 new_text, 2, pixmap, mask
565 * DND "drag_data_delete" handler, this function is called when
566 * the data on the source `should' be deleted (ie if the DND was
569 static void DNDDataDeleteCB(
570 GtkWidget *widget, GdkDragContext *dc, gpointer data
576 my_prog_window_struct *win = (my_prog_window_struct *)data;
577 if((widget == NULL) || (win == NULL) || (dc == NULL))
580 clist = GTK_CLIST(widget);
582 /* Get last selected row on the clist. */
583 glist = clist->selection_end;
584 row = (glist != NULL) ? (gint)glist->data : -1;
586 /* Remove the last selected row. */
587 gtk_clist_remove(clist, row);
592 * Creates a new window with everything set up.
594 static my_prog_window_struct *WindowNew(const gchar *title)
597 GtkWidget *w, *parent, *scrolled_window;
599 GtkTargetEntry target_entry[3];
600 my_prog_window_struct *win = (my_prog_window_struct *)g_malloc0(
601 sizeof(my_prog_window_struct)
606 /* Create toplevel. */
607 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
610 GTK_OBJECT(w), "delete_event",
611 GTK_SIGNAL_FUNC(HandleCloseCB), win
613 gtk_widget_set_usize(w, 200, 150);
614 gtk_window_set_title(
616 (title == NULL) ? "Untitled" : title
618 /* Do not show the toplevel just yet. */
621 /* Main vbox to hold clist. */
622 w = gtk_vbox_new(FALSE, 0);
623 gtk_container_add(GTK_CONTAINER(parent), w);
627 /* Create the scrolled window for the clist and the clist
630 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
631 gtk_box_pack_start(GTK_BOX(parent), scrolled_window, TRUE, TRUE, 0);
632 gtk_scrolled_window_set_policy(
633 GTK_SCROLLED_WINDOW (scrolled_window),
634 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC
636 gtk_widget_show(scrolled_window);
639 w = gtk_clist_new_with_titles(1, heading);
640 clist = GTK_CLIST(w);
642 gtk_clist_set_selection_mode(clist, GTK_SELECTION_SINGLE);
643 gtk_clist_set_shadow_type(clist, GTK_SHADOW_IN);
644 gtk_clist_column_titles_passive(clist);
645 gtk_clist_set_column_justification(
646 clist, 0, GTK_JUSTIFY_LEFT
648 gtk_clist_set_row_height(clist, 20);
650 GTK_OBJECT(w), "select_row",
651 GTK_SIGNAL_FUNC(CListSelectRowCB), win
654 GTK_OBJECT(w), "unselect_row",
655 GTK_SIGNAL_FUNC(CListUnselectRowCB), win
657 gtk_container_add(GTK_CONTAINER(scrolled_window), w);
659 /* Realize the clist widget and make sure it has a window,
660 * this will be for DND setup.
662 gtk_widget_realize(w);
663 if(!GTK_WIDGET_NO_WINDOW(w))
665 /* DND: Set up the clist as a potential DND destination.
666 * First we set up target_entry which is a sequence of of
667 * structure which specify the kinds (which we define) of
668 * drops accepted on this widget.
671 /* Set up the list of data format types that our DND
672 * callbacks will accept.
674 target_entry[0].target = DRAG_TAR_NAME_0;
675 target_entry[0].flags = 0;
676 target_entry[0].info = DRAG_TAR_INFO_0;
677 target_entry[1].target = DRAG_TAR_NAME_1;
678 target_entry[1].flags = 0;
679 target_entry[1].info = DRAG_TAR_INFO_1;
680 target_entry[2].target = DRAG_TAR_NAME_2;
681 target_entry[2].flags = 0;
682 target_entry[2].info = DRAG_TAR_INFO_2;
684 /* Set the drag destination for this widget, using the
685 * above target entry types, accept move's and coppies'.
689 GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT |
690 GTK_DEST_DEFAULT_DROP,
692 sizeof(target_entry) / sizeof(GtkTargetEntry),
693 GDK_ACTION_MOVE | GDK_ACTION_COPY
696 GTK_OBJECT(w), "drag_motion",
697 GTK_SIGNAL_FUNC(DNDDragMotionCB),
701 /* Set the drag source for this widget, allowing the user
702 * to drag items off of this clist.
706 GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
708 sizeof(target_entry) / sizeof(GtkTargetEntry),
709 GDK_ACTION_MOVE | GDK_ACTION_COPY
711 /* Set DND signals on clist. */
713 GTK_OBJECT(w), "drag_begin",
714 GTK_SIGNAL_FUNC(DNDBeginCB), win
717 GTK_OBJECT(w), "drag_end",
718 GTK_SIGNAL_FUNC(DNDEndCB), win
721 GTK_OBJECT(w), "drag_data_get",
722 GTK_SIGNAL_FUNC(DNDDataRequestCB), win
725 GTK_OBJECT(w), "drag_data_received",
726 GTK_SIGNAL_FUNC(DNDDataRecievedCB), win
729 GTK_OBJECT(w), "drag_data_delete",
730 GTK_SIGNAL_FUNC(DNDDataDeleteCB), win
738 /* Now show the toplevel. */
739 gtk_widget_show(win->toplevel);
745 * Dealloactes the given window and all its resources.
747 static void WindowDelete(my_prog_window_struct *win)
755 /* Destroy the GtkCList, first remove the DND settings on it
756 * and then destroy it.
761 /* Remove DND settings on this widget. */
762 gtk_drag_dest_unset(w);
763 gtk_drag_source_unset(w);
766 gtk_widget_destroy(w);
769 /* Destroy toplevel GtkWindow, thus destroying all of its
775 win->toplevel = NULL;
776 gtk_widget_destroy(w);
779 /* Deallocate the structure itself. */
784 int main(int argc, char **argv)
791 my_prog_window_struct *win[2];
794 gtk_init(&argc, &argv);
797 /* Load the pixmaps that we will be using as GtkCList cell
798 * pixmaps and DND icons.
800 * This requires that we use the root window for loading of
801 * the pixmaps (hence the need to include gdk/gdkx.h).
803 style = gtk_widget_get_default_style();
804 window = (GdkWindow *)GDK_ROOT_PARENT();
806 pixmap = &sphere_pixmap;
808 pixmap_data = sphere_xpm;
809 *pixmap = gdk_pixmap_create_from_xpm_d(
811 &style->bg[GTK_STATE_NORMAL],
815 pixmap = &cube_pixmap;
817 pixmap_data = cube_xpm;
818 *pixmap = gdk_pixmap_create_from_xpm_d(
820 &style->bg[GTK_STATE_NORMAL],
824 pixmap = &cone_pixmap;
826 pixmap_data = cone_xpm;
827 *pixmap = gdk_pixmap_create_from_xpm_d(
829 &style->bg[GTK_STATE_NORMAL],
834 /* Create two windows. */
835 win[0] = WindowNew("List 1");
836 win[1] = WindowNew("List 2");
838 /* Add some items to the first list. */
841 GtkCList *clist = (GtkCList *)win[0]->clist;
848 /* Reset cells text array for adding a new row,
849 * we'll set the actual text after the row has
854 /* Begin creating new rows. */
856 new_row = gtk_clist_append(clist, text);
857 gtk_clist_set_pixtext(
860 sphere_pixmap, sphere_mask
863 new_row = gtk_clist_append(clist, text);
864 gtk_clist_set_pixtext(
867 cube_pixmap, cube_mask
870 new_row = gtk_clist_append(clist, text);
871 gtk_clist_set_pixtext(
874 cone_pixmap, cone_mask
880 /* Enter main gtk loop. */
884 /* Application now shutting down, begin deallocating
887 WindowDelete(win[0]);
888 WindowDelete(win[1]);
890 gdk_pixmap_unref(sphere_pixmap);
891 gdk_bitmap_unref(sphere_mask);
892 gdk_pixmap_unref(cube_pixmap);
893 gdk_bitmap_unref(cube_mask);
894 gdk_pixmap_unref(cone_pixmap);
895 gdk_bitmap_unref(cone_mask);