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