f1c0281cc9596c9c47fc28bbdc9b61b22ac75f16
[geeqie.git] / src / view-file / view-file-list.cc
1 /*
2  * Copyright (C) 2004 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "view-file-list.h"
23
24 #include <cstring>
25 #include <vector>
26
27 #include <gdk-pixbuf/gdk-pixbuf.h>
28 #include <glib-object.h>
29
30 #include "collect.h"
31 #include "debug.h"
32 #include "dnd.h"
33 #include "filedata.h"
34 #include "img-view.h"
35 #include "intl.h"
36 #include "layout-image.h"
37 #include "layout.h"
38 #include "main-defines.h"
39 #include "metadata.h"
40 #include "misc.h"
41 #include "options.h"
42 #include "ui-fileops.h"
43 #include "ui-menu.h"
44 #include "ui-misc.h"
45 #include "ui-tree-edit.h"
46 #include "uri-utils.h"
47 #include "utilops.h"
48 #include "view-file.h"
49
50 /* Index to tree store */
51 enum {
52         FILE_COLUMN_POINTER = 0,
53         FILE_COLUMN_VERSION,
54         FILE_COLUMN_THUMB,
55         FILE_COLUMN_FORMATTED,
56         FILE_COLUMN_FORMATTED_WITH_STARS,
57         FILE_COLUMN_NAME,
58         FILE_COLUMN_SIDECARS,
59         FILE_COLUMN_STAR_RATING,
60         FILE_COLUMN_SIZE,
61         FILE_COLUMN_DATE,
62         FILE_COLUMN_EXPANDED,
63         FILE_COLUMN_COLOR,
64         FILE_COLUMN_MARKS,
65         FILE_COLUMN_MARKS_LAST = FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
66         FILE_COLUMN_COUNT
67 };
68
69
70 /* Index to tree view */
71 enum {
72         FILE_VIEW_COLUMN_MARKS = 0,
73         FILE_VIEW_COLUMN_MARKS_LAST = FILE_VIEW_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
74         FILE_VIEW_COLUMN_THUMB,
75         FILE_VIEW_COLUMN_FORMATTED,
76         FILE_VIEW_COLUMN_FORMATTED_WITH_STARS,
77         FILE_VIEW_COLUMN_STAR_RATING,
78         FILE_VIEW_COLUMN_SIZE,
79         FILE_VIEW_COLUMN_DATE,
80         FILE_VIEW_COLUMN_COUNT
81 };
82
83
84
85 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd);
86 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old_name, const gchar *new_name, gpointer data);
87 static void vflist_populate_view(ViewFile *vf, gboolean force);
88 static gboolean vflist_is_multiline(ViewFile *vf);
89 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded);
90
91
92 /*
93  *-----------------------------------------------------------------------------
94  * misc
95  *-----------------------------------------------------------------------------
96  */
97 struct ViewFileFindRowData {
98         const FileData *fd;
99         GtkTreeIter *iter;
100         gboolean found;
101         gint row;
102 };
103
104 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
105 {
106         auto find = static_cast<ViewFileFindRowData *>(data);
107         FileData *fd;
108         gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
109         if (fd == find->fd)
110                 {
111                 *find->iter = *iter;
112                 find->found = TRUE;
113                 return TRUE;
114                 }
115         find->row++;
116         return FALSE;
117 }
118
119 static gint vflist_find_row(const ViewFile *vf, const FileData *fd, GtkTreeIter *iter)
120 {
121         GtkTreeModel *store;
122         ViewFileFindRowData data = {fd, iter, FALSE, 0};
123
124         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
125         gtk_tree_model_foreach(store, vflist_find_row_cb, &data);
126
127         if (data.found)
128                 {
129                 return data.row;
130                 }
131
132         return -1;
133 }
134
135 static FileData *vflist_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *)
136 {
137         GtkTreePath *tpath;
138         GtkTreeViewColumn *column;
139
140         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
141                                           &tpath, &column, nullptr, nullptr))
142                 {
143                 GtkTreeModel *store;
144                 GtkTreeIter row;
145                 FileData *fd;
146
147                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
148                 gtk_tree_model_get_iter(store, &row, tpath);
149                 gtk_tree_path_free(tpath);
150                 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &fd, -1);
151
152                 return fd;
153                 }
154
155         return nullptr;
156 }
157
158 static gboolean vflist_store_clear_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
159 {
160         FileData *fd;
161         gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
162
163         /* it seems that gtk_tree_store_clear may call some callbacks
164            that use the column. Set the pointer to NULL to be safe. */
165         gtk_tree_store_set(GTK_TREE_STORE(model), iter, FILE_COLUMN_POINTER, NULL, -1);
166         file_data_unref(fd);
167         return FALSE;
168 }
169
170 static void vflist_store_clear(ViewFile *vf, gboolean unlock_files)
171 {
172         GtkTreeModel *store;
173         GList *files = nullptr;
174
175         if (unlock_files && vf->marks_enabled)
176                 {
177                 // unlock locked files in this directory
178                 filelist_read(vf->dir_fd, &files, nullptr);
179                 GList *work = files;
180                 while (work)
181                         {
182                         auto fd = static_cast<FileData *>(work->data);
183                         work = work->next;
184                         file_data_unlock(fd);
185                         file_data_unref(fd);  // undo the ref that got added in filelist_read
186                         }
187                 }
188
189         g_list_free(files);
190         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
191         gtk_tree_model_foreach(store, vflist_store_clear_cb, nullptr);
192         gtk_tree_store_clear(GTK_TREE_STORE(store));
193 }
194
195 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set)
196 {
197         GtkTreeModel *store;
198         GtkTreeIter iter;
199
200         if (vflist_find_row(vf, fd, &iter) < 0) return;
201         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
202         gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_COLOR, color_set, -1);
203 }
204
205 static void vflist_move_cursor(ViewFile *vf, GtkTreeIter *iter)
206 {
207         GtkTreeModel *store;
208         GtkTreePath *tpath;
209
210         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
211
212         tpath = gtk_tree_model_get_path(store, iter);
213         gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
214         gtk_tree_path_free(tpath);
215 }
216
217
218 /*
219  *-----------------------------------------------------------------------------
220  * dnd
221  *-----------------------------------------------------------------------------
222  */
223
224 static void vflist_dnd_get(GtkWidget *, GdkDragContext *,
225                            GtkSelectionData *selection_data, guint,
226                            guint, gpointer data)
227 {
228         auto vf = static_cast<ViewFile *>(data);
229         GList *list = nullptr;
230
231         if (!vf->click_fd) return;
232
233         if (vflist_row_is_selected(vf, vf->click_fd))
234                 {
235                 list = vf_selection_get_list(vf);
236                 }
237         else
238                 {
239                 list = g_list_append(nullptr, file_data_ref(vf->click_fd));
240                 }
241
242         if (!list) return;
243         uri_selection_data_set_uris_from_filelist(selection_data, list);
244         filelist_free(list);
245 }
246
247 static void vflist_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
248 {
249         auto vf = static_cast<ViewFile *>(data);
250
251         vflist_color_set(vf, vf->click_fd, TRUE);
252
253         if (VFLIST(vf)->thumbs_enabled &&
254             vf->click_fd && vf->click_fd->thumb_pixbuf)
255                 {
256                 guint items;
257
258                 if (vflist_row_is_selected(vf, vf->click_fd))
259                         items = vf_selection_count(vf, nullptr);
260                 else
261                         items = 1;
262
263                 dnd_set_drag_icon(widget, context, vf->click_fd->thumb_pixbuf, items);
264                 }
265 }
266
267 static void vflist_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
268 {
269         auto vf = static_cast<ViewFile *>(data);
270
271         vflist_color_set(vf, vf->click_fd, FALSE);
272
273         if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
274                 {
275                 vf_refresh(vf);
276                 }
277 }
278
279 static void vflist_drag_data_received(GtkWidget *, GdkDragContext *,
280                                       int x, int y, GtkSelectionData *selection,
281                                       guint info, guint, gpointer data)
282 {
283         auto vf = static_cast<ViewFile *>(data);
284
285         if (info == TARGET_TEXT_PLAIN) {
286                 FileData *fd = vflist_find_data_by_coord(vf, x, y, nullptr);
287
288                 if (fd) {
289                         /* Add keywords to file */
290                         auto str = reinterpret_cast<gchar *>(gtk_selection_data_get_text(selection));
291                         GList *kw_list = string_to_keywords_list(str);
292
293                         metadata_append_list(fd, KEYWORD_KEY, kw_list);
294                         g_list_free_full(kw_list, g_free);
295                         g_free(str);
296                 }
297         }
298 }
299
300 void vflist_dnd_init(ViewFile *vf)
301 {
302         gtk_drag_source_set(vf->listview, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
303                             dnd_file_drag_types, dnd_file_drag_types_count,
304                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
305         gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
306                             dnd_file_drag_types, dnd_file_drag_types_count,
307                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
308
309         g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
310                          G_CALLBACK(vflist_dnd_get), vf);
311         g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
312                          G_CALLBACK(vflist_dnd_begin), vf);
313         g_signal_connect(G_OBJECT(vf->listview), "drag_end",
314                          G_CALLBACK(vflist_dnd_end), vf);
315         g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
316                          G_CALLBACK(vflist_drag_data_received), vf);
317 }
318
319 /*
320  *-----------------------------------------------------------------------------
321  * pop-up menu
322  *-----------------------------------------------------------------------------
323  */
324
325 GList *vflist_selection_get_one(ViewFile *vf, FileData *fd)
326 {
327         GList *list = nullptr;
328
329         if (fd->sidecar_files)
330                 {
331                 /* check if the row is expanded */
332                 GtkTreeModel *store;
333                 GtkTreeIter iter;
334
335                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
336                 if (vflist_find_row(vf, fd, &iter) >= 0)
337                         {
338                         GtkTreePath *tpath;
339
340                         tpath = gtk_tree_model_get_path(store, &iter);
341                         if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
342                                 {
343                                 /* unexpanded - add whole group */
344                                 list = filelist_copy(fd->sidecar_files);
345                                 }
346                         gtk_tree_path_free(tpath);
347                         }
348                 }
349
350         return g_list_prepend(list, file_data_ref(fd));
351 }
352
353 GList *vflist_pop_menu_file_list(ViewFile *vf)
354 {
355         if (!vf->click_fd) return nullptr;
356
357         if (vflist_row_is_selected(vf, vf->click_fd))
358                 {
359                 return vf_selection_get_list(vf);
360                 }
361         return vflist_selection_get_one(vf, vf->click_fd);
362 }
363
364
365 void vflist_pop_menu_view_cb(ViewFile *vf)
366 {
367         if (vflist_row_is_selected(vf, vf->click_fd))
368                 {
369                 GList *list;
370
371                 list = vf_selection_get_list(vf);
372                 view_window_new_from_list(list);
373                 filelist_free(list);
374                 }
375         else
376                 {
377                 view_window_new(vf->click_fd);
378                 }
379 }
380
381 void vflist_pop_menu_rename_cb(ViewFile *vf)
382 {
383         GList *list;
384
385         list = vf_pop_menu_file_list(vf);
386         if (options->file_ops.enable_in_place_rename &&
387             list && !list->next && vf->click_fd)
388                 {
389                 GtkTreeModel *store;
390                 GtkTreeIter iter;
391
392                 filelist_free(list);
393
394                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
395                 if (vflist_find_row(vf, vf->click_fd, &iter) >= 0)
396                         {
397                         GtkTreePath *tpath;
398
399                         tpath = gtk_tree_model_get_path(store, &iter);
400                         tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
401                                           FILE_VIEW_COLUMN_FORMATTED, vf->click_fd->name,
402                                           vflist_row_rename_cb, vf);
403                         gtk_tree_path_free(tpath);
404                         }
405                 return;
406                 }
407
408         file_util_rename(nullptr, list, vf->listview);
409 }
410
411 static void vflist_pop_menu_thumbs_cb(GtkWidget *, gpointer data)
412 {
413         auto vf = static_cast<ViewFile *>(data);
414
415         vflist_color_set(vf, vf->click_fd, FALSE);
416         if (vf->layout)
417                 {
418                 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
419                 }
420         else
421                 {
422                 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
423                 }
424 }
425
426 void vflist_pop_menu_add_items(ViewFile *vf, GtkWidget *menu)
427 {
428         menu_item_add_check(menu, _("Show _thumbnails"), VFLIST(vf)->thumbs_enabled,
429                             G_CALLBACK(vflist_pop_menu_thumbs_cb), vf);
430 }
431
432 void vflist_star_rating_set(ViewFile *vf, gboolean enable)
433 {
434         GList *columns;
435         GList *work;
436
437         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
438
439         work = columns;
440         while (work)
441                 {
442                 auto column = static_cast<GtkTreeViewColumn *>(work->data);
443                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
444                 work = work->next;
445
446                 if (vflist_is_multiline(vf))
447                         {
448                         if (col_idx == FILE_COLUMN_FORMATTED_WITH_STARS)
449                                 {
450                                 gtk_tree_view_column_set_visible(column, enable);
451                                 }
452                         if (col_idx == FILE_COLUMN_FORMATTED)
453                                 {
454                                 gtk_tree_view_column_set_visible(column, !enable);
455                                 }
456                         }
457                 else
458                         {
459                         if (col_idx == FILE_COLUMN_STAR_RATING)
460                                 {
461                                 gtk_tree_view_column_set_visible(column, enable);
462                                 }
463                         }
464                 }
465         g_list_free(columns);
466 }
467
468 void vflist_pop_menu_show_star_rating_cb(ViewFile *vf)
469 {
470         vflist_populate_view(vf, TRUE);
471
472         vflist_color_set(vf, vf->click_fd, FALSE);
473         vflist_star_rating_set(vf, options->show_star_rating);
474 }
475
476 void vflist_pop_menu_refresh_cb(ViewFile *vf)
477 {
478         vflist_color_set(vf, vf->click_fd, FALSE);
479         vf_refresh(vf);
480         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
481 }
482
483 void vflist_popup_destroy_cb(ViewFile *vf)
484 {
485         vflist_color_set(vf, vf->click_fd, FALSE);
486 }
487
488
489 /*
490  *-----------------------------------------------------------------------------
491  * callbacks
492  *-----------------------------------------------------------------------------
493  */
494
495 static gboolean vflist_row_rename_cb(TreeEditData *, const gchar *old_name, const gchar *new_name, gpointer data)
496 {
497         auto vf = static_cast<ViewFile *>(data);
498         gchar *new_path;
499
500         if (!new_name || !new_name[0]) return FALSE;
501
502         new_path = g_build_filename(vf->dir_fd->path, new_name, NULL);
503
504         if (strchr(new_name, G_DIR_SEPARATOR) != nullptr)
505                 {
506                 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new_name);
507                 file_util_warning_dialog(_("Error renaming file"), text, GQ_ICON_DIALOG_ERROR, vf->listview);
508                 g_free(text);
509                 }
510         else
511                 {
512                 gchar *old_path = g_build_filename(vf->dir_fd->path, old_name, NULL);
513                 FileData *fd = file_data_new_group(old_path); /* get the fd from cache */
514                 file_util_rename_simple(fd, new_path, vf->listview);
515                 file_data_unref(fd);
516                 g_free(old_path);
517                 }
518
519         g_free(new_path);
520
521         return FALSE;
522 }
523
524 gboolean vflist_press_key_cb(ViewFile *vf, GtkWidget *widget, GdkEventKey *event)
525 {
526         GtkTreePath *tpath;
527
528         if (event->keyval != GDK_KEY_Menu) return FALSE;
529
530         gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, nullptr);
531         if (tpath)
532                 {
533                 GtkTreeModel *store;
534                 GtkTreeIter iter;
535
536                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
537                 gtk_tree_model_get_iter(store, &iter, tpath);
538                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &vf->click_fd, -1);
539                 gtk_tree_path_free(tpath);
540                 }
541         else
542                 {
543                 vf->click_fd = nullptr;
544                 }
545
546         vf->popup = vf_pop_menu(vf);
547         gtk_menu_popup_at_widget(GTK_MENU(vf->popup), widget, GDK_GRAVITY_EAST, GDK_GRAVITY_CENTER, nullptr);
548
549         return TRUE;
550 }
551
552 gboolean vflist_press_cb(ViewFile *vf, GtkWidget *widget, GdkEventButton *bevent)
553 {
554         GtkTreePath *tpath;
555         GtkTreeIter iter;
556         FileData *fd = nullptr;
557         GtkTreeViewColumn *column;
558
559         vf->clicked_mark = 0;
560
561         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
562                                           &tpath, &column, nullptr, nullptr))
563                 {
564                 GtkTreeModel *store;
565                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
566
567                 if (bevent->button == MOUSE_BUTTON_LEFT &&
568                     col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
569                         return FALSE;
570
571                 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
572                         vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
573
574                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
575
576                 gtk_tree_model_get_iter(store, &iter, tpath);
577                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
578                 gtk_tree_path_free(tpath);
579                 }
580
581         vf->click_fd = fd;
582
583         if (bevent->button == MOUSE_BUTTON_RIGHT)
584                 {
585                 vf->popup = vf_pop_menu(vf);
586                 gtk_menu_popup_at_pointer(GTK_MENU(vf->popup), nullptr);
587                 return TRUE;
588                 }
589
590         if (!fd) return FALSE;
591
592         if (bevent->button == MOUSE_BUTTON_MIDDLE)
593                 {
594                 if (!vflist_row_is_selected(vf, fd))
595                         {
596                         vflist_color_set(vf, fd, TRUE);
597                         }
598                 return TRUE;
599                 }
600
601
602         if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
603             !(bevent->state & GDK_SHIFT_MASK ) &&
604             !(bevent->state & GDK_CONTROL_MASK ) &&
605             vflist_row_is_selected(vf, fd))
606                 {
607                 GtkTreeSelection *selection;
608
609                 gtk_widget_grab_focus(widget);
610
611
612                 /* returning FALSE and further processing of the event is needed for
613                    correct operation of the expander, to show the sidecar files.
614                    It however resets the selection of multiple files. With this condition
615                    it should work for both cases */
616                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
617                 return (gtk_tree_selection_count_selected_rows(selection) > 1);
618                 }
619
620         if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
621                 {
622                 if (vf->click_fd->format_class == FORMAT_CLASS_COLLECTION)
623                         {
624                         collection_window_new(vf->click_fd->path);
625                         }
626                 else
627                         {
628                         if (vf->layout) layout_image_full_screen_start(vf->layout);
629                         }
630                 }
631
632         return FALSE;
633 }
634
635 gboolean vflist_release_cb(ViewFile *vf, GtkWidget *widget, GdkEventButton *bevent)
636 {
637         GtkTreePath *tpath;
638         GtkTreeIter iter;
639         FileData *fd = nullptr;
640
641         if (defined_mouse_buttons(widget, bevent, vf->layout))
642                 {
643                 return TRUE;
644                 }
645
646         if (bevent->button == MOUSE_BUTTON_MIDDLE)
647                 {
648                 vflist_color_set(vf, vf->click_fd, FALSE);
649                 }
650
651         if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
652                 {
653                 return TRUE;
654                 }
655
656         if ((bevent->x != 0 || bevent->y != 0) &&
657             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
658                                           &tpath, nullptr, nullptr, nullptr))
659                 {
660                 GtkTreeModel *store;
661
662                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
663                 gtk_tree_model_get_iter(store, &iter, tpath);
664                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
665                 gtk_tree_path_free(tpath);
666                 }
667
668         if (bevent->button == MOUSE_BUTTON_MIDDLE)
669                 {
670                 if (fd && vf->click_fd == fd)
671                         {
672                         GtkTreeSelection *selection;
673
674                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
675                         if (vflist_row_is_selected(vf, fd))
676                                 {
677                                 gtk_tree_selection_unselect_iter(selection, &iter);
678                                 }
679                         else
680                                 {
681                                 gtk_tree_selection_select_iter(selection, &iter);
682                                 }
683                         }
684                 return TRUE;
685                 }
686
687         if (fd && vf->click_fd == fd &&
688             !(bevent->state & GDK_SHIFT_MASK ) &&
689             !(bevent->state & GDK_CONTROL_MASK ) &&
690             vflist_row_is_selected(vf, fd))
691                 {
692                 GtkTreeSelection *selection;
693
694                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
695                 gtk_tree_selection_unselect_all(selection);
696                 gtk_tree_selection_select_iter(selection, &iter);
697                 vflist_move_cursor(vf, &iter);
698                 }
699
700         return FALSE;
701 }
702
703 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
704 {
705         FileData *read_ahead_fd = nullptr;
706         gint row;
707         FileData *cur_fd;
708
709         if (!sel_fd) return;
710
711         cur_fd = layout_image_get_fd(vf->layout);
712         if (sel_fd == cur_fd) return; /* no change */
713
714         row = g_list_index(vf->list, sel_fd);
715         /** @FIXME sidecar data */
716
717         if (sel_fd && options->image.enable_read_ahead && row >= 0)
718                 {
719                 if (row > g_list_index(vf->list, cur_fd) &&
720                     static_cast<guint>(row + 1) < vf_count(vf, nullptr))
721                         {
722                         read_ahead_fd = vf_index_get_data(vf, row + 1);
723                         }
724                 else if (row > 0)
725                         {
726                         read_ahead_fd = vf_index_get_data(vf, row - 1);
727                         }
728                 }
729
730         layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
731 }
732
733 static gboolean vflist_select_idle_cb(gpointer data)
734 {
735         auto vf = static_cast<ViewFile *>(data);
736
737         if (!vf->layout)
738                 {
739                 VFLIST(vf)->select_idle_id = 0;
740                 return G_SOURCE_REMOVE;
741                 }
742
743         vf_send_update(vf);
744
745         if (VFLIST(vf)->select_fd)
746                 {
747                 vflist_select_image(vf, VFLIST(vf)->select_fd);
748                 VFLIST(vf)->select_fd = nullptr;
749                 }
750
751         VFLIST(vf)->select_idle_id = 0;
752         return G_SOURCE_REMOVE;
753 }
754
755 static void vflist_select_idle_cancel(ViewFile *vf)
756 {
757         if (VFLIST(vf)->select_idle_id)
758                 {
759                 g_source_remove(VFLIST(vf)->select_idle_id);
760                 VFLIST(vf)->select_idle_id = 0;
761                 }
762 }
763
764 static gboolean vflist_select_cb(GtkTreeSelection *, GtkTreeModel *store, GtkTreePath *tpath, gboolean path_currently_selected, gpointer data)
765 {
766         auto vf = static_cast<ViewFile *>(data);
767         GtkTreeIter iter;
768         GtkTreePath *cursor_path;
769
770         VFLIST(vf)->select_fd = nullptr;
771
772         if (!path_currently_selected && gtk_tree_model_get_iter(store, &iter, tpath))
773                 {
774                 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &cursor_path, nullptr);
775                 if (cursor_path)
776                         {
777                         gtk_tree_model_get_iter(store, &iter, cursor_path);
778                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
779                         gtk_tree_path_free(cursor_path);
780                         }
781                 }
782
783         if (vf->layout &&
784             !VFLIST(vf)->select_idle_id)
785                 {
786                 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
787                 }
788
789         return TRUE;
790 }
791
792 static void vflist_expand_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
793 {
794         auto vf = static_cast<ViewFile *>(data);
795         vflist_set_expanded(vf, iter, TRUE);
796 }
797
798 static void vflist_collapse_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
799 {
800         auto vf = static_cast<ViewFile *>(data);
801         vflist_set_expanded(vf, iter, FALSE);
802 }
803
804 /*
805  *-----------------------------------------------------------------------------
806  * misc
807  *-----------------------------------------------------------------------------
808  */
809
810
811 static gchar* vflist_get_formatted(ViewFile *vf, const gchar *name, const gchar *sidecars, const gchar *size, const gchar *time, gboolean expanded, gboolean with_stars, const gchar *star_rating)
812  {
813         gboolean multiline = vflist_is_multiline(vf);
814         gchar *text;
815
816         if (multiline)
817                 {
818                 if (with_stars)
819                         {
820                                         text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
821                         }
822                 else
823                         {
824                         text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
825                         }
826                 }
827         else
828                 {
829                 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
830                 }
831         return text;
832 }
833
834 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
835 {
836         GtkTreeStore *store;
837         gchar *name;
838         gchar *sidecars;
839         gchar *size;
840         gchar *time;
841         gchar *formatted;
842         gchar *formatted_with_stars;
843         gchar *star_rating;
844         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
845
846         gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
847                                         FILE_COLUMN_NAME, &name,
848                                         FILE_COLUMN_SIDECARS, &sidecars,
849                                         FILE_COLUMN_SIZE, &size,
850                                         FILE_COLUMN_DATE, &time,
851                                         FILE_COLUMN_STAR_RATING, &star_rating,
852                                         -1);
853
854         formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
855         formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
856
857         gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
858                                         FILE_COLUMN_EXPANDED, expanded,
859                                         -1);
860         gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
861                                         FILE_COLUMN_EXPANDED, expanded,
862                                         -1);
863         g_free(time);
864         g_free(size);
865         g_free(sidecars);
866         g_free(name);
867         g_free(formatted);
868         g_free(formatted_with_stars);
869 }
870
871 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
872 {
873         gchar *size;
874         gchar *sidecars = nullptr;
875         gchar *name;
876         const gchar *time = text_from_time(fd->date);
877         const gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
878         const gchar *disabled_grouping;
879         gchar *formatted;
880         gchar *formatted_with_stars;
881         gboolean expanded = FALSE;
882         gchar *star_rating;
883
884         if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
885                 {
886                 star_rating = convert_rating_to_stars(fd->rating);
887                 }
888         else
889                 {
890                 star_rating = nullptr;
891                 }
892
893         if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
894                 {
895                 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
896                 }
897
898         sidecars = file_data_sc_list_to_string(fd);
899
900         disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
901         name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
902         size = text_from_size(fd->size);
903
904         formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
905         formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
906
907         gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
908                                         FILE_COLUMN_VERSION, fd->version,
909                                         FILE_COLUMN_THUMB, fd->thumb_pixbuf,
910                                         FILE_COLUMN_FORMATTED, formatted,
911                                         FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
912                                         FILE_COLUMN_SIDECARS, sidecars,
913                                         FILE_COLUMN_NAME, name,
914                                         FILE_COLUMN_STAR_RATING, star_rating,
915                                         FILE_COLUMN_SIZE, size,
916                                         FILE_COLUMN_DATE, time,
917 #define STORE_SET_IS_SLOW 1
918 #if STORE_SET_IS_SLOW
919 /* this is 3x faster on a directory with 20000 files */
920                                         FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
921                                         FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
922                                         FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
923                                         FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
924                                         FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
925                                         FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
926                                         FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
927                                         FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
928                                         FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
929                                         FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
930 #if FILEDATA_MARKS_SIZE != 10
931 #error this needs to be updated
932 #endif
933 #endif
934                                         FILE_COLUMN_COLOR, FALSE, -1);
935
936 #if !STORE_SET_IS_SLOW
937         {
938         gint i;
939         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
940                 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
941         }
942 #endif
943         g_free(size);
944         g_free(sidecars);
945         g_free(name);
946         g_free(formatted);
947 }
948
949 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
950 {
951         GList *work;
952         GtkTreeIter iter;
953         gboolean valid;
954         gint num_ordered = 0;
955         gint num_prepended = 0;
956
957         valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
958
959         work = list;
960         while (work)
961                 {
962                 gint match;
963                 auto fd = static_cast<FileData *>(work->data);
964                 gboolean done = FALSE;
965
966                 while (!done)
967                         {
968                         FileData *old_fd = nullptr;
969                         gint old_version = 0;
970
971                         if (valid)
972                                 {
973                                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
974                                                    FILE_COLUMN_POINTER, &old_fd,
975                                                    FILE_COLUMN_VERSION, &old_version,
976                                                    -1);
977
978                                 if (fd == old_fd)
979                                         {
980                                         match = 0;
981                                         }
982                                 else
983                                         {
984                                         if (parent_iter)
985                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
986                                         else
987                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
988
989                                         if (match == 0) g_warning("multiple fd for the same path");
990                                         }
991
992                                 }
993                         else
994                                 {
995                                 match = -1;
996                                 }
997
998                         if (match < 0)
999                                 {
1000                                 GtkTreeIter new_iter;
1001
1002                                 if (valid)
1003                                         {
1004                                         num_ordered++;
1005                                         gtk_tree_store_insert_before(store, &new_iter, parent_iter, &iter);
1006                                         }
1007                                 else
1008                                         {
1009                                         /*
1010                                             here should be used gtk_tree_store_append, but this function seems to be O(n)
1011                                             and it seems to be much faster to add new entries to the beginning and reorder later
1012                                         */
1013                                         num_prepended++;
1014                                         gtk_tree_store_prepend(store, &new_iter, parent_iter);
1015                                         }
1016
1017                                 vflist_setup_iter(vf, store, &new_iter, file_data_ref(fd));
1018                                 vflist_setup_iter_recursive(vf, store, &new_iter, fd->sidecar_files, selected, force);
1019
1020                                 if (g_list_find(selected, fd))
1021                                         {
1022                                         /* renamed files - the same fd appears at different position - select it again*/
1023                                         GtkTreeSelection *selection;
1024                                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1025                                         gtk_tree_selection_select_iter(selection, &new_iter);
1026                                         }
1027
1028                                 done = TRUE;
1029                                 }
1030                         else if (match > 0)
1031                                 {
1032                                 file_data_unref(old_fd);
1033                                 valid = gtk_tree_store_remove(store, &iter);
1034                                 }
1035                         else
1036                                 {
1037                                 num_ordered++;
1038                                 if (fd->version != old_version || force)
1039                                         {
1040                                         vflist_setup_iter(vf, store, &iter, fd);
1041                                         vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
1042                                         }
1043
1044                                 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1045
1046                                 done = TRUE;
1047                                 }
1048                         }
1049                 work = work->next;
1050                 }
1051
1052         while (valid)
1053                 {
1054                 FileData *old_fd;
1055                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
1056                 file_data_unref(old_fd);
1057
1058                 valid = gtk_tree_store_remove(store, &iter);
1059                 }
1060
1061         /* move the prepended entries to the correct position */
1062         if (num_prepended)
1063                 {
1064                 gint num_total = num_prepended + num_ordered;
1065                 std::vector<gint> new_order;
1066                 new_order.reserve(num_total);
1067
1068                 for (gint i = 0; i < num_ordered; i++)
1069                         {
1070                         new_order.push_back(num_prepended + i);
1071                         }
1072                 for (gint i = num_ordered; i < num_total; i++)
1073                         {
1074                         new_order.push_back(num_total - 1 - i);
1075                         }
1076                 gtk_tree_store_reorder(store, parent_iter, new_order.data());
1077                 }
1078 }
1079
1080 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend, gboolean case_sensitive)
1081 {
1082         gint i;
1083         GHashTable *fd_idx_hash = g_hash_table_new(nullptr, nullptr);
1084         GtkTreeStore *store;
1085         GList *work;
1086
1087         if (vf->sort_method == type && vf->sort_ascend == ascend && vf->sort_case == case_sensitive) return;
1088         if (!vf->list) return;
1089
1090         work = vf->list;
1091         i = 0;
1092         while (work)
1093                 {
1094                 auto fd = static_cast<FileData *>(work->data);
1095                 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1096                 i++;
1097                 work = work->next;
1098                 }
1099
1100         vf->sort_method = type;
1101         vf->sort_ascend = ascend;
1102         vf->sort_case = case_sensitive;
1103
1104         vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1105
1106         std::vector<gint> new_order;
1107         new_order.reserve(i);
1108
1109         work = vf->list;
1110         while (work)
1111                 {
1112                 auto fd = static_cast<FileData *>(work->data);
1113                 new_order.push_back(GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd)));
1114                 work = work->next;
1115                 }
1116
1117         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1118         gtk_tree_store_reorder(store, nullptr, new_order.data());
1119
1120         g_hash_table_destroy(fd_idx_hash);
1121 }
1122
1123 /*
1124  *-----------------------------------------------------------------------------
1125  * thumb updates
1126  *-----------------------------------------------------------------------------
1127  */
1128
1129
1130 void vflist_thumb_progress_count(const GList *list, gint &count, gint &done)
1131 {
1132         for (const GList *work = list; work; work = work->next)
1133                 {
1134                 auto fd = static_cast<FileData *>(work->data);
1135
1136                 if (fd->thumb_pixbuf) done++;
1137
1138                 if (fd->sidecar_files)
1139                         {
1140                         vflist_thumb_progress_count(fd->sidecar_files, count, done);
1141                         }
1142                 count++;
1143                 }
1144 }
1145
1146 void vflist_read_metadata_progress_count(const GList *list, gint &count, gint &done)
1147 {
1148         for (const GList *work = list; work; work = work->next)
1149                 {
1150                 auto fd = static_cast<FileData *>(work->data);
1151
1152                 if (fd->metadata_in_idle_loaded) done++;
1153
1154                 if (fd->sidecar_files)
1155                         {
1156                         vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1157                         }
1158                 count++;
1159                 }
1160 }
1161
1162 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1163 {
1164         GtkTreeStore *store;
1165         GtkTreeIter iter;
1166
1167         if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1168
1169         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1170         gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1171 }
1172
1173 FileData *vflist_thumb_next_fd(ViewFile *vf)
1174 {
1175         GtkTreePath *tpath;
1176         FileData *fd = nullptr;
1177
1178         /* first check the visible files */
1179
1180         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1181                 {
1182                 GtkTreeModel *store;
1183                 GtkTreeIter iter;
1184                 gboolean valid = TRUE;
1185
1186                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1187                 gtk_tree_model_get_iter(store, &iter, tpath);
1188                 gtk_tree_path_free(tpath);
1189                 tpath = nullptr;
1190
1191                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1192                         {
1193                         FileData *nfd;
1194
1195                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1196
1197                         if (!nfd->thumb_pixbuf) fd = nfd;
1198
1199                         valid = gtk_tree_model_iter_next(store, &iter);
1200                         }
1201                 }
1202
1203         /* then find first undone */
1204
1205         if (!fd)
1206                 {
1207                 GList *work = vf->list;
1208                 while (work && !fd)
1209                         {
1210                         auto fd_p = static_cast<FileData *>(work->data);
1211                         if (!fd_p->thumb_pixbuf)
1212                                 fd = fd_p;
1213                         else
1214                                 {
1215                                 GList *work2 = fd_p->sidecar_files;
1216
1217                                 while (work2 && !fd)
1218                                         {
1219                                         fd_p = static_cast<FileData *>(work2->data);
1220                                         if (!fd_p->thumb_pixbuf) fd = fd_p;
1221                                         work2 = work2->next;
1222                                         }
1223                                 }
1224                         work = work->next;
1225                         }
1226                 }
1227
1228         return fd;
1229 }
1230
1231 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1232 {
1233         GtkTreeStore *store;
1234         GtkTreeIter iter;
1235         gchar *name;
1236         gchar *sidecars;
1237         gchar *size;
1238         gchar *time;
1239         gchar *star_rating;
1240         gchar *formatted_with_stars;
1241         gboolean expanded;
1242
1243         if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1244
1245         star_rating = metadata_read_rating_stars(fd);
1246
1247         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1248         gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1249
1250         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1251                                         FILE_COLUMN_NAME, &name,
1252                                         FILE_COLUMN_SIDECARS, &sidecars,
1253                                         FILE_COLUMN_SIZE, &size,
1254                                         FILE_COLUMN_DATE, &time,
1255                                         FILE_COLUMN_EXPANDED, &expanded,
1256                                         -1);
1257
1258         formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1259
1260         gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1261                                         FILE_COLUMN_EXPANDED, expanded,
1262                                         -1);
1263
1264         g_free(star_rating);
1265         g_free(formatted_with_stars);
1266 }
1267
1268 FileData *vflist_star_next_fd(ViewFile *vf)
1269 {
1270         GtkTreePath *tpath;
1271         FileData *fd = nullptr;
1272         FileData *nfd = nullptr;
1273
1274         /* first check the visible files */
1275
1276         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1277                 {
1278                 GtkTreeModel *store;
1279                 GtkTreeIter iter;
1280                 gboolean valid = TRUE;
1281
1282                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1283                 gtk_tree_model_get_iter(store, &iter, tpath);
1284                 gtk_tree_path_free(tpath);
1285                 tpath = nullptr;
1286
1287                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1288                         {
1289                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1290
1291                         if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1292                                 {
1293                                 fd = nfd;
1294                                 }
1295
1296                         valid = gtk_tree_model_iter_next(store, &iter);
1297                         }
1298
1299                 if (fd)
1300                         {
1301                         vf->stars_filedata = fd;
1302
1303                         if (vf->stars_id == 0)
1304                                 {
1305                                 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1306                                 }
1307                         }
1308                 }
1309
1310         /* then find first undone */
1311
1312         if (!fd)
1313                 {
1314                 GList *work = vf->list;
1315
1316                 while (work && !fd)
1317                         {
1318                         auto fd_p = static_cast<FileData *>(work->data);
1319
1320                         if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1321                                 {
1322                                 fd = fd_p;
1323                                 }
1324                         else
1325                                 {
1326                                 fd = nullptr;
1327                                 }
1328
1329                         work = work->next;
1330                         }
1331
1332                 if (fd)
1333                         {
1334                         vf->stars_filedata = fd;
1335
1336                         if (vf->stars_id == 0)
1337                                 {
1338                                 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1339                                 }
1340                         }
1341                 }
1342
1343         return fd;
1344 }
1345
1346 /*
1347  *-----------------------------------------------------------------------------
1348  * row stuff
1349  *-----------------------------------------------------------------------------
1350  */
1351
1352 gint vflist_index_by_fd(const ViewFile *vf, const FileData *fd)
1353 {
1354         gint p = 0;
1355
1356         for (const GList *work = vf->list; work; work = work->next)
1357                 {
1358                 auto list_fd = static_cast<FileData *>(work->data);
1359                 if (list_fd == fd) return p;
1360
1361                 /** @FIXME return the same index also for sidecars
1362                    it is sufficient for next/prev navigation but it should be rewritten
1363                    without using indexes at all
1364                 */
1365                 if (g_list_find(list_fd->sidecar_files, fd)) return p;
1366
1367                 p++;
1368                 }
1369
1370         return -1;
1371 }
1372
1373 /*
1374  *-----------------------------------------------------------------------------
1375  * selections
1376  *-----------------------------------------------------------------------------
1377  */
1378
1379 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1380 {
1381         GtkTreeModel *store;
1382         GtkTreeSelection *selection;
1383         GList *slist;
1384         GList *work;
1385         gboolean found = FALSE;
1386
1387         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1388         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1389         work = slist;
1390         while (!found && work)
1391                 {
1392                 auto tpath = static_cast<GtkTreePath *>(work->data);
1393                 FileData *fd_n;
1394                 GtkTreeIter iter;
1395
1396                 gtk_tree_model_get_iter(store, &iter, tpath);
1397                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1398                 if (fd_n == fd) found = TRUE;
1399                 work = work->next;
1400                 }
1401         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1402
1403         return found;
1404 }
1405
1406 #pragma GCC diagnostic push
1407 #pragma GCC diagnostic ignored "-Wunused-function"
1408 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1409 {
1410         FileData *fd;
1411
1412         fd = vf_index_get_data(vf, row);
1413         return vflist_row_is_selected(vf, fd);
1414 }
1415 #pragma GCC diagnostic pop
1416
1417 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1418 {
1419         GtkTreeModel *store;
1420         GtkTreeSelection *selection;
1421         GList *slist;
1422         guint count;
1423
1424         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1425         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1426
1427         if (bytes)
1428                 {
1429                 gint64 b = 0;
1430                 GList *work;
1431
1432                 work = slist;
1433                 while (work)
1434                         {
1435                         auto tpath = static_cast<GtkTreePath *>(work->data);
1436                         GtkTreeIter iter;
1437                         FileData *fd;
1438
1439                         gtk_tree_model_get_iter(store, &iter, tpath);
1440                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1441                         b += fd->size;
1442
1443                         work = work->next;
1444                         }
1445
1446                 *bytes = b;
1447                 }
1448
1449         count = g_list_length(slist);
1450         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1451
1452         return count;
1453 }
1454
1455 GList *vflist_selection_get_list(ViewFile *vf)
1456 {
1457         GtkTreeModel *store;
1458         GtkTreeSelection *selection;
1459         GList *slist;
1460         GList *list = nullptr;
1461
1462         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1463         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1464         for (GList *work = g_list_last(slist); work; work = work->prev)
1465                 {
1466                 auto tpath = static_cast<GtkTreePath *>(work->data);
1467                 FileData *fd;
1468                 GtkTreeIter iter;
1469
1470                 gtk_tree_model_get_iter(store, &iter, tpath);
1471                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1472
1473                 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1474                         {
1475                         /* unexpanded - add whole group */
1476                         list = g_list_concat(filelist_copy(fd->sidecar_files), list);
1477                         }
1478
1479                 list = g_list_prepend(list, file_data_ref(fd));
1480                 }
1481         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1482
1483         return list;
1484 }
1485
1486 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1487 {
1488         GtkTreeModel *store;
1489         GtkTreeSelection *selection;
1490         GList *slist;
1491         GList *list = nullptr;
1492         GList *work;
1493
1494         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1495         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1496         work = slist;
1497         while (work)
1498                 {
1499                 auto tpath = static_cast<GtkTreePath *>(work->data);
1500                 FileData *fd;
1501                 GtkTreeIter iter;
1502
1503                 gtk_tree_model_get_iter(store, &iter, tpath);
1504                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1505
1506                 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1507
1508                 work = work->next;
1509                 }
1510         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1511
1512         return g_list_reverse(list);
1513 }
1514
1515 void vflist_selection_foreach(ViewFile *vf, const ViewFile::SelectionCallback &func)
1516 {
1517         GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1518         GtkTreeModel *store;
1519         GtkTreeIter iter;
1520         FileData *fd_n;
1521
1522         for (GList *work = gtk_tree_selection_get_selected_rows(selection, &store); work; work = work->next)
1523                 {
1524                 auto *tpath = static_cast<GtkTreePath *>(work->data);
1525
1526                 gtk_tree_model_get_iter(store, &iter, tpath);
1527                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1528
1529                 func(fd_n);
1530                 }
1531 }
1532
1533 void vflist_select_all(ViewFile *vf)
1534 {
1535         GtkTreeSelection *selection;
1536
1537         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1538         gtk_tree_selection_select_all(selection);
1539
1540         VFLIST(vf)->select_fd = nullptr;
1541 }
1542
1543 void vflist_select_none(ViewFile *vf)
1544 {
1545         GtkTreeSelection *selection;
1546
1547         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1548         gtk_tree_selection_unselect_all(selection);
1549 }
1550
1551 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1552 {
1553         GtkTreePath *tpath;
1554         gboolean result;
1555
1556         tpath = gtk_tree_model_get_path(store, iter);
1557         result = gtk_tree_path_prev(tpath);
1558         if (result)
1559                 gtk_tree_model_get_iter(store, iter, tpath);
1560
1561         gtk_tree_path_free(tpath);
1562
1563         return result;
1564 }
1565
1566 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1567 {
1568         if (!gtk_tree_model_get_iter_first(store, iter))
1569                 return FALSE;
1570
1571         while (TRUE)
1572                 {
1573                 GtkTreeIter next = *iter;
1574
1575                 if (gtk_tree_model_iter_next(store, &next))
1576                         *iter = next;
1577                 else
1578                         break;
1579                 }
1580
1581         return TRUE;
1582 }
1583
1584 void vflist_select_invert(ViewFile *vf)
1585 {
1586         GtkTreeIter iter;
1587         GtkTreeSelection *selection;
1588         GtkTreeModel *store;
1589         gboolean valid;
1590
1591         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1592         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1593
1594         /* Backward iteration prevents scrolling to the end of the list,
1595          * it scrolls to the first selected row instead. */
1596         valid = tree_model_get_iter_last(store, &iter);
1597
1598         while (valid)
1599                 {
1600                 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1601
1602                 if (selected)
1603                         gtk_tree_selection_unselect_iter(selection, &iter);
1604                 else
1605                         gtk_tree_selection_select_iter(selection, &iter);
1606
1607                 valid = tree_model_iter_prev(store, &iter);
1608                 }
1609 }
1610
1611 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1612 {
1613         GtkTreeIter iter;
1614
1615         if (vflist_find_row(vf, fd, &iter) < 0) return;
1616
1617         tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1618
1619         if (!vflist_row_is_selected(vf, fd))
1620                 {
1621                 GtkTreeSelection *selection;
1622                 GtkTreeModel *store;
1623                 GtkTreePath *tpath;
1624
1625                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1626                 gtk_tree_selection_unselect_all(selection);
1627                 gtk_tree_selection_select_iter(selection, &iter);
1628                 vflist_move_cursor(vf, &iter);
1629
1630                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1631                 tpath = gtk_tree_model_get_path(store, &iter);
1632                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
1633                 gtk_tree_path_free(tpath);
1634                 }
1635 }
1636
1637 void vflist_select_list(ViewFile *vf, GList *list)
1638 {
1639         GtkTreeIter iter;
1640         GList *work;
1641
1642         work = list;
1643
1644         while (work)
1645                 {
1646                 FileData *fd;
1647
1648                 fd = static_cast<FileData *>(work->data);
1649
1650                 if (vflist_find_row(vf, fd, &iter) < 0) return;
1651                 if (!vflist_row_is_selected(vf, fd))
1652                         {
1653                         GtkTreeSelection *selection;
1654
1655                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1656                         gtk_tree_selection_select_iter(selection, &iter);
1657                         }
1658                 work = work->next;
1659                 }
1660 }
1661
1662 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1663 {
1664         GList *work;
1665         FileData *fd = nullptr;
1666
1667         if (sel_fd->parent) sel_fd = sel_fd->parent;
1668         work = vf->list;
1669
1670         while (work)
1671                 {
1672                 gint match;
1673                 fd = static_cast<FileData *>(work->data);
1674                 work = work->next;
1675
1676                 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1677
1678                 if (match >= 0) break;
1679                 }
1680
1681         if (fd) vflist_select_by_fd(vf, fd);
1682
1683 }
1684
1685 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1686 {
1687         GtkTreeModel *store;
1688         GtkTreeIter iter;
1689         GtkTreeSelection *selection;
1690         gboolean valid;
1691         gint n = mark - 1;
1692
1693         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1694
1695         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1696         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1697
1698         valid = gtk_tree_model_get_iter_first(store, &iter);
1699         while (valid)
1700                 {
1701                 FileData *fd;
1702                 gboolean mark_val;
1703                 gboolean selected;
1704                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1705
1706                 mark_val = file_data_get_mark(fd, n);
1707                 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1708
1709                 switch (mode)
1710                         {
1711                         case MTS_MODE_SET: selected = mark_val;
1712                                 break;
1713                         case MTS_MODE_OR: selected = mark_val || selected;
1714                                 break;
1715                         case MTS_MODE_AND: selected = mark_val && selected;
1716                                 break;
1717                         case MTS_MODE_MINUS: selected = !mark_val && selected;
1718                                 break;
1719                         }
1720
1721                 if (selected)
1722                         gtk_tree_selection_select_iter(selection, &iter);
1723                 else
1724                         gtk_tree_selection_unselect_iter(selection, &iter);
1725
1726                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1727                 }
1728 }
1729
1730 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1731 {
1732         GtkTreeModel *store;
1733         GtkTreeSelection *selection;
1734         GList *slist;
1735         GList *work;
1736         gint n = mark - 1;
1737
1738         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1739
1740         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1741         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1742         work = slist;
1743         while (work)
1744                 {
1745                 auto tpath = static_cast<GtkTreePath *>(work->data);
1746                 FileData *fd;
1747                 GtkTreeIter iter;
1748
1749                 gtk_tree_model_get_iter(store, &iter, tpath);
1750                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1751
1752                 /* the change has a very limited range and the standard notification would trigger
1753                    complete re-read of the directory - try to do only minimal update instead */
1754                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1755
1756                 switch (mode)
1757                         {
1758                         case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1759                                 break;
1760                         case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1761                                 break;
1762                         case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1763                                 break;
1764                         }
1765
1766                 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1767                         {
1768                         vf_refresh_idle(vf);
1769                         }
1770                 else
1771                         {
1772                         /* mark functions can have various side effects - update all columns to be sure */
1773                         vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1774                         /* mark functions can change sidecars too */
1775                         vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1776                         }
1777
1778
1779                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1780
1781                 work = work->next;
1782                 }
1783         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1784 }
1785
1786 /*
1787  *-----------------------------------------------------------------------------
1788  * core (population)
1789  *-----------------------------------------------------------------------------
1790  */
1791
1792 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1793 {
1794         GtkTreeViewColumn *column;
1795         GtkCellRenderer *cell;
1796         GList *list;
1797
1798         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1799         if (!column) return;
1800
1801         gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1802
1803         list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1804         if (!list) return;
1805         cell = static_cast<GtkCellRenderer *>(list->data);
1806         g_list_free(list);
1807
1808         g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1809         gtk_tree_view_column_set_visible(column, thumb);
1810
1811         if (options->show_star_rating)
1812                 {
1813                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1814                 if (!column) return;
1815                 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1816                 gtk_tree_view_column_set_visible(column, TRUE);
1817
1818                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1819                 if (!column) return;
1820                 gtk_tree_view_column_set_visible(column, FALSE);
1821                 }
1822         else
1823                 {
1824                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1825                 if (!column) return;
1826                 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1827                 gtk_tree_view_column_set_visible(column, TRUE);
1828
1829                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1830                 if (!column) return;
1831                 gtk_tree_view_column_set_visible(column, FALSE);
1832                 }
1833
1834         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1835         if (!column) return;
1836         gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1837
1838         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1839         if (!column) return;
1840         gtk_tree_view_column_set_visible(column, !multiline);
1841
1842         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1843         if (!column) return;
1844         gtk_tree_view_column_set_visible(column, !multiline);
1845 }
1846
1847 static gboolean vflist_is_multiline(ViewFile *vf)
1848 {
1849         return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1850 }
1851
1852
1853 static void vflist_populate_view(ViewFile *vf, gboolean force)
1854 {
1855         GtkTreeStore *store;
1856         GList *selected;
1857
1858         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1859
1860         vf_thumb_stop(vf);
1861         vf_star_stop(vf);
1862
1863         if (!vf->list)
1864                 {
1865                 vflist_store_clear(vf, FALSE);
1866                 vf_send_update(vf);
1867                 return;
1868                 }
1869
1870         vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1871
1872         selected = vflist_selection_get_list(vf);
1873
1874         vflist_setup_iter_recursive(vf, store, nullptr, vf->list, selected, force);
1875
1876         if (selected && vflist_selection_count(vf, nullptr) == 0)
1877                 {
1878                 /* all selected files disappeared */
1879                 vflist_select_closest(vf, static_cast<FileData *>(selected->data));
1880                 }
1881
1882         filelist_free(selected);
1883
1884         vf_send_update(vf);
1885         vf_thumb_update(vf);
1886         vf_star_update(vf);
1887 }
1888
1889 gboolean vflist_refresh(ViewFile *vf)
1890 {
1891         GList *old_list;
1892         gboolean ret = TRUE;
1893
1894         old_list = vf->list;
1895         vf->list = nullptr;
1896
1897         DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1898         if (vf->dir_fd)
1899                 {
1900                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1901
1902                 ret = filelist_read(vf->dir_fd, &vf->list, nullptr);
1903
1904                 if (vf->marks_enabled)
1905                         {
1906                         // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1907                         // each time a mark is changed.
1908                         file_data_lock_list(vf->list);
1909                         }
1910                 else
1911                         {
1912                         /** @FIXME only do this when needed (aka when we just switched from */
1913                         /** @FIXME marks-enabled to marks-disabled) */
1914                         file_data_unlock_list(vf->list);
1915                         }
1916
1917                 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1918                 vf->list = g_list_first(vf->list);
1919                 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1920
1921                 vf->list = g_list_first(vf->list);
1922                 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1923
1924                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1925
1926                 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1927                 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1928                 }
1929
1930         DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1931
1932         vflist_populate_view(vf, FALSE);
1933
1934         DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1935
1936         filelist_free(old_list);
1937         DEBUG_1("%s vflist_refresh: done", get_exec_time());
1938
1939         return ret;
1940 }
1941
1942
1943 static GdkRGBA *vflist_listview_color_shifted(GtkWidget *widget)
1944 {
1945         static GdkRGBA color;
1946         static GtkWidget *done = nullptr;
1947
1948         if (done != widget)
1949                 {
1950                 GtkStyle *style;
1951
1952                 style = gtk_widget_get_style(widget);
1953                 convert_gdkcolor_to_gdkrgba(&style->base[GTK_STATE_NORMAL], &color);
1954
1955                 shift_color(&color, -1, 0);
1956                 done = widget;
1957                 }
1958
1959         return &color;
1960 }
1961
1962 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1963                                      GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1964 {
1965         auto vf = static_cast<ViewFile *>(data);
1966         gboolean set;
1967
1968         gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1969         g_object_set(G_OBJECT(cell),
1970                      "cell-background-rgba", vflist_listview_color_shifted(vf->listview),
1971                      "cell-background-set", set, NULL);
1972 }
1973
1974 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1975 {
1976         GtkTreeViewColumn *column;
1977         GtkCellRenderer *renderer;
1978
1979         column = gtk_tree_view_column_new();
1980         gtk_tree_view_column_set_title(column, title);
1981         gtk_tree_view_column_set_min_width(column, 4);
1982
1983         if (!image)
1984                 {
1985                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1986                 renderer = gtk_cell_renderer_text_new();
1987                 if (right_justify)
1988                         {
1989                         g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
1990                         }
1991                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1992                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
1993                 if (expand)
1994                         gtk_tree_view_column_set_expand(column, TRUE);
1995                 }
1996         else
1997                 {
1998                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1999                 renderer = gtk_cell_renderer_pixbuf_new();
2000                 cell_renderer_height_override(renderer);
2001                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2002                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2003                 }
2004
2005         gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
2006         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2007         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2008
2009         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2010 }
2011
2012 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2013 {
2014         auto vf = static_cast<ViewFile *>(data);
2015         GtkTreeStore *store;
2016         GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2017         GtkTreeIter iter;
2018         FileData *fd;
2019         gboolean marked;
2020         guint col_idx;
2021
2022         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2023         if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2024                 return;
2025
2026         col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2027
2028         g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2029
2030         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2031         marked = !marked;
2032
2033         /* the change has a very limited range and the standard notification would trigger
2034            complete re-read of the directory - try to do only minimal update instead */
2035         file_data_unregister_notify_func(vf_notify_cb, vf);
2036         file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2037         if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2038                 {
2039                 vf_refresh_idle(vf);
2040                 }
2041         else
2042                 {
2043                 /* mark functions can have various side effects - update all columns to be sure */
2044                 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2045                 /* mark functions can change sidecars too */
2046                 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
2047                 }
2048         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2049
2050         gtk_tree_path_free(path);
2051 }
2052
2053 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2054 {
2055         GtkTreeViewColumn *column;
2056         GtkCellRenderer *renderer;
2057
2058         renderer = gtk_cell_renderer_toggle_new();
2059         column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2060
2061         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2062         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2063         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2064
2065         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2066         gtk_tree_view_column_set_fixed_width(column, 22);
2067         gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2068
2069
2070         g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2071 }
2072
2073 /*
2074  *-----------------------------------------------------------------------------
2075  * base
2076  *-----------------------------------------------------------------------------
2077  */
2078
2079 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2080 {
2081         gboolean ret;
2082         if (!dir_fd) return FALSE;
2083         if (vf->dir_fd == dir_fd) return TRUE;
2084
2085         file_data_unref(vf->dir_fd);
2086         vf->dir_fd = file_data_ref(dir_fd);
2087
2088         /* force complete reload */
2089         vflist_store_clear(vf, TRUE);
2090
2091         filelist_free(vf->list);
2092         vf->list = nullptr;
2093
2094         ret = vf_refresh(vf);
2095         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2096         return ret;
2097 }
2098
2099 void vflist_destroy_cb(ViewFile *vf)
2100 {
2101         file_data_unregister_notify_func(vf_notify_cb, vf);
2102
2103         vflist_select_idle_cancel(vf);
2104         vf_refresh_idle_cancel(vf);
2105         vf_thumb_stop(vf);
2106         vf_star_stop(vf);
2107
2108         filelist_free(vf->list);
2109 }
2110
2111 ViewFile *vflist_new(ViewFile *vf)
2112 {
2113         GtkTreeStore *store;
2114         GtkTreeSelection *selection;
2115         GType flist_types[FILE_COLUMN_COUNT];
2116         gint i;
2117         gint column;
2118
2119         vf->info = g_new0(ViewFileInfoList, 1);
2120
2121         flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2122         flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2123         flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2124         flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2125         flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2126         flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2127         flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2128         flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2129         flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2130         flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2131         flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2132         flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2133         for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2134                 flist_types[i] = G_TYPE_BOOLEAN;
2135
2136         store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2137
2138         vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2139         g_object_unref(store);
2140
2141         g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2142                          G_CALLBACK(vflist_expand_cb), vf);
2143
2144         g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2145                          G_CALLBACK(vflist_collapse_cb), vf);
2146
2147         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2148         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2149         gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2150
2151         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2152         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2153
2154         gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2155
2156         column = 0;
2157
2158         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2159                 {
2160                 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2161                 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2162                 column++;
2163                 }
2164
2165         vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2166         g_assert(column == FILE_VIEW_COLUMN_THUMB);
2167         column++;
2168
2169         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2170         g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2171         column++;
2172
2173         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2174         g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2175         column++;
2176
2177         vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2178         g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2179         column++;
2180
2181         vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2182         g_assert(column == FILE_VIEW_COLUMN_SIZE);
2183         column++;
2184
2185         vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2186         g_assert(column == FILE_VIEW_COLUMN_DATE);
2187         column++;
2188
2189         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2190         return vf;
2191 }
2192
2193 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2194 {
2195         if (VFLIST(vf)->thumbs_enabled == enable) return;
2196
2197         VFLIST(vf)->thumbs_enabled = enable;
2198
2199         /* vflist_populate_view is better than vf_refresh:
2200            - no need to re-read the directory
2201            - force update because the formatted string has changed
2202         */
2203         if (vf->layout)
2204                 {
2205                 vflist_populate_view(vf, TRUE);
2206                 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2207                 }
2208 }
2209
2210 void vflist_marks_set(ViewFile *vf, gboolean enable)
2211 {
2212         GList *columns;
2213         GList *work;
2214
2215         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2216
2217         work = columns;
2218         while (work)
2219                 {
2220                 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2221                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2222                 work = work->next;
2223
2224                 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2225                         gtk_tree_view_column_set_visible(column, enable);
2226                 }
2227
2228         if (enable)
2229                 {
2230                 // Previously disabled, which means that vf->list is complete
2231                 file_data_lock_list(vf->list);
2232                 }
2233         else
2234                 {
2235                 // Previously enabled, which means that vf->list is incomplete
2236                 }
2237
2238         g_list_free(columns);
2239 }
2240
2241 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */