forked from AuroraMiddleware/gtk
7733f646d6
This is to go along with the newly introduced GdkDrop. This commit includes the necessary updates to the X11, Wayland and Broadway backends. Other backends have to be updated separately.
1440 lines
43 KiB
C
1440 lines
43 KiB
C
/* 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 "gtkdndprivate.h"
|
||
|
||
#include "gtkdragdest.h"
|
||
#include "gtkimageprivate.h"
|
||
#include "gtkintl.h"
|
||
#include "gtkmain.h"
|
||
#include "gtkpicture.h"
|
||
#include "gtkselectionprivate.h"
|
||
#include "gtksettingsprivate.h"
|
||
#include "gtktooltipprivate.h"
|
||
#include "gtkwidgetprivate.h"
|
||
#include "gtkwindowgroup.h"
|
||
#include "gtkwindowprivate.h"
|
||
|
||
#include "gdk/gdkcontentformatsprivate.h"
|
||
#include "gdk/gdktextureprivate.h"
|
||
|
||
#include <math.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
|
||
/**
|
||
* SECTION:gtkdnd
|
||
* @Short_description: Functions for controlling drag and drop handling
|
||
* @Title: Drag and Drop
|
||
*
|
||
* GTK+ has a rich set of functions for doing inter-process communication
|
||
* via the drag-and-drop metaphor.
|
||
*
|
||
* As well as the functions listed here, applications may need to use some
|
||
* facilities provided for [Selections][gtk3-Selections]. Also, the Drag and
|
||
* Drop API makes use of signals in the #GtkWidget class.
|
||
*/
|
||
|
||
|
||
typedef struct _GtkDragSourceInfo GtkDragSourceInfo;
|
||
typedef struct _GtkDragDestInfo GtkDragDestInfo;
|
||
|
||
struct _GtkDragSourceInfo
|
||
{
|
||
GtkWidget *widget;
|
||
GdkContentFormats *target_list; /* Targets for drag data */
|
||
GdkDrag *drag; /* drag context */
|
||
GtkWidget *icon_window; /* Window for drag */
|
||
GtkWidget *icon_widget; /* Widget for drag */
|
||
|
||
guint drop_timeout; /* Timeout for aborting drop */
|
||
guint destroy_icon : 1; /* If true, destroy icon_widget */
|
||
};
|
||
|
||
struct _GtkDragDestInfo
|
||
{
|
||
GtkWidget *widget; /* Widget in which drag is in */
|
||
GdkDrop *drop; /* drop */
|
||
};
|
||
|
||
#define DROP_ABORT_TIME 300000
|
||
|
||
typedef gboolean (* GtkDragDestCallback) (GtkWidget *widget,
|
||
GdkDrop *drop,
|
||
gint x,
|
||
gint y,
|
||
guint32 time);
|
||
|
||
/* Forward declarations */
|
||
static gboolean gtk_drop_find_widget (GtkWidget *widget,
|
||
GdkDrop *drop,
|
||
GtkDragDestInfo *info,
|
||
gint x,
|
||
gint y,
|
||
guint32 time,
|
||
GtkDragDestCallback callback);
|
||
static void gtk_drag_dest_leave (GtkWidget *widget,
|
||
GdkDrop *drop,
|
||
guint time);
|
||
static gboolean gtk_drag_dest_motion (GtkWidget *widget,
|
||
GdkDrop *drop,
|
||
gint x,
|
||
gint y,
|
||
guint time);
|
||
static gboolean gtk_drag_dest_drop (GtkWidget *widget,
|
||
GdkDrop *drop,
|
||
gint x,
|
||
gint y,
|
||
guint time);
|
||
static void gtk_drag_dest_set_widget (GtkDragDestInfo *info,
|
||
GtkWidget *widget);
|
||
|
||
static GtkDragDestInfo * gtk_drag_get_dest_info (GdkDrop *drop,
|
||
gboolean create);
|
||
static GtkDragSourceInfo *gtk_drag_get_source_info (GdkDrag *drag,
|
||
gboolean create);
|
||
static void gtk_drag_clear_source_info (GdkDrag *drag);
|
||
|
||
static void gtk_drag_drop (GtkDragSourceInfo *info);
|
||
static void gtk_drag_drop_finished (GtkDragSourceInfo *info,
|
||
GtkDragResult result);
|
||
static void gtk_drag_cancel_internal (GtkDragSourceInfo *info,
|
||
GtkDragResult result);
|
||
|
||
static void gtk_drag_remove_icon (GtkDragSourceInfo *info);
|
||
static void gtk_drag_source_info_destroy (GtkDragSourceInfo *info);
|
||
|
||
static void gtk_drag_drop_performed_cb (GdkDrag *drag,
|
||
GtkDragSourceInfo *info);
|
||
static void gtk_drag_cancel_cb (GdkDrag *drag,
|
||
GdkDragCancelReason reason,
|
||
GtkDragSourceInfo *info);
|
||
static void gtk_drag_dnd_finished_cb (GdkDrag *drag,
|
||
GtkDragSourceInfo *info);
|
||
|
||
static gboolean gtk_drag_abort_timeout (gpointer data);
|
||
|
||
static void set_icon_helper (GdkDrag *drag,
|
||
GtkImageDefinition *def,
|
||
gint hot_x,
|
||
gint hot_y);
|
||
|
||
|
||
/********************
|
||
* Destination side *
|
||
********************/
|
||
|
||
typedef struct {
|
||
GdkDrop *drop;
|
||
GtkWidget *widget;
|
||
const char *mime_type;
|
||
} GtkDragGetData;
|
||
|
||
static void
|
||
gtk_drag_get_data_finish (GtkDragGetData *data,
|
||
guchar *bytes,
|
||
gsize size)
|
||
{
|
||
GtkDragDestSite *site;
|
||
GtkSelectionData sdata;
|
||
|
||
site = g_object_get_data (G_OBJECT (data->widget), "gtk-drag-dest");
|
||
|
||
sdata.target = data->mime_type;
|
||
sdata.type = data->mime_type;
|
||
sdata.format = 8;
|
||
sdata.length = size;
|
||
sdata.data = bytes;
|
||
sdata.display = gtk_widget_get_display (data->widget);
|
||
|
||
if (site && site->target_list)
|
||
{
|
||
if (gdk_content_formats_contain_mime_type (site->target_list, data->mime_type))
|
||
{
|
||
if (!(site->flags & GTK_DEST_DEFAULT_DROP) ||
|
||
size >= 0)
|
||
g_signal_emit_by_name (data->widget,
|
||
"drag-data-received",
|
||
data->drop,
|
||
&sdata);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
g_signal_emit_by_name (data->widget,
|
||
"drag-data-received",
|
||
data->drop,
|
||
&sdata);
|
||
}
|
||
|
||
if (site && site->flags & GTK_DEST_DEFAULT_DROP)
|
||
{
|
||
GdkDragAction action = site->actions & gdk_drop_get_actions (data->drop);
|
||
|
||
if (size == 0)
|
||
action = 0;
|
||
|
||
if (!gdk_drag_action_is_unique (action))
|
||
{
|
||
if (action & GDK_ACTION_COPY)
|
||
action = GDK_ACTION_COPY;
|
||
else if (action & GDK_ACTION_MOVE)
|
||
action = GDK_ACTION_MOVE;
|
||
}
|
||
|
||
gdk_drop_finish (data->drop, action);
|
||
}
|
||
|
||
g_object_unref (data->widget);
|
||
g_object_unref (data->drop);
|
||
g_slice_free (GtkDragGetData, data);
|
||
}
|
||
|
||
static void
|
||
gtk_drag_get_data_got_data (GObject *source,
|
||
GAsyncResult *result,
|
||
gpointer data)
|
||
{
|
||
gssize written;
|
||
|
||
written = g_output_stream_splice_finish (G_OUTPUT_STREAM (source), result, NULL);
|
||
if (written < 0)
|
||
{
|
||
gtk_drag_get_data_finish (data, NULL, 0);
|
||
}
|
||
else
|
||
{
|
||
gtk_drag_get_data_finish (data,
|
||
g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (source)),
|
||
g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (source)));
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_drag_get_data_got_stream (GObject *source,
|
||
GAsyncResult *result,
|
||
gpointer user_data)
|
||
{
|
||
GtkDragGetData *data = user_data;
|
||
GInputStream *input_stream;
|
||
GOutputStream *output_stream;
|
||
|
||
input_stream = gdk_drop_read_finish (GDK_DROP (source), &data->mime_type, result, NULL);
|
||
if (input_stream == NULL)
|
||
{
|
||
gtk_drag_get_data_finish (data, NULL, 0);
|
||
return;
|
||
}
|
||
|
||
output_stream = g_memory_output_stream_new_resizable ();
|
||
g_output_stream_splice_async (output_stream,
|
||
input_stream,
|
||
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
|
||
G_PRIORITY_DEFAULT,
|
||
NULL,
|
||
gtk_drag_get_data_got_data,
|
||
data);
|
||
g_object_unref (output_stream);
|
||
g_object_unref (input_stream);
|
||
|
||
}
|
||
|
||
/**
|
||
* gtk_drag_get_data: (method)
|
||
* @widget: the widget that will receive the
|
||
* #GtkWidget::drag-data-received signal
|
||
* @drop: the #GdkDrop
|
||
* @target: the target (form of the data) to retrieve
|
||
*
|
||
* Gets the data associated with a drag. When the data
|
||
* is received or the retrieval fails, GTK+ will emit a
|
||
* #GtkWidget::drag-data-received signal. Failure of the retrieval
|
||
* is indicated by the length field of the @selection_data
|
||
* signal parameter being negative. However, when gtk_drag_get_data()
|
||
* is called implicitely because the %GTK_DEST_DEFAULT_DROP was set,
|
||
* then the widget will not receive notification of failed
|
||
* drops.
|
||
*/
|
||
void
|
||
gtk_drag_get_data (GtkWidget *widget,
|
||
GdkDrop *drop,
|
||
GdkAtom target)
|
||
{
|
||
GtkDragGetData *data;
|
||
|
||
g_return_if_fail (GTK_IS_WIDGET (widget));
|
||
g_return_if_fail (GDK_IS_DROP (drop));
|
||
|
||
data = g_slice_new0 (GtkDragGetData);
|
||
data->widget = g_object_ref (widget);
|
||
data->drop = g_object_ref (drop);
|
||
data->mime_type = target;
|
||
|
||
gdk_drop_read_async (drop,
|
||
(const gchar *[2]) { target, NULL },
|
||
G_PRIORITY_DEFAULT,
|
||
NULL,
|
||
gtk_drag_get_data_got_stream,
|
||
data);
|
||
}
|
||
|
||
/**
|
||
* gtk_drag_get_source_widget:
|
||
* @drag: a drag context
|
||
*
|
||
* Determines the source widget for a drag.
|
||
*
|
||
* Returns: (nullable) (transfer none): if the drag is occurring
|
||
* within a single application, a pointer to the source widget.
|
||
* Otherwise, %NULL.
|
||
*/
|
||
GtkWidget *
|
||
gtk_drag_get_source_widget (GdkDrag *drag)
|
||
{
|
||
GtkDragSourceInfo *info;
|
||
|
||
g_return_val_if_fail (GDK_IS_DRAG (drag), NULL);
|
||
|
||
info = gtk_drag_get_source_info (drag, FALSE);
|
||
if (info == NULL)
|
||
return NULL;
|
||
|
||
return info->widget;
|
||
}
|
||
|
||
/**
|
||
* gtk_drag_highlight: (method)
|
||
* @widget: a widget to highlight
|
||
*
|
||
* Highlights a widget as a currently hovered drop target.
|
||
* To end the highlight, call gtk_drag_unhighlight().
|
||
* GTK+ calls this automatically if %GTK_DEST_DEFAULT_HIGHLIGHT is set.
|
||
*/
|
||
void
|
||
gtk_drag_highlight (GtkWidget *widget)
|
||
{
|
||
g_return_if_fail (GTK_IS_WIDGET (widget));
|
||
|
||
gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_DROP_ACTIVE, FALSE);
|
||
}
|
||
|
||
/**
|
||
* gtk_drag_unhighlight: (method)
|
||
* @widget: a widget to remove the highlight from
|
||
*
|
||
* Removes a highlight set by gtk_drag_highlight() from
|
||
* a widget.
|
||
*/
|
||
void
|
||
gtk_drag_unhighlight (GtkWidget *widget)
|
||
{
|
||
g_return_if_fail (GTK_IS_WIDGET (widget));
|
||
|
||
gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_DROP_ACTIVE);
|
||
}
|
||
|
||
/*
|
||
* _gtk_drag_dest_handle_event:
|
||
* @toplevel: Toplevel widget that received the event
|
||
* @event: the event to handle
|
||
*
|
||
* Called from widget event handling code on Drag events
|
||
* for destinations.
|
||
*/
|
||
void
|
||
_gtk_drag_dest_handle_event (GtkWidget *toplevel,
|
||
GdkEvent *event)
|
||
{
|
||
GtkDragDestInfo *info;
|
||
GdkDrop *drop;
|
||
guint32 time;
|
||
GdkEventType event_type;
|
||
|
||
g_return_if_fail (toplevel != NULL);
|
||
g_return_if_fail (event != NULL);
|
||
|
||
event_type = gdk_event_get_event_type (event);
|
||
drop = gdk_event_get_drop (event);
|
||
time = gdk_event_get_time (event);
|
||
|
||
info = gtk_drag_get_dest_info (drop, TRUE);
|
||
|
||
/* Find the widget for the event */
|
||
switch ((guint) event_type)
|
||
{
|
||
case GDK_DRAG_ENTER:
|
||
break;
|
||
|
||
case GDK_DRAG_LEAVE:
|
||
if (info->widget)
|
||
{
|
||
gtk_drag_dest_leave (info->widget, drop, time);
|
||
gtk_drag_dest_set_widget (info, NULL);
|
||
}
|
||
break;
|
||
|
||
case GDK_DRAG_MOTION:
|
||
case GDK_DROP_START:
|
||
{
|
||
GdkSurface *surface;
|
||
gint tx, ty;
|
||
double x_root, y_root;
|
||
gboolean found;
|
||
|
||
if (event_type == GDK_DROP_START)
|
||
{
|
||
/* We send a leave here so that the widget unhighlights
|
||
* properly.
|
||
*/
|
||
if (info->widget)
|
||
{
|
||
gtk_drag_dest_leave (info->widget, drop, time);
|
||
gtk_drag_dest_set_widget (info, NULL);
|
||
}
|
||
}
|
||
|
||
surface = gtk_widget_get_surface (toplevel);
|
||
|
||
gdk_surface_get_position (surface, &tx, &ty);
|
||
gdk_event_get_root_coords (event, &x_root, &y_root);
|
||
|
||
found = gtk_drop_find_widget (toplevel,
|
||
drop,
|
||
info,
|
||
x_root - tx,
|
||
y_root - ty,
|
||
time,
|
||
(event_type == GDK_DRAG_MOTION) ?
|
||
gtk_drag_dest_motion :
|
||
gtk_drag_dest_drop);
|
||
|
||
if (info->widget && !found)
|
||
{
|
||
gtk_drag_dest_leave (info->widget, drop, time);
|
||
gtk_drag_dest_set_widget (info, NULL);
|
||
}
|
||
|
||
/* Send a reply.
|
||
*/
|
||
if (!found)
|
||
gdk_drop_status (drop, 0);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
g_assert_not_reached ();
|
||
}
|
||
}
|
||
|
||
static gboolean
|
||
gtk_drop_find_widget (GtkWidget *widget,
|
||
GdkDrop *drop,
|
||
GtkDragDestInfo *info,
|
||
gint x,
|
||
gint y,
|
||
guint32 time,
|
||
GtkDragDestCallback callback)
|
||
{
|
||
if (!gtk_widget_get_mapped (widget) ||
|
||
!gtk_widget_get_sensitive (widget))
|
||
return FALSE;
|
||
|
||
/* Get the widget at the pointer coordinates and travel up
|
||
* the widget hierarchy from there.
|
||
*/
|
||
widget = _gtk_widget_find_at_coords (gtk_widget_get_surface (widget),
|
||
x, y, &x, &y);
|
||
if (!widget)
|
||
return FALSE;
|
||
|
||
while (widget)
|
||
{
|
||
GtkWidget *parent;
|
||
GList *hierarchy = NULL;
|
||
gboolean found = FALSE;
|
||
|
||
if (!gtk_widget_get_mapped (widget))
|
||
return FALSE;
|
||
|
||
if (gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_INSENSITIVE)
|
||
{
|
||
widget = gtk_widget_get_parent (widget);
|
||
continue;
|
||
}
|
||
|
||
/* need to reference the entire hierarchy temporarily in case the
|
||
* ::drag-motion/::drag-drop callbacks change the widget hierarchy.
|
||
*/
|
||
for (parent = widget;
|
||
parent;
|
||
parent = gtk_widget_get_parent (parent))
|
||
{
|
||
hierarchy = g_list_prepend (hierarchy, g_object_ref (parent));
|
||
}
|
||
|
||
/* If the current widget is registered as a drop site, check to
|
||
* emit "drag-motion" to check if we are actually in a drop
|
||
* site.
|
||
*/
|
||
if (g_object_get_data (G_OBJECT (widget), "gtk-drag-dest"))
|
||
{
|
||
found = callback (widget, drop, x, y, time);
|
||
|
||
/* If so, send a "drag-leave" to the last widget */
|
||
if (found && info->widget != widget)
|
||
{
|
||
if (info->widget)
|
||
gtk_drag_dest_leave (info->widget, drop, time);
|
||
|
||
gtk_drag_dest_set_widget (info, widget);
|
||
}
|
||
}
|
||
|
||
if (!found)
|
||
{
|
||
/* Get the parent before unreffing the hierarchy because
|
||
* invoking the callback might have destroyed the widget
|
||
*/
|
||
parent = gtk_widget_get_parent (widget);
|
||
|
||
/* The parent might be going away when unreffing the
|
||
* hierarchy, so also protect againt that
|
||
*/
|
||
if (parent)
|
||
g_object_add_weak_pointer (G_OBJECT (parent), (gpointer *) &parent);
|
||
}
|
||
|
||
g_list_free_full (hierarchy, g_object_unref);
|
||
|
||
if (found)
|
||
return TRUE;
|
||
|
||
if (parent)
|
||
g_object_remove_weak_pointer (G_OBJECT (parent), (gpointer *) &parent);
|
||
else
|
||
return FALSE;
|
||
|
||
if (!gtk_widget_translate_coordinates (widget, parent, x, y, &x, &y))
|
||
return FALSE;
|
||
|
||
widget = parent;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void
|
||
gtk_drag_dest_set_widget (GtkDragDestInfo *info,
|
||
GtkWidget *widget)
|
||
{
|
||
if (info->widget)
|
||
g_object_remove_weak_pointer (G_OBJECT (info->widget), (gpointer *) &info->widget);
|
||
|
||
info->widget = widget;
|
||
|
||
if (info->widget)
|
||
g_object_add_weak_pointer (G_OBJECT (info->widget), (gpointer *) &info->widget);
|
||
}
|
||
|
||
static void
|
||
gtk_drag_dest_info_destroy (gpointer data)
|
||
{
|
||
GtkDragDestInfo *info = (GtkDragDestInfo *)data;
|
||
|
||
gtk_drag_dest_set_widget (info, NULL);
|
||
|
||
g_slice_free (GtkDragDestInfo, data);
|
||
}
|
||
|
||
static GtkDragDestInfo *
|
||
gtk_drag_get_dest_info (GdkDrop *drop,
|
||
gboolean create)
|
||
{
|
||
GtkDragDestInfo *info;
|
||
static GQuark info_quark = 0;
|
||
if (!info_quark)
|
||
info_quark = g_quark_from_static_string ("gtk-dest-info");
|
||
|
||
info = g_object_get_qdata (G_OBJECT (drop), info_quark);
|
||
if (!info && create)
|
||
{
|
||
info = g_slice_new0 (GtkDragDestInfo);
|
||
info->drop = drop;
|
||
g_object_set_qdata_full (G_OBJECT (drop), info_quark,
|
||
info, gtk_drag_dest_info_destroy);
|
||
}
|
||
|
||
return info;
|
||
}
|
||
|
||
static GQuark dest_info_quark = 0;
|
||
|
||
static GtkDragSourceInfo *
|
||
gtk_drag_get_source_info (GdkDrag *drag,
|
||
gboolean create)
|
||
{
|
||
GtkDragSourceInfo *info;
|
||
if (!dest_info_quark)
|
||
dest_info_quark = g_quark_from_static_string ("gtk-source-info");
|
||
|
||
info = g_object_get_qdata (G_OBJECT (drag), dest_info_quark);
|
||
if (!info && create)
|
||
{
|
||
info = g_new0 (GtkDragSourceInfo, 1);
|
||
info->drag = drag;
|
||
g_object_set_qdata (G_OBJECT (drag), dest_info_quark, info);
|
||
}
|
||
|
||
return info;
|
||
}
|
||
|
||
static void
|
||
gtk_drag_clear_source_info (GdkDrag *drag)
|
||
{
|
||
g_object_set_qdata (G_OBJECT (drag), dest_info_quark, NULL);
|
||
}
|
||
|
||
/*
|
||
* Default drag handlers
|
||
*/
|
||
static void
|
||
gtk_drag_dest_leave (GtkWidget *widget,
|
||
GdkDrop *drop,
|
||
guint time)
|
||
{
|
||
GtkDragDestSite *site;
|
||
|
||
site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
|
||
g_return_if_fail (site != NULL);
|
||
|
||
if ((site->flags & GTK_DEST_DEFAULT_HIGHLIGHT) && site->have_drag)
|
||
gtk_drag_unhighlight (widget);
|
||
|
||
if (!(site->flags & GTK_DEST_DEFAULT_MOTION) || site->have_drag ||
|
||
site->track_motion)
|
||
g_signal_emit_by_name (widget, "drag-leave", drop, time);
|
||
|
||
site->have_drag = FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
gtk_drag_dest_motion (GtkWidget *widget,
|
||
GdkDrop *drop,
|
||
gint x,
|
||
gint y,
|
||
guint time)
|
||
{
|
||
GtkDragDestSite *site;
|
||
gboolean retval;
|
||
|
||
site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
|
||
g_return_val_if_fail (site != NULL, FALSE);
|
||
|
||
if (site->track_motion || site->flags & GTK_DEST_DEFAULT_MOTION)
|
||
{
|
||
GdkDragAction actions;
|
||
|
||
actions = gdk_drop_get_actions (drop);
|
||
|
||
if ((actions & site->actions) == 0)
|
||
actions = 0;
|
||
|
||
if (actions && gtk_drag_dest_find_target (widget, drop, NULL))
|
||
{
|
||
if (!site->have_drag)
|
||
{
|
||
site->have_drag = TRUE;
|
||
if (site->flags & GTK_DEST_DEFAULT_HIGHLIGHT)
|
||
gtk_drag_highlight (widget);
|
||
}
|
||
|
||
gdk_drop_status (drop, site->actions);
|
||
}
|
||
else
|
||
{
|
||
gdk_drop_status (drop, 0);
|
||
if (!site->track_motion)
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
g_signal_emit_by_name (widget, "drag-motion",
|
||
drop, x, y, &retval);
|
||
|
||
return (site->flags & GTK_DEST_DEFAULT_MOTION) ? TRUE : retval;
|
||
}
|
||
|
||
static gboolean
|
||
gtk_drag_dest_drop (GtkWidget *widget,
|
||
GdkDrop *drop,
|
||
gint x,
|
||
gint y,
|
||
guint time)
|
||
{
|
||
GtkDragDestSite *site;
|
||
GtkDragDestInfo *info;
|
||
gboolean retval;
|
||
|
||
site = g_object_get_data (G_OBJECT (widget), "gtk-drag-dest");
|
||
g_return_val_if_fail (site != NULL, FALSE);
|
||
|
||
info = gtk_drag_get_dest_info (drop, FALSE);
|
||
g_return_val_if_fail (info != NULL, FALSE);
|
||
|
||
if (site->flags & GTK_DEST_DEFAULT_DROP)
|
||
{
|
||
GdkAtom target = gtk_drag_dest_find_target (widget, drop, NULL);
|
||
|
||
if (target == NULL)
|
||
{
|
||
gdk_drop_finish (drop, 0);
|
||
return TRUE;
|
||
}
|
||
else
|
||
gtk_drag_get_data (widget, drop, target);
|
||
}
|
||
|
||
g_signal_emit_by_name (widget, "drag-drop",
|
||
drop, x, y, &retval);
|
||
|
||
return (site->flags & GTK_DEST_DEFAULT_DROP) ? TRUE : retval;
|
||
}
|
||
|
||
/***************
|
||
* Source side *
|
||
***************/
|
||
|
||
#define GTK_TYPE_DRAG_CONTENT (gtk_drag_content_get_type ())
|
||
#define GTK_DRAG_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_DRAG_CONTENT, GtkDragContent))
|
||
#define GTK_IS_DRAG_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_DRAG_CONTENT))
|
||
#define GTK_DRAG_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_DRAG_CONTENT, GtkDragContentClass))
|
||
#define GTK_IS_DRAG_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_DRAG_CONTENT))
|
||
#define GTK_DRAG_CONTENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_DRAG_CONTENT, GtkDragContentClass))
|
||
|
||
typedef struct _GtkDragContent GtkDragContent;
|
||
typedef struct _GtkDragContentClass GtkDragContentClass;
|
||
|
||
struct _GtkDragContent
|
||
{
|
||
GdkContentProvider parent;
|
||
|
||
GtkWidget *widget;
|
||
GdkDrag *drag;
|
||
GdkContentFormats *formats;
|
||
guint32 time;
|
||
};
|
||
|
||
struct _GtkDragContentClass
|
||
{
|
||
GdkContentProviderClass parent_class;
|
||
};
|
||
|
||
GType gtk_drag_content_get_type (void) G_GNUC_CONST;
|
||
|
||
G_DEFINE_TYPE (GtkDragContent, gtk_drag_content, GDK_TYPE_CONTENT_PROVIDER)
|
||
|
||
static GdkContentFormats *
|
||
gtk_drag_content_ref_formats (GdkContentProvider *provider)
|
||
{
|
||
GtkDragContent *content = GTK_DRAG_CONTENT (provider);
|
||
|
||
return gdk_content_formats_ref (content->formats);
|
||
}
|
||
|
||
static void
|
||
gtk_drag_content_write_mime_type_done (GObject *stream,
|
||
GAsyncResult *result,
|
||
gpointer task)
|
||
{
|
||
GError *error = NULL;
|
||
|
||
if (!g_output_stream_write_all_finish (G_OUTPUT_STREAM (stream),
|
||
result,
|
||
NULL,
|
||
&error))
|
||
{
|
||
g_task_return_error (task, error);
|
||
}
|
||
else
|
||
{
|
||
g_task_return_boolean (task, TRUE);
|
||
}
|
||
|
||
g_object_unref (task);
|
||
}
|
||
|
||
static void
|
||
gtk_drag_content_write_mime_type_async (GdkContentProvider *provider,
|
||
const char *mime_type,
|
||
GOutputStream *stream,
|
||
int io_priority,
|
||
GCancellable *cancellable,
|
||
GAsyncReadyCallback callback,
|
||
gpointer user_data)
|
||
{
|
||
GtkDragContent *content = GTK_DRAG_CONTENT (provider);
|
||
GtkSelectionData sdata = { 0, };
|
||
GTask *task;
|
||
|
||
task = g_task_new (content, cancellable, callback, user_data);
|
||
g_task_set_priority (task, io_priority);
|
||
g_task_set_source_tag (task, gtk_drag_content_write_mime_type_async);
|
||
|
||
sdata.target = g_intern_string (mime_type);
|
||
sdata.length = -1;
|
||
sdata.display = gtk_widget_get_display (content->widget);
|
||
|
||
g_signal_emit_by_name (content->widget, "drag-data-get",
|
||
content->drag,
|
||
&sdata);
|
||
|
||
if (sdata.length == -1)
|
||
{
|
||
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("Cannot provide contents as “%s”"), mime_type);
|
||
g_object_unref (task);
|
||
return;
|
||
}
|
||
g_task_set_task_data (task, sdata.data, g_free);
|
||
|
||
g_output_stream_write_all_async (stream,
|
||
sdata.data,
|
||
sdata.length,
|
||
io_priority,
|
||
cancellable,
|
||
gtk_drag_content_write_mime_type_done,
|
||
task);
|
||
}
|
||
|
||
static gboolean
|
||
gtk_drag_content_write_mime_type_finish (GdkContentProvider *provider,
|
||
GAsyncResult *result,
|
||
GError **error)
|
||
{
|
||
g_return_val_if_fail (g_task_is_valid (result, provider), FALSE);
|
||
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gtk_drag_content_write_mime_type_async, FALSE);
|
||
|
||
return g_task_propagate_boolean (G_TASK (result), error);
|
||
}
|
||
|
||
static void
|
||
gtk_drag_content_finalize (GObject *object)
|
||
{
|
||
GtkDragContent *content = GTK_DRAG_CONTENT (object);
|
||
|
||
g_clear_object (&content->widget);
|
||
g_clear_pointer (&content->formats, (GDestroyNotify) gdk_content_formats_unref);
|
||
|
||
G_OBJECT_CLASS (gtk_drag_content_parent_class)->finalize (object);
|
||
}
|
||
|
||
static void
|
||
gtk_drag_content_class_init (GtkDragContentClass *class)
|
||
{
|
||
GdkContentProviderClass *provider_class = GDK_CONTENT_PROVIDER_CLASS (class);
|
||
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||
|
||
object_class->finalize = gtk_drag_content_finalize;
|
||
|
||
provider_class->ref_formats = gtk_drag_content_ref_formats;
|
||
provider_class->write_mime_type_async = gtk_drag_content_write_mime_type_async;
|
||
provider_class->write_mime_type_finish = gtk_drag_content_write_mime_type_finish;
|
||
}
|
||
|
||
static void
|
||
gtk_drag_content_init (GtkDragContent *content)
|
||
{
|
||
}
|
||
|
||
/* Like gtk_drag_begin(), but also takes a GtkImageDefinition
|
||
* so that we can set the icon from the source site information
|
||
*/
|
||
GdkDrag *
|
||
gtk_drag_begin_internal (GtkWidget *widget,
|
||
GdkDevice *device,
|
||
GtkImageDefinition *icon,
|
||
GdkContentFormats *target_list,
|
||
GdkDragAction actions,
|
||
int x,
|
||
int y)
|
||
{
|
||
GtkDragSourceInfo *info;
|
||
GtkWidget *toplevel;
|
||
GdkDrag *drag;
|
||
int dx, dy;
|
||
GtkDragContent *content;
|
||
|
||
if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
|
||
device = gdk_device_get_associated_device (device);
|
||
|
||
toplevel = gtk_widget_get_toplevel (widget);
|
||
gtk_widget_translate_coordinates (widget, toplevel,
|
||
x, y, &x, &y);
|
||
gdk_surface_get_device_position (gtk_widget_get_surface (toplevel),
|
||
device,
|
||
&dx, &dy,
|
||
NULL);
|
||
dx -= x;
|
||
dy -= y;
|
||
|
||
content = g_object_new (GTK_TYPE_DRAG_CONTENT, NULL);
|
||
content->widget = g_object_ref (widget);
|
||
content->formats = gdk_content_formats_ref (target_list);
|
||
|
||
drag = gdk_drag_begin (gtk_widget_get_surface (toplevel), device, GDK_CONTENT_PROVIDER (content), actions, dx, dy);
|
||
if (drag == NULL)
|
||
{
|
||
g_object_unref (content);
|
||
return NULL;
|
||
}
|
||
|
||
content->drag = drag;
|
||
g_object_unref (content);
|
||
|
||
info = gtk_drag_get_source_info (drag, TRUE);
|
||
|
||
g_object_set_data (G_OBJECT (widget), I_("gtk-info"), info);
|
||
|
||
info->widget = g_object_ref (widget);
|
||
|
||
info->target_list = target_list;
|
||
gdk_content_formats_ref (target_list);
|
||
|
||
info->icon_window = NULL;
|
||
info->icon_widget = NULL;
|
||
info->destroy_icon = FALSE;
|
||
|
||
gtk_widget_reset_controllers (widget);
|
||
|
||
g_signal_emit_by_name (widget, "drag-begin", info->drag);
|
||
|
||
/* Ensure that we have an icon before we start the drag; the
|
||
* application may have set one in ::drag_begin, or it may
|
||
* not have set one.
|
||
*/
|
||
if (!info->icon_widget)
|
||
{
|
||
if (icon)
|
||
{
|
||
set_icon_helper (info->drag, icon, 0, 0);
|
||
}
|
||
else
|
||
{
|
||
icon = gtk_image_definition_new_icon_name ("text-x-generic");
|
||
set_icon_helper (info->drag, icon, 0, 0);
|
||
gtk_image_definition_unref (icon);
|
||
}
|
||
}
|
||
|
||
g_signal_connect (drag, "drop-performed",
|
||
G_CALLBACK (gtk_drag_drop_performed_cb), info);
|
||
g_signal_connect (drag, "dnd-finished",
|
||
G_CALLBACK (gtk_drag_dnd_finished_cb), info);
|
||
g_signal_connect (drag, "cancel",
|
||
G_CALLBACK (gtk_drag_cancel_cb), info);
|
||
|
||
return info->drag;
|
||
}
|
||
|
||
/**
|
||
* gtk_drag_begin_with_coordinates: (method)
|
||
* @widget: the source widget
|
||
* @device: (nullable): the device that starts the drag or %NULL to use the default
|
||
* pointer.
|
||
* @targets: The targets (data formats) in which the
|
||
* source can provide the data
|
||
* @actions: A bitmask of the allowed drag actions for this drag
|
||
* @x: The initial x coordinate to start dragging from, in the coordinate space
|
||
* of @widget.
|
||
* @y: The initial y coordinate to start dragging from, in the coordinate space
|
||
* of @widget.
|
||
*
|
||
* Initiates a drag on the source side. The function only needs to be used
|
||
* when the application is starting drags itself, and is not needed when
|
||
* gtk_drag_source_set() is used.
|
||
*
|
||
* Returns: (transfer none): the context for this drag
|
||
*/
|
||
GdkDrag *
|
||
gtk_drag_begin_with_coordinates (GtkWidget *widget,
|
||
GdkDevice *device,
|
||
GdkContentFormats *targets,
|
||
GdkDragAction actions,
|
||
gint x,
|
||
gint y)
|
||
{
|
||
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
|
||
g_return_val_if_fail (device == NULL || GDK_IS_DEVICE (device), NULL);
|
||
g_return_val_if_fail (gtk_widget_get_realized (widget), NULL);
|
||
g_return_val_if_fail (targets != NULL, NULL);
|
||
|
||
if (device == NULL)
|
||
{
|
||
GdkSeat *seat;
|
||
|
||
seat = gdk_display_get_default_seat (gtk_widget_get_display (widget));
|
||
device = gdk_seat_get_pointer (seat);
|
||
}
|
||
|
||
return gtk_drag_begin_internal (widget,
|
||
device,
|
||
NULL,
|
||
targets,
|
||
actions,
|
||
x, y);
|
||
}
|
||
|
||
static void
|
||
icon_widget_destroyed (GtkWidget *widget,
|
||
GtkDragSourceInfo *info)
|
||
{
|
||
g_clear_object (&info->icon_widget);
|
||
}
|
||
|
||
static void
|
||
gtk_drag_set_icon_widget_internal (GdkDrag *drag,
|
||
GtkWidget *widget,
|
||
gint hot_x,
|
||
gint hot_y,
|
||
gboolean destroy_on_release)
|
||
{
|
||
GtkDragSourceInfo *info;
|
||
|
||
g_return_if_fail (!GTK_IS_WINDOW (widget));
|
||
|
||
info = gtk_drag_get_source_info (drag, FALSE);
|
||
if (info == NULL)
|
||
{
|
||
if (destroy_on_release)
|
||
gtk_widget_destroy (widget);
|
||
return;
|
||
}
|
||
|
||
gtk_drag_remove_icon (info);
|
||
|
||
if (widget)
|
||
g_object_ref (widget);
|
||
|
||
info->icon_widget = widget;
|
||
info->destroy_icon = destroy_on_release;
|
||
|
||
if (!widget)
|
||
return;
|
||
|
||
g_signal_connect (widget, "destroy", G_CALLBACK (icon_widget_destroyed), info);
|
||
|
||
gdk_drag_set_hotspot (drag, hot_x, hot_y);
|
||
|
||
if (!info->icon_window)
|
||
{
|
||
GdkDisplay *display;
|
||
|
||
display = gdk_drag_get_display (drag);
|
||
|
||
info->icon_window = gtk_window_new (GTK_WINDOW_POPUP);
|
||
gtk_window_set_type_hint (GTK_WINDOW (info->icon_window), GDK_SURFACE_TYPE_HINT_DND);
|
||
gtk_window_set_display (GTK_WINDOW (info->icon_window), display);
|
||
gtk_widget_set_size_request (info->icon_window, 24, 24);
|
||
gtk_style_context_remove_class (gtk_widget_get_style_context (info->icon_window), "background");
|
||
|
||
gtk_window_set_hardcoded_surface (GTK_WINDOW (info->icon_window),
|
||
gdk_drag_get_drag_surface (drag));
|
||
gtk_widget_show (info->icon_window);
|
||
}
|
||
|
||
if (gtk_bin_get_child (GTK_BIN (info->icon_window)))
|
||
gtk_container_remove (GTK_CONTAINER (info->icon_window), gtk_bin_get_child (GTK_BIN (info->icon_window)));
|
||
gtk_container_add (GTK_CONTAINER (info->icon_window), widget);
|
||
}
|
||
|
||
/**
|
||
* gtk_drag_set_icon_widget:
|
||
* @drag: the context for a drag
|
||
* @widget: a widget to use as an icon
|
||
* @hot_x: the X offset within @widget of the hotspot
|
||
* @hot_y: the Y offset within @widget of the hotspot
|
||
*
|
||
* Changes the icon for drag operation to a given widget.
|
||
* GTK+ will not destroy the widget, so if you don’t want
|
||
* it to persist, you should connect to the “drag-end”
|
||
* signal and destroy it yourself.
|
||
*/
|
||
void
|
||
gtk_drag_set_icon_widget (GdkDrag *drag,
|
||
GtkWidget *widget,
|
||
gint hot_x,
|
||
gint hot_y)
|
||
{
|
||
g_return_if_fail (GDK_IS_DRAG (drag));
|
||
g_return_if_fail (GTK_IS_WIDGET (widget));
|
||
|
||
gtk_drag_set_icon_widget_internal (drag, widget, hot_x, hot_y, FALSE);
|
||
}
|
||
|
||
static void
|
||
set_icon_helper (GdkDrag *drag,
|
||
GtkImageDefinition *def,
|
||
gint hot_x,
|
||
gint hot_y)
|
||
{
|
||
GtkWidget *widget;
|
||
|
||
widget = gtk_image_new ();
|
||
gtk_style_context_add_class (gtk_widget_get_style_context (widget), "drag-icon");
|
||
gtk_image_set_from_definition (GTK_IMAGE (widget), def);
|
||
gtk_drag_set_icon_widget_internal (drag, widget, hot_x, hot_y, TRUE);
|
||
}
|
||
|
||
void
|
||
gtk_drag_set_icon_definition (GdkDrag *drag,
|
||
GtkImageDefinition *def,
|
||
gint hot_x,
|
||
gint hot_y)
|
||
{
|
||
g_return_if_fail (GDK_IS_DRAG (drag));
|
||
g_return_if_fail (def != NULL);
|
||
|
||
set_icon_helper (drag, def, hot_x, hot_y);
|
||
}
|
||
|
||
/**
|
||
* gtk_drag_set_icon_paintable:
|
||
* @drag: the context for a drag
|
||
* @paintable: the #GdkPaintable to use as icon
|
||
* @hot_x: the X offset of the hotspot within the icon
|
||
* @hot_y: the Y offset of the hotspot within the icon
|
||
*
|
||
* Sets @paintable as the icon for a given drag. GTK+ retains
|
||
* references for the arguments, and will release them when
|
||
* they are no longer needed.
|
||
*
|
||
* To position the @paintable relative to the mouse, its top
|
||
* left will be positioned @hot_x, @hot_y pixels from the
|
||
* mouse cursor.
|
||
*/
|
||
void
|
||
gtk_drag_set_icon_paintable (GdkDrag *drag,
|
||
GdkPaintable *paintable,
|
||
int hot_x,
|
||
int hot_y)
|
||
{
|
||
GtkWidget *widget;
|
||
|
||
g_return_if_fail (GDK_IS_DRAG (drag));
|
||
g_return_if_fail (GDK_IS_PAINTABLE (paintable));
|
||
|
||
widget = gtk_picture_new_for_paintable (paintable);
|
||
gtk_picture_set_can_shrink (GTK_PICTURE (widget), FALSE);
|
||
|
||
gtk_drag_set_icon_widget_internal (drag, widget, hot_x, hot_y, TRUE);
|
||
}
|
||
|
||
/**
|
||
* gtk_drag_set_icon_name:
|
||
* @drag: the context for a drag
|
||
* @icon_name: name of icon to use
|
||
* @hot_x: the X offset of the hotspot within the icon
|
||
* @hot_y: the Y offset of the hotspot within the icon
|
||
*
|
||
* Sets the icon for a given drag from a named themed icon. See
|
||
* the docs for #GtkIconTheme for more details. Note that the
|
||
* size of the icon depends on the icon theme (the icon is
|
||
* loaded at the symbolic size #GTK_ICON_SIZE_DND), thus
|
||
* @hot_x and @hot_y have to be used with care.
|
||
*/
|
||
void
|
||
gtk_drag_set_icon_name (GdkDrag *drag,
|
||
const gchar *icon_name,
|
||
gint hot_x,
|
||
gint hot_y)
|
||
{
|
||
GtkImageDefinition *def;
|
||
|
||
g_return_if_fail (GDK_IS_DRAG (drag));
|
||
g_return_if_fail (icon_name != NULL && icon_name[0] != '\0');
|
||
|
||
def = gtk_image_definition_new_icon_name (icon_name);
|
||
set_icon_helper (drag, def, hot_x, hot_y);
|
||
|
||
gtk_image_definition_unref (def);
|
||
}
|
||
|
||
/**
|
||
* gtk_drag_set_icon_gicon:
|
||
* @drag: the context for a drag
|
||
* @icon: a #GIcon
|
||
* @hot_x: the X offset of the hotspot within the icon
|
||
* @hot_y: the Y offset of the hotspot within the icon
|
||
*
|
||
* Sets the icon for a given drag from the given @icon.
|
||
* See the documentation for gtk_drag_set_icon_name()
|
||
* for more details about using icons in drag and drop.
|
||
*/
|
||
void
|
||
gtk_drag_set_icon_gicon (GdkDrag *drag,
|
||
GIcon *icon,
|
||
gint hot_x,
|
||
gint hot_y)
|
||
{
|
||
GtkImageDefinition *def;
|
||
|
||
g_return_if_fail (GDK_IS_DRAG (drag));
|
||
g_return_if_fail (icon != NULL);
|
||
|
||
def = gtk_image_definition_new_gicon (icon);
|
||
set_icon_helper (drag, def, hot_x, hot_y);
|
||
|
||
gtk_image_definition_unref (def);
|
||
}
|
||
|
||
/**
|
||
* gtk_drag_set_icon_default:
|
||
* @drag: the context for a drag
|
||
*
|
||
* Sets the icon for a particular drag to the default
|
||
* icon.
|
||
*/
|
||
void
|
||
gtk_drag_set_icon_default (GdkDrag *drag)
|
||
{
|
||
g_return_if_fail (GDK_IS_DRAG (drag));
|
||
|
||
gtk_drag_set_icon_name (drag, "text-x-generic", -2, -2);
|
||
}
|
||
|
||
/* Clean up from the drag, and display snapback, if necessary. */
|
||
static void
|
||
gtk_drag_drop_finished (GtkDragSourceInfo *info,
|
||
GtkDragResult result)
|
||
{
|
||
gboolean success;
|
||
|
||
success = (result == GTK_DRAG_RESULT_SUCCESS);
|
||
|
||
if (!success)
|
||
g_signal_emit_by_name (info->widget, "drag-failed",
|
||
info->drag, result, &success);
|
||
|
||
gdk_drag_drop_done (info->drag, success);
|
||
gtk_drag_source_info_destroy (info);
|
||
}
|
||
|
||
static void
|
||
gtk_drag_drop (GtkDragSourceInfo *info)
|
||
{
|
||
if (info->icon_window)
|
||
gtk_widget_hide (info->icon_window);
|
||
|
||
info->drop_timeout = g_timeout_add (DROP_ABORT_TIME, gtk_drag_abort_timeout, info);
|
||
g_source_set_name_by_id (info->drop_timeout, "[gtk+] gtk_drag_abort_timeout");
|
||
}
|
||
|
||
/*
|
||
* Source side callbacks.
|
||
*/
|
||
static void
|
||
gtk_drag_remove_icon (GtkDragSourceInfo *info)
|
||
{
|
||
if (info->icon_widget)
|
||
{
|
||
GtkWidget *widget;
|
||
|
||
widget = info->icon_widget;
|
||
info->icon_widget = NULL;
|
||
|
||
g_signal_handlers_disconnect_by_func (widget, icon_widget_destroyed, info);
|
||
|
||
gtk_widget_hide (widget);
|
||
gtk_widget_set_opacity (widget, 1.0);
|
||
|
||
if (info->destroy_icon)
|
||
gtk_widget_destroy (widget);
|
||
else
|
||
gtk_container_remove (GTK_CONTAINER (info->icon_window), widget);
|
||
|
||
g_object_unref (widget);
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_drag_source_info_free (GtkDragSourceInfo *info)
|
||
{
|
||
gtk_drag_remove_icon (info);
|
||
gtk_widget_destroy (info->icon_window);
|
||
g_free (info);
|
||
}
|
||
|
||
static void
|
||
gtk_drag_source_info_destroy (GtkDragSourceInfo *info)
|
||
{
|
||
g_signal_handlers_disconnect_by_func (info->drag, gtk_drag_drop_performed_cb, info);
|
||
g_signal_handlers_disconnect_by_func (info->drag, gtk_drag_dnd_finished_cb, info);
|
||
g_signal_handlers_disconnect_by_func (info->drag, gtk_drag_cancel_cb, info);
|
||
|
||
g_signal_emit_by_name (info->widget, "drag-end", info->drag);
|
||
|
||
g_object_set_data (G_OBJECT (info->widget), I_("gtk-info"), NULL);
|
||
|
||
g_clear_object (&info->widget);
|
||
|
||
gdk_content_formats_unref (info->target_list);
|
||
|
||
if (info->drop_timeout)
|
||
g_source_remove (info->drop_timeout);
|
||
|
||
/* keep the icon_window alive until the (possible) drag cancel animation is done */
|
||
g_object_set_data_full (G_OBJECT (info->drag), "former-gtk-source-info", info, (GDestroyNotify)gtk_drag_source_info_free);
|
||
|
||
gtk_drag_clear_source_info (info->drag);
|
||
g_object_unref (info->drag);
|
||
}
|
||
|
||
/* Called on cancellation of a drag, either by the user
|
||
* or programmatically.
|
||
*/
|
||
static void
|
||
gtk_drag_cancel_internal (GtkDragSourceInfo *info,
|
||
GtkDragResult result)
|
||
{
|
||
gtk_drag_drop_finished (info, result);
|
||
}
|
||
|
||
static void
|
||
gtk_drag_drop_performed_cb (GdkDrag *drag,
|
||
GtkDragSourceInfo *info)
|
||
{
|
||
gtk_drag_drop (info);
|
||
}
|
||
|
||
static void
|
||
gtk_drag_cancel_cb (GdkDrag *drag,
|
||
GdkDragCancelReason reason,
|
||
GtkDragSourceInfo *info)
|
||
{
|
||
GtkDragResult result;
|
||
|
||
switch (reason)
|
||
{
|
||
case GDK_DRAG_CANCEL_NO_TARGET:
|
||
result = GTK_DRAG_RESULT_NO_TARGET;
|
||
break;
|
||
case GDK_DRAG_CANCEL_USER_CANCELLED:
|
||
result = GTK_DRAG_RESULT_USER_CANCELLED;
|
||
break;
|
||
case GDK_DRAG_CANCEL_ERROR:
|
||
default:
|
||
result = GTK_DRAG_RESULT_ERROR;
|
||
break;
|
||
}
|
||
gtk_drag_cancel_internal (info, result);
|
||
}
|
||
|
||
static void
|
||
gtk_drag_dnd_finished_cb (GdkDrag *drag,
|
||
GtkDragSourceInfo *info)
|
||
{
|
||
if (gdk_drag_get_selected_action (drag) == GDK_ACTION_MOVE)
|
||
{
|
||
g_signal_emit_by_name (info->widget,
|
||
"drag-data-delete",
|
||
drag);
|
||
}
|
||
|
||
gtk_drag_source_info_destroy (info);
|
||
}
|
||
|
||
static gboolean
|
||
gtk_drag_abort_timeout (gpointer data)
|
||
{
|
||
GtkDragSourceInfo *info = data;
|
||
|
||
info->drop_timeout = 0;
|
||
gtk_drag_drop_finished (info, GTK_DRAG_RESULT_TIMEOUT_EXPIRED);
|
||
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
/**
|
||
* gtk_drag_check_threshold: (method)
|
||
* @widget: a #GtkWidget
|
||
* @start_x: X coordinate of start of drag
|
||
* @start_y: Y coordinate of start of drag
|
||
* @current_x: current X coordinate
|
||
* @current_y: current Y coordinate
|
||
*
|
||
* Checks to see if a mouse drag starting at (@start_x, @start_y) and ending
|
||
* at (@current_x, @current_y) has passed the GTK+ drag threshold, and thus
|
||
* should trigger the beginning of a drag-and-drop operation.
|
||
*
|
||
* Returns: %TRUE if the drag threshold has been passed.
|
||
*/
|
||
gboolean
|
||
gtk_drag_check_threshold (GtkWidget *widget,
|
||
gint start_x,
|
||
gint start_y,
|
||
gint current_x,
|
||
gint current_y)
|
||
{
|
||
gint drag_threshold;
|
||
|
||
g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
|
||
|
||
drag_threshold = gtk_settings_get_dnd_drag_threshold (gtk_widget_get_settings (widget));
|
||
|
||
return (ABS (current_x - start_x) > drag_threshold ||
|
||
ABS (current_y - start_y) > drag_threshold);
|
||
}
|
||
|
||
/**
|
||
* gtk_drag_cancel:
|
||
* @drag: a drag context, as e.g. returned by gtk_drag_begin_with_coordinates()
|
||
*
|
||
* Cancels an ongoing drag operation on the source side.
|
||
*
|
||
* If you want to be able to cancel a drag operation in this way,
|
||
* you need to keep a pointer to the drag context, either from an
|
||
* explicit call to gtk_drag_begin_with_coordinates(), or by
|
||
* connecting to #GtkWidget::drag-begin.
|
||
*
|
||
* If @context does not refer to an ongoing drag operation, this
|
||
* function does nothing.
|
||
*
|
||
* If a drag is cancelled in this way, the @result argument of
|
||
* #GtkWidget::drag-failed is set to @GTK_DRAG_RESULT_ERROR.
|
||
*/
|
||
void
|
||
gtk_drag_cancel (GdkDrag *drag)
|
||
{
|
||
GtkDragSourceInfo *info;
|
||
|
||
g_return_if_fail (GDK_IS_DRAG (drag));
|
||
|
||
info = gtk_drag_get_source_info (drag, FALSE);
|
||
if (info != NULL)
|
||
gtk_drag_cancel_internal (info, GTK_DRAG_RESULT_ERROR);
|
||
}
|