1#include <gtk/gtk.h>
2
3#ifdef HAVE_UNISTD_H
4#include <unistd.h>
5#elif defined (G_OS_WIN32)
6#include <io.h>
7#endif
8
9static GdkTexture *
10render_paintable_to_texture (GdkPaintable *paintable)
11{
12 GtkSnapshot *snapshot;
13 GskRenderNode *node;
14 int width, height;
15 cairo_surface_t *surface;
16 cairo_t *cr;
17 GdkTexture *texture;
18 GBytes *bytes;
19
20 width = gdk_paintable_get_intrinsic_width (paintable);
21 if (width == 0)
22 width = 32;
23
24 height = gdk_paintable_get_intrinsic_height (paintable);
25 if (height == 0)
26 height = 32;
27
28 surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32, width, height);
29
30 snapshot = gtk_snapshot_new ();
31 gdk_paintable_snapshot (paintable, snapshot, width, height);
32 node = gtk_snapshot_free_to_node (snapshot);
33
34 cr = cairo_create (target: surface);
35 gsk_render_node_draw (node, cr);
36 cairo_destroy (cr);
37
38 gsk_render_node_unref (node);
39
40 bytes = g_bytes_new_with_free_func (data: cairo_image_surface_get_data (surface),
41 size: cairo_image_surface_get_height (surface)
42 * cairo_image_surface_get_stride (surface),
43 free_func: (GDestroyNotify) cairo_surface_destroy,
44 user_data: cairo_surface_reference (surface));
45 texture = gdk_memory_texture_new (width: cairo_image_surface_get_width (surface),
46 height: cairo_image_surface_get_height (surface),
47 GDK_MEMORY_DEFAULT,
48 bytes,
49 stride: cairo_image_surface_get_stride (surface));
50 g_bytes_unref (bytes);
51 cairo_surface_destroy (surface);
52
53 return texture;
54}
55
56static GdkTexture *
57get_image_texture (GtkImage *image)
58{
59 GtkIconTheme *icon_theme;
60 const char *icon_name;
61 int width = 48;
62 GdkPaintable *paintable = NULL;
63 GdkTexture *texture = NULL;
64 GtkIconPaintable *icon;
65
66 switch (gtk_image_get_storage_type (image))
67 {
68 case GTK_IMAGE_PAINTABLE:
69 paintable = gtk_image_get_paintable (image);
70 break;
71 case GTK_IMAGE_ICON_NAME:
72 icon_name = gtk_image_get_icon_name (image);
73 icon_theme = gtk_icon_theme_get_for_display (display: gtk_widget_get_display (GTK_WIDGET (image)));
74 icon = gtk_icon_theme_lookup_icon (self: icon_theme,
75 icon_name,
76 NULL,
77 size: width, scale: 1,
78 direction: gtk_widget_get_direction (GTK_WIDGET (image)),
79 flags: 0);
80 paintable = GDK_PAINTABLE (ptr: icon);
81 break;
82 case GTK_IMAGE_GICON:
83 case GTK_IMAGE_EMPTY:
84 default:
85 g_warning ("Image storage type %d not handled",
86 gtk_image_get_storage_type (image));
87 }
88
89 if (paintable)
90 texture = render_paintable_to_texture (paintable);
91
92 g_object_unref (object: paintable);
93
94 return texture;
95}
96
97enum {
98 TOP_LEFT,
99 CENTER,
100 BOTTOM_RIGHT
101};
102
103static void
104got_texture (GObject *source,
105 GAsyncResult *result,
106 gpointer data)
107{
108 GdkDrop *drop = GDK_DROP (source);
109 GtkWidget *image = data;
110 const GValue *value;
111 GError *error = NULL;
112
113 value = gdk_drop_read_value_finish (self: drop, result, error: &error);
114 if (value)
115 {
116 GdkTexture *texture = g_value_get_object (value);
117 gtk_image_set_from_paintable (GTK_IMAGE (image), paintable: GDK_PAINTABLE (ptr: texture));
118 gdk_drop_finish (self: drop, action: GDK_ACTION_COPY);
119 }
120 else
121 {
122 g_error_free (error);
123 gdk_drop_finish (self: drop, action: 0);
124 }
125
126 g_object_set_data (G_OBJECT (image), key: "drop", NULL);
127}
128
129static void
130perform_drop (GdkDrop *drop,
131 GtkWidget *image)
132{
133 if (gdk_content_formats_contain_gtype (formats: gdk_drop_get_formats (self: drop), GDK_TYPE_TEXTURE))
134 gdk_drop_read_value_async (self: drop, GDK_TYPE_TEXTURE, G_PRIORITY_DEFAULT, NULL, callback: got_texture, user_data: image);
135 else
136 {
137 gdk_drop_finish (self: drop, action: 0);
138 g_object_set_data (G_OBJECT (image), key: "drop", NULL);
139 }
140}
141
142static void
143do_copy (GtkWidget *button)
144{
145 GtkWidget *popover = gtk_widget_get_ancestor (widget: button, GTK_TYPE_POPOVER);
146 GtkWidget *image = gtk_widget_get_parent (widget: popover);
147 GdkDrop *drop = GDK_DROP (g_object_get_data (G_OBJECT (image), "drop"));
148
149 gtk_popover_popdown (GTK_POPOVER (popover));
150 perform_drop (drop, image);
151}
152
153static void
154do_cancel (GtkWidget *button)
155{
156 GtkWidget *popover = gtk_widget_get_ancestor (widget: button, GTK_TYPE_POPOVER);
157 GtkWidget *image = gtk_widget_get_parent (widget: popover);
158 GdkDrop *drop = GDK_DROP (g_object_get_data (G_OBJECT (image), "drop"));
159
160 gtk_popover_popdown (GTK_POPOVER (popover));
161 gdk_drop_finish (self: drop, action: 0);
162
163 g_object_set_data (G_OBJECT (image), key: "drop", NULL);
164}
165
166static void
167ask_actions (GdkDrop *drop,
168 GtkWidget *image)
169{
170 GtkWidget *popover, *box, *button;
171
172 popover = g_object_get_data (G_OBJECT (image), key: "popover");
173 if (!popover)
174 {
175 popover = gtk_popover_new ();
176 gtk_widget_set_parent (widget: popover, parent: image);
177 g_object_set_data (G_OBJECT (image), key: "popover", data: popover);
178
179 box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
180 gtk_popover_set_child (GTK_POPOVER (popover), child: box);
181 button = gtk_button_new_with_label (label: "Copy");
182 g_signal_connect (button, "clicked", G_CALLBACK (do_copy), NULL);
183 gtk_box_append (GTK_BOX (box), child: button);
184 button = gtk_button_new_with_label (label: "Move");
185 g_signal_connect (button, "clicked", G_CALLBACK (do_copy), NULL);
186 gtk_box_append (GTK_BOX (box), child: button);
187 button = gtk_button_new_with_label (label: "Cancel");
188 g_signal_connect (button, "clicked", G_CALLBACK (do_cancel), NULL);
189 gtk_box_append (GTK_BOX (box), child: button);
190 }
191 gtk_popover_popup (GTK_POPOVER (popover));
192}
193
194static gboolean
195delayed_deny (gpointer data)
196{
197 GtkDropTargetAsync *dest = data;
198 GtkWidget *image = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (dest));
199 GdkDrop *drop = GDK_DROP (g_object_get_data (G_OBJECT (image), "drop"));
200
201 if (drop)
202 {
203 g_print (format: "denying drop, late\n");
204 gtk_drop_target_async_reject_drop (self: dest, drop);
205 }
206
207 return G_SOURCE_REMOVE;
208}
209
210static gboolean
211image_drag_accept (GtkDropTargetAsync *dest,
212 GdkDrop *drop,
213 gpointer data)
214{
215 GtkWidget *image = data;
216 g_object_set_data_full (G_OBJECT (image), key: "drop", g_object_ref (drop), destroy: g_object_unref);
217
218 g_timeout_add (interval: 1000, function: delayed_deny, data: dest);
219
220 return TRUE;
221}
222
223static gboolean
224image_drag_drop (GtkDropTarget *dest,
225 GdkDrop *drop,
226 double x,
227 double y,
228 gpointer data)
229{
230 GtkWidget *image = data;
231 GdkDragAction action = gdk_drop_get_actions (self: drop);
232
233 g_object_set_data_full (G_OBJECT (image), key: "drop", g_object_ref (drop), destroy: g_object_unref);
234
235 g_print (format: "drop, actions %d\n", action);
236 if (!gdk_drag_action_is_unique (action))
237 ask_actions (drop, image);
238 else
239 perform_drop (drop, image);
240
241 return TRUE;
242}
243
244static void
245update_source_icon (GtkDragSource *source,
246 const char *icon_name,
247 int hotspot)
248{
249 GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (source));
250 GtkIconPaintable *icon;
251 int hot_x, hot_y;
252 int size = 48;
253
254 if (widget == NULL)
255 return;
256
257 icon = gtk_icon_theme_lookup_icon (self: gtk_icon_theme_get_for_display (display: gtk_widget_get_display (widget)),
258 icon_name,
259 NULL,
260 size, scale: 1,
261 direction: gtk_widget_get_direction (widget),
262 flags: 0);
263 switch (hotspot)
264 {
265 default:
266 case TOP_LEFT:
267 hot_x = 0;
268 hot_y = 0;
269 break;
270 case CENTER:
271 hot_x = size / 2;
272 hot_y = size / 2;
273 break;
274 case BOTTOM_RIGHT:
275 hot_x = size;
276 hot_y = size;
277 break;
278 }
279 gtk_drag_source_set_icon (source, paintable: GDK_PAINTABLE (ptr: icon), hot_x, hot_y);
280 g_object_unref (object: icon);
281}
282
283static GdkContentProvider *
284drag_prepare (GtkDragSource *source,
285 double x,
286 double y)
287{
288 GtkImage *image = GTK_IMAGE (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (source)));
289 GdkTexture *texture;
290 GdkContentProvider *content;
291
292 content = gdk_content_provider_new_typed (G_TYPE_STRING, gtk_image_get_icon_name (GTK_IMAGE (image)));
293
294 texture = get_image_texture (image);
295 if (texture)
296 {
297 content = gdk_content_provider_new_union (providers: (GdkContentProvider *[2]) {
298 gdk_content_provider_new_typed (GDK_TYPE_TEXTURE, texture),
299 content,
300 }, n_providers: 2);
301 g_object_unref (object: texture);
302 }
303
304 return content;
305}
306
307static void
308drag_begin (GtkDragSource *source)
309{
310 g_print (format: "drag begin\n");
311}
312
313static void
314drag_end (GtkDragSource *source)
315{
316 g_print (format: "drag end\n");
317}
318
319static gboolean
320drag_cancel (GtkDragSource *source,
321 GdkDrag *drag,
322 GdkDragCancelReason reason)
323{
324 g_print (format: "drag failed: %d\n", reason);
325 return FALSE;
326}
327
328static GtkWidget *
329make_image (const char *icon_name, int hotspot)
330{
331 GtkWidget *image;
332 GtkDragSource *source;
333 GtkDropTargetAsync *dest;
334 GdkContentFormats *formats;
335 GdkContentFormatsBuilder *builder;
336
337 image = gtk_image_new_from_icon_name (icon_name);
338 gtk_image_set_icon_size (GTK_IMAGE (image), icon_size: GTK_ICON_SIZE_LARGE);
339
340 builder = gdk_content_formats_builder_new ();
341 gdk_content_formats_builder_add_gtype (builder, GDK_TYPE_TEXTURE);
342 gdk_content_formats_builder_add_gtype (builder, G_TYPE_STRING);
343 formats = gdk_content_formats_builder_free_to_formats (builder);
344
345 source = gtk_drag_source_new ();
346 gtk_drag_source_set_actions (source, actions: GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_ASK);
347 update_source_icon (source, icon_name, hotspot);
348
349 g_signal_connect (source, "prepare", G_CALLBACK (drag_prepare), NULL);
350 g_signal_connect (source, "drag-begin", G_CALLBACK (drag_begin), NULL);
351 g_signal_connect (source, "drag-end", G_CALLBACK (drag_end), NULL);
352 g_signal_connect (source, "drag-cancel", G_CALLBACK (drag_cancel), NULL);
353 gtk_widget_add_controller (widget: image, GTK_EVENT_CONTROLLER (source));
354
355 dest = gtk_drop_target_async_new (formats, actions: GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_ASK);
356 g_signal_connect (dest, "accept", G_CALLBACK (image_drag_accept), image);
357 g_signal_connect (dest, "drop", G_CALLBACK (image_drag_drop), image);
358 gtk_widget_add_controller (widget: image, GTK_EVENT_CONTROLLER (dest));
359
360 return image;
361}
362
363static void
364spinner_drag_begin (GtkDragSource *source,
365 GdkDrag *drag,
366 GtkWidget *widget)
367{
368 GdkPaintable *paintable;
369
370 paintable = gtk_widget_paintable_new (widget);
371 gtk_drag_source_set_icon (source, paintable, hot_x: 0, hot_y: 0);
372 g_object_unref (object: paintable);
373}
374
375static GtkWidget *
376make_spinner (void)
377{
378 GtkWidget *spinner;
379 GtkDragSource *source;
380 GdkContentProvider *content;
381
382 spinner = gtk_spinner_new ();
383 gtk_spinner_start (GTK_SPINNER (spinner));
384
385 content = gdk_content_provider_new_typed (G_TYPE_STRING, "ACTIVE");
386 source = gtk_drag_source_new ();
387 gtk_drag_source_set_content (source, content);
388 g_signal_connect (source, "drag-begin", G_CALLBACK (spinner_drag_begin), spinner);
389 gtk_widget_add_controller (widget: spinner, GTK_EVENT_CONTROLLER (source));
390
391 g_object_unref (object: content);
392
393 return spinner;
394}
395
396int
397main (int argc, char *Argv[])
398{
399 GtkWidget *window;
400 GtkWidget *grid;
401 GtkWidget *entry;
402
403 gtk_init ();
404
405 window = gtk_window_new ();
406 gtk_window_set_title (GTK_WINDOW (window), title: "Drag And Drop");
407 gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
408
409 grid = gtk_grid_new ();
410 g_object_set (object: grid,
411 first_property_name: "margin-start", 20,
412 "margin-end", 20,
413 "margin-top", 20,
414 "margin-bottom", 20,
415 "row-spacing", 20,
416 "column-spacing", 20,
417 NULL);
418 gtk_window_set_child (GTK_WINDOW (window), child: grid);
419 gtk_grid_attach (GTK_GRID (grid), child: make_image (icon_name: "dialog-warning", hotspot: TOP_LEFT), column: 0, row: 0, width: 1, height: 1);
420 gtk_grid_attach (GTK_GRID (grid), child: make_image (icon_name: "process-stop", hotspot: BOTTOM_RIGHT), column: 1, row: 0, width: 1, height: 1);
421
422 entry = gtk_entry_new ();
423 gtk_grid_attach (GTK_GRID (grid), child: entry, column: 0, row: 1, width: 2, height: 1);
424
425 gtk_grid_attach (GTK_GRID (grid), child: make_spinner (), column: 0, row: 2, width: 1, height: 1);
426 gtk_grid_attach (GTK_GRID (grid), child: make_image (icon_name: "weather-clear", hotspot: CENTER), column: 1, row: 2, width: 1, height: 1);
427
428 gtk_grid_attach (GTK_GRID (grid), child: make_image (icon_name: "dialog-question", hotspot: TOP_LEFT), column: 0, row: 3, width: 1, height: 1);
429
430 gtk_grid_attach (GTK_GRID (grid), child: make_image (icon_name: "dialog-information", hotspot: CENTER), column: 1, row: 3, width: 1, height: 1);
431
432 gtk_widget_show (widget: window);
433 while (TRUE)
434 g_main_context_iteration (NULL, TRUE);
435
436 return 0;
437}
438

source code of gtk/tests/testdnd2.c