Root/
Source at commit d4c4031b9afeb0a1509767bcbfec8a5248cb9866 created 13 years 9 months ago. By werner, Added support for reordering frames in the GUI. | |
---|---|
1 | /* |
2 | * gui_frame_drag.c - GUI, dragging of frame items |
3 | * |
4 | * Written 2010 by Werner Almesberger |
5 | * Copyright 2010 by Werner Almesberger |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License as published by |
9 | * the Free Software Foundation; either version 2 of the License, or |
10 | * (at your option) any later version. |
11 | */ |
12 | |
13 | |
14 | #include <assert.h> |
15 | #include <gtk/gtk.h> |
16 | |
17 | #include "util.h" |
18 | #include "obj.h" |
19 | #include "gui_util.h" |
20 | #include "gui.h" |
21 | #include "gui_canvas.h" |
22 | #include "gui_frame_drag.h" |
23 | |
24 | #if 0 |
25 | #include "icons/frame.xpm" |
26 | #endif |
27 | |
28 | |
29 | enum { |
30 | target_id_var, |
31 | target_id_value, |
32 | target_id_frame, |
33 | target_id_canvas, |
34 | }; |
35 | |
36 | |
37 | static GtkTargetEntry target_var = { |
38 | .target = "var", |
39 | .flags = GTK_TARGET_SAME_APP, |
40 | .info = target_id_var, |
41 | }; |
42 | |
43 | static GtkTargetEntry target_value = { |
44 | .target = "value", |
45 | .flags = GTK_TARGET_SAME_APP, |
46 | .info = target_id_value, |
47 | }; |
48 | |
49 | static GtkTargetEntry target_frame = { |
50 | .target = "frame", |
51 | .flags = GTK_TARGET_SAME_APP, |
52 | .info = target_id_frame, |
53 | }; |
54 | |
55 | |
56 | /* ----- dragging status --------------------------------------------------- */ |
57 | |
58 | |
59 | /* |
60 | * Pointer to whatever it is we're dragging. NULL if not dragging. |
61 | */ |
62 | |
63 | static void *dragging; |
64 | |
65 | |
66 | int is_dragging(void *this) |
67 | { |
68 | return this == dragging; |
69 | } |
70 | |
71 | |
72 | int is_dragging_anything(void) |
73 | { |
74 | return !!dragging; |
75 | } |
76 | |
77 | |
78 | /* ----- helper functions for indexed list --------------------------------- */ |
79 | |
80 | |
81 | #define NDX(first, item) \ |
82 | ({ typeof(first) NDX_walk; \ |
83 | int NDX_n = 0; \ |
84 | for (NDX_walk = (first); NDX_walk != (item); \ |
85 | NDX_walk = NDX_walk->next) \ |
86 | NDX_n++; \ |
87 | NDX_n; }) |
88 | |
89 | #define NTH(first, n) \ |
90 | ({ typeof(first) *NTH_walk; \ |
91 | int NTH_n = (n); \ |
92 | for (NTH_walk = &(first); NTH_n; NTH_n--) \ |
93 | NTH_walk = &(*NTH_walk)->next; \ |
94 | NTH_walk; }) |
95 | |
96 | |
97 | /* ----- generic helper functions. maybe move to gui_util later ------------ */ |
98 | |
99 | |
100 | static void get_cell_coords(GtkWidget *widget, guint res[4]) |
101 | { |
102 | GtkWidget *tab; |
103 | |
104 | tab = gtk_widget_get_ancestor(widget, GTK_TYPE_TABLE); |
105 | gtk_container_child_get(GTK_CONTAINER(tab), widget, |
106 | "left-attach", res, |
107 | "right-attach", res+1, |
108 | "top-attach", res+2, |
109 | "bottom-attach", res+3, NULL); |
110 | } |
111 | |
112 | |
113 | static void swap_table_cells(GtkWidget *a, GtkWidget *b) |
114 | { |
115 | GtkWidget *tab_a, *tab_b; |
116 | guint pos_a[4], pos_b[4]; |
117 | |
118 | tab_a = gtk_widget_get_ancestor(a, GTK_TYPE_TABLE); |
119 | tab_b = gtk_widget_get_ancestor(b, GTK_TYPE_TABLE); |
120 | get_cell_coords(a, pos_a); |
121 | get_cell_coords(b, pos_b); |
122 | g_object_ref(a); |
123 | g_object_ref(b); |
124 | gtk_container_remove(GTK_CONTAINER(tab_a), a); |
125 | gtk_container_remove(GTK_CONTAINER(tab_b), b); |
126 | gtk_table_attach_defaults(GTK_TABLE(tab_a), b, |
127 | pos_a[0], pos_a[1], pos_a[2], pos_a[3]); |
128 | gtk_table_attach_defaults(GTK_TABLE(tab_b), a, |
129 | pos_b[0], pos_b[1], pos_b[2], pos_b[3]); |
130 | g_object_unref(a); |
131 | g_object_unref(b); |
132 | } |
133 | |
134 | |
135 | static GtkWidget *pick_table_cell(GtkWidget *table, int x, int y) |
136 | { |
137 | GList *children, *walk; |
138 | GtkWidget *child; |
139 | guint pos[4]; |
140 | |
141 | children = gtk_container_get_children(GTK_CONTAINER(table)); |
142 | for (walk = children; walk; walk = g_list_next(walk)) { |
143 | child = g_list_nth_data(walk, 0); |
144 | assert(child); |
145 | get_cell_coords(child, pos); |
146 | if (pos[0] == x && pos[2] == y) |
147 | break; |
148 | } |
149 | g_list_free(children); |
150 | return walk ? child : NULL; |
151 | } |
152 | |
153 | |
154 | static void swap_table_cells_by_coord(GtkWidget *table_a, |
155 | int a_col, int a_row, GtkWidget *table_b, int b_col, int b_row) |
156 | { |
157 | GtkWidget *a, *b; |
158 | |
159 | a = pick_table_cell(table_a, a_col, a_row); |
160 | b = pick_table_cell(table_b, b_col, b_row); |
161 | if (a) { |
162 | g_object_ref(a); |
163 | gtk_container_remove(GTK_CONTAINER(table_a), a); |
164 | } |
165 | if (b) { |
166 | g_object_ref(b); |
167 | gtk_container_remove(GTK_CONTAINER(table_b), b); |
168 | } |
169 | if (a) |
170 | gtk_table_attach_defaults(GTK_TABLE(table_b), a, |
171 | b_col, b_col+1, b_row, b_row+1); |
172 | if (b) |
173 | gtk_table_attach_defaults(GTK_TABLE(table_a), b, |
174 | a_col, a_col+1, a_row, a_row+1); |
175 | if (a) |
176 | g_object_unref(a); |
177 | if (b) |
178 | g_object_unref(b); |
179 | } |
180 | |
181 | |
182 | static void swap_table_rows(GtkWidget *table, int a, int b) |
183 | { |
184 | guint cols; |
185 | int i; |
186 | |
187 | g_object_get(table, "n-columns", &cols, NULL); |
188 | for (i = 0; i != cols; i++) |
189 | swap_table_cells_by_coord(table, i, a, table, i, b); |
190 | } |
191 | |
192 | |
193 | /* ----- swap table items -------------------------------------------------- */ |
194 | |
195 | |
196 | static void swap_vars(struct table *table, int a, int b) |
197 | { |
198 | struct var **var_a, **var_b; |
199 | |
200 | var_a = NTH(table->vars, a); |
201 | var_b = NTH(table->vars, b); |
202 | |
203 | swap_table_cells(box_of_label((*var_a)->widget), |
204 | box_of_label((*var_b)->widget)); |
205 | |
206 | swap(*var_a, *var_b); |
207 | swap((*var_a)->next, (*var_b)->next); |
208 | } |
209 | |
210 | |
211 | static void swap_values(struct row *row, int a, int b) |
212 | { |
213 | struct value **value_a, **value_b; |
214 | |
215 | value_a = NTH(row->values, a); |
216 | value_b = NTH(row->values, b); |
217 | |
218 | swap_table_cells(box_of_label((*value_a)->widget), |
219 | box_of_label((*value_b)->widget)); |
220 | |
221 | swap(*value_a, *value_b); |
222 | swap((*value_a)->next, (*value_b)->next); |
223 | } |
224 | |
225 | |
226 | |
227 | static void swap_cols(struct table *table, int a, int b) |
228 | { |
229 | struct row *row; |
230 | |
231 | swap_vars(table, a, b); |
232 | for (row = table->rows; row; row = row->next) |
233 | swap_values(row, a, b); |
234 | } |
235 | |
236 | |
237 | static void swap_rows(struct row **a, struct row **b) |
238 | { |
239 | struct value *value_a, *value_b; |
240 | |
241 | value_a = (*a)->values; |
242 | value_b = (*b)->values; |
243 | while (value_a) { |
244 | swap_table_cells(box_of_label(value_a->widget), |
245 | box_of_label(value_b->widget)); |
246 | value_a = value_a->next; |
247 | value_b = value_b->next; |
248 | } |
249 | swap(*a, *b); |
250 | swap((*a)->next, (*b)->next); |
251 | } |
252 | |
253 | |
254 | /* ----- swap frames ------------------------------------------------------- */ |
255 | |
256 | |
257 | static void swap_frames(GtkWidget *table, int a, int b) |
258 | { |
259 | struct frame **frame_a = NTH(frames, a); |
260 | struct frame **frame_b = NTH(frames, b); |
261 | |
262 | swap_table_rows(table, 2*a+1, 2*b+1); |
263 | swap_table_rows(table, 2*a+2, 2*b+2); |
264 | |
265 | swap(*frame_a, *frame_b); |
266 | swap((*frame_a)->next, (*frame_b)->next); |
267 | } |
268 | |
269 | |
270 | /* ----- common functions -------------------------------------------------- */ |
271 | |
272 | |
273 | /* |
274 | * according to |
275 | * http://www.pubbs.net/201004/gtk/22819-re-drag-and-drop-drag-motion-cursor-lockup-fixed-.html |
276 | * http://www.cryingwolf.org/articles/gtk-dnd.html |
277 | */ |
278 | |
279 | static int has_target(GtkWidget *widget, GdkDragContext *drag_context, |
280 | const char *name) |
281 | { |
282 | GdkAtom target; |
283 | |
284 | target = gtk_drag_dest_find_target(widget, drag_context, NULL); |
285 | |
286 | /* |
287 | * Force allocation so that we don't have to check for GDK_NONE. |
288 | */ |
289 | return target == gdk_atom_intern(name, FALSE); |
290 | } |
291 | |
292 | |
293 | static void drag_begin(GtkWidget *widget, |
294 | GtkTextDirection previous_direction, gpointer user_data) |
295 | { |
296 | GdkPixbuf *pixbuf; |
297 | |
298 | /* |
299 | * Suppress the icon. PixBufs can't be zero-sized, but nobody will |
300 | * notice a lone pixel either :-) |
301 | */ |
302 | pixbuf = |
303 | gdk_pixbuf_get_from_drawable(NULL, DA, NULL, 0, 0, 0, 0, 1, 1); |
304 | gtk_drag_source_set_icon_pixbuf(widget, pixbuf); |
305 | g_object_unref(pixbuf); |
306 | |
307 | dragging = user_data; |
308 | } |
309 | |
310 | |
311 | static void drag_end(GtkWidget *widget, GdkDragContext *drag_context, |
312 | gpointer user_data) |
313 | { |
314 | dragging = NULL; |
315 | } |
316 | |
317 | |
318 | static void setup_drag_common(GtkWidget *widget, void *user_arg) |
319 | { |
320 | g_signal_connect(G_OBJECT(widget), "drag-begin", |
321 | G_CALLBACK(drag_begin), user_arg); |
322 | g_signal_connect(G_OBJECT(widget), "drag-end", |
323 | G_CALLBACK(drag_end), user_arg); |
324 | } |
325 | |
326 | |
327 | /* ----- drag variables ---------------------------------------------------- */ |
328 | |
329 | |
330 | static gboolean drag_var_motion(GtkWidget *widget, |
331 | GdkDragContext *drag_context, gint x, gint y, guint time_, |
332 | gpointer user_data) |
333 | { |
334 | struct var *from = dragging; |
335 | struct var *to = user_data; |
336 | int from_n, to_n, i; |
337 | |
338 | if (!has_target(widget, drag_context, "var")) |
339 | return FALSE; |
340 | if (from == to || from->table != to->table) |
341 | return FALSE; |
342 | from_n = NDX(from->table->vars, from); |
343 | to_n = NDX(to->table->vars, to); |
344 | for (i = from_n < to_n ? from_n : to_n; |
345 | i != (from_n < to_n ? to_n : from_n); i++) |
346 | swap_cols(from->table, i, i+1); |
347 | return FALSE; |
348 | } |
349 | |
350 | |
351 | void setup_var_drag(struct var *var) |
352 | { |
353 | GtkWidget *box; |
354 | |
355 | box = box_of_label(var->widget); |
356 | gtk_drag_source_set(box, GDK_BUTTON1_MASK, |
357 | &target_var, 1, GDK_ACTION_PRIVATE); |
358 | gtk_drag_dest_set(box, GTK_DEST_DEFAULT_MOTION, |
359 | &target_var, 1, GDK_ACTION_PRIVATE); |
360 | setup_drag_common(box, var); |
361 | g_signal_connect(G_OBJECT(box), "drag-motion", |
362 | G_CALLBACK(drag_var_motion), var); |
363 | } |
364 | |
365 | |
366 | /* ----- drag values ------------------------------------------------------- */ |
367 | |
368 | |
369 | static gboolean drag_value_motion(GtkWidget *widget, |
370 | GdkDragContext *drag_context, gint x, gint y, guint time_, |
371 | gpointer user_data) |
372 | { |
373 | struct value *from = dragging; |
374 | struct value *to = user_data; |
375 | struct table *table; |
376 | struct row **row, *end; |
377 | int from_n, to_n, i; |
378 | |
379 | if (!has_target(widget, drag_context, "value")) |
380 | return FALSE; |
381 | table = from->row->table; |
382 | if (table != to->row->table) |
383 | return FALSE; |
384 | |
385 | /* columns */ |
386 | |
387 | from_n = NDX(from->row->values, from); |
388 | to_n = NDX(to->row->values, to); |
389 | for (i = from_n < to_n ? from_n : to_n; |
390 | i != (from_n < to_n ? to_n : from_n); i++) |
391 | swap_cols(table, i, i+1); |
392 | |
393 | /* rows */ |
394 | |
395 | if (from->row == to->row) |
396 | return FALSE; |
397 | row = &table->rows; |
398 | while (1) { |
399 | if (*row == from->row) { |
400 | end = to->row; |
401 | break; |
402 | } |
403 | if (*row == to->row) { |
404 | end = from->row; |
405 | break; |
406 | } |
407 | row = &(*row)->next; |
408 | } |
409 | while (1) { |
410 | swap_rows(row, &(*row)->next); |
411 | if (*row == end) |
412 | break; |
413 | row = &(*row)->next; |
414 | } |
415 | |
416 | return FALSE; |
417 | } |
418 | |
419 | |
420 | void setup_value_drag(struct value *value) |
421 | { |
422 | GtkWidget *box; |
423 | |
424 | box = box_of_label(value->widget); |
425 | gtk_drag_source_set(box, GDK_BUTTON1_MASK, |
426 | &target_value, 1, GDK_ACTION_PRIVATE); |
427 | gtk_drag_dest_set(box, GTK_DEST_DEFAULT_MOTION, |
428 | &target_value, 1, GDK_ACTION_PRIVATE); |
429 | setup_drag_common(box, value); |
430 | g_signal_connect(G_OBJECT(box), "drag-motion", |
431 | G_CALLBACK(drag_value_motion), value); |
432 | } |
433 | |
434 | |
435 | /* ----- frame to canvas helper functions ---------------------------------- */ |
436 | |
437 | |
438 | static int frame_on_canvas = 0; |
439 | |
440 | |
441 | static void leave_canvas(void) |
442 | { |
443 | if (frame_on_canvas) |
444 | canvas_frame_end(); |
445 | frame_on_canvas = 0; |
446 | } |
447 | |
448 | |
449 | /* ----- drag frame labels ------------------------------------------------- */ |
450 | |
451 | |
452 | #if 0 |
453 | |
454 | /* |
455 | * Setting our own icon looks nice but it slows things down to the point where |
456 | * cursor movements can lag noticeable and it adds yet another element to an |
457 | * already crowded cursor. |
458 | */ |
459 | |
460 | static void drag_frame_begin(GtkWidget *widget, |
461 | GtkTextDirection previous_direction, gpointer user_data) |
462 | { |
463 | GdkPixmap *pixmap; |
464 | GdkBitmap *mask; |
465 | GdkColormap *cmap; |
466 | |
467 | pixmap = gdk_pixmap_create_from_xpm_d(DA, &mask, NULL, xpm_frame); |
468 | cmap = gdk_drawable_get_colormap(root->window); |
469 | gtk_drag_source_set_icon(widget, cmap, pixmap, mask); |
470 | g_object_unref(pixmap); |
471 | g_object_unref(mask); |
472 | |
473 | dragging = user_data; |
474 | } |
475 | |
476 | #endif |
477 | |
478 | |
479 | static gboolean drag_frame_motion(GtkWidget *widget, |
480 | GdkDragContext *drag_context, gint x, gint y, guint time_, |
481 | gpointer user_data) |
482 | { |
483 | struct frame *from = dragging; |
484 | struct frame *to = user_data; |
485 | int from_n, to_n, i; |
486 | |
487 | if (!has_target(widget, drag_context, "frame")) |
488 | return FALSE; |
489 | assert(from != frames); |
490 | assert(to != frames); |
491 | from_n = NDX(frames, from); |
492 | to_n = NDX(frames, to); |
493 | for (i = from_n < to_n ? from_n : to_n; |
494 | i != (from_n < to_n ? to_n : from_n); i++) |
495 | swap_frames(gtk_widget_get_ancestor(widget, GTK_TYPE_TABLE), |
496 | i, i+1); |
497 | return FALSE; |
498 | } |
499 | |
500 | |
501 | static void drag_frame_end(GtkWidget *widget, GdkDragContext *drag_context, |
502 | gpointer user_data) |
503 | { |
504 | leave_canvas(); |
505 | drag_end(widget, drag_context, user_data); |
506 | } |
507 | |
508 | |
509 | void setup_frame_drag(struct frame *frame) |
510 | { |
511 | GtkWidget *box; |
512 | |
513 | box = box_of_label(frame->label); |
514 | gtk_drag_source_set(box, GDK_BUTTON1_MASK, |
515 | &target_frame, 1, GDK_ACTION_COPY | GDK_ACTION_MOVE); |
516 | gtk_drag_dest_set(box, GTK_DEST_DEFAULT_MOTION, |
517 | &target_frame, 1, GDK_ACTION_MOVE); |
518 | setup_drag_common(box, frame); |
519 | |
520 | /* override */ |
521 | #if 0 |
522 | g_signal_connect(G_OBJECT(box), "drag-begin", |
523 | G_CALLBACK(drag_frame_begin), frame); |
524 | #endif |
525 | g_signal_connect(G_OBJECT(box), "drag-end", |
526 | G_CALLBACK(drag_frame_end), frame); |
527 | |
528 | g_signal_connect(G_OBJECT(box), "drag-motion", |
529 | G_CALLBACK(drag_frame_motion), frame); |
530 | } |
531 | |
532 | |
533 | /* ----- drag to the canvas ------------------------------------------------ */ |
534 | |
535 | |
536 | static gboolean drag_canvas_motion(GtkWidget *widget, |
537 | GdkDragContext *drag_context, gint x, gint y, guint time_, |
538 | gpointer user_data) |
539 | { |
540 | if (!has_target(widget, drag_context, "frame")) |
541 | return FALSE; |
542 | if (!frame_on_canvas) { |
543 | frame_on_canvas = 1; |
544 | canvas_frame_begin(dragging); |
545 | } |
546 | if (canvas_frame_motion(dragging, x, y)) { |
547 | gdk_drag_status(drag_context, GDK_ACTION_COPY, time_); |
548 | return TRUE; |
549 | } else { |
550 | gdk_drag_status(drag_context, 0, time_); |
551 | return FALSE; |
552 | } |
553 | } |
554 | |
555 | |
556 | static void drag_canvas_leave(GtkWidget *widget, GdkDragContext *drag_context, |
557 | guint time_, gpointer user_data) |
558 | { |
559 | leave_canvas(); |
560 | } |
561 | |
562 | |
563 | static gboolean drag_canvas_drop(GtkWidget *widget, |
564 | GdkDragContext *drag_context, gint x, gint y, guint time_, |
565 | gpointer user_data) |
566 | { |
567 | if (!has_target(widget, drag_context, "frame")) |
568 | return FALSE; |
569 | if (!canvas_frame_drop(dragging, x, y)) |
570 | return FALSE; |
571 | gtk_drag_finish(drag_context, TRUE, FALSE, time_); |
572 | drag_end(widget, drag_context, user_data); |
573 | return TRUE; |
574 | } |
575 | |
576 | |
577 | void setup_canvas_drag(GtkWidget *canvas) |
578 | { |
579 | gtk_drag_dest_set(canvas, |
580 | GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, |
581 | &target_frame, 1, GDK_ACTION_COPY); |
582 | |
583 | g_signal_connect(G_OBJECT(canvas), "drag-motion", |
584 | G_CALLBACK(drag_canvas_motion), NULL); |
585 | g_signal_connect(G_OBJECT(canvas), "drag-leave", |
586 | G_CALLBACK(drag_canvas_leave), NULL); |
587 | g_signal_connect(G_OBJECT(canvas), "drag-drop", |
588 | G_CALLBACK(drag_canvas_drop), NULL); |
589 | } |
590 |
Branches:
master