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