Root/
Source at commit ceaa519ccb1b6d400ff57c692a13f183f5f6af61 created 13 years 3 months ago. By Xiangfu Liu, update to r5997 | |
---|---|
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 | #define FOR_UNORDERED(var, a, b) \ |
97 | for (var = (a) < (b) ? (a) : (b); var != ((a) < (b) ? (b) : (a)); \ |
98 | var++) |
99 | |
100 | |
101 | /* ----- generic helper functions. maybe move to gui_util later ------------ */ |
102 | |
103 | |
104 | static void get_cell_coords(GtkWidget *widget, guint res[4]) |
105 | { |
106 | GtkWidget *tab; |
107 | |
108 | tab = gtk_widget_get_ancestor(widget, GTK_TYPE_TABLE); |
109 | gtk_container_child_get(GTK_CONTAINER(tab), widget, |
110 | "left-attach", res, |
111 | "right-attach", res+1, |
112 | "top-attach", res+2, |
113 | "bottom-attach", res+3, NULL); |
114 | } |
115 | |
116 | |
117 | static void swap_table_cells(GtkWidget *a, GtkWidget *b) |
118 | { |
119 | GtkWidget *tab_a, *tab_b; |
120 | guint pos_a[4], pos_b[4]; |
121 | |
122 | tab_a = gtk_widget_get_ancestor(a, GTK_TYPE_TABLE); |
123 | tab_b = gtk_widget_get_ancestor(b, GTK_TYPE_TABLE); |
124 | get_cell_coords(a, pos_a); |
125 | get_cell_coords(b, pos_b); |
126 | g_object_ref(a); |
127 | g_object_ref(b); |
128 | gtk_container_remove(GTK_CONTAINER(tab_a), a); |
129 | gtk_container_remove(GTK_CONTAINER(tab_b), b); |
130 | gtk_table_attach_defaults(GTK_TABLE(tab_a), b, |
131 | pos_a[0], pos_a[1], pos_a[2], pos_a[3]); |
132 | gtk_table_attach_defaults(GTK_TABLE(tab_b), a, |
133 | pos_b[0], pos_b[1], pos_b[2], pos_b[3]); |
134 | g_object_unref(a); |
135 | g_object_unref(b); |
136 | } |
137 | |
138 | |
139 | static GtkWidget *pick_table_cell(GtkWidget *table, int x, int y) |
140 | { |
141 | GList *children, *walk; |
142 | GtkWidget *child; |
143 | guint pos[4]; |
144 | |
145 | children = gtk_container_get_children(GTK_CONTAINER(table)); |
146 | for (walk = children; walk; walk = g_list_next(walk)) { |
147 | child = g_list_nth_data(walk, 0); |
148 | assert(child); |
149 | get_cell_coords(child, pos); |
150 | if (pos[0] == x && pos[2] == y) |
151 | break; |
152 | } |
153 | g_list_free(children); |
154 | return walk ? child : NULL; |
155 | } |
156 | |
157 | |
158 | static void swap_table_cells_by_coord(GtkWidget *table_a, |
159 | int a_col, int a_row, GtkWidget *table_b, int b_col, int b_row) |
160 | { |
161 | GtkWidget *a, *b; |
162 | |
163 | a = pick_table_cell(table_a, a_col, a_row); |
164 | b = pick_table_cell(table_b, b_col, b_row); |
165 | if (a) { |
166 | g_object_ref(a); |
167 | gtk_container_remove(GTK_CONTAINER(table_a), a); |
168 | } |
169 | if (b) { |
170 | g_object_ref(b); |
171 | gtk_container_remove(GTK_CONTAINER(table_b), b); |
172 | } |
173 | if (a) |
174 | gtk_table_attach_defaults(GTK_TABLE(table_b), a, |
175 | b_col, b_col+1, b_row, b_row+1); |
176 | if (b) |
177 | gtk_table_attach_defaults(GTK_TABLE(table_a), b, |
178 | a_col, a_col+1, a_row, a_row+1); |
179 | if (a) |
180 | g_object_unref(a); |
181 | if (b) |
182 | g_object_unref(b); |
183 | } |
184 | |
185 | |
186 | static void swap_table_rows(GtkWidget *table, int a, int b) |
187 | { |
188 | guint cols; |
189 | int i; |
190 | |
191 | g_object_get(table, "n-columns", &cols, NULL); |
192 | for (i = 0; i != cols; i++) |
193 | swap_table_cells_by_coord(table, i, a, table, i, b); |
194 | } |
195 | |
196 | |
197 | /* ----- swap table items -------------------------------------------------- */ |
198 | |
199 | |
200 | static void swap_vars(struct table *table, int a, int b) |
201 | { |
202 | struct var **var_a, **var_b; |
203 | |
204 | var_a = NTH(table->vars, a); |
205 | var_b = NTH(table->vars, b); |
206 | |
207 | swap_table_cells(box_of_label((*var_a)->widget), |
208 | box_of_label((*var_b)->widget)); |
209 | |
210 | SWAP(*var_a, *var_b); |
211 | SWAP((*var_a)->next, (*var_b)->next); |
212 | } |
213 | |
214 | |
215 | static void swap_values(struct row *row, int a, int b) |
216 | { |
217 | struct value **value_a, **value_b; |
218 | |
219 | value_a = NTH(row->values, a); |
220 | value_b = NTH(row->values, b); |
221 | |
222 | swap_table_cells(box_of_label((*value_a)->widget), |
223 | box_of_label((*value_b)->widget)); |
224 | |
225 | SWAP(*value_a, *value_b); |
226 | SWAP((*value_a)->next, (*value_b)->next); |
227 | } |
228 | |
229 | |
230 | static void swap_cols(struct table *table, int a, int b) |
231 | { |
232 | struct row *row; |
233 | |
234 | swap_vars(table, a, b); |
235 | for (row = table->rows; row; row = row->next) |
236 | swap_values(row, a, b); |
237 | } |
238 | |
239 | |
240 | static void swap_rows(struct row **a, struct row **b) |
241 | { |
242 | struct value *value_a, *value_b; |
243 | |
244 | value_a = (*a)->values; |
245 | value_b = (*b)->values; |
246 | while (value_a) { |
247 | swap_table_cells(box_of_label(value_a->widget), |
248 | box_of_label(value_b->widget)); |
249 | value_a = value_a->next; |
250 | value_b = value_b->next; |
251 | } |
252 | SWAP(*a, *b); |
253 | SWAP((*a)->next, (*b)->next); |
254 | } |
255 | |
256 | |
257 | /* ----- swap frames ------------------------------------------------------- */ |
258 | |
259 | |
260 | static void swap_frames(GtkWidget *table, int a, int b) |
261 | { |
262 | struct frame **frame_a = NTH(frames, a); |
263 | struct frame **frame_b = NTH(frames, b); |
264 | |
265 | swap_table_rows(table, 2*a+1, 2*b+1); |
266 | swap_table_rows(table, 2*a+2, 2*b+2); |
267 | |
268 | SWAP(*frame_a, *frame_b); |
269 | SWAP((*frame_a)->next, (*frame_b)->next); |
270 | } |
271 | |
272 | |
273 | /* ----- common functions -------------------------------------------------- */ |
274 | |
275 | |
276 | /* |
277 | * according to |
278 | * http://www.pubbs.net/201004/gtk/22819-re-drag-and-drop-drag-motion-cursor-lockup-fixed-.html |
279 | * http://www.cryingwolf.org/articles/gtk-dnd.html |
280 | */ |
281 | |
282 | static int has_target(GtkWidget *widget, GdkDragContext *drag_context, |
283 | const char *name) |
284 | { |
285 | GdkAtom target; |
286 | |
287 | target = gtk_drag_dest_find_target(widget, drag_context, NULL); |
288 | |
289 | /* |
290 | * Force allocation so that we don't have to check for GDK_NONE. |
291 | */ |
292 | return target == gdk_atom_intern(name, FALSE); |
293 | } |
294 | |
295 | |
296 | static void drag_begin(GtkWidget *widget, |
297 | GtkTextDirection previous_direction, gpointer user_data) |
298 | { |
299 | GdkPixbuf *pixbuf; |
300 | |
301 | /* |
302 | * Suppress the icon. PixBufs can't be zero-sized, but nobody will |
303 | * notice a lone pixel either :-) |
304 | */ |
305 | pixbuf = |
306 | gdk_pixbuf_get_from_drawable(NULL, DA, NULL, 0, 0, 0, 0, 1, 1); |
307 | gtk_drag_source_set_icon_pixbuf(widget, pixbuf); |
308 | g_object_unref(pixbuf); |
309 | |
310 | dragging = user_data; |
311 | } |
312 | |
313 | |
314 | static void drag_end(GtkWidget *widget, GdkDragContext *drag_context, |
315 | gpointer user_data) |
316 | { |
317 | dragging = NULL; |
318 | } |
319 | |
320 | |
321 | static void setup_drag_common(GtkWidget *widget, void *user_arg) |
322 | { |
323 | g_signal_connect(G_OBJECT(widget), "drag-begin", |
324 | G_CALLBACK(drag_begin), user_arg); |
325 | g_signal_connect(G_OBJECT(widget), "drag-end", |
326 | G_CALLBACK(drag_end), user_arg); |
327 | } |
328 | |
329 | |
330 | /* ----- drag variables ---------------------------------------------------- */ |
331 | |
332 | |
333 | static gboolean drag_var_motion(GtkWidget *widget, |
334 | GdkDragContext *drag_context, gint x, gint y, guint time_, |
335 | gpointer user_data) |
336 | { |
337 | struct var *from = dragging; |
338 | struct var *to = user_data; |
339 | int from_n, to_n, i; |
340 | |
341 | if (!has_target(widget, drag_context, "var")) |
342 | return FALSE; |
343 | if (from == to || from->table != to->table) |
344 | return FALSE; |
345 | from_n = NDX(from->table->vars, from); |
346 | to_n = NDX(to->table->vars, to); |
347 | FOR_UNORDERED(i, from_n, to_n) |
348 | swap_cols(from->table, i, i+1); |
349 | return FALSE; |
350 | } |
351 | |
352 | |
353 | void setup_var_drag(struct var *var) |
354 | { |
355 | GtkWidget *box; |
356 | |
357 | box = box_of_label(var->widget); |
358 | gtk_drag_source_set(box, GDK_BUTTON1_MASK, |
359 | &target_var, 1, GDK_ACTION_PRIVATE); |
360 | gtk_drag_dest_set(box, GTK_DEST_DEFAULT_MOTION, |
361 | &target_var, 1, GDK_ACTION_PRIVATE); |
362 | setup_drag_common(box, var); |
363 | g_signal_connect(G_OBJECT(box), "drag-motion", |
364 | G_CALLBACK(drag_var_motion), var); |
365 | } |
366 | |
367 | |
368 | /* ----- drag values ------------------------------------------------------- */ |
369 | |
370 | |
371 | static gboolean drag_value_motion(GtkWidget *widget, |
372 | GdkDragContext *drag_context, gint x, gint y, guint time_, |
373 | gpointer user_data) |
374 | { |
375 | struct value *from = dragging; |
376 | struct value *to = user_data; |
377 | struct table *table; |
378 | struct row **row, *end; |
379 | int from_n, to_n, i; |
380 | |
381 | if (!has_target(widget, drag_context, "value")) |
382 | return FALSE; |
383 | table = from->row->table; |
384 | if (table != to->row->table) |
385 | return FALSE; |
386 | |
387 | /* columns */ |
388 | |
389 | from_n = NDX(from->row->values, from); |
390 | to_n = NDX(to->row->values, to); |
391 | FOR_UNORDERED(i, from_n, to_n) |
392 | swap_cols(table, i, i+1); |
393 | |
394 | /* rows */ |
395 | |
396 | if (from->row == to->row) |
397 | return FALSE; |
398 | row = &table->rows; |
399 | while (1) { |
400 | if (*row == from->row) { |
401 | end = to->row; |
402 | break; |
403 | } |
404 | if (*row == to->row) { |
405 | end = from->row; |
406 | break; |
407 | } |
408 | row = &(*row)->next; |
409 | } |
410 | while (1) { |
411 | swap_rows(row, &(*row)->next); |
412 | if (*row == end) |
413 | break; |
414 | row = &(*row)->next; |
415 | } |
416 | |
417 | return FALSE; |
418 | } |
419 | |
420 | |
421 | void setup_value_drag(struct value *value) |
422 | { |
423 | GtkWidget *box; |
424 | |
425 | box = box_of_label(value->widget); |
426 | gtk_drag_source_set(box, GDK_BUTTON1_MASK, |
427 | &target_value, 1, GDK_ACTION_PRIVATE); |
428 | gtk_drag_dest_set(box, GTK_DEST_DEFAULT_MOTION, |
429 | &target_value, 1, GDK_ACTION_PRIVATE); |
430 | setup_drag_common(box, value); |
431 | g_signal_connect(G_OBJECT(box), "drag-motion", |
432 | G_CALLBACK(drag_value_motion), value); |
433 | } |
434 | |
435 | |
436 | /* ----- frame to canvas helper functions ---------------------------------- */ |
437 | |
438 | |
439 | static int frame_on_canvas = 0; |
440 | |
441 | |
442 | static void leave_canvas(void) |
443 | { |
444 | if (frame_on_canvas) |
445 | canvas_frame_end(); |
446 | frame_on_canvas = 0; |
447 | } |
448 | |
449 | |
450 | /* ----- drag frame labels ------------------------------------------------- */ |
451 | |
452 | |
453 | #if 0 |
454 | |
455 | /* |
456 | * Setting our own icon looks nice but it slows things down to the point where |
457 | * cursor movements can lag noticeable and it adds yet another element to an |
458 | * already crowded cursor. |
459 | */ |
460 | |
461 | static void drag_frame_begin(GtkWidget *widget, |
462 | GtkTextDirection previous_direction, gpointer user_data) |
463 | { |
464 | GdkPixmap *pixmap; |
465 | GdkBitmap *mask; |
466 | GdkColormap *cmap; |
467 | |
468 | pixmap = gdk_pixmap_create_from_xpm_d(DA, &mask, NULL, xpm_frame); |
469 | cmap = gdk_drawable_get_colormap(root->window); |
470 | gtk_drag_source_set_icon(widget, cmap, pixmap, mask); |
471 | g_object_unref(pixmap); |
472 | g_object_unref(mask); |
473 | |
474 | dragging = user_data; |
475 | } |
476 | |
477 | #endif |
478 | |
479 | |
480 | static gboolean drag_frame_motion(GtkWidget *widget, |
481 | GdkDragContext *drag_context, gint x, gint y, guint time_, |
482 | gpointer user_data) |
483 | { |
484 | struct frame *from = dragging; |
485 | struct frame *to = user_data; |
486 | int from_n, to_n, i; |
487 | |
488 | if (!has_target(widget, drag_context, "frame")) |
489 | return FALSE; |
490 | assert(from != frames); |
491 | assert(to != frames); |
492 | from_n = NDX(frames, from); |
493 | to_n = NDX(frames, to); |
494 | FOR_UNORDERED(i, from_n, to_n) |
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