1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2
3 /*
4 * Caja
5 *
6 * Copyright (C) 2002 Sun Microsystems, Inc.
7 *
8 * Caja is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
12 *
13 * Caja is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public
19 * License along with this program; if not, write to the
20 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 *
23 * Author: Dave Camp <dave@ximian.com>
24 * XDS support: Benedikt Meurer <benny@xfce.org> (adapted by Amos Brocco <amos.brocco@unifr.ch>)
25 */
26
27 /* caja-tree-view-drag-dest.c: Handles drag and drop for treeviews which
28 * contain a hierarchy of files
29 */
30
31 #include <config.h>
32 #include <stdio.h>
33 #include <string.h>
34
35 #include <gtk/gtk.h>
36
37 #include <eel/eel-gtk-macros.h>
38
39 #include "caja-tree-view-drag-dest.h"
40 #include "caja-file-dnd.h"
41 #include "caja-file-changes-queue.h"
42 #include "caja-icon-dnd.h"
43 #include "caja-link.h"
44 #include "caja-marshal.h"
45 #include "caja-debug-log.h"
46
47 #define AUTO_SCROLL_MARGIN 20
48
49 #define HOVER_EXPAND_TIMEOUT 1
50
51 struct _CajaTreeViewDragDestDetails
52 {
53 GtkTreeView *tree_view;
54
55 gboolean drop_occurred;
56
57 gboolean have_drag_data;
58 guint drag_type;
59 GtkSelectionData *drag_data;
60 GList *drag_list;
61
62 guint highlight_id;
63 guint scroll_id;
64 guint expand_id;
65
66 char *direct_save_uri;
67 };
68
69 enum
70 {
71 GET_ROOT_URI,
72 GET_FILE_FOR_PATH,
73 MOVE_COPY_ITEMS,
74 HANDLE_NETSCAPE_URL,
75 HANDLE_URI_LIST,
76 HANDLE_TEXT,
77 HANDLE_RAW,
78 LAST_SIGNAL
79 };
80
81 static guint signals[LAST_SIGNAL] = { 0 };
82
83 G_DEFINE_TYPE (CajaTreeViewDragDest, caja_tree_view_drag_dest,
84 G_TYPE_OBJECT);
85
86 #define parent_class caja_tree_view_drag_dest_parent_class
87
88 static const GtkTargetEntry drag_types [] =
89 {
90 { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST },
91 /* prefer "_NETSCAPE_URL" over "text/uri-list" to satisfy web browsers. */
92 { CAJA_ICON_DND_NETSCAPE_URL_TYPE, 0, CAJA_ICON_DND_NETSCAPE_URL },
93 { CAJA_ICON_DND_URI_LIST_TYPE, 0, CAJA_ICON_DND_URI_LIST },
94 { CAJA_ICON_DND_KEYWORD_TYPE, 0, CAJA_ICON_DND_KEYWORD },
95 { CAJA_ICON_DND_XDNDDIRECTSAVE_TYPE, 0, CAJA_ICON_DND_XDNDDIRECTSAVE }, /* XDS Protocol Type */
96 { CAJA_ICON_DND_RAW_TYPE, 0, CAJA_ICON_DND_RAW }
97 };
98
99
100 static void
gtk_tree_view_vertical_autoscroll(GtkTreeView * tree_view)101 gtk_tree_view_vertical_autoscroll (GtkTreeView *tree_view)
102 {
103 GdkRectangle visible_rect;
104 GtkAdjustment *vadjustment;
105 GdkDisplay *display;
106 GdkSeat *seat;
107 GdkDevice *pointer;
108 GdkWindow *window;
109 int y;
110 int offset;
111 float value;
112
113 window = gtk_tree_view_get_bin_window (tree_view);
114
115 vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE(tree_view));
116
117 display = gtk_widget_get_display (GTK_WIDGET (tree_view));
118 seat = gdk_display_get_default_seat (display);
119 pointer = gdk_seat_get_pointer (seat);
120 gdk_window_get_device_position (window, pointer,
121 NULL, &y, NULL);
122
123 y += gtk_adjustment_get_value (vadjustment);
124
125 gtk_tree_view_get_visible_rect (tree_view, &visible_rect);
126
127 offset = y - (visible_rect.y + 2 * AUTO_SCROLL_MARGIN);
128 if (offset > 0)
129 {
130 offset = y - (visible_rect.y + visible_rect.height - 2 * AUTO_SCROLL_MARGIN);
131 if (offset < 0)
132 {
133 return;
134 }
135 }
136
137 value = CLAMP (gtk_adjustment_get_value (vadjustment) + offset, 0.0,
138 gtk_adjustment_get_upper (vadjustment) - gtk_adjustment_get_page_size (vadjustment));
139 gtk_adjustment_set_value (vadjustment, value);
140 }
141
142 static int
scroll_timeout(gpointer data)143 scroll_timeout (gpointer data)
144 {
145 GtkTreeView *tree_view = GTK_TREE_VIEW (data);
146
147 gtk_tree_view_vertical_autoscroll (tree_view);
148
149 return TRUE;
150 }
151
152 static void
remove_scroll_timeout(CajaTreeViewDragDest * dest)153 remove_scroll_timeout (CajaTreeViewDragDest *dest)
154 {
155 if (dest->details->scroll_id)
156 {
157 g_source_remove (dest->details->scroll_id);
158 dest->details->scroll_id = 0;
159 }
160 }
161
162 static int
expand_timeout(gpointer data)163 expand_timeout (gpointer data)
164 {
165 GtkTreeView *tree_view;
166 GtkTreePath *drop_path;
167
168 tree_view = GTK_TREE_VIEW (data);
169
170 gtk_tree_view_get_drag_dest_row (tree_view, &drop_path, NULL);
171
172 if (drop_path)
173 {
174 gtk_tree_view_expand_row (tree_view, drop_path, FALSE);
175 gtk_tree_path_free (drop_path);
176 }
177
178 return FALSE;
179 }
180
181 static void
remove_expand_timeout(CajaTreeViewDragDest * dest)182 remove_expand_timeout (CajaTreeViewDragDest *dest)
183 {
184 if (dest->details->expand_id)
185 {
186 g_source_remove (dest->details->expand_id);
187 dest->details->expand_id = 0;
188 }
189 }
190
191 static gboolean
highlight_draw(GtkWidget * widget,cairo_t * cr,gpointer data)192 highlight_draw (GtkWidget *widget,
193 cairo_t *cr,
194 gpointer data)
195 {
196 GdkWindow *bin_window;
197 int width;
198 int height;
199 GtkStyleContext *style;
200
201 /* FIXMEchpe: is bin window right here??? */
202 bin_window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget));
203
204 width = gdk_window_get_width(bin_window);
205 height = gdk_window_get_height(bin_window);
206
207 style = gtk_widget_get_style_context (widget);
208
209 gtk_style_context_save (style);
210 gtk_style_context_add_class (style, "treeview-drop-indicator");
211
212 gtk_render_focus (style,
213 cr,
214 0, 0, width, height);
215
216 gtk_style_context_restore (style);
217
218 return FALSE;
219 }
220
221 static void
set_widget_highlight(CajaTreeViewDragDest * dest,gboolean highlight)222 set_widget_highlight (CajaTreeViewDragDest *dest, gboolean highlight)
223 {
224 if (!highlight && dest->details->highlight_id)
225 {
226 g_signal_handler_disconnect (dest->details->tree_view,
227 dest->details->highlight_id);
228 dest->details->highlight_id = 0;
229 gtk_widget_queue_draw (GTK_WIDGET (dest->details->tree_view));
230 }
231
232 if (highlight && !dest->details->highlight_id)
233 {
234 dest->details->highlight_id =
235 g_signal_connect_object (dest->details->tree_view,
236 "draw",
237 G_CALLBACK (highlight_draw),
238 dest, 0);
239 gtk_widget_queue_draw (GTK_WIDGET (dest->details->tree_view));
240 }
241 }
242
243 static void
set_drag_dest_row(CajaTreeViewDragDest * dest,GtkTreePath * path)244 set_drag_dest_row (CajaTreeViewDragDest *dest,
245 GtkTreePath *path)
246 {
247 if (path)
248 {
249 set_widget_highlight (dest, FALSE);
250 gtk_tree_view_set_drag_dest_row
251 (dest->details->tree_view,
252 path,
253 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
254 }
255 else
256 {
257 set_widget_highlight (dest, TRUE);
258 gtk_tree_view_set_drag_dest_row (dest->details->tree_view,
259 NULL,
260 0);
261 }
262 }
263
264 static void
clear_drag_dest_row(CajaTreeViewDragDest * dest)265 clear_drag_dest_row (CajaTreeViewDragDest *dest)
266 {
267 gtk_tree_view_set_drag_dest_row (dest->details->tree_view, NULL, 0);
268 set_widget_highlight (dest, FALSE);
269 }
270
271 static gboolean
get_drag_data(CajaTreeViewDragDest * dest,GdkDragContext * context,guint32 time)272 get_drag_data (CajaTreeViewDragDest *dest,
273 GdkDragContext *context,
274 guint32 time)
275 {
276 GdkAtom target;
277
278 target = gtk_drag_dest_find_target (GTK_WIDGET (dest->details->tree_view),
279 context,
280 NULL);
281
282 if (target == GDK_NONE)
283 {
284 return FALSE;
285 }
286
287 if (target == gdk_atom_intern (CAJA_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE) &&
288 !dest->details->drop_occurred)
289 {
290 dest->details->drag_type = CAJA_ICON_DND_XDNDDIRECTSAVE;
291 dest->details->have_drag_data = TRUE;
292 return TRUE;
293 }
294
295 gtk_drag_get_data (GTK_WIDGET (dest->details->tree_view),
296 context, target, time);
297
298 return TRUE;
299 }
300
301 static void
free_drag_data(CajaTreeViewDragDest * dest)302 free_drag_data (CajaTreeViewDragDest *dest)
303 {
304 dest->details->have_drag_data = FALSE;
305
306 if (dest->details->drag_data)
307 {
308 gtk_selection_data_free (dest->details->drag_data);
309 dest->details->drag_data = NULL;
310 }
311
312 if (dest->details->drag_list)
313 {
314 caja_drag_destroy_selection_list (dest->details->drag_list);
315 dest->details->drag_list = NULL;
316 }
317
318 g_free (dest->details->direct_save_uri);
319 dest->details->direct_save_uri = NULL;
320 }
321
322 static char *
get_root_uri(CajaTreeViewDragDest * dest)323 get_root_uri (CajaTreeViewDragDest *dest)
324 {
325 char *uri;
326
327 g_signal_emit (dest, signals[GET_ROOT_URI], 0, &uri);
328
329 return uri;
330 }
331
332 static CajaFile *
file_for_path(CajaTreeViewDragDest * dest,GtkTreePath * path)333 file_for_path (CajaTreeViewDragDest *dest, GtkTreePath *path)
334 {
335 CajaFile *file;
336
337 if (path)
338 {
339 g_signal_emit (dest, signals[GET_FILE_FOR_PATH], 0, path, &file);
340 }
341 else
342 {
343 char *uri;
344
345 uri = get_root_uri (dest);
346
347 file = NULL;
348 if (uri != NULL)
349 {
350 file = caja_file_get_by_uri (uri);
351 }
352
353 g_free (uri);
354 }
355
356 return file;
357 }
358
359 static GtkTreePath *
get_drop_path(CajaTreeViewDragDest * dest,GtkTreePath * path)360 get_drop_path (CajaTreeViewDragDest *dest,
361 GtkTreePath *path)
362 {
363 CajaFile *file;
364 GtkTreePath *ret;
365
366 if (!path || !dest->details->have_drag_data)
367 {
368 return NULL;
369 }
370
371 ret = gtk_tree_path_copy (path);
372 file = file_for_path (dest, ret);
373
374 /* Go up the tree until we find a file that can accept a drop */
375 while (file == NULL /* dummy row */ ||
376 !caja_drag_can_accept_info (file,
377 dest->details->drag_type,
378 dest->details->drag_list))
379 {
380 if (gtk_tree_path_get_depth (ret) == 1)
381 {
382 gtk_tree_path_free (ret);
383 ret = NULL;
384 break;
385 }
386 else
387 {
388 gtk_tree_path_up (ret);
389
390 caja_file_unref (file);
391 file = file_for_path (dest, ret);
392 }
393 }
394 caja_file_unref (file);
395
396 return ret;
397 }
398
399 static char *
get_drop_target_uri_for_path(CajaTreeViewDragDest * dest,GtkTreePath * path)400 get_drop_target_uri_for_path (CajaTreeViewDragDest *dest,
401 GtkTreePath *path)
402 {
403 CajaFile *file;
404 char *target;
405
406 file = file_for_path (dest, path);
407 if (file == NULL)
408 {
409 return NULL;
410 }
411
412 target = caja_file_get_drop_target_uri (file);
413 caja_file_unref (file);
414
415 return target;
416 }
417
418 static guint
get_drop_action(CajaTreeViewDragDest * dest,GdkDragContext * context,GtkTreePath * path)419 get_drop_action (CajaTreeViewDragDest *dest,
420 GdkDragContext *context,
421 GtkTreePath *path)
422 {
423 char *drop_target;
424 int action;
425
426 if (!dest->details->have_drag_data ||
427 (dest->details->drag_type == CAJA_ICON_DND_MATE_ICON_LIST &&
428 dest->details->drag_list == NULL))
429 {
430 return 0;
431 }
432
433 switch (dest->details->drag_type)
434 {
435 case CAJA_ICON_DND_MATE_ICON_LIST :
436 drop_target = get_drop_target_uri_for_path (dest, path);
437
438 if (!drop_target)
439 {
440 return 0;
441 }
442
443 caja_drag_default_drop_action_for_icons
444 (context,
445 drop_target,
446 dest->details->drag_list,
447 &action);
448
449 g_free (drop_target);
450
451 return action;
452
453 case CAJA_ICON_DND_NETSCAPE_URL:
454 drop_target = get_drop_target_uri_for_path (dest, path);
455
456 if (drop_target == NULL)
457 {
458 return 0;
459 }
460
461 action = caja_drag_default_drop_action_for_netscape_url (context);
462
463 g_free (drop_target);
464
465 return action;
466
467 case CAJA_ICON_DND_URI_LIST :
468 drop_target = get_drop_target_uri_for_path (dest, path);
469
470 if (drop_target == NULL)
471 {
472 return 0;
473 }
474
475 g_free (drop_target);
476
477 return gdk_drag_context_get_suggested_action (context);
478
479 case CAJA_ICON_DND_TEXT:
480 case CAJA_ICON_DND_RAW:
481 case CAJA_ICON_DND_XDNDDIRECTSAVE:
482 return GDK_ACTION_COPY;
483
484 case CAJA_ICON_DND_KEYWORD:
485
486 if (!path)
487 {
488 return 0;
489 }
490
491 return GDK_ACTION_COPY;
492 }
493
494 return 0;
495 }
496
497 static gboolean
drag_motion_callback(GtkWidget * widget,GdkDragContext * context,int x,int y,guint32 time,gpointer data)498 drag_motion_callback (GtkWidget *widget,
499 GdkDragContext *context,
500 int x,
501 int y,
502 guint32 time,
503 gpointer data)
504 {
505 CajaTreeViewDragDest *dest;
506 GtkTreePath *path;
507 GtkTreePath *drop_path, *old_drop_path;
508 GtkTreeIter drop_iter;
509 GtkTreeViewDropPosition pos;
510 GdkWindow *bin_window;
511 guint action;
512 gboolean res = TRUE;
513
514 dest = CAJA_TREE_VIEW_DRAG_DEST (data);
515
516 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
517 x, y, &path, &pos);
518
519
520 if (!dest->details->have_drag_data)
521 {
522 res = get_drag_data (dest, context, time);
523 }
524
525 if (!res)
526 {
527 return FALSE;
528 }
529
530 drop_path = get_drop_path (dest, path);
531
532 action = 0;
533 bin_window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget));
534 if (bin_window != NULL)
535 {
536 int bin_x, bin_y;
537 gdk_window_get_position (bin_window, &bin_x, &bin_y);
538 if (bin_y <= y)
539 {
540 /* ignore drags on the header */
541 action = get_drop_action (dest, context, drop_path);
542 }
543 }
544
545 gtk_tree_view_get_drag_dest_row (GTK_TREE_VIEW (widget), &old_drop_path,
546 NULL);
547
548 if (action)
549 {
550 GtkTreeModel *model;
551
552 set_drag_dest_row (dest, drop_path);
553 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
554 if (drop_path == NULL || (old_drop_path != NULL &&
555 gtk_tree_path_compare (old_drop_path, drop_path) != 0))
556 {
557 remove_expand_timeout (dest);
558 }
559 if (dest->details->expand_id == 0 && drop_path != NULL)
560 {
561 gtk_tree_model_get_iter (model, &drop_iter, drop_path);
562 if (gtk_tree_model_iter_has_child (model, &drop_iter))
563 {
564 dest->details->expand_id = g_timeout_add_seconds (HOVER_EXPAND_TIMEOUT,
565 expand_timeout,
566 dest->details->tree_view);
567 }
568 }
569 }
570 else
571 {
572 clear_drag_dest_row (dest);
573 remove_expand_timeout (dest);
574 }
575
576 if (path)
577 {
578 gtk_tree_path_free (path);
579 }
580
581 if (drop_path)
582 {
583 gtk_tree_path_free (drop_path);
584 }
585
586 if (old_drop_path)
587 {
588 gtk_tree_path_free (old_drop_path);
589 }
590
591 if (dest->details->scroll_id == 0)
592 {
593 dest->details->scroll_id =
594 g_timeout_add (150,
595 scroll_timeout,
596 dest->details->tree_view);
597 }
598
599 gdk_drag_status (context, action, time);
600
601 return TRUE;
602 }
603
604 static void
drag_leave_callback(GtkWidget * widget,GdkDragContext * context,guint32 time,gpointer data)605 drag_leave_callback (GtkWidget *widget,
606 GdkDragContext *context,
607 guint32 time,
608 gpointer data)
609 {
610 CajaTreeViewDragDest *dest;
611
612 dest = CAJA_TREE_VIEW_DRAG_DEST (data);
613
614 clear_drag_dest_row (dest);
615
616 free_drag_data (dest);
617
618 remove_scroll_timeout (dest);
619 remove_expand_timeout (dest);
620 }
621
622 static char *
get_drop_target_uri_at_pos(CajaTreeViewDragDest * dest,int x,int y)623 get_drop_target_uri_at_pos (CajaTreeViewDragDest *dest, int x, int y)
624 {
625 char *drop_target;
626 GtkTreePath *path;
627 GtkTreePath *drop_path;
628 GtkTreeViewDropPosition pos;
629
630 gtk_tree_view_get_dest_row_at_pos (dest->details->tree_view, x, y,
631 &path, &pos);
632
633 drop_path = get_drop_path (dest, path);
634
635 drop_target = get_drop_target_uri_for_path (dest, drop_path);
636
637 if (path != NULL)
638 {
639 gtk_tree_path_free (path);
640 }
641
642 if (drop_path != NULL)
643 {
644 gtk_tree_path_free (drop_path);
645 }
646
647 return drop_target;
648 }
649
650 static void
receive_uris(CajaTreeViewDragDest * dest,GdkDragContext * context,GList * source_uris,int x,int y)651 receive_uris (CajaTreeViewDragDest *dest,
652 GdkDragContext *context,
653 GList *source_uris,
654 int x, int y)
655 {
656 char *drop_target;
657 GdkDragAction action, real_action;
658
659 drop_target = get_drop_target_uri_at_pos (dest, x, y);
660 g_assert (drop_target != NULL);
661
662 real_action = gdk_drag_context_get_selected_action (context);
663
664 if (real_action == GDK_ACTION_ASK)
665 {
666 if (caja_drag_selection_includes_special_link (dest->details->drag_list))
667 {
668 /* We only want to move the trash */
669 action = GDK_ACTION_MOVE;
670 }
671 else
672 {
673 action = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK;
674 }
675 real_action = caja_drag_drop_action_ask
676 (GTK_WIDGET (dest->details->tree_view), action);
677 }
678
679 /* We only want to copy external uris */
680 if (dest->details->drag_type == CAJA_ICON_DND_URI_LIST)
681 {
682 action = GDK_ACTION_COPY;
683 }
684
685 if (real_action > 0)
686 {
687 if (!caja_drag_uris_local (drop_target, source_uris)
688 || real_action != GDK_ACTION_MOVE)
689 {
690 g_signal_emit (dest, signals[MOVE_COPY_ITEMS], 0,
691 source_uris,
692 drop_target,
693 real_action,
694 x, y);
695 }
696 }
697
698 g_free (drop_target);
699 }
700
701 static void
receive_dropped_icons(CajaTreeViewDragDest * dest,GdkDragContext * context,int x,int y)702 receive_dropped_icons (CajaTreeViewDragDest *dest,
703 GdkDragContext *context,
704 int x, int y)
705 {
706 GList *source_uris;
707 GList *l;
708
709 /* FIXME: ignore local only moves */
710
711 if (!dest->details->drag_list)
712 {
713 return;
714 }
715
716 source_uris = NULL;
717 for (l = dest->details->drag_list; l != NULL; l = l->next)
718 {
719 source_uris = g_list_prepend (source_uris,
720 ((CajaDragSelectionItem *)l->data)->uri);
721 }
722
723 source_uris = g_list_reverse (source_uris);
724
725 receive_uris (dest, context, source_uris, x, y);
726
727 g_list_free (source_uris);
728 }
729
730 static void
receive_dropped_uri_list(CajaTreeViewDragDest * dest,GdkDragContext * context,int x,int y)731 receive_dropped_uri_list (CajaTreeViewDragDest *dest,
732 GdkDragContext *context,
733 int x, int y)
734 {
735 char *drop_target;
736
737 if (!dest->details->drag_data)
738 {
739 return;
740 }
741
742 drop_target = get_drop_target_uri_at_pos (dest, x, y);
743 g_assert (drop_target != NULL);
744
745 g_signal_emit (dest, signals[HANDLE_URI_LIST], 0,
746 (char*) gtk_selection_data_get_data (dest->details->drag_data),
747 drop_target,
748 gdk_drag_context_get_selected_action (context),
749 x, y);
750
751 g_free (drop_target);
752 }
753
754 static void
receive_dropped_text(CajaTreeViewDragDest * dest,GdkDragContext * context,int x,int y)755 receive_dropped_text (CajaTreeViewDragDest *dest,
756 GdkDragContext *context,
757 int x, int y)
758 {
759 char *drop_target;
760 char *text;
761
762 if (!dest->details->drag_data)
763 {
764 return;
765 }
766
767 drop_target = get_drop_target_uri_at_pos (dest, x, y);
768 g_assert (drop_target != NULL);
769
770 text = gtk_selection_data_get_text (dest->details->drag_data);
771 g_signal_emit (dest, signals[HANDLE_TEXT], 0,
772 (char *) text, drop_target,
773 gdk_drag_context_get_selected_action (context),
774 x, y);
775
776 g_free (text);
777 g_free (drop_target);
778 }
779
780 static void
receive_dropped_raw(CajaTreeViewDragDest * dest,const char * raw_data,int length,GdkDragContext * context,int x,int y)781 receive_dropped_raw (CajaTreeViewDragDest *dest,
782 const char *raw_data, int length,
783 GdkDragContext *context,
784 int x, int y)
785 {
786 char *drop_target;
787
788 if (!dest->details->drag_data)
789 {
790 return;
791 }
792
793 drop_target = get_drop_target_uri_at_pos (dest, x, y);
794 g_assert (drop_target != NULL);
795
796 g_signal_emit (dest, signals[HANDLE_RAW], 0,
797 raw_data, length, drop_target,
798 dest->details->direct_save_uri,
799 gdk_drag_context_get_selected_action (context),
800 x, y);
801
802 g_free (drop_target);
803 }
804
805 static void
receive_dropped_netscape_url(CajaTreeViewDragDest * dest,GdkDragContext * context,int x,int y)806 receive_dropped_netscape_url (CajaTreeViewDragDest *dest,
807 GdkDragContext *context,
808 int x, int y)
809 {
810 char *drop_target;
811
812 if (!dest->details->drag_data)
813 {
814 return;
815 }
816
817 drop_target = get_drop_target_uri_at_pos (dest, x, y);
818 g_assert (drop_target != NULL);
819
820 g_signal_emit (dest, signals[HANDLE_NETSCAPE_URL], 0,
821 (char*) gtk_selection_data_get_data (dest->details->drag_data),
822 drop_target,
823 gdk_drag_context_get_selected_action (context),
824 x, y);
825
826 g_free (drop_target);
827 }
828
829 static void
receive_dropped_keyword(CajaTreeViewDragDest * dest,GdkDragContext * context,int x,int y)830 receive_dropped_keyword (CajaTreeViewDragDest *dest,
831 GdkDragContext *context,
832 int x, int y)
833 {
834 char *drop_target_uri;
835 CajaFile *drop_target_file;
836
837 if (!dest->details->drag_data)
838 {
839 return;
840 }
841
842 drop_target_uri = get_drop_target_uri_at_pos (dest, x, y);
843 g_assert (drop_target_uri != NULL);
844
845 drop_target_file = caja_file_get_by_uri (drop_target_uri);
846
847 if (drop_target_file != NULL)
848 {
849 caja_drag_file_receive_dropped_keyword (drop_target_file,
850 (char *) gtk_selection_data_get_data (dest->details->drag_data));
851 caja_file_unref (drop_target_file);
852 }
853
854 g_free (drop_target_uri);
855 }
856
857 static gboolean
receive_xds(CajaTreeViewDragDest * dest,GtkWidget * widget,guint32 time,GdkDragContext * context,int x,int y)858 receive_xds (CajaTreeViewDragDest *dest,
859 GtkWidget *widget,
860 guint32 time,
861 GdkDragContext *context,
862 int x, int y)
863 {
864 GFile *location;
865 const guchar *selection_data;
866 gint selection_format;
867 gint selection_length;
868
869 selection_data = gtk_selection_data_get_data (dest->details->drag_data);
870 selection_format = gtk_selection_data_get_format (dest->details->drag_data);
871 selection_length = gtk_selection_data_get_length (dest->details->drag_data);
872
873 if (selection_format == 8
874 && selection_length == 1
875 && selection_data[0] == 'F')
876 {
877 gtk_drag_get_data (widget, context,
878 gdk_atom_intern (CAJA_ICON_DND_RAW_TYPE,
879 FALSE),
880 time);
881 return FALSE;
882 }
883 else if (selection_format == 8
884 && selection_length == 1
885 && selection_data[0] == 'S')
886 {
887 g_assert (dest->details->direct_save_uri != NULL);
888 location = g_file_new_for_uri (dest->details->direct_save_uri);
889
890 caja_file_changes_queue_file_added (location);
891 caja_file_changes_consume_changes (TRUE);
892
893 g_object_unref (location);
894 }
895 return TRUE;
896 }
897
898
899 static gboolean
drag_data_received_callback(GtkWidget * widget,GdkDragContext * context,int x,int y,GtkSelectionData * selection_data,guint info,guint32 time,gpointer data)900 drag_data_received_callback (GtkWidget *widget,
901 GdkDragContext *context,
902 int x,
903 int y,
904 GtkSelectionData *selection_data,
905 guint info,
906 guint32 time,
907 gpointer data)
908 {
909 CajaTreeViewDragDest *dest;
910 gboolean success, finished;
911
912 dest = CAJA_TREE_VIEW_DRAG_DEST (data);
913
914 if (!dest->details->have_drag_data)
915 {
916 dest->details->have_drag_data = TRUE;
917 dest->details->drag_type = info;
918 dest->details->drag_data =
919 gtk_selection_data_copy (selection_data);
920 if (info == CAJA_ICON_DND_MATE_ICON_LIST)
921 {
922 dest->details->drag_list =
923 caja_drag_build_selection_list (selection_data);
924 }
925 }
926
927 if (dest->details->drop_occurred)
928 {
929 success = FALSE;
930 finished = TRUE;
931 switch (info)
932 {
933 case CAJA_ICON_DND_MATE_ICON_LIST :
934 receive_dropped_icons (dest, context, x, y);
935 success = TRUE;
936 break;
937 case CAJA_ICON_DND_NETSCAPE_URL :
938 receive_dropped_netscape_url (dest, context, x, y);
939 success = TRUE;
940 break;
941 case CAJA_ICON_DND_URI_LIST :
942 receive_dropped_uri_list (dest, context, x, y);
943 success = TRUE;
944 break;
945 case CAJA_ICON_DND_TEXT:
946 receive_dropped_text (dest, context, x, y);
947 success = TRUE;
948 break;
949 case CAJA_ICON_DND_RAW:
950 {
951 const char *tmp;
952 int length;
953
954 length = gtk_selection_data_get_length (selection_data);
955 tmp = gtk_selection_data_get_data (selection_data);
956 receive_dropped_raw (dest, tmp, length, context, x, y);
957 success = TRUE;
958 break;
959 }
960 case CAJA_ICON_DND_KEYWORD:
961 receive_dropped_keyword (dest, context, x, y);
962 success = TRUE;
963 break;
964 case CAJA_ICON_DND_XDNDDIRECTSAVE:
965 finished = receive_xds (dest, widget, time, context, x, y);
966 success = TRUE;
967 break;
968 }
969
970 if (finished)
971 {
972 dest->details->drop_occurred = FALSE;
973 free_drag_data (dest);
974 gtk_drag_finish (context, success, FALSE, time);
975 }
976 }
977
978 /* appease GtkTreeView by preventing its drag_data_receive
979 * from being called */
980 g_signal_stop_emission_by_name (dest->details->tree_view,
981 "drag_data_received");
982
983 return TRUE;
984 }
985
986 static char *
get_direct_save_filename(GdkDragContext * context)987 get_direct_save_filename (GdkDragContext *context)
988 {
989 guchar *prop_text;
990 gint prop_len;
991
992 if (!gdk_property_get (gdk_drag_context_get_source_window (context), gdk_atom_intern (CAJA_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
993 gdk_atom_intern ("text/plain", FALSE), 0, 1024, FALSE, NULL, NULL,
994 &prop_len, &prop_text))
995 {
996 return NULL;
997 }
998
999 /* Zero-terminate the string */
1000 prop_text = g_realloc (prop_text, prop_len + 1);
1001 prop_text[prop_len] = '\0';
1002
1003 /* Verify that the file name provided by the source is valid */
1004 if (*prop_text == '\0' ||
1005 strchr ((const gchar *) prop_text, G_DIR_SEPARATOR) != NULL)
1006 {
1007 caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
1008 "Invalid filename provided by XDS drag site");
1009 g_free (prop_text);
1010 return NULL;
1011 }
1012
1013 return prop_text;
1014 }
1015
1016 static gboolean
set_direct_save_uri(CajaTreeViewDragDest * dest,GdkDragContext * context,int x,int y)1017 set_direct_save_uri (CajaTreeViewDragDest *dest,
1018 GdkDragContext *context,
1019 int x, int y)
1020 {
1021 char *drop_uri;
1022 char *uri;
1023
1024 g_assert (dest->details->direct_save_uri == NULL);
1025
1026 uri = NULL;
1027
1028 drop_uri = get_drop_target_uri_at_pos (dest, x, y);
1029 if (drop_uri != NULL)
1030 {
1031 char *filename;
1032
1033 filename = get_direct_save_filename (context);
1034 if (filename != NULL)
1035 {
1036 GFile *base, *child;
1037
1038 /* Resolve relative path */
1039 base = g_file_new_for_uri (drop_uri);
1040 child = g_file_get_child (base, filename);
1041 uri = g_file_get_uri (child);
1042
1043 g_object_unref (base);
1044 g_object_unref (child);
1045 g_free (filename);
1046
1047 /* Change the property */
1048 gdk_property_change (gdk_drag_context_get_source_window (context),
1049 gdk_atom_intern (CAJA_ICON_DND_XDNDDIRECTSAVE_TYPE, FALSE),
1050 gdk_atom_intern ("text/plain", FALSE), 8,
1051 GDK_PROP_MODE_REPLACE, (const guchar *) uri,
1052 strlen (uri));
1053
1054 dest->details->direct_save_uri = uri;
1055 }
1056 else
1057 {
1058 caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
1059 "Invalid filename provided by XDS drag site");
1060 }
1061 }
1062 else
1063 {
1064 caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER,
1065 "Could not retrieve XDS drop destination");
1066 }
1067
1068 return uri != NULL;
1069 }
1070
1071 static gboolean
drag_drop_callback(GtkWidget * widget,GdkDragContext * context,int x,int y,guint32 time,gpointer data)1072 drag_drop_callback (GtkWidget *widget,
1073 GdkDragContext *context,
1074 int x,
1075 int y,
1076 guint32 time,
1077 gpointer data)
1078 {
1079 CajaTreeViewDragDest *dest;
1080 guint info;
1081 GdkAtom target;
1082
1083 dest = CAJA_TREE_VIEW_DRAG_DEST (data);
1084
1085 target = gtk_drag_dest_find_target (GTK_WIDGET (dest->details->tree_view),
1086 context,
1087 NULL);
1088 if (target == GDK_NONE)
1089 {
1090 return FALSE;
1091 }
1092
1093 info = dest->details->drag_type;
1094
1095 if (info == CAJA_ICON_DND_XDNDDIRECTSAVE)
1096 {
1097 /* We need to set this or get_drop_path will fail, and it
1098 was unset by drag_leave_callback */
1099 dest->details->have_drag_data = TRUE;
1100 if (!set_direct_save_uri (dest, context, x, y))
1101 {
1102 return FALSE;
1103 }
1104 dest->details->have_drag_data = FALSE;
1105 }
1106
1107 dest->details->drop_occurred = TRUE;
1108
1109 get_drag_data (dest, context, time);
1110 remove_scroll_timeout (dest);
1111 remove_expand_timeout (dest);
1112 clear_drag_dest_row (dest);
1113
1114 return TRUE;
1115 }
1116
1117 static void
tree_view_weak_notify(gpointer user_data,GObject * object)1118 tree_view_weak_notify (gpointer user_data,
1119 GObject *object)
1120 {
1121 CajaTreeViewDragDest *dest;
1122
1123 dest = CAJA_TREE_VIEW_DRAG_DEST (user_data);
1124
1125 remove_scroll_timeout (dest);
1126 remove_expand_timeout (dest);
1127
1128 dest->details->tree_view = NULL;
1129 }
1130
1131 static void
caja_tree_view_drag_dest_dispose(GObject * object)1132 caja_tree_view_drag_dest_dispose (GObject *object)
1133 {
1134 CajaTreeViewDragDest *dest;
1135
1136 dest = CAJA_TREE_VIEW_DRAG_DEST (object);
1137
1138 if (dest->details->tree_view)
1139 {
1140 g_object_weak_unref (G_OBJECT (dest->details->tree_view),
1141 tree_view_weak_notify,
1142 dest);
1143 }
1144
1145 remove_scroll_timeout (dest);
1146 remove_expand_timeout (dest);
1147
1148 EEL_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
1149 }
1150
1151 static void
caja_tree_view_drag_dest_finalize(GObject * object)1152 caja_tree_view_drag_dest_finalize (GObject *object)
1153 {
1154 CajaTreeViewDragDest *dest;
1155
1156 dest = CAJA_TREE_VIEW_DRAG_DEST (object);
1157
1158 free_drag_data (dest);
1159
1160 g_free (dest->details);
1161
1162 EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
1163 }
1164
1165 static void
caja_tree_view_drag_dest_init(CajaTreeViewDragDest * dest)1166 caja_tree_view_drag_dest_init (CajaTreeViewDragDest *dest)
1167 {
1168 dest->details = g_new0 (CajaTreeViewDragDestDetails, 1);
1169 }
1170
1171 static void
caja_tree_view_drag_dest_class_init(CajaTreeViewDragDestClass * class)1172 caja_tree_view_drag_dest_class_init (CajaTreeViewDragDestClass *class)
1173 {
1174 GObjectClass *gobject_class;
1175
1176 gobject_class = G_OBJECT_CLASS (class);
1177
1178 gobject_class->dispose = caja_tree_view_drag_dest_dispose;
1179 gobject_class->finalize = caja_tree_view_drag_dest_finalize;
1180
1181 signals[GET_ROOT_URI] =
1182 g_signal_new ("get_root_uri",
1183 G_TYPE_FROM_CLASS (class),
1184 G_SIGNAL_RUN_LAST,
1185 G_STRUCT_OFFSET (CajaTreeViewDragDestClass,
1186 get_root_uri),
1187 NULL, NULL,
1188 caja_marshal_STRING__VOID,
1189 G_TYPE_STRING, 0);
1190 signals[GET_FILE_FOR_PATH] =
1191 g_signal_new ("get_file_for_path",
1192 G_TYPE_FROM_CLASS (class),
1193 G_SIGNAL_RUN_LAST,
1194 G_STRUCT_OFFSET (CajaTreeViewDragDestClass,
1195 get_file_for_path),
1196 NULL, NULL,
1197 caja_marshal_OBJECT__BOXED,
1198 CAJA_TYPE_FILE, 1,
1199 GTK_TYPE_TREE_PATH);
1200 signals[MOVE_COPY_ITEMS] =
1201 g_signal_new ("move_copy_items",
1202 G_TYPE_FROM_CLASS (class),
1203 G_SIGNAL_RUN_LAST,
1204 G_STRUCT_OFFSET (CajaTreeViewDragDestClass,
1205 move_copy_items),
1206 NULL, NULL,
1207
1208 caja_marshal_VOID__POINTER_STRING_ENUM_INT_INT,
1209 G_TYPE_NONE, 5,
1210 G_TYPE_POINTER,
1211 G_TYPE_STRING,
1212 GDK_TYPE_DRAG_ACTION,
1213 G_TYPE_INT,
1214 G_TYPE_INT);
1215 signals[HANDLE_NETSCAPE_URL] =
1216 g_signal_new ("handle_netscape_url",
1217 G_TYPE_FROM_CLASS (class),
1218 G_SIGNAL_RUN_LAST,
1219 G_STRUCT_OFFSET (CajaTreeViewDragDestClass,
1220 handle_netscape_url),
1221 NULL, NULL,
1222 caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
1223 G_TYPE_NONE, 5,
1224 G_TYPE_STRING,
1225 G_TYPE_STRING,
1226 GDK_TYPE_DRAG_ACTION,
1227 G_TYPE_INT,
1228 G_TYPE_INT);
1229 signals[HANDLE_URI_LIST] =
1230 g_signal_new ("handle_uri_list",
1231 G_TYPE_FROM_CLASS (class),
1232 G_SIGNAL_RUN_LAST,
1233 G_STRUCT_OFFSET (CajaTreeViewDragDestClass,
1234 handle_uri_list),
1235 NULL, NULL,
1236 caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
1237 G_TYPE_NONE, 5,
1238 G_TYPE_STRING,
1239 G_TYPE_STRING,
1240 GDK_TYPE_DRAG_ACTION,
1241 G_TYPE_INT,
1242 G_TYPE_INT);
1243 signals[HANDLE_TEXT] =
1244 g_signal_new ("handle_text",
1245 G_TYPE_FROM_CLASS (class),
1246 G_SIGNAL_RUN_LAST,
1247 G_STRUCT_OFFSET (CajaTreeViewDragDestClass,
1248 handle_text),
1249 NULL, NULL,
1250 caja_marshal_VOID__STRING_STRING_ENUM_INT_INT,
1251 G_TYPE_NONE, 5,
1252 G_TYPE_STRING,
1253 G_TYPE_STRING,
1254 GDK_TYPE_DRAG_ACTION,
1255 G_TYPE_INT,
1256 G_TYPE_INT);
1257 signals[HANDLE_RAW] =
1258 g_signal_new ("handle_raw",
1259 G_TYPE_FROM_CLASS (class),
1260 G_SIGNAL_RUN_LAST,
1261 G_STRUCT_OFFSET (CajaTreeViewDragDestClass,
1262 handle_raw),
1263 NULL, NULL,
1264 caja_marshal_VOID__POINTER_INT_STRING_STRING_ENUM_INT_INT,
1265 G_TYPE_NONE, 7,
1266 G_TYPE_POINTER,
1267 G_TYPE_INT,
1268 G_TYPE_STRING,
1269 G_TYPE_STRING,
1270 GDK_TYPE_DRAG_ACTION,
1271 G_TYPE_INT,
1272 G_TYPE_INT);
1273 }
1274
1275
1276
1277 CajaTreeViewDragDest *
caja_tree_view_drag_dest_new(GtkTreeView * tree_view)1278 caja_tree_view_drag_dest_new (GtkTreeView *tree_view)
1279 {
1280 CajaTreeViewDragDest *dest;
1281 GtkTargetList *targets;
1282
1283 dest = g_object_new (CAJA_TYPE_TREE_VIEW_DRAG_DEST, NULL);
1284
1285 dest->details->tree_view = tree_view;
1286 g_object_weak_ref (G_OBJECT (dest->details->tree_view),
1287 tree_view_weak_notify, dest);
1288
1289 gtk_drag_dest_set (GTK_WIDGET (tree_view),
1290 0, drag_types, G_N_ELEMENTS (drag_types),
1291 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK);
1292
1293 targets = gtk_drag_dest_get_target_list (GTK_WIDGET (tree_view));
1294 gtk_target_list_add_text_targets (targets, CAJA_ICON_DND_TEXT);
1295
1296 g_signal_connect_object (tree_view,
1297 "drag_motion",
1298 G_CALLBACK (drag_motion_callback),
1299 dest, 0);
1300 g_signal_connect_object (tree_view,
1301 "drag_leave",
1302 G_CALLBACK (drag_leave_callback),
1303 dest, 0);
1304 g_signal_connect_object (tree_view,
1305 "drag_drop",
1306 G_CALLBACK (drag_drop_callback),
1307 dest, 0);
1308 g_signal_connect_object (tree_view,
1309 "drag_data_received",
1310 G_CALLBACK (drag_data_received_callback),
1311 dest, 0);
1312
1313 return dest;
1314 }
1315