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