68c6e52cca9d98584c043f2e98e503a7cdc8b10a
[geeqie.git] / src / view-file / view-file.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 "view-file.h"
22
23 #include <gdk/gdk.h>
24 #include <glib-object.h>
25
26 #include "archives.h"
27 #include "compat.h"
28 #include "debug.h"
29 #include "dnd.h"
30 #include "dupe.h"
31 #include "filedata.h"
32 #include "history-list.h"
33 #include "intl.h"
34 #include "layout.h"
35 #include "main-defines.h"
36 #include "main.h"
37 #include "menu.h"
38 #include "metadata.h"
39 #include "misc.h"
40 #include "options.h"
41 #include "thumb.h"
42 #include "ui-fileops.h"
43 #include "ui-menu.h"
44 #include "ui-misc.h"
45 #include "ui-utildlg.h"
46 #include "uri-utils.h"
47 #include "utilops.h"
48 #include "view-file/view-file-icon.h"
49 #include "view-file/view-file-list.h"
50 #include "window.h"
51
52 /*
53  *-----------------------------------------------------------------------------
54  * signals
55  *-----------------------------------------------------------------------------
56  */
57
58 void vf_send_update(ViewFile *vf)
59 {
60         if (vf->func_status) vf->func_status(vf, vf->data_status);
61 }
62
63 /*
64  *-----------------------------------------------------------------------------
65  * misc
66  *-----------------------------------------------------------------------------
67  */
68
69 void vf_sort_set(ViewFile *vf, SortType type, gboolean ascend, gboolean case_sensitive)
70 {
71         switch (vf->type)
72         {
73         case FILEVIEW_LIST: vflist_sort_set(vf, type, ascend, case_sensitive); break;
74         case FILEVIEW_ICON: vficon_sort_set(vf, type, ascend, case_sensitive); break;
75         }
76 }
77
78 /*
79  *-----------------------------------------------------------------------------
80  * row stuff
81  *-----------------------------------------------------------------------------
82  */
83
84 FileData *vf_index_get_data(ViewFile *vf, gint row)
85 {
86         return static_cast<FileData *>(g_list_nth_data(vf->list, row));
87 }
88
89 gint vf_index_by_fd(ViewFile *vf, FileData *fd)
90 {
91         gint ret;
92
93         switch (vf->type)
94         {
95         case FILEVIEW_LIST: ret = vflist_index_by_fd(vf, fd); break;
96         case FILEVIEW_ICON: ret = vficon_index_by_fd(vf, fd); break;
97         default: ret = 0;
98         }
99
100         return ret;
101 }
102
103 guint vf_count(ViewFile *vf, gint64 *bytes)
104 {
105         if (bytes)
106                 {
107                 gint64 b = 0;
108                 GList *work;
109
110                 work = vf->list;
111                 while (work)
112                         {
113                         auto fd = static_cast<FileData *>(work->data);
114                         work = work->next;
115
116                         b += fd->size;
117                         }
118
119                 *bytes = b;
120                 }
121
122         return g_list_length(vf->list);
123 }
124
125 GList *vf_get_list(ViewFile *vf)
126 {
127         return filelist_copy(vf->list);
128 }
129
130 /*
131  *-------------------------------------------------------------------
132  * keyboard
133  *-------------------------------------------------------------------
134  */
135
136 static gboolean vf_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
137 {
138         auto vf = static_cast<ViewFile *>(data);
139         gboolean ret;
140
141         switch (vf->type)
142         {
143         case FILEVIEW_LIST: ret = vflist_press_key_cb(vf, widget, event); break;
144         case FILEVIEW_ICON: ret = vficon_press_key_cb(vf, widget, event); break;
145         default: ret = FALSE;
146         }
147
148         return ret;
149 }
150
151 /*
152  *-------------------------------------------------------------------
153  * mouse
154  *-------------------------------------------------------------------
155  */
156
157 static gboolean vf_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
158 {
159         auto vf = static_cast<ViewFile *>(data);
160         gboolean ret;
161
162         switch (vf->type)
163         {
164         case FILEVIEW_LIST: ret = vflist_press_cb(vf, widget, bevent); break;
165         case FILEVIEW_ICON: ret = vficon_press_cb(vf, widget, bevent); break;
166         default: ret = FALSE;
167         }
168
169         return ret;
170 }
171
172 static gboolean vf_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
173 {
174         auto vf = static_cast<ViewFile *>(data);
175         gboolean ret;
176
177         switch (vf->type)
178         {
179         case FILEVIEW_LIST: ret = vflist_release_cb(vf, widget, bevent); break;
180         case FILEVIEW_ICON: ret = vficon_release_cb(vf, widget, bevent); break;
181         default: ret = FALSE;
182         }
183
184         return ret;
185 }
186
187
188 /*
189  *-----------------------------------------------------------------------------
190  * selections
191  *-----------------------------------------------------------------------------
192  */
193
194 guint vf_selection_count(ViewFile *vf, gint64 *bytes)
195 {
196         guint ret;
197
198         switch (vf->type)
199         {
200         case FILEVIEW_LIST: ret = vflist_selection_count(vf, bytes); break;
201         case FILEVIEW_ICON: ret = vficon_selection_count(vf, bytes); break;
202         default: ret = 0;
203         }
204
205         return ret;
206 }
207
208 GList *vf_selection_get_list(ViewFile *vf)
209 {
210         GList *ret;
211
212         switch (vf->type)
213         {
214         case FILEVIEW_LIST: ret = vflist_selection_get_list(vf); break;
215         case FILEVIEW_ICON: ret = vficon_selection_get_list(vf); break;
216         default: ret = nullptr;
217         }
218
219         return ret;
220 }
221
222 GList *vf_selection_get_list_by_index(ViewFile *vf)
223 {
224         GList *ret;
225
226         switch (vf->type)
227         {
228         case FILEVIEW_LIST: ret = vflist_selection_get_list_by_index(vf); break;
229         case FILEVIEW_ICON: ret = vficon_selection_get_list_by_index(vf); break;
230         default: ret = nullptr;
231         }
232
233         return ret;
234 }
235
236 void vf_selection_foreach(ViewFile *vf, const ViewFile::SelectionCallback &func)
237 {
238         if (!vf) return;
239
240         switch (vf->type)
241         {
242         case FILEVIEW_LIST: vflist_selection_foreach(vf, func); break;
243         case FILEVIEW_ICON: vficon_selection_foreach(vf, func); break;
244         }
245 }
246
247 void vf_select_all(ViewFile *vf)
248 {
249         switch (vf->type)
250         {
251         case FILEVIEW_LIST: vflist_select_all(vf); break;
252         case FILEVIEW_ICON: vficon_select_all(vf); break;
253         }
254 }
255
256 void vf_select_none(ViewFile *vf)
257 {
258         switch (vf->type)
259         {
260         case FILEVIEW_LIST: vflist_select_none(vf); break;
261         case FILEVIEW_ICON: vficon_select_none(vf); break;
262         }
263 }
264
265 void vf_select_invert(ViewFile *vf)
266 {
267         switch (vf->type)
268         {
269         case FILEVIEW_LIST: vflist_select_invert(vf); break;
270         case FILEVIEW_ICON: vficon_select_invert(vf); break;
271         }
272 }
273
274 void vf_select_by_fd(ViewFile *vf, FileData *fd)
275 {
276         switch (vf->type)
277         {
278         case FILEVIEW_LIST: vflist_select_by_fd(vf, fd); break;
279         case FILEVIEW_ICON: vficon_select_by_fd(vf, fd); break;
280         }
281 }
282
283 void vf_select_list(ViewFile *vf, GList *list)
284 {
285         switch (vf->type)
286         {
287         case FILEVIEW_LIST: vflist_select_list(vf, list); break;
288         case FILEVIEW_ICON: vficon_select_list(vf, list); break;
289         }
290 }
291
292 void vf_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
293 {
294         switch (vf->type)
295         {
296         case FILEVIEW_LIST: vflist_mark_to_selection(vf, mark, mode); break;
297         case FILEVIEW_ICON: vficon_mark_to_selection(vf, mark, mode); break;
298         }
299 }
300
301 void vf_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
302 {
303         switch (vf->type)
304         {
305         case FILEVIEW_LIST: vflist_selection_to_mark(vf, mark, mode); break;
306         case FILEVIEW_ICON: vficon_selection_to_mark(vf, mark, mode); break;
307         }
308 }
309
310 /*
311  *-----------------------------------------------------------------------------
312  * dnd
313  *-----------------------------------------------------------------------------
314  */
315
316 static gboolean vf_is_selected(ViewFile *vf, FileData *fd)
317 {
318         switch (vf->type)
319         {
320         case FILEVIEW_LIST: return vflist_is_selected(vf, fd);
321         case FILEVIEW_ICON: return vficon_is_selected(vf, fd);
322         }
323
324         return FALSE;
325 }
326
327 static void vf_dnd_get(GtkWidget *, GdkDragContext *,
328                        GtkSelectionData *selection_data, guint,
329                        guint, gpointer data)
330 {
331         auto *vf = static_cast<ViewFile *>(data);
332
333         if (!vf->click_fd) return;
334
335         GList *list = nullptr;
336
337         if (vf_is_selected(vf, vf->click_fd))
338                 {
339                 list = vf_selection_get_list(vf);
340                 }
341         else
342                 {
343                 list = g_list_append(nullptr, file_data_ref(vf->click_fd));
344                 }
345
346         if (!list) return;
347
348         uri_selection_data_set_uris_from_filelist(selection_data, list);
349         filelist_free(list);
350 }
351
352 static void vf_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
353 {
354         auto *vf = static_cast<ViewFile *>(data);
355
356         switch (vf->type)
357         {
358         case FILEVIEW_LIST: vflist_dnd_begin(vf, widget, context); break;
359         case FILEVIEW_ICON: vficon_dnd_begin(vf, widget, context); break;
360         }
361 }
362
363 static void vf_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
364 {
365         auto *vf = static_cast<ViewFile *>(data);
366
367         switch (vf->type)
368         {
369         case FILEVIEW_LIST: vflist_dnd_end(vf, context); break;
370         case FILEVIEW_ICON: vficon_dnd_end(vf, context); break;
371         }
372 }
373
374 static FileData *vf_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *iter)
375 {
376         switch (vf->type)
377         {
378         case FILEVIEW_LIST: return vflist_find_data_by_coord(vf, x, y, iter);
379         case FILEVIEW_ICON: return vficon_find_data_by_coord(vf, x, y, iter);
380         }
381
382         return nullptr;
383 }
384
385 static void vf_drag_data_received(GtkWidget *, GdkDragContext *,
386                                   int x, int y, GtkSelectionData *selection,
387                                   guint info, guint, gpointer data)
388 {
389         if (info != TARGET_TEXT_PLAIN) return;
390
391         auto *vf = static_cast<ViewFile *>(data);
392
393         FileData *fd = vf_find_data_by_coord(vf, x, y, nullptr);
394         if (!fd) return;
395
396         /* Add keywords to file */
397         auto str = reinterpret_cast<gchar *>(gtk_selection_data_get_text(selection));
398         GList *kw_list = string_to_keywords_list(str);
399
400         metadata_append_list(fd, KEYWORD_KEY, kw_list);
401
402         g_list_free_full(kw_list, g_free);
403         g_free(str);
404 }
405
406 static void vf_dnd_init(ViewFile *vf)
407 {
408         gtk_drag_source_set(vf->listview, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
409                             dnd_file_drag_types, dnd_file_drag_types_count,
410                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
411         gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
412                           dnd_file_drag_types, dnd_file_drag_types_count,
413                           static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
414
415         g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
416                          G_CALLBACK(vf_dnd_get), vf);
417         g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
418                          G_CALLBACK(vf_dnd_begin), vf);
419         g_signal_connect(G_OBJECT(vf->listview), "drag_end",
420                          G_CALLBACK(vf_dnd_end), vf);
421         g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
422                          G_CALLBACK(vf_drag_data_received), vf);
423 }
424
425 /*
426  *-----------------------------------------------------------------------------
427  * pop-up menu
428  *-----------------------------------------------------------------------------
429  */
430
431 GList *vf_pop_menu_file_list(ViewFile *vf)
432 {
433         GList *ret;
434
435         switch (vf->type)
436         {
437         case FILEVIEW_LIST: ret = vflist_pop_menu_file_list(vf); break;
438         case FILEVIEW_ICON: ret = vficon_pop_menu_file_list(vf); break;
439         default: ret = nullptr;
440         }
441
442         return ret;
443 }
444
445 GList *vf_selection_get_one(ViewFile *vf, FileData *fd)
446 {
447         GList *ret;
448
449         switch (vf->type)
450         {
451         case FILEVIEW_LIST: ret = vflist_selection_get_one(vf, fd); break;
452         case FILEVIEW_ICON: ret = vficon_selection_get_one(vf, fd); break;
453         default: ret = nullptr;
454         }
455
456         return ret;
457 }
458
459 static void vf_pop_menu_edit_cb(GtkWidget *widget, gpointer data)
460 {
461         ViewFile *vf;
462         auto key = static_cast<const gchar *>(data);
463
464         vf = static_cast<ViewFile *>(submenu_item_get_data(widget));
465
466         if (!vf) return;
467
468         file_util_start_editor_from_filelist(key, vf_pop_menu_file_list(vf), vf->dir_fd->path, vf->listview);
469 }
470
471 static void vf_pop_menu_view_cb(GtkWidget *, gpointer data)
472 {
473         auto vf = static_cast<ViewFile *>(data);
474
475         switch (vf->type)
476         {
477         case FILEVIEW_LIST: vflist_pop_menu_view_cb(vf); break;
478         case FILEVIEW_ICON: vficon_pop_menu_view_cb(vf); break;
479         }
480 }
481
482 static void vf_pop_menu_open_archive_cb(GtkWidget *, gpointer data)
483 {
484         auto vf = static_cast<ViewFile *>(data);
485         LayoutWindow *lw_new;
486         gchar *dest_dir;
487
488         dest_dir = open_archive(vf->click_fd);
489         if (dest_dir)
490                 {
491                 lw_new = layout_new_from_default();
492                 layout_set_path(lw_new, dest_dir);
493                 g_free(dest_dir);
494                 }
495         else
496                 {
497                 warning_dialog(_("Cannot open archive file"), _("See the Log Window"), GQ_ICON_DIALOG_WARNING, nullptr);
498                 }
499 }
500
501 static void vf_pop_menu_copy_cb(GtkWidget *, gpointer data)
502 {
503         auto vf = static_cast<ViewFile *>(data);
504
505         file_util_copy(nullptr, vf_pop_menu_file_list(vf), nullptr, vf->listview);
506 }
507
508 static void vf_pop_menu_move_cb(GtkWidget *, gpointer data)
509 {
510         auto vf = static_cast<ViewFile *>(data);
511
512         file_util_move(nullptr, vf_pop_menu_file_list(vf), nullptr, vf->listview);
513 }
514
515 static void vf_pop_menu_rename_cb(GtkWidget *, gpointer data)
516 {
517         auto vf = static_cast<ViewFile *>(data);
518
519         switch (vf->type)
520         {
521         case FILEVIEW_LIST: vflist_pop_menu_rename_cb(vf); break;
522         case FILEVIEW_ICON: vficon_pop_menu_rename_cb(vf); break;
523         }
524 }
525
526 static void vf_pop_menu_delete_cb(GtkWidget *, gpointer data)
527 {
528         auto vf = static_cast<ViewFile *>(data);
529
530         options->file_ops.safe_delete_enable = FALSE;
531         file_util_delete(nullptr, vf_pop_menu_file_list(vf), vf->listview);
532 }
533
534 static void vf_pop_menu_move_to_trash_cb(GtkWidget *, gpointer data)
535 {
536         auto vf = static_cast<ViewFile *>(data);
537
538         options->file_ops.safe_delete_enable = TRUE;
539         file_util_delete(nullptr, vf_pop_menu_file_list(vf), vf->listview);
540 }
541
542 static void vf_pop_menu_copy_path_cb(GtkWidget *, gpointer data)
543 {
544         auto vf = static_cast<ViewFile *>(data);
545
546         file_util_path_list_to_clipboard(vf_pop_menu_file_list(vf), TRUE, TRUE);
547 }
548
549 static void vf_pop_menu_copy_path_unquoted_cb(GtkWidget *, gpointer data)
550 {
551         auto vf = static_cast<ViewFile *>(data);
552
553         file_util_path_list_to_clipboard(vf_pop_menu_file_list(vf), FALSE, TRUE);
554 }
555
556 static void vf_pop_menu_cut_path_cb(GtkWidget *, gpointer data)
557 {
558         auto vf = static_cast<ViewFile *>(data);
559
560         file_util_path_list_to_clipboard(vf_pop_menu_file_list(vf), FALSE, FALSE);
561 }
562
563 static void vf_pop_menu_enable_grouping_cb(GtkWidget *, gpointer data)
564 {
565         auto vf = static_cast<ViewFile *>(data);
566
567         file_data_disable_grouping_list(vf_pop_menu_file_list(vf), FALSE);
568 }
569
570 static void vf_pop_menu_duplicates_cb(GtkWidget *, gpointer data)
571 {
572         auto vf = static_cast<ViewFile *>(data);
573         DupeWindow *dw;
574
575         dw = dupe_window_new();
576         dupe_window_add_files(dw, vf_pop_menu_file_list(vf), FALSE);
577 }
578
579 static void vf_pop_menu_disable_grouping_cb(GtkWidget *, gpointer data)
580 {
581         auto vf = static_cast<ViewFile *>(data);
582
583         file_data_disable_grouping_list(vf_pop_menu_file_list(vf), TRUE);
584 }
585
586 static void vf_pop_menu_sort_cb(GtkWidget *widget, gpointer data)
587 {
588         ViewFile *vf;
589         SortType type;
590
591         if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) return;
592
593         vf = static_cast<ViewFile *>(submenu_item_get_data(widget));
594         if (!vf) return;
595
596         type = static_cast<SortType>GPOINTER_TO_INT(data);
597
598         if (type == SORT_EXIFTIME || type == SORT_EXIFTIMEDIGITIZED || type == SORT_RATING)
599                 {
600                 vf_read_metadata_in_idle(vf);
601                 }
602
603         if (vf->layout)
604                 {
605                 layout_sort_set_files(vf->layout, type, vf->sort_ascend, vf->sort_case);
606                 }
607         else
608                 {
609                 vf_sort_set(vf, type, vf->sort_ascend, vf->sort_case);
610                 }
611 }
612
613 static void vf_pop_menu_sort_ascend_cb(GtkWidget *, gpointer data)
614 {
615         auto vf = static_cast<ViewFile *>(data);
616
617         if (vf->layout)
618                 {
619                 layout_sort_set_files(vf->layout, vf->sort_method, !vf->sort_ascend, vf->sort_case);
620                 }
621         else
622                 {
623                 vf_sort_set(vf, vf->sort_method, !vf->sort_ascend, vf->sort_case);
624                 }
625 }
626
627 static void vf_pop_menu_sort_case_cb(GtkWidget *, gpointer data)
628 {
629         auto vf = static_cast<ViewFile *>(data);
630
631         if (vf->layout)
632                 {
633                 layout_sort_set_files(vf->layout, vf->sort_method, vf->sort_ascend, !vf->sort_case);
634                 }
635         else
636                 {
637                 vf_sort_set(vf, vf->sort_method, vf->sort_ascend, !vf->sort_case);
638                 }
639 }
640
641 static void vf_pop_menu_sel_mark_cb(GtkWidget *, gpointer data)
642 {
643         auto vf = static_cast<ViewFile *>(data);
644         vf_mark_to_selection(vf, vf->active_mark, MTS_MODE_SET);
645 }
646
647 static void vf_pop_menu_sel_mark_and_cb(GtkWidget *, gpointer data)
648 {
649         auto vf = static_cast<ViewFile *>(data);
650         vf_mark_to_selection(vf, vf->active_mark, MTS_MODE_AND);
651 }
652
653 static void vf_pop_menu_sel_mark_or_cb(GtkWidget *, gpointer data)
654 {
655         auto vf = static_cast<ViewFile *>(data);
656         vf_mark_to_selection(vf, vf->active_mark, MTS_MODE_OR);
657 }
658
659 static void vf_pop_menu_sel_mark_minus_cb(GtkWidget *, gpointer data)
660 {
661         auto vf = static_cast<ViewFile *>(data);
662         vf_mark_to_selection(vf, vf->active_mark, MTS_MODE_MINUS);
663 }
664
665 static void vf_pop_menu_set_mark_sel_cb(GtkWidget *, gpointer data)
666 {
667         auto vf = static_cast<ViewFile *>(data);
668         vf_selection_to_mark(vf, vf->active_mark, STM_MODE_SET);
669 }
670
671 static void vf_pop_menu_res_mark_sel_cb(GtkWidget *, gpointer data)
672 {
673         auto vf = static_cast<ViewFile *>(data);
674         vf_selection_to_mark(vf, vf->active_mark, STM_MODE_RESET);
675 }
676
677 static void vf_pop_menu_toggle_mark_sel_cb(GtkWidget *, gpointer data)
678 {
679         auto vf = static_cast<ViewFile *>(data);
680         vf_selection_to_mark(vf, vf->active_mark, STM_MODE_TOGGLE);
681 }
682
683 static void vf_pop_menu_toggle_view_type_cb(GtkWidget *widget, gpointer data)
684 {
685         auto vf = static_cast<ViewFile *>(data);
686         auto new_type = static_cast<FileViewType>(GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "menu_item_radio_data")));
687         if (!vf->layout) return;
688
689         layout_views_set(vf->layout, vf->layout->options.dir_view_type, new_type);
690 }
691
692 static void vf_pop_menu_refresh_cb(GtkWidget *, gpointer data)
693 {
694         auto vf = static_cast<ViewFile *>(data);
695
696         switch (vf->type)
697         {
698         case FILEVIEW_LIST: vflist_pop_menu_refresh_cb(vf); break;
699         case FILEVIEW_ICON: vficon_pop_menu_refresh_cb(vf); break;
700         }
701 }
702
703 static void vf_popup_destroy_cb(GtkWidget *, gpointer data)
704 {
705         auto vf = static_cast<ViewFile *>(data);
706
707         switch (vf->type)
708         {
709         case FILEVIEW_LIST: vflist_popup_destroy_cb(vf); break;
710         case FILEVIEW_ICON: vficon_popup_destroy_cb(vf); break;
711         }
712
713         vf->click_fd = nullptr;
714         vf->popup = nullptr;
715
716         filelist_free(vf->editmenu_fd_list);
717         vf->editmenu_fd_list = nullptr;
718 }
719
720 /**
721  * @brief Add file selection list to a collection
722  * @param[in] widget
723  * @param[in] data Index to the collection list menu item selected, or -1 for new collection
724  *
725  *
726  */
727 static void vf_pop_menu_collections_cb(GtkWidget *widget, gpointer data)
728 {
729         ViewFile *vf;
730         GList *selection_list;
731
732         vf = static_cast<ViewFile *>(submenu_item_get_data(widget));
733         selection_list = vf_selection_get_list(vf);
734         pop_menu_collections(selection_list, data);
735
736         filelist_free(selection_list);
737 }
738
739 static void vf_pop_menu_show_star_rating_cb(GtkWidget *, gpointer data)
740 {
741         auto *vf = static_cast<ViewFile *>(data);
742
743         options->show_star_rating = !options->show_star_rating;
744
745         switch (vf->type)
746         {
747         case FILEVIEW_LIST: vflist_pop_menu_show_star_rating_cb(vf); break;
748         case FILEVIEW_ICON: vficon_pop_menu_show_star_rating_cb(vf); break;
749         }
750 }
751
752 GtkWidget *vf_pop_menu(ViewFile *vf)
753 {
754         GtkWidget *menu;
755         GtkWidget *item;
756         GtkWidget *submenu;
757         gboolean active = FALSE;
758         gboolean class_archive = FALSE;
759         GtkAccelGroup *accel_group;
760
761         if (vf->type == FILEVIEW_LIST)
762                 {
763                 vflist_color_set(vf, vf->click_fd, TRUE);
764                 }
765
766         active = (vf->click_fd != nullptr);
767         class_archive = (vf->click_fd != nullptr && vf->click_fd->format_class == FORMAT_CLASS_ARCHIVE);
768
769         menu = popup_menu_short_lived();
770
771         accel_group = gtk_accel_group_new();
772         gtk_menu_set_accel_group(GTK_MENU(menu), accel_group);
773
774         g_object_set_data(G_OBJECT(menu), "window_keys", nullptr);
775         g_object_set_data(G_OBJECT(menu), "accel_group", accel_group);
776
777         g_signal_connect(G_OBJECT(menu), "destroy",
778                          G_CALLBACK(vf_popup_destroy_cb), vf);
779
780         if (vf->clicked_mark > 0)
781                 {
782                 gint mark = vf->clicked_mark;
783                 gchar *str_set_mark = g_strdup_printf(_("_Set mark %d"), mark);
784                 gchar *str_res_mark = g_strdup_printf(_("_Reset mark %d"), mark);
785                 gchar *str_toggle_mark = g_strdup_printf(_("_Toggle mark %d"), mark);
786                 gchar *str_sel_mark = g_strdup_printf(_("_Select mark %d"), mark);
787                 gchar *str_sel_mark_or = g_strdup_printf(_("_Add mark %d"), mark);
788                 gchar *str_sel_mark_and = g_strdup_printf(_("_Intersection with mark %d"), mark);
789                 gchar *str_sel_mark_minus = g_strdup_printf(_("_Unselect mark %d"), mark);
790
791                 g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
792
793                 vf->active_mark = mark;
794                 vf->clicked_mark = 0;
795
796                 menu_item_add_sensitive(menu, str_set_mark, active,
797                                         G_CALLBACK(vf_pop_menu_set_mark_sel_cb), vf);
798
799                 menu_item_add_sensitive(menu, str_res_mark, active,
800                                         G_CALLBACK(vf_pop_menu_res_mark_sel_cb), vf);
801
802                 menu_item_add_sensitive(menu, str_toggle_mark, active,
803                                         G_CALLBACK(vf_pop_menu_toggle_mark_sel_cb), vf);
804
805                 menu_item_add_divider(menu);
806
807                 menu_item_add_sensitive(menu, str_sel_mark, active,
808                                         G_CALLBACK(vf_pop_menu_sel_mark_cb), vf);
809                 menu_item_add_sensitive(menu, str_sel_mark_or, active,
810                                         G_CALLBACK(vf_pop_menu_sel_mark_or_cb), vf);
811                 menu_item_add_sensitive(menu, str_sel_mark_and, active,
812                                         G_CALLBACK(vf_pop_menu_sel_mark_and_cb), vf);
813                 menu_item_add_sensitive(menu, str_sel_mark_minus, active,
814                                         G_CALLBACK(vf_pop_menu_sel_mark_minus_cb), vf);
815
816                 menu_item_add_divider(menu);
817
818                 g_free(str_set_mark);
819                 g_free(str_res_mark);
820                 g_free(str_toggle_mark);
821                 g_free(str_sel_mark);
822                 g_free(str_sel_mark_and);
823                 g_free(str_sel_mark_or);
824                 g_free(str_sel_mark_minus);
825                 }
826
827         vf->editmenu_fd_list = vf_pop_menu_file_list(vf);
828         submenu_add_edit(menu, &item, G_CALLBACK(vf_pop_menu_edit_cb), vf, vf->editmenu_fd_list);
829         gtk_widget_set_sensitive(item, active);
830
831         menu_item_add_icon_sensitive(menu, _("View in _new window"), GQ_ICON_NEW, active,
832                                       G_CALLBACK(vf_pop_menu_view_cb), vf);
833
834         menu_item_add_icon_sensitive(menu, _("Open archive"), GQ_ICON_OPEN, active & class_archive, G_CALLBACK(vf_pop_menu_open_archive_cb), vf);
835
836         menu_item_add_divider(menu);
837         menu_item_add_icon_sensitive(menu, _("_Copy..."), GQ_ICON_COPY, active,
838                                       G_CALLBACK(vf_pop_menu_copy_cb), vf);
839         menu_item_add_sensitive(menu, _("_Move..."), active,
840                                 G_CALLBACK(vf_pop_menu_move_cb), vf);
841         menu_item_add_sensitive(menu, _("_Rename..."), active,
842                                 G_CALLBACK(vf_pop_menu_rename_cb), vf);
843         menu_item_add_sensitive(menu, _("_Copy to clipboard"), active,
844                                 G_CALLBACK(vf_pop_menu_copy_path_cb), vf);
845         menu_item_add_sensitive(menu, _("_Copy to clipboard (unquoted)"), active,
846                                 G_CALLBACK(vf_pop_menu_copy_path_unquoted_cb), vf);
847         menu_item_add_sensitive(menu, _("_Cut to clipboard"), active,
848                                 G_CALLBACK(vf_pop_menu_cut_path_cb), vf);
849         menu_item_add_divider(menu);
850         menu_item_add_icon_sensitive(menu,
851                                 options->file_ops.confirm_move_to_trash ? _("Move to Trash...") :
852                                         _("Move to Trash"), GQ_ICON_DELETE, active,
853                                 G_CALLBACK(vf_pop_menu_move_to_trash_cb), vf);
854         menu_item_add_icon_sensitive(menu,
855                                 options->file_ops.confirm_delete ? _("_Delete...") :
856                                         _("_Delete"), GQ_ICON_DELETE_SHRED, active,
857                                 G_CALLBACK(vf_pop_menu_delete_cb), vf);
858         menu_item_add_divider(menu);
859
860         menu_item_add_sensitive(menu, _("Enable file _grouping"), active,
861                                 G_CALLBACK(vf_pop_menu_enable_grouping_cb), vf);
862         menu_item_add_sensitive(menu, _("Disable file groupi_ng"), active,
863                                 G_CALLBACK(vf_pop_menu_disable_grouping_cb), vf);
864
865         menu_item_add_divider(menu);
866         menu_item_add_icon_sensitive(menu, _("_Find duplicates..."), GQ_ICON_FIND, active,
867                                 G_CALLBACK(vf_pop_menu_duplicates_cb), vf);
868         menu_item_add_divider(menu);
869
870         submenu = submenu_add_collections(menu, &item,
871                                 G_CALLBACK(vf_pop_menu_collections_cb), vf);
872         gtk_widget_set_sensitive(item, active);
873         menu_item_add_divider(menu);
874
875         submenu = submenu_add_sort(nullptr, G_CALLBACK(vf_pop_menu_sort_cb), vf,
876                                    FALSE, FALSE, TRUE, vf->sort_method);
877         menu_item_add_divider(submenu);
878         menu_item_add_check(submenu, _("Ascending"), vf->sort_ascend,
879                             G_CALLBACK(vf_pop_menu_sort_ascend_cb), vf);
880         menu_item_add_check(submenu, _("Case"), vf->sort_ascend,
881                             G_CALLBACK(vf_pop_menu_sort_case_cb), vf);
882
883         item = menu_item_add(menu, _("_Sort"), nullptr, nullptr);
884         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
885
886         item = menu_item_add_radio(menu, _("Images as List"), GINT_TO_POINTER(FILEVIEW_LIST), vf->type == FILEVIEW_LIST,
887                                            G_CALLBACK(vf_pop_menu_toggle_view_type_cb), vf);
888
889         item = menu_item_add_radio(menu, _("Images as Icons"), GINT_TO_POINTER(FILEVIEW_ICON), vf->type == FILEVIEW_ICON,
890                                            G_CALLBACK(vf_pop_menu_toggle_view_type_cb), vf);
891
892         switch (vf->type)
893         {
894         case FILEVIEW_LIST: vflist_pop_menu_add_items(vf, menu); break;
895         case FILEVIEW_ICON: vficon_pop_menu_add_items(vf, menu); break;
896         }
897
898         menu_item_add_check(menu, _("Show star rating"), options->show_star_rating,
899                             G_CALLBACK(vf_pop_menu_show_star_rating_cb), vf);
900
901         menu_item_add_icon(menu, _("Re_fresh"), GQ_ICON_REFRESH, G_CALLBACK(vf_pop_menu_refresh_cb), vf);
902
903         return menu;
904 }
905
906 gboolean vf_refresh(ViewFile *vf)
907 {
908         gboolean ret;
909
910         switch (vf->type)
911         {
912         case FILEVIEW_LIST: ret = vflist_refresh(vf); break;
913         case FILEVIEW_ICON: ret = vficon_refresh(vf); break;
914         default: ret = FALSE;
915         }
916
917         return ret;
918 }
919
920 gboolean vf_set_fd(ViewFile *vf, FileData *dir_fd)
921 {
922         gboolean ret;
923
924         switch (vf->type)
925         {
926         case FILEVIEW_LIST: ret = vflist_set_fd(vf, dir_fd); break;
927         case FILEVIEW_ICON: ret = vficon_set_fd(vf, dir_fd); break;
928         default: ret = FALSE;
929         }
930
931         return ret;
932 }
933
934 static void vf_destroy_cb(GtkWidget *, gpointer data)
935 {
936         auto vf = static_cast<ViewFile *>(data);
937
938         switch (vf->type)
939         {
940         case FILEVIEW_LIST: vflist_destroy_cb(vf); break;
941         case FILEVIEW_ICON: vficon_destroy_cb(vf); break;
942         }
943
944         if (vf->popup)
945                 {
946                 g_signal_handlers_disconnect_matched(G_OBJECT(vf->popup), G_SIGNAL_MATCH_DATA,
947                                                      0, 0, nullptr, nullptr, vf);
948                 gq_gtk_widget_destroy(vf->popup);
949                 }
950
951         if (vf->read_metadata_in_idle_id)
952                 {
953                 g_idle_remove_by_data(vf);
954                 }
955         file_data_unref(vf->dir_fd);
956         g_free(vf->info);
957         g_free(vf);
958 }
959
960 static void vf_marks_filter_toggle_cb(GtkWidget *, gpointer data)
961 {
962         auto vf = static_cast<ViewFile *>(data);
963         vf_refresh_idle(vf);
964 }
965
966 struct MarksTextEntry {
967         GenericDialog *gd;
968         gint mark_no;
969         GtkWidget *edit_widget;
970         gchar *text_entry;
971         GtkWidget *parent;
972 };
973
974 static void vf_marks_tooltip_cancel_cb(GenericDialog *gd, gpointer data)
975 {
976         auto mte = static_cast<MarksTextEntry *>(data);
977
978         g_free(mte->text_entry);
979         generic_dialog_close(gd);
980 }
981
982 static void vf_marks_tooltip_ok_cb(GenericDialog *gd, gpointer data)
983 {
984         auto mte = static_cast<MarksTextEntry *>(data);
985
986         g_free(options->marks_tooltips[mte->mark_no]);
987         options->marks_tooltips[mte->mark_no] = g_strdup(gq_gtk_entry_get_text(GTK_ENTRY(mte->edit_widget)));
988
989         gtk_widget_set_tooltip_text(mte->parent, options->marks_tooltips[mte->mark_no]);
990
991         g_free(mte->text_entry);
992         generic_dialog_close(gd);
993 }
994
995 void vf_marks_filter_on_icon_press(GtkEntry *, GtkEntryIconPosition, GdkEvent *, gpointer userdata)
996 {
997         auto mte = static_cast<MarksTextEntry *>(userdata);
998
999         g_free(mte->text_entry);
1000         mte->text_entry = g_strdup("");
1001         gq_gtk_entry_set_text(GTK_ENTRY(mte->edit_widget), "");
1002 }
1003
1004 static void vf_marks_tooltip_help_cb(GenericDialog *, gpointer)
1005 {
1006         help_window_show("GuideImageMarks.html");
1007 }
1008
1009 static gboolean vf_marks_tooltip_cb(GtkWidget *widget,
1010                                                                                 GdkEventButton *event,
1011                                                                                 gpointer user_data)
1012 {
1013         GtkWidget *table;
1014         gint i = GPOINTER_TO_INT(user_data);
1015
1016         if (event->button != MOUSE_BUTTON_RIGHT)
1017                 return FALSE;
1018
1019         auto mte = g_new0(MarksTextEntry, 1);
1020         mte->mark_no = i;
1021         mte->text_entry = g_strdup(options->marks_tooltips[i]);
1022         mte->parent = widget;
1023
1024         mte->gd = generic_dialog_new(_("Mark text"), "mark_text",
1025                                      widget, FALSE,
1026                                      vf_marks_tooltip_cancel_cb, mte);
1027         generic_dialog_add_message(mte->gd, GQ_ICON_DIALOG_QUESTION, _("Set mark text"),
1028                                    _("This will set or clear the mark text."), FALSE);
1029         generic_dialog_add_button(mte->gd, GQ_ICON_OK, "OK",
1030                                   vf_marks_tooltip_ok_cb, TRUE);
1031         generic_dialog_add_button(mte->gd, GQ_ICON_HELP, _("Help"),
1032                                   vf_marks_tooltip_help_cb, FALSE);
1033
1034         table = pref_table_new(mte->gd->vbox, 3, 1, FALSE, TRUE);
1035         pref_table_label(table, 0, 0, g_strdup_printf("%s%d", _("Mark "), mte->mark_no + 1), GTK_ALIGN_END);
1036         mte->edit_widget = gtk_entry_new();
1037         gtk_widget_set_size_request(mte->edit_widget, 300, -1);
1038         if (mte->text_entry)
1039                 {
1040                 gq_gtk_entry_set_text(GTK_ENTRY(mte->edit_widget), mte->text_entry);
1041                 }
1042         gq_gtk_grid_attach_default(GTK_GRID(table), mte->edit_widget, 1, 2, 0, 1);
1043         generic_dialog_attach_default(mte->gd, mte->edit_widget);
1044
1045         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(mte->edit_widget),
1046                                       GTK_ENTRY_ICON_SECONDARY, GQ_ICON_CLEAR);
1047         gtk_entry_set_icon_tooltip_text(GTK_ENTRY(mte->edit_widget),
1048                                         GTK_ENTRY_ICON_SECONDARY, _("Clear"));
1049         g_signal_connect(GTK_ENTRY(mte->edit_widget), "icon-press",
1050                          G_CALLBACK(vf_marks_filter_on_icon_press), mte);
1051
1052         gtk_widget_show(mte->edit_widget);
1053         gtk_widget_grab_focus(mte->edit_widget);
1054         gtk_widget_show(GTK_WIDGET(mte->gd->dialog));
1055
1056         return TRUE;
1057 }
1058
1059 static void vf_file_filter_save_cb(GtkWidget *, gpointer data)
1060 {
1061         auto vf = static_cast<ViewFile *>(data);
1062         gchar *entry_text;
1063         gchar *remove_text = nullptr;
1064         gchar *index_text = nullptr;
1065         gboolean text_found = FALSE;
1066         gint i;
1067
1068         entry_text = g_strdup(gq_gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(vf->file_filter.combo)))));
1069
1070         if (entry_text[0] == '\0' && vf->file_filter.last_selected >= 0)
1071                 {
1072                 gtk_combo_box_set_active(GTK_COMBO_BOX(vf->file_filter.combo), vf->file_filter.last_selected);
1073                 remove_text = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(vf->file_filter.combo));
1074                 history_list_item_remove("file_filter", remove_text);
1075                 gtk_combo_box_text_remove(GTK_COMBO_BOX_TEXT(vf->file_filter.combo), vf->file_filter.last_selected);
1076                 g_free(remove_text);
1077
1078                 gtk_combo_box_set_active(GTK_COMBO_BOX(vf->file_filter.combo), -1);
1079                 vf->file_filter.last_selected = - 1;
1080                 gq_gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(vf->file_filter.combo))), "");
1081                 vf->file_filter.count--;
1082                 }
1083         else
1084                 {
1085                 if (entry_text[0] != '\0')
1086                         {
1087                         for (i = 0; i < vf->file_filter.count; i++)
1088                                 {
1089                                 if (index_text)
1090                                         {
1091                                         g_free(index_text);
1092                                         }
1093                                 gtk_combo_box_set_active(GTK_COMBO_BOX(vf->file_filter.combo), i);
1094                                 index_text = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(vf->file_filter.combo));
1095
1096                                 if (g_strcmp0(index_text, entry_text) == 0)
1097                                         {
1098                                         text_found = TRUE;
1099                                         break;
1100                                         }
1101                                 }
1102
1103                         g_free(index_text);
1104                         if (!text_found)
1105                                 {
1106                                 history_list_add_to_key("file_filter", entry_text, 10);
1107                                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(vf->file_filter.combo), entry_text);
1108                                 vf->file_filter.count++;
1109                                 gtk_combo_box_set_active(GTK_COMBO_BOX(vf->file_filter.combo), vf->file_filter.count - 1);
1110                                 }
1111                         }
1112                 }
1113         vf_refresh(vf);
1114
1115         g_free(entry_text);
1116 }
1117
1118 static void vf_file_filter_cb(GtkWidget *, gpointer data)
1119 {
1120         auto vf = static_cast<ViewFile *>(data);
1121
1122         vf_refresh(vf);
1123 }
1124
1125 static gboolean vf_file_filter_press_cb(GtkWidget *widget, GdkEventButton *, gpointer data)
1126 {
1127         auto vf = static_cast<ViewFile *>(data);
1128         vf->file_filter.last_selected = gtk_combo_box_get_active(GTK_COMBO_BOX(vf->file_filter.combo));
1129
1130         gtk_widget_grab_focus(widget);
1131
1132         return TRUE;
1133 }
1134
1135 static GtkWidget *vf_marks_filter_init(ViewFile *vf)
1136 {
1137         GtkWidget *frame = gtk_frame_new(nullptr);
1138         GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1139
1140         gint i;
1141
1142         for (i = 0; i < FILEDATA_MARKS_SIZE ; i++)
1143                 {
1144                 GtkWidget *check = gtk_check_button_new();
1145                 gq_gtk_box_pack_start(GTK_BOX(hbox), check, FALSE, FALSE, 0);
1146                 g_signal_connect(G_OBJECT(check), "toggled",
1147                          G_CALLBACK(vf_marks_filter_toggle_cb), vf);
1148                 g_signal_connect(G_OBJECT(check), "button_press_event",
1149                          G_CALLBACK(vf_marks_tooltip_cb), GINT_TO_POINTER(i));
1150                 gtk_widget_set_tooltip_text(check, options->marks_tooltips[i]);
1151
1152                 gtk_widget_show(check);
1153                 vf->filter_check[i] = check;
1154                 }
1155         gq_gtk_container_add(GTK_WIDGET(frame), hbox);
1156         gtk_widget_show(hbox);
1157         return frame;
1158 }
1159
1160 void vf_file_filter_set(ViewFile *vf, gboolean enable)
1161 {
1162         if (enable)
1163                 {
1164                 gtk_widget_show(vf->file_filter.combo);
1165                 gtk_widget_show(vf->file_filter.frame);
1166                 }
1167         else
1168                 {
1169                 gtk_widget_hide(vf->file_filter.combo);
1170                 gtk_widget_hide(vf->file_filter.frame);
1171                 }
1172
1173         vf_refresh(vf);
1174 }
1175
1176 static gboolean vf_file_filter_class_cb(GtkWidget *widget, gpointer data)
1177 {
1178         auto vf = static_cast<ViewFile *>(data);
1179         gint i;
1180
1181         gboolean state = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
1182
1183         for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1184                 {
1185                 if (g_strcmp0(format_class_list[i], gtk_menu_item_get_label(GTK_MENU_ITEM(widget))) == 0)
1186                         {
1187                         options->class_filter[i] = state;
1188                         }
1189                 }
1190         vf_refresh(vf);
1191
1192         return TRUE;
1193 }
1194
1195 static gboolean vf_file_filter_class_set_all_cb(GtkWidget *widget, gpointer data)
1196 {
1197         auto vf = static_cast<ViewFile *>(data);
1198         GtkWidget *parent;
1199         GList *children;
1200         GtkWidget *child;
1201         gint i;
1202         gboolean state;
1203
1204         if (g_strcmp0(_("Select all"), gtk_menu_item_get_label(GTK_MENU_ITEM(widget))) == 0)
1205                 {
1206                 state = TRUE;
1207                 }
1208         else
1209                 {
1210                 state = FALSE;
1211                 }
1212
1213         for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1214                 {
1215                 options->class_filter[i] = state;
1216                 }
1217
1218         i = 0;
1219         parent = gtk_widget_get_parent(widget);
1220         children = gtk_container_get_children(GTK_CONTAINER(parent));
1221         while (children)
1222                 {
1223                 child = static_cast<GtkWidget *>(children->data);
1224                 if (i < FILE_FORMAT_CLASSES)
1225                         {
1226                         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(child), state);
1227                         }
1228                 i++;
1229                 children = children->next;
1230                 }
1231         g_list_free(children);
1232         vf_refresh(vf);
1233
1234         return TRUE;
1235 }
1236
1237 static GtkWidget *class_filter_menu (ViewFile *vf)
1238 {
1239         GtkWidget *menu;
1240         GtkWidget *menu_item;
1241         int i;
1242
1243         menu = gtk_menu_new();
1244
1245         for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1246             {
1247                 menu_item = gtk_check_menu_item_new_with_label(format_class_list[i]);
1248                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item), options->class_filter[i]);
1249                 g_signal_connect(G_OBJECT(menu_item), "toggled", G_CALLBACK(vf_file_filter_class_cb), vf);
1250                 gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item);
1251                 gtk_widget_show(menu_item);
1252                 }
1253
1254         menu_item = gtk_menu_item_new_with_label(_("Select all"));
1255         gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item);
1256         gtk_widget_show(menu_item);
1257         g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(vf_file_filter_class_set_all_cb), vf);
1258
1259         menu_item = gtk_menu_item_new_with_label(_("Select none"));
1260         gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item);
1261         gtk_widget_show(menu_item);
1262         g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(vf_file_filter_class_set_all_cb), vf);
1263
1264         return menu;
1265 }
1266
1267 static void case_sensitive_cb(GtkWidget *widget, gpointer data)
1268 {
1269         auto vf = static_cast<ViewFile *>(data);
1270
1271         vf->file_filter.case_sensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
1272         vf_refresh(vf);
1273 }
1274
1275 static void file_filter_clear_cb(GtkEntry *, GtkEntryIconPosition pos, GdkEvent *, gpointer userdata)
1276 {
1277         if (pos == GTK_ENTRY_ICON_SECONDARY)
1278                 {
1279                 gq_gtk_entry_set_text(GTK_ENTRY(userdata), "");
1280                 gtk_widget_grab_focus(GTK_WIDGET(userdata));
1281                 }
1282 }
1283
1284 static GtkWidget *vf_file_filter_init(ViewFile *vf)
1285 {
1286         GtkWidget *frame = gtk_frame_new(nullptr);
1287         GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1288         GList *work;
1289         gint n = 0;
1290         GtkWidget *combo_entry;
1291         GtkWidget *menubar;
1292         GtkWidget *menuitem;
1293         GtkWidget *case_sensitive;
1294         GtkWidget *box;
1295         GtkWidget *icon;
1296         GtkWidget *label;
1297
1298         vf->file_filter.combo = gtk_combo_box_text_new_with_entry();
1299         combo_entry = gtk_bin_get_child(GTK_BIN(vf->file_filter.combo));
1300         gtk_widget_show(gtk_bin_get_child(GTK_BIN(vf->file_filter.combo)));
1301         gtk_widget_show((GTK_WIDGET(vf->file_filter.combo)));
1302         gtk_widget_set_tooltip_text(GTK_WIDGET(vf->file_filter.combo), _("Use regular expressions"));
1303
1304         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(combo_entry), GTK_ENTRY_ICON_SECONDARY, GQ_ICON_CLEAR);
1305         gtk_entry_set_icon_tooltip_text (GTK_ENTRY(combo_entry), GTK_ENTRY_ICON_SECONDARY, _("Clear"));
1306         g_signal_connect(GTK_ENTRY(combo_entry), "icon-press", G_CALLBACK(file_filter_clear_cb), combo_entry);
1307
1308         work = history_list_get_by_key("file_filter");
1309         while (work)
1310                 {
1311                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(vf->file_filter.combo), static_cast<gchar *>(work->data));
1312                 work = work->next;
1313                 n++;
1314                 vf->file_filter.count = n;
1315                 }
1316         gtk_combo_box_set_active(GTK_COMBO_BOX(vf->file_filter.combo), 0);
1317
1318         g_signal_connect(G_OBJECT(combo_entry), "activate",
1319                 G_CALLBACK(vf_file_filter_save_cb), vf);
1320
1321         g_signal_connect(G_OBJECT(vf->file_filter.combo), "changed",
1322                 G_CALLBACK(vf_file_filter_cb), vf);
1323
1324         g_signal_connect(G_OBJECT(combo_entry), "button_press_event",
1325                          G_CALLBACK(vf_file_filter_press_cb), vf);
1326
1327         gq_gtk_box_pack_start(GTK_BOX(hbox), vf->file_filter.combo, FALSE, FALSE, 0);
1328         gtk_widget_show(vf->file_filter.combo);
1329         gq_gtk_container_add(GTK_WIDGET(frame), hbox);
1330         gtk_widget_show(hbox);
1331
1332         case_sensitive = gtk_check_button_new_with_label(_("Case"));
1333         gq_gtk_box_pack_start(GTK_BOX(hbox), case_sensitive, FALSE, FALSE, 0);
1334         gtk_widget_set_tooltip_text(GTK_WIDGET(case_sensitive), _("Case sensitive"));
1335         g_signal_connect(G_OBJECT(case_sensitive), "clicked", G_CALLBACK(case_sensitive_cb), vf);
1336         gtk_widget_show(case_sensitive);
1337
1338         menubar = gtk_menu_bar_new();
1339         gq_gtk_box_pack_start(GTK_BOX(hbox), menubar, FALSE, TRUE, 0);
1340         gtk_widget_show(menubar);
1341
1342         box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);
1343         icon = gtk_image_new_from_icon_name(GQ_ICON_PAN_DOWN, GTK_ICON_SIZE_MENU);
1344         label = gtk_label_new(_("Class"));
1345
1346         gq_gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
1347         gq_gtk_box_pack_end(GTK_BOX(box), icon, FALSE, FALSE, 0);
1348
1349         menuitem = gtk_menu_item_new();
1350
1351         gtk_widget_set_tooltip_text(GTK_WIDGET(menuitem), _("Select Class filter"));
1352         gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), class_filter_menu(vf));
1353         gtk_menu_shell_append(GTK_MENU_SHELL(menubar), menuitem);
1354         gq_gtk_container_add(GTK_WIDGET(menuitem), box);
1355         gq_gtk_widget_show_all(menuitem);
1356
1357         return frame;
1358 }
1359
1360 void vf_mark_filter_toggle(ViewFile *vf, gint mark)
1361 {
1362         gint n = mark - 1;
1363         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vf->filter_check[n]),
1364                                      !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vf->filter_check[n])));
1365 }
1366
1367 ViewFile *vf_new(FileViewType type, FileData *dir_fd)
1368 {
1369         ViewFile *vf;
1370
1371         vf = g_new0(ViewFile, 1);
1372
1373         vf->type = type;
1374         vf->sort_method = SORT_NAME;
1375         vf->sort_ascend = TRUE;
1376         vf->read_metadata_in_idle_id = 0;
1377
1378         vf->scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
1379         gq_gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(vf->scrolled), GTK_SHADOW_IN);
1380         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vf->scrolled),
1381                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1382
1383         vf->filter = vf_marks_filter_init(vf);
1384         vf->file_filter.frame = vf_file_filter_init(vf);
1385
1386         vf->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1387         gq_gtk_box_pack_start(GTK_BOX(vf->widget), vf->filter, FALSE, FALSE, 0);
1388         gq_gtk_box_pack_start(GTK_BOX(vf->widget), vf->file_filter.frame, FALSE, FALSE, 0);
1389         gq_gtk_box_pack_start(GTK_BOX(vf->widget), vf->scrolled, TRUE, TRUE, 0);
1390         gtk_widget_show(vf->scrolled);
1391
1392         g_signal_connect(G_OBJECT(vf->widget), "destroy",
1393                          G_CALLBACK(vf_destroy_cb), vf);
1394
1395         switch (type)
1396         {
1397         case FILEVIEW_LIST: vf = vflist_new(vf); break;
1398         case FILEVIEW_ICON: vf = vficon_new(vf); break;
1399         }
1400
1401         vf_dnd_init(vf);
1402
1403         g_signal_connect(G_OBJECT(vf->listview), "key_press_event",
1404                          G_CALLBACK(vf_press_key_cb), vf);
1405         g_signal_connect(G_OBJECT(vf->listview), "button_press_event",
1406                          G_CALLBACK(vf_press_cb), vf);
1407         g_signal_connect(G_OBJECT(vf->listview), "button_release_event",
1408                          G_CALLBACK(vf_release_cb), vf);
1409
1410         gq_gtk_container_add(GTK_WIDGET(vf->scrolled), vf->listview);
1411         gtk_widget_show(vf->listview);
1412
1413         if (dir_fd) vf_set_fd(vf, dir_fd);
1414
1415         return vf;
1416 }
1417
1418 void vf_set_status_func(ViewFile *vf, void (*func)(ViewFile *vf, gpointer data), gpointer data)
1419 {
1420         vf->func_status = func;
1421         vf->data_status = data;
1422 }
1423
1424 void vf_set_thumb_status_func(ViewFile *vf, void (*func)(ViewFile *vf, gdouble val, const gchar *text, gpointer data), gpointer data)
1425 {
1426         vf->func_thumb_status = func;
1427         vf->data_thumb_status = data;
1428 }
1429
1430 void vf_thumb_set(ViewFile *vf, gboolean enable)
1431 {
1432         switch (vf->type)
1433         {
1434         case FILEVIEW_LIST: vflist_thumb_set(vf, enable); break;
1435         case FILEVIEW_ICON: /*vficon_thumb_set(vf, enable);*/ break;
1436         }
1437 }
1438
1439
1440 static gboolean vf_thumb_next(ViewFile *vf);
1441
1442 static gdouble vf_thumb_progress(ViewFile *vf)
1443 {
1444         gint count = 0;
1445         gint done = 0;
1446
1447         switch (vf->type)
1448         {
1449         case FILEVIEW_LIST: vflist_thumb_progress_count(vf->list, count, done); break;
1450         case FILEVIEW_ICON: vficon_thumb_progress_count(vf->list, count, done); break;
1451         }
1452
1453         DEBUG_1("thumb progress: %d of %d", done, count);
1454         return static_cast<gdouble>(done) / count;
1455 }
1456
1457 static gdouble vf_read_metadata_in_idle_progress(ViewFile *vf)
1458 {
1459         gint count = 0;
1460         gint done = 0;
1461
1462         switch (vf->type)
1463         {
1464         case FILEVIEW_LIST: vflist_read_metadata_progress_count(vf->list, count, done); break;
1465         case FILEVIEW_ICON: vficon_read_metadata_progress_count(vf->list, count, done); break;
1466         }
1467
1468         return static_cast<gdouble>(done) / count;
1469 }
1470
1471 static void vf_set_thumb_fd(ViewFile *vf, FileData *fd)
1472 {
1473         switch (vf->type)
1474         {
1475         case FILEVIEW_LIST: vflist_set_thumb_fd(vf, fd); break;
1476         case FILEVIEW_ICON: vficon_set_thumb_fd(vf, fd); break;
1477         }
1478 }
1479
1480 static void vf_thumb_status(ViewFile *vf, gdouble val, const gchar *text)
1481 {
1482         if (vf->func_thumb_status)
1483                 {
1484                 vf->func_thumb_status(vf, val, text, vf->data_thumb_status);
1485                 }
1486 }
1487
1488 static void vf_thumb_do(ViewFile *vf, FileData *fd)
1489 {
1490         if (!fd) return;
1491
1492         vf_set_thumb_fd(vf, fd);
1493         vf_thumb_status(vf, vf_thumb_progress(vf), _("Loading thumbs..."));
1494 }
1495
1496 void vf_thumb_cleanup(ViewFile *vf)
1497 {
1498         vf_thumb_status(vf, 0.0, nullptr);
1499
1500         vf->thumbs_running = FALSE;
1501
1502         thumb_loader_free(vf->thumbs_loader);
1503         vf->thumbs_loader = nullptr;
1504
1505         vf->thumbs_filedata = nullptr;
1506 }
1507
1508 void vf_thumb_stop(ViewFile *vf)
1509 {
1510         if (vf->thumbs_running) vf_thumb_cleanup(vf);
1511 }
1512
1513 static void vf_thumb_common_cb(ThumbLoader *tl, gpointer data)
1514 {
1515         auto vf = static_cast<ViewFile *>(data);
1516
1517         if (vf->thumbs_filedata && vf->thumbs_loader == tl)
1518                 {
1519                 vf_thumb_do(vf, vf->thumbs_filedata);
1520                 }
1521
1522         while (vf_thumb_next(vf));
1523 }
1524
1525 static void vf_thumb_error_cb(ThumbLoader *tl, gpointer data)
1526 {
1527         vf_thumb_common_cb(tl, data);
1528 }
1529
1530 static void vf_thumb_done_cb(ThumbLoader *tl, gpointer data)
1531 {
1532         vf_thumb_common_cb(tl, data);
1533 }
1534
1535 static gboolean vf_thumb_next(ViewFile *vf)
1536 {
1537         FileData *fd = nullptr;
1538
1539         if (!gtk_widget_get_realized(vf->listview))
1540                 {
1541                 vf_thumb_status(vf, 0.0, nullptr);
1542                 return FALSE;
1543                 }
1544
1545         switch (vf->type)
1546         {
1547         case FILEVIEW_LIST: fd = vflist_thumb_next_fd(vf); break;
1548         case FILEVIEW_ICON: fd = vficon_thumb_next_fd(vf); break;
1549         }
1550
1551         if (!fd)
1552                 {
1553                 /* done */
1554                 vf_thumb_cleanup(vf);
1555                 return FALSE;
1556                 }
1557
1558         vf->thumbs_filedata = fd;
1559
1560         thumb_loader_free(vf->thumbs_loader);
1561
1562         vf->thumbs_loader = thumb_loader_new(options->thumbnails.max_width, options->thumbnails.max_height);
1563         thumb_loader_set_callbacks(vf->thumbs_loader,
1564                                    vf_thumb_done_cb,
1565                                    vf_thumb_error_cb,
1566                                    nullptr,
1567                                    vf);
1568
1569         if (!thumb_loader_start(vf->thumbs_loader, fd))
1570                 {
1571                 /* set icon to unknown, continue */
1572                 DEBUG_1("thumb loader start failed %s", fd->path);
1573                 vf_thumb_do(vf, fd);
1574
1575                 return TRUE;
1576                 }
1577
1578         return FALSE;
1579 }
1580
1581 static void vf_thumb_reset_all(ViewFile *vf)
1582 {
1583         GList *work;
1584
1585         for (work = vf->list; work; work = work->next)
1586                 {
1587                 auto fd = static_cast<FileData *>(work->data);
1588                 if (fd->thumb_pixbuf)
1589                         {
1590                         g_object_unref(fd->thumb_pixbuf);
1591                         fd->thumb_pixbuf = nullptr;
1592                         }
1593                 }
1594 }
1595
1596 void vf_thumb_update(ViewFile *vf)
1597 {
1598         vf_thumb_stop(vf);
1599
1600         if (vf->type == FILEVIEW_LIST && !VFLIST(vf)->thumbs_enabled) return;
1601
1602         vf_thumb_status(vf, 0.0, _("Loading thumbs..."));
1603         vf->thumbs_running = TRUE;
1604
1605         if (thumb_format_changed)
1606                 {
1607                 vf_thumb_reset_all(vf);
1608                 thumb_format_changed = FALSE;
1609                 }
1610
1611         while (vf_thumb_next(vf));
1612 }
1613
1614 void vf_star_cleanup(ViewFile *vf)
1615 {
1616         if (vf->stars_id != 0)
1617                 {
1618                 g_source_remove(vf->stars_id);
1619                 }
1620
1621         vf->stars_id = 0;
1622         vf->stars_filedata = nullptr;
1623 }
1624
1625 void vf_star_stop(ViewFile *vf)
1626 {
1627          vf_star_cleanup(vf);
1628 }
1629
1630 static void vf_set_star_fd(ViewFile *vf, FileData *fd)
1631 {
1632         switch (vf->type)
1633                 {
1634                 case FILEVIEW_LIST: vflist_set_star_fd(vf, fd); break;
1635                 case FILEVIEW_ICON: vficon_set_star_fd(vf, fd); break;
1636                 default: break;
1637                 }
1638 }
1639
1640 static void vf_star_do(ViewFile *vf, FileData *fd)
1641 {
1642         if (!fd) return;
1643
1644         vf_set_star_fd(vf, fd);
1645 }
1646
1647 static gboolean vf_star_next(ViewFile *vf)
1648 {
1649         FileData *fd = nullptr;
1650
1651         switch (vf->type)
1652                 {
1653                 case FILEVIEW_LIST: fd = vflist_star_next_fd(vf); break;
1654                 case FILEVIEW_ICON: fd = vficon_star_next_fd(vf); break;
1655                 default: break;
1656                 }
1657
1658         if (!fd)
1659                 {
1660                 /* done */
1661                 vf_star_cleanup(vf);
1662                 return FALSE;
1663                 }
1664
1665         return TRUE;
1666 }
1667
1668 gboolean vf_stars_cb(gpointer data)
1669 {
1670         auto vf = static_cast<ViewFile *>(data);
1671         FileData *fd = vf->stars_filedata;
1672
1673         if (fd)
1674                 {
1675                 read_rating_data(fd);
1676
1677                 vf_star_do(vf, fd);
1678
1679                 if (vf_star_next(vf))
1680                         {
1681                         return G_SOURCE_CONTINUE;
1682                         }
1683
1684                 vf->stars_filedata = nullptr;
1685                 vf->stars_id = 0;
1686                 return G_SOURCE_REMOVE;
1687                 }
1688
1689         return G_SOURCE_REMOVE;
1690 }
1691
1692 void vf_star_update(ViewFile *vf)
1693 {
1694         vf_star_stop(vf);
1695
1696         if (!options->show_star_rating)
1697                 {
1698                 return;
1699                 }
1700
1701         vf_star_next(vf);
1702 }
1703
1704 void vf_marks_set(ViewFile *vf, gboolean enable)
1705 {
1706         if (vf->marks_enabled == enable) return;
1707
1708         vf->marks_enabled = enable;
1709
1710         switch (vf->type)
1711         {
1712         case FILEVIEW_LIST: vflist_marks_set(vf, enable); break;
1713         case FILEVIEW_ICON: vficon_marks_set(vf, enable); break;
1714         }
1715         if (enable)
1716                 gtk_widget_show(vf->filter);
1717         else
1718                 gtk_widget_hide(vf->filter);
1719
1720         vf_refresh_idle(vf);
1721 }
1722 #pragma GCC diagnostic push
1723 #pragma GCC diagnostic ignored "-Wunused-function"
1724 void vf_star_rating_set_unused(ViewFile *vf, gboolean enable)
1725 {
1726         if (options->show_star_rating == enable) return;
1727         options->show_star_rating = enable;
1728
1729         switch (vf->type)
1730                 {
1731                 case FILEVIEW_LIST: vflist_star_rating_set(vf, enable); break;
1732                 case FILEVIEW_ICON: vficon_star_rating_set(vf, enable); break;
1733                 }
1734         vf_refresh_idle(vf);
1735 }
1736 #pragma GCC diagnostic pop
1737
1738 guint vf_marks_get_filter(ViewFile *vf)
1739 {
1740         guint ret = 0;
1741         gint i;
1742         if (!vf->marks_enabled) return 0;
1743
1744         for (i = 0; i < FILEDATA_MARKS_SIZE ; i++)
1745                 {
1746                 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vf->filter_check[i])))
1747                         {
1748                         ret |= 1 << i;
1749                         }
1750                 }
1751         return ret;
1752 }
1753
1754 GRegex *vf_file_filter_get_filter(ViewFile *vf)
1755 {
1756         GRegex *ret = nullptr;
1757         GError *error = nullptr;
1758         gchar *file_filter_text = nullptr;
1759
1760         if (!gtk_widget_get_visible(vf->file_filter.combo))
1761                 {
1762                 return g_regex_new("", static_cast<GRegexCompileFlags>(0), static_cast<GRegexMatchFlags>(0), nullptr);
1763                 }
1764
1765         file_filter_text = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(vf->file_filter.combo));
1766
1767         if (file_filter_text[0] != '\0')
1768                 {
1769                 ret = g_regex_new(file_filter_text, vf->file_filter.case_sensitive ? static_cast<GRegexCompileFlags>(0) : G_REGEX_CASELESS, static_cast<GRegexMatchFlags>(0), &error);
1770                 if (error)
1771                         {
1772                         log_printf("Error: could not compile regular expression %s\n%s\n", file_filter_text, error->message);
1773                         g_error_free(error);
1774                         error = nullptr;
1775                         ret = g_regex_new("", static_cast<GRegexCompileFlags>(0), static_cast<GRegexMatchFlags>(0), nullptr);
1776                         }
1777                 g_free(file_filter_text);
1778                 }
1779         else
1780                 {
1781                 ret = g_regex_new("", static_cast<GRegexCompileFlags>(0), static_cast<GRegexMatchFlags>(0), nullptr);
1782                 }
1783
1784         return ret;
1785 }
1786
1787 guint vf_class_get_filter(ViewFile *vf)
1788 {
1789         guint ret = 0;
1790         gint i;
1791
1792         if (!gtk_widget_get_visible(vf->file_filter.combo))
1793                 {
1794                 return G_MAXUINT;
1795                 }
1796
1797         for ( i = 0; i < FILE_FORMAT_CLASSES; i++)
1798                 {
1799                 if (options->class_filter[i])
1800                         {
1801                         ret |= 1 << i;
1802                         }
1803                 }
1804
1805         return ret;
1806 }
1807
1808 void vf_set_layout(ViewFile *vf, LayoutWindow *layout)
1809 {
1810         vf->layout = layout;
1811 }
1812
1813
1814 /*
1815  *-----------------------------------------------------------------------------
1816  * maintenance (for rename, move, remove)
1817  *-----------------------------------------------------------------------------
1818  */
1819
1820 static gboolean vf_refresh_idle_cb(gpointer data)
1821 {
1822         auto vf = static_cast<ViewFile *>(data);
1823
1824         vf_refresh(vf);
1825         vf->refresh_idle_id = 0;
1826         return G_SOURCE_REMOVE;
1827 }
1828
1829 void vf_refresh_idle_cancel(ViewFile *vf)
1830 {
1831         if (vf->refresh_idle_id)
1832                 {
1833                 g_source_remove(vf->refresh_idle_id);
1834                 vf->refresh_idle_id = 0;
1835                 }
1836 }
1837
1838
1839 void vf_refresh_idle(ViewFile *vf)
1840 {
1841         if (!vf->refresh_idle_id)
1842                 {
1843                 vf->time_refresh_set = time(nullptr);
1844                 /* file operations run with G_PRIORITY_DEFAULT_IDLE */
1845                 vf->refresh_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE + 50, vf_refresh_idle_cb, vf, nullptr);
1846                 }
1847         else if (time(nullptr) - vf->time_refresh_set > 1)
1848                 {
1849                 /* more than 1 sec since last update - increase priority */
1850                 vf_refresh_idle_cancel(vf);
1851                 vf->time_refresh_set = time(nullptr);
1852                 vf->refresh_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE - 50, vf_refresh_idle_cb, vf, nullptr);
1853                 }
1854 }
1855
1856 void vf_notify_cb(FileData *fd, NotifyType type, gpointer data)
1857 {
1858         auto vf = static_cast<ViewFile *>(data);
1859         gboolean refresh;
1860
1861         auto interested = static_cast<NotifyType>(NOTIFY_CHANGE | NOTIFY_REREAD | NOTIFY_GROUPING);
1862         if (vf->marks_enabled) interested = static_cast<NotifyType>(interested | NOTIFY_MARKS | NOTIFY_METADATA);
1863         /** @FIXME NOTIFY_METADATA should be checked by the keyword-to-mark functions and converted to NOTIFY_MARKS only if there was a change */
1864
1865         if (!(type & interested) || vf->refresh_idle_id || !vf->dir_fd) return;
1866
1867         refresh = (fd == vf->dir_fd);
1868
1869         if (!refresh)
1870                 {
1871                 gchar *base = remove_level_from_path(fd->path);
1872                 refresh = (g_strcmp0(base, vf->dir_fd->path) == 0);
1873                 g_free(base);
1874                 }
1875
1876         if ((type & NOTIFY_CHANGE) && fd->change)
1877                 {
1878                 if (!refresh && fd->change->dest)
1879                         {
1880                         gchar *dest_base = remove_level_from_path(fd->change->dest);
1881                         refresh = (g_strcmp0(dest_base, vf->dir_fd->path) == 0);
1882                         g_free(dest_base);
1883                         }
1884
1885                 if (!refresh && fd->change->source)
1886                         {
1887                         gchar *source_base = remove_level_from_path(fd->change->source);
1888                         refresh = (g_strcmp0(source_base, vf->dir_fd->path) == 0);
1889                         g_free(source_base);
1890                         }
1891                 }
1892
1893         if (refresh)
1894                 {
1895                 DEBUG_1("Notify vf: %s %04x", fd->path, type);
1896                 vf_refresh_idle(vf);
1897                 }
1898 }
1899
1900 static gboolean vf_read_metadata_in_idle_cb(gpointer data)
1901 {
1902         FileData *fd;
1903         auto vf = static_cast<ViewFile *>(data);
1904         GList *work;
1905
1906         vf_thumb_status(vf, vf_read_metadata_in_idle_progress(vf), _("Loading meta..."));
1907
1908         work = vf->list;
1909
1910         while (work)
1911                 {
1912                 fd = static_cast<FileData *>(work->data);
1913
1914                 if (fd && !fd->metadata_in_idle_loaded)
1915                         {
1916                         if (!fd->exifdate)
1917                                 {
1918                                 read_exif_time_data(fd);
1919                                 }
1920                         if (!fd->exifdate_digitized)
1921                                 {
1922                                 read_exif_time_digitized_data(fd);
1923                                 }
1924                         if (fd->rating == STAR_RATING_NOT_READ)
1925                                 {
1926                                 read_rating_data(fd);
1927                                 }
1928                         fd->metadata_in_idle_loaded = TRUE;
1929                         return G_SOURCE_CONTINUE;
1930                         }
1931                 work = work->next;
1932                 }
1933
1934         vf_thumb_status(vf, 0.0, nullptr);
1935         vf->read_metadata_in_idle_id = 0;
1936         vf_refresh(vf);
1937         return G_SOURCE_REMOVE;
1938 }
1939
1940 static void vf_read_metadata_in_idle_finished_cb(gpointer data)
1941 {
1942         auto vf = static_cast<ViewFile *>(data);
1943
1944         vf_thumb_status(vf, 0.0, _("Loading meta..."));
1945         vf->read_metadata_in_idle_id = 0;
1946 }
1947
1948 void vf_read_metadata_in_idle(ViewFile *vf)
1949 {
1950         if (!vf) return;
1951
1952         if (vf->read_metadata_in_idle_id)
1953                 {
1954                 g_idle_remove_by_data(vf);
1955                 }
1956         vf->read_metadata_in_idle_id = 0;
1957
1958         if (vf->list)
1959                 {
1960                 vf->read_metadata_in_idle_id = g_idle_add_full(G_PRIORITY_LOW, vf_read_metadata_in_idle_cb, vf, vf_read_metadata_in_idle_finished_cb);
1961                 }
1962 }
1963
1964 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */