Prefer internal ViewFile submodule functions
[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
714 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)
715  {
716         gboolean multiline = vflist_is_multiline(vf);
717         gchar *text;
718
719         if (multiline)
720                 {
721                 if (with_stars)
722                         {
723                                         text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
724                         }
725                 else
726                         {
727                         text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
728                         }
729                 }
730         else
731                 {
732                 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
733                 }
734         return text;
735 }
736
737 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
738 {
739         GtkTreeStore *store;
740         gchar *name;
741         gchar *sidecars;
742         gchar *size;
743         gchar *time;
744         gchar *formatted;
745         gchar *formatted_with_stars;
746         gchar *star_rating;
747         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
748
749         gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
750                                         FILE_COLUMN_NAME, &name,
751                                         FILE_COLUMN_SIDECARS, &sidecars,
752                                         FILE_COLUMN_SIZE, &size,
753                                         FILE_COLUMN_DATE, &time,
754                                         FILE_COLUMN_STAR_RATING, &star_rating,
755                                         -1);
756
757         formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
758         formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
759
760         gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
761                                         FILE_COLUMN_EXPANDED, expanded,
762                                         -1);
763         gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
764                                         FILE_COLUMN_EXPANDED, expanded,
765                                         -1);
766         g_free(time);
767         g_free(size);
768         g_free(sidecars);
769         g_free(name);
770         g_free(formatted);
771         g_free(formatted_with_stars);
772 }
773
774 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
775 {
776         gchar *size;
777         gchar *sidecars = nullptr;
778         gchar *name;
779         const gchar *time = text_from_time(fd->date);
780         const gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
781         const gchar *disabled_grouping;
782         gchar *formatted;
783         gchar *formatted_with_stars;
784         gboolean expanded = FALSE;
785         gchar *star_rating;
786
787         if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
788                 {
789                 star_rating = convert_rating_to_stars(fd->rating);
790                 }
791         else
792                 {
793                 star_rating = nullptr;
794                 }
795
796         if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
797                 {
798                 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
799                 }
800
801         sidecars = file_data_sc_list_to_string(fd);
802
803         disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
804         name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
805         size = text_from_size(fd->size);
806
807         formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, nullptr);
808         formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
809
810         gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
811                                         FILE_COLUMN_VERSION, fd->version,
812                                         FILE_COLUMN_THUMB, fd->thumb_pixbuf,
813                                         FILE_COLUMN_FORMATTED, formatted,
814                                         FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
815                                         FILE_COLUMN_SIDECARS, sidecars,
816                                         FILE_COLUMN_NAME, name,
817                                         FILE_COLUMN_STAR_RATING, star_rating,
818                                         FILE_COLUMN_SIZE, size,
819                                         FILE_COLUMN_DATE, time,
820 #define STORE_SET_IS_SLOW 1
821 #if STORE_SET_IS_SLOW
822 /* this is 3x faster on a directory with 20000 files */
823                                         FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
824                                         FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
825                                         FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
826                                         FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
827                                         FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
828                                         FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
829                                         FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
830                                         FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
831                                         FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
832                                         FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
833 #if FILEDATA_MARKS_SIZE != 10
834 #error this needs to be updated
835 #endif
836 #endif
837                                         FILE_COLUMN_COLOR, FALSE, -1);
838
839 #if !STORE_SET_IS_SLOW
840         {
841         gint i;
842         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
843                 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
844         }
845 #endif
846         g_free(size);
847         g_free(sidecars);
848         g_free(name);
849         g_free(formatted);
850 }
851
852 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
853 {
854         GList *work;
855         GtkTreeIter iter;
856         gboolean valid;
857         gint num_ordered = 0;
858         gint num_prepended = 0;
859
860         valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
861
862         work = list;
863         while (work)
864                 {
865                 gint match;
866                 auto fd = static_cast<FileData *>(work->data);
867                 gboolean done = FALSE;
868
869                 while (!done)
870                         {
871                         FileData *old_fd = nullptr;
872                         gint old_version = 0;
873
874                         if (valid)
875                                 {
876                                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
877                                                    FILE_COLUMN_POINTER, &old_fd,
878                                                    FILE_COLUMN_VERSION, &old_version,
879                                                    -1);
880
881                                 if (fd == old_fd)
882                                         {
883                                         match = 0;
884                                         }
885                                 else
886                                         {
887                                         if (parent_iter)
888                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
889                                         else
890                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
891
892                                         if (match == 0) g_warning("multiple fd for the same path");
893                                         }
894
895                                 }
896                         else
897                                 {
898                                 match = -1;
899                                 }
900
901                         if (match < 0)
902                                 {
903                                 GtkTreeIter new_iter;
904
905                                 if (valid)
906                                         {
907                                         num_ordered++;
908                                         gtk_tree_store_insert_before(store, &new_iter, parent_iter, &iter);
909                                         }
910                                 else
911                                         {
912                                         /*
913                                             here should be used gtk_tree_store_append, but this function seems to be O(n)
914                                             and it seems to be much faster to add new entries to the beginning and reorder later
915                                         */
916                                         num_prepended++;
917                                         gtk_tree_store_prepend(store, &new_iter, parent_iter);
918                                         }
919
920                                 vflist_setup_iter(vf, store, &new_iter, file_data_ref(fd));
921                                 vflist_setup_iter_recursive(vf, store, &new_iter, fd->sidecar_files, selected, force);
922
923                                 if (g_list_find(selected, fd))
924                                         {
925                                         /* renamed files - the same fd appears at different position - select it again*/
926                                         GtkTreeSelection *selection;
927                                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
928                                         gtk_tree_selection_select_iter(selection, &new_iter);
929                                         }
930
931                                 done = TRUE;
932                                 }
933                         else if (match > 0)
934                                 {
935                                 file_data_unref(old_fd);
936                                 valid = gtk_tree_store_remove(store, &iter);
937                                 }
938                         else
939                                 {
940                                 num_ordered++;
941                                 if (fd->version != old_version || force)
942                                         {
943                                         vflist_setup_iter(vf, store, &iter, fd);
944                                         vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
945                                         }
946
947                                 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
948
949                                 done = TRUE;
950                                 }
951                         }
952                 work = work->next;
953                 }
954
955         while (valid)
956                 {
957                 FileData *old_fd;
958                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
959                 file_data_unref(old_fd);
960
961                 valid = gtk_tree_store_remove(store, &iter);
962                 }
963
964         /* move the prepended entries to the correct position */
965         if (num_prepended)
966                 {
967                 gint num_total = num_prepended + num_ordered;
968                 std::vector<gint> new_order;
969                 new_order.reserve(num_total);
970
971                 for (gint i = 0; i < num_ordered; i++)
972                         {
973                         new_order.push_back(num_prepended + i);
974                         }
975                 for (gint i = num_ordered; i < num_total; i++)
976                         {
977                         new_order.push_back(num_total - 1 - i);
978                         }
979                 gtk_tree_store_reorder(store, parent_iter, new_order.data());
980                 }
981 }
982
983 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend, gboolean case_sensitive)
984 {
985         gint i;
986         GHashTable *fd_idx_hash = g_hash_table_new(nullptr, nullptr);
987         GtkTreeStore *store;
988         GList *work;
989
990         if (vf->sort_method == type && vf->sort_ascend == ascend && vf->sort_case == case_sensitive) return;
991         if (!vf->list) return;
992
993         work = vf->list;
994         i = 0;
995         while (work)
996                 {
997                 auto fd = static_cast<FileData *>(work->data);
998                 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
999                 i++;
1000                 work = work->next;
1001                 }
1002
1003         vf->sort_method = type;
1004         vf->sort_ascend = ascend;
1005         vf->sort_case = case_sensitive;
1006
1007         vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1008
1009         std::vector<gint> new_order;
1010         new_order.reserve(i);
1011
1012         work = vf->list;
1013         while (work)
1014                 {
1015                 auto fd = static_cast<FileData *>(work->data);
1016                 new_order.push_back(GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd)));
1017                 work = work->next;
1018                 }
1019
1020         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1021         gtk_tree_store_reorder(store, nullptr, new_order.data());
1022
1023         g_hash_table_destroy(fd_idx_hash);
1024 }
1025
1026 /*
1027  *-----------------------------------------------------------------------------
1028  * thumb updates
1029  *-----------------------------------------------------------------------------
1030  */
1031
1032
1033 void vflist_thumb_progress_count(const GList *list, gint &count, gint &done)
1034 {
1035         for (const GList *work = list; work; work = work->next)
1036                 {
1037                 auto fd = static_cast<FileData *>(work->data);
1038
1039                 if (fd->thumb_pixbuf) done++;
1040
1041                 if (fd->sidecar_files)
1042                         {
1043                         vflist_thumb_progress_count(fd->sidecar_files, count, done);
1044                         }
1045                 count++;
1046                 }
1047 }
1048
1049 void vflist_read_metadata_progress_count(const GList *list, gint &count, gint &done)
1050 {
1051         for (const GList *work = list; work; work = work->next)
1052                 {
1053                 auto fd = static_cast<FileData *>(work->data);
1054
1055                 if (fd->metadata_in_idle_loaded) done++;
1056
1057                 if (fd->sidecar_files)
1058                         {
1059                         vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1060                         }
1061                 count++;
1062                 }
1063 }
1064
1065 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1066 {
1067         GtkTreeStore *store;
1068         GtkTreeIter iter;
1069
1070         if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1071
1072         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1073         gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1074 }
1075
1076 FileData *vflist_thumb_next_fd(ViewFile *vf)
1077 {
1078         GtkTreePath *tpath;
1079         FileData *fd = nullptr;
1080
1081         /* first check the visible files */
1082
1083         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1084                 {
1085                 GtkTreeModel *store;
1086                 GtkTreeIter iter;
1087                 gboolean valid = TRUE;
1088
1089                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1090                 gtk_tree_model_get_iter(store, &iter, tpath);
1091                 gtk_tree_path_free(tpath);
1092                 tpath = nullptr;
1093
1094                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1095                         {
1096                         FileData *nfd;
1097
1098                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1099
1100                         if (!nfd->thumb_pixbuf) fd = nfd;
1101
1102                         valid = gtk_tree_model_iter_next(store, &iter);
1103                         }
1104                 }
1105
1106         /* then find first undone */
1107
1108         if (!fd)
1109                 {
1110                 GList *work = vf->list;
1111                 while (work && !fd)
1112                         {
1113                         auto fd_p = static_cast<FileData *>(work->data);
1114                         if (!fd_p->thumb_pixbuf)
1115                                 fd = fd_p;
1116                         else
1117                                 {
1118                                 GList *work2 = fd_p->sidecar_files;
1119
1120                                 while (work2 && !fd)
1121                                         {
1122                                         fd_p = static_cast<FileData *>(work2->data);
1123                                         if (!fd_p->thumb_pixbuf) fd = fd_p;
1124                                         work2 = work2->next;
1125                                         }
1126                                 }
1127                         work = work->next;
1128                         }
1129                 }
1130
1131         return fd;
1132 }
1133
1134 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1135 {
1136         GtkTreeStore *store;
1137         GtkTreeIter iter;
1138         gchar *name;
1139         gchar *sidecars;
1140         gchar *size;
1141         gchar *time;
1142         gchar *star_rating;
1143         gchar *formatted_with_stars;
1144         gboolean expanded;
1145
1146         if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1147
1148         star_rating = metadata_read_rating_stars(fd);
1149
1150         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1151         gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1152
1153         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1154                                         FILE_COLUMN_NAME, &name,
1155                                         FILE_COLUMN_SIDECARS, &sidecars,
1156                                         FILE_COLUMN_SIZE, &size,
1157                                         FILE_COLUMN_DATE, &time,
1158                                         FILE_COLUMN_EXPANDED, &expanded,
1159                                         -1);
1160
1161         formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1162
1163         gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1164                                         FILE_COLUMN_EXPANDED, expanded,
1165                                         -1);
1166
1167         g_free(star_rating);
1168         g_free(formatted_with_stars);
1169 }
1170
1171 FileData *vflist_star_next_fd(ViewFile *vf)
1172 {
1173         GtkTreePath *tpath;
1174         FileData *fd = nullptr;
1175         FileData *nfd = nullptr;
1176
1177         /* first check the visible files */
1178
1179         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1180                 {
1181                 GtkTreeModel *store;
1182                 GtkTreeIter iter;
1183                 gboolean valid = TRUE;
1184
1185                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1186                 gtk_tree_model_get_iter(store, &iter, tpath);
1187                 gtk_tree_path_free(tpath);
1188                 tpath = nullptr;
1189
1190                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1191                         {
1192                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1193
1194                         if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1195                                 {
1196                                 fd = nfd;
1197                                 }
1198
1199                         valid = gtk_tree_model_iter_next(store, &iter);
1200                         }
1201
1202                 if (fd)
1203                         {
1204                         vf->stars_filedata = fd;
1205
1206                         if (vf->stars_id == 0)
1207                                 {
1208                                 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1209                                 }
1210                         }
1211                 }
1212
1213         /* then find first undone */
1214
1215         if (!fd)
1216                 {
1217                 GList *work = vf->list;
1218
1219                 while (work && !fd)
1220                         {
1221                         auto fd_p = static_cast<FileData *>(work->data);
1222
1223                         if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1224                                 {
1225                                 fd = fd_p;
1226                                 }
1227                         else
1228                                 {
1229                                 fd = nullptr;
1230                                 }
1231
1232                         work = work->next;
1233                         }
1234
1235                 if (fd)
1236                         {
1237                         vf->stars_filedata = fd;
1238
1239                         if (vf->stars_id == 0)
1240                                 {
1241                                 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1242                                 }
1243                         }
1244                 }
1245
1246         return fd;
1247 }
1248
1249 /*
1250  *-----------------------------------------------------------------------------
1251  * row stuff
1252  *-----------------------------------------------------------------------------
1253  */
1254
1255 gint vflist_index_by_fd(const ViewFile *vf, const FileData *fd)
1256 {
1257         gint p = 0;
1258
1259         for (const GList *work = vf->list; work; work = work->next)
1260                 {
1261                 auto list_fd = static_cast<FileData *>(work->data);
1262                 if (list_fd == fd) return p;
1263
1264                 /** @FIXME return the same index also for sidecars
1265                    it is sufficient for next/prev navigation but it should be rewritten
1266                    without using indexes at all
1267                 */
1268                 if (g_list_find(list_fd->sidecar_files, fd)) return p;
1269
1270                 p++;
1271                 }
1272
1273         return -1;
1274 }
1275
1276 /*
1277  *-----------------------------------------------------------------------------
1278  * selections
1279  *-----------------------------------------------------------------------------
1280  */
1281
1282 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1283 {
1284         GtkTreeModel *store;
1285         GtkTreeSelection *selection;
1286         GList *slist;
1287         GList *work;
1288         gboolean found = FALSE;
1289
1290         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1291         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1292         work = slist;
1293         while (!found && work)
1294                 {
1295                 auto tpath = static_cast<GtkTreePath *>(work->data);
1296                 FileData *fd_n;
1297                 GtkTreeIter iter;
1298
1299                 gtk_tree_model_get_iter(store, &iter, tpath);
1300                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1301                 if (fd_n == fd) found = TRUE;
1302                 work = work->next;
1303                 }
1304         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1305
1306         return found;
1307 }
1308
1309 #pragma GCC diagnostic push
1310 #pragma GCC diagnostic ignored "-Wunused-function"
1311 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1312 {
1313         FileData *fd;
1314
1315         fd = vf_index_get_data(vf, row);
1316         return vflist_row_is_selected(vf, fd);
1317 }
1318 #pragma GCC diagnostic pop
1319
1320 gboolean vflist_is_selected(ViewFile *vf, FileData *fd)
1321 {
1322         return vflist_row_is_selected(vf, fd);
1323 }
1324
1325 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1326 {
1327         GtkTreeModel *store;
1328         GtkTreeSelection *selection;
1329         GList *slist;
1330         guint count;
1331
1332         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1333         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1334
1335         if (bytes)
1336                 {
1337                 gint64 b = 0;
1338                 GList *work;
1339
1340                 work = slist;
1341                 while (work)
1342                         {
1343                         auto tpath = static_cast<GtkTreePath *>(work->data);
1344                         GtkTreeIter iter;
1345                         FileData *fd;
1346
1347                         gtk_tree_model_get_iter(store, &iter, tpath);
1348                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1349                         b += fd->size;
1350
1351                         work = work->next;
1352                         }
1353
1354                 *bytes = b;
1355                 }
1356
1357         count = g_list_length(slist);
1358         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1359
1360         return count;
1361 }
1362
1363 GList *vflist_selection_get_list(ViewFile *vf)
1364 {
1365         GtkTreeModel *store;
1366         GtkTreeSelection *selection;
1367         GList *slist;
1368         GList *list = nullptr;
1369
1370         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1371         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1372         for (GList *work = g_list_last(slist); work; work = work->prev)
1373                 {
1374                 auto tpath = static_cast<GtkTreePath *>(work->data);
1375                 FileData *fd;
1376                 GtkTreeIter iter;
1377
1378                 gtk_tree_model_get_iter(store, &iter, tpath);
1379                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1380
1381                 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1382                         {
1383                         /* unexpanded - add whole group */
1384                         list = g_list_concat(filelist_copy(fd->sidecar_files), list);
1385                         }
1386
1387                 list = g_list_prepend(list, file_data_ref(fd));
1388                 }
1389         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1390
1391         return list;
1392 }
1393
1394 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1395 {
1396         GtkTreeModel *store;
1397         GtkTreeSelection *selection;
1398         GList *slist;
1399         GList *list = nullptr;
1400         GList *work;
1401
1402         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1403         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1404         work = slist;
1405         while (work)
1406                 {
1407                 auto tpath = static_cast<GtkTreePath *>(work->data);
1408                 FileData *fd;
1409                 GtkTreeIter iter;
1410
1411                 gtk_tree_model_get_iter(store, &iter, tpath);
1412                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1413
1414                 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1415
1416                 work = work->next;
1417                 }
1418         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1419
1420         return g_list_reverse(list);
1421 }
1422
1423 void vflist_selection_foreach(ViewFile *vf, const ViewFile::SelectionCallback &func)
1424 {
1425         GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1426         GtkTreeModel *store;
1427         GtkTreeIter iter;
1428         FileData *fd_n;
1429
1430         for (GList *work = gtk_tree_selection_get_selected_rows(selection, &store); work; work = work->next)
1431                 {
1432                 auto *tpath = static_cast<GtkTreePath *>(work->data);
1433
1434                 gtk_tree_model_get_iter(store, &iter, tpath);
1435                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1436
1437                 func(fd_n);
1438                 }
1439 }
1440
1441 void vflist_select_all(ViewFile *vf)
1442 {
1443         GtkTreeSelection *selection;
1444
1445         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1446         gtk_tree_selection_select_all(selection);
1447
1448         VFLIST(vf)->select_fd = nullptr;
1449 }
1450
1451 void vflist_select_none(ViewFile *vf)
1452 {
1453         GtkTreeSelection *selection;
1454
1455         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1456         gtk_tree_selection_unselect_all(selection);
1457 }
1458
1459 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1460 {
1461         GtkTreePath *tpath;
1462         gboolean result;
1463
1464         tpath = gtk_tree_model_get_path(store, iter);
1465         result = gtk_tree_path_prev(tpath);
1466         if (result)
1467                 gtk_tree_model_get_iter(store, iter, tpath);
1468
1469         gtk_tree_path_free(tpath);
1470
1471         return result;
1472 }
1473
1474 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1475 {
1476         if (!gtk_tree_model_get_iter_first(store, iter))
1477                 return FALSE;
1478
1479         while (TRUE)
1480                 {
1481                 GtkTreeIter next = *iter;
1482
1483                 if (gtk_tree_model_iter_next(store, &next))
1484                         *iter = next;
1485                 else
1486                         break;
1487                 }
1488
1489         return TRUE;
1490 }
1491
1492 void vflist_select_invert(ViewFile *vf)
1493 {
1494         GtkTreeIter iter;
1495         GtkTreeSelection *selection;
1496         GtkTreeModel *store;
1497         gboolean valid;
1498
1499         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1500         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1501
1502         /* Backward iteration prevents scrolling to the end of the list,
1503          * it scrolls to the first selected row instead. */
1504         valid = tree_model_get_iter_last(store, &iter);
1505
1506         while (valid)
1507                 {
1508                 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1509
1510                 if (selected)
1511                         gtk_tree_selection_unselect_iter(selection, &iter);
1512                 else
1513                         gtk_tree_selection_select_iter(selection, &iter);
1514
1515                 valid = tree_model_iter_prev(store, &iter);
1516                 }
1517 }
1518
1519 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1520 {
1521         GtkTreeIter iter;
1522
1523         if (vflist_find_row(vf, fd, &iter) < 0) return;
1524
1525         tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1526
1527         if (!vflist_row_is_selected(vf, fd))
1528                 {
1529                 GtkTreeSelection *selection;
1530                 GtkTreeModel *store;
1531                 GtkTreePath *tpath;
1532
1533                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1534                 gtk_tree_selection_unselect_all(selection);
1535                 gtk_tree_selection_select_iter(selection, &iter);
1536                 vflist_move_cursor(vf, &iter);
1537
1538                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1539                 tpath = gtk_tree_model_get_path(store, &iter);
1540                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
1541                 gtk_tree_path_free(tpath);
1542                 }
1543 }
1544
1545 void vflist_select_list(ViewFile *vf, GList *list)
1546 {
1547         GtkTreeIter iter;
1548         GList *work;
1549
1550         work = list;
1551
1552         while (work)
1553                 {
1554                 FileData *fd;
1555
1556                 fd = static_cast<FileData *>(work->data);
1557
1558                 if (vflist_find_row(vf, fd, &iter) < 0) return;
1559                 if (!vflist_row_is_selected(vf, fd))
1560                         {
1561                         GtkTreeSelection *selection;
1562
1563                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1564                         gtk_tree_selection_select_iter(selection, &iter);
1565                         }
1566                 work = work->next;
1567                 }
1568 }
1569
1570 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1571 {
1572         GList *work;
1573         FileData *fd = nullptr;
1574
1575         if (sel_fd->parent) sel_fd = sel_fd->parent;
1576         work = vf->list;
1577
1578         while (work)
1579                 {
1580                 gint match;
1581                 fd = static_cast<FileData *>(work->data);
1582                 work = work->next;
1583
1584                 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1585
1586                 if (match >= 0) break;
1587                 }
1588
1589         if (fd) vflist_select_by_fd(vf, fd);
1590
1591 }
1592
1593 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1594 {
1595         GtkTreeModel *store;
1596         GtkTreeIter iter;
1597         GtkTreeSelection *selection;
1598         gboolean valid;
1599         gint n = mark - 1;
1600
1601         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1602
1603         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1604         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1605
1606         valid = gtk_tree_model_get_iter_first(store, &iter);
1607         while (valid)
1608                 {
1609                 FileData *fd;
1610                 gboolean mark_val;
1611                 gboolean selected;
1612                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1613
1614                 mark_val = file_data_get_mark(fd, n);
1615                 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1616
1617                 switch (mode)
1618                         {
1619                         case MTS_MODE_SET: selected = mark_val;
1620                                 break;
1621                         case MTS_MODE_OR: selected = mark_val || selected;
1622                                 break;
1623                         case MTS_MODE_AND: selected = mark_val && selected;
1624                                 break;
1625                         case MTS_MODE_MINUS: selected = !mark_val && selected;
1626                                 break;
1627                         }
1628
1629                 if (selected)
1630                         gtk_tree_selection_select_iter(selection, &iter);
1631                 else
1632                         gtk_tree_selection_unselect_iter(selection, &iter);
1633
1634                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1635                 }
1636 }
1637
1638 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1639 {
1640         GtkTreeModel *store;
1641         GtkTreeSelection *selection;
1642         GList *slist;
1643         GList *work;
1644         gint n = mark - 1;
1645
1646         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1647
1648         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1649         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1650         work = slist;
1651         while (work)
1652                 {
1653                 auto tpath = static_cast<GtkTreePath *>(work->data);
1654                 FileData *fd;
1655                 GtkTreeIter iter;
1656
1657                 gtk_tree_model_get_iter(store, &iter, tpath);
1658                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1659
1660                 /* the change has a very limited range and the standard notification would trigger
1661                    complete re-read of the directory - try to do only minimal update instead */
1662                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1663
1664                 switch (mode)
1665                         {
1666                         case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1667                                 break;
1668                         case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1669                                 break;
1670                         case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1671                                 break;
1672                         }
1673
1674                 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1675                         {
1676                         vf_refresh_idle(vf);
1677                         }
1678                 else
1679                         {
1680                         /* mark functions can have various side effects - update all columns to be sure */
1681                         vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1682                         /* mark functions can change sidecars too */
1683                         vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1684                         }
1685
1686
1687                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1688
1689                 work = work->next;
1690                 }
1691         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1692 }
1693
1694 /*
1695  *-----------------------------------------------------------------------------
1696  * core (population)
1697  *-----------------------------------------------------------------------------
1698  */
1699
1700 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1701 {
1702         GtkTreeViewColumn *column;
1703         GtkCellRenderer *cell;
1704         GList *list;
1705
1706         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1707         if (!column) return;
1708
1709         gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1710
1711         list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1712         if (!list) return;
1713         cell = static_cast<GtkCellRenderer *>(list->data);
1714         g_list_free(list);
1715
1716         g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1717         gtk_tree_view_column_set_visible(column, thumb);
1718
1719         if (options->show_star_rating)
1720                 {
1721                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1722                 if (!column) return;
1723                 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1724                 gtk_tree_view_column_set_visible(column, TRUE);
1725
1726                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1727                 if (!column) return;
1728                 gtk_tree_view_column_set_visible(column, FALSE);
1729                 }
1730         else
1731                 {
1732                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1733                 if (!column) return;
1734                 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1735                 gtk_tree_view_column_set_visible(column, TRUE);
1736
1737                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1738                 if (!column) return;
1739                 gtk_tree_view_column_set_visible(column, FALSE);
1740                 }
1741
1742         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1743         if (!column) return;
1744         gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1745
1746         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1747         if (!column) return;
1748         gtk_tree_view_column_set_visible(column, !multiline);
1749
1750         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1751         if (!column) return;
1752         gtk_tree_view_column_set_visible(column, !multiline);
1753 }
1754
1755 static gboolean vflist_is_multiline(ViewFile *vf)
1756 {
1757         return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1758 }
1759
1760
1761 static void vflist_populate_view(ViewFile *vf, gboolean force)
1762 {
1763         GtkTreeStore *store;
1764         GList *selected;
1765
1766         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1767
1768         vf_thumb_stop(vf);
1769         vf_star_stop(vf);
1770
1771         if (!vf->list)
1772                 {
1773                 vflist_store_clear(vf, FALSE);
1774                 vf_send_update(vf);
1775                 return;
1776                 }
1777
1778         vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1779
1780         selected = vflist_selection_get_list(vf);
1781
1782         vflist_setup_iter_recursive(vf, store, nullptr, vf->list, selected, force);
1783
1784         if (selected && vflist_selection_count(vf, nullptr) == 0)
1785                 {
1786                 /* all selected files disappeared */
1787                 vflist_select_closest(vf, static_cast<FileData *>(selected->data));
1788                 }
1789
1790         filelist_free(selected);
1791
1792         vf_send_update(vf);
1793         vf_thumb_update(vf);
1794         vf_star_update(vf);
1795 }
1796
1797 gboolean vflist_refresh(ViewFile *vf)
1798 {
1799         GList *old_list;
1800         gboolean ret = TRUE;
1801
1802         old_list = vf->list;
1803         vf->list = nullptr;
1804
1805         DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1806         if (vf->dir_fd)
1807                 {
1808                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1809
1810                 ret = filelist_read(vf->dir_fd, &vf->list, nullptr);
1811
1812                 if (vf->marks_enabled)
1813                         {
1814                         // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1815                         // each time a mark is changed.
1816                         file_data_lock_list(vf->list);
1817                         }
1818                 else
1819                         {
1820                         /** @FIXME only do this when needed (aka when we just switched from */
1821                         /** @FIXME marks-enabled to marks-disabled) */
1822                         file_data_unlock_list(vf->list);
1823                         }
1824
1825                 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1826                 vf->list = g_list_first(vf->list);
1827                 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1828
1829                 vf->list = g_list_first(vf->list);
1830                 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1831
1832                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1833
1834                 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1835                 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1836                 }
1837
1838         DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1839
1840         vflist_populate_view(vf, FALSE);
1841
1842         DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1843
1844         filelist_free(old_list);
1845         DEBUG_1("%s vflist_refresh: done", get_exec_time());
1846
1847         return ret;
1848 }
1849
1850
1851 static GdkRGBA *vflist_listview_color_shifted(GtkWidget *widget)
1852 {
1853         static GdkRGBA color;
1854         static GtkWidget *done = nullptr;
1855
1856         if (done != widget)
1857                 {
1858                 GtkStyle *style;
1859
1860                 style = gtk_widget_get_style(widget);
1861                 convert_gdkcolor_to_gdkrgba(&style->base[GTK_STATE_NORMAL], &color);
1862
1863                 shift_color(&color, -1, 0);
1864                 done = widget;
1865                 }
1866
1867         return &color;
1868 }
1869
1870 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1871                                      GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1872 {
1873         auto vf = static_cast<ViewFile *>(data);
1874         gboolean set;
1875
1876         gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1877         g_object_set(G_OBJECT(cell),
1878                      "cell-background-rgba", vflist_listview_color_shifted(vf->listview),
1879                      "cell-background-set", set, NULL);
1880 }
1881
1882 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1883 {
1884         GtkTreeViewColumn *column;
1885         GtkCellRenderer *renderer;
1886
1887         column = gtk_tree_view_column_new();
1888         gtk_tree_view_column_set_title(column, title);
1889         gtk_tree_view_column_set_min_width(column, 4);
1890
1891         if (!image)
1892                 {
1893                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1894                 renderer = gtk_cell_renderer_text_new();
1895                 if (right_justify)
1896                         {
1897                         g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
1898                         }
1899                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1900                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
1901                 if (expand)
1902                         gtk_tree_view_column_set_expand(column, TRUE);
1903                 }
1904         else
1905                 {
1906                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1907                 renderer = gtk_cell_renderer_pixbuf_new();
1908                 cell_renderer_height_override(renderer);
1909                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1910                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
1911                 }
1912
1913         gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
1914         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1915         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1916
1917         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1918 }
1919
1920 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
1921 {
1922         auto vf = static_cast<ViewFile *>(data);
1923         GtkTreeStore *store;
1924         GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
1925         GtkTreeIter iter;
1926         FileData *fd;
1927         gboolean marked;
1928         guint col_idx;
1929
1930         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1931         if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
1932                 return;
1933
1934         col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
1935
1936         g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
1937
1938         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
1939         marked = !marked;
1940
1941         /* the change has a very limited range and the standard notification would trigger
1942            complete re-read of the directory - try to do only minimal update instead */
1943         file_data_unregister_notify_func(vf_notify_cb, vf);
1944         file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
1945         if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1946                 {
1947                 vf_refresh_idle(vf);
1948                 }
1949         else
1950                 {
1951                 /* mark functions can have various side effects - update all columns to be sure */
1952                 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1953                 /* mark functions can change sidecars too */
1954                 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1955                 }
1956         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1957
1958         gtk_tree_path_free(path);
1959 }
1960
1961 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
1962 {
1963         GtkTreeViewColumn *column;
1964         GtkCellRenderer *renderer;
1965
1966         renderer = gtk_cell_renderer_toggle_new();
1967         column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
1968
1969         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1970         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1971         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1972
1973         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1974         gtk_tree_view_column_set_fixed_width(column, 22);
1975         gtk_tree_view_column_set_visible(column, vf->marks_enabled);
1976
1977
1978         g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
1979 }
1980
1981 /*
1982  *-----------------------------------------------------------------------------
1983  * base
1984  *-----------------------------------------------------------------------------
1985  */
1986
1987 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
1988 {
1989         gboolean ret;
1990         if (!dir_fd) return FALSE;
1991         if (vf->dir_fd == dir_fd) return TRUE;
1992
1993         file_data_unref(vf->dir_fd);
1994         vf->dir_fd = file_data_ref(dir_fd);
1995
1996         /* force complete reload */
1997         vflist_store_clear(vf, TRUE);
1998
1999         filelist_free(vf->list);
2000         vf->list = nullptr;
2001
2002         ret = vflist_refresh(vf);
2003         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2004         return ret;
2005 }
2006
2007 void vflist_destroy_cb(ViewFile *vf)
2008 {
2009         file_data_unregister_notify_func(vf_notify_cb, vf);
2010
2011         vflist_select_idle_cancel(vf);
2012         vf_refresh_idle_cancel(vf);
2013         vf_thumb_stop(vf);
2014         vf_star_stop(vf);
2015
2016         filelist_free(vf->list);
2017 }
2018
2019 ViewFile *vflist_new(ViewFile *vf)
2020 {
2021         GtkTreeStore *store;
2022         GtkTreeSelection *selection;
2023         GType flist_types[FILE_COLUMN_COUNT];
2024         gint i;
2025         gint column;
2026
2027         vf->info = g_new0(ViewFileInfoList, 1);
2028
2029         flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2030         flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2031         flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2032         flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2033         flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2034         flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2035         flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2036         flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2037         flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2038         flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2039         flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2040         flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2041         for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2042                 flist_types[i] = G_TYPE_BOOLEAN;
2043
2044         store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2045
2046         vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2047         g_object_unref(store);
2048
2049         g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2050                          G_CALLBACK(vflist_expand_cb), vf);
2051
2052         g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2053                          G_CALLBACK(vflist_collapse_cb), vf);
2054
2055         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2056         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2057         gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2058
2059         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2060         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2061
2062         gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2063
2064         column = 0;
2065
2066         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2067                 {
2068                 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2069                 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2070                 column++;
2071                 }
2072
2073         vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2074         g_assert(column == FILE_VIEW_COLUMN_THUMB);
2075         column++;
2076
2077         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2078         g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2079         column++;
2080
2081         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2082         g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2083         column++;
2084
2085         vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2086         g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2087         column++;
2088
2089         vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2090         g_assert(column == FILE_VIEW_COLUMN_SIZE);
2091         column++;
2092
2093         vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2094         g_assert(column == FILE_VIEW_COLUMN_DATE);
2095         column++;
2096
2097         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2098         return vf;
2099 }
2100
2101 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2102 {
2103         if (VFLIST(vf)->thumbs_enabled == enable) return;
2104
2105         VFLIST(vf)->thumbs_enabled = enable;
2106
2107         /* vflist_populate_view is better than vflist_refresh:
2108            - no need to re-read the directory
2109            - force update because the formatted string has changed
2110         */
2111         if (vf->layout)
2112                 {
2113                 vflist_populate_view(vf, TRUE);
2114                 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2115                 }
2116 }
2117
2118 void vflist_marks_set(ViewFile *vf, gboolean enable)
2119 {
2120         GList *columns;
2121         GList *work;
2122
2123         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2124
2125         work = columns;
2126         while (work)
2127                 {
2128                 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2129                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2130                 work = work->next;
2131
2132                 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2133                         gtk_tree_view_column_set_visible(column, enable);
2134                 }
2135
2136         if (enable)
2137                 {
2138                 // Previously disabled, which means that vf->list is complete
2139                 file_data_lock_list(vf->list);
2140                 }
2141         else
2142                 {
2143                 // Previously enabled, which means that vf->list is incomplete
2144                 }
2145
2146         g_list_free(columns);
2147 }
2148
2149 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */