gtk2/gtk/gtkdragdest.c

955 lines
27 KiB
C
Raw Normal View History

/* 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 "gtkdragdestprivate.h"
#include "gtkintl.h"
#include "gtknative.h"
#include "gtktypebuiltins.h"
#include "gtkeventcontrollerprivate.h"
#include "gtkmarshalers.h"
#include "gtkselectionprivate.h"
/**
2020-01-05 14:36:02 +00:00
* SECTION:gtkdroptarget
2020-01-06 05:29:02 +00:00
* @Short_description: Event controller to receive DND drops
* @Title: GtkDropTarget
*
* GtkDropTarget is an auxiliary object that is used to receive
* Drag-and-Drop operations.
*
* To use a GtkDropTarget to receive drops on a widget, you create
* a GtkDropTarget object, connect to its signals, and then attach
* it to the widget with gtk_widget_add_controller().
*/
struct _GtkDropTarget
{
GtkEventController parent_object;
GdkContentFormats *formats;
GdkDragAction actions;
GtkWidget *widget;
GdkDrop *drop;
2020-01-07 20:39:19 +00:00
gboolean contains;
gboolean contains_pending;
};
struct _GtkDropTargetClass
{
GtkEventControllerClass parent_class;
gboolean (*drag_motion) (GtkDropTarget *dest,
int x,
int y);
};
enum {
PROP_FORMATS = 1,
PROP_ACTIONS,
2020-01-07 20:39:19 +00:00
PROP_CONTAINS,
NUM_PROPERTIES
};
static GParamSpec *properties[NUM_PROPERTIES];
enum {
DRAG_LEAVE,
DRAG_MOTION,
DRAG_DROP,
NUM_SIGNALS
};
static guint signals[NUM_SIGNALS];
static gboolean gtk_drop_target_drag_motion (GtkDropTarget *dest,
int x,
int y);
static gboolean gtk_drop_target_handle_event (GtkEventController *controller,
const GdkEvent *event);
static gboolean gtk_drop_target_filter_event (GtkEventController *controller,
const GdkEvent *event);
static void gtk_drop_target_set_widget (GtkEventController *controller,
GtkWidget *widget);
static void gtk_drop_target_unset_widget (GtkEventController *controller);
2020-01-07 20:39:19 +00:00
static gboolean gtk_drop_target_get_contains (GtkDropTarget *dest);
static void gtk_drop_target_set_contains (GtkDropTarget *dest,
gboolean contains);
G_DEFINE_TYPE (GtkDropTarget, gtk_drop_target, GTK_TYPE_EVENT_CONTROLLER);
static void
gtk_drop_target_init (GtkDropTarget *dest)
{
}
static void
gtk_drop_target_finalize (GObject *object)
{
GtkDropTarget *dest = GTK_DROP_TARGET (object);
g_clear_pointer (&dest->formats, gdk_content_formats_unref);
G_OBJECT_CLASS (gtk_drop_target_parent_class)->finalize (object);
}
static void
gtk_drop_target_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkDropTarget *dest = GTK_DROP_TARGET (object);
switch (prop_id)
{
case PROP_FORMATS:
gtk_drop_target_set_formats (dest, g_value_get_boxed (value));
break;
case PROP_ACTIONS:
gtk_drop_target_set_actions (dest, g_value_get_flags (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gtk_drop_target_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkDropTarget *dest = GTK_DROP_TARGET (object);
switch (prop_id)
{
case PROP_FORMATS:
g_value_set_boxed (value, gtk_drop_target_get_formats (dest));
break;
case PROP_ACTIONS:
g_value_set_flags (value, gtk_drop_target_get_actions (dest));
break;
2020-01-07 20:39:19 +00:00
case PROP_CONTAINS:
g_value_set_boolean (value, gtk_drop_target_get_contains (dest));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gtk_drop_target_class_init (GtkDropTargetClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (class);
object_class->finalize = gtk_drop_target_finalize;
object_class->set_property = gtk_drop_target_set_property;
object_class->get_property = gtk_drop_target_get_property;
controller_class->handle_event = gtk_drop_target_handle_event;
controller_class->filter_event = gtk_drop_target_filter_event;
controller_class->set_widget = gtk_drop_target_set_widget;
controller_class->unset_widget = gtk_drop_target_unset_widget;
class->drag_motion = gtk_drop_target_drag_motion;
/**
* GtkDropTarget:formats:
*
* The #GdkContentFormats that determines the supported data formats
*/
properties[PROP_FORMATS] =
g_param_spec_boxed ("formats", P_("Formats"), P_("Formats"),
GDK_TYPE_CONTENT_FORMATS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkDropTarget:actions:
*
2020-01-05 14:36:02 +00:00
* The #GdkDragActions that this drop target supports
*/
properties[PROP_ACTIONS] =
g_param_spec_flags ("actions", P_("Actions"), P_("Actions"),
GDK_TYPE_DRAG_ACTION, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
/**
2020-01-07 20:39:19 +00:00
* GtkDropTarget:contains:
*
* Whether the drop target is currently the targed of an ongoing drag operation,
* and highlighted.
*/
2020-01-07 20:39:19 +00:00
properties[PROP_CONTAINS] =
g_param_spec_boolean ("contains", P_("Contains an ongoing drag"), P_("Contains the current drag"),
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
/**
* GtkDropTarget::drag-leave:
* @dest: the #GtkDropTarget
*
* The ::drag-leave signal is emitted on the drop site when the cursor
* leaves the widget. A typical reason to connect to this signal is to
* undo things done in #GtkDropTarget::drag-motion, e.g. undo highlighting.
*
* Likewise, the #GtkWidget::drag-leave signal is also emitted before the
* #GtkDropTarget::drag-drop signal, for instance to allow cleaning up of
* a preview item created in the #GtkDropTarget::drag-motion signal handler.
*/
signals[DRAG_LEAVE] =
g_signal_new (I_("drag-leave"),
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
/**
* GtkWidget::drag-motion:
* @dest: the #GtkDropTarget
* @x: the x coordinate of the current cursor position
* @y: the y coordinate of the current cursor position
*
* The ::drag-motion signal is emitted on the drop site when the user
* moves the cursor over the widget during a drag. The signal handler
* must determine whether the cursor position is in a drop zone or not.
* If it is not in a drop zone, it returns %FALSE and no further processing
* is necessary. Otherwise, the handler returns %TRUE. In this case, the
* handler is responsible for providing the necessary information for
* displaying feedback to the user, by calling gdk_drag_status().
*
* The default handler for this signal decides whether to accept the drop
* based on the type of the data.
*
* If the decision whether the drop will be accepted or rejected can't be
* made based solely on the cursor position and the type of the data, the
* handler may inspect the dragged data by calling one of the #GdkDrop
* read functions, and defer the gdk_drag_status() call to when it has
2020-01-05 14:36:02 +00:00
* received the data.
*
* Also note that there is no drag-enter signal. The drag receiver has to
* keep track of whether he has received any drag-motion signals since the
* last #GtkWidget::drag-leave and if not, treat the drag-motion signal as
* an "enter" signal. Upon an "enter", the handler will typically highlight
* the drop site with gtk_drag_highlight().
*
* Returns: whether the cursor position is in a drop zone
*/
signals[DRAG_MOTION] =
g_signal_new (I_("drag-motion"),
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkDropTargetClass, drag_motion),
g_signal_accumulator_first_wins, NULL,
NULL,
G_TYPE_BOOLEAN, 2,
G_TYPE_INT, G_TYPE_INT);
/**
* GtkDropTarget::drag-drop:
* @dest: the #GtkDropTarget
* @x: the x coordinate of the current cursor position
* @y: the y coordinate of the current cursor position
*
* The ::drag-drop signal is emitted on the drop site when the user drops
* the data onto the widget. The signal handler must determine whether
* the cursor position is in a drop zone or not. If it is not in a drop
* zone, it returns %FALSE and no further processing is necessary.
*
* Otherwise, the handler returns %TRUE. In this case, the handler must
* ensure that gdk_drop_finish() is called to let the source know that
* the drop is done. The call to gtk_drag_finish() can be done either
* directly or after receiving the data.
*
2020-01-05 14:36:02 +00:00
* To receive the data, use one of the read functions provides by #GtkDrop
* and #GtkDragDest: gdk_drop_read_async(), gdk_drop_read_value_async(),
* gdk_drop_read_text_async(), gtk_drop_target_read_selection().
*
* You can use gtk_drop_target_get_drop() to obtain the #GtkDrop object
* for the ongoing operation in your signal handler. If you call one of the
* read functions in your handler, GTK will ensure that the #GtkDrop object
* stays alive until the read is completed. If you delay obtaining the data
2020-01-06 05:29:02 +00:00
* (e.g. to handle %GDK_ACTION_ASK by showing a #GtkPopover), you need to
2020-01-05 14:36:02 +00:00
* hold a reference on the #GtkDrop.
*
* Returns: whether the cursor position is in a drop zone
*/
signals[DRAG_DROP] =
g_signal_new (I_("drag-drop"),
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
NULL,
G_TYPE_BOOLEAN, 2,
G_TYPE_INT, G_TYPE_INT);
}
/**
* gtk_drop_target_new:
* @formats: (nullable): the supported data formats
* @actions: the supported actions
*
2020-01-06 05:29:02 +00:00
* Creates a new #GtkDropTarget object.
*
* Returns: the new #GtkDropTarget
*/
GtkDropTarget *
gtk_drop_target_new (GdkContentFormats *formats,
GdkDragAction actions)
{
return g_object_new (GTK_TYPE_DROP_TARGET,
"formats", formats,
"actions", actions,
NULL);
}
/**
* gtk_drop_target_set_formats:
* @dest: a #GtkDropTarget
* @formats: (nullable): the supported data formats
*
2020-01-06 05:29:02 +00:00
* Sets the data formats that this drop target will accept.
*/
void
gtk_drop_target_set_formats (GtkDropTarget *dest,
GdkContentFormats *formats)
{
g_return_if_fail (GTK_IS_DROP_TARGET (dest));
if (dest->formats == formats)
return;
if (dest->formats)
gdk_content_formats_unref (dest->formats);
dest->formats = formats;
if (dest->formats)
gdk_content_formats_ref (dest->formats);
g_object_notify_by_pspec (G_OBJECT (dest), properties[PROP_FORMATS]);
}
/**
* gtk_drop_target_get_formats:
* @dest: a #GtkDropTarget
*
* Gets the data formats that this drop target accepts.
*
* Returns: the supported data formats
*/
GdkContentFormats *
gtk_drop_target_get_formats (GtkDropTarget *dest)
{
g_return_val_if_fail (GTK_IS_DROP_TARGET (dest), NULL);
return dest->formats;
}
/**
* gtk_drop_target_set_actions:
* @dest: a #GtkDropTarget
* @actions: the supported actions
*
* Sets the actions that this drop target supports.
*/
void
gtk_drop_target_set_actions (GtkDropTarget *dest,
GdkDragAction actions)
{
g_return_if_fail (GTK_IS_DROP_TARGET (dest));
if (dest->actions == actions)
return;
dest->actions = actions;
g_object_notify_by_pspec (G_OBJECT (dest), properties[PROP_ACTIONS]);
}
/**
* gtk_drop_target_get_actions:
* @dst: a #GtkDropTarget
*
* Gets the actions that this drop target supports.
*
* Returns: the actions that this drop target supports
*/
GdkDragAction
gtk_drop_target_get_actions (GtkDropTarget *dest)
{
g_return_val_if_fail (GTK_IS_DROP_TARGET (dest), 0);
return dest->actions;
}
static void
gtk_drag_dest_realized (GtkWidget *widget)
{
GtkNative *native = gtk_widget_get_native (widget);
gdk_surface_register_dnd (gtk_native_get_surface (native));
}
static void
gtk_drag_dest_hierarchy_changed (GtkWidget *widget,
GParamSpec *pspec,
gpointer data)
{
GtkNative *native = gtk_widget_get_native (widget);
if (native && gtk_widget_get_realized (GTK_WIDGET (native)))
gdk_surface_register_dnd (gtk_native_get_surface (native));
}
/**
* gtk_drop_target_get_drop:
* @dest: a #GtkDropTarget
*
* Returns the underlying #GtkDrop object for an ongoing drag.
*
* Returns: (nullable): the #GtkDrop of the current drag operation, or %NULL
*/
GdkDrop *
gtk_drop_target_get_drop (GtkDropTarget *dest)
{
g_return_val_if_fail (GTK_IS_DROP_TARGET (dest), NULL);
return dest->drop;
}
static const char *
gtk_drop_target_match (GtkDropTarget *dest,
GdkDrop *drop)
{
GdkContentFormats *formats;
const char *match;
formats = gdk_content_formats_ref (dest->formats);
formats = gdk_content_formats_union_deserialize_mime_types (formats);
match = gdk_content_formats_match_mime_type (formats, gdk_drop_get_formats (drop));
gdk_content_formats_unref (formats);
return match;
}
/**
* gtk_drop_target_find_mimetype:
* @dest: a #GtkDropTarget
*
* Returns a mimetype that is supported both by @dest and the ongoing
* drag.
*
* Returns: (nullable): a matching mimetype for the ongoing drag, or %NULL
*/
const char *
gtk_drop_target_find_mimetype (GtkDropTarget *dest)
{
if (!dest->drop)
return NULL;
return gtk_drop_target_match (dest, dest->drop);
}
static gboolean
gtk_drop_target_drag_motion (GtkDropTarget *dest,
int x,
int y)
{
GdkDrop *drop = dest->drop;
GdkDragAction dest_actions;
GdkDragAction actions;
GdkAtom target;
dest_actions = gtk_drop_target_get_actions (dest);
actions = dest_actions & gdk_drop_get_actions (drop);
target = gtk_drop_target_match (dest, drop);
if (actions && target)
{
gdk_drop_status (drop, dest_actions);
2020-01-07 20:39:19 +00:00
gtk_drop_target_set_contains (dest, TRUE);
}
else
{
gdk_drop_status (drop, 0);
2020-01-07 20:39:19 +00:00
gtk_drop_target_set_contains (dest, FALSE);
}
return TRUE;
}
static void
set_drop (GtkDropTarget *dest,
GdkDrop *drop)
{
if (dest->drop == drop)
return;
if (dest->drop)
g_object_remove_weak_pointer (G_OBJECT (dest->drop), (gpointer *)&dest->drop);
dest->drop = drop;
if (dest->drop)
g_object_add_weak_pointer (G_OBJECT (dest->drop), (gpointer *)&dest->drop);
}
static void
gtk_drop_target_emit_drag_leave (GtkDropTarget *dest,
GdkDrop *drop)
{
set_drop (dest, drop);
g_signal_emit (dest, signals[DRAG_LEAVE], 0, time);
set_drop (dest, NULL);
2020-01-07 20:39:19 +00:00
gtk_drop_target_set_contains (dest, FALSE);
}
static gboolean
gtk_drop_target_emit_drag_motion (GtkDropTarget *dest,
GdkDrop *drop,
int x,
int y)
{
gboolean result = FALSE;
2020-01-07 20:39:19 +00:00
dest->contains_pending = TRUE;
set_drop (dest, drop);
g_signal_emit (dest, signals[DRAG_MOTION], 0, x, y, &result);
2020-01-07 20:39:19 +00:00
if (dest->contains_pending)
gtk_drop_target_set_contains (dest, result);
return result;
}
static gboolean
gtk_drop_target_emit_drag_drop (GtkDropTarget *dest,
GdkDrop *drop,
int x,
int y)
{
gboolean result = FALSE;
set_drop (dest, drop);
g_signal_emit (dest, signals[DRAG_DROP], 0, x, y, &result);
return result;
}
static void
2020-01-07 20:39:19 +00:00
gtk_drop_target_set_contains (GtkDropTarget *dest,
gboolean contains)
{
GtkWidget *widget;
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (dest));
2020-01-07 20:39:19 +00:00
dest->contains_pending = FALSE;
2020-01-07 20:39:19 +00:00
if (dest->contains == contains)
return;
2020-01-07 20:39:19 +00:00
dest->contains = contains;
2020-01-07 20:39:19 +00:00
if (contains)
gtk_drag_highlight (widget);
else
gtk_drag_unhighlight (widget);
2020-01-07 20:39:19 +00:00
g_object_notify_by_pspec (G_OBJECT (dest), properties[PROP_CONTAINS]);
}
static gboolean
2020-01-07 20:39:19 +00:00
gtk_drop_target_get_contains (GtkDropTarget *dest)
{
2020-01-07 20:39:19 +00:00
return dest->contains;
}
static gboolean
gtk_drop_target_filter_event (GtkEventController *controller,
const GdkEvent *event)
{
switch ((int)gdk_event_get_event_type (event))
{
case GDK_DRAG_ENTER:
case GDK_DRAG_LEAVE:
case GDK_DRAG_MOTION:
case GDK_DROP_START:
return GTK_EVENT_CONTROLLER_CLASS (gtk_drop_target_parent_class)->filter_event (controller, event);
default:;
}
return TRUE;
}
static void
clear_current_dest (gpointer data, GObject *former_object)
{
g_object_set_data (G_OBJECT (data), "current-dest", NULL);
}
static void
gtk_drop_set_current_dest (GdkDrop *drop,
GtkDropTarget *dest)
{
GtkDropTarget *old_dest;
old_dest = g_object_get_data (G_OBJECT (drop), "current-dest");
if (old_dest)
g_object_weak_unref (G_OBJECT (old_dest), clear_current_dest, drop);
g_object_set_data (G_OBJECT (drop), "current-dest", dest);
if (dest)
g_object_weak_ref (G_OBJECT (dest), clear_current_dest, drop);
}
static GtkDropTarget *
gtk_drop_get_current_dest (GdkDrop *drop)
{
return g_object_get_data (G_OBJECT (drop), "current-dest");
}
static gboolean
gtk_drop_target_handle_event (GtkEventController *controller,
const GdkEvent *event)
{
GtkDropTarget *dest = GTK_DROP_TARGET (controller);
GdkDrop *drop;
GtkDropTarget *old_dest;
double x, y;
gboolean found = FALSE;
gdk_event_get_coords (event, &x, &y);
drop = gdk_event_get_drop (event);
old_dest = gtk_drop_get_current_dest (drop);
switch ((int)gdk_event_get_event_type (event))
{
case GDK_DRAG_MOTION:
found = gtk_drop_target_emit_drag_motion (dest, drop, x, y);
break;
case GDK_DROP_START:
/* We send a leave before the drop so that the widget unhighlights
* properly.
*/
if (old_dest)
{
gtk_drop_target_emit_drag_leave (old_dest, drop);
gtk_drop_set_current_dest (drop, NULL);
}
found = gtk_drop_target_emit_drag_drop (dest, drop, x, y);
break;
default:
break;
}
if (found)
{
if (old_dest && old_dest != dest)
{
gtk_drop_target_emit_drag_leave (old_dest, drop);
gtk_drop_set_current_dest (drop, NULL);
}
gtk_drop_set_current_dest (drop, dest);
}
return found;
}
/*
* This function is called if none of the event
* controllers has handled a drag event.
*/
void
gtk_drag_dest_handle_event (GtkWidget *toplevel,
GdkEvent *event)
{
GtkDropTarget *dest;
GdkDrop *drop;
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);
switch ((guint) event_type)
{
case GDK_DRAG_ENTER:
break;
case GDK_DRAG_LEAVE:
dest = gtk_drop_get_current_dest (drop);
if (dest)
{
gtk_drop_target_emit_drag_leave (dest, drop);
gtk_drop_set_current_dest (drop, NULL);
}
break;
case GDK_DRAG_MOTION:
case GDK_DROP_START:
gdk_drop_status (drop, 0);
break;
default:
g_assert_not_reached ();
}
}
static void
gtk_drop_target_set_widget (GtkEventController *controller,
GtkWidget *widget)
{
GtkDropTarget *dest = GTK_DROP_TARGET (controller);
GTK_EVENT_CONTROLLER_CLASS (gtk_drop_target_parent_class)->set_widget (controller, widget);
if (gtk_widget_get_realized (widget))
gtk_drag_dest_realized (widget);
g_signal_connect (widget, "realize", G_CALLBACK (gtk_drag_dest_realized), dest);
g_signal_connect (widget, "notify::root", G_CALLBACK (gtk_drag_dest_hierarchy_changed), dest);
}
static void
gtk_drop_target_unset_widget (GtkEventController *controller)
{
GtkWidget *widget;
widget = gtk_event_controller_get_widget (controller);
g_signal_handlers_disconnect_by_func (widget, gtk_drag_dest_realized, controller);
g_signal_handlers_disconnect_by_func (widget, gtk_drag_dest_hierarchy_changed, controller);
GTK_EVENT_CONTROLLER_CLASS (gtk_drop_target_parent_class)->unset_widget (controller);
}
2020-01-02 15:12:44 +00:00
/**
* 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);
}
static void
gtk_drag_get_data_got_data (GObject *source,
GAsyncResult *result,
gpointer data)
{
GTask *task = data;
gssize written;
GError *error = NULL;
guchar *bytes;
gsize size;
GtkSelectionData *sdata;
GtkDropTarget *dest;
GdkDrop *drop;
GdkDisplay *display;
written = g_output_stream_splice_finish (G_OUTPUT_STREAM (source), result, &error);
if (written < 0)
{
g_task_return_error (task, error);
g_object_unref (task);
return;
}
bytes = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (source));
size = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (source));
dest = GTK_DROP_TARGET (g_task_get_source_object (task));
drop = GDK_DROP (g_object_get_data (G_OBJECT (task), "drop"));
display = GDK_DISPLAY (g_object_get_data (G_OBJECT (task), "display"));
sdata = g_slice_new0 (GtkSelectionData);
sdata->target = g_task_get_task_data (task);
sdata->type = g_task_get_task_data (task);
sdata->format = 8;
sdata->length = size;
sdata->data = bytes ? bytes : (guchar *)g_strdup ("");
sdata->display = display;
set_drop (dest, drop);
g_task_return_pointer (task, sdata, NULL);
set_drop (dest, NULL);
g_object_unref (task);
}
static void
gtk_drag_get_data_got_stream (GObject *source,
GAsyncResult *result,
gpointer data)
{
GTask *task = data;
GInputStream *input_stream;
GOutputStream *output_stream;
GError *error = NULL;
const char *mime_type;
input_stream = gdk_drop_read_finish (GDK_DROP (source), result, &mime_type, &error);
if (input_stream == NULL)
{
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_task_set_task_data (task, (gpointer)g_intern_string (mime_type), NULL);
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,
task);
g_object_unref (output_stream);
g_object_unref (input_stream);
}
/**
* gtk_drop_target_read_selection:
* @dest: a #GtkDropTarget
* @target: the data format to read
* @cancellable: (nullable): a cancellable
* @callback: callback to call on completion
* @user_data: data to pass to @callback
*
* Asynchronously reads the dropped data from an ongoing
* drag on a #GtkDropTarget, and returns the data in a
* #GtkSelectionData object.
*
* This function is meant for cases where a #GtkSelectionData
* object is needed, such as when using the #GtkTreeModel DND
* support. In most other cases, the #GdkDrop async read
* APIs that return in input stream or #GValue are more
* convenient and should be preferred.
*/
void
gtk_drop_target_read_selection (GtkDropTarget *dest,
GdkAtom target,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GtkWidget *widget;
g_return_if_fail (GTK_IS_DROP_TARGET (dest));
task = g_task_new (dest, NULL, callback, user_data);
g_object_set_data_full (G_OBJECT (task), "drop", g_object_ref (dest->drop), g_object_unref);
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (dest));
if (widget)
g_object_set_data (G_OBJECT (task), "display", gtk_widget_get_display (widget));
gdk_drop_read_async (dest->drop,
(const char *[2]) { target, NULL },
G_PRIORITY_DEFAULT,
NULL,
gtk_drag_get_data_got_stream,
task);
}
/**
* gtk_drop_target_read_selection_finish:
* @dest: a #GtkDropTarget
* @result: a #GAsyncResult
* @error: (allow-none): location to store error information on failure, or %NULL
*
* Finishes an async drop read operation, see gtk_drop_target_read_selection().
*
* Returns: (nullable) (transfer full): the #GtkSelectionData, or %NULL
*/
GtkSelectionData *
gtk_drop_target_read_selection_finish (GtkDropTarget *dest,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (GTK_IS_DROP_TARGET (dest), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}