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