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 | |
9 | static GdkTexture * |
10 | render_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 | |
56 | static GdkTexture * |
57 | get_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 | |
97 | enum { |
98 | TOP_LEFT, |
99 | CENTER, |
100 | BOTTOM_RIGHT |
101 | }; |
102 | |
103 | static void |
104 | got_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 | |
129 | static void |
130 | perform_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 | |
142 | static void |
143 | do_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 | |
153 | static void |
154 | do_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 | |
166 | static void |
167 | ask_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 | |
194 | static gboolean |
195 | delayed_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 | |
210 | static gboolean |
211 | image_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 | |
223 | static gboolean |
224 | image_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 | |
244 | static void |
245 | update_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 | |
283 | static GdkContentProvider * |
284 | drag_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 | |
307 | static void |
308 | drag_begin (GtkDragSource *source) |
309 | { |
310 | g_print (format: "drag begin\n" ); |
311 | } |
312 | |
313 | static void |
314 | drag_end (GtkDragSource *source) |
315 | { |
316 | g_print (format: "drag end\n" ); |
317 | } |
318 | |
319 | static gboolean |
320 | drag_cancel (GtkDragSource *source, |
321 | GdkDrag *drag, |
322 | GdkDragCancelReason reason) |
323 | { |
324 | g_print (format: "drag failed: %d\n" , reason); |
325 | return FALSE; |
326 | } |
327 | |
328 | static GtkWidget * |
329 | make_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 | |
363 | static void |
364 | spinner_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 | |
375 | static GtkWidget * |
376 | make_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 | |
396 | int |
397 | main (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 | |