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