Root/
| 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
