fd9ca1ee190bec5924d36588fef3e2f3ea25b6aa
[geeqie.git] / src / view-dir.cc
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 "menu.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         auto pf = g_new0(PixmapFolders, 1);
44         GError *error = nullptr;
45         GdkPixbuf *icon;
46         gint scale;
47
48         GtkIconSize size = GTK_ICON_SIZE_MENU;
49
50 /** @FIXME Emblems should be attached to icons via e.g.: \n
51  * GIcon *.... \n
52  * icon = g_themed_icon_new("folder"); \n
53  * emblem_icon = g_themed_icon_new("emblem_symbolic_link"); \n
54  * emblem = g_emblem_new(emblem_icon); \n
55  * emblemed = g_emblemed_icon_new(icon, emblem); \n
56  * gtk_icon_info_load_icon(icon_info, NULL) \n
57  * But there does not seem to be a way to get GtkIconInfo from a GIcon
58  */
59
60         /* Attempt to use stock gtk icons */
61         pf->close  = gtk_widget_render_icon(widget, GTK_STOCK_DIRECTORY, size, nullptr);
62         pf->open   = gtk_widget_render_icon(widget, GTK_STOCK_OPEN, size, nullptr);
63         pf->parent = gtk_widget_render_icon(widget, GTK_STOCK_GO_UP, size, nullptr);
64
65         if (gtk_icon_theme_has_icon(gtk_icon_theme_get_default(), "emblem-unreadable"))
66                 {
67                 icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), "emblem-unreadable", size, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
68                 if (error)
69                         {
70                         log_printf("Error: %s\n", error->message);
71                         g_error_free(error);
72
73                         pf->deny = gdk_pixbuf_copy(gtk_widget_render_icon(widget, GTK_STOCK_STOP, size, nullptr));
74                         }
75                 else
76                         {
77                         pf->deny = gdk_pixbuf_copy(gtk_widget_render_icon(widget, GTK_STOCK_DIRECTORY, size, nullptr));
78                         scale = gdk_pixbuf_get_width(icon) / 2;
79                         gdk_pixbuf_composite(icon, pf->deny, scale, scale, scale, scale, scale, scale, 0.5, 0.5, GDK_INTERP_HYPER, 255);
80
81                         }
82                 g_object_unref(icon);
83                 }
84         else
85                 {
86                 pf->deny = gdk_pixbuf_copy(gtk_widget_render_icon(widget, GTK_STOCK_STOP, size, nullptr));
87                 }
88
89         if (gtk_icon_theme_has_icon(gtk_icon_theme_get_default(), "emblem-symbolic-link"))
90                 {
91                 icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), "emblem-symbolic-link", size, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
92                 if (error)
93                         {
94                         log_printf("Error: %s\n", error->message);
95                         g_error_free(error);
96
97                         pf->link = gdk_pixbuf_copy(gtk_widget_render_icon(widget, GTK_STOCK_REDO, size, nullptr));
98                         }
99                 else
100                         {
101                         pf->link = gdk_pixbuf_copy(gtk_widget_render_icon(widget, GTK_STOCK_DIRECTORY, size, nullptr));
102                         scale = gdk_pixbuf_get_width(icon) / 2;
103                         gdk_pixbuf_composite(icon, pf->link, scale, scale, scale, scale, scale, scale, 0.5, 0.5, GDK_INTERP_HYPER, 255);
104                         }
105                 g_object_unref(icon);
106                 }
107         else
108                 {
109                 pf->link = gdk_pixbuf_copy(gtk_widget_render_icon(widget, GTK_STOCK_REDO, size, nullptr));
110                 }
111
112         pf->read_only = gdk_pixbuf_copy(gtk_widget_render_icon(widget, GTK_STOCK_DIRECTORY, size, nullptr));
113
114         if (gtk_icon_theme_has_icon(gtk_icon_theme_get_default(), "emblem-readonly"))
115                 {
116                 icon = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), "emblem-readonly", size, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
117                 if (error)
118                         {
119                         log_printf("Error: %s\n", error->message);
120                         g_error_free(error);
121
122                         pf->read_only = gdk_pixbuf_copy(gtk_widget_render_icon(widget, GTK_STOCK_DIRECTORY, size, nullptr));
123                         }
124                 else
125                         {
126                         gint scale = gdk_pixbuf_get_width(icon) / 2;
127                         gdk_pixbuf_composite(icon, pf->read_only, scale, scale, scale, scale, scale, scale, 0.5, 0.5, GDK_INTERP_HYPER, 255);
128
129                         }
130                 g_object_unref(icon);
131                 }
132
133         return pf;
134 }
135
136 static void folder_icons_free(PixmapFolders *pf)
137 {
138         if (!pf) return;
139
140         g_object_unref(pf->close);
141         g_object_unref(pf->open);
142         g_object_unref(pf->deny);
143         g_object_unref(pf->parent);
144         g_object_unref(pf->link);
145         g_object_unref(pf->read_only);
146
147         g_free(pf);
148 }
149
150
151
152 static void vd_notify_cb(FileData *fd, NotifyType type, gpointer data);
153
154 static void vd_destroy_cb(GtkWidget *widget, gpointer data)
155 {
156         auto vd = static_cast<ViewDir *>(data);
157
158         file_data_unregister_notify_func(vd_notify_cb, vd);
159
160         if (vd->popup)
161                 {
162                 g_signal_handlers_disconnect_matched(G_OBJECT(vd->popup), G_SIGNAL_MATCH_DATA,
163                                                      0, 0, nullptr, nullptr, vd);
164                 gtk_widget_destroy(vd->popup);
165                 }
166
167         switch (vd->type)
168         {
169         case DIRVIEW_LIST: vdlist_destroy_cb(widget, data); break;
170         case DIRVIEW_TREE: vdtree_destroy_cb(widget, data); break;
171         }
172
173         if (vd->pf) folder_icons_free(vd->pf);
174         if (vd->drop_list) filelist_free(vd->drop_list);
175
176         if (vd->dir_fd) file_data_unref(vd->dir_fd);
177         if (vd->info) g_free(vd->info);
178
179         g_free(vd);
180 }
181
182 ViewDir *vd_new(LayoutWindow *lw)
183 {
184         auto vd = g_new0(ViewDir, 1);
185
186         vd->widget = gtk_scrolled_window_new(nullptr, nullptr);
187         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(vd->widget), GTK_SHADOW_IN);
188         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vd->widget),
189                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
190
191         vd->layout = lw;
192         vd->pf = folder_icons_new(vd->widget);
193
194         switch (lw->options.dir_view_type)
195                 {
196                 case DIRVIEW_LIST: vd = vdlist_new(vd, lw->dir_fd); break;
197                 case DIRVIEW_TREE: vd = vdtree_new(vd, lw->dir_fd); break;
198                 }
199
200         gtk_container_add(GTK_CONTAINER(vd->widget), vd->view);
201
202         vd_dnd_init(vd);
203
204         g_signal_connect(G_OBJECT(vd->view), "row_activated",
205                          G_CALLBACK(vd_activate_cb), vd);
206         g_signal_connect(G_OBJECT(vd->widget), "destroy",
207                          G_CALLBACK(vd_destroy_cb), vd);
208         g_signal_connect(G_OBJECT(vd->view), "key_press_event",
209                          G_CALLBACK(vd_press_key_cb), vd);
210         g_signal_connect(G_OBJECT(vd->view), "button_press_event",
211                          G_CALLBACK(vd_press_cb), vd);
212         g_signal_connect(G_OBJECT(vd->view), "button_release_event",
213                          G_CALLBACK(vd_release_cb), vd);
214
215         file_data_register_notify_func(vd_notify_cb, vd, NOTIFY_PRIORITY_HIGH);
216
217         /* vd_set_fd expects that vd_notify_cb is already registered */
218         if (lw->dir_fd) vd_set_fd(vd, lw->dir_fd);
219
220         gtk_widget_show(vd->view);
221
222         return vd;
223 }
224
225 void vd_set_select_func(ViewDir *vd,
226                         void (*func)(ViewDir *vd, FileData *fd, gpointer data), gpointer data)
227 {
228         vd->select_func = func;
229         vd->select_data = data;
230 }
231
232 #pragma GCC diagnostic push
233 #pragma GCC diagnostic ignored "-Wunused-function"
234 void vd_set_layout_unused(ViewDir *vd, LayoutWindow *layout)
235 {
236         vd->layout = layout;
237 }
238 #pragma GCC diagnostic pop
239
240 gboolean vd_set_fd(ViewDir *vd, FileData *dir_fd)
241 {
242         gboolean ret = FALSE;
243
244         file_data_unregister_notify_func(vd_notify_cb, vd);
245
246         switch (vd->type)
247         {
248         case DIRVIEW_LIST: ret = vdlist_set_fd(vd, dir_fd); break;
249         case DIRVIEW_TREE: ret = vdtree_set_fd(vd, dir_fd); break;
250         }
251
252         file_data_register_notify_func(vd_notify_cb, vd, NOTIFY_PRIORITY_HIGH);
253
254         return ret;
255 }
256
257 void vd_refresh(ViewDir *vd)
258 {
259         switch (vd->type)
260         {
261         case DIRVIEW_LIST: vdlist_refresh(vd); break;
262         case DIRVIEW_TREE: vdtree_refresh(vd); break;
263         }
264 }
265
266 #pragma GCC diagnostic push
267 #pragma GCC diagnostic ignored "-Wunused-function"
268 const gchar *vd_row_get_path_unused(ViewDir *vd, gint row)
269 {
270         const gchar *ret = NULL;
271
272         switch (vd->type)
273         {
274         case DIRVIEW_LIST: ret = vdlist_row_get_path(vd, row); break;
275         case DIRVIEW_TREE: ret = vdtree_row_get_path(vd, row); break;
276         }
277
278         return ret;
279 }
280 #pragma GCC diagnostic pop
281
282 /* the calling stack is this:
283    vd_select_row -> select_func -> layout_set_fd -> vd_set_fd
284 */
285 void vd_select_row(ViewDir *vd, FileData *fd)
286 {
287         if (fd && vd->select_func)
288                 {
289                 vd->select_func(vd, fd, vd->select_data);
290                 }
291 }
292
293 gboolean vd_find_row(ViewDir *vd, FileData *fd, GtkTreeIter *iter)
294 {
295         gboolean ret = FALSE;
296
297         switch (vd->type)
298         {
299         case DIRVIEW_LIST: ret = vdlist_find_row(vd, fd, iter); break;
300         case DIRVIEW_TREE: ret = vdtree_find_row(vd, fd, iter, nullptr); break;
301         }
302
303         return ret;
304 }
305
306 FileData *vd_get_fd_from_tree_path(ViewDir *vd, GtkTreeView *tview, GtkTreePath *tpath)
307 {
308         GtkTreeIter iter;
309         FileData *fd = nullptr;
310         GtkTreeModel *store;
311
312         store = gtk_tree_view_get_model(tview);
313         gtk_tree_model_get_iter(store, &iter, tpath);
314         switch (vd->type)
315                 {
316                 case DIRVIEW_LIST:
317                         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &fd, -1);
318                         break;
319                 case DIRVIEW_TREE:
320                         {
321                         NodeData *nd;
322                         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
323                         fd = (nd) ? nd->fd : nullptr;
324                         };
325                         break;
326                 }
327
328         return fd;
329 }
330
331 static void vd_rename_finished_cb(gboolean success, const gchar *new_path, gpointer data)
332 {
333         auto vd = static_cast<ViewDir *>(data);
334         if (success)
335                 {
336                 FileData *fd = file_data_new_dir(new_path);
337                 GtkTreeIter iter;
338
339                 if (vd_find_row(vd, fd, &iter))
340                         {
341                         tree_view_row_make_visible(GTK_TREE_VIEW(vd->view), &iter, TRUE);
342                         }
343
344                 file_data_unref(fd);
345                 }
346 }
347
348 static gboolean vd_rename_cb(TreeEditData *td, const gchar *, const gchar *new_name, gpointer data)
349 {
350         auto vd = static_cast<ViewDir *>(data);
351         FileData *fd;
352         gchar *new_path;
353         gchar *base;
354
355         fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(vd->view), td->path);
356         if (!fd) return FALSE;
357
358         base = remove_level_from_path(fd->path);
359         new_path = g_build_filename(base, new_name, NULL);
360         g_free(base);
361
362         file_util_rename_dir(fd, new_path, vd->view, vd_rename_finished_cb, vd);
363
364         g_free(new_path);
365
366         return FALSE;
367 }
368
369 static void vd_rename_by_data(ViewDir *vd, FileData *fd)
370 {
371         GtkTreeModel *store;
372         GtkTreePath *tpath;
373         GtkTreeIter iter;
374
375         if (!fd || !vd_find_row(vd, fd, &iter)) return;
376         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
377         tpath = gtk_tree_model_get_path(store, &iter);
378
379         tree_edit_by_path(GTK_TREE_VIEW(vd->view), tpath, 0, fd->name,
380                           vd_rename_cb, vd);
381         gtk_tree_path_free(tpath);
382 }
383
384
385 void vd_color_set(ViewDir *vd, FileData *fd, gint color_set)
386 {
387         GtkTreeModel *store;
388         GtkTreeIter iter;
389
390         if (!vd_find_row(vd, fd, &iter)) return;
391         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
392
393         switch (vd->type)
394         {
395         case DIRVIEW_LIST:
396                 gtk_list_store_set(GTK_LIST_STORE(store), &iter, DIR_COLUMN_COLOR, color_set, -1);
397                 break;
398         case DIRVIEW_TREE:
399                 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, DIR_COLUMN_COLOR, color_set, -1);
400                 break;
401         }
402 }
403
404 void vd_popup_destroy_cb(GtkWidget *, gpointer data)
405 {
406         auto vd = static_cast<ViewDir *>(data);
407
408         vd_color_set(vd, vd->click_fd, FALSE);
409         vd->click_fd = nullptr;
410         vd->popup = nullptr;
411
412         vd_color_set(vd, vd->drop_fd, FALSE);
413         filelist_free(vd->drop_list);
414         vd->drop_list = nullptr;
415         vd->drop_fd = nullptr;
416 }
417
418 /*
419  *-----------------------------------------------------------------------------
420  * drop menu (from dnd)
421  *-----------------------------------------------------------------------------
422  */
423
424 static void vd_drop_menu_copy_cb(GtkWidget *, gpointer data)
425 {
426         auto vd = static_cast<ViewDir *>(data);
427         const gchar *path;
428         GList *list;
429
430         if (!vd->drop_fd) return;
431
432         path = vd->drop_fd->path;
433         list = vd->drop_list;
434         vd->drop_list = nullptr;
435
436         file_util_copy_simple(list, path, vd->widget);
437 }
438
439 static void vd_drop_menu_move_cb(GtkWidget *, gpointer data)
440 {
441         auto vd = static_cast<ViewDir *>(data);
442         const gchar *path;
443         GList *list;
444
445         if (!vd->drop_fd) return;
446
447         path = vd->drop_fd->path;
448         list = vd->drop_list;
449
450         vd->drop_list = nullptr;
451
452         file_util_move_simple(list, path, vd->widget);
453 }
454
455 static void vd_drop_menu_filter_cb(GtkWidget *widget, gpointer data)
456 {
457         auto vd = static_cast<ViewDir *>(data);
458         const gchar *path;
459         GList *list;
460         const gchar *key;
461
462         if (!vd->drop_fd) return;
463
464         key = static_cast<const gchar *>(g_object_get_data(G_OBJECT(widget), "filter_key"));
465
466         path = vd->drop_fd->path;
467         list = vd->drop_list;
468
469         vd->drop_list = nullptr;
470
471         file_util_start_filter_from_filelist(key, list, path, vd->widget);
472 }
473
474 static void vd_drop_menu_edit_item_free(gpointer data)
475 {
476         g_free(data);
477 }
478
479 GtkWidget *vd_drop_menu(ViewDir *vd, gint active)
480 {
481         GtkWidget *menu;
482         GList *editors_list = editor_list_get();
483         GList *work = editors_list;
484
485         menu = popup_menu_short_lived();
486         g_signal_connect(G_OBJECT(menu), "destroy",
487                          G_CALLBACK(vd_popup_destroy_cb), vd);
488
489         menu_item_add_stock_sensitive(menu, _("_Copy"), GTK_STOCK_COPY, active,
490                                       G_CALLBACK(vd_drop_menu_copy_cb), vd);
491         menu_item_add_sensitive(menu, _("_Move"), active, G_CALLBACK(vd_drop_menu_move_cb), vd);
492
493         while (work)
494                 {
495                 GtkWidget *item;
496                 auto editor = static_cast<const EditorDescription *>(work->data);
497                 gchar *key;
498                 work = work->next;
499
500                 if (!editor_is_filter(editor->key)) continue;
501                 key = g_strdup(editor->key);
502                 item = menu_item_add_sensitive(menu, editor->name, active, G_CALLBACK(vd_drop_menu_filter_cb), vd);
503                 g_object_set_data_full(G_OBJECT(item), "filter_key", key, vd_drop_menu_edit_item_free);
504                 }
505
506         g_list_free(editors_list);
507
508         menu_item_add_divider(menu);
509         menu_item_add_stock(menu, _("Cancel"), GTK_STOCK_CANCEL, nullptr, vd);
510
511         return menu;
512 }
513
514 /*
515  *-----------------------------------------------------------------------------
516  * pop-up menu
517  *-----------------------------------------------------------------------------
518  */
519
520 static void vd_pop_menu_up_cb(GtkWidget *, gpointer data)
521 {
522         auto vd = static_cast<ViewDir *>(data);
523         gchar *path;
524
525         if (!vd->dir_fd || strcmp(vd->dir_fd->path, G_DIR_SEPARATOR_S) == 0) return;
526         path = remove_level_from_path(vd->dir_fd->path);
527
528         if (vd->select_func)
529                 {
530                 FileData *fd = file_data_new_dir(path);
531                 vd->select_func(vd, fd, vd->select_data);
532                 file_data_unref(fd);
533                 }
534
535         g_free(path);
536 }
537
538 static void vd_pop_menu_slide_cb(GtkWidget *, gpointer data)
539 {
540         auto vd = static_cast<ViewDir *>(data);
541
542         if (!vd->layout) return;
543         if (!vd->click_fd) return;
544
545         layout_set_fd(vd->layout, vd->click_fd);
546         layout_select_none(vd->layout);
547         layout_image_slideshow_stop(vd->layout);
548         layout_image_slideshow_start(vd->layout);
549 }
550
551 static void vd_pop_menu_slide_rec_cb(GtkWidget *, gpointer data)
552 {
553         auto vd = static_cast<ViewDir *>(data);
554         GList *list;
555
556         if (!vd->layout) return;
557         if (!vd->click_fd) return;
558
559         list = filelist_recursive_full(vd->click_fd, vd->layout->sort_method, vd->layout->sort_ascend);
560
561         layout_image_slideshow_stop(vd->layout);
562         layout_image_slideshow_start_from_list(vd->layout, list);
563 }
564
565 static void vd_pop_menu_dupe(ViewDir *vd, gint recursive)
566 {
567         DupeWindow *dw;
568         GList *list = nullptr;
569
570         if (!vd->click_fd) return;
571
572         if (recursive)
573                 {
574                 list = g_list_append(list, file_data_ref(vd->click_fd));
575                 }
576         else
577                 {
578                 filelist_read(vd->click_fd, &list, nullptr);
579                 list = filelist_filter(list, FALSE);
580                 }
581
582         dw = dupe_window_new();
583         dupe_window_add_files(dw, list, recursive);
584
585         filelist_free(list);
586 }
587
588 static void vd_pop_menu_dupe_cb(GtkWidget *, gpointer data)
589 {
590         auto vd = static_cast<ViewDir *>(data);
591         vd_pop_menu_dupe(vd, FALSE);
592 }
593
594 static void vd_pop_menu_dupe_rec_cb(GtkWidget *, gpointer data)
595 {
596         auto vd = static_cast<ViewDir *>(data);
597         vd_pop_menu_dupe(vd, TRUE);
598 }
599
600 static void vd_pop_menu_delete_cb(GtkWidget *, gpointer data)
601 {
602         auto vd = static_cast<ViewDir *>(data);
603
604         if (!vd->click_fd) return;
605         file_util_delete_dir(vd->click_fd, vd->widget);
606 }
607
608 static void vd_pop_menu_copy_path_cb(GtkWidget *, gpointer data)
609 {
610         auto vd = static_cast<ViewDir *>(data);
611
612         if (!vd->click_fd) return;
613
614         file_util_copy_path_to_clipboard(vd->click_fd, TRUE);
615 }
616
617 static void vd_pop_menu_copy_path_unquoted_cb(GtkWidget *, gpointer data)
618 {
619         auto vd = static_cast<ViewDir *>(data);
620
621         if (!vd->click_fd) return;
622
623         file_util_copy_path_to_clipboard(vd->click_fd, FALSE);
624 }
625
626 static void vd_pop_submenu_dir_view_as_cb(GtkWidget *widget, gpointer data)
627 {
628         auto vd = static_cast<ViewDir *>(data);
629
630         auto new_type = static_cast<DirViewType>(GPOINTER_TO_INT((g_object_get_data(G_OBJECT(widget), "menu_item_radio_data"))));
631         layout_views_set(vd->layout, new_type, vd->layout->options.file_view_type);
632 }
633
634 static void vd_pop_menu_refresh_cb(GtkWidget *, gpointer data)
635 {
636         auto vd = static_cast<ViewDir *>(data);
637
638         if (vd->layout) layout_refresh(vd->layout);
639 }
640
641 static void vd_toggle_show_hidden_files_cb(GtkWidget *, gpointer data)
642 {
643         auto vd = static_cast<ViewDir *>(data);
644
645         options->file_filter.show_hidden_files = !options->file_filter.show_hidden_files;
646         if (vd->layout) layout_refresh(vd->layout);
647 }
648
649 static void vd_pop_menu_new_folder_cb(gboolean success, const gchar *new_path, gpointer data)
650 {
651         auto vd = static_cast<ViewDir *>(data);
652         FileData *fd = nullptr;
653         GtkTreeIter iter;
654         GtkTreePath *tpath;
655         GtkTreeModel *store;
656
657         if (!success) return;
658
659         switch (vd->type)
660                 {
661                 case DIRVIEW_LIST:
662                         {
663                         vd_refresh(vd);
664                         fd = vdlist_row_by_path(vd, new_path, nullptr);
665                         };
666                         break;
667                 case DIRVIEW_TREE:
668                         {
669                         FileData *new_fd = file_data_new_dir(new_path);
670                         fd = vdtree_populate_path(vd, new_fd, TRUE, TRUE);
671                         file_data_unref(new_fd);
672                         }
673                         break;
674                 }
675
676         if (!fd || !vd_find_row(vd, fd, &iter)) return;
677         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
678         tpath = gtk_tree_model_get_path(store, &iter);
679         gtk_tree_view_set_cursor(GTK_TREE_VIEW(vd->view), tpath, nullptr, FALSE);
680
681         gtk_tree_path_free(tpath);
682 }
683
684 static void vd_pop_menu_new_cb(GtkWidget *, gpointer data)
685 {
686         auto vd = static_cast<ViewDir *>(data);
687         FileData *dir_fd = nullptr;
688
689         switch (vd->type)
690                 {
691                 case DIRVIEW_LIST:
692                         {
693                         if (!vd->dir_fd) return;
694                         dir_fd = vd->dir_fd;
695                         };
696                         break;
697                 case DIRVIEW_TREE:
698                         {
699                         if (!vd->click_fd) return;
700                         dir_fd = vd->click_fd;
701                         };
702                         break;
703                 }
704
705         file_util_create_dir(dir_fd, vd->layout->window, vd_pop_menu_new_folder_cb, vd);
706 }
707
708 static void vd_pop_menu_rename_cb(GtkWidget *, gpointer data)
709 {
710         auto vd = static_cast<ViewDir *>(data);
711
712         vd_rename_by_data(vd, vd->click_fd);
713 }
714
715 static void vd_pop_menu_sort_ascend_cb(GtkWidget *widget, gpointer data)
716 {
717         auto vd = static_cast<ViewDir *>(data);
718         gboolean ascend;
719
720         if (!vd) return;
721
722         if (!vd->layout) return;
723
724         ascend = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
725         layout_views_set_sort(vd->layout, vd->layout->options.dir_view_list_sort.method, ascend);
726
727         if (vd->layout) layout_refresh(vd->layout);
728 }
729
730 static void vd_pop_menu_sort_cb(GtkWidget *widget, gpointer data)
731 {
732         ViewDir *vd;
733         SortType type;
734
735         vd = static_cast<ViewDir *>(submenu_item_get_data(widget));
736
737         if (!vd) return;
738         if (!vd->layout) return;
739
740         type = static_cast<SortType>GPOINTER_TO_INT(data);
741
742         if (type == SORT_NAME || type == SORT_TIME)
743                 {
744                 layout_views_set_sort(vd->layout, type, vd->layout->options.dir_view_list_sort.ascend);
745
746                 if (vd->layout) layout_refresh(vd->layout);
747                 }
748 }
749
750 GtkWidget *vd_pop_menu(ViewDir *vd, FileData *fd)
751 {
752         GtkWidget *menu;
753         gboolean active;
754         gboolean rename_delete_active = FALSE;
755         gboolean new_folder_active = FALSE;
756         GtkWidget *submenu;
757         GtkWidget *item;
758
759         active = (fd != nullptr);
760         switch (vd->type)
761                 {
762                 case DIRVIEW_LIST:
763                         {
764                         /* check using . (always row 0) */
765                         new_folder_active = (vd->dir_fd && access_file(vd->dir_fd->path , W_OK | X_OK));
766
767                         /* ignore .. and . */
768                         rename_delete_active = (new_folder_active && fd &&
769                                 strcmp(fd->name, ".") != 0 &&
770                                 strcmp(fd->name, "..") != 0 &&
771                                 access_file(fd->path, W_OK | X_OK));
772                         };
773                         break;
774                 case DIRVIEW_TREE:
775                         {
776                         if (fd)
777                                 {
778                                 gchar *parent;
779                                 new_folder_active = (fd && access_file(fd->path, W_OK | X_OK));
780                                 parent = remove_level_from_path(fd->path);
781                                 rename_delete_active = access_file(parent, W_OK | X_OK);
782                                 g_free(parent);
783                                 };
784                         }
785                         break;
786                 }
787
788         menu = popup_menu_short_lived();
789         g_signal_connect(G_OBJECT(menu), "destroy",
790                          G_CALLBACK(vd_popup_destroy_cb), vd);
791
792         menu_item_add_stock_sensitive(menu, _("_Up to parent"), GTK_STOCK_GO_UP,
793                                       (vd->dir_fd && strcmp(vd->dir_fd->path, G_DIR_SEPARATOR_S) != 0),
794                                       G_CALLBACK(vd_pop_menu_up_cb), vd);
795
796         menu_item_add_divider(menu);
797         menu_item_add_sensitive(menu, _("_Slideshow"), active,
798                                 G_CALLBACK(vd_pop_menu_slide_cb), vd);
799         menu_item_add_sensitive(menu, _("Slideshow recursive"), active,
800                                 G_CALLBACK(vd_pop_menu_slide_rec_cb), vd);
801
802         menu_item_add_divider(menu);
803         menu_item_add_stock_sensitive(menu, _("Find _duplicates..."), GTK_STOCK_FIND, active,
804                                       G_CALLBACK(vd_pop_menu_dupe_cb), vd);
805         menu_item_add_stock_sensitive(menu, _("Find duplicates recursive..."), GTK_STOCK_FIND, active,
806                                       G_CALLBACK(vd_pop_menu_dupe_rec_cb), vd);
807
808         menu_item_add_divider(menu);
809
810         menu_item_add_sensitive(menu, _("_New folder..."), new_folder_active,
811                                 G_CALLBACK(vd_pop_menu_new_cb), vd);
812
813         menu_item_add_sensitive(menu, _("_Rename..."), rename_delete_active,
814                                 G_CALLBACK(vd_pop_menu_rename_cb), vd);
815
816         menu_item_add(menu, _("_Copy path"),
817                       G_CALLBACK(vd_pop_menu_copy_path_cb), vd);
818
819         menu_item_add(menu, _("_Copy path unquoted"),
820                       G_CALLBACK(vd_pop_menu_copy_path_unquoted_cb), vd);
821
822         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, rename_delete_active,
823                                       G_CALLBACK(vd_pop_menu_delete_cb), vd);
824         menu_item_add_divider(menu);
825
826
827         menu_item_add_radio(menu, _("View as _List"), GINT_TO_POINTER(DIRVIEW_LIST), vd->type == DIRVIEW_LIST,
828                         G_CALLBACK(vd_pop_submenu_dir_view_as_cb), vd);
829
830         menu_item_add_radio(menu, _("View as _Tree"), GINT_TO_POINTER(DIRVIEW_TREE), vd->type == DIRVIEW_TREE,
831                         G_CALLBACK(vd_pop_submenu_dir_view_as_cb), vd);
832
833         if (vd->type == DIRVIEW_LIST)
834                 {
835                 submenu = submenu_add_dir_sort(nullptr, G_CALLBACK(vd_pop_menu_sort_cb), vd, FALSE, FALSE, TRUE, vd->layout->options.dir_view_list_sort.method);
836                 menu_item_add_check(submenu, _("Ascending"), vd->layout->options.dir_view_list_sort.ascend, G_CALLBACK(vd_pop_menu_sort_ascend_cb), (vd));
837                 item = menu_item_add(menu, _("_Sort"), nullptr, nullptr);
838                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
839                 }
840
841         menu_item_add_divider(menu);
842
843         menu_item_add_check(menu, _("Show _hidden files"), options->file_filter.show_hidden_files,
844                             G_CALLBACK(vd_toggle_show_hidden_files_cb), vd);
845
846         menu_item_add_stock(menu, _("Re_fresh"), GTK_STOCK_REFRESH,
847                             G_CALLBACK(vd_pop_menu_refresh_cb), vd);
848
849         return menu;
850 }
851
852 void vd_new_folder(ViewDir *vd, FileData *dir_fd)
853 {
854         file_util_create_dir(dir_fd, vd->layout->window, vd_pop_menu_new_folder_cb, vd);
855 }
856
857 /*
858  *-----------------------------------------------------------------------------
859  * dnd
860  *-----------------------------------------------------------------------------
861  */
862
863 static GtkTargetEntry vd_dnd_drop_types[] = {
864         { const_cast<gchar *>("text/uri-list"), 0, TARGET_URI_LIST }
865 };
866 static gint vd_dnd_drop_types_count = 1;
867
868 static void vd_dest_set(ViewDir *vd, gint enable)
869 {
870         if (enable)
871                 {
872                 gtk_drag_dest_set(vd->view,
873                                   static_cast<GtkDestDefaults>(GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP),
874                                   vd_dnd_drop_types, vd_dnd_drop_types_count,
875                                   static_cast<GdkDragAction>(GDK_ACTION_MOVE | GDK_ACTION_COPY));
876                 }
877         else
878                 {
879                 gtk_drag_dest_unset(vd->view);
880                 }
881 }
882
883 static void vd_dnd_get(GtkWidget *, GdkDragContext *,
884                            GtkSelectionData *selection_data, guint info,
885                            guint, gpointer data)
886 {
887         auto vd = static_cast<ViewDir *>(data);
888         GList *list;
889
890         if (!vd->click_fd) return;
891
892         switch (info)
893                 {
894                 case TARGET_URI_LIST:
895                 case TARGET_TEXT_PLAIN:
896                         list = g_list_prepend(nullptr, vd->click_fd);
897                         uri_selection_data_set_uris_from_filelist(selection_data, list);
898                         g_list_free(list);
899                         break;
900                 }
901 }
902
903 static void vd_dnd_begin(GtkWidget *, GdkDragContext *, gpointer data)
904 {
905         auto vd = static_cast<ViewDir *>(data);
906
907         vd_color_set(vd, vd->click_fd, TRUE);
908         vd_dest_set(vd, FALSE);
909 }
910
911 static void vd_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
912 {
913         auto vd = static_cast<ViewDir *>(data);
914
915         vd_color_set(vd, vd->click_fd, FALSE);
916
917         if (vd->type == DIRVIEW_LIST && gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
918                 {
919                 vd_refresh(vd);
920                 }
921         vd_dest_set(vd, TRUE);
922 }
923
924 static void vd_dnd_drop_receive(GtkWidget *widget, GdkDragContext *,
925                                 gint x, gint y,
926                                 GtkSelectionData *selection_data, guint info,
927                                 guint, gpointer data)
928 {
929         auto vd = static_cast<ViewDir *>(data);
930         GtkTreePath *tpath;
931         FileData *fd = nullptr;
932         GdkDragAction action = GDK_ACTION_ASK;
933
934         vd->click_fd = nullptr;
935
936         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y,
937                                           &tpath, nullptr, nullptr, nullptr))
938                 {
939                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(widget), tpath);
940                 gtk_tree_path_free(tpath);
941                 }
942
943         if (!fd) return;
944
945         if (info == TARGET_URI_LIST)
946                 {
947                 GList *list;
948                 gint active;
949                 gboolean done = FALSE;
950
951                 list = uri_filelist_from_gtk_selection_data(selection_data);
952                 if (!list) return;
953
954                 active = access_file(fd->path, W_OK | X_OK);
955
956                 vd_color_set(vd, fd, TRUE);
957
958                 if (active)
959                         {
960 /** @FIXME With GTK2 gdk_drag_context_get_actions() shows the state of the
961  * shift and control keys during the drag operation. With GTK3 this is not
962  * so. This is a workaround.
963  */
964                         GdkModifierType mask;
965
966                         gdk_window_get_pointer(gtk_widget_get_window(widget), nullptr, nullptr, &mask);
967                         if (mask & GDK_CONTROL_MASK)
968                                 {
969                                 action = GDK_ACTION_COPY;
970                                 }
971                         else if (mask & GDK_SHIFT_MASK)
972                                 {
973                                 action = GDK_ACTION_MOVE;
974                                 }
975
976                         if (action != GDK_ACTION_COPY && action != GDK_ACTION_MOVE)
977                                 {
978                                 if (options->dnd_default_action == DND_ACTION_COPY)
979                                         {
980                                         action = GDK_ACTION_COPY;
981                                         }
982                                 else if (options->dnd_default_action == DND_ACTION_MOVE)
983                                         {
984                                         action = GDK_ACTION_MOVE;
985                                         }
986                                 }
987
988                         if (action == GDK_ACTION_COPY)
989                                 {
990                                 file_util_copy_simple(list, fd->path, vd->widget);
991                                 done = TRUE;
992                                 list = nullptr;
993                                 }
994                         else if (action == GDK_ACTION_MOVE)
995                                 {
996                                 file_util_move_simple(list, fd->path, vd->widget);
997                                 done = TRUE;
998                                 list = nullptr;
999                                 }
1000                         }
1001
1002                 if (done == FALSE)
1003                         {
1004                         vd->popup = vd_drop_menu(vd, active);
1005                         gtk_menu_popup_at_pointer(GTK_MENU(vd->popup), nullptr);
1006                         }
1007
1008                 vd->drop_fd = fd;
1009                 vd->drop_list = list;
1010                 }
1011 }
1012
1013 static void vd_dnd_drop_update(ViewDir *vd, gint x, gint y)
1014 {
1015         GtkTreePath *tpath;
1016         FileData *fd = nullptr;
1017
1018         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vd->view), x, y,
1019                                           &tpath, nullptr, nullptr, nullptr))
1020                 {
1021                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(vd->view), tpath);
1022                 gtk_tree_path_free(tpath);
1023                 }
1024
1025         if (fd != vd->drop_fd)
1026                 {
1027                 vd_color_set(vd, vd->drop_fd, FALSE);
1028                 vd_color_set(vd, fd, TRUE);
1029                 if (fd && vd->dnd_drop_update_func) vd->dnd_drop_update_func(vd);
1030                 }
1031
1032         vd->drop_fd = fd;
1033 }
1034
1035 void vd_dnd_drop_scroll_cancel(ViewDir *vd)
1036 {
1037         if (vd->drop_scroll_id)
1038                 {
1039                 g_source_remove(vd->drop_scroll_id);
1040                 vd->drop_scroll_id = 0;
1041                 }
1042 }
1043
1044 static gboolean vd_auto_scroll_idle_cb(gpointer data)
1045 {
1046         auto vd = static_cast<ViewDir *>(data);
1047         GdkSeat *seat;
1048         GdkDevice *device;
1049
1050         if (vd->drop_fd)
1051                 {
1052                 GdkWindow *window;
1053                 gint x, y;
1054                 gint w, h;
1055
1056                 window = gtk_widget_get_window(vd->view);
1057                 seat = gdk_display_get_default_seat(gdk_window_get_display(window));
1058                 device = gdk_seat_get_pointer(seat);
1059                 gdk_window_get_device_position(window, device, &x, &y, nullptr);
1060
1061                 w = gdk_window_get_width(window);
1062                 h = gdk_window_get_height(window);
1063                 if (x >= 0 && x < w && y >= 0 && y < h)
1064                         {
1065                         vd_dnd_drop_update(vd, x, y);
1066                         }
1067                 }
1068
1069         vd->drop_scroll_id = 0;
1070         return G_SOURCE_REMOVE;
1071 }
1072
1073 static gboolean vd_auto_scroll_notify_cb(GtkWidget *, gint, gint, gpointer data)
1074 {
1075         auto vd = static_cast<ViewDir *>(data);
1076
1077         if (!vd->drop_fd || vd->drop_list) return FALSE;
1078
1079         if (!vd->drop_scroll_id) vd->drop_scroll_id = g_idle_add(vd_auto_scroll_idle_cb, vd);
1080
1081         return TRUE;
1082 }
1083
1084 static gboolean vd_dnd_drop_motion(GtkWidget *, GdkDragContext *context, gint x, gint y, guint time, gpointer data)
1085 {
1086         auto vd = static_cast<ViewDir *>(data);
1087
1088         vd->click_fd = nullptr;
1089
1090         if (gtk_drag_get_source_widget(context) == vd->view)
1091                 {
1092                 /* from same window */
1093                 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1094                 return TRUE;
1095                 }
1096         else
1097                 {
1098                 gdk_drag_status(context, gdk_drag_context_get_suggested_action(context), time);
1099                 }
1100
1101         vd_dnd_drop_update(vd, x, y);
1102
1103         if (vd->drop_fd)
1104                 {
1105                 GtkAdjustment *adj = gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(vd->view));
1106                 widget_auto_scroll_start(vd->view, adj, -1, -1, vd_auto_scroll_notify_cb, vd);
1107                 }
1108
1109         return FALSE;
1110 }
1111
1112 static void vd_dnd_drop_leave(GtkWidget *, GdkDragContext *, guint, gpointer data)
1113 {
1114         auto vd = static_cast<ViewDir *>(data);
1115
1116         if (vd->drop_fd != vd->click_fd) vd_color_set(vd, vd->drop_fd, FALSE);
1117
1118         vd->drop_fd = nullptr;
1119
1120         if (vd->dnd_drop_leave_func) vd->dnd_drop_leave_func(vd);
1121 }
1122
1123 void vd_dnd_init(ViewDir *vd)
1124 {
1125         gtk_drag_source_set(vd->view, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
1126                             dnd_file_drag_types, dnd_file_drag_types_count,
1127                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK));
1128         g_signal_connect(G_OBJECT(vd->view), "drag_data_get",
1129                          G_CALLBACK(vd_dnd_get), vd);
1130         g_signal_connect(G_OBJECT(vd->view), "drag_begin",
1131                          G_CALLBACK(vd_dnd_begin), vd);
1132         g_signal_connect(G_OBJECT(vd->view), "drag_end",
1133                          G_CALLBACK(vd_dnd_end), vd);
1134
1135         vd_dest_set(vd, TRUE);
1136         g_signal_connect(G_OBJECT(vd->view), "drag_data_received",
1137                          G_CALLBACK(vd_dnd_drop_receive), vd);
1138         g_signal_connect(G_OBJECT(vd->view), "drag_motion",
1139                          G_CALLBACK(vd_dnd_drop_motion), vd);
1140         g_signal_connect(G_OBJECT(vd->view), "drag_leave",
1141                          G_CALLBACK(vd_dnd_drop_leave), vd);
1142 }
1143
1144 /*
1145  *----------------------------------------------------------------------------
1146  * callbacks
1147  *----------------------------------------------------------------------------
1148  */
1149
1150 #pragma GCC diagnostic push
1151 #pragma GCC diagnostic ignored "-Wunused-function"
1152 void vd_menu_position_cb_unused(GtkMenu *menu, gint *x, gint *y, gboolean *, gpointer data)
1153 {
1154         ViewDir *vd = static_cast<ViewDir *>(data);
1155         GtkTreeModel *store;
1156         GtkTreeIter iter;
1157         GtkTreePath *tpath;
1158         gint cw, ch;
1159
1160         if (!vd_find_row(vd, vd->click_fd, &iter)) return;
1161         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
1162         tpath = gtk_tree_model_get_path(store, &iter);
1163         tree_view_get_cell_clamped(GTK_TREE_VIEW(vd->view), tpath, 0, TRUE, x, y, &cw, &ch);
1164         gtk_tree_path_free(tpath);
1165         *y += ch;
1166         popup_menu_position_clamp(menu, x, y, 0);
1167 }
1168 #pragma GCC diagnostic pop
1169
1170 void vd_activate_cb(GtkTreeView *tview, GtkTreePath *tpath, GtkTreeViewColumn *, gpointer data)
1171 {
1172         auto vd = static_cast<ViewDir *>(data);
1173         FileData *fd = vd_get_fd_from_tree_path(vd, tview, tpath);
1174
1175         vd_select_row(vd, fd);
1176 }
1177
1178 static GdkColor *vd_color_shifted(GtkWidget *widget)
1179 {
1180         static GdkColor color;
1181         static GtkWidget *done = nullptr;
1182
1183         if (done != widget)
1184                 {
1185                 GtkStyle *style;
1186
1187                 style = gtk_widget_get_style(widget);
1188                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
1189                 shift_color(&color, -1, 0);
1190                 done = widget;
1191                 }
1192
1193         return &color;
1194 }
1195
1196 void vd_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1197 {
1198         auto vd = static_cast<ViewDir *>(data);
1199         gboolean set;
1200
1201         gtk_tree_model_get(tree_model, iter, DIR_COLUMN_COLOR, &set, -1);
1202         g_object_set(G_OBJECT(cell),
1203                      "cell-background-gdk", vd_color_shifted(vd->view),
1204                      "cell-background-set", set, NULL);
1205 }
1206
1207 gboolean vd_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1208 {
1209         auto vd = static_cast<ViewDir *>(data);
1210         GtkTreePath *tpath;
1211         FileData *fd = nullptr;
1212
1213         if (defined_mouse_buttons(widget, bevent, vd->layout))
1214                 {
1215                 return TRUE;
1216                 }
1217
1218         if (vd->type == DIRVIEW_LIST && !options->view_dir_list_single_click_enter)
1219                 return FALSE;
1220
1221         if (!vd->click_fd) return FALSE;
1222         vd_color_set(vd, vd->click_fd, FALSE);
1223
1224         if (bevent->button != MOUSE_BUTTON_LEFT) return TRUE;
1225
1226         if ((bevent->x != 0 || bevent->y != 0) &&
1227             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
1228                                           &tpath, nullptr, nullptr, nullptr))
1229                 {
1230                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(widget), tpath);
1231                 gtk_tree_path_free(tpath);
1232                 }
1233
1234         if (fd && vd->click_fd == fd)
1235                 {
1236                 vd_select_row(vd, vd->click_fd);
1237                 }
1238
1239         return FALSE;
1240 }
1241
1242 gboolean vd_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1243 {
1244         auto vd = static_cast<ViewDir *>(data);
1245         gboolean ret = FALSE;
1246
1247         switch (vd->type)
1248         {
1249         case DIRVIEW_LIST: ret = vdlist_press_key_cb(widget, event, data); break;
1250         case DIRVIEW_TREE: ret = vdtree_press_key_cb(widget, event, data); break;
1251         }
1252
1253         return ret;
1254 }
1255
1256 gboolean vd_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1257 {
1258         auto vd = static_cast<ViewDir *>(data);
1259         gboolean ret = FALSE;
1260         FileData *fd;
1261         GtkTreePath *tpath;
1262         GtkTreeIter iter;
1263         NodeData *nd = nullptr;
1264         GtkTreeModel *store;
1265
1266         if (bevent->button == MOUSE_BUTTON_RIGHT)
1267                 {
1268                 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y, &tpath, nullptr, nullptr, nullptr))
1269                         {
1270                         store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1271                         gtk_tree_model_get_iter(store, &iter, tpath);
1272
1273                         switch (vd->type)
1274                                 {
1275                                 case DIRVIEW_LIST:
1276                                         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &fd, -1);
1277                                         vd->click_fd = fd;
1278                                         break;
1279                                 case DIRVIEW_TREE:
1280                                         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
1281                                         vd->click_fd = (nd) ? nd->fd : nullptr;
1282                                 }
1283
1284                         if (vd->click_fd)
1285                                 {
1286                                 vd_color_set(vd, vd->click_fd, TRUE);
1287                                 }
1288                         }
1289
1290                 vd->popup = vd_pop_menu(vd, vd->click_fd);
1291                 gtk_menu_popup_at_pointer(GTK_MENU(vd->popup), nullptr);
1292
1293                 return TRUE;
1294                 }
1295
1296         switch (vd->type)
1297         {
1298         case DIRVIEW_LIST: ret = vdlist_press_cb(widget, bevent, data); break;
1299         case DIRVIEW_TREE: ret = vdtree_press_cb(widget, bevent, data); break;
1300         }
1301
1302         return ret;
1303 }
1304
1305 static void vd_notify_cb(FileData *fd, NotifyType type, gpointer data)
1306 {
1307         auto vd = static_cast<ViewDir *>(data);
1308         gboolean refresh;
1309         gchar *base;
1310
1311         if (!S_ISDIR(fd->mode)) return; /* this gives correct results even on recently deleted files/directories */
1312
1313         DEBUG_1("Notify vd: %s %04x", fd->path, type);
1314
1315         base = remove_level_from_path(fd->path);
1316
1317         if (vd->type == DIRVIEW_LIST)
1318                 {
1319                 refresh = (fd == vd->dir_fd);
1320
1321                 if (!refresh)
1322                         {
1323                         refresh = (strcmp(base, vd->dir_fd->path) == 0);
1324                         }
1325
1326                 if ((type & NOTIFY_CHANGE) && fd->change)
1327                         {
1328                         if (!refresh && fd->change->dest)
1329                                 {
1330                                 gchar *dest_base = remove_level_from_path(fd->change->dest);
1331                                 refresh = (strcmp(dest_base, vd->dir_fd->path) == 0);
1332                                 g_free(dest_base);
1333                                 }
1334
1335                         if (!refresh && fd->change->source)
1336                                 {
1337                                 gchar *source_base = remove_level_from_path(fd->change->source);
1338                                 refresh = (strcmp(source_base, vd->dir_fd->path) == 0);
1339                                 g_free(source_base);
1340                                 }
1341                         }
1342
1343                 if (refresh) vd_refresh(vd);
1344                 }
1345
1346         if (vd->type == DIRVIEW_TREE)
1347                 {
1348                 GtkTreeIter iter;
1349                 FileData *base_fd = file_data_new_dir(base);
1350
1351                 if (vd_find_row(vd, base_fd, &iter))
1352                         {
1353                         vdtree_populate_path_by_iter(vd, &iter, TRUE, vd->dir_fd);
1354                         }
1355
1356                 file_data_unref(base_fd);
1357                 }
1358
1359         g_free(base);
1360 }
1361 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */