1 | /* Drag-and-Drop |
2 | * #Keywords: dnd, menu, popover, gesture |
3 | * |
4 | * This demo shows dragging colors and widgets. |
5 | * The items in this demo can be moved, recolored |
6 | * and rotated. |
7 | * |
8 | * The demo also has an example for creating a |
9 | * menu-like popover without using a menu model. |
10 | */ |
11 | |
12 | #include <gtk/gtk.h> |
13 | |
14 | G_DECLARE_FINAL_TYPE (CanvasItem, canvas_item, CANVAS, ITEM, GtkWidget) |
15 | |
16 | struct _CanvasItem { |
17 | GtkWidget parent; |
18 | |
19 | GtkWidget *fixed; |
20 | GtkWidget *label; |
21 | |
22 | double r; |
23 | double angle; |
24 | double delta; |
25 | |
26 | GtkWidget *editor; |
27 | }; |
28 | |
29 | struct _CanvasItemClass { |
30 | GtkWidgetClass parent_class; |
31 | }; |
32 | |
33 | G_DEFINE_TYPE (CanvasItem, canvas_item, GTK_TYPE_WIDGET) |
34 | |
35 | static int n_items = 0; |
36 | |
37 | static void |
38 | set_color (CanvasItem *item, |
39 | GdkRGBA *color) |
40 | { |
41 | char *css; |
42 | char *str; |
43 | GtkStyleContext *context; |
44 | GtkCssProvider *provider; |
45 | const char *old_class; |
46 | |
47 | str = gdk_rgba_to_string (rgba: color); |
48 | css = g_strdup_printf (format: "* { background: %s; }" , str); |
49 | |
50 | context = gtk_widget_get_style_context (widget: item->label); |
51 | provider = g_object_get_data (G_OBJECT (context), key: "style-provider" ); |
52 | if (provider) |
53 | gtk_style_context_remove_provider (context, GTK_STYLE_PROVIDER (provider)); |
54 | |
55 | old_class = (const char *)g_object_get_data (G_OBJECT (item->label), key: "css-class" ); |
56 | if (old_class) |
57 | gtk_widget_remove_css_class (widget: item->label, css_class: old_class); |
58 | |
59 | provider = gtk_css_provider_new (); |
60 | gtk_css_provider_load_from_data (css_provider: provider, data: css, length: -1); |
61 | gtk_style_context_add_provider (context: gtk_widget_get_style_context (widget: item->label), GTK_STYLE_PROVIDER (provider), priority: 800); |
62 | g_object_set_data_full (G_OBJECT (context), key: "style-provider" , data: provider, destroy: g_object_unref); |
63 | |
64 | g_free (mem: str); |
65 | g_free (mem: css); |
66 | } |
67 | |
68 | static void |
69 | set_css (CanvasItem *item, |
70 | const char *class) |
71 | { |
72 | GtkStyleContext *context; |
73 | GtkCssProvider *provider; |
74 | const char *old_class; |
75 | |
76 | context = gtk_widget_get_style_context (widget: item->label); |
77 | provider = g_object_get_data (G_OBJECT (context), key: "style-provider" ); |
78 | if (provider) |
79 | gtk_style_context_remove_provider (context, GTK_STYLE_PROVIDER (provider)); |
80 | |
81 | old_class = (const char *)g_object_get_data (G_OBJECT (item->label), key: "css-class" ); |
82 | if (old_class) |
83 | gtk_widget_remove_css_class (widget: item->label, css_class: old_class); |
84 | |
85 | g_object_set_data_full (G_OBJECT (item->label), key: "css-class" , data: g_strdup (str: class), destroy: g_free); |
86 | gtk_widget_add_css_class (widget: item->label, css_class: class); |
87 | } |
88 | |
89 | static gboolean |
90 | item_drag_drop (GtkDropTarget *dest, |
91 | const GValue *value, |
92 | double x, |
93 | double y) |
94 | { |
95 | GtkWidget *label = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (dest)); |
96 | CanvasItem *item = CANVAS_ITEM (ptr: gtk_widget_get_parent (widget: gtk_widget_get_parent (widget: label))); |
97 | |
98 | if (G_VALUE_TYPE (value) == GDK_TYPE_RGBA) |
99 | set_color (item, color: g_value_get_boxed (value)); |
100 | else if (G_VALUE_TYPE (value) == G_TYPE_STRING) |
101 | set_css (item, class: g_value_get_string (value)); |
102 | |
103 | return TRUE; |
104 | } |
105 | |
106 | static void |
107 | apply_transform (CanvasItem *item) |
108 | { |
109 | GskTransform *transform; |
110 | double x, y; |
111 | |
112 | x = gtk_widget_get_allocated_width (widget: item->label) / 2.0; |
113 | y = gtk_widget_get_allocated_height (widget: item->label) / 2.0; |
114 | item->r = sqrt (x: x*x + y*y); |
115 | |
116 | transform = gsk_transform_translate (NULL, point: &(graphene_point_t) { item->r, item->r }); |
117 | transform = gsk_transform_rotate (next: transform, angle: item->angle + item->delta); |
118 | transform = gsk_transform_translate (next: transform, point: &(graphene_point_t) { -x, -y }); |
119 | |
120 | gtk_fixed_set_child_transform (GTK_FIXED (item->fixed), widget: item->label, transform); |
121 | gsk_transform_unref (self: transform); |
122 | } |
123 | |
124 | static void |
125 | angle_changed (GtkGestureRotate *gesture, |
126 | double angle, |
127 | double delta) |
128 | { |
129 | CanvasItem *item = CANVAS_ITEM (ptr: gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture))); |
130 | |
131 | item->delta = angle / M_PI * 180.0; |
132 | |
133 | apply_transform (item); |
134 | } |
135 | |
136 | static void |
137 | rotate_done (GtkGesture *gesture) |
138 | { |
139 | CanvasItem *item = CANVAS_ITEM (ptr: gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture))); |
140 | |
141 | item->angle = item->angle + item->delta; |
142 | item->delta = 0; |
143 | } |
144 | |
145 | static void |
146 | click_done (GtkGesture *gesture) |
147 | { |
148 | GtkWidget *item = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); |
149 | GtkWidget *canvas = gtk_widget_get_parent (widget: item); |
150 | GtkWidget *last_child; |
151 | |
152 | last_child = gtk_widget_get_last_child (widget: canvas); |
153 | if (item != last_child) |
154 | gtk_widget_insert_after (widget: item, parent: canvas, previous_sibling: last_child); |
155 | } |
156 | |
157 | static gboolean |
158 | theme_is_dark (void) |
159 | { |
160 | GtkSettings *settings; |
161 | char *theme; |
162 | gboolean prefer_dark; |
163 | gboolean dark; |
164 | |
165 | settings = gtk_settings_get_default (); |
166 | g_object_get (object: settings, |
167 | first_property_name: "gtk-theme-name" , &theme, |
168 | "gtk-application-prefer-dark-theme" , &prefer_dark, |
169 | NULL); |
170 | |
171 | if ((strcmp (s1: theme, s2: "Adwaita" ) == 0 && prefer_dark) || strcmp (s1: theme, s2: "HighContrastInverse" ) == 0) |
172 | dark = TRUE; |
173 | else |
174 | dark = FALSE; |
175 | |
176 | g_free (mem: theme); |
177 | |
178 | return dark; |
179 | } |
180 | |
181 | static void |
182 | canvas_item_init (CanvasItem *item) |
183 | { |
184 | char *text; |
185 | char *id; |
186 | GdkRGBA rgba; |
187 | GtkDropTarget *dest; |
188 | GtkGesture *gesture; |
189 | GType types[2] = { GDK_TYPE_RGBA, G_TYPE_STRING }; |
190 | |
191 | n_items++; |
192 | |
193 | text = g_strdup_printf (format: "Item %d" , n_items); |
194 | item->label = gtk_label_new (str: text); |
195 | gtk_widget_add_css_class (widget: item->label, css_class: "canvasitem" ); |
196 | g_free (mem: text); |
197 | |
198 | item->fixed = gtk_fixed_new (); |
199 | gtk_widget_set_parent (widget: item->fixed, GTK_WIDGET (item)); |
200 | gtk_fixed_put (GTK_FIXED (item->fixed), widget: item->label, x: 0, y: 0); |
201 | |
202 | gtk_widget_add_css_class (widget: item->label, css_class: "frame" ); |
203 | |
204 | id = g_strdup_printf (format: "item%d" , n_items); |
205 | gtk_widget_set_name (widget: item->label, name: id); |
206 | g_free (mem: id); |
207 | |
208 | if (theme_is_dark ()) |
209 | gdk_rgba_parse (rgba: &rgba, spec: "blue" ); |
210 | else |
211 | gdk_rgba_parse (rgba: &rgba, spec: "yellow" ); |
212 | |
213 | set_color (item, color: &rgba); |
214 | |
215 | item->angle = 0; |
216 | |
217 | dest = gtk_drop_target_new (G_TYPE_INVALID, actions: GDK_ACTION_COPY); |
218 | gtk_drop_target_set_gtypes (self: dest, types, G_N_ELEMENTS (types)); |
219 | g_signal_connect (dest, "drop" , G_CALLBACK (item_drag_drop), NULL); |
220 | gtk_widget_add_controller (GTK_WIDGET (item->label), GTK_EVENT_CONTROLLER (dest)); |
221 | |
222 | gesture = gtk_gesture_rotate_new (); |
223 | g_signal_connect (gesture, "angle-changed" , G_CALLBACK (angle_changed), NULL); |
224 | g_signal_connect (gesture, "end" , G_CALLBACK (rotate_done), NULL); |
225 | gtk_widget_add_controller (GTK_WIDGET (item), GTK_EVENT_CONTROLLER (gesture)); |
226 | |
227 | gesture = gtk_gesture_click_new (); |
228 | g_signal_connect (gesture, "released" , G_CALLBACK (click_done), NULL); |
229 | gtk_widget_add_controller (GTK_WIDGET (item), GTK_EVENT_CONTROLLER (gesture)); |
230 | } |
231 | |
232 | static void |
233 | canvas_item_dispose (GObject *object) |
234 | { |
235 | CanvasItem *item = CANVAS_ITEM (ptr: object); |
236 | |
237 | g_clear_pointer (&item->fixed, gtk_widget_unparent); |
238 | g_clear_pointer (&item->editor, gtk_widget_unparent); |
239 | |
240 | G_OBJECT_CLASS (canvas_item_parent_class)->dispose (object); |
241 | } |
242 | |
243 | static void |
244 | canvas_item_map (GtkWidget *widget) |
245 | { |
246 | CanvasItem *item = CANVAS_ITEM (ptr: widget); |
247 | |
248 | GTK_WIDGET_CLASS (canvas_item_parent_class)->map (widget); |
249 | |
250 | apply_transform (item); |
251 | } |
252 | |
253 | static void |
254 | canvas_item_class_init (CanvasItemClass *class) |
255 | { |
256 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
257 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); |
258 | |
259 | object_class->dispose = canvas_item_dispose; |
260 | |
261 | widget_class->map = canvas_item_map; |
262 | |
263 | gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); |
264 | gtk_widget_class_set_css_name (widget_class, name: "item" ); |
265 | } |
266 | |
267 | static GtkWidget * |
268 | canvas_item_new (void) |
269 | { |
270 | CanvasItem *item = g_object_new (object_type: canvas_item_get_type (), NULL); |
271 | |
272 | return GTK_WIDGET (item); |
273 | } |
274 | |
275 | static GdkPaintable * |
276 | canvas_item_get_drag_icon (CanvasItem *item) |
277 | { |
278 | return gtk_widget_paintable_new (widget: item->fixed); |
279 | } |
280 | |
281 | static gboolean |
282 | canvas_item_is_editing (CanvasItem *item) |
283 | { |
284 | return item->editor != NULL; |
285 | } |
286 | |
287 | static void |
288 | scale_changed (GtkRange *range, |
289 | CanvasItem *item) |
290 | { |
291 | item->angle = gtk_range_get_value (range); |
292 | apply_transform (item); |
293 | } |
294 | |
295 | static void |
296 | text_changed (GtkEditable *editable, |
297 | GParamSpec *pspec, |
298 | CanvasItem *item) |
299 | { |
300 | gtk_label_set_text (GTK_LABEL (item->label), str: gtk_editable_get_text (editable)); |
301 | apply_transform (item); |
302 | } |
303 | |
304 | static void |
305 | canvas_item_stop_editing (CanvasItem *item) |
306 | { |
307 | GtkWidget *scale; |
308 | |
309 | if (!item->editor) |
310 | return; |
311 | |
312 | scale = gtk_widget_get_last_child (widget: item->editor); |
313 | g_signal_handlers_disconnect_by_func (scale, scale_changed, item); |
314 | |
315 | gtk_fixed_remove (GTK_FIXED (gtk_widget_get_parent (item->editor)), widget: item->editor); |
316 | item->editor = NULL; |
317 | } |
318 | |
319 | static void |
320 | canvas_item_start_editing (CanvasItem *item) |
321 | { |
322 | GtkWidget *canvas = gtk_widget_get_parent (GTK_WIDGET (item)); |
323 | GtkWidget *entry; |
324 | GtkWidget *scale; |
325 | double x, y; |
326 | |
327 | if (item->editor) |
328 | return; |
329 | |
330 | item->editor = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 12); |
331 | |
332 | entry = gtk_entry_new (); |
333 | |
334 | gtk_editable_set_text (GTK_EDITABLE (entry), |
335 | text: gtk_label_get_text (GTK_LABEL (item->label))); |
336 | |
337 | gtk_editable_set_width_chars (GTK_EDITABLE (entry), n_chars: 12); |
338 | g_signal_connect (entry, "notify::text" , G_CALLBACK (text_changed), item); |
339 | g_signal_connect_swapped (entry, "activate" , G_CALLBACK (canvas_item_stop_editing), item); |
340 | |
341 | gtk_box_append (GTK_BOX (item->editor), child: entry); |
342 | |
343 | scale = gtk_scale_new_with_range (orientation: GTK_ORIENTATION_HORIZONTAL, min: 0, max: 360, step: 1); |
344 | gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE); |
345 | gtk_range_set_value (GTK_RANGE (scale), value: fmod (x: item->angle, y: 360)); |
346 | |
347 | g_signal_connect (scale, "value-changed" , G_CALLBACK (scale_changed), item); |
348 | |
349 | gtk_box_append (GTK_BOX (item->editor), child: scale); |
350 | |
351 | gtk_widget_translate_coordinates (GTK_WIDGET (item), dest_widget: canvas, src_x: 0, src_y: 0, dest_x: &x, dest_y: &y); |
352 | gtk_fixed_put (GTK_FIXED (canvas), widget: item->editor, x, y: y + 2 * item->r); |
353 | gtk_widget_grab_focus (widget: entry); |
354 | |
355 | } |
356 | |
357 | static GdkContentProvider * |
358 | prepare (GtkDragSource *source, |
359 | double x, |
360 | double y) |
361 | { |
362 | GtkWidget *canvas; |
363 | GtkWidget *item; |
364 | |
365 | canvas = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (source)); |
366 | item = gtk_widget_pick (widget: canvas, x, y, flags: GTK_PICK_DEFAULT); |
367 | |
368 | item = gtk_widget_get_ancestor (widget: item, widget_type: canvas_item_get_type ()); |
369 | if (!item) |
370 | return NULL; |
371 | |
372 | g_object_set_data (G_OBJECT (canvas), key: "dragged-item" , data: item); |
373 | |
374 | return gdk_content_provider_new_typed (GTK_TYPE_WIDGET, item); |
375 | } |
376 | |
377 | static void |
378 | drag_begin (GtkDragSource *source, |
379 | GdkDrag *drag) |
380 | { |
381 | GtkWidget *canvas; |
382 | CanvasItem *item; |
383 | GdkPaintable *paintable; |
384 | |
385 | canvas = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (source)); |
386 | item = CANVAS_ITEM (ptr: g_object_get_data (G_OBJECT (canvas), key: "dragged-item" )); |
387 | |
388 | paintable = canvas_item_get_drag_icon (item); |
389 | gtk_drag_source_set_icon (source, paintable, hot_x: item->r, hot_y: item->r); |
390 | g_object_unref (object: paintable); |
391 | |
392 | gtk_widget_set_opacity (GTK_WIDGET (item), opacity: 0.3); |
393 | } |
394 | |
395 | static void |
396 | drag_end (GtkDragSource *source, |
397 | GdkDrag *drag) |
398 | { |
399 | GtkWidget *canvas; |
400 | GtkWidget *item; |
401 | |
402 | canvas = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (source)); |
403 | item = g_object_get_data (G_OBJECT (canvas), key: "dragged-item" ); |
404 | g_object_set_data (G_OBJECT (canvas), key: "dragged-item" , NULL); |
405 | |
406 | gtk_widget_set_opacity (widget: item, opacity: 1.0); |
407 | } |
408 | |
409 | static gboolean |
410 | drag_cancel (GtkDragSource *source, |
411 | GdkDrag *drag, |
412 | GdkDragCancelReason reason) |
413 | { |
414 | return FALSE; |
415 | } |
416 | |
417 | static gboolean |
418 | drag_drop (GtkDropTarget *target, |
419 | const GValue *value, |
420 | double x, |
421 | double y) |
422 | { |
423 | CanvasItem *item; |
424 | GtkWidget *canvas; |
425 | GtkWidget *last_child; |
426 | |
427 | item = g_value_get_object (value); |
428 | |
429 | canvas = gtk_widget_get_parent (GTK_WIDGET (item)); |
430 | last_child = gtk_widget_get_last_child (widget: canvas); |
431 | if (GTK_WIDGET (item) != last_child) |
432 | gtk_widget_insert_after (GTK_WIDGET (item), parent: canvas, previous_sibling: last_child); |
433 | |
434 | gtk_fixed_move (GTK_FIXED (canvas), GTK_WIDGET (item), x: x - item->r, y: y - item->r); |
435 | |
436 | return TRUE; |
437 | } |
438 | |
439 | static void |
440 | new_item_cb (GtkWidget *button, gpointer data) |
441 | { |
442 | GtkWidget *canvas = data; |
443 | GtkWidget *popover; |
444 | GtkWidget *item; |
445 | GdkRectangle rect; |
446 | |
447 | popover = gtk_widget_get_ancestor (widget: button, GTK_TYPE_POPOVER); |
448 | gtk_popover_get_pointing_to (GTK_POPOVER (popover), rect: &rect); |
449 | |
450 | item = canvas_item_new (); |
451 | gtk_fixed_put (GTK_FIXED (canvas), widget: item, x: rect.x, y: rect.y); |
452 | apply_transform (item: CANVAS_ITEM (ptr: item)); |
453 | |
454 | gtk_popover_popdown (GTK_POPOVER (gtk_widget_get_ancestor (button, GTK_TYPE_POPOVER))); |
455 | } |
456 | |
457 | static void |
458 | edit_cb (GtkWidget *button, GtkWidget *child) |
459 | { |
460 | CanvasItem *item = CANVAS_ITEM (ptr: child); |
461 | |
462 | if (button) |
463 | gtk_popover_popdown (GTK_POPOVER (gtk_widget_get_ancestor (button, GTK_TYPE_POPOVER))); |
464 | |
465 | if (!canvas_item_is_editing (item)) |
466 | canvas_item_start_editing (item); |
467 | } |
468 | |
469 | static void |
470 | delete_cb (GtkWidget *button, GtkWidget *child) |
471 | { |
472 | GtkWidget *canvas = gtk_widget_get_parent (widget: child); |
473 | |
474 | gtk_fixed_remove (GTK_FIXED (canvas), widget: child); |
475 | |
476 | gtk_popover_popdown (GTK_POPOVER (gtk_widget_get_ancestor (button, GTK_TYPE_POPOVER))); |
477 | } |
478 | |
479 | static void |
480 | pressed_cb (GtkGesture *gesture, |
481 | int n_press, |
482 | double x, |
483 | double y, |
484 | gpointer data) |
485 | { |
486 | GtkWidget *widget; |
487 | GtkWidget *child; |
488 | |
489 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); |
490 | child = gtk_widget_pick (widget, x, y, flags: GTK_PICK_DEFAULT); |
491 | child = gtk_widget_get_ancestor (widget: child, widget_type: canvas_item_get_type ()); |
492 | |
493 | if (gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)) == GDK_BUTTON_SECONDARY) |
494 | { |
495 | GtkWidget *; |
496 | GtkWidget *box; |
497 | GtkWidget *item; |
498 | |
499 | menu = gtk_popover_new (); |
500 | gtk_widget_set_parent (widget: menu, parent: widget); |
501 | gtk_popover_set_has_arrow (GTK_POPOVER (menu), FALSE); |
502 | gtk_popover_set_pointing_to (GTK_POPOVER (menu), rect: &(GdkRectangle){ x, y, 1, 1}); |
503 | box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
504 | gtk_popover_set_child (GTK_POPOVER (menu), child: box); |
505 | |
506 | item = gtk_button_new_with_label (label: "New" ); |
507 | gtk_button_set_has_frame (GTK_BUTTON (item), FALSE); |
508 | g_signal_connect (item, "clicked" , G_CALLBACK (new_item_cb), widget); |
509 | gtk_box_append (GTK_BOX (box), child: item); |
510 | |
511 | item = gtk_separator_new (orientation: GTK_ORIENTATION_HORIZONTAL); |
512 | gtk_box_append (GTK_BOX (box), child: item); |
513 | |
514 | item = gtk_button_new_with_label (label: "Edit" ); |
515 | gtk_button_set_has_frame (GTK_BUTTON (item), FALSE); |
516 | gtk_widget_set_sensitive (widget: item, sensitive: child != NULL && child != widget); |
517 | g_signal_connect (item, "clicked" , G_CALLBACK (edit_cb), child); |
518 | gtk_box_append (GTK_BOX (box), child: item); |
519 | |
520 | item = gtk_separator_new (orientation: GTK_ORIENTATION_HORIZONTAL); |
521 | gtk_box_append (GTK_BOX (box), child: item); |
522 | |
523 | item = gtk_button_new_with_label (label: "Delete" ); |
524 | gtk_button_set_has_frame (GTK_BUTTON (item), FALSE); |
525 | gtk_widget_set_sensitive (widget: item, sensitive: child != NULL && child != widget); |
526 | g_signal_connect (item, "clicked" , G_CALLBACK (delete_cb), child); |
527 | gtk_box_append (GTK_BOX (box), child: item); |
528 | |
529 | gtk_popover_popup (GTK_POPOVER (menu)); |
530 | } |
531 | } |
532 | |
533 | static void |
534 | released_cb (GtkGesture *gesture, |
535 | int n_press, |
536 | double x, |
537 | double y, |
538 | gpointer data) |
539 | { |
540 | GtkWidget *widget; |
541 | GtkWidget *child; |
542 | CanvasItem *item; |
543 | |
544 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); |
545 | child = gtk_widget_pick (widget, x, y, flags: 0); |
546 | item = (CanvasItem *)gtk_widget_get_ancestor (widget: child, widget_type: canvas_item_get_type ()); |
547 | if (!item) |
548 | return; |
549 | |
550 | if (gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)) == GDK_BUTTON_PRIMARY) |
551 | { |
552 | if (canvas_item_is_editing (item)) |
553 | canvas_item_stop_editing (item); |
554 | else |
555 | canvas_item_start_editing (item); |
556 | } |
557 | } |
558 | |
559 | static GtkWidget * |
560 | canvas_new (void) |
561 | { |
562 | GtkWidget *canvas; |
563 | GtkDragSource *source; |
564 | GtkDropTarget *dest; |
565 | GtkGesture *gesture; |
566 | |
567 | canvas = gtk_fixed_new (); |
568 | gtk_widget_set_hexpand (widget: canvas, TRUE); |
569 | gtk_widget_set_vexpand (widget: canvas, TRUE); |
570 | |
571 | source = gtk_drag_source_new (); |
572 | gtk_drag_source_set_actions (source, actions: GDK_ACTION_MOVE); |
573 | g_signal_connect (source, "prepare" , G_CALLBACK (prepare), NULL); |
574 | g_signal_connect (source, "drag-begin" , G_CALLBACK (drag_begin), NULL); |
575 | g_signal_connect (source, "drag-end" , G_CALLBACK (drag_end), NULL); |
576 | g_signal_connect (source, "drag-cancel" , G_CALLBACK (drag_cancel), NULL); |
577 | gtk_widget_add_controller (widget: canvas, GTK_EVENT_CONTROLLER (source)); |
578 | |
579 | dest = gtk_drop_target_new (GTK_TYPE_WIDGET, actions: GDK_ACTION_MOVE); |
580 | g_signal_connect (dest, "drop" , G_CALLBACK (drag_drop), NULL); |
581 | gtk_widget_add_controller (widget: canvas, GTK_EVENT_CONTROLLER (dest)); |
582 | |
583 | gesture = gtk_gesture_click_new (); |
584 | gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), button: 0); |
585 | g_signal_connect (gesture, "pressed" , G_CALLBACK (pressed_cb), NULL); |
586 | g_signal_connect (gesture, "released" , G_CALLBACK (released_cb), NULL); |
587 | gtk_widget_add_controller (widget: canvas, GTK_EVENT_CONTROLLER (gesture)); |
588 | |
589 | return canvas; |
590 | } |
591 | |
592 | static GdkContentProvider * |
593 | css_drag_prepare (GtkDragSource *source, |
594 | double x, |
595 | double y, |
596 | GtkWidget *button) |
597 | { |
598 | const char *class; |
599 | GdkPaintable *paintable; |
600 | |
601 | class = (const char *)g_object_get_data (G_OBJECT (button), key: "css-class" ); |
602 | |
603 | paintable = gtk_widget_paintable_new (widget: button); |
604 | gtk_drag_source_set_icon (source, paintable, hot_x: 0, hot_y: 0); |
605 | g_object_unref (object: paintable); |
606 | |
607 | return gdk_content_provider_new_typed (G_TYPE_STRING, class); |
608 | } |
609 | |
610 | static GtkWidget * |
611 | css_button_new (const char *class) |
612 | { |
613 | GtkWidget *button; |
614 | GtkDragSource *source; |
615 | |
616 | button = gtk_image_new (); |
617 | gtk_widget_set_size_request (widget: button, width: 48, height: 32); |
618 | gtk_widget_add_css_class (widget: button, css_class: class); |
619 | g_object_set_data (G_OBJECT (button), key: "css-class" , data: (gpointer)class); |
620 | |
621 | source = gtk_drag_source_new (); |
622 | g_signal_connect (source, "prepare" , G_CALLBACK (css_drag_prepare), button); |
623 | gtk_widget_add_controller (widget: button, GTK_EVENT_CONTROLLER (source)); |
624 | |
625 | return button; |
626 | } |
627 | |
628 | typedef struct |
629 | { |
630 | GtkWidget parent_instance; |
631 | GdkRGBA color; |
632 | } ColorSwatch; |
633 | |
634 | typedef struct |
635 | { |
636 | GtkWidgetClass parent_class; |
637 | } ColorSwatchClass; |
638 | |
639 | G_DEFINE_TYPE (ColorSwatch, color_swatch, GTK_TYPE_WIDGET) |
640 | |
641 | static GdkContentProvider * |
642 | color_swatch_drag_prepare (GtkDragSource *source, |
643 | double x, |
644 | double y, |
645 | ColorSwatch *swatch) |
646 | { |
647 | return gdk_content_provider_new_typed (GDK_TYPE_RGBA, &swatch->color); |
648 | } |
649 | |
650 | static void |
651 | color_swatch_init (ColorSwatch *swatch) |
652 | { |
653 | GtkDragSource *source = gtk_drag_source_new (); |
654 | g_signal_connect (source, "prepare" , G_CALLBACK (color_swatch_drag_prepare), swatch); |
655 | gtk_widget_add_controller (GTK_WIDGET (swatch), GTK_EVENT_CONTROLLER (source)); |
656 | } |
657 | |
658 | static void |
659 | color_swatch_snapshot (GtkWidget *widget, |
660 | GtkSnapshot *snapshot) |
661 | { |
662 | ColorSwatch *swatch = (ColorSwatch *)widget; |
663 | float w = gtk_widget_get_width (widget); |
664 | float h = gtk_widget_get_height (widget); |
665 | |
666 | gtk_snapshot_append_color (snapshot, color: &swatch->color, |
667 | bounds: &GRAPHENE_RECT_INIT(0, 0, w, h)); |
668 | } |
669 | |
670 | void |
671 | color_swatch_measure (GtkWidget *widget, |
672 | GtkOrientation orientation, |
673 | int for_size, |
674 | int *minimum_size, |
675 | int *natural_size, |
676 | int *minimum_baseline, |
677 | int *natural_baseline) |
678 | { |
679 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
680 | *minimum_size = *natural_size = 48; |
681 | else |
682 | *minimum_size = *natural_size = 32; |
683 | } |
684 | |
685 | static void |
686 | color_swatch_class_init (ColorSwatchClass *class) |
687 | { |
688 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); |
689 | |
690 | widget_class->snapshot = color_swatch_snapshot; |
691 | widget_class->measure = color_swatch_measure; |
692 | gtk_widget_class_set_css_name (widget_class, name: "colorswatch" ); |
693 | } |
694 | |
695 | static GtkWidget * |
696 | color_swatch_new (const char *color) |
697 | { |
698 | ColorSwatch *swatch = g_object_new (object_type: color_swatch_get_type (), NULL); |
699 | |
700 | gdk_rgba_parse (rgba: &swatch->color, spec: color); |
701 | |
702 | return GTK_WIDGET (swatch); |
703 | } |
704 | |
705 | static GtkWidget *window = NULL; |
706 | |
707 | GtkWidget * |
708 | do_dnd (GtkWidget *do_widget) |
709 | { |
710 | if (!window) |
711 | { |
712 | GtkWidget *button; |
713 | GtkWidget *sw; |
714 | GtkWidget *canvas; |
715 | GtkWidget *box, *box2, *box3; |
716 | const char *colors[] = { |
717 | "red" , "green" , "blue" , "magenta" , "orange" , "gray" , "black" , "yellow" , |
718 | "white" , "gray" , "brown" , "pink" , "cyan" , "bisque" , "gold" , "maroon" , |
719 | "navy" , "orchid" , "olive" , "peru" , "salmon" , "silver" , "wheat" , |
720 | NULL |
721 | }; |
722 | int i; |
723 | int x, y; |
724 | GtkCssProvider *provider; |
725 | |
726 | button = gtk_color_button_new (); |
727 | g_object_unref (g_object_ref_sink (button)); |
728 | |
729 | provider = gtk_css_provider_new (); |
730 | gtk_css_provider_load_from_resource (css_provider: provider, resource_path: "/dnd/dnd.css" ); |
731 | gtk_style_context_add_provider_for_display (display: gdk_display_get_default (), |
732 | GTK_STYLE_PROVIDER (provider), |
733 | priority: 800); |
734 | g_object_unref (object: provider); |
735 | |
736 | window = gtk_window_new (); |
737 | gtk_window_set_display (GTK_WINDOW (window), |
738 | display: gtk_widget_get_display (widget: do_widget)); |
739 | gtk_window_set_title (GTK_WINDOW (window), title: "Drag-and-Drop" ); |
740 | gtk_window_set_default_size (GTK_WINDOW (window), width: 640, height: 480); |
741 | g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer *)&window); |
742 | |
743 | box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
744 | gtk_window_set_child (GTK_WINDOW (window), child: box); |
745 | |
746 | box2 = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0); |
747 | gtk_box_append (GTK_BOX (box), child: box2); |
748 | |
749 | canvas = canvas_new (); |
750 | gtk_box_append (GTK_BOX (box2), child: canvas); |
751 | |
752 | n_items = 0; |
753 | |
754 | x = y = 40; |
755 | for (i = 0; i < 4; i++) |
756 | { |
757 | GtkWidget *item; |
758 | |
759 | item = canvas_item_new (); |
760 | gtk_fixed_put (GTK_FIXED (canvas), widget: item, x, y); |
761 | apply_transform (item: CANVAS_ITEM (ptr: item)); |
762 | |
763 | x += 150; |
764 | y += 100; |
765 | } |
766 | |
767 | gtk_box_append (GTK_BOX (box), child: gtk_separator_new (orientation: GTK_ORIENTATION_HORIZONTAL)); |
768 | |
769 | sw = gtk_scrolled_window_new (); |
770 | gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), |
771 | hscrollbar_policy: GTK_POLICY_AUTOMATIC, |
772 | vscrollbar_policy: GTK_POLICY_NEVER); |
773 | gtk_box_append (GTK_BOX (box), child: sw); |
774 | |
775 | box3 = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0); |
776 | gtk_widget_add_css_class (widget: box3, css_class: "linked" ); |
777 | gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: box3); |
778 | |
779 | for (i = 0; colors[i]; i++) |
780 | gtk_box_append (GTK_BOX (box3), child: color_swatch_new (color: colors[i])); |
781 | |
782 | gtk_box_append (GTK_BOX (box3), child: css_button_new (class: "rainbow1" )); |
783 | gtk_box_append (GTK_BOX (box3), child: css_button_new (class: "rainbow2" )); |
784 | gtk_box_append (GTK_BOX (box3), child: css_button_new (class: "rainbow3" )); |
785 | } |
786 | |
787 | if (!gtk_widget_get_visible (widget: window)) |
788 | gtk_widget_show (widget: window); |
789 | else |
790 | gtk_window_destroy (GTK_WINDOW (window)); |
791 | |
792 | return window; |
793 | } |
794 | |