1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald |
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 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 | |
18 | /* |
19 | * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS |
20 | * file for a list of people on the GTK+ Team. See the ChangeLog |
21 | * files for a list of changes. These files are distributed with |
22 | * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
23 | */ |
24 | |
25 | #include "config.h" |
26 | |
27 | #include "gtkdroptarget.h" |
28 | |
29 | #include "gtkdropprivate.h" |
30 | #include "gtkeventcontrollerprivate.h" |
31 | #include "gtkintl.h" |
32 | #include "gtkmarshalers.h" |
33 | #include "gtknative.h" |
34 | #include "gtkprivate.h" |
35 | #include "gtktypebuiltins.h" |
36 | |
37 | |
38 | /** |
39 | * GtkDropTarget: |
40 | * |
41 | * `GtkDropTarget` is an event controller to receive Drag-and-Drop operations. |
42 | * |
43 | * The most basic way to use a `GtkDropTarget` to receive drops on a |
44 | * widget is to create it via [ctor@Gtk.DropTarget.new], passing in the |
45 | * `GType` of the data you want to receive and connect to the |
46 | * [signal@Gtk.DropTarget::drop] signal to receive the data: |
47 | * |
48 | * ```c |
49 | * static gboolean |
50 | * on_drop (GtkDropTarget *target, |
51 | * const GValue *value, |
52 | * double x, |
53 | * double y, |
54 | * gpointer data) |
55 | * { |
56 | * MyWidget *self = data; |
57 | * |
58 | * // Call the appropriate setter depending on the type of data |
59 | * // that we received |
60 | * if (G_VALUE_HOLDS (value, G_TYPE_FILE)) |
61 | * my_widget_set_file (self, g_value_get_object (value)); |
62 | * else if (G_VALUE_HOLDS (value, GDK_TYPE_PIXBUF)) |
63 | * my_widget_set_pixbuf (self, g_value_get_object (value)); |
64 | * else |
65 | * return FALSE; |
66 | * |
67 | * return TRUE; |
68 | * } |
69 | * |
70 | * static void |
71 | * my_widget_init (MyWidget *self) |
72 | * { |
73 | * GtkDropTarget *target = |
74 | * gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_COPY); |
75 | * |
76 | * // This widget accepts two types of drop types: GFile objects |
77 | * // and GdkPixbuf objects |
78 | * gtk_drop_target_set_gtypes (target, (GTypes [2]) { |
79 | * G_TYPE_FILE, |
80 | * GDK_TYPE_PIXBUF, |
81 | * }, 2); |
82 | * |
83 | * g_signal_connect (target, "drop", G_CALLBACK (on_drop), self); |
84 | * gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (target)); |
85 | * } |
86 | * ``` |
87 | * |
88 | * `GtkDropTarget` supports more options, such as: |
89 | * |
90 | * * rejecting potential drops via the [signal@Gtk.DropTarget::accept] signal |
91 | * and the [method@Gtk.DropTarget.reject] function to let other drop |
92 | * targets handle the drop |
93 | * * tracking an ongoing drag operation before the drop via the |
94 | * [signal@Gtk.DropTarget::enter], [signal@Gtk.DropTarget::motion] and |
95 | * [signal@Gtk.DropTarget::leave] signals |
96 | * * configuring how to receive data by setting the |
97 | * [property@Gtk.DropTarget:preload] property and listening for its |
98 | * availability via the [property@Gtk.DropTarget:value] property |
99 | * |
100 | * However, `GtkDropTarget` is ultimately modeled in a synchronous way |
101 | * and only supports data transferred via `GType`. If you want full control |
102 | * over an ongoing drop, the [class@Gtk.DropTargetAsync] object gives you |
103 | * this ability. |
104 | * |
105 | * While a pointer is dragged over the drop target's widget and the drop |
106 | * has not been rejected, that widget will receive the |
107 | * %GTK_STATE_FLAG_DROP_ACTIVE state, which can be used to style the widget. |
108 | * |
109 | * If you are not interested in receiving the drop, but just want to update |
110 | * UI state during a Drag-and-Drop operation (e.g. switching tabs), you can |
111 | * use [class@Gtk.DropControllerMotion]. |
112 | */ |
113 | |
114 | struct _GtkDropTarget |
115 | { |
116 | GtkEventController parent_object; |
117 | |
118 | GdkContentFormats *formats; |
119 | GdkDragAction actions; |
120 | guint preload : 1; |
121 | |
122 | guint dropping : 1; |
123 | graphene_point_t coords; |
124 | GdkDrop *drop; |
125 | GCancellable *cancellable; /* NULL unless doing a read of value */ |
126 | GValue value; |
127 | }; |
128 | |
129 | struct _GtkDropTargetClass |
130 | { |
131 | GtkEventControllerClass parent_class; |
132 | |
133 | gboolean (* accept) (GtkDropTarget *self, |
134 | GdkDrop *drop); |
135 | GdkDragAction (* enter) (GtkDropTarget *self, |
136 | double x, |
137 | double y); |
138 | GdkDragAction (* motion) (GtkDropTarget *self, |
139 | double x, |
140 | double y); |
141 | void (* leave) (GtkDropTarget *self, |
142 | GdkDrop *drop); |
143 | gboolean (* drop) (GtkDropTarget *self, |
144 | const GValue *value, |
145 | double x, |
146 | double y); |
147 | }; |
148 | |
149 | enum { |
150 | PROP_0, |
151 | PROP_ACTIONS, |
152 | PROP_CURRENT_DROP, |
153 | PROP_DROP, |
154 | PROP_FORMATS, |
155 | PROP_PRELOAD, |
156 | PROP_VALUE, |
157 | NUM_PROPERTIES |
158 | }; |
159 | |
160 | static GParamSpec *properties[NUM_PROPERTIES]; |
161 | |
162 | enum { |
163 | ACCEPT, |
164 | ENTER, |
165 | MOTION, |
166 | LEAVE, |
167 | DROP, |
168 | NUM_SIGNALS |
169 | }; |
170 | |
171 | static guint signals[NUM_SIGNALS]; |
172 | |
173 | G_DEFINE_TYPE (GtkDropTarget, gtk_drop_target, GTK_TYPE_EVENT_CONTROLLER); |
174 | |
175 | static void |
176 | gtk_drop_target_end_drop (GtkDropTarget *self) |
177 | { |
178 | if (self->drop == NULL) |
179 | return; |
180 | |
181 | g_object_freeze_notify (G_OBJECT (self)); |
182 | |
183 | if (self->dropping) |
184 | { |
185 | gdk_drop_finish (self: self->drop, action: 0); |
186 | self->dropping = FALSE; |
187 | } |
188 | |
189 | g_clear_object (&self->drop); |
190 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_DROP]); |
191 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_CURRENT_DROP]); |
192 | |
193 | if (G_IS_VALUE (&self->value)) |
194 | { |
195 | g_value_unset (value: &self->value); |
196 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_VALUE]); |
197 | } |
198 | |
199 | if (self->cancellable) |
200 | { |
201 | g_cancellable_cancel (cancellable: self->cancellable); |
202 | g_clear_object (&self->cancellable); |
203 | } |
204 | |
205 | gtk_widget_unset_state_flags (widget: gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)), |
206 | flags: GTK_STATE_FLAG_DROP_ACTIVE); |
207 | |
208 | g_object_thaw_notify (G_OBJECT (self)); |
209 | } |
210 | |
211 | static GdkDragAction |
212 | make_action_unique (GdkDragAction actions) |
213 | { |
214 | if (actions & GDK_ACTION_COPY) |
215 | return GDK_ACTION_COPY; |
216 | |
217 | if (actions & GDK_ACTION_MOVE) |
218 | return GDK_ACTION_MOVE; |
219 | |
220 | if (actions & GDK_ACTION_LINK) |
221 | return GDK_ACTION_LINK; |
222 | |
223 | return 0; |
224 | } |
225 | |
226 | static void |
227 | gtk_drop_target_do_drop (GtkDropTarget *self) |
228 | { |
229 | gboolean success; |
230 | |
231 | g_assert (self->dropping); |
232 | g_assert (G_IS_VALUE (&self->value)); |
233 | |
234 | g_signal_emit (instance: self, signal_id: signals[DROP], detail: 0, &self->value, self->coords.x, self->coords.y, &success); |
235 | |
236 | if (success) |
237 | gdk_drop_finish (self: self->drop, action: make_action_unique (actions: self->actions & gdk_drop_get_actions (self: self->drop))); |
238 | else |
239 | gdk_drop_finish (self: self->drop, action: 0); |
240 | |
241 | self->dropping = FALSE; |
242 | |
243 | gtk_drop_target_end_drop (self); |
244 | } |
245 | |
246 | static void |
247 | gtk_drop_target_load_done (GObject *source, |
248 | GAsyncResult *res, |
249 | gpointer data) |
250 | { |
251 | GtkDropTarget *self = data; |
252 | const GValue *value; |
253 | GError *error = NULL; |
254 | |
255 | value = gdk_drop_read_value_finish (GDK_DROP (source), result: res, error: &error); |
256 | if (value == NULL) |
257 | { |
258 | /* If this happens, data/self is invalid */ |
259 | if (g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_CANCELLED)) |
260 | { |
261 | g_clear_error (err: &error); |
262 | return; |
263 | } |
264 | |
265 | g_clear_object (&self->cancellable); |
266 | /* XXX: Should this be a warning? */ |
267 | g_warning ("Failed to receive drop data: %s" , error->message); |
268 | g_clear_error (err: &error); |
269 | gtk_drop_target_end_drop (self); |
270 | return; |
271 | } |
272 | |
273 | g_clear_object (&self->cancellable); |
274 | g_value_init (value: &self->value, G_VALUE_TYPE (value)); |
275 | g_value_copy (src_value: value, dest_value: &self->value); |
276 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_VALUE]); |
277 | |
278 | if (self->dropping) |
279 | gtk_drop_target_do_drop (self); |
280 | } |
281 | |
282 | static gboolean |
283 | gtk_drop_target_load_local (GtkDropTarget *self, |
284 | GType type) |
285 | { |
286 | GdkDrag *drag; |
287 | |
288 | drag = gdk_drop_get_drag (self: self->drop); |
289 | if (drag == NULL) |
290 | return FALSE; |
291 | |
292 | g_value_init (value: &self->value, g_type: type); |
293 | if (gdk_content_provider_get_value (provider: gdk_drag_get_content (drag), |
294 | value: &self->value, |
295 | NULL)) |
296 | return TRUE; |
297 | |
298 | g_value_unset (value: &self->value); |
299 | return FALSE; |
300 | } |
301 | |
302 | static gboolean |
303 | gtk_drop_target_load (GtkDropTarget *self) |
304 | { |
305 | GType type; |
306 | |
307 | g_assert (self->drop); |
308 | |
309 | if (G_IS_VALUE (&self->value)) |
310 | return TRUE; |
311 | |
312 | if (self->cancellable) |
313 | return FALSE; |
314 | |
315 | type = gdk_content_formats_match_gtype (first: self->formats, second: gdk_drop_get_formats (self: self->drop)); |
316 | |
317 | if (gtk_drop_target_load_local (self, type)) |
318 | return TRUE; |
319 | |
320 | self->cancellable = g_cancellable_new (); |
321 | |
322 | gdk_drop_read_value_async (self: self->drop, |
323 | type, |
324 | G_PRIORITY_DEFAULT, |
325 | cancellable: self->cancellable, |
326 | callback: gtk_drop_target_load_done, |
327 | g_object_ref (self)); |
328 | return FALSE; |
329 | } |
330 | |
331 | static void |
332 | gtk_drop_target_start_drop (GtkDropTarget *self, |
333 | GdkDrop *drop) |
334 | { |
335 | g_object_freeze_notify (G_OBJECT (self)); |
336 | |
337 | gtk_drop_target_end_drop (self); |
338 | |
339 | self->drop = g_object_ref (drop); |
340 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_DROP]); |
341 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_CURRENT_DROP]); |
342 | |
343 | if (self->preload) |
344 | gtk_drop_target_load (self); |
345 | |
346 | gtk_widget_set_state_flags (widget: gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)), |
347 | flags: GTK_STATE_FLAG_DROP_ACTIVE, |
348 | FALSE); |
349 | |
350 | g_object_thaw_notify (G_OBJECT (self)); |
351 | } |
352 | |
353 | static gboolean |
354 | gtk_drop_target_accept (GtkDropTarget *self, |
355 | GdkDrop *drop) |
356 | { |
357 | if ((gdk_drop_get_actions (self: drop) & gtk_drop_target_get_actions (self)) == 0) |
358 | return FALSE; |
359 | |
360 | if (self->formats == NULL) |
361 | return TRUE; |
362 | |
363 | return gdk_content_formats_match_gtype (first: self->formats, second: gdk_drop_get_formats (self: drop)) != G_TYPE_INVALID; |
364 | } |
365 | |
366 | static GdkDragAction |
367 | gtk_drop_target_enter (GtkDropTarget *self, |
368 | double x, |
369 | double y) |
370 | { |
371 | return make_action_unique (actions: self->actions & gdk_drop_get_actions (self: self->drop)); |
372 | } |
373 | |
374 | static GdkDragAction |
375 | gtk_drop_target_motion (GtkDropTarget *self, |
376 | double x, |
377 | double y) |
378 | { |
379 | return make_action_unique (actions: self->actions & gdk_drop_get_actions (self: self->drop)); |
380 | } |
381 | |
382 | static gboolean |
383 | gtk_drop_target_drop (GtkDropTarget *self, |
384 | const GValue *value, |
385 | double x, |
386 | double y) |
387 | { |
388 | return FALSE; |
389 | } |
390 | |
391 | static gboolean |
392 | gtk_drop_target_filter_event (GtkEventController *controller, |
393 | GdkEvent *event) |
394 | { |
395 | switch ((int) gdk_event_get_event_type (event)) |
396 | { |
397 | case GDK_DRAG_ENTER: |
398 | case GDK_DRAG_LEAVE: |
399 | case GDK_DRAG_MOTION: |
400 | case GDK_DROP_START: |
401 | return GTK_EVENT_CONTROLLER_CLASS (gtk_drop_target_parent_class)->filter_event (controller, event); |
402 | |
403 | default:; |
404 | } |
405 | |
406 | return TRUE; |
407 | } |
408 | |
409 | static gboolean |
410 | gtk_drop_target_handle_event (GtkEventController *controller, |
411 | GdkEvent *event, |
412 | double x, |
413 | double y) |
414 | { |
415 | GtkDropTarget *self = GTK_DROP_TARGET (controller); |
416 | |
417 | /* All drops have been rejected. New drops only arrive via crossing |
418 | * events, so we can: */ |
419 | if (self->drop == NULL) |
420 | return FALSE; |
421 | |
422 | switch ((int) gdk_event_get_event_type (event)) |
423 | { |
424 | case GDK_DRAG_MOTION: |
425 | { |
426 | GtkWidget *widget = gtk_event_controller_get_widget (controller); |
427 | GdkDragAction preferred; |
428 | |
429 | /* sanity check */ |
430 | g_return_val_if_fail (self->drop == gdk_dnd_event_get_drop (event), FALSE); |
431 | |
432 | graphene_point_init (p: &self->coords, x, y); |
433 | g_signal_emit (instance: self, signal_id: signals[MOTION], detail: 0, x, y, &preferred); |
434 | if (preferred && |
435 | gtk_drop_status (drop: self->drop, actions: self->actions, preferred_action: preferred)) |
436 | { |
437 | gtk_widget_set_state_flags (widget, flags: GTK_STATE_FLAG_DROP_ACTIVE, FALSE); |
438 | } |
439 | else |
440 | { |
441 | gtk_widget_unset_state_flags (widget, flags: GTK_STATE_FLAG_DROP_ACTIVE); |
442 | } |
443 | } |
444 | return FALSE; |
445 | |
446 | case GDK_DROP_START: |
447 | { |
448 | /* sanity check */ |
449 | g_return_val_if_fail (self->drop == gdk_dnd_event_get_drop (event), FALSE); |
450 | |
451 | graphene_point_init (p: &self->coords, x, y); |
452 | self->dropping = TRUE; |
453 | if (gtk_drop_target_load (self)) |
454 | gtk_drop_target_do_drop (self); |
455 | |
456 | return TRUE; |
457 | } |
458 | |
459 | default: |
460 | return FALSE; |
461 | } |
462 | } |
463 | |
464 | static void |
465 | gtk_drop_target_handle_crossing (GtkEventController *controller, |
466 | const GtkCrossingData *crossing, |
467 | double x, |
468 | double y) |
469 | { |
470 | GtkDropTarget *self = GTK_DROP_TARGET (controller); |
471 | GtkWidget *widget = gtk_event_controller_get_widget (controller); |
472 | |
473 | if (crossing->type != GTK_CROSSING_DROP) |
474 | return; |
475 | |
476 | /* sanity check */ |
477 | g_warn_if_fail (self->drop == NULL || self->drop == crossing->drop); |
478 | |
479 | if (crossing->direction == GTK_CROSSING_IN) |
480 | { |
481 | gboolean accept = FALSE; |
482 | GdkDragAction preferred; |
483 | |
484 | if (self->drop != NULL) |
485 | return; |
486 | |
487 | /* if we were a target already but self->drop == NULL, the drop |
488 | * was rejected already */ |
489 | if (crossing->old_descendent != NULL || |
490 | crossing->old_target == widget) |
491 | return; |
492 | |
493 | g_signal_emit (instance: self, signal_id: signals[ACCEPT], detail: 0, crossing->drop, &accept); |
494 | if (!accept) |
495 | return; |
496 | |
497 | graphene_point_init (p: &self->coords, x, y); |
498 | gtk_drop_target_start_drop (self, drop: crossing->drop); |
499 | |
500 | g_signal_emit (instance: self, signal_id: signals[ENTER], detail: 0, x, y, &preferred); |
501 | if (preferred && |
502 | gtk_drop_status (drop: self->drop, actions: self->actions, preferred_action: preferred)) |
503 | { |
504 | gtk_widget_set_state_flags (widget, flags: GTK_STATE_FLAG_DROP_ACTIVE, FALSE); |
505 | } |
506 | else |
507 | { |
508 | gtk_widget_unset_state_flags (widget, flags: GTK_STATE_FLAG_DROP_ACTIVE); |
509 | } |
510 | } |
511 | else |
512 | { |
513 | if (crossing->new_descendent != NULL || |
514 | crossing->new_target == widget) |
515 | return; |
516 | |
517 | g_signal_emit (instance: self, signal_id: signals[LEAVE], detail: 0); |
518 | if (!self->dropping) |
519 | gtk_drop_target_end_drop (self); |
520 | gtk_widget_unset_state_flags (widget, flags: GTK_STATE_FLAG_DROP_ACTIVE); |
521 | } |
522 | } |
523 | |
524 | static void |
525 | gtk_drop_target_finalize (GObject *object) |
526 | { |
527 | GtkDropTarget *self = GTK_DROP_TARGET (object); |
528 | |
529 | g_clear_pointer (&self->formats, gdk_content_formats_unref); |
530 | |
531 | G_OBJECT_CLASS (gtk_drop_target_parent_class)->finalize (object); |
532 | } |
533 | |
534 | static void |
535 | gtk_drop_target_set_property (GObject *object, |
536 | guint prop_id, |
537 | const GValue *value, |
538 | GParamSpec *pspec) |
539 | { |
540 | GtkDropTarget *self = GTK_DROP_TARGET (object); |
541 | |
542 | switch (prop_id) |
543 | { |
544 | case PROP_ACTIONS: |
545 | gtk_drop_target_set_actions (self, actions: g_value_get_flags (value)); |
546 | break; |
547 | |
548 | case PROP_FORMATS: |
549 | self->formats = g_value_dup_boxed (value); |
550 | if (self->formats == NULL) |
551 | self->formats = gdk_content_formats_new (NULL, n_mime_types: 0); |
552 | break; |
553 | |
554 | case PROP_PRELOAD: |
555 | gtk_drop_target_set_preload (self, preload: g_value_get_boolean (value)); |
556 | break; |
557 | |
558 | default: |
559 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
560 | } |
561 | } |
562 | |
563 | static void |
564 | gtk_drop_target_get_property (GObject *object, |
565 | guint prop_id, |
566 | GValue *value, |
567 | GParamSpec *pspec) |
568 | { |
569 | GtkDropTarget *self = GTK_DROP_TARGET (object); |
570 | |
571 | switch (prop_id) |
572 | { |
573 | case PROP_ACTIONS: |
574 | g_value_set_flags (value, v_flags: self->actions); |
575 | break; |
576 | |
577 | case PROP_DROP: |
578 | case PROP_CURRENT_DROP: |
579 | g_value_set_object (value, v_object: self->drop); |
580 | break; |
581 | |
582 | case PROP_FORMATS: |
583 | g_value_set_boxed (value, v_boxed: self->formats); |
584 | break; |
585 | |
586 | case PROP_PRELOAD: |
587 | g_value_set_boolean (value, v_boolean: self->preload); |
588 | break; |
589 | |
590 | case PROP_VALUE: |
591 | if (G_IS_VALUE (&self->value)) |
592 | g_value_set_boxed (value, v_boxed: &self->value); |
593 | else |
594 | g_value_set_boxed (value, NULL); |
595 | break; |
596 | |
597 | default: |
598 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
599 | } |
600 | } |
601 | |
602 | static void |
603 | gtk_drop_target_class_init (GtkDropTargetClass *class) |
604 | { |
605 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
606 | GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (class); |
607 | |
608 | object_class->finalize = gtk_drop_target_finalize; |
609 | object_class->set_property = gtk_drop_target_set_property; |
610 | object_class->get_property = gtk_drop_target_get_property; |
611 | |
612 | controller_class->handle_event = gtk_drop_target_handle_event; |
613 | controller_class->filter_event = gtk_drop_target_filter_event; |
614 | controller_class->handle_crossing = gtk_drop_target_handle_crossing; |
615 | |
616 | class->accept = gtk_drop_target_accept; |
617 | class->enter = gtk_drop_target_enter; |
618 | class->motion = gtk_drop_target_motion; |
619 | class->drop = gtk_drop_target_drop; |
620 | |
621 | /** |
622 | * GtkDropTarget:actions: (attributes org.gtk.Property.get=gtk_drop_target_get_actions org.gtk.Property.set=gtk_drop_target_set_actions) |
623 | * |
624 | * The `GdkDragActions` that this drop target supports. |
625 | */ |
626 | properties[PROP_ACTIONS] = |
627 | g_param_spec_flags (name: "actions" , |
628 | P_("Actions" ), |
629 | P_("The actions supported by this drop target" ), |
630 | flags_type: GDK_TYPE_DRAG_ACTION, default_value: 0, |
631 | flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); |
632 | |
633 | /** |
634 | * GtkDropTarget:drop: (attributes org.gtk.Property.get=gtk_drop_target_get_drop) (skip) |
635 | * |
636 | * The `GdkDrop` that is currently being performed. |
637 | * |
638 | * Deprecated: 4.4: Use [property@Gtk.DropTarget:current-drop] instead |
639 | */ |
640 | properties[PROP_DROP] = |
641 | g_param_spec_object (name: "drop" , |
642 | P_("Drop" ), |
643 | P_("Current drop" ), |
644 | GDK_TYPE_DROP, |
645 | GTK_PARAM_READABLE | G_PARAM_DEPRECATED); |
646 | |
647 | /** |
648 | * GtkDropTarget:current-drop: (attributes org.gtk.Property.get=gtk_drop_target_get_current_drop) |
649 | * |
650 | * The `GdkDrop` that is currently being performed. |
651 | * |
652 | * Since: 4.4 |
653 | */ |
654 | properties[PROP_CURRENT_DROP] = |
655 | g_param_spec_object (name: "current-drop" , |
656 | P_("Current drop" ), |
657 | P_("Current drop" ), |
658 | GDK_TYPE_DROP, |
659 | GTK_PARAM_READABLE); |
660 | |
661 | /** |
662 | * GtkDropTarget:formats: (attributes org.gtk.Property.get=gtk_drop_target_get_formats) |
663 | * |
664 | * The `GdkContentFormats` that determine the supported data formats. |
665 | */ |
666 | properties[PROP_FORMATS] = |
667 | g_param_spec_boxed (name: "formats" , |
668 | P_("Formats" ), |
669 | P_("The supported formats" ), |
670 | GDK_TYPE_CONTENT_FORMATS, |
671 | GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); |
672 | |
673 | /** |
674 | * GtkDropTarget:preload: (attributes org.gtk.Property.get=gtk_drop_target_get_preload org.gtk.Property.set=gtk_drop_target_set_preload) |
675 | * |
676 | * Whether the drop data should be preloaded when the pointer is only |
677 | * hovering over the widget but has not been released. |
678 | * |
679 | * Setting this property allows finer grained reaction to an ongoing |
680 | * drop at the cost of loading more data. |
681 | * |
682 | * The default value for this property is %FALSE to avoid downloading |
683 | * huge amounts of data by accident. |
684 | * |
685 | * For example, if somebody drags a full document of gigabytes of text |
686 | * from a text editor across a widget with a preloading drop target, |
687 | * this data will be downloaded, even if the data is ultimately dropped |
688 | * elsewhere. |
689 | * |
690 | * For a lot of data formats, the amount of data is very small (like |
691 | * %GDK_TYPE_RGBA), so enabling this property does not hurt at all. |
692 | * And for local-only Drag-and-Drop operations, no data transfer is done, |
693 | * so enabling it there is free. |
694 | */ |
695 | properties[PROP_PRELOAD] = |
696 | g_param_spec_boolean (name: "preload" , |
697 | P_("Preload" ), |
698 | P_("Whether drop data should be preloaded while hovering" ), |
699 | FALSE, |
700 | flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); |
701 | |
702 | /** |
703 | * GtkDropTarget:value: (attributes org.gtk.Property.get=gtk_drop_target_get_value) |
704 | * |
705 | * The value for this drop operation. |
706 | * |
707 | * This is %NULL if the data has not been loaded yet or no drop |
708 | * operation is going on. |
709 | * |
710 | * Data may be available before the [signal@Gtk.DropTarget::drop] |
711 | * signal gets emitted - for example when the [property@Gtk.DropTarget:preload] |
712 | * property is set. You can use the ::notify signal to be notified |
713 | * of available data. |
714 | */ |
715 | properties[PROP_VALUE] = |
716 | g_param_spec_boxed (name: "value" , |
717 | P_("Value" ), |
718 | P_("The value for this drop operation" ), |
719 | G_TYPE_VALUE, |
720 | GTK_PARAM_READABLE); |
721 | |
722 | g_object_class_install_properties (oclass: object_class, n_pspecs: NUM_PROPERTIES, pspecs: properties); |
723 | |
724 | /** |
725 | * GtkDropTarget::accept: |
726 | * @self: the `GtkDropTarget` |
727 | * @drop: the `GdkDrop` |
728 | * |
729 | * Emitted on the drop site when a drop operation is about to begin. |
730 | * |
731 | * If the drop is not accepted, %FALSE will be returned and the drop target |
732 | * will ignore the drop. If %TRUE is returned, the drop is accepted for now |
733 | * but may be rejected later via a call to [method@Gtk.DropTarget.reject] |
734 | * or ultimately by returning %FALSE from a [signal@Gtk.DropTarget::drop] |
735 | * handler. |
736 | * |
737 | * The default handler for this signal decides whether to accept the drop |
738 | * based on the formats provided by the @drop. |
739 | * |
740 | * If the decision whether the drop will be accepted or rejected depends |
741 | * on the data, this function should return %TRUE, the |
742 | * [property@Gtk.DropTarget:preload] property should be set and the value |
743 | * should be inspected via the ::notify:value signal, calling |
744 | * [method@Gtk.DropTarget.reject] if required. |
745 | * |
746 | * Returns: %TRUE if @drop is accepted |
747 | */ |
748 | signals[ACCEPT] = |
749 | g_signal_new (I_("accept" ), |
750 | G_TYPE_FROM_CLASS (class), |
751 | signal_flags: G_SIGNAL_RUN_LAST, |
752 | G_STRUCT_OFFSET (GtkDropTargetClass, accept), |
753 | accumulator: g_signal_accumulator_first_wins, NULL, |
754 | NULL, |
755 | G_TYPE_BOOLEAN, n_params: 1, |
756 | GDK_TYPE_DROP); |
757 | |
758 | /** |
759 | * GtkDropTarget::enter: |
760 | * @self: the `GtkDropTarget` |
761 | * @x: the x coordinate of the current pointer position |
762 | * @y: the y coordinate of the current pointer position |
763 | * |
764 | * Emitted on the drop site when the pointer enters the widget. |
765 | * |
766 | * It can be used to set up custom highlighting. |
767 | * |
768 | * Returns: Preferred action for this drag operation or 0 if |
769 | * dropping is not supported at the current @x,@y location. |
770 | */ |
771 | signals[ENTER] = |
772 | g_signal_new (I_("enter" ), |
773 | G_TYPE_FROM_CLASS (class), |
774 | signal_flags: G_SIGNAL_RUN_LAST, |
775 | G_STRUCT_OFFSET (GtkDropTargetClass, enter), |
776 | accumulator: g_signal_accumulator_first_wins, NULL, |
777 | NULL, |
778 | return_type: GDK_TYPE_DRAG_ACTION, n_params: 2, |
779 | G_TYPE_DOUBLE, G_TYPE_DOUBLE); |
780 | |
781 | /** |
782 | * GtkDropTarget::motion: |
783 | * @self: the `GtkDropTarget` |
784 | * @x: the x coordinate of the current pointer position |
785 | * @y: the y coordinate of the current pointer position |
786 | * |
787 | * Emitted while the pointer is moving over the drop target. |
788 | * |
789 | * Returns: Preferred action for this drag operation or 0 if |
790 | * dropping is not supported at the current @x,@y location. |
791 | */ |
792 | signals[MOTION] = |
793 | g_signal_new (I_("motion" ), |
794 | G_TYPE_FROM_CLASS (class), |
795 | signal_flags: G_SIGNAL_RUN_LAST, |
796 | G_STRUCT_OFFSET (GtkDropTargetClass, motion), |
797 | accumulator: g_signal_accumulator_first_wins, NULL, |
798 | NULL, |
799 | return_type: GDK_TYPE_DRAG_ACTION, n_params: 2, |
800 | G_TYPE_DOUBLE, G_TYPE_DOUBLE); |
801 | |
802 | /** |
803 | * GtkDropTarget::leave: |
804 | * @self: the `GtkDropTarget` |
805 | * |
806 | * Emitted on the drop site when the pointer leaves the widget. |
807 | * |
808 | * Its main purpose it to undo things done in |
809 | * [signal@Gtk.DropTarget::enter]. |
810 | */ |
811 | signals[LEAVE] = |
812 | g_signal_new (I_("leave" ), |
813 | G_TYPE_FROM_CLASS (class), |
814 | signal_flags: G_SIGNAL_RUN_LAST, |
815 | G_STRUCT_OFFSET (GtkDropTargetClass, leave), |
816 | NULL, NULL, |
817 | NULL, |
818 | G_TYPE_NONE, n_params: 0); |
819 | |
820 | /** |
821 | * GtkDropTarget::drop: |
822 | * @self: the `GtkDropTarget` |
823 | * @value: the `GValue` being dropped |
824 | * @x: the x coordinate of the current pointer position |
825 | * @y: the y coordinate of the current pointer position |
826 | * |
827 | * Emitted on the drop site when the user drops the data onto the widget. |
828 | * |
829 | * The signal handler must determine whether the pointer position is in |
830 | * a drop zone or not. If it is not in a drop zone, it returns %FALSE |
831 | * and no further processing is necessary. |
832 | * |
833 | * Otherwise, the handler returns %TRUE. In this case, this handler will |
834 | * accept the drop. The handler is responsible for using the given @value |
835 | * and performing the drop operation. |
836 | * |
837 | * Returns: whether the drop was accepted at the given pointer position |
838 | */ |
839 | signals[DROP] = |
840 | g_signal_new (I_("drop" ), |
841 | G_TYPE_FROM_CLASS (class), |
842 | signal_flags: G_SIGNAL_RUN_LAST, |
843 | class_offset: 0, |
844 | accumulator: g_signal_accumulator_first_wins, NULL, |
845 | NULL, |
846 | G_TYPE_BOOLEAN, n_params: 3, |
847 | G_TYPE_VALUE, G_TYPE_DOUBLE, G_TYPE_DOUBLE); |
848 | } |
849 | |
850 | static void |
851 | gtk_drop_target_init (GtkDropTarget *self) |
852 | { |
853 | } |
854 | |
855 | /** |
856 | * gtk_drop_target_new: |
857 | * @type: The supported type or %G_TYPE_INVALID |
858 | * @actions: the supported actions |
859 | * |
860 | * Creates a new `GtkDropTarget` object. |
861 | * |
862 | * If the drop target should support more than 1 type, pass |
863 | * %G_TYPE_INVALID for @type and then call |
864 | * [method@Gtk.DropTarget.set_gtypes]. |
865 | * |
866 | * Returns: the new `GtkDropTarget` |
867 | */ |
868 | GtkDropTarget * |
869 | gtk_drop_target_new (GType type, |
870 | GdkDragAction actions) |
871 | { |
872 | GtkDropTarget *result; |
873 | GdkContentFormats *formats; |
874 | |
875 | if (type != G_TYPE_INVALID) |
876 | formats = gdk_content_formats_new_for_gtype (type); |
877 | else |
878 | formats = NULL; |
879 | |
880 | result = g_object_new (GTK_TYPE_DROP_TARGET, |
881 | first_property_name: "formats" , formats, |
882 | "actions" , actions, |
883 | NULL); |
884 | |
885 | g_clear_pointer (&formats, gdk_content_formats_unref); |
886 | |
887 | return result; |
888 | } |
889 | |
890 | /** |
891 | * gtk_drop_target_get_formats: (attributes org.gtk.Method.get_property=formats) |
892 | * @self: a `GtkDropTarget` |
893 | * |
894 | * Gets the data formats that this drop target accepts. |
895 | * |
896 | * If the result is %NULL, all formats are expected to be supported. |
897 | * |
898 | * Returns: (nullable) (transfer none): the supported data formats |
899 | */ |
900 | GdkContentFormats * |
901 | gtk_drop_target_get_formats (GtkDropTarget *self) |
902 | { |
903 | g_return_val_if_fail (GTK_IS_DROP_TARGET (self), NULL); |
904 | |
905 | return self->formats; |
906 | } |
907 | |
908 | /** |
909 | * gtk_drop_target_set_gtypes: |
910 | * @self: a `GtkDropTarget` |
911 | * @types: (nullable) (transfer none) (array length=n_types): all supported `GType`s |
912 | * that can be dropped on the target |
913 | * @n_types: number of @types |
914 | * |
915 | * Sets the supported `GTypes` for this drop target. |
916 | */ |
917 | void |
918 | gtk_drop_target_set_gtypes (GtkDropTarget *self, |
919 | GType *types, |
920 | gsize n_types) |
921 | { |
922 | GdkContentFormatsBuilder *builder; |
923 | gsize i; |
924 | |
925 | g_return_if_fail (GTK_IS_DROP_TARGET (self)); |
926 | g_return_if_fail (n_types == 0 || types != NULL); |
927 | |
928 | gdk_content_formats_unref (formats: self->formats); |
929 | |
930 | builder = gdk_content_formats_builder_new (); |
931 | for (i = 0; i < n_types; i++) |
932 | gdk_content_formats_builder_add_gtype (builder, type: types[i]); |
933 | |
934 | self->formats = gdk_content_formats_builder_free_to_formats (builder); |
935 | |
936 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_FORMATS]); |
937 | } |
938 | |
939 | /** |
940 | * gtk_drop_target_get_gtypes: |
941 | * @self: a `GtkDropTarget` |
942 | * @n_types: (out) (optional): the number of `GType`s contained in the |
943 | * return value |
944 | * |
945 | * Gets the list of supported `GType`s that can be dropped on the target. |
946 | * |
947 | * If no types have been set, `NULL` will be returned. |
948 | * |
949 | * Returns: (transfer none) (nullable) (array length=n_types): |
950 | * the `G_TYPE_INVALID`-terminated array of types included in |
951 | * formats |
952 | */ |
953 | const GType * |
954 | gtk_drop_target_get_gtypes (GtkDropTarget *self, |
955 | gsize *n_types) |
956 | { |
957 | g_return_val_if_fail (GTK_IS_DROP_TARGET (self), NULL); |
958 | |
959 | return gdk_content_formats_get_gtypes (formats: self->formats, n_gtypes: n_types); |
960 | } |
961 | |
962 | /** |
963 | * gtk_drop_target_set_actions: (attributes org.gtk.Method.set_property=actions) |
964 | * @self: a `GtkDropTarget` |
965 | * @actions: the supported actions |
966 | * |
967 | * Sets the actions that this drop target supports. |
968 | */ |
969 | void |
970 | gtk_drop_target_set_actions (GtkDropTarget *self, |
971 | GdkDragAction actions) |
972 | { |
973 | g_return_if_fail (GTK_IS_DROP_TARGET (self)); |
974 | |
975 | if (self->actions == actions) |
976 | return; |
977 | |
978 | self->actions = actions; |
979 | |
980 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ACTIONS]); |
981 | } |
982 | |
983 | /** |
984 | * gtk_drop_target_get_actions: (attributes org.gtk.Method.get_property=actions) |
985 | * @self: a `GtkDropTarget` |
986 | * |
987 | * Gets the actions that this drop target supports. |
988 | * |
989 | * Returns: the actions that this drop target supports |
990 | */ |
991 | GdkDragAction |
992 | gtk_drop_target_get_actions (GtkDropTarget *self) |
993 | { |
994 | g_return_val_if_fail (GTK_IS_DROP_TARGET (self), 0); |
995 | |
996 | return self->actions; |
997 | } |
998 | |
999 | /** |
1000 | * gtk_drop_target_set_preload: (attributes org.gtk.Method.set_property=preload) |
1001 | * @self: a `GtkDropTarget` |
1002 | * @preload: %TRUE to preload drop data |
1003 | * |
1004 | * Sets whether data should be preloaded on hover. |
1005 | */ |
1006 | void |
1007 | gtk_drop_target_set_preload (GtkDropTarget *self, |
1008 | gboolean preload) |
1009 | { |
1010 | g_return_if_fail (GTK_IS_DROP_TARGET (self)); |
1011 | |
1012 | if (self->preload == preload) |
1013 | return; |
1014 | |
1015 | self->preload = preload; |
1016 | |
1017 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_PRELOAD]); |
1018 | } |
1019 | |
1020 | /** |
1021 | * gtk_drop_target_get_preload: (attributes org.gtk.Method.get_property=preload) |
1022 | * @self: a `GtkDropTarget` |
1023 | * |
1024 | * Gets whether data should be preloaded on hover. |
1025 | * |
1026 | * Returns: %TRUE if drop data should be preloaded |
1027 | */ |
1028 | gboolean |
1029 | gtk_drop_target_get_preload (GtkDropTarget *self) |
1030 | { |
1031 | g_return_val_if_fail (GTK_IS_DROP_TARGET (self), 0); |
1032 | |
1033 | return self->preload; |
1034 | } |
1035 | |
1036 | /** |
1037 | * gtk_drop_target_get_drop: (attributes org.gtk.Method.get_property=drop) |
1038 | * @self: a `GtkDropTarget` |
1039 | * |
1040 | * Gets the currently handled drop operation. |
1041 | * |
1042 | * If no drop operation is going on, %NULL is returned. |
1043 | * |
1044 | * Returns: (nullable) (transfer none): The current drop |
1045 | * |
1046 | * Deprecated: 4.4: Use [method@Gtk.DropTarget.get_current_drop] instead |
1047 | */ |
1048 | GdkDrop * |
1049 | gtk_drop_target_get_drop (GtkDropTarget *self) |
1050 | { |
1051 | g_return_val_if_fail (GTK_IS_DROP_TARGET (self), NULL); |
1052 | |
1053 | return self->drop; |
1054 | } |
1055 | |
1056 | /** |
1057 | * gtk_drop_target_get_current_drop: (attributes org.gtk.Method.get_property=current-drop) |
1058 | * @self: a `GtkDropTarget` |
1059 | * |
1060 | * Gets the currently handled drop operation. |
1061 | * |
1062 | * If no drop operation is going on, %NULL is returned. |
1063 | * |
1064 | * Returns: (nullable) (transfer none): The current drop |
1065 | * |
1066 | * Since: 4.4 |
1067 | */ |
1068 | GdkDrop * |
1069 | gtk_drop_target_get_current_drop (GtkDropTarget *self) |
1070 | { |
1071 | g_return_val_if_fail (GTK_IS_DROP_TARGET (self), NULL); |
1072 | |
1073 | return self->drop; |
1074 | } |
1075 | |
1076 | /** |
1077 | * gtk_drop_target_get_value: (attributes org.gtk.Method.get_property=value) |
1078 | * @self: a `GtkDropTarget` |
1079 | * |
1080 | * Gets the current drop data, as a `GValue`. |
1081 | * |
1082 | * Returns: (nullable) (transfer none): The current drop data |
1083 | */ |
1084 | const GValue * |
1085 | gtk_drop_target_get_value (GtkDropTarget *self) |
1086 | { |
1087 | g_return_val_if_fail (GTK_IS_DROP_TARGET (self), NULL); |
1088 | |
1089 | if (!G_IS_VALUE (&self->value)) |
1090 | return NULL; |
1091 | |
1092 | return &self->value; |
1093 | } |
1094 | |
1095 | /** |
1096 | * gtk_drop_target_reject: |
1097 | * @self: a `GtkDropTarget` |
1098 | * |
1099 | * Rejects the ongoing drop operation. |
1100 | * |
1101 | * If no drop operation is ongoing, i.e when [property@Gtk.DropTarget:current-drop] |
1102 | * is %NULL, this function does nothing. |
1103 | * |
1104 | * This function should be used when delaying the decision |
1105 | * on whether to accept a drag or not until after reading |
1106 | * the data. |
1107 | */ |
1108 | void |
1109 | gtk_drop_target_reject (GtkDropTarget *self) |
1110 | { |
1111 | g_return_if_fail (GTK_IS_DROP_TARGET (self)); |
1112 | |
1113 | if (self->drop == NULL) |
1114 | return; |
1115 | |
1116 | gtk_drop_target_end_drop (self); |
1117 | } |
1118 | |
1119 | |