1 /* nautilus-list-view-dnd.c
2  *
3  * Copyright (C) 2015 Carlos Soriano <csoriano@gnome.org>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <config.h>
20 
21 #include "nautilus-list-view-dnd.h"
22 #include "nautilus-list-view-private.h"
23 
24 static GtkTargetList *source_target_list = NULL;
25 
26 static void
27 drag_info_data_free (NautilusListView *list_view);
28 
29 static void
drag_data_get_callback(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time,gpointer user_data)30 drag_data_get_callback (GtkWidget        *widget,
31                         GdkDragContext   *context,
32                         GtkSelectionData *selection_data,
33                         guint             info,
34                         guint             time,
35                         gpointer          user_data)
36 {
37     GtkTreeView *tree_view;
38     GtkTreeModel *model;
39     NautilusListView *list_view;
40 
41     tree_view = GTK_TREE_VIEW (widget);
42     list_view = NAUTILUS_LIST_VIEW (user_data);
43 
44     model = gtk_tree_view_get_model (tree_view);
45 
46     if (model == NULL)
47     {
48         return;
49     }
50 
51     if (list_view->details->drag_source_info == NULL ||
52         list_view->details->drag_source_info->selection_cache == NULL)
53     {
54         return;
55     }
56 
57     nautilus_drag_drag_data_get_from_cache (list_view->details->drag_source_info->selection_cache,
58                                             context, selection_data, info, time);
59 }
60 
61 static cairo_surface_t *
get_drag_surface(NautilusListView * view)62 get_drag_surface (NautilusListView *view)
63 {
64     GtkTreeModel *model;
65     GtkTreePath *path;
66     GtkTreeIter iter;
67     cairo_surface_t *ret;
68     GdkRectangle cell_area;
69 
70     ret = NULL;
71 
72     if (gtk_tree_view_get_path_at_pos (view->details->tree_view,
73                                        view->details->drag_x,
74                                        view->details->drag_y,
75                                        &path, NULL, NULL, NULL))
76     {
77         model = gtk_tree_view_get_model (view->details->tree_view);
78         gtk_tree_model_get_iter (model, &iter, path);
79         gtk_tree_model_get (model, &iter,
80                             nautilus_list_model_get_column_id_from_zoom_level (view->details->zoom_level),
81                             &ret,
82                             -1);
83     }
84 
85     gtk_tree_view_get_cell_area (view->details->tree_view,
86                                  path,
87                                  view->details->file_name_column,
88                                  &cell_area);
89 
90     gtk_tree_path_free (path);
91 
92     return ret;
93 }
94 
95 /* iteration glue struct */
96 typedef struct
97 {
98     NautilusListView *view;
99     NautilusDragEachSelectedItemDataGet iteratee;
100     gpointer iteratee_data;
101 } ListGetDataBinderContext;
102 
103 static void
item_get_data_binder(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)104 item_get_data_binder (GtkTreeModel *model,
105                       GtkTreePath  *path,
106                       GtkTreeIter  *iter,
107                       gpointer      data)
108 {
109     ListGetDataBinderContext *context = data;
110     NautilusFile *file;
111     GtkTreeView *treeview;
112     GtkTreeViewColumn *column;
113     GdkRectangle cell_area;
114     int drag_begin_y = 0;
115     char *uri;
116 
117     treeview = nautilus_list_model_get_drag_view (context->view->details->model,
118                                                   NULL,
119                                                   &drag_begin_y);
120     column = gtk_tree_view_get_column (treeview, 0);
121 
122     file = nautilus_list_model_file_for_path (NAUTILUS_LIST_MODEL (model), path);
123     if (file == NULL)
124     {
125         return;
126     }
127 
128     gtk_tree_view_get_cell_area (treeview,
129                                  path,
130                                  column,
131                                  &cell_area);
132 
133     uri = nautilus_file_get_activation_uri (file);
134 
135     nautilus_file_unref (file);
136 
137     /* pass the uri, mouse-relative x/y and icon width/height */
138     context->iteratee (uri,
139                        0,
140                        cell_area.y - drag_begin_y,
141                        cell_area.width,
142                        cell_area.height,
143                        context->iteratee_data);
144 
145     g_free (uri);
146 }
147 
148 static void
each_item_get_data_binder(NautilusDragEachSelectedItemDataGet iteratee,gpointer iterator_context,gpointer data)149 each_item_get_data_binder (NautilusDragEachSelectedItemDataGet iteratee,
150                            gpointer                            iterator_context,
151                            gpointer                            data)
152 {
153     NautilusListView *view = NAUTILUS_LIST_VIEW (iterator_context);
154     ListGetDataBinderContext context;
155     GtkTreeSelection *selection;
156 
157     context.view = view;
158     context.iteratee = iteratee;
159     context.iteratee_data = data;
160 
161     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->details->tree_view));
162     gtk_tree_selection_selected_foreach (selection, item_get_data_binder, &context);
163 }
164 
165 static void
drag_begin_callback(GtkWidget * widget,GdkDragContext * context,NautilusListView * view)166 drag_begin_callback (GtkWidget        *widget,
167                      GdkDragContext   *context,
168                      NautilusListView *view)
169 {
170     cairo_surface_t *surface;
171     NautilusWindow *window;
172     GList *dragged_files;
173 
174     window = nautilus_files_view_get_window (NAUTILUS_FILES_VIEW (view));
175     surface = get_drag_surface (view);
176     if (surface)
177     {
178         gtk_drag_set_icon_surface (context, surface);
179         cairo_surface_destroy (surface);
180     }
181     else
182     {
183         gtk_drag_set_icon_default (context);
184     }
185 
186     view->details->drag_button = 0;
187     view->details->drag_started = TRUE;
188 
189     view->details->drag_source_info->selection_cache = nautilus_drag_create_selection_cache (view,
190                                                                                              each_item_get_data_binder);
191 
192     dragged_files = nautilus_drag_file_list_from_selection_list (view->details->drag_source_info->selection_cache);
193     if (nautilus_file_list_are_all_folders (dragged_files))
194     {
195         nautilus_window_start_dnd (window, context);
196     }
197     g_list_free_full (dragged_files, g_object_unref);
198 }
199 
200 static void
drag_end_callback(GtkWidget * widget,GdkDragContext * context,NautilusListView * list_view)201 drag_end_callback (GtkWidget        *widget,
202                    GdkDragContext   *context,
203                    NautilusListView *list_view)
204 {
205     NautilusWindow *window;
206 
207     window = nautilus_files_view_get_window (NAUTILUS_FILES_VIEW (list_view));
208 
209     nautilus_window_end_dnd (window, context);
210 
211     drag_info_data_free (list_view);
212 }
213 
214 static void
drag_info_data_free(NautilusListView * list_view)215 drag_info_data_free (NautilusListView *list_view)
216 {
217     nautilus_drag_destroy_selection_list (list_view->details->drag_source_info->selection_cache);
218     list_view->details->drag_source_info->selection_cache = NULL;
219 
220     g_free (list_view->details->drag_source_info);
221     list_view->details->drag_source_info = NULL;
222 
223     g_signal_handlers_disconnect_by_func (list_view->details->tree_view, drag_begin_callback, list_view);
224     g_signal_handlers_disconnect_by_func (list_view->details->tree_view, drag_data_get_callback, list_view);
225     g_signal_handlers_disconnect_by_func (list_view->details->tree_view, drag_end_callback, list_view);
226 }
227 
228 NautilusDragInfo *
nautilus_list_view_dnd_get_drag_source_data(NautilusListView * list_view,GdkDragContext * context)229 nautilus_list_view_dnd_get_drag_source_data (NautilusListView *list_view,
230                                              GdkDragContext   *context)
231 {
232     GtkTreeView *tree_view;
233     GtkTreeModel *model;
234 
235     tree_view = GTK_TREE_VIEW (list_view->details->tree_view);
236 
237     model = gtk_tree_view_get_model (tree_view);
238 
239     if (model == NULL)
240     {
241         return NULL;
242     }
243 
244     if (list_view->details->drag_source_info == NULL ||
245         list_view->details->drag_source_info->selection_cache == NULL)
246     {
247         return NULL;
248     }
249 
250     return list_view->details->drag_source_info;
251 }
252 
253 void
nautilus_list_view_dnd_init(NautilusListView * list_view)254 nautilus_list_view_dnd_init (NautilusListView *list_view)
255 {
256     if (list_view->details->drag_source_info != NULL)
257     {
258         return;
259     }
260 
261     list_view->details->drag_source_info = g_new0 (NautilusDragInfo, 1);
262 
263     g_signal_connect_object (list_view->details->tree_view, "drag-begin",
264                              G_CALLBACK (drag_begin_callback), list_view, 0);
265     g_signal_connect_object (list_view->details->tree_view, "drag-end",
266                              G_CALLBACK (drag_end_callback), list_view, 0);
267     g_signal_connect_object (list_view->details->tree_view, "drag-data-get",
268                              G_CALLBACK (drag_data_get_callback), list_view, 0);
269 }
270 
271 void
nautilus_list_view_dnd_drag_begin(NautilusListView * list_view,gdouble offset_x,gdouble offset_y,const GdkEvent * event)272 nautilus_list_view_dnd_drag_begin (NautilusListView *list_view,
273                                    gdouble           offset_x,
274                                    gdouble           offset_y,
275                                    const GdkEvent   *event)
276 {
277     if (list_view->details->drag_button == 0)
278     {
279         return;
280     }
281 
282     if (!source_target_list)
283     {
284         source_target_list = nautilus_list_model_get_drag_target_list ();
285     }
286 
287     if (gtk_drag_check_threshold (GTK_WIDGET (list_view->details->tree_view),
288                                   list_view->details->drag_x,
289                                   list_view->details->drag_y,
290                                   list_view->details->drag_x + offset_x,
291                                   list_view->details->drag_y + offset_y))
292     {
293         guint32 actions;
294 
295         actions = GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK;
296         list_view->details->drag_source_info->source_actions = actions;
297         gtk_drag_begin_with_coordinates (GTK_WIDGET (list_view->details->tree_view),
298                                          source_target_list,
299                                          actions,
300                                          list_view->details->drag_button,
301                                          (GdkEvent *) event,
302                                          -1,
303                                          -1);
304     }
305 }
306