/* GTK - The GIMP Toolkit * Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. */ /* * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ #include "config.h" #include "gtkdragdest.h" #include "gtkdnd.h" #include "gtkdndprivate.h" #include "gtkselectionprivate.h" #include "gtkintl.h" static void gtk_drag_dest_realized (GtkWidget *widget) { GtkWidget *toplevel = gtk_widget_get_toplevel (widget); if (gtk_widget_is_toplevel (toplevel)) gdk_window_register_dnd (gtk_widget_get_window (toplevel)); } static void gtk_drag_dest_hierarchy_changed (GtkWidget *widget, GtkWidget *previous_toplevel) { GtkWidget *toplevel = gtk_widget_get_toplevel (widget); if (gtk_widget_is_toplevel (toplevel) && gtk_widget_get_realized (toplevel)) gdk_window_register_dnd (gtk_widget_get_window (toplevel)); } static void gtk_drag_dest_site_destroy (gpointer data) { GtkDragDestSite *site = data; if (site->proxy_window) g_object_unref (site->proxy_window); if (site->target_list) gtk_target_list_unref (site->target_list); g_slice_free (GtkDragDestSite, site); } static void gtk_drag_dest_set_internal (GtkWidget *widget, GtkDragDestSite *site) { GtkDragDestSite *old_site; old_site = g_object_get_data (G_OBJECT (widget), I_("gtk-drag-dest")); if (old_site) { g_signal_handlers_disconnect_by_func (widget, gtk_drag_dest_realized, old_site); g_signal_handlers_disconnect_by_func (widget, gtk_drag_dest_hierarchy_changed, old_site); site->track_motion = old_site->track_motion; } if (gtk_widget_get_realized (widget)) gtk_drag_dest_realized (widget); g_signal_connect (widget, "realize", G_CALLBACK (gtk_drag_dest_realized), site); g_signal_connect (widget, "hierarchy-changed", G_CALLBACK (gtk_drag_dest_hierarchy_changed), site); g_object_set_data_full (G_OBJECT (widget), I_("gtk-drag-dest"), site, gtk_drag_dest_site_destroy); } /** * gtk_drag_dest_set: (method) * @widget: a #GtkWidget * @flags: which types of default drag behavior to use * @targets: (allow-none) (array length=n_targets): a pointer to an array of * #GtkTargetEntrys indicating the drop types that this @widget will * accept, or %NULL. Later you can access the list with * gtk_drag_dest_get_target_list() and gtk_drag_dest_find_target(). * @n_targets: the number of entries in @targets * @actions: a bitmask of possible actions for a drop onto this @widget. * * Sets a widget as a potential drop destination, and adds default behaviors. * * The default behaviors listed in @flags have an effect similar * to installing default handlers for the widget’s drag-and-drop signals * (#GtkWidget::drag-motion, #GtkWidget::drag-drop, ...). They all exist * for convenience. When passing #GTK_DEST_DEFAULT_ALL for instance it is * sufficient to connect to the widget’s #GtkWidget::drag-data-received * signal to get primitive, but consistent drag-and-drop support. * * Things become more complicated when you try to preview the dragged data, * as described in the documentation for #GtkWidget::drag-motion. The default * behaviors described by @flags make some assumptions, that can conflict * with your own signal handlers. For instance #GTK_DEST_DEFAULT_DROP causes * invokations of gdk_drag_status() in the context of #GtkWidget::drag-motion, * and invokations of gtk_drag_finish() in #GtkWidget::drag-data-received. * Especially the later is dramatic, when your own #GtkWidget::drag-motion * handler calls gtk_drag_get_data() to inspect the dragged data. * * There’s no way to set a default action here, you can use the * #GtkWidget::drag-motion callback for that. Here’s an example which selects * the action to use depending on whether the control key is pressed or not: * |[<!-- language="C" --> * static void * drag_motion (GtkWidget *widget, * GdkDragContext *context, * gint x, * gint y, * guint time) * { * GdkModifierType mask; * * gdk_window_get_pointer (gtk_widget_get_window (widget), * NULL, NULL, &mask); * if (mask & GDK_CONTROL_MASK) * gdk_drag_status (context, GDK_ACTION_COPY, time); * else * gdk_drag_status (context, GDK_ACTION_MOVE, time); * } * ]| */ void gtk_drag_dest_set (GtkWidget *widget, GtkDestDefaults flags, const GtkTargetEntry *targets, gint n_targets, GdkDragAction actions) { GtkDragDestSite *site; g_return_if_fail (GTK_IS_WIDGET (widget)); site = g_slice_new0 (GtkDragDestSite); site->flags = flags; site->have_drag = FALSE; if (targets) site->target_list = gtk_target_list_new (targets, n_targets); else site->target_list = NULL; site->actions = actions; site->do_proxy = FALSE; site->proxy_window = NULL; site->track_motion = FALSE; gtk_drag_dest_set_internal (widget, site); } /** * gtk_drag_dest_set_proxy: (method) * @widget: a #GtkWidget * @proxy_window: the window to which to forward drag events * @protocol: the drag protocol which the @proxy_window accepts * (You can use gdk_drag_get_protocol() to determine this) * @use_coordinates: If %TRUE, send the same coordinates to the * destination, because it is an embedded * subwindow. * * Sets this widget as a proxy for drops to another window. * * Deprecated: 3.22 */ void gtk_drag_dest_set_proxy (GtkWidget *widget, GdkWindow *proxy_window, GdkDragProtocol protocol, gboolean use_coordinates) { GtkDragDestSite *site; g_return_if_fail (GTK_IS_WIDGET (widget)); g_return_if_fail (!proxy_window || GDK_IS_WINDOW (proxy_window)); site = g_slice_new (GtkDragDestSite); site->flags = 0; site->have_drag = FALSE; site->target_list = NULL; site->actions = 0; site->proxy_window = proxy_window; if (proxy_window) g_object_ref (proxy_window); site->do_proxy = TRUE; site->proxy_protocol = protocol; site->proxy_coords = use_coordinates; site->track_motion = FALSE; gtk_drag_dest_set_internal (widget, site); } /** * gtk_drag_dest_unset: (method) * @widget: a #GtkWidget * * Clears information about a drop destination set with * gtk_drag_dest_set(). The widget will no longer receive * notification of drags. */ void gtk_drag_dest_unset (GtkWidget *widget) { GtkDragDestSite *old_site; g_return_if_fail (GTK_IS_WIDGET (widget)); old_site = g_object_get_data (G_OBJECT (widget), I_("gtk-drag-dest")); if (old_site) { g_signal_handlers_disconnect_by_func (widget, gtk_drag_dest_realized, old_site); g_signal_handlers_disconnect_by_func (widget, gtk_drag_dest_hierarchy_changed, old_site); } g_object_set_data (G_OBJECT (widget), I_("gtk-drag-dest"), NULL); } /** * gtk_drag_dest_get_target_list: (method) * @widget: a #GtkWidget * * Returns the list of targets this widget can accept from * drag-and-drop. * * Returns: (nullable) (transfer none): the #GtkTargetList, or %NULL if none */ GtkTargetList * gtk_drag_dest_get_target_list (GtkWidget *widget) { GtkDragDestSite *site; g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); site = g_object_get_data (G_OBJECT (widget), I_("gtk-drag-dest")); return site ? site->target_list : NULL; } /** * gtk_drag_dest_set_target_list: (method) * @widget: a #GtkWidget that’s a drag destination * @target_list: (allow-none): list of droppable targets, or %NULL for none * * Sets the target types that this widget can accept from drag-and-drop. * The widget must first be made into a drag destination with * gtk_drag_dest_set(). */ void gtk_drag_dest_set_target_list (GtkWidget *widget, GtkTargetList *target_list) { GtkDragDestSite *site; g_return_if_fail (GTK_IS_WIDGET (widget)); site = g_object_get_data (G_OBJECT (widget), I_("gtk-drag-dest")); if (!site) { g_warning ("Can't set a target list on a widget until you've called gtk_drag_dest_set() " "to make the widget into a drag destination"); return; } if (target_list) gtk_target_list_ref (target_list); if (site->target_list) gtk_target_list_unref (site->target_list); site->target_list = target_list; } /** * gtk_drag_dest_add_text_targets: (method) * @widget: a #GtkWidget that’s a drag destination * * Add the text targets supported by #GtkSelectionData to * the target list of the drag destination. The targets * are added with @info = 0. If you need another value, * use gtk_target_list_add_text_targets() and * gtk_drag_dest_set_target_list(). * * Since: 2.6 */ void gtk_drag_dest_add_text_targets (GtkWidget *widget) { GtkTargetList *target_list; target_list = gtk_drag_dest_get_target_list (widget); if (target_list) gtk_target_list_ref (target_list); else target_list = gtk_target_list_new (NULL, 0); gtk_target_list_add_text_targets (target_list, 0); gtk_drag_dest_set_target_list (widget, target_list); gtk_target_list_unref (target_list); } /** * gtk_drag_dest_add_image_targets: (method) * @widget: a #GtkWidget that’s a drag destination * * Add the image targets supported by #GtkSelectionData to * the target list of the drag destination. The targets * are added with @info = 0. If you need another value, * use gtk_target_list_add_image_targets() and * gtk_drag_dest_set_target_list(). * * Since: 2.6 */ void gtk_drag_dest_add_image_targets (GtkWidget *widget) { GtkTargetList *target_list; target_list = gtk_drag_dest_get_target_list (widget); if (target_list) gtk_target_list_ref (target_list); else target_list = gtk_target_list_new (NULL, 0); gtk_target_list_add_image_targets (target_list, 0, FALSE); gtk_drag_dest_set_target_list (widget, target_list); gtk_target_list_unref (target_list); } /** * gtk_drag_dest_add_uri_targets: (method) * @widget: a #GtkWidget that’s a drag destination * * Add the URI targets supported by #GtkSelectionData to * the target list of the drag destination. The targets * are added with @info = 0. If you need another value, * use gtk_target_list_add_uri_targets() and * gtk_drag_dest_set_target_list(). * * Since: 2.6 */ void gtk_drag_dest_add_uri_targets (GtkWidget *widget) { GtkTargetList *target_list; target_list = gtk_drag_dest_get_target_list (widget); if (target_list) gtk_target_list_ref (target_list); else target_list = gtk_target_list_new (NULL, 0); gtk_target_list_add_uri_targets (target_list, 0); gtk_drag_dest_set_target_list (widget, target_list); gtk_target_list_unref (target_list); } /** * gtk_drag_dest_set_track_motion: (method) * @widget: a #GtkWidget that’s a drag destination * @track_motion: whether to accept all targets * * Tells the widget to emit #GtkWidget::drag-motion and * #GtkWidget::drag-leave events regardless of the targets and the * %GTK_DEST_DEFAULT_MOTION flag. * * This may be used when a widget wants to do generic * actions regardless of the targets that the source offers. * * Since: 2.10 */ void gtk_drag_dest_set_track_motion (GtkWidget *widget, gboolean track_motion) { GtkDragDestSite *site; g_return_if_fail (GTK_IS_WIDGET (widget)); site = g_object_get_data (G_OBJECT (widget), I_("gtk-drag-dest")); g_return_if_fail (site != NULL); site->track_motion = track_motion != FALSE; } /** * gtk_drag_dest_get_track_motion: (method) * @widget: a #GtkWidget that’s a drag destination * * Returns whether the widget has been configured to always * emit #GtkWidget::drag-motion signals. * * Returns: %TRUE if the widget always emits * #GtkWidget::drag-motion events * * Since: 2.10 */ gboolean gtk_drag_dest_get_track_motion (GtkWidget *widget) { GtkDragDestSite *site; g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); site = g_object_get_data (G_OBJECT (widget), I_("gtk-drag-dest")); if (site) return site->track_motion; return FALSE; } /** * gtk_drag_dest_find_target: (method) * @widget: drag destination widget * @context: drag context * @target_list: (allow-none): list of droppable targets, or %NULL to use * gtk_drag_dest_get_target_list (@widget). * * Looks for a match between the supported targets of @context and the * @dest_target_list, returning the first matching target, otherwise * returning %GDK_NONE. @dest_target_list should usually be the return * value from gtk_drag_dest_get_target_list(), but some widgets may * have different valid targets for different parts of the widget; in * that case, they will have to implement a drag_motion handler that * passes the correct target list to this function. * * Returns: (transfer none): first target that the source offers * and the dest can accept, or %GDK_NONE */ GdkAtom gtk_drag_dest_find_target (GtkWidget *widget, GdkDragContext *context, GtkTargetList *target_list) { GList *tmp_target; GList *tmp_source = NULL; GtkWidget *source_widget; g_return_val_if_fail (GTK_IS_WIDGET (widget), GDK_NONE); g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), GDK_NONE); source_widget = gtk_drag_get_source_widget (context); if (target_list == NULL) target_list = gtk_drag_dest_get_target_list (widget); if (target_list == NULL) return GDK_NONE; tmp_target = target_list->list; while (tmp_target) { GtkTargetPair *pair = tmp_target->data; tmp_source = gdk_drag_context_list_targets (context); while (tmp_source) { if (tmp_source->data == GUINT_TO_POINTER (pair->target)) { if ((!(pair->flags & GTK_TARGET_SAME_APP) || source_widget) && (!(pair->flags & GTK_TARGET_SAME_WIDGET) || (source_widget == widget)) && (!(pair->flags & GTK_TARGET_OTHER_APP) || !source_widget) && (!(pair->flags & GTK_TARGET_OTHER_WIDGET) || (source_widget != widget))) return pair->target; else break; } tmp_source = tmp_source->next; } tmp_target = tmp_target->next; } return GDK_NONE; }