1/* GTK - The GIMP Toolkit
2 * Copyright 2019 Matthias Clasen
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#include "config.h"
19
20#include "gtkdragicon.h"
21
22#include "gtkintl.h"
23#include "gtkwidgetprivate.h"
24#include "gtkcssstyleprivate.h"
25#include "gtkcsstypesprivate.h"
26#include "gtknativeprivate.h"
27#include "gtkpicture.h"
28#include "gtkcssboxesimplprivate.h"
29#include "gtkcssnumbervalueprivate.h"
30
31#include "gdk/gdksurfaceprivate.h"
32
33/* for the drag icons */
34#include "gtkcolorswatchprivate.h"
35#include "gtkimage.h"
36#include "gtklabel.h"
37#include "gtkrendernodepaintableprivate.h"
38#include "gtktextutil.h"
39
40
41/**
42 * GtkDragIcon:
43 *
44 * `GtkDragIcon` is a `GtkRoot` implementation for drag icons.
45 *
46 * A drag icon moves with the pointer during a Drag-and-Drop operation
47 * and is destroyed when the drag ends.
48 *
49 * To set up a drag icon and associate it with an ongoing drag operation,
50 * use [func@Gtk.DragIcon.get_for_drag] to get the icon for a drag. You can
51 * then use it like any other widget and use [method@Gtk.DragIcon.set_child]
52 * to set whatever widget should be used for the drag icon.
53 *
54 * Keep in mind that drag icons do not allow user input.
55 */
56struct _GtkDragIcon
57{
58 GtkWidget parent_instance;
59
60 GdkSurface *surface;
61 GskRenderer *renderer;
62 GtkWidget *child;
63};
64
65struct _GtkDragIconClass
66{
67 GtkWidgetClass parent_class;
68};
69
70enum {
71 PROP_0,
72 PROP_CHILD,
73
74 LAST_ARG
75};
76
77static GParamSpec *properties[LAST_ARG] = { NULL, };
78
79static void gtk_drag_icon_root_init (GtkRootInterface *iface);
80static void gtk_drag_icon_native_init (GtkNativeInterface *iface);
81
82G_DEFINE_TYPE_WITH_CODE (GtkDragIcon, gtk_drag_icon, GTK_TYPE_WIDGET,
83 G_IMPLEMENT_INTERFACE (GTK_TYPE_NATIVE,
84 gtk_drag_icon_native_init)
85 G_IMPLEMENT_INTERFACE (GTK_TYPE_ROOT,
86 gtk_drag_icon_root_init))
87
88static GdkDisplay *
89gtk_drag_icon_root_get_display (GtkRoot *self)
90{
91 GtkDragIcon *icon = GTK_DRAG_ICON (ptr: self);
92
93 if (icon->surface)
94 return gdk_surface_get_display (surface: icon->surface);
95
96 return gdk_display_get_default ();
97}
98
99static void
100gtk_drag_icon_root_init (GtkRootInterface *iface)
101{
102 iface->get_display = gtk_drag_icon_root_get_display;
103}
104
105static GdkSurface *
106gtk_drag_icon_native_get_surface (GtkNative *native)
107{
108 GtkDragIcon *icon = GTK_DRAG_ICON (ptr: native);
109
110 return icon->surface;
111}
112
113static GskRenderer *
114gtk_drag_icon_native_get_renderer (GtkNative *native)
115{
116 GtkDragIcon *icon = GTK_DRAG_ICON (ptr: native);
117
118 return icon->renderer;
119}
120
121static void
122gtk_drag_icon_native_get_surface_transform (GtkNative *native,
123 double *x,
124 double *y)
125{
126 GtkCssBoxes css_boxes;
127 const graphene_rect_t *margin_rect;
128
129 gtk_css_boxes_init (boxes: &css_boxes, GTK_WIDGET (native));
130 margin_rect = gtk_css_boxes_get_margin_rect (boxes: &css_boxes);
131
132 *x = - margin_rect->origin.x;
133 *y = - margin_rect->origin.y;
134}
135
136static void
137gtk_drag_icon_move_resize (GtkDragIcon *icon)
138{
139 GtkRequisition req;
140
141 if (icon->surface)
142 {
143 gtk_widget_get_preferred_size (GTK_WIDGET (icon), NULL, natural_size: &req);
144 gdk_drag_surface_present (drag_surface: GDK_DRAG_SURFACE (ptr: icon->surface),
145 MAX (1, req.width),
146 MAX (1, req.height));
147 }
148}
149
150static void
151gtk_drag_icon_present (GtkDragIcon *icon)
152{
153 GtkWidget *widget = GTK_WIDGET (icon);
154
155 if (!_gtk_widget_get_alloc_needed (widget))
156 gtk_widget_ensure_allocate (widget);
157 else if (gtk_widget_get_visible (widget))
158 gtk_drag_icon_move_resize (icon);
159}
160
161static void
162gtk_drag_icon_native_layout (GtkNative *native,
163 int width,
164 int height)
165{
166 gtk_widget_allocate (GTK_WIDGET (native), width, height, baseline: -1, NULL);
167}
168
169static void
170gtk_drag_icon_native_init (GtkNativeInterface *iface)
171{
172 iface->get_surface = gtk_drag_icon_native_get_surface;
173 iface->get_renderer = gtk_drag_icon_native_get_renderer;
174 iface->get_surface_transform = gtk_drag_icon_native_get_surface_transform;
175 iface->layout = gtk_drag_icon_native_layout;
176}
177
178static gboolean
179surface_render (GdkSurface *surface,
180 cairo_region_t *region,
181 GtkWidget *widget)
182{
183 gtk_widget_render (widget, surface, region);
184 return TRUE;
185}
186
187static void
188gtk_drag_icon_realize (GtkWidget *widget)
189{
190 GtkDragIcon *icon = GTK_DRAG_ICON (ptr: widget);
191
192 g_warn_if_fail (icon->surface != NULL);
193
194 gdk_surface_set_widget (self: icon->surface, widget);
195
196 g_signal_connect (icon->surface, "render", G_CALLBACK (surface_render), widget);
197
198 GTK_WIDGET_CLASS (gtk_drag_icon_parent_class)->realize (widget);
199
200 icon->renderer = gsk_renderer_new_for_surface (surface: icon->surface);
201
202 gtk_native_realize (self: GTK_NATIVE (ptr: icon));
203}
204
205static void
206gtk_drag_icon_unrealize (GtkWidget *widget)
207{
208 GtkDragIcon *icon = GTK_DRAG_ICON (ptr: widget);
209
210 gtk_native_unrealize (self: GTK_NATIVE (ptr: icon));
211
212 GTK_WIDGET_CLASS (gtk_drag_icon_parent_class)->unrealize (widget);
213
214 gsk_renderer_unrealize (renderer: icon->renderer);
215 g_clear_object (&icon->renderer);
216
217 if (icon->surface)
218 {
219 g_signal_handlers_disconnect_by_func (icon->surface, surface_render, widget);
220 gdk_surface_set_widget (self: icon->surface, NULL);
221 }
222}
223
224static void
225gtk_drag_icon_map (GtkWidget *widget)
226{
227 GtkDragIcon *icon = GTK_DRAG_ICON (ptr: widget);
228
229 gtk_drag_icon_move_resize (icon);
230
231 GTK_WIDGET_CLASS (gtk_drag_icon_parent_class)->map (widget);
232
233 if (icon->child && gtk_widget_get_visible (widget: icon->child))
234 gtk_widget_map (widget: icon->child);
235}
236
237static void
238gtk_drag_icon_unmap (GtkWidget *widget)
239{
240 GtkDragIcon *icon = GTK_DRAG_ICON (ptr: widget);
241
242 g_warn_if_fail (icon->surface != NULL);
243 GTK_WIDGET_CLASS (gtk_drag_icon_parent_class)->unmap (widget);
244 if (icon->surface)
245 gdk_surface_hide (surface: icon->surface);
246
247 if (icon->child)
248 gtk_widget_unmap (widget: icon->child);
249}
250
251static void
252gtk_drag_icon_measure (GtkWidget *widget,
253 GtkOrientation orientation,
254 int for_size,
255 int *minimum,
256 int *natural,
257 int *minimum_baseline,
258 int *natural_baseline)
259{
260 GtkDragIcon *icon = GTK_DRAG_ICON (ptr: widget);
261
262 if (icon->child)
263 gtk_widget_measure (widget: icon->child,
264 orientation, for_size,
265 minimum, natural,
266 minimum_baseline, natural_baseline);
267}
268
269static void
270gtk_drag_icon_size_allocate (GtkWidget *widget,
271 int width,
272 int height,
273 int baseline)
274{
275 GtkDragIcon *icon = GTK_DRAG_ICON (ptr: widget);
276
277 if (icon->child)
278 gtk_widget_allocate (widget: icon->child, width, height, baseline, NULL);
279}
280
281static void
282gtk_drag_icon_show (GtkWidget *widget)
283{
284 _gtk_widget_set_visible_flag (widget, TRUE);
285 gtk_css_node_validate (cssnode: gtk_widget_get_css_node (widget));
286 gtk_widget_realize (widget);
287 gtk_drag_icon_present (icon: GTK_DRAG_ICON (ptr: widget));
288 gtk_widget_map (widget);
289}
290
291static void
292gtk_drag_icon_hide (GtkWidget *widget)
293{
294 _gtk_widget_set_visible_flag (widget, FALSE);
295 gtk_widget_unmap (widget);
296}
297
298static void
299gtk_drag_icon_dispose (GObject *object)
300{
301 GtkDragIcon *icon = GTK_DRAG_ICON (ptr: object);
302
303 g_clear_pointer (&icon->child, gtk_widget_unparent);
304
305 G_OBJECT_CLASS (gtk_drag_icon_parent_class)->dispose (object);
306
307 g_clear_object (&icon->surface);
308}
309
310static void
311gtk_drag_icon_get_property (GObject *object,
312 guint prop_id,
313 GValue *value,
314 GParamSpec *pspec)
315{
316 GtkDragIcon *self = GTK_DRAG_ICON (ptr: object);
317
318 switch (prop_id)
319 {
320 case PROP_CHILD:
321 g_value_set_object (value, v_object: self->child);
322 break;
323
324 default:
325 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
326 break;
327 }
328}
329
330static void
331gtk_drag_icon_set_property (GObject *object,
332 guint prop_id,
333 const GValue *value,
334 GParamSpec *pspec)
335{
336 GtkDragIcon *self = GTK_DRAG_ICON (ptr: object);
337
338 switch (prop_id)
339 {
340 case PROP_CHILD:
341 gtk_drag_icon_set_child (self, child: g_value_get_object (value));
342 break;
343
344 default:
345 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
346 break;
347 }
348}
349
350static void
351gtk_drag_icon_class_init (GtkDragIconClass *klass)
352{
353 GObjectClass *object_class = G_OBJECT_CLASS (klass);
354 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
355
356 object_class->dispose = gtk_drag_icon_dispose;
357 object_class->get_property = gtk_drag_icon_get_property;
358 object_class->set_property = gtk_drag_icon_set_property;
359
360 widget_class->realize = gtk_drag_icon_realize;
361 widget_class->unrealize = gtk_drag_icon_unrealize;
362 widget_class->map = gtk_drag_icon_map;
363 widget_class->unmap = gtk_drag_icon_unmap;
364 widget_class->measure = gtk_drag_icon_measure;
365 widget_class->size_allocate = gtk_drag_icon_size_allocate;
366 widget_class->show = gtk_drag_icon_show;
367 widget_class->hide = gtk_drag_icon_hide;
368
369 /**
370 * GtkDragIcon:child: (attributes org.gtk.Property.get=gtk_drag_icon_get_child org.gtk.Property.set=gtk_drag_icon_set_child)
371 *
372 * The widget to display as drag icon.
373 */
374 properties[PROP_CHILD] =
375 g_param_spec_object (name: "child",
376 P_("Child"),
377 P_("The widget to display as drag icon."),
378 GTK_TYPE_WIDGET,
379 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
380
381 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_ARG, pspecs: properties);
382
383 gtk_widget_class_set_css_name (widget_class, name: "dnd");
384}
385
386static void
387gtk_drag_icon_init (GtkDragIcon *self)
388{
389 gtk_widget_set_can_target (GTK_WIDGET (self), FALSE);
390}
391
392/**
393 * gtk_drag_icon_get_for_drag:
394 * @drag: a `GdkDrag`
395 *
396 * Gets the `GtkDragIcon` in use with @drag.
397 *
398 * If no drag icon exists yet, a new one will be created
399 * and shown.
400 *
401 * Returns: (transfer none): the `GtkDragIcon`
402 */
403GtkWidget *
404gtk_drag_icon_get_for_drag (GdkDrag *drag)
405{
406 static GQuark drag_icon_quark = 0;
407 GtkWidget *self;
408
409 g_return_val_if_fail (GDK_IS_DRAG (drag), NULL);
410
411 if (G_UNLIKELY (drag_icon_quark == 0))
412 drag_icon_quark = g_quark_from_static_string (string: "-gtk-drag-icon");
413
414 self = g_object_get_qdata (G_OBJECT (drag), quark: drag_icon_quark);
415 if (self == NULL)
416 {
417 self = g_object_new (GTK_TYPE_DRAG_ICON, NULL);
418
419 GTK_DRAG_ICON (ptr: self)->surface = g_object_ref (gdk_drag_get_drag_surface (drag));
420
421 g_object_set_qdata_full (G_OBJECT (drag), quark: drag_icon_quark, g_object_ref_sink (self), destroy: g_object_unref);
422
423 if (GTK_DRAG_ICON (ptr: self)->child != NULL)
424 gtk_widget_show (widget: self);
425 }
426
427 return self;
428}
429
430/**
431 * gtk_drag_icon_set_from_paintable:
432 * @drag: a `GdkDrag`
433 * @paintable: a `GdkPaintable` to display
434 * @hot_x: X coordinate of the hotspot
435 * @hot_y: Y coordinate of the hotspot
436 *
437 * Creates a `GtkDragIcon` that shows @paintable, and associates
438 * it with the drag operation.
439 *
440 * The hotspot position on the paintable is aligned with the
441 * hotspot of the cursor.
442 */
443void
444gtk_drag_icon_set_from_paintable (GdkDrag *drag,
445 GdkPaintable *paintable,
446 int hot_x,
447 int hot_y)
448{
449 GtkWidget *icon;
450 GtkWidget *picture;
451
452 gdk_drag_set_hotspot (drag, hot_x, hot_y);
453
454 icon = gtk_drag_icon_get_for_drag (drag);
455
456 picture = gtk_picture_new_for_paintable (paintable);
457 gtk_picture_set_can_shrink (self: GTK_PICTURE (ptr: picture), FALSE);
458 gtk_drag_icon_set_child (self: GTK_DRAG_ICON (ptr: icon), child: picture);
459}
460
461/**
462 * gtk_drag_icon_set_child: (attributes org.gtk.Method.set_property=child)
463 * @self: a `GtkDragIcon`
464 * @child: (nullable): a `GtkWidget`
465 *
466 * Sets the widget to display as the drag icon.
467 */
468void
469gtk_drag_icon_set_child (GtkDragIcon *self,
470 GtkWidget *child)
471{
472 g_return_if_fail (GTK_IS_DRAG_ICON (self));
473 g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
474
475 if (self->child == child)
476 return;
477
478 if (self->child)
479 gtk_widget_unparent (widget: self->child);
480
481 self->child = child;
482
483 if (self->child)
484 {
485 gtk_widget_set_parent (widget: self->child, GTK_WIDGET (self));
486 gtk_widget_show (GTK_WIDGET (self));
487 }
488
489 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_CHILD]);
490}
491
492/**
493 * gtk_drag_icon_get_child: (attributes org.gtk.Method.get_property=child)
494 * @self: a `GtkDragIcon`
495 *
496 * Gets the widget currently used as drag icon.
497 *
498 * Returns: (nullable) (transfer none): The drag icon
499 **/
500GtkWidget *
501gtk_drag_icon_get_child (GtkDragIcon *self)
502{
503 g_return_val_if_fail (GTK_IS_DRAG_ICON (self), NULL);
504
505 return self->child;
506}
507
508/**
509 * gtk_drag_icon_create_widget_for_value:
510 * @value: a `GValue`
511 *
512 * Creates a widget that can be used as a drag icon for the given
513 * @value.
514 *
515 * Supported types include strings, `GdkRGBA` and `GtkTextBuffer`.
516 * If GTK does not know how to create a widget for a given value,
517 * it will return %NULL.
518 *
519 * This method is used to set the default drag icon on drag-and-drop
520 * operations started by `GtkDragSource`, so you don't need to set
521 * a drag icon using this function there.
522 *
523 * Returns: (nullable) (transfer full): A new `GtkWidget`
524 * for displaying @value as a drag icon.
525 */
526GtkWidget *
527gtk_drag_icon_create_widget_for_value (const GValue *value)
528{
529 g_return_val_if_fail (G_IS_VALUE (value), NULL);
530
531 if (G_VALUE_HOLDS (value, G_TYPE_STRING))
532 {
533 return gtk_label_new (str: g_value_get_string (value));
534 }
535 else if (G_VALUE_HOLDS (value, GDK_TYPE_PAINTABLE))
536 {
537 GtkWidget *image;
538
539 image = gtk_image_new_from_paintable (paintable: g_value_get_object (value));
540 gtk_widget_add_css_class (widget: image, css_class: "large-icons");
541
542 return image;
543 }
544 else if (G_VALUE_HOLDS (value, GDK_TYPE_RGBA))
545 {
546 GtkWidget *swatch;
547
548 swatch = gtk_color_swatch_new ();
549 gtk_color_swatch_set_can_drag (GTK_COLOR_SWATCH (swatch), FALSE);
550 gtk_color_swatch_set_can_drop (GTK_COLOR_SWATCH (swatch), FALSE);
551 gtk_color_swatch_set_rgba (GTK_COLOR_SWATCH (swatch), color: g_value_get_boxed (value));
552
553 return swatch;
554 }
555 else if (G_VALUE_HOLDS (value, G_TYPE_FILE))
556 {
557 GFileInfo *info;
558 GtkWidget *image;
559
560 info = g_file_query_info (G_FILE (g_value_get_object (value)), attributes: "standard::icon", flags: 0, NULL, NULL);
561 image = gtk_image_new_from_gicon (icon: g_file_info_get_icon (info));
562 gtk_widget_add_css_class (widget: image, css_class: "large-icons");
563 g_object_unref (object: info);
564
565 return image;
566 }
567 else if (G_VALUE_HOLDS (value, GTK_TYPE_TEXT_BUFFER))
568 {
569 GtkTextBuffer *buffer = g_value_get_object (value);
570 GtkTextIter start, end;
571 GdkPaintable *paintable;
572 GtkWidget *picture;
573
574 if (buffer == NULL || !gtk_text_buffer_get_selection_bounds (buffer, start: &start, end: &end))
575 return NULL;
576
577 picture = gtk_picture_new ();
578 paintable = gtk_text_util_create_rich_drag_icon (widget: picture, buffer, start: &start, end: &end);
579 gtk_picture_set_paintable (self: GTK_PICTURE (ptr: picture), paintable);
580 gtk_picture_set_can_shrink (self: GTK_PICTURE (ptr: picture), FALSE);
581 g_object_unref (object: paintable);
582
583 return picture;
584 }
585 else if (G_VALUE_HOLDS (value, GSK_TYPE_RENDER_NODE))
586 {
587 GskRenderNode *node;
588 GdkPaintable *paintable;
589 graphene_rect_t bounds;
590 GtkWidget *image;
591
592 node = gsk_value_get_render_node (value);
593 if (node == NULL)
594 return NULL;
595
596 gsk_render_node_get_bounds (node, bounds: &bounds);
597 paintable = gtk_render_node_paintable_new (node, bounds: &bounds);
598 image = gtk_image_new_from_paintable (paintable);
599 gtk_image_set_icon_size (GTK_IMAGE (image), icon_size: GTK_ICON_SIZE_LARGE);
600 g_object_unref (object: paintable);
601
602 return image;
603 }
604 else
605 {
606 return NULL;
607 }
608}
609
610

source code of gtk/gtk/gtkdragicon.c