a1bc99556021b1878730588aa91d84c204275e01
[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 <config.h>
25
26 #include "collect.h"
27 #include "debug.h"
28 #include "dnd.h"
29 #include "img-view.h"
30 #include "intl.h"
31 #include "layout.h"
32 #include "layout-image.h"
33 #include "main-defines.h"
34 #include "metadata.h"
35 #include "misc.h"
36 #include "utilops.h"
37 #include "ui-fileops.h"
38 #include "ui-misc.h"
39 #include "ui-tree-edit.h"
40 #include "uri-utils.h"
41 #include "view-file.h"
42
43 #include <vector>
44
45 /* Index to tree store */
46 enum {
47         FILE_COLUMN_POINTER = 0,
48         FILE_COLUMN_VERSION,
49         FILE_COLUMN_THUMB,
50         FILE_COLUMN_FORMATTED,
51         FILE_COLUMN_FORMATTED_WITH_STARS,
52         FILE_COLUMN_NAME,
53         FILE_COLUMN_SIDECARS,
54         FILE_COLUMN_STAR_RATING,
55         FILE_COLUMN_SIZE,
56         FILE_COLUMN_DATE,
57         FILE_COLUMN_EXPANDED,
58         FILE_COLUMN_COLOR,
59         FILE_COLUMN_MARKS,
60         FILE_COLUMN_MARKS_LAST = FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
61         FILE_COLUMN_COUNT
62 };
63
64
65 /* Index to tree view */
66 enum {
67         FILE_VIEW_COLUMN_MARKS = 0,
68         FILE_VIEW_COLUMN_MARKS_LAST = FILE_VIEW_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
69         FILE_VIEW_COLUMN_THUMB,
70         FILE_VIEW_COLUMN_FORMATTED,
71         FILE_VIEW_COLUMN_FORMATTED_WITH_STARS,
72         FILE_VIEW_COLUMN_STAR_RATING,
73         FILE_VIEW_COLUMN_SIZE,
74         FILE_VIEW_COLUMN_DATE,
75         FILE_VIEW_COLUMN_COUNT
76 };
77
78
79
80 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd);
81 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old_name, const gchar *new_name, gpointer data);
82 static void vflist_populate_view(ViewFile *vf, gboolean force);
83 static gboolean vflist_is_multiline(ViewFile *vf);
84 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded);
85
86
87 /*
88  *-----------------------------------------------------------------------------
89  * misc
90  *-----------------------------------------------------------------------------
91  */
92 struct ViewFileFindRowData {
93         const FileData *fd;
94         GtkTreeIter *iter;
95         gboolean found;
96         gint row;
97 };
98
99 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
100 {
101         auto find = static_cast<ViewFileFindRowData *>(data);
102         FileData *fd;
103         gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
104         if (fd == find->fd)
105                 {
106                 *find->iter = *iter;
107                 find->found = TRUE;
108                 return TRUE;
109                 }
110         find->row++;
111         return FALSE;
112 }
113
114 static gint vflist_find_row(const ViewFile *vf, const FileData *fd, GtkTreeIter *iter)
115 {
116         GtkTreeModel *store;
117         ViewFileFindRowData data = {fd, iter, FALSE, 0};
118
119         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
120         gtk_tree_model_foreach(store, vflist_find_row_cb, &data);
121
122         if (data.found)
123                 {
124                 return data.row;
125                 }
126
127         return -1;
128 }
129
130 static FileData *vflist_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *)
131 {
132         GtkTreePath *tpath;
133         GtkTreeViewColumn *column;
134
135         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
136                                           &tpath, &column, nullptr, nullptr))
137                 {
138                 GtkTreeModel *store;
139                 GtkTreeIter row;
140                 FileData *fd;
141
142                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
143                 gtk_tree_model_get_iter(store, &row, tpath);
144                 gtk_tree_path_free(tpath);
145                 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &fd, -1);
146
147                 return fd;
148                 }
149
150         return nullptr;
151 }
152
153 static gboolean vflist_store_clear_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
154 {
155         FileData *fd;
156         gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
157
158         /* it seems that gtk_tree_store_clear may call some callbacks
159            that use the column. Set the pointer to NULL to be safe. */
160         gtk_tree_store_set(GTK_TREE_STORE(model), iter, FILE_COLUMN_POINTER, NULL, -1);
161         file_data_unref(fd);
162         return FALSE;
163 }
164
165 static void vflist_store_clear(ViewFile *vf, gboolean unlock_files)
166 {
167         GtkTreeModel *store;
168         GList *files = nullptr;
169
170         if (unlock_files && vf->marks_enabled)
171                 {
172                 // unlock locked files in this directory
173                 filelist_read(vf->dir_fd, &files, nullptr);
174                 GList *work = files;
175                 while (work)
176                         {
177                         auto fd = static_cast<FileData *>(work->data);
178                         work = work->next;
179                         file_data_unlock(fd);
180                         file_data_unref(fd);  // undo the ref that got added in filelist_read
181                         }
182                 }
183
184         g_list_free(files);
185         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
186         gtk_tree_model_foreach(store, vflist_store_clear_cb, nullptr);
187         gtk_tree_store_clear(GTK_TREE_STORE(store));
188 }
189
190 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set)
191 {
192         GtkTreeModel *store;
193         GtkTreeIter iter;
194
195         if (vflist_find_row(vf, fd, &iter) < 0) return;
196         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
197         gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_COLOR, color_set, -1);
198 }
199
200 static void vflist_move_cursor(ViewFile *vf, GtkTreeIter *iter)
201 {
202         GtkTreeModel *store;
203         GtkTreePath *tpath;
204
205         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
206
207         tpath = gtk_tree_model_get_path(store, iter);
208         gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
209         gtk_tree_path_free(tpath);
210 }
211
212
213 /*
214  *-----------------------------------------------------------------------------
215  * dnd
216  *-----------------------------------------------------------------------------
217  */
218
219 static void vflist_dnd_get(GtkWidget *, GdkDragContext *,
220                            GtkSelectionData *selection_data, guint,
221                            guint, gpointer data)
222 {
223         auto vf = static_cast<ViewFile *>(data);
224         GList *list = nullptr;
225
226         if (!VFLIST(vf)->click_fd) return;
227
228         if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
229                 {
230                 list = vf_selection_get_list(vf);
231                 }
232         else
233                 {
234                 list = g_list_append(nullptr, file_data_ref(VFLIST(vf)->click_fd));
235                 }
236
237         if (!list) return;
238         uri_selection_data_set_uris_from_filelist(selection_data, list);
239         filelist_free(list);
240 }
241
242 static void vflist_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
243 {
244         auto vf = static_cast<ViewFile *>(data);
245
246         vflist_color_set(vf, VFLIST(vf)->click_fd, TRUE);
247
248         if (VFLIST(vf)->thumbs_enabled &&
249             VFLIST(vf)->click_fd && VFLIST(vf)->click_fd->thumb_pixbuf)
250                 {
251                 guint items;
252
253                 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
254                         items = vf_selection_count(vf, nullptr);
255                 else
256                         items = 1;
257
258                 dnd_set_drag_icon(widget, context, VFLIST(vf)->click_fd->thumb_pixbuf, items);
259                 }
260 }
261
262 static void vflist_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
263 {
264         auto vf = static_cast<ViewFile *>(data);
265
266         vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
267
268         if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
269                 {
270                 vf_refresh(vf);
271                 }
272 }
273
274 static void vflist_drag_data_received(GtkWidget *, GdkDragContext *,
275                                       int x, int y, GtkSelectionData *selection,
276                                       guint info, guint, gpointer data)
277 {
278         auto vf = static_cast<ViewFile *>(data);
279
280         if (info == TARGET_TEXT_PLAIN) {
281                 FileData *fd = vflist_find_data_by_coord(vf, x, y, nullptr);
282
283                 if (fd) {
284                         /* Add keywords to file */
285                         auto str = reinterpret_cast<gchar *>(gtk_selection_data_get_text(selection));
286                         GList *kw_list = string_to_keywords_list(str);
287
288                         metadata_append_list(fd, KEYWORD_KEY, kw_list);
289                         g_list_free_full(kw_list, g_free);
290                         g_free(str);
291                 }
292         }
293 }
294
295 void vflist_dnd_init(ViewFile *vf)
296 {
297         gtk_drag_source_set(vf->listview, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
298                             dnd_file_drag_types, dnd_file_drag_types_count,
299                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
300         gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
301                             dnd_file_drag_types, dnd_file_drag_types_count,
302                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
303
304         g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
305                          G_CALLBACK(vflist_dnd_get), vf);
306         g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
307                          G_CALLBACK(vflist_dnd_begin), vf);
308         g_signal_connect(G_OBJECT(vf->listview), "drag_end",
309                          G_CALLBACK(vflist_dnd_end), vf);
310         g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
311                          G_CALLBACK(vflist_drag_data_received), vf);
312 }
313
314 /*
315  *-----------------------------------------------------------------------------
316  * pop-up menu
317  *-----------------------------------------------------------------------------
318  */
319
320 GList *vflist_selection_get_one(ViewFile *vf, FileData *fd)
321 {
322         GList *list = nullptr;
323
324         if (fd->sidecar_files)
325                 {
326                 /* check if the row is expanded */
327                 GtkTreeModel *store;
328                 GtkTreeIter iter;
329
330                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
331                 if (vflist_find_row(vf, fd, &iter) >= 0)
332                         {
333                         GtkTreePath *tpath;
334
335                         tpath = gtk_tree_model_get_path(store, &iter);
336                         if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
337                                 {
338                                 /* unexpanded - add whole group */
339                                 list = filelist_copy(fd->sidecar_files);
340                                 }
341                         gtk_tree_path_free(tpath);
342                         }
343                 }
344
345         return g_list_prepend(list, file_data_ref(fd));
346 }
347
348 GList *vflist_pop_menu_file_list(ViewFile *vf)
349 {
350         if (!VFLIST(vf)->click_fd) return nullptr;
351
352         if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
353                 {
354                 return vf_selection_get_list(vf);
355                 }
356         return vflist_selection_get_one(vf, VFLIST(vf)->click_fd);
357 }
358
359
360 void vflist_pop_menu_view_cb(GtkWidget *, gpointer data)
361 {
362         auto vf = static_cast<ViewFile *>(data);
363
364         if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
365                 {
366                 GList *list;
367
368                 list = vf_selection_get_list(vf);
369                 view_window_new_from_list(list);
370                 filelist_free(list);
371                 }
372         else
373                 {
374                 view_window_new(VFLIST(vf)->click_fd);
375                 }
376 }
377
378 void vflist_pop_menu_rename_cb(GtkWidget *, gpointer data)
379 {
380         auto vf = static_cast<ViewFile *>(data);
381         GList *list;
382
383         list = vf_pop_menu_file_list(vf);
384         if (options->file_ops.enable_in_place_rename &&
385             list && !list->next && VFLIST(vf)->click_fd)
386                 {
387                 GtkTreeModel *store;
388                 GtkTreeIter iter;
389
390                 filelist_free(list);
391
392                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
393                 if (vflist_find_row(vf, VFLIST(vf)->click_fd, &iter) >= 0)
394                         {
395                         GtkTreePath *tpath;
396
397                         tpath = gtk_tree_model_get_path(store, &iter);
398                         tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
399                                           FILE_VIEW_COLUMN_FORMATTED, VFLIST(vf)->click_fd->name,
400                                           vflist_row_rename_cb, vf);
401                         gtk_tree_path_free(tpath);
402                         }
403                 return;
404                 }
405
406         file_util_rename(nullptr, list, vf->listview);
407 }
408
409 void vflist_pop_menu_thumbs_cb(GtkWidget *, gpointer data)
410 {
411         auto vf = static_cast<ViewFile *>(data);
412
413         vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
414         if (vf->layout)
415                 {
416                 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
417                 }
418         else
419                 {
420                 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
421                 }
422 }
423
424 void vflist_star_rating_set(ViewFile *vf, gboolean enable)
425 {
426         GList *columns;
427         GList *work;
428
429         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
430
431         work = columns;
432         while (work)
433                 {
434                 auto column = static_cast<GtkTreeViewColumn *>(work->data);
435                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
436                 work = work->next;
437
438                 if (vflist_is_multiline(vf))
439                         {
440                         if (col_idx == FILE_COLUMN_FORMATTED_WITH_STARS)
441                                 {
442                                 gtk_tree_view_column_set_visible(column, enable);
443                                 }
444                         if (col_idx == FILE_COLUMN_FORMATTED)
445                                 {
446                                 gtk_tree_view_column_set_visible(column, !enable);
447                                 }
448                         }
449                 else
450                         {
451                         if (col_idx == FILE_COLUMN_STAR_RATING)
452                                 {
453                                 gtk_tree_view_column_set_visible(column, enable);
454                                 }
455                         }
456                 }
457         g_list_free(columns);
458 }
459
460 void vflist_pop_menu_show_star_rating_cb(GtkWidget *, gpointer data)
461 {
462         auto vf = static_cast<ViewFile *>(data);
463
464         options->show_star_rating = !options->show_star_rating;
465
466         vflist_populate_view(vf, TRUE);
467
468         vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
469         vflist_star_rating_set(vf, options->show_star_rating);
470 }
471
472 void vflist_pop_menu_refresh_cb(GtkWidget *, gpointer data)
473 {
474         auto vf = static_cast<ViewFile *>(data);
475
476         vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
477         vf_refresh(vf);
478         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
479 }
480
481 void vflist_popup_destroy_cb(GtkWidget *, gpointer data)
482 {
483         auto vf = static_cast<ViewFile *>(data);
484         vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
485         VFLIST(vf)->click_fd = nullptr;
486         vf->popup = nullptr;
487 }
488
489
490 /*
491  *-----------------------------------------------------------------------------
492  * callbacks
493  *-----------------------------------------------------------------------------
494  */
495
496 static gboolean vflist_row_rename_cb(TreeEditData *, const gchar *old_name, const gchar *new_name, gpointer data)
497 {
498         auto vf = static_cast<ViewFile *>(data);
499         gchar *new_path;
500
501         if (!new_name || !new_name[0]) return FALSE;
502
503         new_path = g_build_filename(vf->dir_fd->path, new_name, NULL);
504
505         if (strchr(new_name, G_DIR_SEPARATOR) != nullptr)
506                 {
507                 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new_name);
508                 file_util_warning_dialog(_("Error renaming file"), text, GQ_ICON_DIALOG_ERROR, vf->listview);
509                 g_free(text);
510                 }
511         else
512                 {
513                 gchar *old_path = g_build_filename(vf->dir_fd->path, old_name, NULL);
514                 FileData *fd = file_data_new_group(old_path); /* get the fd from cache */
515                 file_util_rename_simple(fd, new_path, vf->listview);
516                 file_data_unref(fd);
517                 g_free(old_path);
518                 }
519
520         g_free(new_path);
521
522         return FALSE;
523 }
524
525 gboolean vflist_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
526 {
527         auto vf = static_cast<ViewFile *>(data);
528         GtkTreePath *tpath;
529
530         if (event->keyval != GDK_KEY_Menu) return FALSE;
531
532         gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, nullptr);
533         if (tpath)
534                 {
535                 GtkTreeModel *store;
536                 GtkTreeIter iter;
537
538                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
539                 gtk_tree_model_get_iter(store, &iter, tpath);
540                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->click_fd, -1);
541                 gtk_tree_path_free(tpath);
542                 }
543         else
544                 {
545                 VFLIST(vf)->click_fd = nullptr;
546                 }
547
548         vf->popup = vf_pop_menu(vf);
549         gtk_menu_popup_at_widget(GTK_MENU(vf->popup), widget, GDK_GRAVITY_EAST, GDK_GRAVITY_CENTER, nullptr);
550
551         return TRUE;
552 }
553
554 gboolean vflist_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
555 {
556         auto vf = static_cast<ViewFile *>(data);
557         GtkTreePath *tpath;
558         GtkTreeIter iter;
559         FileData *fd = nullptr;
560         GtkTreeViewColumn *column;
561
562         vf->clicked_mark = 0;
563
564         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
565                                           &tpath, &column, nullptr, nullptr))
566                 {
567                 GtkTreeModel *store;
568                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
569
570                 if (bevent->button == MOUSE_BUTTON_LEFT &&
571                     col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
572                         return FALSE;
573
574                 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
575                         vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
576
577                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
578
579                 gtk_tree_model_get_iter(store, &iter, tpath);
580                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
581                 gtk_tree_path_free(tpath);
582                 }
583
584         VFLIST(vf)->click_fd = fd;
585
586         if (bevent->button == MOUSE_BUTTON_RIGHT)
587                 {
588                 vf->popup = vf_pop_menu(vf);
589                 gtk_menu_popup_at_pointer(GTK_MENU(vf->popup), nullptr);
590                 return TRUE;
591                 }
592
593         if (!fd) return FALSE;
594
595         if (bevent->button == MOUSE_BUTTON_MIDDLE)
596                 {
597                 if (!vflist_row_is_selected(vf, fd))
598                         {
599                         vflist_color_set(vf, fd, TRUE);
600                         }
601                 return TRUE;
602                 }
603
604
605         if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
606             !(bevent->state & GDK_SHIFT_MASK ) &&
607             !(bevent->state & GDK_CONTROL_MASK ) &&
608             vflist_row_is_selected(vf, fd))
609                 {
610                 GtkTreeSelection *selection;
611
612                 gtk_widget_grab_focus(widget);
613
614
615                 /* returning FALSE and further processing of the event is needed for
616                    correct operation of the expander, to show the sidecar files.
617                    It however resets the selection of multiple files. With this condition
618                    it should work for both cases */
619                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
620                 return (gtk_tree_selection_count_selected_rows(selection) > 1);
621                 }
622
623         if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
624                 {
625                 if (VFLIST(vf)->click_fd->format_class == FORMAT_CLASS_COLLECTION)
626                         {
627                         collection_window_new(VFLIST(vf)->click_fd->path);
628                         }
629                 else
630                         {
631                         if (vf->layout) layout_image_full_screen_start(vf->layout);
632                         }
633                 }
634
635         return FALSE;
636 }
637
638 gboolean vflist_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
639 {
640         auto vf = static_cast<ViewFile *>(data);
641         GtkTreePath *tpath;
642         GtkTreeIter iter;
643         FileData *fd = nullptr;
644
645         if (defined_mouse_buttons(widget, bevent, vf->layout))
646                 {
647                 return TRUE;
648                 }
649
650         if (bevent->button == MOUSE_BUTTON_MIDDLE)
651                 {
652                 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
653                 }
654
655         if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
656                 {
657                 return TRUE;
658                 }
659
660         if ((bevent->x != 0 || bevent->y != 0) &&
661             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
662                                           &tpath, nullptr, nullptr, nullptr))
663                 {
664                 GtkTreeModel *store;
665
666                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
667                 gtk_tree_model_get_iter(store, &iter, tpath);
668                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
669                 gtk_tree_path_free(tpath);
670                 }
671
672         if (bevent->button == MOUSE_BUTTON_MIDDLE)
673                 {
674                 if (fd && VFLIST(vf)->click_fd == fd)
675                         {
676                         GtkTreeSelection *selection;
677
678                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
679                         if (vflist_row_is_selected(vf, fd))
680                                 {
681                                 gtk_tree_selection_unselect_iter(selection, &iter);
682                                 }
683                         else
684                                 {
685                                 gtk_tree_selection_select_iter(selection, &iter);
686                                 }
687                         }
688                 return TRUE;
689                 }
690
691         if (fd && VFLIST(vf)->click_fd == fd &&
692             !(bevent->state & GDK_SHIFT_MASK ) &&
693             !(bevent->state & GDK_CONTROL_MASK ) &&
694             vflist_row_is_selected(vf, fd))
695                 {
696                 GtkTreeSelection *selection;
697
698                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
699                 gtk_tree_selection_unselect_all(selection);
700                 gtk_tree_selection_select_iter(selection, &iter);
701                 vflist_move_cursor(vf, &iter);
702                 }
703
704         return FALSE;
705 }
706
707 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
708 {
709         FileData *read_ahead_fd = nullptr;
710         gint row;
711         FileData *cur_fd;
712
713         if (!sel_fd) return;
714
715         cur_fd = layout_image_get_fd(vf->layout);
716         if (sel_fd == cur_fd) return; /* no change */
717
718         row = g_list_index(vf->list, sel_fd);
719         /** @FIXME sidecar data */
720
721         if (sel_fd && options->image.enable_read_ahead && row >= 0)
722                 {
723                 if (row > g_list_index(vf->list, cur_fd) &&
724                     static_cast<guint>(row + 1) < vf_count(vf, nullptr))
725                         {
726                         read_ahead_fd = vf_index_get_data(vf, row + 1);
727                         }
728                 else if (row > 0)
729                         {
730                         read_ahead_fd = vf_index_get_data(vf, row - 1);
731                         }
732                 }
733
734         layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
735 }
736
737 static gboolean vflist_select_idle_cb(gpointer data)
738 {
739         auto vf = static_cast<ViewFile *>(data);
740
741         if (!vf->layout)
742                 {
743                 VFLIST(vf)->select_idle_id = 0;
744                 return G_SOURCE_REMOVE;
745                 }
746
747         vf_send_update(vf);
748
749         if (VFLIST(vf)->select_fd)
750                 {
751                 vflist_select_image(vf, VFLIST(vf)->select_fd);
752                 VFLIST(vf)->select_fd = nullptr;
753                 }
754
755         VFLIST(vf)->select_idle_id = 0;
756         return G_SOURCE_REMOVE;
757 }
758
759 static void vflist_select_idle_cancel(ViewFile *vf)
760 {
761         if (VFLIST(vf)->select_idle_id)
762                 {
763                 g_source_remove(VFLIST(vf)->select_idle_id);
764                 VFLIST(vf)->select_idle_id = 0;
765                 }
766 }
767
768 static gboolean vflist_select_cb(GtkTreeSelection *, GtkTreeModel *store, GtkTreePath *tpath, gboolean path_currently_selected, gpointer data)
769 {
770         auto vf = static_cast<ViewFile *>(data);
771         GtkTreeIter iter;
772         GtkTreePath *cursor_path;
773
774         VFLIST(vf)->select_fd = nullptr;
775
776         if (!path_currently_selected && gtk_tree_model_get_iter(store, &iter, tpath))
777                 {
778                 gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &cursor_path, nullptr);
779                 if (cursor_path)
780                         {
781                         gtk_tree_model_get_iter(store, &iter, cursor_path);
782                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
783                         gtk_tree_path_free(cursor_path);
784                         }
785                 }
786
787         if (vf->layout &&
788             !VFLIST(vf)->select_idle_id)
789                 {
790                 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
791                 }
792
793         return TRUE;
794 }
795
796 static void vflist_expand_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
797 {
798         auto vf = static_cast<ViewFile *>(data);
799         vflist_set_expanded(vf, iter, TRUE);
800 }
801
802 static void vflist_collapse_cb(GtkTreeView *, GtkTreeIter *iter, GtkTreePath *, gpointer data)
803 {
804         auto vf = static_cast<ViewFile *>(data);
805         vflist_set_expanded(vf, iter, FALSE);
806 }
807
808 /*
809  *-----------------------------------------------------------------------------
810  * misc
811  *-----------------------------------------------------------------------------
812  */
813
814
815 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)
816  {
817         gboolean multiline = vflist_is_multiline(vf);
818         gchar *text;
819
820         if (multiline)
821                 {
822                 if (with_stars)
823                         {
824                                         text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
825                         }
826                 else
827                         {
828                         text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
829                         }
830                 }
831         else
832                 {
833                 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
834                 }
835         return text;
836 }
837
838 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
839 {
840         GtkTreeStore *store;
841         gchar *name;
842         gchar *sidecars;
843         gchar *size;
844         gchar *time;
845         gchar *formatted;
846         gchar *formatted_with_stars;
847         gchar *star_rating;
848         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
849
850         gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
851                                         FILE_COLUMN_NAME, &name,
852                                         FILE_COLUMN_SIDECARS, &sidecars,
853                                         FILE_COLUMN_SIZE, &size,
854                                         FILE_COLUMN_DATE, &time,
855                                         FILE_COLUMN_STAR_RATING, &star_rating,
856                                         -1);
857
858         formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
859         formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
860
861         gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
862                                         FILE_COLUMN_EXPANDED, expanded,
863                                         -1);
864         gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
865                                         FILE_COLUMN_EXPANDED, expanded,
866                                         -1);
867         g_free(time);
868         g_free(size);
869         g_free(sidecars);
870         g_free(name);
871         g_free(formatted);
872         g_free(formatted_with_stars);
873 }
874
875 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
876 {
877         gchar *size;
878         gchar *sidecars = nullptr;
879         gchar *name;
880         const gchar *time = text_from_time(fd->date);
881         const gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
882         const gchar *disabled_grouping;
883         gchar *formatted;
884         gchar *formatted_with_stars;
885         gboolean expanded = FALSE;
886         gchar *star_rating;
887
888         if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
889                 {
890                 star_rating = convert_rating_to_stars(fd->rating);
891                 }
892         else
893                 {
894                 star_rating = nullptr;
895                 }
896
897         if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
898                 {
899                 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
900                 }
901
902         sidecars = file_data_sc_list_to_string(fd);
903
904         disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
905         name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
906         size = text_from_size(fd->size);
907
908         formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
909         formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
910
911         gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
912                                         FILE_COLUMN_VERSION, fd->version,
913                                         FILE_COLUMN_THUMB, fd->thumb_pixbuf,
914                                         FILE_COLUMN_FORMATTED, formatted,
915                                         FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
916                                         FILE_COLUMN_SIDECARS, sidecars,
917                                         FILE_COLUMN_NAME, name,
918                                         FILE_COLUMN_STAR_RATING, star_rating,
919                                         FILE_COLUMN_SIZE, size,
920                                         FILE_COLUMN_DATE, time,
921 #define STORE_SET_IS_SLOW 1
922 #if STORE_SET_IS_SLOW
923 /* this is 3x faster on a directory with 20000 files */
924                                         FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
925                                         FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
926                                         FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
927                                         FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
928                                         FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
929                                         FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
930                                         FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
931                                         FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
932                                         FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
933                                         FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
934 #if FILEDATA_MARKS_SIZE != 10
935 #error this needs to be updated
936 #endif
937 #endif
938                                         FILE_COLUMN_COLOR, FALSE, -1);
939
940 #if !STORE_SET_IS_SLOW
941         {
942         gint i;
943         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
944                 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
945         }
946 #endif
947         g_free(size);
948         g_free(sidecars);
949         g_free(name);
950         g_free(formatted);
951 }
952
953 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
954 {
955         GList *work;
956         GtkTreeIter iter;
957         gboolean valid;
958         gint num_ordered = 0;
959         gint num_prepended = 0;
960
961         valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
962
963         work = list;
964         while (work)
965                 {
966                 gint match;
967                 auto fd = static_cast<FileData *>(work->data);
968                 gboolean done = FALSE;
969
970                 while (!done)
971                         {
972                         FileData *old_fd = nullptr;
973                         gint old_version = 0;
974
975                         if (valid)
976                                 {
977                                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
978                                                    FILE_COLUMN_POINTER, &old_fd,
979                                                    FILE_COLUMN_VERSION, &old_version,
980                                                    -1);
981
982                                 if (fd == old_fd)
983                                         {
984                                         match = 0;
985                                         }
986                                 else
987                                         {
988                                         if (parent_iter)
989                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
990                                         else
991                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
992
993                                         if (match == 0) g_warning("multiple fd for the same path");
994                                         }
995
996                                 }
997                         else
998                                 {
999                                 match = -1;
1000                                 }
1001
1002                         if (match < 0)
1003                                 {
1004                                 GtkTreeIter new_iter;
1005
1006                                 if (valid)
1007                                         {
1008                                         num_ordered++;
1009                                         gtk_tree_store_insert_before(store, &new_iter, parent_iter, &iter);
1010                                         }
1011                                 else
1012                                         {
1013                                         /*
1014                                             here should be used gtk_tree_store_append, but this function seems to be O(n)
1015                                             and it seems to be much faster to add new entries to the beginning and reorder later
1016                                         */
1017                                         num_prepended++;
1018                                         gtk_tree_store_prepend(store, &new_iter, parent_iter);
1019                                         }
1020
1021                                 vflist_setup_iter(vf, store, &new_iter, file_data_ref(fd));
1022                                 vflist_setup_iter_recursive(vf, store, &new_iter, fd->sidecar_files, selected, force);
1023
1024                                 if (g_list_find(selected, fd))
1025                                         {
1026                                         /* renamed files - the same fd appears at different position - select it again*/
1027                                         GtkTreeSelection *selection;
1028                                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1029                                         gtk_tree_selection_select_iter(selection, &new_iter);
1030                                         }
1031
1032                                 done = TRUE;
1033                                 }
1034                         else if (match > 0)
1035                                 {
1036                                 file_data_unref(old_fd);
1037                                 valid = gtk_tree_store_remove(store, &iter);
1038                                 }
1039                         else
1040                                 {
1041                                 num_ordered++;
1042                                 if (fd->version != old_version || force)
1043                                         {
1044                                         vflist_setup_iter(vf, store, &iter, fd);
1045                                         vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
1046                                         }
1047
1048                                 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1049
1050                                 done = TRUE;
1051                                 }
1052                         }
1053                 work = work->next;
1054                 }
1055
1056         while (valid)
1057                 {
1058                 FileData *old_fd;
1059                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
1060                 file_data_unref(old_fd);
1061
1062                 valid = gtk_tree_store_remove(store, &iter);
1063                 }
1064
1065         /* move the prepended entries to the correct position */
1066         if (num_prepended)
1067                 {
1068                 gint num_total = num_prepended + num_ordered;
1069                 std::vector<gint> new_order;
1070                 new_order.reserve(num_total);
1071
1072                 for (gint i = 0; i < num_ordered; i++)
1073                         {
1074                         new_order.push_back(num_prepended + i);
1075                         }
1076                 for (gint i = num_ordered; i < num_total; i++)
1077                         {
1078                         new_order.push_back(num_total - 1 - i);
1079                         }
1080                 gtk_tree_store_reorder(store, parent_iter, new_order.data());
1081                 }
1082 }
1083
1084 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend, gboolean case_sensitive)
1085 {
1086         gint i;
1087         GHashTable *fd_idx_hash = g_hash_table_new(nullptr, nullptr);
1088         GtkTreeStore *store;
1089         GList *work;
1090
1091         if (vf->sort_method == type && vf->sort_ascend == ascend && vf->sort_case == case_sensitive) return;
1092         if (!vf->list) return;
1093
1094         work = vf->list;
1095         i = 0;
1096         while (work)
1097                 {
1098                 auto fd = static_cast<FileData *>(work->data);
1099                 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1100                 i++;
1101                 work = work->next;
1102                 }
1103
1104         vf->sort_method = type;
1105         vf->sort_ascend = ascend;
1106         vf->sort_case = case_sensitive;
1107
1108         vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1109
1110         std::vector<gint> new_order;
1111         new_order.reserve(i);
1112
1113         work = vf->list;
1114         while (work)
1115                 {
1116                 auto fd = static_cast<FileData *>(work->data);
1117                 new_order.push_back(GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd)));
1118                 work = work->next;
1119                 }
1120
1121         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1122         gtk_tree_store_reorder(store, nullptr, new_order.data());
1123
1124         g_hash_table_destroy(fd_idx_hash);
1125 }
1126
1127 /*
1128  *-----------------------------------------------------------------------------
1129  * thumb updates
1130  *-----------------------------------------------------------------------------
1131  */
1132
1133
1134 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1135 {
1136         GList *work = list;
1137         while (work)
1138                 {
1139                 auto fd = static_cast<FileData *>(work->data);
1140                 work = work->next;
1141
1142                 if (fd->thumb_pixbuf) (*done)++;
1143
1144                 if (fd->sidecar_files)
1145                         {
1146                         vflist_thumb_progress_count(fd->sidecar_files, count, done);
1147                         }
1148                 (*count)++;
1149                 }
1150 }
1151
1152 void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
1153 {
1154         GList *work = list;
1155         while (work)
1156                 {
1157                 auto fd = static_cast<FileData *>(work->data);
1158                 work = work->next;
1159
1160                 if (fd->metadata_in_idle_loaded) (*done)++;
1161
1162                 if (fd->sidecar_files)
1163                         {
1164                         vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1165                         }
1166                 (*count)++;
1167                 }
1168 }
1169
1170 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1171 {
1172         GtkTreeStore *store;
1173         GtkTreeIter iter;
1174
1175         if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1176
1177         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1178         gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1179 }
1180
1181 FileData *vflist_thumb_next_fd(ViewFile *vf)
1182 {
1183         GtkTreePath *tpath;
1184         FileData *fd = nullptr;
1185
1186         /* first check the visible files */
1187
1188         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1189                 {
1190                 GtkTreeModel *store;
1191                 GtkTreeIter iter;
1192                 gboolean valid = TRUE;
1193
1194                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1195                 gtk_tree_model_get_iter(store, &iter, tpath);
1196                 gtk_tree_path_free(tpath);
1197                 tpath = nullptr;
1198
1199                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1200                         {
1201                         FileData *nfd;
1202
1203                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1204
1205                         if (!nfd->thumb_pixbuf) fd = nfd;
1206
1207                         valid = gtk_tree_model_iter_next(store, &iter);
1208                         }
1209                 }
1210
1211         /* then find first undone */
1212
1213         if (!fd)
1214                 {
1215                 GList *work = vf->list;
1216                 while (work && !fd)
1217                         {
1218                         auto fd_p = static_cast<FileData *>(work->data);
1219                         if (!fd_p->thumb_pixbuf)
1220                                 fd = fd_p;
1221                         else
1222                                 {
1223                                 GList *work2 = fd_p->sidecar_files;
1224
1225                                 while (work2 && !fd)
1226                                         {
1227                                         fd_p = static_cast<FileData *>(work2->data);
1228                                         if (!fd_p->thumb_pixbuf) fd = fd_p;
1229                                         work2 = work2->next;
1230                                         }
1231                                 }
1232                         work = work->next;
1233                         }
1234                 }
1235
1236         return fd;
1237 }
1238
1239 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1240 {
1241         GtkTreeStore *store;
1242         GtkTreeIter iter;
1243         gchar *name;
1244         gchar *sidecars;
1245         gchar *size;
1246         gchar *time;
1247         gchar *star_rating;
1248         gchar *formatted_with_stars;
1249         gboolean expanded;
1250
1251         if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1252
1253         star_rating = metadata_read_rating_stars(fd);
1254
1255         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1256         gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1257
1258         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1259                                         FILE_COLUMN_NAME, &name,
1260                                         FILE_COLUMN_SIDECARS, &sidecars,
1261                                         FILE_COLUMN_SIZE, &size,
1262                                         FILE_COLUMN_DATE, &time,
1263                                         FILE_COLUMN_EXPANDED, &expanded,
1264                                         -1);
1265
1266         formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1267
1268         gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1269                                         FILE_COLUMN_EXPANDED, expanded,
1270                                         -1);
1271
1272         g_free(star_rating);
1273         g_free(formatted_with_stars);
1274 }
1275
1276 FileData *vflist_star_next_fd(ViewFile *vf)
1277 {
1278         GtkTreePath *tpath;
1279         FileData *fd = nullptr;
1280         FileData *nfd = nullptr;
1281
1282         /* first check the visible files */
1283
1284         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1285                 {
1286                 GtkTreeModel *store;
1287                 GtkTreeIter iter;
1288                 gboolean valid = TRUE;
1289
1290                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1291                 gtk_tree_model_get_iter(store, &iter, tpath);
1292                 gtk_tree_path_free(tpath);
1293                 tpath = nullptr;
1294
1295                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1296                         {
1297                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1298
1299                         if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1300                                 {
1301                                 fd = nfd;
1302                                 }
1303
1304                         valid = gtk_tree_model_iter_next(store, &iter);
1305                         }
1306
1307                 if (fd)
1308                         {
1309                         vf->stars_filedata = fd;
1310
1311                         if (vf->stars_id == 0)
1312                                 {
1313                                 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1314                                 }
1315                         }
1316                 }
1317
1318         /* then find first undone */
1319
1320         if (!fd)
1321                 {
1322                 GList *work = vf->list;
1323
1324                 while (work && !fd)
1325                         {
1326                         auto fd_p = static_cast<FileData *>(work->data);
1327
1328                         if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1329                                 {
1330                                 fd = fd_p;
1331                                 }
1332                         else
1333                                 {
1334                                 fd = nullptr;
1335                                 }
1336
1337                         work = work->next;
1338                         }
1339
1340                 if (fd)
1341                         {
1342                         vf->stars_filedata = fd;
1343
1344                         if (vf->stars_id == 0)
1345                                 {
1346                                 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1347                                 }
1348                         }
1349                 }
1350
1351         return fd;
1352 }
1353
1354 /*
1355  *-----------------------------------------------------------------------------
1356  * row stuff
1357  *-----------------------------------------------------------------------------
1358  */
1359
1360 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1361 {
1362         gint p = 0;
1363         GList *work;
1364         GList *work2;
1365
1366         work = vf->list;
1367         while (work)
1368                 {
1369                 auto list_fd = static_cast<FileData *>(work->data);
1370                 if (list_fd == fd) return p;
1371
1372                 work2 = list_fd->sidecar_files;
1373                 while (work2)
1374                         {
1375                         /** @FIXME return the same index also for sidecars
1376                            it is sufficient for next/prev navigation but it should be rewritten
1377                            without using indexes at all
1378                         */
1379                         auto sidecar_fd = static_cast<FileData *>(work2->data);
1380                         if (sidecar_fd == fd) return p;
1381                         work2 = work2->next;
1382                         }
1383
1384                 work = work->next;
1385                 p++;
1386                 }
1387
1388         return -1;
1389 }
1390
1391 /*
1392  *-----------------------------------------------------------------------------
1393  * selections
1394  *-----------------------------------------------------------------------------
1395  */
1396
1397 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1398 {
1399         GtkTreeModel *store;
1400         GtkTreeSelection *selection;
1401         GList *slist;
1402         GList *work;
1403         gboolean found = FALSE;
1404
1405         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1406         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1407         work = slist;
1408         while (!found && work)
1409                 {
1410                 auto tpath = static_cast<GtkTreePath *>(work->data);
1411                 FileData *fd_n;
1412                 GtkTreeIter iter;
1413
1414                 gtk_tree_model_get_iter(store, &iter, tpath);
1415                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1416                 if (fd_n == fd) found = TRUE;
1417                 work = work->next;
1418                 }
1419         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1420
1421         return found;
1422 }
1423
1424 #pragma GCC diagnostic push
1425 #pragma GCC diagnostic ignored "-Wunused-function"
1426 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1427 {
1428         FileData *fd;
1429
1430         fd = vf_index_get_data(vf, row);
1431         return vflist_row_is_selected(vf, fd);
1432 }
1433 #pragma GCC diagnostic pop
1434
1435 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1436 {
1437         GtkTreeModel *store;
1438         GtkTreeSelection *selection;
1439         GList *slist;
1440         guint count;
1441
1442         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1443         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1444
1445         if (bytes)
1446                 {
1447                 gint64 b = 0;
1448                 GList *work;
1449
1450                 work = slist;
1451                 while (work)
1452                         {
1453                         auto tpath = static_cast<GtkTreePath *>(work->data);
1454                         GtkTreeIter iter;
1455                         FileData *fd;
1456
1457                         gtk_tree_model_get_iter(store, &iter, tpath);
1458                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1459                         b += fd->size;
1460
1461                         work = work->next;
1462                         }
1463
1464                 *bytes = b;
1465                 }
1466
1467         count = g_list_length(slist);
1468         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1469
1470         return count;
1471 }
1472
1473 GList *vflist_selection_get_list(ViewFile *vf)
1474 {
1475         GtkTreeModel *store;
1476         GtkTreeSelection *selection;
1477         GList *slist;
1478         GList *list = nullptr;
1479
1480         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1481         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1482         for (GList *work = g_list_last(slist); work; work = work->prev)
1483                 {
1484                 auto tpath = static_cast<GtkTreePath *>(work->data);
1485                 FileData *fd;
1486                 GtkTreeIter iter;
1487
1488                 gtk_tree_model_get_iter(store, &iter, tpath);
1489                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1490
1491                 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1492                         {
1493                         /* unexpanded - add whole group */
1494                         list = g_list_concat(filelist_copy(fd->sidecar_files), list);
1495                         }
1496
1497                 list = g_list_prepend(list, file_data_ref(fd));
1498                 }
1499         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1500
1501         return list;
1502 }
1503
1504 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1505 {
1506         GtkTreeModel *store;
1507         GtkTreeSelection *selection;
1508         GList *slist;
1509         GList *list = nullptr;
1510         GList *work;
1511
1512         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1513         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1514         work = slist;
1515         while (work)
1516                 {
1517                 auto tpath = static_cast<GtkTreePath *>(work->data);
1518                 FileData *fd;
1519                 GtkTreeIter iter;
1520
1521                 gtk_tree_model_get_iter(store, &iter, tpath);
1522                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1523
1524                 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1525
1526                 work = work->next;
1527                 }
1528         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1529
1530         return g_list_reverse(list);
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
1944 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1945
1946 enum {
1947         CELL_HEIGHT_OVERRIDE = 512
1948 };
1949
1950 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1951 {
1952         GParamSpec *spec;
1953
1954         spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1955         if (spec && G_IS_PARAM_SPEC_INT(spec))
1956                 {
1957                 GParamSpecInt *spec_int;
1958
1959                 spec_int = G_PARAM_SPEC_INT(spec);
1960                 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1961                 }
1962 }
1963
1964 static GdkRGBA *vflist_listview_color_shifted(GtkWidget *widget)
1965 {
1966         static GdkRGBA color;
1967         static GdkRGBA color_style;
1968         static GtkWidget *done = nullptr;
1969
1970         if (done != widget)
1971                 {
1972                 GtkStyle *style;
1973
1974                 style = gtk_widget_get_style(widget);
1975                 convert_gdkcolor_to_gdkrgba(&style->base[GTK_STATE_NORMAL], &color_style);
1976
1977                 memcpy(&color, &color_style, sizeof(color));
1978                 shift_color(&color, -1, 0);
1979                 done = widget;
1980                 }
1981
1982         return &color;
1983 }
1984
1985 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1986                                      GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1987 {
1988         auto vf = static_cast<ViewFile *>(data);
1989         gboolean set;
1990
1991         gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1992         g_object_set(G_OBJECT(cell),
1993                      "cell-background-rgba", vflist_listview_color_shifted(vf->listview),
1994                      "cell-background-set", set, NULL);
1995 }
1996
1997 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1998 {
1999         GtkTreeViewColumn *column;
2000         GtkCellRenderer *renderer;
2001
2002         column = gtk_tree_view_column_new();
2003         gtk_tree_view_column_set_title(column, title);
2004         gtk_tree_view_column_set_min_width(column, 4);
2005
2006         if (!image)
2007                 {
2008                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2009                 renderer = gtk_cell_renderer_text_new();
2010                 if (right_justify)
2011                         {
2012                         g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2013                         }
2014                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2015                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2016                 if (expand)
2017                         gtk_tree_view_column_set_expand(column, TRUE);
2018                 }
2019         else
2020                 {
2021                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2022                 renderer = gtk_cell_renderer_pixbuf_new();
2023                 cell_renderer_height_override(renderer);
2024                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2025                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2026                 }
2027
2028         gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
2029         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2030         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2031
2032         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2033 }
2034
2035 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2036 {
2037         auto vf = static_cast<ViewFile *>(data);
2038         GtkTreeStore *store;
2039         GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2040         GtkTreeIter iter;
2041         FileData *fd;
2042         gboolean marked;
2043         guint col_idx;
2044
2045         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2046         if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2047                 return;
2048
2049         col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2050
2051         g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2052
2053         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2054         marked = !marked;
2055
2056         /* the change has a very limited range and the standard notification would trigger
2057            complete re-read of the directory - try to do only minimal update instead */
2058         file_data_unregister_notify_func(vf_notify_cb, vf);
2059         file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2060         if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2061                 {
2062                 vf_refresh_idle(vf);
2063                 }
2064         else
2065                 {
2066                 /* mark functions can have various side effects - update all columns to be sure */
2067                 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2068                 /* mark functions can change sidecars too */
2069                 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
2070                 }
2071         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2072
2073         gtk_tree_path_free(path);
2074 }
2075
2076 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2077 {
2078         GtkTreeViewColumn *column;
2079         GtkCellRenderer *renderer;
2080
2081         renderer = gtk_cell_renderer_toggle_new();
2082         column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2083
2084         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2085         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2086         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2087
2088         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2089         gtk_tree_view_column_set_fixed_width(column, 22);
2090         gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2091
2092
2093         g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2094 }
2095
2096 /*
2097  *-----------------------------------------------------------------------------
2098  * base
2099  *-----------------------------------------------------------------------------
2100  */
2101
2102 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2103 {
2104         gboolean ret;
2105         if (!dir_fd) return FALSE;
2106         if (vf->dir_fd == dir_fd) return TRUE;
2107
2108         file_data_unref(vf->dir_fd);
2109         vf->dir_fd = file_data_ref(dir_fd);
2110
2111         /* force complete reload */
2112         vflist_store_clear(vf, TRUE);
2113
2114         filelist_free(vf->list);
2115         vf->list = nullptr;
2116
2117         ret = vf_refresh(vf);
2118         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2119         return ret;
2120 }
2121
2122 void vflist_destroy_cb(GtkWidget *, gpointer data)
2123 {
2124         auto vf = static_cast<ViewFile *>(data);
2125
2126         file_data_unregister_notify_func(vf_notify_cb, vf);
2127
2128         vflist_select_idle_cancel(vf);
2129         vf_refresh_idle_cancel(vf);
2130         vf_thumb_stop(vf);
2131         vf_star_stop(vf);
2132
2133         filelist_free(vf->list);
2134 }
2135
2136 ViewFile *vflist_new(ViewFile *vf, FileData *)
2137 {
2138         GtkTreeStore *store;
2139         GtkTreeSelection *selection;
2140         GType flist_types[FILE_COLUMN_COUNT];
2141         gint i;
2142         gint column;
2143
2144         vf->info = g_new0(ViewFileInfoList, 1);
2145
2146         flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2147         flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2148         flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2149         flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2150         flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2151         flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2152         flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2153         flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2154         flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2155         flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2156         flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2157         flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2158         for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2159                 flist_types[i] = G_TYPE_BOOLEAN;
2160
2161         store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2162
2163         vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2164         g_object_unref(store);
2165
2166         g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2167                          G_CALLBACK(vflist_expand_cb), vf);
2168
2169         g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2170                          G_CALLBACK(vflist_collapse_cb), vf);
2171
2172         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2173         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2174         gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2175
2176         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2177         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2178
2179         gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2180
2181         column = 0;
2182
2183         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2184                 {
2185                 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2186                 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2187                 column++;
2188                 }
2189
2190         vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2191         g_assert(column == FILE_VIEW_COLUMN_THUMB);
2192         column++;
2193
2194         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2195         g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2196         column++;
2197
2198         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2199         g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2200         column++;
2201
2202         vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2203         g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2204         column++;
2205
2206         vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2207         g_assert(column == FILE_VIEW_COLUMN_SIZE);
2208         column++;
2209
2210         vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2211         g_assert(column == FILE_VIEW_COLUMN_DATE);
2212         column++;
2213
2214         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2215         return vf;
2216 }
2217
2218 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2219 {
2220         if (VFLIST(vf)->thumbs_enabled == enable) return;
2221
2222         VFLIST(vf)->thumbs_enabled = enable;
2223
2224         /* vflist_populate_view is better than vf_refresh:
2225            - no need to re-read the directory
2226            - force update because the formatted string has changed
2227         */
2228         if (vf->layout)
2229                 {
2230                 vflist_populate_view(vf, TRUE);
2231                 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2232                 }
2233 }
2234
2235 void vflist_marks_set(ViewFile *vf, gboolean enable)
2236 {
2237         GList *columns;
2238         GList *work;
2239
2240         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2241
2242         work = columns;
2243         while (work)
2244                 {
2245                 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2246                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2247                 work = work->next;
2248
2249                 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2250                         gtk_tree_view_column_set_visible(column, enable);
2251                 }
2252
2253         if (enable)
2254                 {
2255                 // Previously disabled, which means that vf->list is complete
2256                 file_data_lock_list(vf->list);
2257                 }
2258         else
2259                 {
2260                 // Previously enabled, which means that vf->list is incomplete
2261                 }
2262
2263         g_list_free(columns);
2264 }
2265
2266 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */