1 | /* |
2 | * Copyright © 2018 Benjamin Otte |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2.1 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | * |
17 | * Authors: Benjamin Otte <otte@gnome.org> |
18 | */ |
19 | |
20 | /** |
21 | * GdkDrop: |
22 | * |
23 | * The `GdkDrop` object represents the target of an ongoing DND operation. |
24 | * |
25 | * Possible drop sites get informed about the status of the ongoing drag |
26 | * operation with events of type %GDK_DRAG_ENTER, %GDK_DRAG_LEAVE, |
27 | * %GDK_DRAG_MOTION and %GDK_DROP_START. The `GdkDrop` object can be obtained |
28 | * from these [class@Gdk.Event] types using [method@Gdk.DNDEvent.get_drop]. |
29 | * |
30 | * The actual data transfer is initiated from the target side via an async |
31 | * read, using one of the `GdkDrop` methods for this purpose: |
32 | * [method@Gdk.Drop.read_async] or [method@Gdk.Drop.read_value_async]. |
33 | * |
34 | * GTK provides a higher level abstraction based on top of these functions, |
35 | * and so they are not normally needed in GTK applications. See the |
36 | * "Drag and Drop" section of the GTK documentation for more information. |
37 | */ |
38 | |
39 | #include "config.h" |
40 | |
41 | #include "gdkdropprivate.h" |
42 | |
43 | #include "gdkcontentdeserializer.h" |
44 | #include "gdkcontentformats.h" |
45 | #include "gdkcontentprovider.h" |
46 | #include "gdkcontentserializer.h" |
47 | #include "gdkcursor.h" |
48 | #include "gdkdisplay.h" |
49 | #include "gdkdragprivate.h" |
50 | #include "gdkenumtypes.h" |
51 | #include "gdkeventsprivate.h" |
52 | #include "gdkintl.h" |
53 | #include "gdkpipeiostreamprivate.h" |
54 | #include "gdksurface.h" |
55 | |
56 | typedef struct _GdkDropPrivate GdkDropPrivate; |
57 | |
58 | struct _GdkDropPrivate { |
59 | GdkDevice *device; |
60 | GdkDrag *drag; |
61 | GdkContentFormats *formats; |
62 | GdkSurface *surface; |
63 | GdkDragAction actions; |
64 | |
65 | guint entered : 1; /* TRUE if we got an enter event but not a leave event yet */ |
66 | enum { |
67 | GDK_DROP_STATE_NONE, /* pointer is dragging along */ |
68 | GDK_DROP_STATE_DROPPING, /* DROP_START has been sent */ |
69 | GDK_DROP_STATE_FINISHED /* gdk_drop_finish() has been called */ |
70 | } state : 2; |
71 | }; |
72 | |
73 | enum { |
74 | PROP_0, |
75 | PROP_ACTIONS, |
76 | PROP_DEVICE, |
77 | PROP_DISPLAY, |
78 | PROP_DRAG, |
79 | PROP_FORMATS, |
80 | PROP_SURFACE, |
81 | N_PROPERTIES |
82 | }; |
83 | |
84 | static GParamSpec *properties[N_PROPERTIES] = { NULL, }; |
85 | |
86 | G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdkDrop, gdk_drop, G_TYPE_OBJECT) |
87 | |
88 | static void |
89 | gdk_drop_default_status (GdkDrop *self, |
90 | GdkDragAction actions, |
91 | GdkDragAction preferred) |
92 | { |
93 | } |
94 | |
95 | static void |
96 | gdk_drop_read_local_write_done (GObject *drag, |
97 | GAsyncResult *result, |
98 | gpointer stream) |
99 | { |
100 | /* we don't care about the error, we just want to clean up */ |
101 | gdk_drag_write_finish (GDK_DRAG (drag), result, NULL); |
102 | |
103 | /* XXX: Do we need to close_async() here? */ |
104 | g_output_stream_close (stream, NULL, NULL); |
105 | |
106 | g_object_unref (object: stream); |
107 | } |
108 | |
109 | static void |
110 | gdk_drop_read_local_async (GdkDrop *self, |
111 | GdkContentFormats *formats, |
112 | int io_priority, |
113 | GCancellable *cancellable, |
114 | GAsyncReadyCallback callback, |
115 | gpointer user_data) |
116 | { |
117 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
118 | GdkContentFormats *content_formats; |
119 | const char *mime_type; |
120 | GTask *task; |
121 | GdkContentProvider *content; |
122 | |
123 | task = g_task_new (source_object: self, cancellable, callback, callback_data: user_data); |
124 | g_task_set_priority (task, priority: io_priority); |
125 | g_task_set_source_tag (task, gdk_drop_read_local_async); |
126 | |
127 | if (priv->drag == NULL) |
128 | { |
129 | g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED, |
130 | _("Drag’n’drop from other applications is not supported." )); |
131 | g_object_unref (object: task); |
132 | return; |
133 | } |
134 | |
135 | g_object_get (object: priv->drag, first_property_name: "content" , &content, NULL); |
136 | content_formats = gdk_content_provider_ref_formats (provider: content); |
137 | g_object_unref (object: content); |
138 | content_formats = gdk_content_formats_union_serialize_mime_types (formats: content_formats); |
139 | mime_type = gdk_content_formats_match_mime_type (first: content_formats, second: formats); |
140 | |
141 | if (mime_type != NULL) |
142 | { |
143 | GOutputStream *output_stream; |
144 | GIOStream *stream; |
145 | |
146 | stream = gdk_pipe_io_stream_new (); |
147 | output_stream = g_io_stream_get_output_stream (stream); |
148 | gdk_drag_write_async (drag: priv->drag, |
149 | mime_type, |
150 | stream: output_stream, |
151 | io_priority, |
152 | cancellable, |
153 | callback: gdk_drop_read_local_write_done, |
154 | g_object_ref (output_stream)); |
155 | g_task_set_task_data (task, task_data: (gpointer) mime_type, NULL); |
156 | g_task_return_pointer (task, g_object_ref (g_io_stream_get_input_stream (stream)), result_destroy: g_object_unref); |
157 | |
158 | g_object_unref (object: stream); |
159 | } |
160 | else |
161 | { |
162 | g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED, |
163 | _("No compatible formats to transfer contents." )); |
164 | } |
165 | |
166 | gdk_content_formats_unref (formats: content_formats); |
167 | g_object_unref (object: task); |
168 | } |
169 | |
170 | static GInputStream * |
171 | gdk_drop_read_local_finish (GdkDrop *self, |
172 | GAsyncResult *result, |
173 | const char **out_mime_type, |
174 | GError **error) |
175 | { |
176 | g_return_val_if_fail (g_task_is_valid (result, self), NULL); |
177 | g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_drop_read_local_async, NULL); |
178 | |
179 | if (out_mime_type) |
180 | *out_mime_type = g_task_get_task_data (G_TASK (result)); |
181 | |
182 | return g_task_propagate_pointer (G_TASK (result), error); |
183 | } |
184 | |
185 | static void |
186 | gdk_drop_add_formats (GdkDrop *self, |
187 | GdkContentFormats *formats) |
188 | { |
189 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
190 | |
191 | formats = gdk_content_formats_union_deserialize_gtypes (formats: gdk_content_formats_ref (formats)); |
192 | |
193 | if (priv->formats) |
194 | { |
195 | formats = gdk_content_formats_union (first: formats, second: priv->formats); |
196 | gdk_content_formats_unref (formats: priv->formats); |
197 | } |
198 | |
199 | priv->formats = formats; |
200 | } |
201 | |
202 | static void |
203 | gdk_drop_set_property (GObject *gobject, |
204 | guint prop_id, |
205 | const GValue *value, |
206 | GParamSpec *pspec) |
207 | { |
208 | GdkDrop *self = GDK_DROP (gobject); |
209 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
210 | |
211 | switch (prop_id) |
212 | { |
213 | case PROP_ACTIONS: |
214 | gdk_drop_set_actions (self, actions: g_value_get_flags (value)); |
215 | break; |
216 | |
217 | case PROP_DEVICE: |
218 | priv->device = g_value_dup_object (value); |
219 | g_assert (priv->device != NULL); |
220 | if (priv->surface) |
221 | g_assert (gdk_surface_get_display (priv->surface) == gdk_device_get_display (priv->device)); |
222 | break; |
223 | |
224 | case PROP_DRAG: |
225 | priv->drag = g_value_dup_object (value); |
226 | if (priv->drag) |
227 | gdk_drop_add_formats (self, formats: gdk_drag_get_formats (drag: priv->drag)); |
228 | break; |
229 | |
230 | case PROP_FORMATS: |
231 | gdk_drop_add_formats (self, formats: g_value_get_boxed (value)); |
232 | g_assert (priv->formats != NULL); |
233 | break; |
234 | |
235 | case PROP_SURFACE: |
236 | priv->surface = g_value_dup_object (value); |
237 | g_assert (priv->surface != NULL); |
238 | if (priv->device) |
239 | g_assert (gdk_surface_get_display (priv->surface) == gdk_device_get_display (priv->device)); |
240 | break; |
241 | |
242 | default: |
243 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
244 | break; |
245 | } |
246 | } |
247 | |
248 | static void |
249 | gdk_drop_get_property (GObject *gobject, |
250 | guint prop_id, |
251 | GValue *value, |
252 | GParamSpec *pspec) |
253 | { |
254 | GdkDrop *self = GDK_DROP (gobject); |
255 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
256 | |
257 | switch (prop_id) |
258 | { |
259 | case PROP_ACTIONS: |
260 | g_value_set_flags (value, v_flags: priv->actions); |
261 | break; |
262 | |
263 | case PROP_DEVICE: |
264 | g_value_set_object (value, v_object: priv->device); |
265 | break; |
266 | |
267 | case PROP_DISPLAY: |
268 | g_value_set_object (value, v_object: gdk_device_get_display (device: priv->device)); |
269 | break; |
270 | |
271 | case PROP_DRAG: |
272 | g_value_set_object (value, v_object: priv->drag); |
273 | break; |
274 | |
275 | case PROP_FORMATS: |
276 | g_value_set_boxed (value, v_boxed: priv->formats); |
277 | break; |
278 | |
279 | case PROP_SURFACE: |
280 | g_value_set_object (value, v_object: priv->surface); |
281 | break; |
282 | |
283 | default: |
284 | G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); |
285 | break; |
286 | } |
287 | } |
288 | |
289 | static void |
290 | gdk_drop_finalize (GObject *object) |
291 | { |
292 | GdkDrop *self = GDK_DROP (object); |
293 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
294 | |
295 | /* someone forgot to send a LEAVE signal */ |
296 | g_warn_if_fail (!priv->entered); |
297 | |
298 | /* Should we emit finish() here if necessary? |
299 | * For now that's the backends' job |
300 | */ |
301 | g_warn_if_fail (priv->state != GDK_DROP_STATE_DROPPING); |
302 | |
303 | g_clear_object (&priv->device); |
304 | g_clear_object (&priv->drag); |
305 | g_clear_object (&priv->surface); |
306 | g_clear_pointer (&priv->formats, gdk_content_formats_unref); |
307 | |
308 | G_OBJECT_CLASS (gdk_drop_parent_class)->finalize (object); |
309 | } |
310 | |
311 | static void |
312 | gdk_drop_class_init (GdkDropClass *klass) |
313 | { |
314 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
315 | |
316 | klass->status = gdk_drop_default_status; |
317 | |
318 | object_class->get_property = gdk_drop_get_property; |
319 | object_class->set_property = gdk_drop_set_property; |
320 | object_class->finalize = gdk_drop_finalize; |
321 | |
322 | /** |
323 | * GdkDrop:actions: (attributes org.gtk.Property.get=gdk_drop_get_actions) |
324 | * |
325 | * The possible actions for this drop |
326 | */ |
327 | properties[PROP_ACTIONS] = |
328 | g_param_spec_flags (name: "actions" , |
329 | nick: "Actions" , |
330 | blurb: "The possible actions for this drop" , |
331 | flags_type: GDK_TYPE_DRAG_ACTION, |
332 | GDK_ACTION_ALL, |
333 | flags: G_PARAM_READWRITE | |
334 | G_PARAM_CONSTRUCT_ONLY | |
335 | G_PARAM_STATIC_STRINGS | |
336 | G_PARAM_EXPLICIT_NOTIFY); |
337 | |
338 | /** |
339 | * GdkDrop:device: (attributes org.gtk.Property.get=gdk_drop_get_device) |
340 | * |
341 | * The `GdkDevice` performing the drop |
342 | */ |
343 | properties[PROP_DEVICE] = |
344 | g_param_spec_object (name: "device" , |
345 | nick: "Device" , |
346 | blurb: "The device performing the drop" , |
347 | GDK_TYPE_DEVICE, |
348 | flags: G_PARAM_READWRITE | |
349 | G_PARAM_CONSTRUCT_ONLY | |
350 | G_PARAM_STATIC_STRINGS | |
351 | G_PARAM_EXPLICIT_NOTIFY); |
352 | |
353 | /** |
354 | * GdkDrop:display: (attributes org.gtk.Property.get=gdk_drop_get_display) |
355 | * |
356 | * The `GdkDisplay` that the drop belongs to. |
357 | */ |
358 | properties[PROP_DISPLAY] = |
359 | g_param_spec_object (name: "display" , |
360 | nick: "Display" , |
361 | blurb: "Display this drag belongs to" , |
362 | GDK_TYPE_DISPLAY, |
363 | flags: G_PARAM_READABLE | |
364 | G_PARAM_STATIC_STRINGS | |
365 | G_PARAM_EXPLICIT_NOTIFY); |
366 | |
367 | /** |
368 | * GdkDrop:drag: (attributes org.gtk.Property.get=gdk_drop_get_drag) |
369 | * |
370 | * The `GdkDrag` that initiated this drop |
371 | */ |
372 | properties[PROP_DRAG] = |
373 | g_param_spec_object (name: "drag" , |
374 | nick: "Drag" , |
375 | blurb: "The drag that initiated this drop" , |
376 | GDK_TYPE_DRAG, |
377 | flags: G_PARAM_READWRITE | |
378 | G_PARAM_CONSTRUCT_ONLY | |
379 | G_PARAM_STATIC_STRINGS | |
380 | G_PARAM_EXPLICIT_NOTIFY); |
381 | |
382 | /** |
383 | * GdkDrop:formats: (attributes org.gtk.Property.get=gdk_drop_get_formats) |
384 | * |
385 | * The possible formats that the drop can provide its data in. |
386 | */ |
387 | properties[PROP_FORMATS] = |
388 | g_param_spec_boxed (name: "formats" , |
389 | nick: "Formats" , |
390 | blurb: "The possible formats for data" , |
391 | GDK_TYPE_CONTENT_FORMATS, |
392 | flags: G_PARAM_READWRITE | |
393 | G_PARAM_CONSTRUCT_ONLY | |
394 | G_PARAM_STATIC_STRINGS | |
395 | G_PARAM_EXPLICIT_NOTIFY); |
396 | |
397 | /** |
398 | * GdkDrop:surface: (attributes org.gtk.Property.get=gdk_drop_get_surface) |
399 | * |
400 | * The `GdkSurface` the drop happens on |
401 | */ |
402 | properties[PROP_SURFACE] = |
403 | g_param_spec_object (name: "surface" , |
404 | nick: "Surface" , |
405 | blurb: "The surface the drop is happening on" , |
406 | GDK_TYPE_SURFACE, |
407 | flags: G_PARAM_READWRITE | |
408 | G_PARAM_CONSTRUCT_ONLY | |
409 | G_PARAM_STATIC_STRINGS | |
410 | G_PARAM_EXPLICIT_NOTIFY); |
411 | |
412 | g_object_class_install_properties (oclass: object_class, n_pspecs: N_PROPERTIES, pspecs: properties); |
413 | } |
414 | |
415 | static void |
416 | gdk_drop_init (GdkDrop *self) |
417 | { |
418 | } |
419 | |
420 | /** |
421 | * gdk_drop_get_display: (attributes org.gtk.Method.get_property=display) |
422 | * @self: a `GdkDrop` |
423 | * |
424 | * Gets the `GdkDisplay` that @self was created for. |
425 | * |
426 | * Returns: (transfer none): a `GdkDisplay` |
427 | */ |
428 | GdkDisplay * |
429 | gdk_drop_get_display (GdkDrop *self) |
430 | { |
431 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
432 | |
433 | g_return_val_if_fail (GDK_IS_DROP (self), NULL); |
434 | |
435 | return gdk_device_get_display (device: priv->device); |
436 | } |
437 | |
438 | /** |
439 | * gdk_drop_get_device: (attributes org.gtk.Method.get_property=device) |
440 | * @self: a `GdkDrop` |
441 | * |
442 | * Returns the `GdkDevice` performing the drop. |
443 | * |
444 | * Returns: (transfer none): The `GdkDevice` performing the drop. |
445 | */ |
446 | GdkDevice * |
447 | gdk_drop_get_device (GdkDrop *self) |
448 | { |
449 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
450 | |
451 | g_return_val_if_fail (GDK_IS_DROP (self), NULL); |
452 | |
453 | return priv->device; |
454 | } |
455 | |
456 | /** |
457 | * gdk_drop_get_formats: (attributes org.gtk.Method.get_property=formats) |
458 | * @self: a `GdkDrop` |
459 | * |
460 | * Returns the `GdkContentFormats` that the drop offers the data |
461 | * to be read in. |
462 | * |
463 | * Returns: (transfer none): The possible `GdkContentFormats` |
464 | */ |
465 | GdkContentFormats * |
466 | gdk_drop_get_formats (GdkDrop *self) |
467 | { |
468 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
469 | |
470 | g_return_val_if_fail (GDK_IS_DROP (self), NULL); |
471 | |
472 | return priv->formats; |
473 | } |
474 | |
475 | /** |
476 | * gdk_drop_get_surface: (attributes org.gtk.Method.get_property=surface) |
477 | * @self: a `GdkDrop` |
478 | * |
479 | * Returns the `GdkSurface` performing the drop. |
480 | * |
481 | * Returns: (transfer none): The `GdkSurface` performing the drop. |
482 | */ |
483 | GdkSurface * |
484 | gdk_drop_get_surface (GdkDrop *self) |
485 | { |
486 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
487 | |
488 | g_return_val_if_fail (GDK_IS_DROP (self), NULL); |
489 | |
490 | return priv->surface; |
491 | } |
492 | |
493 | /** |
494 | * gdk_drop_get_actions: (attributes org.gtk.Method.get_property=actions) |
495 | * @self: a `GdkDrop` |
496 | * |
497 | * Returns the possible actions for this `GdkDrop`. |
498 | * |
499 | * If this value contains multiple actions - i.e. |
500 | * [func@Gdk.DragAction.is_unique] returns %FALSE for the result - |
501 | * [method@Gdk.Drop.finish] must choose the action to use when |
502 | * accepting the drop. This will only happen if you passed |
503 | * %GDK_ACTION_ASK as one of the possible actions in |
504 | * [method@Gdk.Drop.status]. %GDK_ACTION_ASK itself will not |
505 | * be included in the actions returned by this function. |
506 | * |
507 | * This value may change over the lifetime of the [class@Gdk.Drop] |
508 | * both as a response to source side actions as well as to calls to |
509 | * [method@Gdk.Drop.status] or [method@Gdk.Drop.finish]. The source |
510 | * side will not change this value anymore once a drop has started. |
511 | * |
512 | * Returns: The possible `GdkDragActions` |
513 | */ |
514 | GdkDragAction |
515 | gdk_drop_get_actions (GdkDrop *self) |
516 | { |
517 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
518 | |
519 | g_return_val_if_fail (GDK_IS_DROP (self), 0); |
520 | |
521 | return priv->actions; |
522 | } |
523 | |
524 | void |
525 | gdk_drop_set_actions (GdkDrop *self, |
526 | GdkDragAction actions) |
527 | { |
528 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
529 | |
530 | g_return_if_fail (GDK_IS_DROP (self)); |
531 | g_return_if_fail (priv->state == GDK_DROP_STATE_NONE); |
532 | g_return_if_fail ((actions & GDK_ACTION_ASK) == 0); |
533 | |
534 | if (priv->actions == actions) |
535 | return; |
536 | |
537 | priv->actions = actions; |
538 | |
539 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ACTIONS]); |
540 | } |
541 | |
542 | /** |
543 | * gdk_drop_get_drag: (attributes org.gtk.Method.get_property=drag) |
544 | * @self: a `GdkDrop` |
545 | * |
546 | * If this is an in-app drag-and-drop operation, returns the `GdkDrag` |
547 | * that corresponds to this drop. |
548 | * |
549 | * If it is not, %NULL is returned. |
550 | * |
551 | * Returns: (transfer none) (nullable): the corresponding `GdkDrag` |
552 | */ |
553 | GdkDrag * |
554 | gdk_drop_get_drag (GdkDrop *self) |
555 | { |
556 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
557 | |
558 | g_return_val_if_fail (GDK_IS_DROP (self), 0); |
559 | |
560 | return priv->drag; |
561 | } |
562 | |
563 | /** |
564 | * gdk_drop_status: |
565 | * @self: a `GdkDrop` |
566 | * @actions: Supported actions of the destination, or 0 to indicate |
567 | * that a drop will not be accepted |
568 | * @preferred: A unique action that's a member of @actions indicating the |
569 | * preferred action |
570 | * |
571 | * Selects all actions that are potentially supported by the destination. |
572 | * |
573 | * When calling this function, do not restrict the passed in actions to |
574 | * the ones provided by [method@Gdk.Drop.get_actions]. Those actions may |
575 | * change in the future, even depending on the actions you provide here. |
576 | * |
577 | * The @preferred action is a hint to the drag-and-drop mechanism about which |
578 | * action to use when multiple actions are possible. |
579 | * |
580 | * This function should be called by drag destinations in response to |
581 | * %GDK_DRAG_ENTER or %GDK_DRAG_MOTION events. If the destination does |
582 | * not yet know the exact actions it supports, it should set any possible |
583 | * actions first and then later call this function again. |
584 | */ |
585 | void |
586 | gdk_drop_status (GdkDrop *self, |
587 | GdkDragAction actions, |
588 | GdkDragAction preferred) |
589 | { |
590 | #ifndef G_DISABLE_CHECKS |
591 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
592 | #endif |
593 | |
594 | g_return_if_fail (GDK_IS_DROP (self)); |
595 | g_return_if_fail (priv->state != GDK_DROP_STATE_FINISHED); |
596 | g_return_if_fail (gdk_drag_action_is_unique (preferred)); |
597 | g_return_if_fail ((preferred & actions) == preferred); |
598 | |
599 | GDK_DROP_GET_CLASS (self)->status (self, actions, preferred); |
600 | } |
601 | |
602 | /** |
603 | * gdk_drop_finish: |
604 | * @self: a `GdkDrop` |
605 | * @action: the action performed by the destination or 0 if the drop failed |
606 | * |
607 | * Ends the drag operation after a drop. |
608 | * |
609 | * The @action must be a single action selected from the actions |
610 | * available via [method@Gdk.Drop.get_actions]. |
611 | */ |
612 | void |
613 | gdk_drop_finish (GdkDrop *self, |
614 | GdkDragAction action) |
615 | { |
616 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
617 | |
618 | g_return_if_fail (GDK_IS_DROP (self)); |
619 | g_return_if_fail (priv->state == GDK_DROP_STATE_DROPPING); |
620 | g_return_if_fail (gdk_drag_action_is_unique (action)); |
621 | |
622 | GDK_DROP_GET_CLASS (self)->finish (self, action); |
623 | |
624 | priv->state = GDK_DROP_STATE_FINISHED; |
625 | } |
626 | |
627 | static void |
628 | gdk_drop_read_internal (GdkDrop *self, |
629 | GdkContentFormats *formats, |
630 | int io_priority, |
631 | GCancellable *cancellable, |
632 | GAsyncReadyCallback callback, |
633 | gpointer user_data) |
634 | { |
635 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
636 | |
637 | g_return_if_fail (priv->state != GDK_DROP_STATE_FINISHED); |
638 | |
639 | if (priv->drag) |
640 | { |
641 | gdk_drop_read_local_async (self, |
642 | formats, |
643 | io_priority, |
644 | cancellable, |
645 | callback, |
646 | user_data); |
647 | } |
648 | else |
649 | { |
650 | GDK_DROP_GET_CLASS (self)->read_async (self, |
651 | formats, |
652 | io_priority, |
653 | cancellable, |
654 | callback, |
655 | user_data); |
656 | } |
657 | } |
658 | |
659 | /** |
660 | * gdk_drop_read_async: |
661 | * @self: a `GdkDrop` |
662 | * @mime_types: (array zero-terminated=1) (element-type utf8): |
663 | * pointer to an array of mime types |
664 | * @io_priority: the I/O priority for the read operation |
665 | * @cancellable: (nullable): optional `GCancellable` object |
666 | * @callback: (scope async): a `GAsyncReadyCallback` to call when |
667 | * the request is satisfied |
668 | * @user_data: (closure): the data to pass to @callback |
669 | * |
670 | * Asynchronously read the dropped data from a `GdkDrop` |
671 | * in a format that complies with one of the mime types. |
672 | */ |
673 | void |
674 | gdk_drop_read_async (GdkDrop *self, |
675 | const char **mime_types, |
676 | int io_priority, |
677 | GCancellable *cancellable, |
678 | GAsyncReadyCallback callback, |
679 | gpointer user_data) |
680 | { |
681 | GdkContentFormats *formats; |
682 | |
683 | g_return_if_fail (GDK_IS_DROP (self)); |
684 | g_return_if_fail (mime_types != NULL && mime_types[0] != NULL); |
685 | g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); |
686 | g_return_if_fail (callback != NULL); |
687 | |
688 | formats = gdk_content_formats_new (mime_types, n_mime_types: g_strv_length (str_array: (char **) mime_types)); |
689 | |
690 | gdk_drop_read_internal (self, formats, io_priority, cancellable, callback, user_data); |
691 | |
692 | gdk_content_formats_unref (formats); |
693 | } |
694 | |
695 | /** |
696 | * gdk_drop_read_finish: |
697 | * @self: a `GdkDrop` |
698 | * @result: a `GAsyncResult` |
699 | * @out_mime_type: (out) (type utf8): return location for the used mime type |
700 | * @error: (nullable): location to store error information on failure |
701 | * |
702 | * Finishes an async drop read operation. |
703 | * |
704 | * Note that you must not use blocking read calls on the returned stream |
705 | * in the GTK thread, since some platforms might require communication with |
706 | * GTK to complete the data transfer. You can use async APIs such as |
707 | * g_input_stream_read_bytes_async(). |
708 | * |
709 | * See [method@Gdk.Drop.read_async]. |
710 | * |
711 | * Returns: (nullable) (transfer full): the `GInputStream` |
712 | */ |
713 | GInputStream * |
714 | gdk_drop_read_finish (GdkDrop *self, |
715 | GAsyncResult *result, |
716 | const char **out_mime_type, |
717 | GError **error) |
718 | { |
719 | g_return_val_if_fail (GDK_IS_DROP (self), NULL); |
720 | g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
721 | |
722 | if (g_async_result_is_tagged (res: result, source_tag: gdk_drop_read_local_async)) |
723 | { |
724 | return gdk_drop_read_local_finish (self, result, out_mime_type, error); |
725 | } |
726 | else |
727 | { |
728 | return GDK_DROP_GET_CLASS (self)->read_finish (self, result, out_mime_type, error); |
729 | } |
730 | } |
731 | |
732 | static void |
733 | gdk_drop_read_value_done (GObject *source, |
734 | GAsyncResult *result, |
735 | gpointer data) |
736 | { |
737 | GTask *task = data; |
738 | GError *error = NULL; |
739 | GValue *value; |
740 | |
741 | value = g_task_get_task_data (task); |
742 | |
743 | if (!gdk_content_deserialize_finish (result, value, error: &error)) |
744 | g_task_return_error (task, error); |
745 | else |
746 | g_task_return_pointer (task, result: value, NULL); |
747 | |
748 | g_object_unref (object: task); |
749 | } |
750 | |
751 | static void |
752 | gdk_drop_read_value_got_stream (GObject *source, |
753 | GAsyncResult *result, |
754 | gpointer data) |
755 | { |
756 | GInputStream *stream; |
757 | GError *error = NULL; |
758 | GTask *task = data; |
759 | const char *mime_type; |
760 | |
761 | stream = gdk_drop_read_finish (GDK_DROP (source), result, out_mime_type: &mime_type, error: &error); |
762 | if (stream == NULL) |
763 | { |
764 | g_task_return_error (task, error); |
765 | return; |
766 | } |
767 | |
768 | gdk_content_deserialize_async (stream, |
769 | mime_type, |
770 | G_VALUE_TYPE (g_task_get_task_data (task)), |
771 | io_priority: g_task_get_priority (task), |
772 | cancellable: g_task_get_cancellable (task), |
773 | callback: gdk_drop_read_value_done, |
774 | user_data: task); |
775 | g_object_unref (object: stream); |
776 | } |
777 | |
778 | static void |
779 | free_value (gpointer value) |
780 | { |
781 | g_value_unset (value); |
782 | g_slice_free (GValue, value); |
783 | } |
784 | |
785 | static void |
786 | gdk_drop_read_value_internal (GdkDrop *self, |
787 | GType type, |
788 | gpointer source_tag, |
789 | int io_priority, |
790 | GCancellable *cancellable, |
791 | GAsyncReadyCallback callback, |
792 | gpointer user_data) |
793 | { |
794 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
795 | GdkContentFormatsBuilder *builder; |
796 | GdkContentFormats *formats; |
797 | GValue *value; |
798 | GTask *task; |
799 | |
800 | g_return_if_fail (priv->state != GDK_DROP_STATE_FINISHED); |
801 | |
802 | task = g_task_new (source_object: self, cancellable, callback, callback_data: user_data); |
803 | g_task_set_priority (task, priority: io_priority); |
804 | g_task_set_source_tag (task, source_tag); |
805 | value = g_slice_new0 (GValue); |
806 | g_value_init (value, g_type: type); |
807 | g_task_set_task_data (task, task_data: value, task_data_destroy: free_value); |
808 | |
809 | if (priv->drag) |
810 | { |
811 | GError *error = NULL; |
812 | gboolean res; |
813 | |
814 | res = gdk_content_provider_get_value (provider: gdk_drag_get_content (drag: priv->drag), |
815 | value, |
816 | error: &error); |
817 | |
818 | if (res) |
819 | { |
820 | g_task_return_pointer (task, result: value, NULL); |
821 | g_object_unref (object: task); |
822 | return; |
823 | } |
824 | else if (!g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED)) |
825 | { |
826 | g_task_return_error (task, error); |
827 | g_object_unref (object: task); |
828 | return; |
829 | } |
830 | else |
831 | { |
832 | /* fall through to regular stream transfer */ |
833 | g_clear_error (err: &error); |
834 | } |
835 | } |
836 | |
837 | builder = gdk_content_formats_builder_new (); |
838 | gdk_content_formats_builder_add_gtype (builder, type); |
839 | formats = gdk_content_formats_builder_free_to_formats (builder); |
840 | formats = gdk_content_formats_union_deserialize_mime_types (formats); |
841 | |
842 | gdk_drop_read_internal (self, |
843 | formats, |
844 | io_priority, |
845 | cancellable, |
846 | callback: gdk_drop_read_value_got_stream, |
847 | user_data: task); |
848 | |
849 | gdk_content_formats_unref (formats); |
850 | } |
851 | |
852 | /** |
853 | * gdk_drop_read_value_async: |
854 | * @self: a `GdkDrop` |
855 | * @type: a `GType` to read |
856 | * @io_priority: the I/O priority of the request. |
857 | * @cancellable: (nullable): optional `GCancellable` object, %NULL to ignore. |
858 | * @callback: (scope async): callback to call when the request is satisfied |
859 | * @user_data: (closure): the data to pass to callback function |
860 | * |
861 | * Asynchronously request the drag operation's contents converted |
862 | * to the given @type. |
863 | * |
864 | * When the operation is finished @callback will be called. You must |
865 | * then call [method@Gdk.Drop.read_value_finish] to get the resulting |
866 | * `GValue`. |
867 | * |
868 | * For local drag-and-drop operations that are available in the given |
869 | * `GType`, the value will be copied directly. Otherwise, GDK will |
870 | * try to use [func@Gdk.content_deserialize_async] to convert the data. |
871 | */ |
872 | void |
873 | gdk_drop_read_value_async (GdkDrop *self, |
874 | GType type, |
875 | int io_priority, |
876 | GCancellable *cancellable, |
877 | GAsyncReadyCallback callback, |
878 | gpointer user_data) |
879 | { |
880 | g_return_if_fail (GDK_IS_DROP (self)); |
881 | g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); |
882 | g_return_if_fail (callback != NULL); |
883 | |
884 | gdk_drop_read_value_internal (self, |
885 | type, |
886 | source_tag: gdk_drop_read_value_async, |
887 | io_priority, |
888 | cancellable, |
889 | callback, |
890 | user_data); |
891 | } |
892 | |
893 | /** |
894 | * gdk_drop_read_value_finish: |
895 | * @self: a `GdkDrop` |
896 | * @result: a `GAsyncResult` |
897 | * @error: a `GError` location to store the error occurring |
898 | * |
899 | * Finishes an async drop read. |
900 | * |
901 | * See [method@Gdk.Drop.read_value_async]. |
902 | * |
903 | * Returns: (transfer none): a `GValue` containing the result. |
904 | */ |
905 | const GValue * |
906 | gdk_drop_read_value_finish (GdkDrop *self, |
907 | GAsyncResult *result, |
908 | GError **error) |
909 | { |
910 | g_return_val_if_fail (g_task_is_valid (result, self), NULL); |
911 | g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_drop_read_value_async, NULL); |
912 | g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
913 | |
914 | return g_task_propagate_pointer (G_TASK (result), error); |
915 | } |
916 | |
917 | static void |
918 | gdk_drop_do_emit_event (GdkEvent *event, |
919 | gboolean dont_queue) |
920 | { |
921 | if (dont_queue) |
922 | { |
923 | _gdk_event_emit (event); |
924 | gdk_event_unref (event); |
925 | } |
926 | else |
927 | { |
928 | _gdk_event_queue_append (display: gdk_event_get_display (event), event); |
929 | } |
930 | } |
931 | void |
932 | gdk_drop_emit_enter_event (GdkDrop *self, |
933 | gboolean dont_queue, |
934 | double x, |
935 | double y, |
936 | guint32 time) |
937 | { |
938 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
939 | GdkEvent *event; |
940 | |
941 | g_warn_if_fail (!priv->entered); |
942 | |
943 | event = gdk_dnd_event_new (type: GDK_DRAG_ENTER, |
944 | surface: priv->surface, |
945 | device: priv->device, |
946 | drop: self, |
947 | time, |
948 | x: 0, y: 0); |
949 | |
950 | priv->entered = TRUE; |
951 | |
952 | gdk_drop_do_emit_event (event, dont_queue); |
953 | } |
954 | |
955 | void |
956 | gdk_drop_emit_motion_event (GdkDrop *self, |
957 | gboolean dont_queue, |
958 | double x, |
959 | double y, |
960 | guint32 time) |
961 | { |
962 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
963 | GdkEvent *event; |
964 | |
965 | g_warn_if_fail (priv->entered); |
966 | |
967 | event = gdk_dnd_event_new (type: GDK_DRAG_MOTION, |
968 | surface: priv->surface, |
969 | device: priv->device, |
970 | drop: self, |
971 | time, |
972 | x, y); |
973 | |
974 | gdk_drop_do_emit_event (event, dont_queue); |
975 | } |
976 | |
977 | void |
978 | gdk_drop_emit_leave_event (GdkDrop *self, |
979 | gboolean dont_queue, |
980 | guint32 time) |
981 | { |
982 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
983 | GdkEvent *event; |
984 | |
985 | g_warn_if_fail (priv->entered); |
986 | |
987 | event = gdk_dnd_event_new (type: GDK_DRAG_LEAVE, |
988 | surface: priv->surface, |
989 | device: priv->device, |
990 | drop: self, |
991 | time, |
992 | x: 0, y: 0); |
993 | |
994 | priv->entered = FALSE; |
995 | |
996 | gdk_drop_do_emit_event (event, dont_queue); |
997 | } |
998 | |
999 | void |
1000 | gdk_drop_emit_drop_event (GdkDrop *self, |
1001 | gboolean dont_queue, |
1002 | double x, |
1003 | double y, |
1004 | guint32 time) |
1005 | { |
1006 | GdkDropPrivate *priv = gdk_drop_get_instance_private (self); |
1007 | GdkEvent *event; |
1008 | |
1009 | g_warn_if_fail (priv->entered); |
1010 | g_warn_if_fail (priv->state == GDK_DROP_STATE_NONE); |
1011 | |
1012 | event = gdk_dnd_event_new (type: GDK_DROP_START, |
1013 | surface: priv->surface, |
1014 | device: priv->device, |
1015 | drop: self, |
1016 | time, |
1017 | x, y); |
1018 | |
1019 | priv->state = GDK_DROP_STATE_DROPPING; |
1020 | |
1021 | gdk_drop_do_emit_event (event, dont_queue); |
1022 | } |
1023 | |