Ref #598: Option to set default drag-drop behaviour to local folder as 'move' not...
[geeqie.git] / src / view_dir.c
1 /*
2  * Copyright (C) 2008 - 2016 The Geeqie Team
3  *
4  * Author: Laurent Monin
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include "main.h"
22 #include "view_dir.h"
23
24 #include "dnd.h"
25 #include "dupe.h"
26 #include "editors.h"
27 #include "filedata.h"
28 #include "layout_image.h"
29 #include "layout_util.h"
30 #include "pixbuf_util.h"
31 #include "ui_fileops.h"
32 #include "ui_tree_edit.h"
33 #include "ui_menu.h"
34 #include "ui_misc.h"
35 #include "utilops.h"
36 #include "uri_utils.h"
37 #include "view_dir_list.h"
38 #include "view_dir_tree.h"
39
40 /* Folders icons to be used in tree or list directory view */
41 static PixmapFolders *folder_icons_new(GtkWidget *widget)
42 {
43         PixmapFolders *pf = g_new0(PixmapFolders, 1);
44
45 #if 1
46         GtkIconSize size = GTK_ICON_SIZE_MENU;
47
48         /* Attempt to use stock gtk icons */
49         pf->close  = gtk_widget_render_icon(widget, GTK_STOCK_DIRECTORY, size, NULL);
50         pf->open   = gtk_widget_render_icon(widget, GTK_STOCK_OPEN, size, NULL);
51         pf->deny   = gtk_widget_render_icon(widget, GTK_STOCK_STOP, size, NULL);
52         pf->parent = gtk_widget_render_icon(widget, GTK_STOCK_GO_UP, size, NULL);
53         /* FIXME: this is not a suitable icon */
54         pf->link = gtk_widget_render_icon(widget, GTK_STOCK_REDO, size, NULL);
55 #else
56         /* GQView legacy icons */
57         pf->close  = pixbuf_inline(PIXBUF_INLINE_FOLDER_CLOSED);
58         pf->open   = pixbuf_inline(PIXBUF_INLINE_FOLDER_OPEN);
59         pf->deny   = pixbuf_inline(PIXBUF_INLINE_FOLDER_LOCKED);
60         pf->parent = pixbuf_inline(PIXBUF_INLINE_FOLDER_UP);
61 #endif
62         return pf;
63 }
64
65 static void folder_icons_free(PixmapFolders *pf)
66 {
67         if (!pf) return;
68
69         g_object_unref(pf->close);
70         g_object_unref(pf->open);
71         g_object_unref(pf->deny);
72         g_object_unref(pf->parent);
73         g_object_unref(pf->link);
74
75         g_free(pf);
76 }
77
78
79
80 static void vd_notify_cb(FileData *fd, NotifyType type, gpointer data);
81
82 static void vd_destroy_cb(GtkWidget *widget, gpointer data)
83 {
84         ViewDir *vd = data;
85
86         file_data_unregister_notify_func(vd_notify_cb, vd);
87
88         if (vd->popup)
89                 {
90                 g_signal_handlers_disconnect_matched(G_OBJECT(vd->popup), G_SIGNAL_MATCH_DATA,
91                                                      0, 0, 0, NULL, vd);
92                 gtk_widget_destroy(vd->popup);
93                 }
94
95         switch (vd->type)
96         {
97         case DIRVIEW_LIST: vdlist_destroy_cb(widget, data); break;
98         case DIRVIEW_TREE: vdtree_destroy_cb(widget, data); break;
99         }
100
101         if (vd->pf) folder_icons_free(vd->pf);
102         if (vd->drop_list) filelist_free(vd->drop_list);
103
104         if (vd->dir_fd) file_data_unref(vd->dir_fd);
105         if (vd->info) g_free(vd->info);
106
107         g_free(vd);
108 }
109
110 ViewDir *vd_new(DirViewType type, FileData *dir_fd)
111 {
112         ViewDir *vd = g_new0(ViewDir, 1);
113
114         vd->widget = gtk_scrolled_window_new(NULL, NULL);
115         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(vd->widget), GTK_SHADOW_IN);
116         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vd->widget),
117                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
118
119         vd->pf = folder_icons_new(vd->widget);
120
121         switch (type)
122         {
123         case DIRVIEW_LIST: vd = vdlist_new(vd, dir_fd); break;
124         case DIRVIEW_TREE: vd = vdtree_new(vd, dir_fd); break;
125         }
126
127         gtk_container_add(GTK_CONTAINER(vd->widget), vd->view);
128
129         vd_dnd_init(vd);
130
131         g_signal_connect(G_OBJECT(vd->view), "row_activated",
132                          G_CALLBACK(vd_activate_cb), vd);
133         g_signal_connect(G_OBJECT(vd->widget), "destroy",
134                          G_CALLBACK(vd_destroy_cb), vd);
135         g_signal_connect(G_OBJECT(vd->view), "key_press_event",
136                          G_CALLBACK(vd_press_key_cb), vd);
137         g_signal_connect(G_OBJECT(vd->view), "button_press_event",
138                          G_CALLBACK(vd_press_cb), vd);
139         g_signal_connect(G_OBJECT(vd->view), "button_release_event",
140                          G_CALLBACK(vd_release_cb), vd);
141
142         file_data_register_notify_func(vd_notify_cb, vd, NOTIFY_PRIORITY_HIGH);
143
144         /* vd_set_fd expects that vd_notify_cb is already registered */
145         if (dir_fd) vd_set_fd(vd, dir_fd);
146
147         gtk_widget_show(vd->view);
148
149         return vd;
150 }
151
152 void vd_set_select_func(ViewDir *vd,
153                         void (*func)(ViewDir *vd, FileData *fd, gpointer data), gpointer data)
154 {
155         vd->select_func = func;
156         vd->select_data = data;
157 }
158
159 void vd_set_layout(ViewDir *vd, LayoutWindow *layout)
160 {
161         vd->layout = layout;
162 }
163
164 gboolean vd_set_fd(ViewDir *vd, FileData *dir_fd)
165 {
166         gboolean ret = FALSE;
167
168         file_data_unregister_notify_func(vd_notify_cb, vd);
169
170         switch (vd->type)
171         {
172         case DIRVIEW_LIST: ret = vdlist_set_fd(vd, dir_fd); break;
173         case DIRVIEW_TREE: ret = vdtree_set_fd(vd, dir_fd); break;
174         }
175
176         file_data_register_notify_func(vd_notify_cb, vd, NOTIFY_PRIORITY_HIGH);
177
178         return ret;
179 }
180
181 void vd_refresh(ViewDir *vd)
182 {
183         switch (vd->type)
184         {
185         case DIRVIEW_LIST: vdlist_refresh(vd); break;
186         case DIRVIEW_TREE: vdtree_refresh(vd); break;
187         }
188 }
189
190 const gchar *vd_row_get_path(ViewDir *vd, gint row)
191 {
192         const gchar *ret = NULL;
193
194         switch (vd->type)
195         {
196         case DIRVIEW_LIST: ret = vdlist_row_get_path(vd, row); break;
197         case DIRVIEW_TREE: ret = vdtree_row_get_path(vd, row); break;
198         }
199
200         return ret;
201 }
202
203 /* the calling stack is this:
204    vd_select_row -> select_func -> layout_set_fd -> vd_set_fd
205 */
206 void vd_select_row(ViewDir *vd, FileData *fd)
207 {
208         if (fd && vd->select_func)
209                 {
210                 vd->select_func(vd, fd, vd->select_data);
211                 }
212 }
213
214 gboolean vd_find_row(ViewDir *vd, FileData *fd, GtkTreeIter *iter)
215 {
216         gboolean ret = FALSE;
217
218         switch (vd->type)
219         {
220         case DIRVIEW_LIST: ret = vdlist_find_row(vd, fd, iter); break;
221         case DIRVIEW_TREE: ret = vdtree_find_row(vd, fd, iter, NULL); break;
222         }
223
224         return ret;
225 }
226
227 FileData *vd_get_fd_from_tree_path(ViewDir *vd, GtkTreeView *tview, GtkTreePath *tpath)
228 {
229         GtkTreeIter iter;
230         FileData *fd = NULL;
231         GtkTreeModel *store;
232
233         store = gtk_tree_view_get_model(tview);
234         gtk_tree_model_get_iter(store, &iter, tpath);
235         switch (vd->type)
236                 {
237                 case DIRVIEW_LIST:
238                         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &fd, -1);
239                         break;
240                 case DIRVIEW_TREE:
241                         {
242                         NodeData *nd;
243                         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
244                         fd = (nd) ? nd->fd : NULL;
245                         };
246                         break;
247                 }
248
249         return fd;
250 }
251
252 static void vd_rename_finished_cb(gboolean success, const gchar *new_path, gpointer data)
253 {
254         ViewDir *vd = data;
255         if (success)
256                 {
257                 FileData *fd = file_data_new_dir(new_path);
258                 GtkTreeIter iter;
259
260                 if (vd_find_row(vd, fd, &iter))
261                         {
262                         tree_view_row_make_visible(GTK_TREE_VIEW(vd->view), &iter, TRUE);
263                         }
264
265                 file_data_unref(fd);
266                 }
267 }
268
269 static gboolean vd_rename_cb(TreeEditData *td, const gchar *old, const gchar *new, gpointer data)
270 {
271         ViewDir *vd = data;
272         FileData *fd;
273         gchar *new_path;
274         gchar *base;
275
276         fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(vd->view), td->path);
277         if (!fd) return FALSE;
278
279         base = remove_level_from_path(fd->path);
280         new_path = g_build_filename(base, new, NULL);
281         g_free(base);
282
283         file_util_rename_dir(fd, new_path, vd->view, vd_rename_finished_cb, vd);
284
285         g_free(new_path);
286
287         return FALSE;
288 }
289
290 static void vd_rename_by_data(ViewDir *vd, FileData *fd)
291 {
292         GtkTreeModel *store;
293         GtkTreePath *tpath;
294         GtkTreeIter iter;
295
296         if (!fd || !vd_find_row(vd, fd, &iter)) return;
297         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
298         tpath = gtk_tree_model_get_path(store, &iter);
299
300         tree_edit_by_path(GTK_TREE_VIEW(vd->view), tpath, 0, fd->name,
301                           vd_rename_cb, vd);
302         gtk_tree_path_free(tpath);
303 }
304
305
306 void vd_color_set(ViewDir *vd, FileData *fd, gint color_set)
307 {
308         GtkTreeModel *store;
309         GtkTreeIter iter;
310
311         if (!vd_find_row(vd, fd, &iter)) return;
312         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
313
314         switch (vd->type)
315         {
316         case DIRVIEW_LIST:
317                 gtk_list_store_set(GTK_LIST_STORE(store), &iter, DIR_COLUMN_COLOR, color_set, -1);
318                 break;
319         case DIRVIEW_TREE:
320                 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, DIR_COLUMN_COLOR, color_set, -1);
321                 break;
322         }
323 }
324
325 void vd_popup_destroy_cb(GtkWidget *widget, gpointer data)
326 {
327         ViewDir *vd = data;
328
329         vd_color_set(vd, vd->click_fd, FALSE);
330         vd->click_fd = NULL;
331         vd->popup = NULL;
332
333         vd_color_set(vd, vd->drop_fd, FALSE);
334         filelist_free(vd->drop_list);
335         vd->drop_list = NULL;
336         vd->drop_fd = NULL;
337 }
338
339 /*
340  *-----------------------------------------------------------------------------
341  * drop menu (from dnd)
342  *-----------------------------------------------------------------------------
343  */
344
345 static void vd_drop_menu_copy_cb(GtkWidget *widget, gpointer data)
346 {
347         ViewDir *vd = data;
348         const gchar *path;
349         GList *list;
350
351         if (!vd->drop_fd) return;
352
353         path = vd->drop_fd->path;
354         list = vd->drop_list;
355         vd->drop_list = NULL;
356
357         file_util_copy_simple(list, path, vd->widget);
358 }
359
360 static void vd_drop_menu_move_cb(GtkWidget *widget, gpointer data)
361 {
362         ViewDir *vd = data;
363         const gchar *path;
364         GList *list;
365
366         if (!vd->drop_fd) return;
367
368         path = vd->drop_fd->path;
369         list = vd->drop_list;
370
371         vd->drop_list = NULL;
372
373         file_util_move_simple(list, path, vd->widget);
374 }
375
376 static void vd_drop_menu_filter_cb(GtkWidget *widget, gpointer data)
377 {
378         ViewDir *vd = data;
379         const gchar *path;
380         GList *list;
381         const gchar *key;
382
383         if (!vd->drop_fd) return;
384
385         key = g_object_get_data(G_OBJECT(widget), "filter_key");
386
387         path = vd->drop_fd->path;
388         list = vd->drop_list;
389
390         vd->drop_list = NULL;
391
392         file_util_start_filter_from_filelist(key, list, path, vd->widget);
393 }
394
395 static void vd_drop_menu_edit_item_free(gpointer data)
396 {
397         g_free(data);
398 }
399
400 GtkWidget *vd_drop_menu(ViewDir *vd, gint active)
401 {
402         GtkWidget *menu;
403         GList *editors_list = editor_list_get();
404         GList *work = editors_list;
405
406         menu = popup_menu_short_lived();
407         g_signal_connect(G_OBJECT(menu), "destroy",
408                          G_CALLBACK(vd_popup_destroy_cb), vd);
409
410         menu_item_add_stock_sensitive(menu, _("_Copy"), GTK_STOCK_COPY, active,
411                                       G_CALLBACK(vd_drop_menu_copy_cb), vd);
412         menu_item_add_sensitive(menu, _("_Move"), active, G_CALLBACK(vd_drop_menu_move_cb), vd);
413
414         while (work)
415                 {
416                 GtkWidget *item;
417                 const EditorDescription *editor = work->data;
418                 gchar *key;
419                 work = work->next;
420
421                 if (!editor_is_filter(editor->key)) continue;
422                 key = g_strdup(editor->key);
423                 item = menu_item_add_sensitive(menu, editor->name, active, G_CALLBACK(vd_drop_menu_filter_cb), vd);
424                 g_object_set_data_full(G_OBJECT(item), "filter_key", key, vd_drop_menu_edit_item_free);
425                 }
426
427         g_list_free(editors_list);
428
429         menu_item_add_divider(menu);
430         menu_item_add_stock(menu, _("Cancel"), GTK_STOCK_CANCEL, NULL, vd);
431
432         return menu;
433 }
434
435 /*
436  *-----------------------------------------------------------------------------
437  * pop-up menu
438  *-----------------------------------------------------------------------------
439  */
440
441 static void vd_pop_menu_up_cb(GtkWidget *widget, gpointer data)
442 {
443         ViewDir *vd = data;
444         gchar *path;
445
446         if (!vd->dir_fd || strcmp(vd->dir_fd->path, G_DIR_SEPARATOR_S) == 0) return;
447         path = remove_level_from_path(vd->dir_fd->path);
448
449         if (vd->select_func)
450                 {
451                 FileData *fd = file_data_new_dir(path);
452                 vd->select_func(vd, fd, vd->select_data);
453                 file_data_unref(fd);
454                 }
455
456         g_free(path);
457 }
458
459 static void vd_pop_menu_slide_cb(GtkWidget *widget, gpointer data)
460 {
461         ViewDir *vd = data;
462
463         if (!vd->layout) return;
464         if (!vd->click_fd) return;
465
466         layout_set_fd(vd->layout, vd->click_fd);
467         layout_select_none(vd->layout);
468         layout_image_slideshow_stop(vd->layout);
469         layout_image_slideshow_start(vd->layout);
470 }
471
472 static void vd_pop_menu_slide_rec_cb(GtkWidget *widget, gpointer data)
473 {
474         ViewDir *vd = data;
475         GList *list;
476
477         if (!vd->layout) return;
478         if (!vd->click_fd) return;
479
480         list = filelist_recursive_full(vd->click_fd, vd->layout->sort_method, vd->layout->sort_ascend);
481
482         layout_image_slideshow_stop(vd->layout);
483         layout_image_slideshow_start_from_list(vd->layout, list);
484 }
485
486 static void vd_pop_menu_dupe(ViewDir *vd, gint recursive)
487 {
488         DupeWindow *dw;
489         GList *list = NULL;
490
491         if (!vd->click_fd) return;
492
493         if (recursive)
494                 {
495                 list = g_list_append(list, file_data_ref(vd->click_fd));
496                 }
497         else
498                 {
499                 filelist_read(vd->click_fd, &list, NULL);
500                 list = filelist_filter(list, FALSE);
501                 }
502
503         dw = dupe_window_new();
504         dupe_window_add_files(dw, list, recursive);
505
506         filelist_free(list);
507 }
508
509 static void vd_pop_menu_dupe_cb(GtkWidget *widget, gpointer data)
510 {
511         ViewDir *vd = data;
512         vd_pop_menu_dupe(vd, FALSE);
513 }
514
515 static void vd_pop_menu_dupe_rec_cb(GtkWidget *widget, gpointer data)
516 {
517         ViewDir *vd = data;
518         vd_pop_menu_dupe(vd, TRUE);
519 }
520
521 static void vd_pop_menu_delete_cb(GtkWidget *widget, gpointer data)
522 {
523         ViewDir *vd = data;
524
525         if (!vd->click_fd) return;
526         file_util_delete_dir(vd->click_fd, vd->widget);
527 }
528
529 static void vd_pop_menu_copy_path_cb(GtkWidget *widget, gpointer data)
530 {
531         ViewDir *vd = data;
532
533         if (!vd->click_fd) return;
534
535         file_util_copy_path_to_clipboard(vd->click_fd, TRUE);
536 }
537
538 static void vd_pop_menu_copy_path_unquoted_cb(GtkWidget *widget, gpointer data)
539 {
540         ViewDir *vd = data;
541
542         if (!vd->click_fd) return;
543
544         file_util_copy_path_to_clipboard(vd->click_fd, FALSE);
545 }
546
547 static void vd_pop_submenu_dir_view_as_cb(GtkWidget *widget, gpointer data)
548 {
549         ViewDir *vd = data;
550
551         DirViewType new_type = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "menu_item_radio_data"));
552         layout_views_set(vd->layout, new_type, vd->layout->options.file_view_type);
553 }
554
555 static void vd_pop_menu_refresh_cb(GtkWidget *widget, gpointer data)
556 {
557         ViewDir *vd = data;
558
559         if (vd->layout) layout_refresh(vd->layout);
560 }
561
562 static void vd_toggle_show_hidden_files_cb(GtkWidget *widget, gpointer data)
563 {
564         ViewDir *vd = data;
565
566         options->file_filter.show_hidden_files = !options->file_filter.show_hidden_files;
567         if (vd->layout) layout_refresh(vd->layout);
568 }
569
570 static void vd_pop_menu_new_rename_cb(gboolean success, const gchar *new_path, gpointer data)
571 {
572         ViewDir *vd = data;
573         FileData *fd = NULL;
574         if (!success) return;
575
576         switch (vd->type)
577                 {
578                 case DIRVIEW_LIST:
579                         {
580                         vd_refresh(vd);
581                         fd = vdlist_row_by_path(vd, new_path, NULL);
582                         };
583                         break;
584                 case DIRVIEW_TREE:
585                         {
586                         FileData *new_fd = file_data_new_dir(new_path);
587                         fd = vdtree_populate_path(vd, new_fd, TRUE, TRUE);
588                         file_data_unref(new_fd);
589                         }
590                         break;
591                 }
592         vd_rename_by_data(vd, fd);
593 }
594
595 static void vd_pop_menu_new_cb(GtkWidget *widget, gpointer data)
596 {
597         ViewDir *vd = data;
598         FileData *dir_fd = NULL;
599
600         switch (vd->type)
601                 {
602                 case DIRVIEW_LIST:
603                         {
604                         if (!vd->dir_fd) return;
605                         dir_fd = vd->dir_fd;
606                         };
607                         break;
608                 case DIRVIEW_TREE:
609                         {
610                         if (!vd->click_fd) return;
611                         dir_fd = vd->click_fd;
612                         };
613                         break;
614                 }
615
616         file_util_create_dir(dir_fd, widget, vd_pop_menu_new_rename_cb, vd);
617 }
618
619 static void vd_pop_menu_rename_cb(GtkWidget *widget, gpointer data)
620 {
621         ViewDir *vd = data;
622
623         vd_rename_by_data(vd, vd->click_fd);
624 }
625
626 GtkWidget *vd_pop_menu(ViewDir *vd, FileData *fd)
627 {
628         GtkWidget *menu;
629         gboolean active;
630         gboolean rename_delete_active = FALSE;
631         gboolean new_folder_active = FALSE;
632
633         active = (fd != NULL);
634         switch (vd->type)
635                 {
636                 case DIRVIEW_LIST:
637                         {
638                         /* check using . (always row 0) */
639                         new_folder_active = (vd->dir_fd && access_file(vd->dir_fd->path , W_OK | X_OK));
640
641                         /* ignore .. and . */
642                         rename_delete_active = (new_folder_active && fd &&
643                                 strcmp(fd->name, ".") != 0 &&
644                                 strcmp(fd->name, "..") != 0 &&
645                                 access_file(fd->path, W_OK | X_OK));
646                         };
647                         break;
648                 case DIRVIEW_TREE:
649                         {
650                         if (fd)
651                                 {
652                                 gchar *parent;
653                                 new_folder_active = (fd && access_file(fd->path, W_OK | X_OK));
654                                 parent = remove_level_from_path(fd->path);
655                                 rename_delete_active = access_file(parent, W_OK | X_OK);
656                                 g_free(parent);
657                                 };
658                         }
659                         break;
660                 }
661
662         menu = popup_menu_short_lived();
663         g_signal_connect(G_OBJECT(menu), "destroy",
664                          G_CALLBACK(vd_popup_destroy_cb), vd);
665
666         menu_item_add_stock_sensitive(menu, _("_Up to parent"), GTK_STOCK_GO_UP,
667                                       (vd->dir_fd && strcmp(vd->dir_fd->path, G_DIR_SEPARATOR_S) != 0),
668                                       G_CALLBACK(vd_pop_menu_up_cb), vd);
669
670         menu_item_add_divider(menu);
671         menu_item_add_sensitive(menu, _("_Slideshow"), active,
672                                 G_CALLBACK(vd_pop_menu_slide_cb), vd);
673         menu_item_add_sensitive(menu, _("Slideshow recursive"), active,
674                                 G_CALLBACK(vd_pop_menu_slide_rec_cb), vd);
675
676         menu_item_add_divider(menu);
677         menu_item_add_stock_sensitive(menu, _("Find _duplicates..."), GTK_STOCK_FIND, active,
678                                       G_CALLBACK(vd_pop_menu_dupe_cb), vd);
679         menu_item_add_stock_sensitive(menu, _("Find duplicates recursive..."), GTK_STOCK_FIND, active,
680                                       G_CALLBACK(vd_pop_menu_dupe_rec_cb), vd);
681
682         menu_item_add_divider(menu);
683
684         menu_item_add_sensitive(menu, _("_New folder..."), new_folder_active,
685                                 G_CALLBACK(vd_pop_menu_new_cb), vd);
686
687         menu_item_add_sensitive(menu, _("_Rename..."), rename_delete_active,
688                                 G_CALLBACK(vd_pop_menu_rename_cb), vd);
689
690         menu_item_add(menu, _("_Copy path"),
691                       G_CALLBACK(vd_pop_menu_copy_path_cb), vd);
692
693         menu_item_add(menu, _("_Copy path unquoted"),
694                       G_CALLBACK(vd_pop_menu_copy_path_unquoted_cb), vd);
695
696         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, rename_delete_active,
697                                       G_CALLBACK(vd_pop_menu_delete_cb), vd);
698         menu_item_add_divider(menu);
699
700
701         menu_item_add_radio(menu, _("View as _List"), GINT_TO_POINTER(DIRVIEW_LIST), vd->type == DIRVIEW_LIST,
702                         G_CALLBACK(vd_pop_submenu_dir_view_as_cb), vd);
703
704         menu_item_add_radio(menu, _("View as _Tree"), GINT_TO_POINTER(DIRVIEW_TREE), vd->type == DIRVIEW_TREE,
705                         G_CALLBACK(vd_pop_submenu_dir_view_as_cb), vd);
706
707         menu_item_add_divider(menu);
708
709         menu_item_add_check(menu, _("Show _hidden files"), options->file_filter.show_hidden_files,
710                             G_CALLBACK(vd_toggle_show_hidden_files_cb), vd);
711
712         menu_item_add_stock(menu, _("Re_fresh"), GTK_STOCK_REFRESH,
713                             G_CALLBACK(vd_pop_menu_refresh_cb), vd);
714
715         return menu;
716 }
717
718 void vd_new_folder(ViewDir *vd, FileData *dir_fd)
719 {
720         file_util_create_dir(dir_fd, vd->widget, vd_pop_menu_new_rename_cb, vd);
721 }
722
723 /*
724  *-----------------------------------------------------------------------------
725  * dnd
726  *-----------------------------------------------------------------------------
727  */
728
729 static GtkTargetEntry vd_dnd_drop_types[] = {
730         { "text/uri-list", 0, TARGET_URI_LIST }
731 };
732 static gint vd_dnd_drop_types_count = 1;
733
734 static void vd_dest_set(ViewDir *vd, gint enable)
735 {
736         if (enable)
737                 {
738                 gtk_drag_dest_set(vd->view,
739                                   GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
740                                   vd_dnd_drop_types, vd_dnd_drop_types_count,
741                                   GDK_ACTION_MOVE | GDK_ACTION_COPY);
742                 }
743         else
744                 {
745                 gtk_drag_dest_unset(vd->view);
746                 }
747 }
748
749 static void vd_dnd_get(GtkWidget *widget, GdkDragContext *context,
750                            GtkSelectionData *selection_data, guint info,
751                            guint time, gpointer data)
752 {
753         ViewDir *vd = data;
754         GList *list;
755
756         if (!vd->click_fd) return;
757
758         switch (info)
759                 {
760                 case TARGET_URI_LIST:
761                 case TARGET_TEXT_PLAIN:
762                         list = g_list_prepend(NULL, vd->click_fd);
763                         uri_selection_data_set_uris_from_filelist(selection_data, list);
764                         g_list_free(list);
765                         break;
766                 }
767 }
768
769 static void vd_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
770 {
771         ViewDir *vd = data;
772
773         vd_color_set(vd, vd->click_fd, TRUE);
774         vd_dest_set(vd, FALSE);
775 }
776
777 static void vd_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
778 {
779         ViewDir *vd = data;
780
781         vd_color_set(vd, vd->click_fd, FALSE);
782
783         if (vd->type == DIRVIEW_LIST && gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
784                 {
785                 vd_refresh(vd);
786                 }
787         vd_dest_set(vd, TRUE);
788 }
789
790 static void vd_dnd_drop_receive(GtkWidget *widget,
791                                 GdkDragContext *context, gint x, gint y,
792                                 GtkSelectionData *selection_data, guint info,
793                                 guint time, gpointer data)
794 {
795         ViewDir *vd = data;
796         GtkTreePath *tpath;
797         FileData *fd = NULL;
798         GdkDragAction action;
799         GdkModifierType mask;
800
801         vd->click_fd = NULL;
802
803         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y,
804                                           &tpath, NULL, NULL, NULL))
805                 {
806                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(widget), tpath);
807                 gtk_tree_path_free(tpath);
808                 }
809
810         if (!fd) return;
811
812         if (info == TARGET_URI_LIST)
813                 {
814                 GList *list;
815                 gint active;
816                 gboolean done = FALSE;
817
818                 list = uri_filelist_from_gtk_selection_data(selection_data);
819                 if (!list) return;
820
821                 active = access_file(fd->path, W_OK | X_OK);
822
823                 vd_color_set(vd, fd, TRUE);
824
825                 if (active)
826                         {
827 /* FIXME: With GTK2 gdk_drag_context_get_actions() shows the state of the
828  * shift and control keys during the drag operation. With GTK3 this is not
829  * so. This is a workaround.
830  */
831 #if GTK_CHECK_VERSION(3,0,0)
832                         gdk_window_get_pointer(gtk_widget_get_window(widget), NULL, NULL, &mask);
833                         if (mask & GDK_CONTROL_MASK)
834                                 {
835                                 action = GDK_ACTION_COPY;
836                                 }
837                         else if (mask & GDK_SHIFT_MASK)
838                                 {
839                                 action = GDK_ACTION_MOVE;
840                                 }
841 #else
842                         action = (gdk_drag_context_get_actions(context));
843 #endif
844                         if (action == GDK_ACTION_COPY)
845                                 {
846                                 file_util_copy_simple(list, fd->path, vd->widget);
847                                 done = TRUE;
848                                 list = NULL;
849                                 }
850                         else if (action == GDK_ACTION_MOVE)
851                                 {
852                                 file_util_move_simple(list, fd->path, vd->widget);
853                                 done = TRUE;
854                                 list = NULL;
855                                 }
856                         }
857
858                 if (done == FALSE)
859                         {
860                         vd->popup = vd_drop_menu(vd, active);
861                         gtk_menu_popup(GTK_MENU(vd->popup), NULL, NULL, NULL, NULL, 0, time);
862                         }
863
864                 vd->drop_fd = fd;
865                 vd->drop_list = list;
866                 }
867 }
868
869 static void vd_dnd_drop_update(ViewDir *vd, gint x, gint y)
870 {
871         GtkTreePath *tpath;
872         FileData *fd = NULL;
873
874         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vd->view), x, y,
875                                           &tpath, NULL, NULL, NULL))
876                 {
877                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(vd->view), tpath);
878                 gtk_tree_path_free(tpath);
879                 }
880
881         if (fd != vd->drop_fd)
882                 {
883                 vd_color_set(vd, vd->drop_fd, FALSE);
884                 vd_color_set(vd, fd, TRUE);
885                 if (fd && vd->dnd_drop_update_func) vd->dnd_drop_update_func(vd);
886                 }
887
888         vd->drop_fd = fd;
889 }
890
891 void vd_dnd_drop_scroll_cancel(ViewDir *vd)
892 {
893         if (vd->drop_scroll_id)
894                 {
895                 g_source_remove(vd->drop_scroll_id);
896                 vd->drop_scroll_id = 0;
897                 }
898 }
899
900 static gboolean vd_auto_scroll_idle_cb(gpointer data)
901 {
902         ViewDir *vd = data;
903 #if GTK_CHECK_VERSION(3,0,0)
904         GdkDeviceManager *device_manager;
905         GdkDevice *device;
906 #endif
907
908         if (vd->drop_fd)
909                 {
910                 GdkWindow *window;
911                 gint x, y;
912                 gint w, h;
913
914                 window = gtk_widget_get_window(vd->view);
915 #if GTK_CHECK_VERSION(3,0,0)
916                 device_manager = gdk_display_get_device_manager(gdk_window_get_display(window));
917                 device = gdk_device_manager_get_client_pointer(device_manager);
918                 gdk_window_get_device_position(window, device, &x, &y, NULL);
919 #else
920                 gdk_window_get_pointer(window, &x, &y, NULL);
921 #endif
922                 w = gdk_window_get_width(window);
923                 h = gdk_window_get_height(window);
924                 if (x >= 0 && x < w && y >= 0 && y < h)
925                         {
926                         vd_dnd_drop_update(vd, x, y);
927                         }
928                 }
929
930         vd->drop_scroll_id = 0;
931         return FALSE;
932 }
933
934 static gboolean vd_auto_scroll_notify_cb(GtkWidget *widget, gint x, gint y, gpointer data)
935 {
936         ViewDir *vd = data;
937
938         if (!vd->drop_fd || vd->drop_list) return FALSE;
939
940         if (!vd->drop_scroll_id) vd->drop_scroll_id = g_idle_add(vd_auto_scroll_idle_cb, vd);
941
942         return TRUE;
943 }
944
945 static gboolean vd_dnd_drop_motion(GtkWidget *widget, GdkDragContext *context,
946                                    gint x, gint y, guint time, gpointer data)
947 {
948         ViewDir *vd = data;
949
950         vd->click_fd = NULL;
951
952         if (gtk_drag_get_source_widget(context) == vd->view)
953                 {
954                 /* from same window */
955                 gdk_drag_status(context, 0, time);
956                 return TRUE;
957                 }
958         else
959                 {
960                 gdk_drag_status(context, gdk_drag_context_get_suggested_action(context), time);
961                 }
962
963         vd_dnd_drop_update(vd, x, y);
964
965         if (vd->drop_fd)
966                 {
967                 GtkAdjustment *adj = gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(vd->view));
968                 widget_auto_scroll_start(vd->view, adj, -1, -1, vd_auto_scroll_notify_cb, vd);
969                 }
970
971         return FALSE;
972 }
973
974 static void vd_dnd_drop_leave(GtkWidget *widget, GdkDragContext *context, guint time, gpointer data)
975 {
976         ViewDir *vd = data;
977
978         if (vd->drop_fd != vd->click_fd) vd_color_set(vd, vd->drop_fd, FALSE);
979
980         vd->drop_fd = NULL;
981
982         if (vd->dnd_drop_leave_func) vd->dnd_drop_leave_func(vd);
983 }
984
985 void vd_dnd_init(ViewDir *vd)
986 {
987         gtk_drag_source_set(vd->view, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
988                             dnd_file_drag_types, dnd_file_drag_types_count,
989                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK);
990         g_signal_connect(G_OBJECT(vd->view), "drag_data_get",
991                          G_CALLBACK(vd_dnd_get), vd);
992         g_signal_connect(G_OBJECT(vd->view), "drag_begin",
993                          G_CALLBACK(vd_dnd_begin), vd);
994         g_signal_connect(G_OBJECT(vd->view), "drag_end",
995                          G_CALLBACK(vd_dnd_end), vd);
996
997         vd_dest_set(vd, TRUE);
998         g_signal_connect(G_OBJECT(vd->view), "drag_data_received",
999                          G_CALLBACK(vd_dnd_drop_receive), vd);
1000         g_signal_connect(G_OBJECT(vd->view), "drag_motion",
1001                          G_CALLBACK(vd_dnd_drop_motion), vd);
1002         g_signal_connect(G_OBJECT(vd->view), "drag_leave",
1003                          G_CALLBACK(vd_dnd_drop_leave), vd);
1004 }
1005
1006 /*
1007  *----------------------------------------------------------------------------
1008  * callbacks
1009  *----------------------------------------------------------------------------
1010  */
1011
1012 void vd_menu_position_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
1013 {
1014         ViewDir *vd = data;
1015         GtkTreeModel *store;
1016         GtkTreeIter iter;
1017         GtkTreePath *tpath;
1018         gint cw, ch;
1019
1020         if (!vd_find_row(vd, vd->click_fd, &iter)) return;
1021         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
1022         tpath = gtk_tree_model_get_path(store, &iter);
1023         tree_view_get_cell_clamped(GTK_TREE_VIEW(vd->view), tpath, 0, TRUE, x, y, &cw, &ch);
1024         gtk_tree_path_free(tpath);
1025         *y += ch;
1026         popup_menu_position_clamp(menu, x, y, 0);
1027 }
1028
1029 void vd_activate_cb(GtkTreeView *tview, GtkTreePath *tpath, GtkTreeViewColumn *column, gpointer data)
1030 {
1031         ViewDir *vd = data;
1032         FileData *fd = vd_get_fd_from_tree_path(vd, tview, tpath);
1033
1034         vd_select_row(vd, fd);
1035 }
1036
1037 static GdkColor *vd_color_shifted(GtkWidget *widget)
1038 {
1039         static GdkColor color;
1040         static GtkWidget *done = NULL;
1041
1042         if (done != widget)
1043                 {
1044                 GtkStyle *style;
1045
1046                 style = gtk_widget_get_style(widget);
1047                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
1048                 shift_color(&color, -1, 0);
1049                 done = widget;
1050                 }
1051
1052         return &color;
1053 }
1054
1055 void vd_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1056                  GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1057 {
1058         ViewDir *vd = data;
1059         gboolean set;
1060
1061         gtk_tree_model_get(tree_model, iter, DIR_COLUMN_COLOR, &set, -1);
1062         g_object_set(G_OBJECT(cell),
1063                      "cell-background-gdk", vd_color_shifted(vd->view),
1064                      "cell-background-set", set, NULL);
1065 }
1066
1067 gboolean vd_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1068 {
1069         ViewDir *vd = data;
1070         GtkTreePath *tpath;
1071         FileData *fd = NULL;
1072
1073         if (defined_mouse_buttons(widget, bevent, vd->layout))
1074                 {
1075                 return TRUE;
1076                 }
1077
1078         if (vd->type == DIRVIEW_LIST && !options->view_dir_list_single_click_enter)
1079                 return FALSE;
1080
1081         if (!vd->click_fd) return FALSE;
1082         vd_color_set(vd, vd->click_fd, FALSE);
1083
1084         if (bevent->button != MOUSE_BUTTON_LEFT) return TRUE;
1085
1086         if ((bevent->x != 0 || bevent->y != 0) &&
1087             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
1088                                           &tpath, NULL, NULL, NULL))
1089                 {
1090                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(widget), tpath);
1091                 gtk_tree_path_free(tpath);
1092                 }
1093
1094         if (fd && vd->click_fd == fd)
1095                 {
1096                 vd_select_row(vd, vd->click_fd);
1097                 }
1098
1099         return FALSE;
1100 }
1101
1102 gboolean vd_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1103 {
1104         ViewDir *vd = data;
1105         gboolean ret = FALSE;
1106
1107         switch (vd->type)
1108         {
1109         case DIRVIEW_LIST: ret = vdlist_press_key_cb(widget, event, data); break;
1110         case DIRVIEW_TREE: ret = vdtree_press_key_cb(widget, event, data); break;
1111         }
1112
1113         return ret;
1114 }
1115
1116 gboolean vd_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1117 {
1118         ViewDir *vd = data;
1119         gboolean ret = FALSE;
1120
1121         if (bevent->button == MOUSE_BUTTON_RIGHT)
1122                 {
1123                 vd->popup = vd_pop_menu(vd, vd->click_fd);
1124                 gtk_menu_popup(GTK_MENU(vd->popup), NULL, NULL, NULL, NULL,
1125                                bevent->button, bevent->time);
1126
1127                 return TRUE;
1128                 }
1129
1130         switch (vd->type)
1131         {
1132         case DIRVIEW_LIST: ret = vdlist_press_cb(widget, bevent, data); break;
1133         case DIRVIEW_TREE: ret = vdtree_press_cb(widget, bevent, data); break;
1134         }
1135
1136         return ret;
1137 }
1138
1139 static void vd_notify_cb(FileData *fd, NotifyType type, gpointer data)
1140 {
1141         ViewDir *vd = data;
1142         gboolean refresh;
1143         gchar *base;
1144
1145         if (!S_ISDIR(fd->mode)) return; /* this gives correct results even on recently deleted files/directories */
1146
1147         DEBUG_1("Notify vd: %s %04x", fd->path, type);
1148
1149         base = remove_level_from_path(fd->path);
1150
1151         if (vd->type == DIRVIEW_LIST)
1152                 {
1153                 refresh = (fd == vd->dir_fd);
1154
1155                 if (!refresh)
1156                         {
1157                         refresh = (strcmp(base, vd->dir_fd->path) == 0);
1158                         }
1159
1160                 if ((type & NOTIFY_CHANGE) && fd->change)
1161                         {
1162                         if (!refresh && fd->change->dest)
1163                                 {
1164                                 gchar *dest_base = remove_level_from_path(fd->change->dest);
1165                                 refresh = (strcmp(dest_base, vd->dir_fd->path) == 0);
1166                                 g_free(dest_base);
1167                                 }
1168
1169                         if (!refresh && fd->change->source)
1170                                 {
1171                                 gchar *source_base = remove_level_from_path(fd->change->source);
1172                                 refresh = (strcmp(source_base, vd->dir_fd->path) == 0);
1173                                 g_free(source_base);
1174                                 }
1175                         }
1176
1177                 if (refresh) vd_refresh(vd);
1178                 }
1179
1180         if (vd->type == DIRVIEW_TREE)
1181                 {
1182                 GtkTreeIter iter;
1183                 FileData *base_fd = file_data_new_dir(base);
1184
1185                 if (vd_find_row(vd, base_fd, &iter))
1186                         {
1187                         vdtree_populate_path_by_iter(vd, &iter, TRUE, vd->dir_fd);
1188                         }
1189
1190                 file_data_unref(base_fd);
1191                 }
1192
1193         g_free(base);
1194 }
1195 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */