From: Owen Taylor Subject: Whirlwind new DND API tour To: gtk-devel-list@redhat.com Date: 09 Oct 1998 18:02:43 -0400 Here's a quick tour of the new GTK+ DND API I've been working on. I'm getting pretty happy with the API, things mostly function as they should, and I've converted most of the DND in GNOME to use the new API locally. I hope to release the code in the very near future. What's New ========== - Actions - the user can select "move/copy/link" by holding down modifier keys, or right-drag to indicate "ask" - that is: pop up a dialog with the possible actions. - Transparent support for Motif and Xdnd protocols. - Much more customizable, you can have multiple drag/drop sites per widget, among other things. - More convenient interface - widgets don't need to be realized in advance, drop sites don't need to be window widgets. The simple stuff: ================ To identify lists of drag types ("targets") we use, from gtkselection.h: struct _GtkTargetEntry { gchar *target; guint flags; guint info; }; in this structures, 'target' is a string indicating the mime-type of the dragged data. In most other places, these strings are represented as atoms, which are indices into a table of strings on the server. You can convert back and forth with gdk_atom_intern() and gdk_atom_name(), as always. But, usually, you won't need to, because you've supplied something meaningful for the 'info' field, and that will be returned to you in callbacks. (One would usually do: enum { TARGET_STRING, TARGET_URL }; and use those values for 'info'. Values of 'info' above 0x80000000 are reserved for internal use.) The flags field is currently unused. Then, to enable drags from a widget (a "drag source") we use: void gtk_drag_source_set (GtkWidget *widget, GdkModifierType start_button_mask, GtkTargetEntry *targets, gint n_targets, GdkDragAction actions); 'start_button_mask' specifies the buttons that can be used to initiate a drag, 'actions' is a bitmask: typedef enum { GDK_ACTION_COPY = 1 << 1, GDK_ACTION_MOVE = 1 << 2, GDK_ACTION_LINK = 1 << 3, GDK_ACTION_ASK = 1 << 5 } GdkDragAction; When the data is dropped, we get a "drag_data_get" signal: void "drag_data_get" (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, guint info, guint32 time); One can tell the requested type from "info", and/or selection_data->type (a GdkAtom). One then calls gtk_selection_data_set() to supply the data. To enable drags to a widget, we use: void gtk_drag_dest_set (GtkWidget *widget, GtkDestDefaults flags, GtkTargetEntry *targets, gint n_targets, GdkDragAction actions); "flags" is a bitmask indicating how much GTK+ should do for you, for now, we'll assume that you want everything default and use GTK_DEST_DEFAULT_ALL for this value. "targets" "n_targets" and "actions" are above. When a drop is received, one gets a "drag_data_received" signal: void "drag_data_received" (GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint32 time); One can tell which target was dropped from 'info' or selection_data->type as above. The data is in selection_data->data. That's all you need to go and write DND applications, kids. But there's more, of course... Icons, icons ============ To use set an icon for a drag source, use: void gtk_drag_source_set_icon (GtkWidget *widget, GdkColormap *colormap, GdkPixmap *pixmap, GdkBitmap *mask); We can also set the default icon: void gtk_drag_set_default_icon (GdkColormap *colormap, GdkPixmap *pixmap, GdkBitmap *mask, gint hot_x, gint hot_y); Digging deeper: =============== The interfaces above are merely the "default handlers" - by using the lower level interfaces, DND can be extensively customized. A few additional concepts, first. At the lower level, lists of targets are represented as GtkTargetList's - GtkTargetList *gtk_target_list_new (GtkTargetEntry *targets, guint ntargets); void gtk_target_list_ref (GtkTargetList *list); void gtk_target_list_unref (GtkTargetList *list); void gtk_target_list_add (GtkTargetList *list, GdkAtom target, guint info); void gtk_target_list_add_table (GtkTargetList *list, GtkTargetEntry *targets, guint ntargets); void gtk_target_list_remove (GtkTargetList *list, GdkAtom target); A drag in progress on either side is represented by a GdkDragContext *. There are some publically accessible fields for this structure: struct _GdkDragContext { [...] GList *targets; /* List of target atoms (cast to pointer) */ GdkDragAction actions; /* Actions source supports */ GdkDragAction suggested_action; /* Action source suggests */ GdkDragAction action; /* Action destination has selected */ }; You can also attach your own data to a DragContext using gtk_dataset_set_data(), etc. DragContexts are passed as parameters to all the DND signals. In addition to the two described above, those are, on the source side: void "drag_begin" (GtkWidget *widget, GdkDragContext *context); This one is useful for setting the icon to a something special. void "drag_end" (GtkWidget *widget, GdkDragContext *context); void "drag_data_delete" (GtkWidget *widget, GdkDragContext *context); Called when the destination asks that the data be deleted. (Presumably because the action was MOVE) On the destination side: gboolean "drag_motion" (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time); Called during the drag when the mouse is in a widget registered as a drop target. If (x,y) [allocation relative] is inside a drop site in the widget, the handler should return TRUE, then call: void gdk_drag_status (GdkDragContext *context, GdkDragAction action, guint32 time); with the action it selects. void "drag_leave" (GtkWidget *widget, GdkDragContext *context); Called when the drag leaves a widget. Any highlight applied in response to "drag_motion" should be removed. gboolean "drag_drop" (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time); Called when a drop occurs. If (x,y) is inside a drop site, it should return TRUE, and either get the data with: void gtk_drag_get_data (GtkWidget *widget, GdkDragContext *context, GdkAtom target, guint32 time); and, when the data is received, call: void gtk_drag_finish (GdkDragContext *context, gboolean success, gboolean delete, guint32 time); with "success = TRUE", or call gtk_drag_finish() with "sucess = FALSE" immediately to reject the drag. Whew. You're probably glad for the default handlers now. On the destination side, you can select more precisely what the default handlers do via the "flags" argument to gtk_drag_dest_set() typedef enum { GTK_DEST_DEFAULT_MOTION = 1 << 0, /* respond to "drag_motion" */ GTK_DEST_DEFAULT_HIGHLIGHT = 1 << 1, /* auto-highlight */ GTK_DEST_DEFAULT_DROP = 1 << 2 /* respond to "drag_drop" */ } GtkDestDefaults; if DEFAULT_DROP is set, then the default handler also takes care of calling gtk_drag_finish() as appropriate. and more icons ============== If we aren't using the default handlers for drag sources, we need to set the icons for drags ourselves. We do this with: void gtk_drag_set_icon_pixmap (GdkDragContext *context, GdkColormap *colormap, GdkPixmap *pixmap, GdkBitmap *mask, gint hot_x, gint hot_y); void gtk_drag_set_icon_default (GdkDragContext *context); There's another one which you might well want to use even if you do use the default drag source handler - if you want to drag around things other than icons, you can call: void gtk_drag_set_icon_widget (GdkDragContext *context, GtkWidget *widget, gint hot_x, gint hot_y); in the "drag_begin" signal handler. (e.g., GtkColorSelector now displayed the dragged colors as color swatches) Regards, Owen