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