No issues found
1 /* rbtreednd.c
2 * Copyright (C) 2001 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301 USA.
18 */
19
20 #include "config.h"
21
22 #include <string.h>
23 #include <gtk/gtk.h>
24 #include "rb-tree-dnd.h"
25
26 #include "rb-debug.h"
27
28 #define RB_TREE_DND_STRING "RbTreeDndString"
29 /* must be the same value as in gtk_tree_view.c */
30 #define SCROLL_EDGE_SIZE 15
31
32
33 /**
34 * SECTION:rb-tree-dnd
35 * @short_description: multi-row drag and drop support for GtkTreeViews
36 *
37 * Provides support for drag and drop operations to and from GtkTreeView
38 * widgets that can include multiple rows. The model backing the tree view
39 * widgets must implement the #RbTreeDragSource and #RbTreeDragDest interfaces.
40 */
41
42 /**
43 * RbTreeDestFlag:
44 * @RB_TREE_DEST_EMPTY_VIEW_DROP: If set, drops into empty spaces in the view are accepted
45 * @RB_TREE_DEST_CAN_DROP_INTO: If set, drops into existing rows are accepted
46 * @RB_TREE_DEST_CAN_DROP_BETWEEN: If set, drops between existing rows are accepted
47 * @RB_TREE_DEST_SELECT_ON_DRAG_TIMEOUT: If set, update the drag selection using a timeout
48 *
49 * Flags controlling drag destination behaviour.
50 */
51
52 typedef struct
53 {
54 guint pressed_button;
55 gint x;
56 gint y;
57 guint button_press_event_handler;
58 guint motion_notify_handler;
59 guint button_release_handler;
60 guint drag_data_get_handler;
61 guint drag_data_delete_handler;
62 guint drag_motion_handler;
63 guint drag_leave_handler;
64 guint drag_drop_handler;
65 guint drag_data_received_handler;
66 GSList *event_list;
67 gboolean pending_event;
68
69 GtkTargetList *dest_target_list;
70 GdkDragAction dest_actions;
71 RbTreeDestFlag dest_flags;
72
73 GtkTargetList *source_target_list;
74 GdkDragAction source_actions;
75 GdkModifierType start_button_mask;
76
77 /* Scroll timeout (e.g. during dnd) */
78 guint scroll_timeout;
79
80 /* Select on drag timeout */
81 GtkTreePath * previous_dest_path;
82 guint select_on_drag_timeout;
83 } RbTreeDndData;
84
85 static RbTreeDndData *init_rb_tree_dnd_data (GtkWidget *widget);
86 static GList * get_context_data (GdkDragContext *context);
87 static gboolean filter_drop_position (GtkWidget *widget, GdkDragContext *context, GtkTreePath *path, GtkTreeViewDropPosition *pos);
88 static gint scroll_row_timeout (gpointer data);
89 static gboolean select_on_drag_timeout (gpointer data);
90 static void remove_scroll_timeout (GtkTreeView *tree_view);
91 static void remove_select_on_drag_timeout (GtkTreeView *tree_view);
92
93 GType
94 rb_tree_drag_source_get_type (void)
95 {
96 static GType our_type = 0;
97
98 if (!our_type)
99 {
100 static const GTypeInfo our_info =
101 {
102 sizeof (RbTreeDragSourceIface), /* class_size */
103 NULL, /* base_init */
104 NULL, /* base_finalize */
105 NULL,
106 NULL, /* class_finalize */
107 NULL, /* class_data */
108 0,
109 0, /* n_preallocs */
110 NULL
111 };
112
113 our_type = g_type_register_static (G_TYPE_INTERFACE, "RbTreeDragSource", &our_info, 0);
114 }
115
116 return our_type;
117 }
118
119
120 /**
121 * rb_tree_drag_source_row_draggable:
122 * @drag_source: a #RbTreeDragSource
123 * @path_list: row on which user is initiating a drag
124 *
125 * Asks the #RbTreeDragSource whether a particular row can be used as
126 * the source of a DND operation. If the source doesn't implement
127 * this interface, the row is assumed draggable.
128 *
129 * Return value: %TRUE if the row can be dragged
130 **/
131 gboolean
132 rb_tree_drag_source_row_draggable (RbTreeDragSource *drag_source,
133 GList *path_list)
134 {
135 RbTreeDragSourceIface *iface = RB_TREE_DRAG_SOURCE_GET_IFACE (drag_source);
136
137 g_return_val_if_fail (RB_IS_TREE_DRAG_SOURCE (drag_source), FALSE);
138 g_return_val_if_fail (iface->rb_row_draggable != NULL, FALSE);
139 g_return_val_if_fail (path_list != NULL, FALSE);
140
141 if (iface->rb_row_draggable)
142 return (* iface->rb_row_draggable) (drag_source, path_list);
143 else
144 return TRUE;
145 }
146
147
148 /**
149 * rb_tree_drag_source_drag_data_delete:
150 * @drag_source: a #RbTreeDragSource
151 * @path_list: row that was being dragged
152 *
153 * Asks the #RbTreeDragSource to delete the row at @path, because
154 * it was moved somewhere else via drag-and-drop. Returns %FALSE
155 * if the deletion fails because @path no longer exists, or for
156 * some model-specific reason. Should robustly handle a @path no
157 * longer found in the model!
158 *
159 * Return value: %TRUE if the row was successfully deleted
160 **/
161 gboolean
162 rb_tree_drag_source_drag_data_delete (RbTreeDragSource *drag_source,
163 GList *path_list)
164 {
165 RbTreeDragSourceIface *iface = RB_TREE_DRAG_SOURCE_GET_IFACE (drag_source);
166
167 g_return_val_if_fail (RB_IS_TREE_DRAG_SOURCE (drag_source), FALSE);
168 g_return_val_if_fail (iface->rb_drag_data_delete != NULL, FALSE);
169 g_return_val_if_fail (path_list != NULL, FALSE);
170
171 return (* iface->rb_drag_data_delete) (drag_source, path_list);
172 }
173
174 /**
175 * rb_tree_drag_source_drag_data_get:
176 * @drag_source: a #RbTreeDragSource
177 * @path_list: row that was dragged
178 * @selection_data: a #GtkSelectionData to fill with data from the dragged row
179 *
180 * Asks the #RbTreeDragSource to fill in @selection_data with a
181 * representation of the row at @path. @selection_data->target gives
182 * the required type of the data. Should robustly handle a @path no
183 * longer found in the model!
184 *
185 * Return value: %TRUE if data of the required type was provided
186 **/
187 gboolean
188 rb_tree_drag_source_drag_data_get (RbTreeDragSource *drag_source,
189 GList *path_list,
190 GtkSelectionData *selection_data)
191 {
192 RbTreeDragSourceIface *iface = RB_TREE_DRAG_SOURCE_GET_IFACE (drag_source);
193
194 g_return_val_if_fail (RB_IS_TREE_DRAG_SOURCE (drag_source), FALSE);
195 g_return_val_if_fail (iface->rb_drag_data_get != NULL, FALSE);
196 g_return_val_if_fail (path_list != NULL, FALSE);
197 g_return_val_if_fail (selection_data != NULL, FALSE);
198
199 return (* iface->rb_drag_data_get) (drag_source, path_list, selection_data);
200 }
201
202
203
204 GType
205 rb_tree_drag_dest_get_type (void)
206 {
207 static GType our_type = 0;
208
209 if (!our_type)
210 {
211 static const GTypeInfo our_info =
212 {
213 sizeof (RbTreeDragDestIface), /* class_size */
214 NULL, /* base_init */
215 NULL, /* base_finalize */
216 NULL,
217 NULL, /* class_finalize */
218 NULL, /* class_data */
219 0,
220 0, /* n_preallocs */
221 NULL
222 };
223
224 our_type = g_type_register_static (G_TYPE_INTERFACE, "RbTreeDragDest", &our_info, 0);
225 }
226
227 return our_type;
228 }
229
230
231 /**
232 * rb_tree_drag_dest_drag_data_received:
233 * @drag_dest: a #RbTreeDragDest
234 * @dest: the #GtkTreePath on which the data was dropped
235 * @pos: the drop position relative to the row identified by @dest
236 * @selection_data: a #GtkSelectionData containing the drag data
237 *
238 * Asks a #RbTreeDragDest to accept some drag and drop data.
239 *
240 * Return value: %TRUE if the data was accepted, %FALSE otherwise
241 */
242 gboolean
243 rb_tree_drag_dest_drag_data_received (RbTreeDragDest *drag_dest,
244 GtkTreePath *dest,
245 GtkTreeViewDropPosition pos,
246 GtkSelectionData *selection_data)
247 {
248 RbTreeDragDestIface *iface = RB_TREE_DRAG_DEST_GET_IFACE (drag_dest);
249
250 g_return_val_if_fail (RB_IS_TREE_DRAG_DEST (drag_dest), FALSE);
251 g_return_val_if_fail (iface->rb_drag_data_received != NULL, FALSE);
252 g_return_val_if_fail (selection_data != NULL, FALSE);
253
254 return (* iface->rb_drag_data_received) (drag_dest, dest, pos, selection_data);
255 }
256
257
258 /**
259 * rb_tree_drag_dest_row_drop_possible:
260 * @drag_dest: a #RbTreeDragDest
261 * @dest_path: the #GtkTreePath on which the data may be dropped
262 * @pos: the drop position relative to the row identified by @dest
263 * @selection_data: a #GtkSelectionData containing the drag data
264 *
265 * Asks the #RbTreeDragDest whether data can be dropped on a particular
266 * row. This should probably check based on the format and the row.
267 *
268 * Return value: %TRUE if the data can be dropped there
269 */
270 gboolean
271 rb_tree_drag_dest_row_drop_possible (RbTreeDragDest *drag_dest,
272 GtkTreePath *dest_path,
273 GtkTreeViewDropPosition pos,
274 GtkSelectionData *selection_data)
275 {
276 RbTreeDragDestIface *iface = RB_TREE_DRAG_DEST_GET_IFACE (drag_dest);
277
278 g_return_val_if_fail (RB_IS_TREE_DRAG_DEST (drag_dest), FALSE);
279 g_return_val_if_fail (iface->rb_row_drop_possible != NULL, FALSE);
280 g_return_val_if_fail (selection_data != NULL, FALSE);
281
282 return (* iface->rb_row_drop_possible) (drag_dest, dest_path, pos, selection_data);
283 }
284
285
286 /**
287 * rb_tree_drag_dest_row_drop_position:
288 * @drag_dest: a #RbTreeDragDest
289 * @dest_path: a #GtkTreePath describing a possible drop row
290 * @targets: a #GList containing possible drop target types
291 * @pos: returns the #GtkTreeViewDropPosition to use relative to the row
292 *
293 * Asks the #RbTreeDragDest which drop position to use relative to the specified row.
294 * The drag destination should decide which drop position to use based on the
295 * target row and the list of drag targets.
296 *
297 * Return value: %TRUE if a drop position has been set, %FALSE if a drop should not be
298 * allowed in the specified row
299 */
300 gboolean
301 rb_tree_drag_dest_row_drop_position (RbTreeDragDest *drag_dest,
302 GtkTreePath *dest_path,
303 GList *targets,
304 GtkTreeViewDropPosition *pos)
305 {
306 RbTreeDragDestIface *iface = RB_TREE_DRAG_DEST_GET_IFACE (drag_dest);
307
308 g_return_val_if_fail (RB_IS_TREE_DRAG_DEST (drag_dest), FALSE);
309 g_return_val_if_fail (iface->rb_row_drop_position != NULL, FALSE);
310 g_return_val_if_fail (targets != NULL, FALSE);
311 g_return_val_if_fail (pos != NULL, FALSE);
312
313 return (* iface->rb_row_drop_position) (drag_dest, dest_path, targets, pos);
314 }
315
316 static void
317 rb_tree_dnd_data_free (gpointer data)
318 {
319 RbTreeDndData *priv_data = data;
320
321 if (priv_data->source_target_list != NULL) {
322 gtk_target_list_unref (priv_data->source_target_list);
323 }
324 if (priv_data->dest_target_list != NULL) {
325 gtk_target_list_unref (priv_data->dest_target_list);
326 }
327
328 g_free (priv_data);
329 }
330
331 static RbTreeDndData *
332 init_rb_tree_dnd_data (GtkWidget *widget)
333 {
334 RbTreeDndData *priv_data;
335
336 priv_data = g_object_get_data (G_OBJECT (widget), RB_TREE_DND_STRING);
337 if (priv_data == NULL)
338 {
339 priv_data = g_new0 (RbTreeDndData, 1);
340 priv_data->pending_event = FALSE;
341 g_object_set_data_full (G_OBJECT (widget), RB_TREE_DND_STRING, priv_data, rb_tree_dnd_data_free);
342 priv_data->drag_motion_handler = 0;
343 priv_data->drag_leave_handler = 0;
344 priv_data->button_press_event_handler = 0;
345 priv_data->scroll_timeout = 0;
346 priv_data->previous_dest_path = NULL;
347 priv_data->select_on_drag_timeout = 0;
348 }
349
350 return priv_data;
351 }
352
353 static void
354 stop_drag_check (GtkWidget *widget)
355 {
356 RbTreeDndData *priv_data;
357 GSList *l;
358
359 priv_data = g_object_get_data (G_OBJECT (widget), RB_TREE_DND_STRING);
360
361 for (l = priv_data->event_list; l != NULL; l = l->next)
362 gdk_event_free (l->data);
363
364 g_slist_free (priv_data->event_list);
365 priv_data->event_list = NULL;
366 priv_data->pending_event = FALSE;
367 g_signal_handler_disconnect (widget, priv_data->motion_notify_handler);
368 g_signal_handler_disconnect (widget, priv_data->button_release_handler);
369 }
370
371
372 static gboolean
373 rb_tree_dnd_button_release_event_cb (GtkWidget *widget,
374 GdkEventButton *event,
375 gpointer data)
376 {
377 RbTreeDndData *priv_data;
378 GSList *l;
379
380 priv_data = g_object_get_data (G_OBJECT (widget), RB_TREE_DND_STRING);
381
382 for (l = priv_data->event_list; l != NULL; l = l->next)
383 gtk_propagate_event (widget, l->data);
384
385 stop_drag_check (widget);
386
387 return FALSE;
388 }
389
390
391 static void
392 selection_foreach (GtkTreeModel *model,
393 GtkTreePath *path,
394 GtkTreeIter *iter,
395 gpointer data)
396 {
397 GList **list_ptr;
398
399 list_ptr = (GList **) data;
400 *list_ptr = g_list_prepend (*list_ptr, gtk_tree_row_reference_new (model, path));
401 }
402
403
404 static void
405 path_list_free (GList *path_list)
406 {
407 g_list_foreach (path_list, (GFunc) gtk_tree_row_reference_free, NULL);
408 g_list_free (path_list);
409 }
410
411 static void
412 set_context_data (GdkDragContext *context,
413 GList *path_list)
414 {
415 g_object_set_data_full (G_OBJECT (context),
416 "rb-tree-view-multi-source-row",
417 path_list,
418 (GDestroyNotify) path_list_free);
419
420 rb_debug ("Setting path_list: index=%i", gtk_tree_path_get_indices(path_list->data)[0]);
421 }
422
423 static GList *
424 get_context_data (GdkDragContext *context)
425 {
426 return g_object_get_data (G_OBJECT (context), "rb-tree-view-multi-source-row");
427 }
428
429 static gboolean
430 filter_drop_position (GtkWidget *widget, GdkDragContext *context, GtkTreePath *path, GtkTreeViewDropPosition *pos)
431 {
432 GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
433 GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
434 RbTreeDndData *priv_data = g_object_get_data (G_OBJECT (widget), RB_TREE_DND_STRING);
435 gboolean ret;
436
437 if (!(priv_data->dest_flags & RB_TREE_DEST_CAN_DROP_INTO)) {
438 if (*pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)
439 *pos = GTK_TREE_VIEW_DROP_BEFORE;
440 else if (*pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
441 *pos = GTK_TREE_VIEW_DROP_AFTER;
442 } else if (!(priv_data->dest_flags & RB_TREE_DEST_CAN_DROP_BETWEEN)) {
443 if (*pos == GTK_TREE_VIEW_DROP_BEFORE)
444 *pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
445 else if (*pos == GTK_TREE_VIEW_DROP_AFTER)
446 *pos = GTK_TREE_VIEW_DROP_INTO_OR_AFTER;
447 }
448
449 ret = rb_tree_drag_dest_row_drop_position (RB_TREE_DRAG_DEST (model),
450 path,
451 gdk_drag_context_list_targets (context),
452 pos);
453
454 rb_debug ("filtered drop position: %s", ret ? "TRUE" : "FALSE");
455 return ret;
456 }
457
458
459 /* Scroll function taken/adapted from gtktreeview.c */
460 static gint
461 scroll_row_timeout (gpointer data)
462 {
463 GtkTreeView *tree_view = data;
464 GdkRectangle visible_rect;
465 gint y, x;
466 gint offset;
467 gfloat value;
468 gdouble vadj_value;
469 GtkAdjustment* vadj;
470 RbTreeDndData *priv_data;
471 GdkWindow *window;
472 GdkDeviceManager *device_manager;
473
474 GDK_THREADS_ENTER ();
475
476 priv_data = g_object_get_data (G_OBJECT (tree_view), RB_TREE_DND_STRING);
477 g_return_val_if_fail(priv_data != NULL, TRUE);
478
479 window = gtk_tree_view_get_bin_window (tree_view);
480 device_manager = gdk_display_get_device_manager (gdk_window_get_display (window));
481 gdk_window_get_device_position (window, gdk_device_manager_get_client_pointer (device_manager), &x, &y, NULL);
482 gtk_tree_view_convert_widget_to_bin_window_coords (tree_view, x, y, &x, &y);
483 gtk_tree_view_convert_bin_window_to_tree_coords (tree_view, x, y, &x, &y);
484
485 gtk_tree_view_get_visible_rect (tree_view, &visible_rect);
486
487 /* see if we are near the edge. */
488 if (x < visible_rect.x && x > visible_rect.x + visible_rect.width)
489 {
490 GDK_THREADS_LEAVE ();
491 priv_data->scroll_timeout = 0;
492 return FALSE;
493 }
494
495 offset = y - (visible_rect.y + 2 * SCROLL_EDGE_SIZE);
496 if (offset > 0)
497 {
498 offset = y - (visible_rect.y + visible_rect.height - 2 * SCROLL_EDGE_SIZE);
499 if (offset < 0)
500 {
501 GDK_THREADS_LEAVE ();
502 priv_data->scroll_timeout = 0;
503 return FALSE;
504 }
505 }
506
507 vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (tree_view));
508 vadj_value = gtk_adjustment_get_value (vadj);
509 value = CLAMP (vadj_value + offset,
510 gtk_adjustment_get_lower (vadj),
511 gtk_adjustment_get_upper (vadj) - gtk_adjustment_get_page_size (vadj));
512 gtk_adjustment_set_value (vadj, value);
513
514 /* don't remove it if we're on the edge and not scrolling */
515 if (ABS (vadj_value - value) > 0.0001)
516 remove_select_on_drag_timeout(tree_view);
517
518 GDK_THREADS_LEAVE ();
519
520 return TRUE;
521 }
522
523
524 static gboolean
525 select_on_drag_timeout (gpointer data)
526 {
527 GtkTreeView *tree_view = data;
528 GtkTreeSelection *selection;
529 RbTreeDndData *priv_data;
530
531 GDK_THREADS_ENTER ();
532
533 priv_data = g_object_get_data (G_OBJECT (tree_view), RB_TREE_DND_STRING);
534 g_return_val_if_fail(priv_data != NULL, FALSE);
535 g_return_val_if_fail(priv_data->previous_dest_path != NULL, FALSE);
536
537 selection = gtk_tree_view_get_selection(tree_view);
538 if (!gtk_tree_selection_path_is_selected(selection,priv_data->previous_dest_path)) {
539 rb_debug("Changing selection because of drag timeout");
540 gtk_tree_view_set_cursor(tree_view,priv_data->previous_dest_path,NULL,FALSE);
541 }
542
543 priv_data->select_on_drag_timeout = 0;
544 gtk_tree_path_free(priv_data->previous_dest_path);
545 priv_data->previous_dest_path = NULL;
546
547 GDK_THREADS_LEAVE ();
548 return FALSE;
549 }
550
551
552 static void
553 remove_scroll_timeout (GtkTreeView *tree_view)
554 {
555 RbTreeDndData *priv_data;
556
557 priv_data = g_object_get_data (G_OBJECT (tree_view), RB_TREE_DND_STRING);
558 g_return_if_fail(priv_data != NULL);
559
560 if (priv_data->scroll_timeout != 0)
561 {
562 g_source_remove (priv_data->scroll_timeout);
563 priv_data->scroll_timeout = 0;
564 }
565 }
566
567
568 static void
569 remove_select_on_drag_timeout (GtkTreeView *tree_view)
570 {
571 RbTreeDndData *priv_data;
572
573 priv_data = g_object_get_data (G_OBJECT (tree_view), RB_TREE_DND_STRING);
574 g_return_if_fail(priv_data != NULL);
575
576 if (priv_data->select_on_drag_timeout != 0) {
577 rb_debug("Removing the select on drag timeout");
578 g_source_remove (priv_data->select_on_drag_timeout);
579 priv_data->select_on_drag_timeout = 0;
580 }
581 if (priv_data->previous_dest_path != NULL) {
582 gtk_tree_path_free(priv_data->previous_dest_path);
583 priv_data->previous_dest_path = NULL;
584 }
585 }
586
587
588 static void
589 rb_tree_dnd_drag_data_delete_cb (GtkWidget *widget,
590 GdkDragContext *drag_context,
591 gpointer user_data)
592 {
593 GList *path_list;
594 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW(widget));
595
596 path_list = get_context_data (drag_context);
597 rb_tree_drag_source_drag_data_delete (RB_TREE_DRAG_SOURCE (model),
598 path_list);
599
600 g_signal_stop_emission_by_name (widget, "drag_data_delete");
601 }
602
603
604
605 static void
606 rb_tree_dnd_drag_data_get_cb (GtkWidget *widget,
607 GdkDragContext *context,
608 GtkSelectionData *selection_data,
609 guint info,
610 guint time)
611 {
612 GtkTreeView *tree_view;
613 GtkTreeModel *model;
614 GList *path_list;
615
616 tree_view = GTK_TREE_VIEW (widget);
617 model = gtk_tree_view_get_model (tree_view);
618
619 if (model == NULL)
620 return;
621
622 path_list = get_context_data (context);
623
624 if (path_list == NULL)
625 return;
626
627 /* We can implement the GTK_TREE_MODEL_ROW target generically for
628 * any model; for DragSource models there are some other targets
629 * we also support.
630 */
631 if (RB_IS_TREE_DRAG_SOURCE (model))
632 {
633 rb_tree_drag_source_drag_data_get (RB_TREE_DRAG_SOURCE (model),
634 path_list,
635 selection_data);
636 }
637 }
638
639
640 static gboolean
641 rb_tree_dnd_motion_notify_event_cb (GtkWidget *widget,
642 GdkEventMotion *event,
643 gpointer data)
644 {
645 RbTreeDndData *priv_data;
646
647 priv_data = g_object_get_data (G_OBJECT (widget), RB_TREE_DND_STRING);
648
649 if (gtk_drag_check_threshold (widget,
650 priv_data->x,
651 priv_data->y,
652 event->x,
653 event->y))
654 {
655 GList *path_list = NULL;
656 GtkTreeSelection *selection;
657 GtkTreeModel *model;
658 GdkDragContext *context;
659
660 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
661 stop_drag_check (widget);
662 gtk_tree_selection_selected_foreach (selection, selection_foreach, &path_list);
663 path_list = g_list_reverse (path_list);
664 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
665
666 if (rb_tree_drag_source_row_draggable (RB_TREE_DRAG_SOURCE (model), path_list))
667 {
668 rb_debug ("drag begin");
669 context = gtk_drag_begin (widget,
670 priv_data->source_target_list,
671 priv_data->source_actions,
672 priv_data->pressed_button,
673 (GdkEvent*)event);
674 set_context_data (context, path_list);
675 gtk_drag_set_icon_default (context);
676
677 } else {
678 path_list_free (path_list);
679 }
680 }
681
682 return TRUE;
683 }
684
685 static gboolean
686 rb_tree_dnd_drag_motion_cb (GtkWidget *widget,
687 GdkDragContext *context,
688 gint x,
689 gint y,
690 guint time)
691 {
692 GtkTreeView *tree_view;
693 GtkTreePath *path = NULL;
694 GtkTreeViewDropPosition pos;
695 RbTreeDndData *priv_data;
696 GdkDragAction action;
697
698 rb_debug ("drag and drop motion: (%i,%i)", x, y);
699
700 tree_view = GTK_TREE_VIEW (widget);
701
702 priv_data = g_object_get_data (G_OBJECT (widget), RB_TREE_DND_STRING);
703
704 gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, &pos);
705
706 if ((priv_data->previous_dest_path == NULL)
707 || (path == NULL)
708 || gtk_tree_path_compare(path,priv_data->previous_dest_path)) {
709 remove_select_on_drag_timeout(tree_view);
710 }
711
712 if (path == NULL)
713 {
714 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
715 NULL,
716 GTK_TREE_VIEW_DROP_BEFORE);
717
718 if (!(priv_data->dest_flags & RB_TREE_DEST_EMPTY_VIEW_DROP)) {
719 /* Can't drop here. */
720 gdk_drag_status (context, 0, time);
721
722 return TRUE;
723 } else if (!filter_drop_position (widget, context, path, &pos)) {
724 gdk_drag_status (context, 0, time);
725 return TRUE;
726 }
727 }
728 else
729 {
730 if (!filter_drop_position (widget, context, path, &pos)) {
731 gdk_drag_status (context, 0, time);
732 return TRUE;
733 }
734
735 if (priv_data->scroll_timeout == 0)
736 {
737 priv_data->scroll_timeout = g_timeout_add (150, scroll_row_timeout, tree_view);
738 }
739 }
740
741 if (GTK_WIDGET (tree_view) == gtk_drag_get_source_widget (context) &&
742 gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE)
743 action = GDK_ACTION_MOVE;
744 else
745 action = gdk_drag_context_get_suggested_action (context);
746
747 if (path) {
748 gtk_tree_view_set_drag_dest_row (tree_view, path, pos);
749 if (priv_data->dest_flags & RB_TREE_DEST_SELECT_ON_DRAG_TIMEOUT) {
750 if (priv_data->previous_dest_path != NULL) {
751 gtk_tree_path_free (priv_data->previous_dest_path);
752 }
753 priv_data->previous_dest_path = path;
754 if (priv_data->select_on_drag_timeout == 0) {
755 rb_debug("Setting up a new select on drag timeout");
756 priv_data->select_on_drag_timeout = g_timeout_add_seconds (2, select_on_drag_timeout, tree_view);
757 }
758 } else {
759 gtk_tree_path_free (path);
760 }
761 }
762
763 gdk_drag_status (context, action, time);
764
765 return TRUE;
766 }
767
768
769 static gboolean
770 rb_tree_dnd_drag_leave_cb (GtkWidget *widget,
771 GdkDragContext *context,
772 gint x,
773 gint y,
774 guint time)
775 {
776 remove_select_on_drag_timeout(GTK_TREE_VIEW (widget));
777 return TRUE;
778 }
779
780 static gboolean
781 rb_tree_dnd_drag_drop_cb (GtkWidget *widget,
782 GdkDragContext *context,
783 gint x,
784 gint y,
785 guint time)
786 {
787 GtkTreeView *tree_view;
788 GtkTreePath *path;
789 GtkTreeModel *model;
790 GtkTreeViewDropPosition pos;
791 RbTreeDndData *priv_data;
792
793 tree_view = GTK_TREE_VIEW (widget);
794 model = gtk_tree_view_get_model (tree_view);
795 priv_data = g_object_get_data (G_OBJECT (widget), RB_TREE_DND_STRING);
796 gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, &pos);
797
798 remove_scroll_timeout (tree_view);
799
800 /* Unset this thing */
801 gtk_tree_view_set_drag_dest_row (tree_view,
802 NULL,
803 GTK_TREE_VIEW_DROP_BEFORE);
804
805 if (path || priv_data->dest_flags & RB_TREE_DEST_EMPTY_VIEW_DROP) {
806
807 GdkAtom target;
808 RbTreeDragDestIface *iface = RB_TREE_DRAG_DEST_GET_IFACE (model);
809 if (iface->rb_get_drag_target) {
810 RbTreeDragDest *dest = RB_TREE_DRAG_DEST (model);
811 target = (* iface->rb_get_drag_target) (dest, widget,
812 context, path,
813 priv_data->dest_target_list);
814 } else {
815 target = gtk_drag_dest_find_target (widget, context,
816 priv_data->dest_target_list);
817 }
818
819 if (path)
820 gtk_tree_path_free (path);
821
822 if (target != GDK_NONE) {
823 gtk_drag_get_data (widget, context, target, time);
824 return TRUE;
825 }
826 }
827
828 return FALSE;
829 }
830
831
832 static void
833 rb_tree_dnd_drag_data_received_cb (GtkWidget *widget,
834 GdkDragContext *context,
835 gint x,
836 gint y,
837 GtkSelectionData *selection_data,
838 guint info,
839 guint time)
840 {
841 GtkTreeView *tree_view;
842 GtkTreeModel *model;
843 GtkTreePath *dest_row;
844 GtkTreeViewDropPosition pos;
845 gboolean filtered = TRUE;
846 gboolean accepted = FALSE;
847
848 tree_view = GTK_TREE_VIEW (widget);
849 model = gtk_tree_view_get_model (tree_view);
850
851 gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &dest_row, &pos);
852
853 if (dest_row)
854 if (!filter_drop_position (widget, context, dest_row, &pos))
855 filtered = FALSE;
856
857 if (filtered && (gtk_selection_data_get_length (selection_data) >= 0))
858 {
859 if (rb_tree_drag_dest_drag_data_received (RB_TREE_DRAG_DEST (model),
860 dest_row,
861 pos,
862 selection_data))
863 accepted = TRUE;
864 }
865
866 gtk_drag_finish (context,
867 accepted,
868 (gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE),
869 time);
870
871 if (dest_row)
872 gtk_tree_path_free (dest_row);
873
874 g_signal_stop_emission_by_name (widget, "drag_data_received");
875
876 }
877
878
879 static gboolean
880 rb_tree_dnd_button_press_event_cb (GtkWidget *widget,
881 GdkEventButton *event,
882 gpointer data)
883 {
884 GtkTreeView *tree_view;
885 GtkTreePath *path = NULL;
886 GtkTreeViewColumn *column = NULL;
887 gint cell_x, cell_y;
888 GtkTreeSelection *selection;
889 RbTreeDndData *priv_data;
890
891 if (event->button == 3)
892 return FALSE;
893
894 tree_view = GTK_TREE_VIEW (widget);
895 if (event->window != gtk_tree_view_get_bin_window (tree_view))
896 return FALSE;
897
898 priv_data = g_object_get_data (G_OBJECT (tree_view), RB_TREE_DND_STRING);
899 if (priv_data == NULL)
900 {
901 priv_data = g_new0 (RbTreeDndData, 1);
902 priv_data->pending_event = FALSE;
903 g_object_set_data (G_OBJECT (tree_view), RB_TREE_DND_STRING, priv_data);
904 }
905
906 if (g_slist_find (priv_data->event_list, event))
907 return FALSE;
908
909 if (priv_data->pending_event)
910 {
911 /* save the event to be propagated in order */
912 priv_data->event_list = g_slist_append (priv_data->event_list, gdk_event_copy ((GdkEvent*)event));
913 return TRUE;
914 }
915
916 if (event->type == GDK_2BUTTON_PRESS)
917 return FALSE;
918
919 gtk_tree_view_get_path_at_pos (tree_view,
920 event->x, event->y,
921 &path, &column,
922 &cell_x, &cell_y);
923
924 selection = gtk_tree_view_get_selection (tree_view);
925
926 if (path)
927 {
928 gboolean call_parent = (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK) ||
929 !gtk_tree_selection_path_is_selected (selection, path) ||
930 event->button != 1);
931
932 if (call_parent)
933 (GTK_WIDGET_GET_CLASS (tree_view))->button_press_event (widget, event);
934
935 if (gtk_tree_selection_path_is_selected (selection, path))
936 {
937 priv_data->pressed_button = event->button;
938 priv_data->x = event->x;
939 priv_data->y = event->y;
940
941 priv_data->pending_event = TRUE;
942
943 if (!call_parent)
944 priv_data->event_list = g_slist_append (priv_data->event_list,
945 gdk_event_copy ((GdkEvent*)event));
946
947 priv_data->motion_notify_handler = g_signal_connect (G_OBJECT (tree_view),
948 "motion_notify_event",
949 G_CALLBACK (rb_tree_dnd_motion_notify_event_cb),
950 NULL);
951 priv_data->button_release_handler = g_signal_connect (G_OBJECT (tree_view),
952 "button_release_event",
953 G_CALLBACK (rb_tree_dnd_button_release_event_cb),
954 NULL);
955
956 }
957
958 gtk_tree_path_free (path);
959 /* We called the default handler so we don't let the default handler run */
960 return TRUE;
961 }
962
963 return FALSE;
964 }
965
966 /**
967 * rb_tree_dnd_add_drag_source_support:
968 * @tree_view: a #GtkTreeView that wants to be a drag source
969 * @start_button_mask: a mask describing modifier keys to handle when dragging
970 * @targets: an array of #GtkTargetEntry structures describing drag data types
971 * @n_targets: the number of elements in @targets
972 * @actions: a mask describing drag actions that are allowed from this source
973 *
974 * Adds event handlers to perform multi-row drag and drop operations from the
975 * specified #GtkTreeView widget. The model backing the #GtkTreeView must
976 * implement the #RbTreeDragSource interface. This should be called immediately
977 * after the tree view is created.
978 */
979 void
980 rb_tree_dnd_add_drag_source_support (GtkTreeView *tree_view,
981 GdkModifierType start_button_mask,
982 const GtkTargetEntry *targets,
983 gint n_targets,
984 GdkDragAction actions)
985 {
986 RbTreeDndData *priv_data = NULL;
987 g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
988
989 priv_data = init_rb_tree_dnd_data (GTK_WIDGET(tree_view));
990
991 if (!priv_data->button_press_event_handler) {
992
993 priv_data->source_target_list = gtk_target_list_new (targets, n_targets);
994 priv_data->source_actions = actions;
995 priv_data->start_button_mask = start_button_mask;
996
997 priv_data->button_press_event_handler = g_signal_connect (G_OBJECT (tree_view),
998 "button_press_event",
999 G_CALLBACK (rb_tree_dnd_button_press_event_cb),
1000 NULL);
1001 priv_data->drag_data_get_handler = g_signal_connect (G_OBJECT (tree_view),
1002 "drag_data_get",
1003 G_CALLBACK (rb_tree_dnd_drag_data_get_cb),
1004 NULL);
1005 priv_data->drag_data_delete_handler = g_signal_connect (G_OBJECT (tree_view),
1006 "drag_data_delete",
1007 G_CALLBACK (rb_tree_dnd_drag_data_delete_cb),
1008 NULL);
1009 }
1010 }
1011
1012 /**
1013 * rb_tree_dnd_add_drag_dest_support:
1014 * @tree_view: a #GtkTreeView that wants to be a drag destination
1015 * @flags: #RbTreeDestFlags for this drag destination
1016 * @targets: an array of #GtkTargetEntry structures describing the allowed drag targets
1017 * @n_targets: the number of elements in @targets
1018 * @actions: the allowable drag actions for this destination
1019 *
1020 * Adds event handlers to perform multi-row drag and drop operations to the specified
1021 * #GtkTreeView. The model backing the tree view should implement the #RbTreeDragDest
1022 * interface. This should be called immediately after the tree view is created.
1023 */
1024 void
1025 rb_tree_dnd_add_drag_dest_support (GtkTreeView *tree_view,
1026 RbTreeDestFlag flags,
1027 const GtkTargetEntry *targets,
1028 gint n_targets,
1029 GdkDragAction actions)
1030 {
1031 RbTreeDndData *priv_data = NULL;
1032 g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
1033
1034 priv_data = init_rb_tree_dnd_data (GTK_WIDGET(tree_view));
1035
1036 if (!priv_data->drag_motion_handler) {
1037
1038 priv_data->dest_target_list = gtk_target_list_new (targets, n_targets);
1039 priv_data->dest_actions = actions;
1040 priv_data->dest_flags = flags;
1041
1042 gtk_drag_dest_set (GTK_WIDGET (tree_view),
1043 0,
1044 NULL,
1045 0,
1046 actions);
1047
1048 priv_data->drag_motion_handler = g_signal_connect (G_OBJECT (tree_view),
1049 "drag_motion",
1050 G_CALLBACK (rb_tree_dnd_drag_motion_cb),
1051 NULL);
1052 priv_data->drag_leave_handler = g_signal_connect (G_OBJECT (tree_view),
1053 "drag_leave",
1054 G_CALLBACK (rb_tree_dnd_drag_leave_cb),
1055 NULL);
1056 priv_data->drag_drop_handler = g_signal_connect (G_OBJECT (tree_view),
1057 "drag_drop",
1058 G_CALLBACK (rb_tree_dnd_drag_drop_cb),
1059 NULL);
1060 priv_data->drag_data_received_handler = g_signal_connect (G_OBJECT (tree_view),
1061 "drag_data_received",
1062 G_CALLBACK (rb_tree_dnd_drag_data_received_cb),
1063 NULL);
1064 }
1065 }