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 "gtkdragsourceprivate.h" |
28 | |
29 | #include "gtkgesturedrag.h" |
30 | #include "gtkgesturesingleprivate.h" |
31 | #include "gtkimagedefinitionprivate.h" |
32 | #include "gtknative.h" |
33 | #include "gtkwidgetprivate.h" |
34 | #include "gtkintl.h" |
35 | #include "gtkimageprivate.h" |
36 | #include "gtkdragicon.h" |
37 | #include "gtkprivate.h" |
38 | #include "gtkmarshalers.h" |
39 | #include "gtkicontheme.h" |
40 | #include "gtkpicture.h" |
41 | #include "gtksettingsprivate.h" |
42 | #include "gtkgesturesingle.h" |
43 | |
44 | /** |
45 | * GtkDragSource: |
46 | * |
47 | * `GtkDragSource` is an event controller to initiate Drag-And-Drop operations. |
48 | * |
49 | * `GtkDragSource` can be set up with the necessary |
50 | * ingredients for a DND operation ahead of time. This includes |
51 | * the source for the data that is being transferred, in the form |
52 | * of a [class@Gdk.ContentProvider], the desired action, and the icon to |
53 | * use during the drag operation. After setting it up, the drag |
54 | * source must be added to a widget as an event controller, using |
55 | * [method@Gtk.Widget.add_controller]. |
56 | * |
57 | * ```c |
58 | * static void |
59 | * my_widget_init (MyWidget *self) |
60 | * { |
61 | * GtkDragSource *drag_source = gtk_drag_source_new (); |
62 | * |
63 | * g_signal_connect (drag_source, "prepare", G_CALLBACK (on_drag_prepare), self); |
64 | * g_signal_connect (drag_source, "drag-begin", G_CALLBACK (on_drag_begin), self); |
65 | * |
66 | * gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (drag_source)); |
67 | * } |
68 | * ``` |
69 | * |
70 | * Setting up the content provider and icon ahead of time only makes |
71 | * sense when the data does not change. More commonly, you will want |
72 | * to set them up just in time. To do so, `GtkDragSource` has |
73 | * [signal@Gtk.DragSource::prepare] and [signal@Gtk.DragSource::drag-begin] |
74 | * signals. |
75 | * |
76 | * The ::prepare signal is emitted before a drag is started, and |
77 | * can be used to set the content provider and actions that the |
78 | * drag should be started with. |
79 | * |
80 | * ```c |
81 | * static GdkContentProvider * |
82 | * on_drag_prepare (GtkDragSource *source, |
83 | * double x, |
84 | * double y, |
85 | * MyWidget *self) |
86 | * { |
87 | * // This widget supports two types of content: GFile objects |
88 | * // and GdkPixbuf objects; GTK will handle the serialization |
89 | * // of these types automatically |
90 | * GFile *file = my_widget_get_file (self); |
91 | * GdkPixbuf *pixbuf = my_widget_get_pixbuf (self); |
92 | * |
93 | * return gdk_content_provider_new_union ((GdkContentProvider *[2]) { |
94 | * gdk_content_provider_new_typed (G_TYPE_FILE, file), |
95 | * gdk_content_provider_new_typed (GDK_TYPE_PIXBUF, pixbuf), |
96 | * }, 2); |
97 | * } |
98 | * ``` |
99 | * |
100 | * The ::drag-begin signal is emitted after the `GdkDrag` object has |
101 | * been created, and can be used to set up the drag icon. |
102 | * |
103 | * ```c |
104 | * static void |
105 | * on_drag_begin (GtkDragSource *source, |
106 | * GdkDrag *drag, |
107 | * MyWidget *self) |
108 | * { |
109 | * // Set the widget as the drag icon |
110 | * GdkPaintable *paintable = gtk_widget_paintable_new (GTK_WIDGET (self)); |
111 | * gtk_drag_source_set_icon (source, paintable, 0, 0); |
112 | * g_object_unref (paintable); |
113 | * } |
114 | * ``` |
115 | * |
116 | * During the DND operation, `GtkDragSource` emits signals that |
117 | * can be used to obtain updates about the status of the operation, |
118 | * but it is not normally necessary to connect to any signals, |
119 | * except for one case: when the supported actions include |
120 | * %GDK_ACTION_MOVE, you need to listen for the |
121 | * [signal@Gtk.DragSource::drag-end] signal and delete the |
122 | * data after it has been transferred. |
123 | */ |
124 | |
125 | struct _GtkDragSource |
126 | { |
127 | GtkGestureSingle parent_instance; |
128 | |
129 | GdkContentProvider *content; |
130 | GdkDragAction actions; |
131 | |
132 | GdkPaintable *paintable; |
133 | int hot_x; |
134 | int hot_y; |
135 | |
136 | double start_x; |
137 | double start_y; |
138 | |
139 | GdkDrag *drag; |
140 | }; |
141 | |
142 | struct _GtkDragSourceClass |
143 | { |
144 | GtkGestureSingleClass parent_class; |
145 | |
146 | GdkContentProvider *(* prepare) (GtkDragSource *source, |
147 | double x, |
148 | double y); |
149 | }; |
150 | |
151 | enum { |
152 | PROP_CONTENT = 1, |
153 | PROP_ACTIONS, |
154 | NUM_PROPERTIES |
155 | }; |
156 | |
157 | static GParamSpec *properties[NUM_PROPERTIES]; |
158 | |
159 | enum { |
160 | PREPARE, |
161 | DRAG_BEGIN, |
162 | DRAG_END, |
163 | DRAG_CANCEL, |
164 | NUM_SIGNALS |
165 | }; |
166 | |
167 | static guint signals[NUM_SIGNALS]; |
168 | |
169 | static void gtk_drag_source_dnd_finished_cb (GdkDrag *drag, |
170 | GtkDragSource *source); |
171 | static void gtk_drag_source_cancel_cb (GdkDrag *drag, |
172 | GdkDragCancelReason reason, |
173 | GtkDragSource *source); |
174 | |
175 | static GdkContentProvider *gtk_drag_source_prepare (GtkDragSource *source, |
176 | double x, |
177 | double y); |
178 | |
179 | static void gtk_drag_source_drag_begin (GtkDragSource *source); |
180 | |
181 | G_DEFINE_TYPE (GtkDragSource, gtk_drag_source, GTK_TYPE_GESTURE_SINGLE); |
182 | |
183 | static void |
184 | gtk_drag_source_init (GtkDragSource *source) |
185 | { |
186 | source->actions = GDK_ACTION_COPY; |
187 | } |
188 | |
189 | static void |
190 | gtk_drag_source_finalize (GObject *object) |
191 | { |
192 | GtkDragSource *source = GTK_DRAG_SOURCE (object); |
193 | |
194 | g_clear_object (&source->content); |
195 | g_clear_object (&source->paintable); |
196 | |
197 | G_OBJECT_CLASS (gtk_drag_source_parent_class)->finalize (object); |
198 | } |
199 | |
200 | static void |
201 | gtk_drag_source_set_property (GObject *object, |
202 | guint prop_id, |
203 | const GValue *value, |
204 | GParamSpec *pspec) |
205 | { |
206 | GtkDragSource *source = GTK_DRAG_SOURCE (object); |
207 | |
208 | switch (prop_id) |
209 | { |
210 | case PROP_CONTENT: |
211 | gtk_drag_source_set_content (source, content: g_value_get_object (value)); |
212 | break; |
213 | |
214 | case PROP_ACTIONS: |
215 | gtk_drag_source_set_actions (source, actions: g_value_get_flags (value)); |
216 | break; |
217 | |
218 | default: |
219 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
220 | } |
221 | } |
222 | |
223 | static void |
224 | gtk_drag_source_get_property (GObject *object, |
225 | guint prop_id, |
226 | GValue *value, |
227 | GParamSpec *pspec) |
228 | { |
229 | GtkDragSource *source = GTK_DRAG_SOURCE (object); |
230 | |
231 | switch (prop_id) |
232 | { |
233 | case PROP_CONTENT: |
234 | g_value_set_object (value, v_object: gtk_drag_source_get_content (source)); |
235 | break; |
236 | |
237 | case PROP_ACTIONS: |
238 | g_value_set_flags (value, v_flags: gtk_drag_source_get_actions (source)); |
239 | break; |
240 | |
241 | default: |
242 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
243 | } |
244 | } |
245 | |
246 | static gboolean |
247 | gtk_drag_source_filter_event (GtkEventController *controller, |
248 | GdkEvent *event) |
249 | { |
250 | /* Let touchpad swipe events go through, only if they match n-points */ |
251 | if (gdk_event_get_event_type (event) == GDK_TOUCHPAD_SWIPE) |
252 | { |
253 | guint n_points; |
254 | guint n_fingers; |
255 | |
256 | g_object_get (G_OBJECT (controller), first_property_name: "n-points" , &n_points, NULL); |
257 | n_fingers = gdk_touchpad_event_get_n_fingers (event); |
258 | |
259 | if (n_fingers == n_points) |
260 | return FALSE; |
261 | else |
262 | return TRUE; |
263 | } |
264 | |
265 | return GTK_EVENT_CONTROLLER_CLASS (gtk_drag_source_parent_class)->filter_event (controller, event); |
266 | } |
267 | |
268 | static void |
269 | gtk_drag_source_begin (GtkGesture *gesture, |
270 | GdkEventSequence *sequence) |
271 | { |
272 | GtkDragSource *source = GTK_DRAG_SOURCE (gesture); |
273 | GdkEventSequence *current; |
274 | |
275 | current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); |
276 | |
277 | gtk_gesture_get_point (gesture, sequence: current, x: &source->start_x, y: &source->start_y); |
278 | } |
279 | |
280 | static void |
281 | gtk_drag_source_update (GtkGesture *gesture, |
282 | GdkEventSequence *sequence) |
283 | { |
284 | GtkDragSource *source = GTK_DRAG_SOURCE (gesture); |
285 | GtkWidget *widget; |
286 | double x, y; |
287 | |
288 | if (!gtk_gesture_is_recognized (gesture)) |
289 | return; |
290 | |
291 | gtk_gesture_get_point (gesture, sequence, x: &x, y: &y); |
292 | |
293 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); |
294 | |
295 | if (gtk_drag_check_threshold_double (widget, start_x: source->start_x, start_y: source->start_y, current_x: x, current_y: y)) |
296 | { |
297 | gtk_drag_source_drag_begin (source); |
298 | } |
299 | } |
300 | |
301 | static void |
302 | gtk_drag_source_class_init (GtkDragSourceClass *class) |
303 | { |
304 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
305 | GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (class); |
306 | GtkGestureClass *gesture_class = GTK_GESTURE_CLASS (class); |
307 | |
308 | object_class->finalize = gtk_drag_source_finalize; |
309 | object_class->set_property = gtk_drag_source_set_property; |
310 | object_class->get_property = gtk_drag_source_get_property; |
311 | |
312 | controller_class->filter_event = gtk_drag_source_filter_event; |
313 | |
314 | gesture_class->begin = gtk_drag_source_begin; |
315 | gesture_class->update = gtk_drag_source_update; |
316 | gesture_class->end = NULL; |
317 | |
318 | class->prepare = gtk_drag_source_prepare; |
319 | |
320 | /** |
321 | * GtkDragSource:content: (attributes org.gtk.Property.get=gtk_drag_source_get_content org.gtk.Propery.set=gtk_drag_source_set_content) |
322 | * |
323 | * The data that is offered by drag operations from this source. |
324 | */ |
325 | properties[PROP_CONTENT] = |
326 | g_param_spec_object (name: "content" , |
327 | P_("Content" ), |
328 | P_("The content provider for the dragged data" ), |
329 | GDK_TYPE_CONTENT_PROVIDER, |
330 | flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); |
331 | |
332 | /** |
333 | * GtkDragSource:actions: (attributes org.gtk.Property.get=gtk_drag_source_get_actions org.gtk.Property.set=gtk_drag_source_set_actions) |
334 | * |
335 | * The actions that are supported by drag operations from the source. |
336 | * |
337 | * Note that you must handle the [signal@Gtk.DragSource::drag-end] signal |
338 | * if the actions include %GDK_ACTION_MOVE. |
339 | */ |
340 | properties[PROP_ACTIONS] = |
341 | g_param_spec_flags (name: "actions" , |
342 | P_("Actions" ), |
343 | P_("Supported actions" ), |
344 | flags_type: GDK_TYPE_DRAG_ACTION, default_value: GDK_ACTION_COPY, |
345 | flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); |
346 | |
347 | g_object_class_install_properties (oclass: object_class, n_pspecs: NUM_PROPERTIES, pspecs: properties); |
348 | |
349 | /** |
350 | * GtkDragSource::prepare: |
351 | * @source: the `GtkDragSource` |
352 | * @x: the X coordinate of the drag starting point |
353 | * @y: the Y coordinate fo the drag starting point |
354 | * |
355 | * Emitted when a drag is about to be initiated. |
356 | * |
357 | * It returns the `GdkContentProvider` to use for the drag that is about |
358 | * to start. The default handler for this signal returns the value of |
359 | * the [property@Gtk.DragSource:content] property, so if you set up that |
360 | * property ahead of time, you don't need to connect to this signal. |
361 | * |
362 | * Returns: (transfer full) (nullable): a `GdkContentProvider` |
363 | */ |
364 | signals[PREPARE] = |
365 | g_signal_new (I_("prepare" ), |
366 | G_TYPE_FROM_CLASS (class), |
367 | signal_flags: G_SIGNAL_RUN_LAST, |
368 | G_STRUCT_OFFSET (GtkDragSourceClass, prepare), |
369 | accumulator: g_signal_accumulator_first_wins, NULL, |
370 | NULL, |
371 | GDK_TYPE_CONTENT_PROVIDER, n_params: 2, |
372 | G_TYPE_DOUBLE, G_TYPE_DOUBLE); |
373 | |
374 | /** |
375 | * GtkDragSource::drag-begin: |
376 | * @source: the `GtkDragSource` |
377 | * @drag: the `GdkDrag` object |
378 | * |
379 | * Emitted on the drag source when a drag is started. |
380 | * |
381 | * It can be used to e.g. set a custom drag icon with |
382 | * [method@Gtk.DragSource.set_icon]. |
383 | */ |
384 | signals[DRAG_BEGIN] = |
385 | g_signal_new (I_("drag-begin" ), |
386 | G_TYPE_FROM_CLASS (class), |
387 | signal_flags: G_SIGNAL_RUN_LAST, |
388 | class_offset: 0, |
389 | NULL, NULL, |
390 | NULL, |
391 | G_TYPE_NONE, n_params: 1, |
392 | GDK_TYPE_DRAG); |
393 | |
394 | /** |
395 | * GtkDragSource::drag-end: |
396 | * @source: the `GtkDragSource` |
397 | * @drag: the `GdkDrag` object |
398 | * @delete_data: %TRUE if the drag was performing %GDK_ACTION_MOVE, |
399 | * and the data should be deleted |
400 | * |
401 | * Emitted on the drag source when a drag is finished. |
402 | * |
403 | * A typical reason to connect to this signal is to undo |
404 | * things done in [signal@Gtk.DragSource::prepare] or |
405 | * [signal@Gtk.DragSource::drag-begin] handlers. |
406 | */ |
407 | signals[DRAG_END] = |
408 | g_signal_new (I_("drag-end" ), |
409 | G_TYPE_FROM_CLASS (class), |
410 | signal_flags: G_SIGNAL_RUN_LAST, |
411 | class_offset: 0, |
412 | NULL, NULL, |
413 | NULL, |
414 | G_TYPE_NONE, n_params: 2, |
415 | GDK_TYPE_DRAG, |
416 | G_TYPE_BOOLEAN); |
417 | |
418 | /** |
419 | * GtkDragSource::drag-cancel: |
420 | * @source: the `GtkDragSource` |
421 | * @drag: the `GdkDrag` object |
422 | * @reason: information on why the drag failed |
423 | * |
424 | * Emitted on the drag source when a drag has failed. |
425 | * |
426 | * The signal handler may handle a failed drag operation based on |
427 | * the type of error. It should return %TRUE if the failure has been handled |
428 | * and the default "drag operation failed" animation should not be shown. |
429 | * |
430 | * Returns: %TRUE if the failed drag operation has been already handled |
431 | */ |
432 | signals[DRAG_CANCEL] = |
433 | g_signal_new (I_("drag-cancel" ), |
434 | G_TYPE_FROM_CLASS (class), |
435 | signal_flags: G_SIGNAL_RUN_LAST, |
436 | class_offset: 0, |
437 | accumulator: _gtk_boolean_handled_accumulator, NULL, |
438 | c_marshaller: _gtk_marshal_BOOLEAN__OBJECT_ENUM, |
439 | G_TYPE_BOOLEAN, n_params: 2, |
440 | GDK_TYPE_DRAG, |
441 | GDK_TYPE_DRAG_CANCEL_REASON); |
442 | } |
443 | |
444 | static GdkContentProvider * |
445 | gtk_drag_source_prepare (GtkDragSource *source, |
446 | double x, |
447 | double y) |
448 | { |
449 | if (source->actions == 0) |
450 | return NULL; |
451 | |
452 | if (source->content == NULL) |
453 | return NULL; |
454 | |
455 | return g_object_ref (source->content); |
456 | } |
457 | |
458 | static void |
459 | drag_end (GtkDragSource *source, |
460 | gboolean success) |
461 | { |
462 | gboolean delete_data; |
463 | |
464 | g_signal_handlers_disconnect_by_func (source->drag, gtk_drag_source_dnd_finished_cb, source); |
465 | g_signal_handlers_disconnect_by_func (source->drag, gtk_drag_source_cancel_cb, source); |
466 | |
467 | delete_data = success && gdk_drag_get_selected_action (drag: source->drag) == GDK_ACTION_MOVE; |
468 | |
469 | g_signal_emit (instance: source, signal_id: signals[DRAG_END], detail: 0, source->drag, delete_data); |
470 | |
471 | gdk_drag_drop_done (drag: source->drag, success); |
472 | g_clear_object (&source->drag); |
473 | g_object_unref (object: source); |
474 | } |
475 | |
476 | static void |
477 | gtk_drag_source_dnd_finished_cb (GdkDrag *drag, |
478 | GtkDragSource *source) |
479 | { |
480 | drag_end (source, TRUE); |
481 | } |
482 | |
483 | static void |
484 | gtk_drag_source_cancel_cb (GdkDrag *drag, |
485 | GdkDragCancelReason reason, |
486 | GtkDragSource *source) |
487 | { |
488 | gboolean success = FALSE; |
489 | |
490 | g_signal_emit (instance: source, signal_id: signals[DRAG_CANCEL], detail: 0, source->drag, reason, &success); |
491 | drag_end (source, success); |
492 | } |
493 | |
494 | static void |
495 | gtk_drag_source_ensure_icon (GtkDragSource *self, |
496 | GdkDrag *drag) |
497 | { |
498 | GdkContentProvider *provider; |
499 | GtkWidget *icon, *child; |
500 | GdkContentFormats *formats; |
501 | const GType *types; |
502 | gsize i, n_types; |
503 | |
504 | icon = gtk_drag_icon_get_for_drag (drag); |
505 | /* If an icon has been set already, we don't need to set one. */ |
506 | if (gtk_drag_icon_get_child (self: GTK_DRAG_ICON (ptr: icon))) |
507 | return; |
508 | |
509 | if (self->paintable) |
510 | { |
511 | gtk_drag_icon_set_from_paintable (drag, |
512 | paintable: self->paintable, |
513 | hot_x: self->hot_x, |
514 | hot_y: self->hot_y); |
515 | return; |
516 | } |
517 | |
518 | gdk_drag_set_hotspot (drag, hot_x: -2, hot_y: -2); |
519 | |
520 | provider = gdk_drag_get_content (drag); |
521 | formats = gdk_content_provider_ref_formats (provider); |
522 | types = gdk_content_formats_get_gtypes (formats, n_gtypes: &n_types); |
523 | for (i = 0; i < n_types; i++) |
524 | { |
525 | GValue value = G_VALUE_INIT; |
526 | |
527 | g_value_init (value: &value, g_type: types[i]); |
528 | if (gdk_content_provider_get_value (provider, value: &value, NULL)) |
529 | { |
530 | child = gtk_drag_icon_create_widget_for_value (value: &value); |
531 | |
532 | if (child) |
533 | { |
534 | gtk_drag_icon_set_child (self: GTK_DRAG_ICON (ptr: icon), child); |
535 | g_value_unset (value: &value); |
536 | gdk_content_formats_unref (formats); |
537 | return; |
538 | } |
539 | } |
540 | g_value_unset (value: &value); |
541 | } |
542 | |
543 | gdk_content_formats_unref (formats); |
544 | child = gtk_image_new_from_icon_name (icon_name: "text-x-generic" ); |
545 | gtk_image_set_icon_size (GTK_IMAGE (child), icon_size: GTK_ICON_SIZE_LARGE); |
546 | gtk_drag_icon_set_child (self: GTK_DRAG_ICON (ptr: icon), child); |
547 | } |
548 | |
549 | static void |
550 | gtk_drag_source_drag_begin (GtkDragSource *source) |
551 | { |
552 | GtkWidget *widget; |
553 | GdkDevice *device, *pointer; |
554 | GdkSeat *seat; |
555 | double x, y; |
556 | GtkNative *native; |
557 | GdkSurface *surface; |
558 | double px, py; |
559 | int dx, dy; |
560 | GdkContentProvider *content = NULL; |
561 | |
562 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (source)); |
563 | device = gtk_gesture_get_device (GTK_GESTURE (source)); |
564 | seat = gdk_device_get_seat (device); |
565 | |
566 | if (device == gdk_seat_get_keyboard (seat)) |
567 | pointer = gdk_seat_get_pointer (seat); |
568 | else |
569 | pointer = device; |
570 | |
571 | native = gtk_widget_get_native (widget); |
572 | surface = gtk_native_get_surface (self: native); |
573 | |
574 | gtk_widget_translate_coordinates (src_widget: widget, GTK_WIDGET (native), src_x: source->start_x, src_y: source->start_y, dest_x: &x, dest_y: &y); |
575 | gdk_surface_get_device_position (surface, device: pointer, x: &px, y: &py, NULL); |
576 | |
577 | dx = round (x: px - x); |
578 | dy = round (x: py - y); |
579 | |
580 | g_signal_emit (instance: source, signal_id: signals[PREPARE], detail: 0, source->start_x, source->start_y, &content); |
581 | if (!content) |
582 | return; |
583 | |
584 | source->drag = gdk_drag_begin (surface, device: pointer, content, actions: source->actions, dx, dy); |
585 | |
586 | g_object_unref (object: content); |
587 | |
588 | if (source->drag == NULL) |
589 | return; |
590 | |
591 | gtk_widget_reset_controllers (widget); |
592 | |
593 | g_signal_emit (instance: source, signal_id: signals[DRAG_BEGIN], detail: 0, source->drag); |
594 | |
595 | gtk_drag_source_ensure_icon (self: source, drag: source->drag); |
596 | |
597 | /* Keep the source alive until the drag is done */ |
598 | g_object_ref (source); |
599 | |
600 | g_signal_connect (source->drag, "dnd-finished" , |
601 | G_CALLBACK (gtk_drag_source_dnd_finished_cb), source); |
602 | g_signal_connect (source->drag, "cancel" , |
603 | G_CALLBACK (gtk_drag_source_cancel_cb), source); |
604 | } |
605 | |
606 | /** |
607 | * gtk_drag_source_new: |
608 | * |
609 | * Creates a new `GtkDragSource` object. |
610 | * |
611 | * Returns: the new `GtkDragSource` |
612 | */ |
613 | GtkDragSource * |
614 | gtk_drag_source_new (void) |
615 | { |
616 | return g_object_new (GTK_TYPE_DRAG_SOURCE, NULL); |
617 | } |
618 | |
619 | /** |
620 | * gtk_drag_source_get_content: (attributes org.gtk.Method.get_property=content) |
621 | * @source: a `GtkDragSource` |
622 | * |
623 | * Gets the current content provider of a `GtkDragSource`. |
624 | * |
625 | * Returns: (nullable) (transfer none): the `GdkContentProvider` of @source |
626 | */ |
627 | GdkContentProvider * |
628 | gtk_drag_source_get_content (GtkDragSource *source) |
629 | { |
630 | g_return_val_if_fail (GTK_IS_DRAG_SOURCE (source), NULL); |
631 | |
632 | return source->content; |
633 | } |
634 | |
635 | /** |
636 | * gtk_drag_source_set_content: (attributes org.gtk.Method.set_property=content) |
637 | * @source: a `GtkDragSource` |
638 | * @content: (nullable): a `GdkContentProvider` |
639 | * |
640 | * Sets a content provider on a `GtkDragSource`. |
641 | * |
642 | * When the data is requested in the cause of a DND operation, |
643 | * it will be obtained from the content provider. |
644 | * |
645 | * This function can be called before a drag is started, |
646 | * or in a handler for the [signal@Gtk.DragSource::prepare] signal. |
647 | * |
648 | * You may consider setting the content provider back to |
649 | * %NULL in a [signal@Gtk.DragSource::drag-end] signal handler. |
650 | */ |
651 | void |
652 | gtk_drag_source_set_content (GtkDragSource *source, |
653 | GdkContentProvider *content) |
654 | { |
655 | g_return_if_fail (GTK_IS_DRAG_SOURCE (source)); |
656 | |
657 | if (!g_set_object (&source->content, content)) |
658 | return; |
659 | |
660 | g_object_notify_by_pspec (G_OBJECT (source), pspec: properties[PROP_CONTENT]); |
661 | } |
662 | |
663 | /** |
664 | * gtk_drag_source_get_actions: (attributes org.gtk.Method.get_property=actions) |
665 | * @source: a `GtkDragSource` |
666 | * |
667 | * Gets the actions that are currently set on the `GtkDragSource`. |
668 | * |
669 | * Returns: the actions set on @source |
670 | */ |
671 | GdkDragAction |
672 | gtk_drag_source_get_actions (GtkDragSource *source) |
673 | { |
674 | g_return_val_if_fail (GTK_IS_DRAG_SOURCE (source), 0); |
675 | |
676 | return source->actions; |
677 | } |
678 | |
679 | /** |
680 | * gtk_drag_source_set_actions: (attributes org.gtk.Method.set_property=actions) |
681 | * @source: a `GtkDragSource` |
682 | * @actions: the actions to offer |
683 | * |
684 | * Sets the actions on the `GtkDragSource`. |
685 | * |
686 | * During a DND operation, the actions are offered to potential |
687 | * drop targets. If @actions include %GDK_ACTION_MOVE, you need |
688 | * to listen to the [signal@Gtk.DragSource::drag-end] signal and |
689 | * handle @delete_data being %TRUE. |
690 | * |
691 | * This function can be called before a drag is started, |
692 | * or in a handler for the [signal@Gtk.DragSource::prepare] signal. |
693 | */ |
694 | void |
695 | gtk_drag_source_set_actions (GtkDragSource *source, |
696 | GdkDragAction actions) |
697 | { |
698 | g_return_if_fail (GTK_IS_DRAG_SOURCE (source)); |
699 | |
700 | if (source->actions == actions) |
701 | return; |
702 | |
703 | source->actions = actions; |
704 | |
705 | g_object_notify_by_pspec (G_OBJECT (source), pspec: properties[PROP_ACTIONS]); |
706 | } |
707 | |
708 | /** |
709 | * gtk_drag_source_set_icon: |
710 | * @source: a `GtkDragSource` |
711 | * @paintable: (nullable): the `GdkPaintable` to use as icon |
712 | * @hot_x: the hotspot X coordinate on the icon |
713 | * @hot_y: the hotspot Y coordinate on the icon |
714 | * |
715 | * Sets a paintable to use as icon during DND operations. |
716 | * |
717 | * The hotspot coordinates determine the point on the icon |
718 | * that gets aligned with the hotspot of the cursor. |
719 | * |
720 | * If @paintable is %NULL, a default icon is used. |
721 | * |
722 | * This function can be called before a drag is started, or in |
723 | * a [signal@Gtk.DragSource::prepare] or |
724 | * [signal@Gtk.DragSource::drag-begin] signal handler. |
725 | */ |
726 | void |
727 | gtk_drag_source_set_icon (GtkDragSource *source, |
728 | GdkPaintable *paintable, |
729 | int hot_x, |
730 | int hot_y) |
731 | { |
732 | g_return_if_fail (GTK_IS_DRAG_SOURCE (source)); |
733 | |
734 | g_set_object (&source->paintable, paintable); |
735 | |
736 | source->hot_x = hot_x; |
737 | source->hot_y = hot_y; |
738 | } |
739 | |
740 | /** |
741 | * gtk_drag_source_get_drag: |
742 | * @source: a `GtkDragSource` |
743 | * |
744 | * Returns the underlying `GdkDrag` object for an ongoing drag. |
745 | * |
746 | * Returns: (nullable) (transfer none): the `GdkDrag` of the current |
747 | * drag operation |
748 | */ |
749 | GdkDrag * |
750 | gtk_drag_source_get_drag (GtkDragSource *source) |
751 | { |
752 | g_return_val_if_fail (GTK_IS_DRAG_SOURCE (source), NULL); |
753 | |
754 | return source->drag; |
755 | } |
756 | |
757 | /** |
758 | * gtk_drag_source_drag_cancel: |
759 | * @source: a `GtkDragSource` |
760 | * |
761 | * Cancels a currently ongoing drag operation. |
762 | */ |
763 | void |
764 | gtk_drag_source_drag_cancel (GtkDragSource *source) |
765 | { |
766 | g_return_if_fail (GTK_IS_DRAG_SOURCE (source)); |
767 | |
768 | if (source->drag) |
769 | { |
770 | gboolean success = FALSE; |
771 | |
772 | g_signal_emit (instance: source, signal_id: signals[DRAG_CANCEL], detail: 0, source->drag, GDK_DRAG_CANCEL_ERROR, &success); |
773 | drag_end (source, FALSE); |
774 | } |
775 | } |
776 | |
777 | /** |
778 | * gtk_drag_check_threshold: (method) |
779 | * @widget: a `GtkWidget` |
780 | * @start_x: X coordinate of start of drag |
781 | * @start_y: Y coordinate of start of drag |
782 | * @current_x: current X coordinate |
783 | * @current_y: current Y coordinate |
784 | * |
785 | * Checks to see if a drag movement has passed the GTK drag threshold. |
786 | * |
787 | * Returns: %TRUE if the drag threshold has been passed. |
788 | */ |
789 | gboolean |
790 | gtk_drag_check_threshold (GtkWidget *widget, |
791 | int start_x, |
792 | int start_y, |
793 | int current_x, |
794 | int current_y) |
795 | { |
796 | int drag_threshold; |
797 | |
798 | g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); |
799 | |
800 | drag_threshold = gtk_settings_get_dnd_drag_threshold (settings: gtk_widget_get_settings (widget)); |
801 | |
802 | return (ABS (current_x - start_x) > drag_threshold || |
803 | ABS (current_y - start_y) > drag_threshold); |
804 | } |
805 | |
806 | gboolean |
807 | gtk_drag_check_threshold_double (GtkWidget *widget, |
808 | double start_x, |
809 | double start_y, |
810 | double current_x, |
811 | double current_y) |
812 | { |
813 | int drag_threshold; |
814 | |
815 | g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); |
816 | |
817 | drag_threshold = gtk_settings_get_dnd_drag_threshold (settings: gtk_widget_get_settings (widget)); |
818 | |
819 | return (ABS (current_x - start_x) > drag_threshold || |
820 | ABS (current_y - start_y) > drag_threshold); |
821 | } |
822 | |