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