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