gtk2/gdk/gdkdrop.c
Matthias Clasen c8ad4d5deb dnd: Add another assertion
Assert that gdk_drop_finish() is called after the
drop is performed.
2021-06-19 07:50:41 -07:00

1023 lines
31 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright © 2018 Benjamin Otte
*
* 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.1 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
/**
* GdkDrop:
*
* The `GdkDrop` object represents the target of an ongoing DND operation.
*
* Possible drop sites get informed about the status of the ongoing drag
* operation with events of type %GDK_DRAG_ENTER, %GDK_DRAG_LEAVE,
* %GDK_DRAG_MOTION and %GDK_DROP_START. The `GdkDrop` object can be obtained
* from these [class@Gdk.Event] types using [method@Gdk.DNDEvent.get_drop].
*
* The actual data transfer is initiated from the target side via an async
* read, using one of the `GdkDrop` methods for this purpose:
* [method@Gdk.Drop.read_async] or [method@Gdk.Drop.read_value_async].
*
* GTK provides a higher level abstraction based on top of these functions,
* and so they are not normally needed in GTK applications. See the
* "Drag and Drop" section of the GTK documentation for more information.
*/
#include "config.h"
#include "gdkdropprivate.h"
#include "gdkcontentdeserializer.h"
#include "gdkcontentformats.h"
#include "gdkcontentprovider.h"
#include "gdkcontentserializer.h"
#include "gdkcursor.h"
#include "gdkdisplay.h"
#include "gdkenumtypes.h"
#include "gdkeventsprivate.h"
#include "gdkinternals.h"
#include "gdkintl.h"
#include "gdkpipeiostreamprivate.h"
#include "gdksurface.h"
typedef struct _GdkDropPrivate GdkDropPrivate;
struct _GdkDropPrivate {
GdkDevice *device;
GdkDrag *drag;
GdkContentFormats *formats;
GdkSurface *surface;
GdkDragAction actions;
guint entered : 1; /* TRUE if we got an enter event but not a leave event yet */
enum {
GDK_DROP_STATE_NONE, /* pointer is dragging along */
GDK_DROP_STATE_DROPPING, /* DROP_START has been sent */
GDK_DROP_STATE_FINISHED /* gdk_drop_finish() has been called */
} state : 2;
};
enum {
PROP_0,
PROP_ACTIONS,
PROP_DEVICE,
PROP_DISPLAY,
PROP_DRAG,
PROP_FORMATS,
PROP_SURFACE,
N_PROPERTIES
};
static GParamSpec *properties[N_PROPERTIES] = { NULL, };
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdkDrop, gdk_drop, G_TYPE_OBJECT)
static void
gdk_drop_default_status (GdkDrop *self,
GdkDragAction actions,
GdkDragAction preferred)
{
}
static void
gdk_drop_read_local_write_done (GObject *drag,
GAsyncResult *result,
gpointer stream)
{
/* we don't care about the error, we just want to clean up */
gdk_drag_write_finish (GDK_DRAG (drag), result, NULL);
/* XXX: Do we need to close_async() here? */
g_output_stream_close (stream, NULL, NULL);
g_object_unref (stream);
}
static void
gdk_drop_read_local_async (GdkDrop *self,
GdkContentFormats *formats,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
GdkContentFormats *content_formats;
const char *mime_type;
GTask *task;
GdkContentProvider *content;
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_priority (task, io_priority);
g_task_set_source_tag (task, gdk_drop_read_local_async);
if (priv->drag == NULL)
{
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("Dragndrop from other applications is not supported."));
g_object_unref (task);
return;
}
g_object_get (priv->drag, "content", &content, NULL);
content_formats = gdk_content_provider_ref_formats (content);
g_object_unref (content);
content_formats = gdk_content_formats_union_serialize_mime_types (content_formats);
mime_type = gdk_content_formats_match_mime_type (content_formats, formats);
if (mime_type != NULL)
{
GOutputStream *output_stream;
GIOStream *stream;
stream = gdk_pipe_io_stream_new ();
output_stream = g_io_stream_get_output_stream (stream);
gdk_drag_write_async (priv->drag,
mime_type,
output_stream,
io_priority,
cancellable,
gdk_drop_read_local_write_done,
g_object_ref (output_stream));
g_task_set_task_data (task, (gpointer) mime_type, NULL);
g_task_return_pointer (task, g_object_ref (g_io_stream_get_input_stream (stream)), g_object_unref);
g_object_unref (stream);
}
else
{
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("No compatible formats to transfer contents."));
}
gdk_content_formats_unref (content_formats);
g_object_unref (task);
}
static GInputStream *
gdk_drop_read_local_finish (GdkDrop *self,
GAsyncResult *result,
const char **out_mime_type,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, self), NULL);
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_drop_read_local_async, NULL);
if (out_mime_type)
*out_mime_type = g_task_get_task_data (G_TASK (result));
return g_task_propagate_pointer (G_TASK (result), error);
}
static void
gdk_drop_add_formats (GdkDrop *self,
GdkContentFormats *formats)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
formats = gdk_content_formats_union_deserialize_gtypes (gdk_content_formats_ref (formats));
if (priv->formats)
{
formats = gdk_content_formats_union (formats, priv->formats);
gdk_content_formats_unref (priv->formats);
}
priv->formats = formats;
}
static void
gdk_drop_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GdkDrop *self = GDK_DROP (gobject);
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
switch (prop_id)
{
case PROP_ACTIONS:
gdk_drop_set_actions (self, g_value_get_flags (value));
break;
case PROP_DEVICE:
priv->device = g_value_dup_object (value);
g_assert (priv->device != NULL);
if (priv->surface)
g_assert (gdk_surface_get_display (priv->surface) == gdk_device_get_display (priv->device));
break;
case PROP_DRAG:
priv->drag = g_value_dup_object (value);
if (priv->drag)
gdk_drop_add_formats (self, gdk_drag_get_formats (priv->drag));
break;
case PROP_FORMATS:
gdk_drop_add_formats (self, g_value_get_boxed (value));
g_assert (priv->formats != NULL);
break;
case PROP_SURFACE:
priv->surface = g_value_dup_object (value);
g_assert (priv->surface != NULL);
if (priv->device)
g_assert (gdk_surface_get_display (priv->surface) == gdk_device_get_display (priv->device));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
gdk_drop_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GdkDrop *self = GDK_DROP (gobject);
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
switch (prop_id)
{
case PROP_ACTIONS:
g_value_set_flags (value, priv->actions);
break;
case PROP_DEVICE:
g_value_set_object (value, priv->device);
break;
case PROP_DISPLAY:
g_value_set_object (value, gdk_device_get_display (priv->device));
break;
case PROP_DRAG:
g_value_set_object (value, priv->drag);
break;
case PROP_FORMATS:
g_value_set_boxed (value, priv->formats);
break;
case PROP_SURFACE:
g_value_set_object (value, priv->surface);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
gdk_drop_finalize (GObject *object)
{
GdkDrop *self = GDK_DROP (object);
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
/* someone forgot to send a LEAVE signal */
g_warn_if_fail (!priv->entered);
/* Should we emit finish() here if necessary?
* For now that's the backends' job
*/
g_warn_if_fail (priv->state != GDK_DROP_STATE_DROPPING);
g_clear_object (&priv->device);
g_clear_object (&priv->drag);
g_clear_object (&priv->surface);
g_clear_pointer (&priv->formats, gdk_content_formats_unref);
G_OBJECT_CLASS (gdk_drop_parent_class)->finalize (object);
}
static void
gdk_drop_class_init (GdkDropClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
klass->status = gdk_drop_default_status;
object_class->get_property = gdk_drop_get_property;
object_class->set_property = gdk_drop_set_property;
object_class->finalize = gdk_drop_finalize;
/**
* GdkDrop:actions: (attributes org.gtk.Property.get=gdk_drop_get_actions)
*
* The possible actions for this drop
*/
properties[PROP_ACTIONS] =
g_param_spec_flags ("actions",
"Actions",
"The possible actions for this drop",
GDK_TYPE_DRAG_ACTION,
GDK_ACTION_ALL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GdkDrop:device: (attributes org.gtk.Property.get=gdk_drop_get_device)
*
* The `GdkDevice` performing the drop
*/
properties[PROP_DEVICE] =
g_param_spec_object ("device",
"Device",
"The device performing the drop",
GDK_TYPE_DEVICE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GdkDrop:display: (attributes org.gtk.Property.get=gdk_drop_get_display)
*
* The `GdkDisplay` that the drop belongs to.
*/
properties[PROP_DISPLAY] =
g_param_spec_object ("display",
"Display",
"Display this drag belongs to",
GDK_TYPE_DISPLAY,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GdkDrop:drag: (attributes org.gtk.Property.get=gdk_drop_get_drag)
*
* The `GdkDrag` that initiated this drop
*/
properties[PROP_DRAG] =
g_param_spec_object ("drag",
"Drag",
"The drag that initiated this drop",
GDK_TYPE_DRAG,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GdkDrop:formats: (attributes org.gtk.Property.get=gdk_drop_get_formats)
*
* The possible formats that the drop can provide its data in.
*/
properties[PROP_FORMATS] =
g_param_spec_boxed ("formats",
"Formats",
"The possible formats for data",
GDK_TYPE_CONTENT_FORMATS,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GdkDrop:surface: (attributes org.gtk.Property.get=gdk_drop_get_surface)
*
* The `GdkSurface` the drop happens on
*/
properties[PROP_SURFACE] =
g_param_spec_object ("surface",
"Surface",
"The surface the drop is happening on",
GDK_TYPE_SURFACE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, N_PROPERTIES, properties);
}
static void
gdk_drop_init (GdkDrop *self)
{
}
/**
* gdk_drop_get_display: (attributes org.gtk.Method.get_property=display)
* @self: a `GdkDrop`
*
* Gets the `GdkDisplay` that @self was created for.
*
* Returns: (transfer none): a `GdkDisplay`
*/
GdkDisplay *
gdk_drop_get_display (GdkDrop *self)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
g_return_val_if_fail (GDK_IS_DROP (self), NULL);
return gdk_device_get_display (priv->device);
}
/**
* gdk_drop_get_device: (attributes org.gtk.Method.get_property=device)
* @self: a `GdkDrop`
*
* Returns the `GdkDevice` performing the drop.
*
* Returns: (transfer none): The `GdkDevice` performing the drop.
*/
GdkDevice *
gdk_drop_get_device (GdkDrop *self)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
g_return_val_if_fail (GDK_IS_DROP (self), NULL);
return priv->device;
}
/**
* gdk_drop_get_formats: (attributes org.gtk.Method.get_property=formats)
* @self: a `GdkDrop`
*
* Returns the `GdkContentFormats` that the drop offers the data
* to be read in.
*
* Returns: (transfer none): The possible `GdkContentFormats`
*/
GdkContentFormats *
gdk_drop_get_formats (GdkDrop *self)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
g_return_val_if_fail (GDK_IS_DROP (self), NULL);
return priv->formats;
}
/**
* gdk_drop_get_surface: (attributes org.gtk.Method.get_property=surface)
* @self: a `GdkDrop`
*
* Returns the `GdkSurface` performing the drop.
*
* Returns: (transfer none): The `GdkSurface` performing the drop.
*/
GdkSurface *
gdk_drop_get_surface (GdkDrop *self)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
g_return_val_if_fail (GDK_IS_DROP (self), NULL);
return priv->surface;
}
/**
* gdk_drop_get_actions: (attributes org.gtk.Method.get_property=actions)
* @self: a `GdkDrop`
*
* Returns the possible actions for this `GdkDrop`.
*
* If this value contains multiple actions - i.e.
* [func@Gdk.DragAction.is_unique] returns %FALSE for the result -
* [method@Gdk.Drop.finish] must choose the action to use when
* accepting the drop. This will only happen if you passed
* %GDK_ACTION_ASK as one of the possible actions in
* [method@Gdk.Drop.status]. %GDK_ACTION_ASK itself will not
* be included in the actions returned by this function.
*
* This value may change over the lifetime of the [class@Gdk.Drop]
* both as a response to source side actions as well as to calls to
* [method@Gdk.Drop.status] or [method@Gdk.Drop.finish]. The source
* side will not change this value anymore once a drop has started.
*
* Returns: The possible `GdkDragActions`
*/
GdkDragAction
gdk_drop_get_actions (GdkDrop *self)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
g_return_val_if_fail (GDK_IS_DROP (self), 0);
return priv->actions;
}
void
gdk_drop_set_actions (GdkDrop *self,
GdkDragAction actions)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
g_return_if_fail (GDK_IS_DROP (self));
g_return_if_fail (priv->state == GDK_DROP_STATE_NONE);
g_return_if_fail ((actions & GDK_ACTION_ASK) == 0);
if (priv->actions == actions)
return;
priv->actions = actions;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIONS]);
}
/**
* gdk_drop_get_drag: (attributes org.gtk.Method.get_property=drag)
* @self: a `GdkDrop`
*
* If this is an in-app drag-and-drop operation, returns the `GdkDrag`
* that corresponds to this drop.
*
* If it is not, %NULL is returned.
*
* Returns: (transfer none) (nullable): the corresponding `GdkDrag`
*/
GdkDrag *
gdk_drop_get_drag (GdkDrop *self)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
g_return_val_if_fail (GDK_IS_DROP (self), 0);
return priv->drag;
}
/**
* gdk_drop_status:
* @self: a `GdkDrop`
* @actions: Supported actions of the destination, or 0 to indicate
* that a drop will not be accepted
* @preferred: A unique action that's a member of @actions indicating the
* preferred action
*
* Selects all actions that are potentially supported by the destination.
*
* When calling this function, do not restrict the passed in actions to
* the ones provided by [method@Gdk.Drop.get_actions]. Those actions may
* change in the future, even depending on the actions you provide here.
*
* The @preferred action is a hint to the drag'n'drop mechanism about which
* action to use when multiple actions are possible.
*
* This function should be called by drag destinations in response to
* %GDK_DRAG_ENTER or %GDK_DRAG_MOTION events. If the destination does
* not yet know the exact actions it supports, it should set any possible
* actions first and then later call this function again.
*/
void
gdk_drop_status (GdkDrop *self,
GdkDragAction actions,
GdkDragAction preferred)
{
#ifndef G_DISABLE_CHECKS
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
#endif
g_return_if_fail (GDK_IS_DROP (self));
g_return_if_fail (priv->state != GDK_DROP_STATE_FINISHED);
g_return_if_fail (gdk_drag_action_is_unique (preferred));
g_return_if_fail ((preferred & actions) == preferred);
GDK_DROP_GET_CLASS (self)->status (self, actions, preferred);
}
/**
* gdk_drop_finish:
* @self: a `GdkDrop`
* @action: the action performed by the destination or 0 if the drop failed
*
* Ends the drag operation after a drop.
*
* The @action must be a single action selected from the actions
* available via [method@Gdk.Drop.get_actions].
*/
void
gdk_drop_finish (GdkDrop *self,
GdkDragAction action)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
g_return_if_fail (GDK_IS_DROP (self));
g_return_if_fail (priv->state == GDK_DROP_STATE_DROPPING);
g_return_if_fail (gdk_drag_action_is_unique (action));
GDK_DROP_GET_CLASS (self)->finish (self, action);
priv->state = GDK_DROP_STATE_FINISHED;
}
static void
gdk_drop_read_internal (GdkDrop *self,
GdkContentFormats *formats,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
g_return_if_fail (priv->state != GDK_DROP_STATE_FINISHED);
if (priv->drag)
{
gdk_drop_read_local_async (self,
formats,
io_priority,
cancellable,
callback,
user_data);
}
else
{
GDK_DROP_GET_CLASS (self)->read_async (self,
formats,
io_priority,
cancellable,
callback,
user_data);
}
}
/**
* gdk_drop_read_async:
* @self: a `GdkDrop`
* @mime_types: (array zero-terminated=1) (element-type utf8):
* pointer to an array of mime types
* @io_priority: the I/O priority for the read operation
* @cancellable: (nullable): optional `GCancellable` object
* @callback: (scope async): a `GAsyncReadyCallback` to call when
* the request is satisfied
* @user_data: (closure): the data to pass to @callback
*
* Asynchronously read the dropped data from a `GdkDrop`
* in a format that complies with one of the mime types.
*/
void
gdk_drop_read_async (GdkDrop *self,
const char **mime_types,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GdkContentFormats *formats;
g_return_if_fail (GDK_IS_DROP (self));
g_return_if_fail (mime_types != NULL && mime_types[0] != NULL);
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
g_return_if_fail (callback != NULL);
formats = gdk_content_formats_new (mime_types, g_strv_length ((char **) mime_types));
gdk_drop_read_internal (self, formats, io_priority, cancellable, callback, user_data);
gdk_content_formats_unref (formats);
}
/**
* gdk_drop_read_finish:
* @self: a `GdkDrop`
* @result: a `GAsyncResult`
* @out_mime_type: (out) (type utf8): return location for the used mime type
* @error: (nullable): location to store error information on failure
*
* Finishes an async drop read operation.
*
* Note that you must not use blocking read calls on the returned stream
* in the GTK thread, since some platforms might require communication with
* GTK to complete the data transfer. You can use async APIs such as
* g_input_stream_read_bytes_async().
*
* See [method@Gdk.Drop.read_async].
*
* Returns: (nullable) (transfer full): the `GInputStream`
*/
GInputStream *
gdk_drop_read_finish (GdkDrop *self,
GAsyncResult *result,
const char **out_mime_type,
GError **error)
{
g_return_val_if_fail (GDK_IS_DROP (self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
if (g_async_result_is_tagged (result, gdk_drop_read_local_async))
{
return gdk_drop_read_local_finish (self, result, out_mime_type, error);
}
else
{
return GDK_DROP_GET_CLASS (self)->read_finish (self, result, out_mime_type, error);
}
}
static void
gdk_drop_read_value_done (GObject *source,
GAsyncResult *result,
gpointer data)
{
GTask *task = data;
GError *error = NULL;
GValue *value;
value = g_task_get_task_data (task);
if (!gdk_content_deserialize_finish (result, value, &error))
g_task_return_error (task, error);
else
g_task_return_pointer (task, value, NULL);
g_object_unref (task);
}
static void
gdk_drop_read_value_got_stream (GObject *source,
GAsyncResult *result,
gpointer data)
{
GInputStream *stream;
GError *error = NULL;
GTask *task = data;
const char *mime_type;
stream = gdk_drop_read_finish (GDK_DROP (source), result, &mime_type, &error);
if (stream == NULL)
{
g_task_return_error (task, error);
return;
}
gdk_content_deserialize_async (stream,
mime_type,
G_VALUE_TYPE (g_task_get_task_data (task)),
g_task_get_priority (task),
g_task_get_cancellable (task),
gdk_drop_read_value_done,
task);
g_object_unref (stream);
}
static void
free_value (gpointer value)
{
g_value_unset (value);
g_slice_free (GValue, value);
}
static void
gdk_drop_read_value_internal (GdkDrop *self,
GType type,
gpointer source_tag,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
GdkContentFormatsBuilder *builder;
GdkContentFormats *formats;
GValue *value;
GTask *task;
g_return_if_fail (priv->state != GDK_DROP_STATE_FINISHED);
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_priority (task, io_priority);
g_task_set_source_tag (task, source_tag);
value = g_slice_new0 (GValue);
g_value_init (value, type);
g_task_set_task_data (task, value, free_value);
if (priv->drag)
{
GError *error = NULL;
gboolean res;
res = gdk_content_provider_get_value (gdk_drag_get_content (priv->drag),
value,
&error);
if (res)
{
g_task_return_pointer (task, value, NULL);
g_object_unref (task);
return;
}
else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
{
g_task_return_error (task, error);
g_object_unref (task);
return;
}
else
{
/* fall through to regular stream transfer */
g_clear_error (&error);
}
}
builder = gdk_content_formats_builder_new ();
gdk_content_formats_builder_add_gtype (builder, type);
formats = gdk_content_formats_builder_free_to_formats (builder);
formats = gdk_content_formats_union_deserialize_mime_types (formats);
gdk_drop_read_internal (self,
formats,
io_priority,
cancellable,
gdk_drop_read_value_got_stream,
task);
gdk_content_formats_unref (formats);
}
/**
* gdk_drop_read_value_async:
* @self: a `GdkDrop`
* @type: a `GType` to read
* @io_priority: the I/O priority of the request.
* @cancellable: (nullable): optional `GCancellable` object, %NULL to ignore.
* @callback: (scope async): callback to call when the request is satisfied
* @user_data: (closure): the data to pass to callback function
*
* Asynchronously request the drag operation's contents converted
* to the given @type.
*
* When the operation is finished @callback will be called. You must
* then call [method@Gdk.Drop.read_value_finish] to get the resulting
* `GValue`.
*
* For local drag'n'drop operations that are available in the given
* `GType`, the value will be copied directly. Otherwise, GDK will
* try to use [func@Gdk.content_deserialize_async] to convert the data.
*/
void
gdk_drop_read_value_async (GdkDrop *self,
GType type,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail (GDK_IS_DROP (self));
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
g_return_if_fail (callback != NULL);
gdk_drop_read_value_internal (self,
type,
gdk_drop_read_value_async,
io_priority,
cancellable,
callback,
user_data);
}
/**
* gdk_drop_read_value_finish:
* @self: a `GdkDrop`
* @result: a `GAsyncResult`
* @error: a `GError` location to store the error occurring
*
* Finishes an async drop read.
*
* See [method@Gdk.Drop.read_value_async].
*
* Returns: (transfer none): a `GValue` containing the result.
*/
const GValue *
gdk_drop_read_value_finish (GdkDrop *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, self), NULL);
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_drop_read_value_async, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
static void
gdk_drop_do_emit_event (GdkEvent *event,
gboolean dont_queue)
{
if (dont_queue)
{
_gdk_event_emit (event);
gdk_event_unref (event);
}
else
{
_gdk_event_queue_append (gdk_event_get_display (event), event);
}
}
void
gdk_drop_emit_enter_event (GdkDrop *self,
gboolean dont_queue,
double x,
double y,
guint32 time)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
GdkEvent *event;
g_warn_if_fail (!priv->entered);
event = gdk_dnd_event_new (GDK_DRAG_ENTER,
priv->surface,
priv->device,
self,
time,
0, 0);
priv->entered = TRUE;
gdk_drop_do_emit_event (event, dont_queue);
}
void
gdk_drop_emit_motion_event (GdkDrop *self,
gboolean dont_queue,
double x,
double y,
guint32 time)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
GdkEvent *event;
g_warn_if_fail (priv->entered);
event = gdk_dnd_event_new (GDK_DRAG_MOTION,
priv->surface,
priv->device,
self,
time,
x, y);
gdk_drop_do_emit_event (event, dont_queue);
}
void
gdk_drop_emit_leave_event (GdkDrop *self,
gboolean dont_queue,
guint32 time)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
GdkEvent *event;
g_warn_if_fail (priv->entered);
event = gdk_dnd_event_new (GDK_DRAG_LEAVE,
priv->surface,
priv->device,
self,
time,
0, 0);
priv->entered = FALSE;
gdk_drop_do_emit_event (event, dont_queue);
}
void
gdk_drop_emit_drop_event (GdkDrop *self,
gboolean dont_queue,
double x,
double y,
guint32 time)
{
GdkDropPrivate *priv = gdk_drop_get_instance_private (self);
GdkEvent *event;
g_warn_if_fail (priv->entered);
g_warn_if_fail (priv->state == GDK_DROP_STATE_NONE);
event = gdk_dnd_event_new (GDK_DROP_START,
priv->surface,
priv->device,
self,
time,
x, y);
priv->state = GDK_DROP_STATE_DROPPING;
gdk_drop_do_emit_event (event, dont_queue);
}