(Re)-implement natural and case sorting
[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, gboolean case_sensitive)
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 && vf->sort_case == case_sensitive) 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         vf->sort_case = case_sensitive;
1109
1110         vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1111
1112         std::vector<gint> new_order;
1113         new_order.reserve(i);
1114
1115         work = vf->list;
1116         while (work)
1117                 {
1118                 auto fd = static_cast<FileData *>(work->data);
1119                 new_order.push_back(GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd)));
1120                 work = work->next;
1121                 }
1122
1123         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1124         gtk_tree_store_reorder(store, nullptr, new_order.data());
1125
1126         g_hash_table_destroy(fd_idx_hash);
1127 }
1128
1129 /*
1130  *-----------------------------------------------------------------------------
1131  * thumb updates
1132  *-----------------------------------------------------------------------------
1133  */
1134
1135
1136 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1137 {
1138         GList *work = list;
1139         while (work)
1140                 {
1141                 auto fd = static_cast<FileData *>(work->data);
1142                 work = work->next;
1143
1144                 if (fd->thumb_pixbuf) (*done)++;
1145
1146                 if (fd->sidecar_files)
1147                         {
1148                         vflist_thumb_progress_count(fd->sidecar_files, count, done);
1149                         }
1150                 (*count)++;
1151                 }
1152 }
1153
1154 void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
1155 {
1156         GList *work = list;
1157         while (work)
1158                 {
1159                 auto fd = static_cast<FileData *>(work->data);
1160                 work = work->next;
1161
1162                 if (fd->metadata_in_idle_loaded) (*done)++;
1163
1164                 if (fd->sidecar_files)
1165                         {
1166                         vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1167                         }
1168                 (*count)++;
1169                 }
1170 }
1171
1172 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1173 {
1174         GtkTreeStore *store;
1175         GtkTreeIter iter;
1176
1177         if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1178
1179         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1180         gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1181 }
1182
1183 FileData *vflist_thumb_next_fd(ViewFile *vf)
1184 {
1185         GtkTreePath *tpath;
1186         FileData *fd = nullptr;
1187
1188         /* first check the visible files */
1189
1190         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1191                 {
1192                 GtkTreeModel *store;
1193                 GtkTreeIter iter;
1194                 gboolean valid = TRUE;
1195
1196                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1197                 gtk_tree_model_get_iter(store, &iter, tpath);
1198                 gtk_tree_path_free(tpath);
1199                 tpath = nullptr;
1200
1201                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1202                         {
1203                         FileData *nfd;
1204
1205                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1206
1207                         if (!nfd->thumb_pixbuf) fd = nfd;
1208
1209                         valid = gtk_tree_model_iter_next(store, &iter);
1210                         }
1211                 }
1212
1213         /* then find first undone */
1214
1215         if (!fd)
1216                 {
1217                 GList *work = vf->list;
1218                 while (work && !fd)
1219                         {
1220                         auto fd_p = static_cast<FileData *>(work->data);
1221                         if (!fd_p->thumb_pixbuf)
1222                                 fd = fd_p;
1223                         else
1224                                 {
1225                                 GList *work2 = fd_p->sidecar_files;
1226
1227                                 while (work2 && !fd)
1228                                         {
1229                                         fd_p = static_cast<FileData *>(work2->data);
1230                                         if (!fd_p->thumb_pixbuf) fd = fd_p;
1231                                         work2 = work2->next;
1232                                         }
1233                                 }
1234                         work = work->next;
1235                         }
1236                 }
1237
1238         return fd;
1239 }
1240
1241 void vflist_set_star_fd(ViewFile *vf, FileData *fd)
1242 {
1243         GtkTreeStore *store;
1244         GtkTreeIter iter;
1245         gchar *name;
1246         gchar *sidecars;
1247         gchar *size;
1248         gchar *time;
1249         gchar *star_rating;
1250         gchar *formatted_with_stars;
1251         gboolean expanded;
1252
1253         if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1254
1255         star_rating = metadata_read_rating_stars(fd);
1256
1257         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1258         gtk_tree_store_set(store, &iter, FILE_COLUMN_STAR_RATING, star_rating, -1);
1259
1260         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1261                                         FILE_COLUMN_NAME, &name,
1262                                         FILE_COLUMN_SIDECARS, &sidecars,
1263                                         FILE_COLUMN_SIZE, &size,
1264                                         FILE_COLUMN_DATE, &time,
1265                                         FILE_COLUMN_EXPANDED, &expanded,
1266                                         -1);
1267
1268         formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
1269
1270         gtk_tree_store_set(store, &iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
1271                                         FILE_COLUMN_EXPANDED, expanded,
1272                                         -1);
1273
1274         g_free(star_rating);
1275         g_free(formatted_with_stars);
1276 }
1277
1278 FileData *vflist_star_next_fd(ViewFile *vf)
1279 {
1280         GtkTreePath *tpath;
1281         FileData *fd = nullptr;
1282         FileData *nfd = nullptr;
1283
1284         /* first check the visible files */
1285
1286         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1287                 {
1288                 GtkTreeModel *store;
1289                 GtkTreeIter iter;
1290                 gboolean valid = TRUE;
1291
1292                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1293                 gtk_tree_model_get_iter(store, &iter, tpath);
1294                 gtk_tree_path_free(tpath);
1295                 tpath = nullptr;
1296
1297                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1298                         {
1299                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1300
1301                         if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1302                                 {
1303                                 fd = nfd;
1304                                 }
1305
1306                         valid = gtk_tree_model_iter_next(store, &iter);
1307                         }
1308
1309                 if (fd)
1310                         {
1311                         vf->stars_filedata = fd;
1312
1313                         if (vf->stars_id == 0)
1314                                 {
1315                                 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1316                                 }
1317                         }
1318                 }
1319
1320         /* then find first undone */
1321
1322         if (!fd)
1323                 {
1324                 GList *work = vf->list;
1325
1326                 while (work && !fd)
1327                         {
1328                         auto fd_p = static_cast<FileData *>(work->data);
1329
1330                         if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1331                                 {
1332                                 fd = fd_p;
1333                                 }
1334                         else
1335                                 {
1336                                 fd = nullptr;
1337                                 }
1338
1339                         work = work->next;
1340                         }
1341
1342                 if (fd)
1343                         {
1344                         vf->stars_filedata = fd;
1345
1346                         if (vf->stars_id == 0)
1347                                 {
1348                                 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1349                                 }
1350                         }
1351                 }
1352
1353         return fd;
1354 }
1355
1356 /*
1357  *-----------------------------------------------------------------------------
1358  * row stuff
1359  *-----------------------------------------------------------------------------
1360  */
1361
1362 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1363 {
1364         gint p = 0;
1365         GList *work, *work2;
1366
1367         work = vf->list;
1368         while (work)
1369                 {
1370                 auto list_fd = static_cast<FileData *>(work->data);
1371                 if (list_fd == fd) return p;
1372
1373                 work2 = list_fd->sidecar_files;
1374                 while (work2)
1375                         {
1376                         /** @FIXME return the same index also for sidecars
1377                            it is sufficient for next/prev navigation but it should be rewritten
1378                            without using indexes at all
1379                         */
1380                         auto sidecar_fd = static_cast<FileData *>(work2->data);
1381                         if (sidecar_fd == fd) return p;
1382                         work2 = work2->next;
1383                         }
1384
1385                 work = work->next;
1386                 p++;
1387                 }
1388
1389         return -1;
1390 }
1391
1392 /*
1393  *-----------------------------------------------------------------------------
1394  * selections
1395  *-----------------------------------------------------------------------------
1396  */
1397
1398 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1399 {
1400         GtkTreeModel *store;
1401         GtkTreeSelection *selection;
1402         GList *slist;
1403         GList *work;
1404         gboolean found = FALSE;
1405
1406         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1407         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1408         work = slist;
1409         while (!found && work)
1410                 {
1411                 auto tpath = static_cast<GtkTreePath *>(work->data);
1412                 FileData *fd_n;
1413                 GtkTreeIter iter;
1414
1415                 gtk_tree_model_get_iter(store, &iter, tpath);
1416                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1417                 if (fd_n == fd) found = TRUE;
1418                 work = work->next;
1419                 }
1420         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1421
1422         return found;
1423 }
1424
1425 #pragma GCC diagnostic push
1426 #pragma GCC diagnostic ignored "-Wunused-function"
1427 gboolean vflist_index_is_selected_unused(ViewFile *vf, gint row)
1428 {
1429         FileData *fd;
1430
1431         fd = vf_index_get_data(vf, row);
1432         return vflist_row_is_selected(vf, fd);
1433 }
1434 #pragma GCC diagnostic pop
1435
1436 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1437 {
1438         GtkTreeModel *store;
1439         GtkTreeSelection *selection;
1440         GList *slist;
1441         guint count;
1442
1443         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1444         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1445
1446         if (bytes)
1447                 {
1448                 gint64 b = 0;
1449                 GList *work;
1450
1451                 work = slist;
1452                 while (work)
1453                         {
1454                         auto tpath = static_cast<GtkTreePath *>(work->data);
1455                         GtkTreeIter iter;
1456                         FileData *fd;
1457
1458                         gtk_tree_model_get_iter(store, &iter, tpath);
1459                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1460                         b += fd->size;
1461
1462                         work = work->next;
1463                         }
1464
1465                 *bytes = b;
1466                 }
1467
1468         count = g_list_length(slist);
1469         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1470
1471         return count;
1472 }
1473
1474 GList *vflist_selection_get_list(ViewFile *vf)
1475 {
1476         GtkTreeModel *store;
1477         GtkTreeSelection *selection;
1478         GList *slist;
1479         GList *list = nullptr;
1480         GList *work;
1481
1482         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1483         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1484         work = slist;
1485         while (work)
1486                 {
1487                 auto tpath = static_cast<GtkTreePath *>(work->data);
1488                 FileData *fd;
1489                 GtkTreeIter iter;
1490
1491                 gtk_tree_model_get_iter(store, &iter, tpath);
1492                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1493
1494                 list = g_list_prepend(list, file_data_ref(fd));
1495
1496                 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1497                         {
1498                         /* unexpanded - add whole group */
1499                         GList *work2 = fd->sidecar_files;
1500                         while (work2)
1501                                 {
1502                                 auto sfd = static_cast<FileData *>(work2->data);
1503                                 list = g_list_prepend(list, file_data_ref(sfd));
1504                                 work2 = work2->next;
1505                                 }
1506                         }
1507
1508                 work = work->next;
1509                 }
1510         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1511
1512         return g_list_reverse(list);
1513 }
1514
1515 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1516 {
1517         GtkTreeModel *store;
1518         GtkTreeSelection *selection;
1519         GList *slist;
1520         GList *list = nullptr;
1521         GList *work;
1522
1523         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1524         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1525         work = slist;
1526         while (work)
1527                 {
1528                 auto tpath = static_cast<GtkTreePath *>(work->data);
1529                 FileData *fd;
1530                 GtkTreeIter iter;
1531
1532                 gtk_tree_model_get_iter(store, &iter, tpath);
1533                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1534
1535                 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1536
1537                 work = work->next;
1538                 }
1539         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1540
1541         return g_list_reverse(list);
1542 }
1543
1544 void vflist_select_all(ViewFile *vf)
1545 {
1546         GtkTreeSelection *selection;
1547
1548         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1549         gtk_tree_selection_select_all(selection);
1550
1551         VFLIST(vf)->select_fd = nullptr;
1552 }
1553
1554 void vflist_select_none(ViewFile *vf)
1555 {
1556         GtkTreeSelection *selection;
1557
1558         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1559         gtk_tree_selection_unselect_all(selection);
1560 }
1561
1562 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1563 {
1564         GtkTreePath *tpath;
1565         gboolean result;
1566
1567         tpath = gtk_tree_model_get_path(store, iter);
1568         result = gtk_tree_path_prev(tpath);
1569         if (result)
1570                 gtk_tree_model_get_iter(store, iter, tpath);
1571
1572         gtk_tree_path_free(tpath);
1573
1574         return result;
1575 }
1576
1577 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1578 {
1579         if (!gtk_tree_model_get_iter_first(store, iter))
1580                 return FALSE;
1581
1582         while (TRUE)
1583                 {
1584                 GtkTreeIter next = *iter;
1585
1586                 if (gtk_tree_model_iter_next(store, &next))
1587                         *iter = next;
1588                 else
1589                         break;
1590                 }
1591
1592         return TRUE;
1593 }
1594
1595 void vflist_select_invert(ViewFile *vf)
1596 {
1597         GtkTreeIter iter;
1598         GtkTreeSelection *selection;
1599         GtkTreeModel *store;
1600         gboolean valid;
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         /* Backward iteration prevents scrolling to the end of the list,
1606          * it scrolls to the first selected row instead. */
1607         valid = tree_model_get_iter_last(store, &iter);
1608
1609         while (valid)
1610                 {
1611                 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1612
1613                 if (selected)
1614                         gtk_tree_selection_unselect_iter(selection, &iter);
1615                 else
1616                         gtk_tree_selection_select_iter(selection, &iter);
1617
1618                 valid = tree_model_iter_prev(store, &iter);
1619                 }
1620 }
1621
1622 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1623 {
1624         GtkTreeIter iter;
1625
1626         if (vflist_find_row(vf, fd, &iter) < 0) return;
1627
1628         tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1629
1630         if (!vflist_row_is_selected(vf, fd))
1631                 {
1632                 GtkTreeSelection *selection;
1633                 GtkTreeModel *store;
1634                 GtkTreePath *tpath;
1635
1636                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1637                 gtk_tree_selection_unselect_all(selection);
1638                 gtk_tree_selection_select_iter(selection, &iter);
1639                 vflist_move_cursor(vf, &iter);
1640
1641                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1642                 tpath = gtk_tree_model_get_path(store, &iter);
1643                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, nullptr, FALSE);
1644                 gtk_tree_path_free(tpath);
1645                 }
1646 }
1647
1648 void vflist_select_list(ViewFile *vf, GList *list)
1649 {
1650         GtkTreeIter iter;
1651         GList *work;
1652
1653         work = list;
1654
1655         while (work)
1656                 {
1657                 FileData *fd;
1658
1659                 fd = static_cast<FileData *>(work->data);
1660
1661                 if (vflist_find_row(vf, fd, &iter) < 0) return;
1662                 if (!vflist_row_is_selected(vf, fd))
1663                         {
1664                         GtkTreeSelection *selection;
1665
1666                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1667                         gtk_tree_selection_select_iter(selection, &iter);
1668                         }
1669                 work = work->next;
1670                 }
1671 }
1672
1673 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1674 {
1675         GList *work;
1676         FileData *fd = nullptr;
1677
1678         if (sel_fd->parent) sel_fd = sel_fd->parent;
1679         work = vf->list;
1680
1681         while (work)
1682                 {
1683                 gint match;
1684                 fd = static_cast<FileData *>(work->data);
1685                 work = work->next;
1686
1687                 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1688
1689                 if (match >= 0) break;
1690                 }
1691
1692         if (fd) vflist_select_by_fd(vf, fd);
1693
1694 }
1695
1696 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1697 {
1698         GtkTreeModel *store;
1699         GtkTreeIter iter;
1700         GtkTreeSelection *selection;
1701         gboolean valid;
1702         gint n = mark - 1;
1703
1704         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1705
1706         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1707         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1708
1709         valid = gtk_tree_model_get_iter_first(store, &iter);
1710         while (valid)
1711                 {
1712                 FileData *fd;
1713                 gboolean mark_val, selected;
1714                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1715
1716                 mark_val = file_data_get_mark(fd, n);
1717                 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1718
1719                 switch (mode)
1720                         {
1721                         case MTS_MODE_SET: selected = mark_val;
1722                                 break;
1723                         case MTS_MODE_OR: selected = mark_val || selected;
1724                                 break;
1725                         case MTS_MODE_AND: selected = mark_val && selected;
1726                                 break;
1727                         case MTS_MODE_MINUS: selected = !mark_val && selected;
1728                                 break;
1729                         }
1730
1731                 if (selected)
1732                         gtk_tree_selection_select_iter(selection, &iter);
1733                 else
1734                         gtk_tree_selection_unselect_iter(selection, &iter);
1735
1736                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1737                 }
1738 }
1739
1740 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1741 {
1742         GtkTreeModel *store;
1743         GtkTreeSelection *selection;
1744         GList *slist;
1745         GList *work;
1746         gint n = mark - 1;
1747
1748         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1749
1750         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1751         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1752         work = slist;
1753         while (work)
1754                 {
1755                 auto tpath = static_cast<GtkTreePath *>(work->data);
1756                 FileData *fd;
1757                 GtkTreeIter iter;
1758
1759                 gtk_tree_model_get_iter(store, &iter, tpath);
1760                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1761
1762                 /* the change has a very limited range and the standard notification would trigger
1763                    complete re-read of the directory - try to do only minimal update instead */
1764                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1765
1766                 switch (mode)
1767                         {
1768                         case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1769                                 break;
1770                         case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1771                                 break;
1772                         case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1773                                 break;
1774                         }
1775
1776                 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1777                         {
1778                         vf_refresh_idle(vf);
1779                         }
1780                 else
1781                         {
1782                         /* mark functions can have various side effects - update all columns to be sure */
1783                         vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1784                         /* mark functions can change sidecars too */
1785                         vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
1786                         }
1787
1788
1789                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1790
1791                 work = work->next;
1792                 }
1793         g_list_free_full(slist, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
1794 }
1795
1796 /*
1797  *-----------------------------------------------------------------------------
1798  * core (population)
1799  *-----------------------------------------------------------------------------
1800  */
1801
1802 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1803 {
1804         GtkTreeViewColumn *column;
1805         GtkCellRenderer *cell;
1806         GList *list;
1807
1808         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1809         if (!column) return;
1810
1811         gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1812
1813         list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1814         if (!list) return;
1815         cell = static_cast<GtkCellRenderer *>(list->data);
1816         g_list_free(list);
1817
1818         g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1819         gtk_tree_view_column_set_visible(column, thumb);
1820
1821         if (options->show_star_rating)
1822                 {
1823                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1824                 if (!column) return;
1825                 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1826                 gtk_tree_view_column_set_visible(column, TRUE);
1827
1828                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1829                 if (!column) return;
1830                 gtk_tree_view_column_set_visible(column, FALSE);
1831                 }
1832         else
1833                 {
1834                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1835                 if (!column) return;
1836                 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1837                 gtk_tree_view_column_set_visible(column, TRUE);
1838
1839                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1840                 if (!column) return;
1841                 gtk_tree_view_column_set_visible(column, FALSE);
1842                 }
1843
1844         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1845         if (!column) return;
1846         gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1847
1848         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1849         if (!column) return;
1850         gtk_tree_view_column_set_visible(column, !multiline);
1851
1852         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1853         if (!column) return;
1854         gtk_tree_view_column_set_visible(column, !multiline);
1855 }
1856
1857 static gboolean vflist_is_multiline(ViewFile *vf)
1858 {
1859         return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1860 }
1861
1862
1863 static void vflist_populate_view(ViewFile *vf, gboolean force)
1864 {
1865         GtkTreeStore *store;
1866         GList *selected;
1867
1868         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1869
1870         vf_thumb_stop(vf);
1871         vf_star_stop(vf);
1872
1873         if (!vf->list)
1874                 {
1875                 vflist_store_clear(vf, FALSE);
1876                 vf_send_update(vf);
1877                 return;
1878                 }
1879
1880         vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1881
1882         selected = vflist_selection_get_list(vf);
1883
1884         vflist_setup_iter_recursive(vf, store, nullptr, vf->list, selected, force);
1885
1886         if (selected && vflist_selection_count(vf, nullptr) == 0)
1887                 {
1888                 /* all selected files disappeared */
1889                 vflist_select_closest(vf, static_cast<FileData *>(selected->data));
1890                 }
1891
1892         filelist_free(selected);
1893
1894         vf_send_update(vf);
1895         vf_thumb_update(vf);
1896         vf_star_update(vf);
1897 }
1898
1899 gboolean vflist_refresh(ViewFile *vf)
1900 {
1901         GList *old_list;
1902         gboolean ret = TRUE;
1903
1904         old_list = vf->list;
1905         vf->list = nullptr;
1906
1907         DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1908         if (vf->dir_fd)
1909                 {
1910                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1911
1912                 ret = filelist_read(vf->dir_fd, &vf->list, nullptr);
1913
1914                 if (vf->marks_enabled)
1915                         {
1916                         // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1917                         // each time a mark is changed.
1918                         file_data_lock_list(vf->list);
1919                         }
1920                 else
1921                         {
1922                         /** @FIXME only do this when needed (aka when we just switched from */
1923                         /** @FIXME marks-enabled to marks-disabled) */
1924                         file_data_unlock_list(vf->list);
1925                         }
1926
1927                 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1928                 vf->list = g_list_first(vf->list);
1929                 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1930
1931                 vf->list = g_list_first(vf->list);
1932                 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1933
1934                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1935
1936                 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1937                 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case);
1938                 }
1939
1940         DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1941
1942         vflist_populate_view(vf, FALSE);
1943
1944         DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1945
1946         filelist_free(old_list);
1947         DEBUG_1("%s vflist_refresh: done", get_exec_time());
1948
1949         return ret;
1950 }
1951
1952
1953
1954 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1955
1956 #define CELL_HEIGHT_OVERRIDE 512
1957
1958 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1959 {
1960         GParamSpec *spec;
1961
1962         spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1963         if (spec && G_IS_PARAM_SPEC_INT(spec))
1964                 {
1965                 GParamSpecInt *spec_int;
1966
1967                 spec_int = G_PARAM_SPEC_INT(spec);
1968                 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1969                 }
1970 }
1971
1972 static GdkColor *vflist_listview_color_shifted(GtkWidget *widget)
1973 {
1974         static GdkColor color;
1975         static GtkWidget *done = nullptr;
1976
1977         if (done != widget)
1978                 {
1979                 GtkStyle *style;
1980
1981                 style = gtk_widget_get_style(widget);
1982                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
1983                 shift_color(&color, -1, 0);
1984                 done = widget;
1985                 }
1986
1987         return &color;
1988 }
1989
1990 static void vflist_listview_color_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
1991                                      GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1992 {
1993         auto vf = static_cast<ViewFile *>(data);
1994         gboolean set;
1995
1996         gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1997         g_object_set(G_OBJECT(cell),
1998                      "cell-background-gdk", vflist_listview_color_shifted(vf->listview),
1999                      "cell-background-set", set, NULL);
2000 }
2001
2002 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
2003 {
2004         GtkTreeViewColumn *column;
2005         GtkCellRenderer *renderer;
2006
2007         column = gtk_tree_view_column_new();
2008         gtk_tree_view_column_set_title(column, title);
2009         gtk_tree_view_column_set_min_width(column, 4);
2010
2011         if (!image)
2012                 {
2013                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2014                 renderer = gtk_cell_renderer_text_new();
2015                 if (right_justify)
2016                         {
2017                         g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2018                         }
2019                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2020                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2021                 if (expand)
2022                         gtk_tree_view_column_set_expand(column, TRUE);
2023                 }
2024         else
2025                 {
2026                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2027                 renderer = gtk_cell_renderer_pixbuf_new();
2028                 cell_renderer_height_override(renderer);
2029                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2030                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2031                 }
2032
2033         gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, nullptr);
2034         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2035         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2036
2037         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2038 }
2039
2040 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2041 {
2042         auto vf = static_cast<ViewFile *>(data);
2043         GtkTreeStore *store;
2044         GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2045         GtkTreeIter iter;
2046         FileData *fd;
2047         gboolean marked;
2048         guint col_idx;
2049
2050         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2051         if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2052                 return;
2053
2054         col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2055
2056         g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2057
2058         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2059         marked = !marked;
2060
2061         /* the change has a very limited range and the standard notification would trigger
2062            complete re-read of the directory - try to do only minimal update instead */
2063         file_data_unregister_notify_func(vf_notify_cb, vf);
2064         file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2065         if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2066                 {
2067                 vf_refresh_idle(vf);
2068                 }
2069         else
2070                 {
2071                 /* mark functions can have various side effects - update all columns to be sure */
2072                 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2073                 /* mark functions can change sidecars too */
2074                 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, nullptr, FALSE);
2075                 }
2076         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2077
2078         gtk_tree_path_free(path);
2079 }
2080
2081 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2082 {
2083         GtkTreeViewColumn *column;
2084         GtkCellRenderer *renderer;
2085
2086         renderer = gtk_cell_renderer_toggle_new();
2087         column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2088
2089         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2090         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2091         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2092
2093         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2094         gtk_tree_view_column_set_fixed_width(column, 22);
2095         gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2096
2097
2098         g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2099 }
2100
2101 /*
2102  *-----------------------------------------------------------------------------
2103  * base
2104  *-----------------------------------------------------------------------------
2105  */
2106
2107 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2108 {
2109         gboolean ret;
2110         if (!dir_fd) return FALSE;
2111         if (vf->dir_fd == dir_fd) return TRUE;
2112
2113         file_data_unref(vf->dir_fd);
2114         vf->dir_fd = file_data_ref(dir_fd);
2115
2116         /* force complete reload */
2117         vflist_store_clear(vf, TRUE);
2118
2119         filelist_free(vf->list);
2120         vf->list = nullptr;
2121
2122         ret = vf_refresh(vf);
2123         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2124         return ret;
2125 }
2126
2127 void vflist_destroy_cb(GtkWidget *, gpointer data)
2128 {
2129         auto vf = static_cast<ViewFile *>(data);
2130
2131         file_data_unregister_notify_func(vf_notify_cb, vf);
2132
2133         vflist_select_idle_cancel(vf);
2134         vf_refresh_idle_cancel(vf);
2135         vf_thumb_stop(vf);
2136         vf_star_stop(vf);
2137
2138         filelist_free(vf->list);
2139 }
2140
2141 ViewFile *vflist_new(ViewFile *vf, FileData *)
2142 {
2143         GtkTreeStore *store;
2144         GtkTreeSelection *selection;
2145         GType flist_types[FILE_COLUMN_COUNT];
2146         gint i;
2147         gint column;
2148
2149         vf->info = g_new0(ViewFileInfoList, 1);
2150
2151         flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2152         flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2153         flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2154         flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2155         flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2156         flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2157         flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2158         flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2159         flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2160         flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2161         flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2162         flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2163         for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2164                 flist_types[i] = G_TYPE_BOOLEAN;
2165
2166         store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2167
2168         vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2169         g_object_unref(store);
2170
2171         g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2172                          G_CALLBACK(vflist_expand_cb), vf);
2173
2174         g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2175                          G_CALLBACK(vflist_collapse_cb), vf);
2176
2177         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2178         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2179         gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, nullptr);
2180
2181         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2182         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2183
2184         gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2185
2186         column = 0;
2187
2188         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2189                 {
2190                 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2191                 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2192                 column++;
2193                 }
2194
2195         vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2196         g_assert(column == FILE_VIEW_COLUMN_THUMB);
2197         column++;
2198
2199         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2200         g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2201         column++;
2202
2203         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2204         g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2205         column++;
2206
2207         vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2208         g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2209         column++;
2210
2211         vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2212         g_assert(column == FILE_VIEW_COLUMN_SIZE);
2213         column++;
2214
2215         vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2216         g_assert(column == FILE_VIEW_COLUMN_DATE);
2217         column++;
2218
2219         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2220         return vf;
2221 }
2222
2223 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2224 {
2225         if (VFLIST(vf)->thumbs_enabled == enable) return;
2226
2227         VFLIST(vf)->thumbs_enabled = enable;
2228
2229         /* vflist_populate_view is better than vf_refresh:
2230            - no need to re-read the directory
2231            - force update because the formatted string has changed
2232         */
2233         if (vf->layout)
2234                 {
2235                 vflist_populate_view(vf, TRUE);
2236                 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2237                 }
2238 }
2239
2240 void vflist_marks_set(ViewFile *vf, gboolean enable)
2241 {
2242         GList *columns, *work;
2243
2244         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2245
2246         work = columns;
2247         while (work)
2248                 {
2249                 auto column = static_cast<GtkTreeViewColumn *>(work->data);
2250                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2251                 work = work->next;
2252
2253                 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2254                         gtk_tree_view_column_set_visible(column, enable);
2255                 }
2256
2257         if (enable)
2258                 {
2259                 // Previously disabled, which means that vf->list is complete
2260                 file_data_lock_list(vf->list);
2261                 }
2262         else
2263                 {
2264                 // Previously enabled, which means that vf->list is incomplete
2265                 }
2266
2267         g_list_free(columns);
2268 }
2269
2270 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */