56b9e54272a0cdbb83e4d8183d23e8425d22264d
[geeqie.git] / src / view-file / view-file-icon.cc
1 /*
2  * Copyright (C) 2006 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-icon.h"
23
24 #include <cstring>
25 #include <utility>
26
27 #include <glib-object.h>
28
29 #include "cellrenderericon.h"
30 #include "collect.h"
31 #include "compat.h"
32 #include "debug.h"
33 #include "dnd.h"
34 #include "filedata.h"
35 #include "img-view.h"
36 #include "intl.h"
37 #include "layout-image.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 namespace
50 {
51
52 /* between these, the icon width is increased by thumb_max_width / 2 */
53 constexpr gint THUMB_MIN_ICON_WIDTH = 128;
54 constexpr gint THUMB_MAX_ICON_WIDTH = 160;
55
56 constexpr gint THUMB_MIN_ICON_WIDTH_WITH_MARKS = TOGGLE_SPACING * FILEDATA_MARKS_SIZE;
57
58 constexpr gint VFICON_MAX_COLUMNS = 32;
59
60 constexpr gint THUMB_BORDER_PADDING = 2;
61
62 constexpr gint VFICON_TIP_DELAY = 500;
63
64 enum {
65         FILE_COLUMN_POINTER = VIEW_FILE_COLUMN_POINTER,
66         FILE_COLUMN_COUNT
67 };
68
69 struct ColumnData
70 {
71         ViewFile *vf;
72         gint number;
73 };
74
75 } // namespace
76
77 static void vficon_toggle_filenames(ViewFile *vf);
78 static void vficon_selection_remove(ViewFile *vf, FileData *fd, SelectionType mask, GtkTreeIter *iter);
79 static void vficon_move_focus(ViewFile *vf, gint row, gint col, gboolean relative);
80 static void vficon_set_focus(ViewFile *vf, FileData *fd);
81 static void vficon_populate_at_new_size(ViewFile *vf, gint w, gint h, gboolean force);
82
83
84 /*
85  *-----------------------------------------------------------------------------
86  * pop-up menu
87  *-----------------------------------------------------------------------------
88  */
89
90 GList *vficon_selection_get_one(ViewFile *, FileData *fd)
91 {
92         return g_list_prepend(filelist_copy(fd->sidecar_files), file_data_ref(fd));
93 }
94
95 GList *vficon_pop_menu_file_list(ViewFile *vf)
96 {
97         if (!VFICON(vf)->click_fd) return nullptr;
98
99         if (VFICON(vf)->click_fd->selected & SELECTION_SELECTED)
100                 {
101                 return vf_selection_get_list(vf);
102                 }
103
104         return vficon_selection_get_one(vf, VFICON(vf)->click_fd);
105 }
106
107 void vficon_pop_menu_view_cb(GtkWidget *, gpointer data)
108 {
109         auto vf = static_cast<ViewFile *>(data);
110
111         if (!VFICON(vf)->click_fd) return;
112
113         if (VFICON(vf)->click_fd->selected & SELECTION_SELECTED)
114                 {
115                 GList *list;
116
117                 list = vf_selection_get_list(vf);
118                 view_window_new_from_list(list);
119                 filelist_free(list);
120                 }
121         else
122                 {
123                 view_window_new(VFICON(vf)->click_fd);
124                 }
125 }
126
127 void vficon_pop_menu_rename_cb(GtkWidget *, gpointer data)
128 {
129         auto vf = static_cast<ViewFile *>(data);
130
131         file_util_rename(nullptr, vf_pop_menu_file_list(vf), vf->listview);
132 }
133
134 void vficon_pop_menu_show_names_cb(GtkWidget *, gpointer data)
135 {
136         auto vf = static_cast<ViewFile *>(data);
137
138         vficon_toggle_filenames(vf);
139 }
140
141 void vficon_pop_menu_show_star_rating_cb(ViewFile *vf)
142 {
143         GtkAllocation allocation;
144
145         gtk_widget_get_allocation(vf->listview, &allocation);
146         vficon_populate_at_new_size(vf, allocation.width, allocation.height, TRUE);
147 }
148
149 void vficon_pop_menu_refresh_cb(GtkWidget *, gpointer data)
150 {
151         auto vf = static_cast<ViewFile *>(data);
152
153         vf_refresh(vf);
154 }
155
156 void vficon_popup_destroy_cb(GtkWidget *, gpointer data)
157 {
158         auto vf = static_cast<ViewFile *>(data);
159         vficon_selection_remove(vf, VFICON(vf)->click_fd, SELECTION_PRELIGHT, nullptr);
160         VFICON(vf)->click_fd = nullptr;
161         vf->popup = nullptr;
162 }
163
164 /*
165  *-------------------------------------------------------------------
166  * signals
167  *-------------------------------------------------------------------
168  */
169
170 static void vficon_send_layout_select(ViewFile *vf, FileData *fd)
171 {
172         FileData *read_ahead_fd = nullptr;
173         FileData *sel_fd;
174         FileData *cur_fd;
175
176         if (!vf->layout || !fd) return;
177
178         sel_fd = fd;
179
180         cur_fd = layout_image_get_fd(vf->layout);
181         if (sel_fd == cur_fd) return; /* no change */
182
183         if (options->image.enable_read_ahead)
184                 {
185                 gint row;
186
187                 row = g_list_index(vf->list, fd);
188                 if (row > vficon_index_by_fd(vf, cur_fd) &&
189                     static_cast<guint>(row + 1) < vf_count(vf, nullptr))
190                         {
191                         read_ahead_fd = vf_index_get_data(vf, row + 1);
192                         }
193                 else if (row > 0)
194                         {
195                         read_ahead_fd = vf_index_get_data(vf, row - 1);
196                         }
197                 }
198
199         layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
200 }
201
202 static void vficon_toggle_filenames(ViewFile *vf)
203 {
204         GtkAllocation allocation;
205         VFICON(vf)->show_text = !VFICON(vf)->show_text;
206         options->show_icon_names = VFICON(vf)->show_text;
207
208         gtk_widget_get_allocation(vf->listview, &allocation);
209         vficon_populate_at_new_size(vf, allocation.width, allocation.height, TRUE);
210 }
211
212 static gint vficon_get_icon_width(ViewFile *vf)
213 {
214         gint width;
215
216         if (!VFICON(vf)->show_text && !vf->marks_enabled ) return options->thumbnails.max_width;
217
218         width = options->thumbnails.max_width + options->thumbnails.max_width / 2;
219         if (width < THUMB_MIN_ICON_WIDTH) width = THUMB_MIN_ICON_WIDTH;
220         if (width > THUMB_MAX_ICON_WIDTH) width = options->thumbnails.max_width;
221         if (vf->marks_enabled && width < THUMB_MIN_ICON_WIDTH_WITH_MARKS) width = THUMB_MIN_ICON_WIDTH_WITH_MARKS;
222
223         return width;
224 }
225
226 /*
227  *-------------------------------------------------------------------
228  * misc utils
229  *-------------------------------------------------------------------
230  */
231
232 static gboolean vficon_find_position(ViewFile *vf, FileData *fd, gint *row, gint *col)
233 {
234         gint n;
235
236         n = g_list_index(vf->list, fd);
237
238         if (n < 0) return FALSE;
239
240         *row = n / VFICON(vf)->columns;
241         *col = n - (*row * VFICON(vf)->columns);
242
243         return TRUE;
244 }
245
246 static gboolean vficon_find_iter(ViewFile *vf, FileData *fd, GtkTreeIter *iter, gint *column)
247 {
248         GtkTreeModel *store;
249         gint row;
250         gint col;
251
252         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
253         if (!vficon_find_position(vf, fd, &row, &col)) return FALSE;
254         if (!gtk_tree_model_iter_nth_child(store, iter, nullptr, row)) return FALSE;
255         if (column) *column = col;
256
257         return TRUE;
258 }
259
260 static FileData *vficon_find_data(ViewFile *vf, gint row, gint col, GtkTreeIter *iter)
261 {
262         GtkTreeModel *store;
263         GtkTreeIter p;
264
265         if (row < 0 || col < 0) return nullptr;
266
267         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
268         if (gtk_tree_model_iter_nth_child(store, &p, nullptr, row))
269                 {
270                 GList *list;
271
272                 gtk_tree_model_get(store, &p, FILE_COLUMN_POINTER, &list, -1);
273                 if (!list) return nullptr;
274
275                 if (iter) *iter = p;
276
277                 return static_cast<FileData *>(g_list_nth_data(list, col));
278                 }
279
280         return nullptr;
281 }
282
283 static FileData *vficon_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *iter)
284 {
285         GtkTreePath *tpath;
286         GtkTreeViewColumn *column;
287
288         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
289                                           &tpath, &column, nullptr, nullptr))
290                 {
291                 GtkTreeModel *store;
292                 GtkTreeIter row;
293                 GList *list;
294                 gint n;
295
296                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
297                 gtk_tree_model_get_iter(store, &row, tpath);
298                 gtk_tree_path_free(tpath);
299
300                 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &list, -1);
301
302                 n = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_number"));
303                 if (list)
304                         {
305                         if (iter) *iter = row;
306                         return static_cast<FileData *>(g_list_nth_data(list, n));
307                         }
308                 }
309
310         return nullptr;
311 }
312
313 static void vficon_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
314 {
315         auto vf = static_cast<ViewFile *>(data);
316         GtkTreeModel *store;
317         GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
318         GtkTreeIter row;
319         gint column;
320         GList *list;
321         guint toggled_mark;
322         FileData *fd;
323
324         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
325         if (!path || !gtk_tree_model_get_iter(store, &row, path)) return;
326
327         gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &list, -1);
328
329         column = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_number"));
330         g_object_get(G_OBJECT(cell), "toggled_mark", &toggled_mark, NULL);
331
332         fd = static_cast<FileData *>(g_list_nth_data(list, column));
333         if (fd)
334                 {
335                 file_data_set_mark(fd, toggled_mark, !file_data_get_mark(fd, toggled_mark));
336                 }
337 }
338
339
340 /*
341  *-------------------------------------------------------------------
342  * tooltip type window
343  *-------------------------------------------------------------------
344  */
345
346 static void tip_show(ViewFile *vf)
347 {
348         GtkWidget *label;
349         gint x;
350         gint y;
351         GdkDisplay *display;
352         GdkSeat *seat;
353         GdkDevice *device;
354
355         if (VFICON(vf)->tip_window) return;
356
357         seat = gdk_display_get_default_seat(gdk_window_get_display(gtk_tree_view_get_bin_window(GTK_TREE_VIEW(vf->listview))));
358         device = gdk_seat_get_pointer(seat);
359         gdk_window_get_device_position(gtk_tree_view_get_bin_window(GTK_TREE_VIEW(vf->listview)),
360                                                 device, &x, &y, nullptr);
361
362         VFICON(vf)->tip_fd = vficon_find_data_by_coord(vf, x, y, nullptr);
363         if (!VFICON(vf)->tip_fd) return;
364
365         VFICON(vf)->tip_window = gtk_window_new(GTK_WINDOW_POPUP);
366         gtk_window_set_resizable(GTK_WINDOW(VFICON(vf)->tip_window), FALSE);
367         gtk_container_set_border_width(GTK_CONTAINER(VFICON(vf)->tip_window), 2);
368
369         label = gtk_label_new(VFICON(vf)->tip_fd->name);
370
371         g_object_set_data(G_OBJECT(VFICON(vf)->tip_window), "tip_label", label);
372         gq_gtk_container_add(GTK_WIDGET(VFICON(vf)->tip_window), label);
373         gtk_widget_show(label);
374
375         display = gdk_display_get_default();
376         seat = gdk_display_get_default_seat(display);
377         device = gdk_seat_get_pointer(seat);
378         gdk_device_get_position(device, nullptr, &x, &y);
379
380         if (!gtk_widget_get_realized(VFICON(vf)->tip_window)) gtk_widget_realize(VFICON(vf)->tip_window);
381         gq_gtk_window_move(GTK_WINDOW(VFICON(vf)->tip_window), x + 16, y + 16);
382         gtk_widget_show(VFICON(vf)->tip_window);
383 }
384
385 static void tip_hide(ViewFile *vf)
386 {
387         if (VFICON(vf)->tip_window) gq_gtk_widget_destroy(VFICON(vf)->tip_window);
388         VFICON(vf)->tip_window = nullptr;
389 }
390
391 static gboolean tip_schedule_cb(gpointer data)
392 {
393         auto vf = static_cast<ViewFile *>(data);
394         GtkWidget *window;
395
396         if (!VFICON(vf)->tip_delay_id) return FALSE;
397
398         window = gtk_widget_get_toplevel(vf->listview);
399
400         if (gtk_widget_get_sensitive(window) &&
401             gtk_window_has_toplevel_focus(GTK_WINDOW(window)))
402                 {
403                 tip_show(vf);
404                 }
405
406         VFICON(vf)->tip_delay_id = 0;
407         return FALSE;
408 }
409
410 static void tip_schedule(ViewFile *vf)
411 {
412         tip_hide(vf);
413
414         if (VFICON(vf)->tip_delay_id)
415                 {
416                 g_source_remove(VFICON(vf)->tip_delay_id);
417                 VFICON(vf)->tip_delay_id = 0;
418                 }
419
420         if (!VFICON(vf)->show_text)
421                 {
422                 VFICON(vf)->tip_delay_id = g_timeout_add(VFICON_TIP_DELAY, tip_schedule_cb, vf);
423                 }
424 }
425
426 static void tip_unschedule(ViewFile *vf)
427 {
428         tip_hide(vf);
429
430         if (VFICON(vf)->tip_delay_id)
431                 {
432                 g_source_remove(VFICON(vf)->tip_delay_id);
433                 VFICON(vf)->tip_delay_id = 0;
434                 }
435 }
436
437 static void tip_update(ViewFile *vf, FileData *fd)
438 {
439         GdkDisplay *display = gdk_display_get_default();
440         GdkSeat *seat = gdk_display_get_default_seat(display);
441         GdkDevice *device = gdk_seat_get_pointer(seat);
442
443         if (VFICON(vf)->tip_window)
444                 {
445                 gint x;
446                 gint y;
447
448                 gdk_device_get_position(device, nullptr, &x, &y);
449
450                 gq_gtk_window_move(GTK_WINDOW(VFICON(vf)->tip_window), x + 16, y + 16);
451
452                 if (fd != VFICON(vf)->tip_fd)
453                         {
454                         GtkWidget *label;
455
456                         VFICON(vf)->tip_fd = fd;
457
458                         if (!VFICON(vf)->tip_fd)
459                                 {
460                                 tip_hide(vf);
461                                 tip_schedule(vf);
462                                 return;
463                                 }
464
465                         label = static_cast<GtkWidget *>(g_object_get_data(G_OBJECT(VFICON(vf)->tip_window), "tip_label"));
466                         gtk_label_set_text(GTK_LABEL(label), VFICON(vf)->tip_fd->name);
467                         }
468                 }
469         else
470                 {
471                 tip_schedule(vf);
472                 }
473 }
474
475 /*
476  *-------------------------------------------------------------------
477  * dnd
478  *-------------------------------------------------------------------
479  */
480
481 static void vficon_dnd_get(GtkWidget *, GdkDragContext *,
482                            GtkSelectionData *selection_data, guint,
483                            guint, gpointer data)
484 {
485         auto vf = static_cast<ViewFile *>(data);
486         GList *list = nullptr;
487
488         if (!VFICON(vf)->click_fd) return;
489
490         if (VFICON(vf)->click_fd->selected & SELECTION_SELECTED)
491                 {
492                 list = vf_selection_get_list(vf);
493                 }
494         else
495                 {
496                 list = g_list_append(nullptr, file_data_ref(VFICON(vf)->click_fd));
497                 }
498
499         if (!list) return;
500         uri_selection_data_set_uris_from_filelist(selection_data, list);
501         filelist_free(list);
502 }
503
504 static void vficon_drag_data_received(GtkWidget *, GdkDragContext *,
505                                       int x, int y, GtkSelectionData *selection,
506                                       guint info, guint, gpointer data)
507 {
508         auto vf = static_cast<ViewFile *>(data);
509
510         if (info == TARGET_TEXT_PLAIN) {
511                 FileData *fd = vficon_find_data_by_coord(vf, x, y, nullptr);
512
513                 if (fd) {
514                         /* Add keywords to file */
515                         auto str = reinterpret_cast<gchar *>(gtk_selection_data_get_text(selection));
516                         GList *kw_list = string_to_keywords_list(str);
517
518                         metadata_append_list(fd, KEYWORD_KEY, kw_list);
519                         g_list_free_full(kw_list, g_free);
520                         g_free(str);
521                 }
522         }
523 }
524
525 static void vficon_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
526 {
527         auto vf = static_cast<ViewFile *>(data);
528
529         tip_unschedule(vf);
530
531         if (VFICON(vf)->click_fd && VFICON(vf)->click_fd->thumb_pixbuf)
532                 {
533                 gint items;
534
535                 if (VFICON(vf)->click_fd->selected & SELECTION_SELECTED)
536                         items = g_list_length(VFICON(vf)->selection);
537                 else
538                         items = 1;
539
540                 dnd_set_drag_icon(widget, context, VFICON(vf)->click_fd->thumb_pixbuf, items);
541                 }
542 }
543
544 static void vficon_dnd_end(GtkWidget *, GdkDragContext *context, gpointer data)
545 {
546         auto vf = static_cast<ViewFile *>(data);
547
548         vficon_selection_remove(vf, VFICON(vf)->click_fd, SELECTION_PRELIGHT, nullptr);
549
550         if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
551                 {
552                 vf_refresh(vf);
553                 }
554
555         tip_unschedule(vf);
556 }
557
558 void vficon_dnd_init(ViewFile *vf)
559 {
560         gtk_drag_source_set(vf->listview, static_cast<GdkModifierType>(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK),
561                             dnd_file_drag_types, dnd_file_drag_types_count,
562                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
563         gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
564                             dnd_file_drag_types, dnd_file_drag_types_count,
565                             static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
566
567         g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
568                          G_CALLBACK(vficon_dnd_get), vf);
569         g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
570                          G_CALLBACK(vficon_dnd_begin), vf);
571         g_signal_connect(G_OBJECT(vf->listview), "drag_end",
572                          G_CALLBACK(vficon_dnd_end), vf);
573         g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
574                          G_CALLBACK(vficon_drag_data_received), vf);
575 }
576
577 /*
578  *-------------------------------------------------------------------
579  * cell updates
580  *-------------------------------------------------------------------
581  */
582
583 static void vficon_selection_set(ViewFile *vf, FileData *fd, SelectionType value, GtkTreeIter *iter)
584 {
585         GtkTreeModel *store;
586         GList *list;
587
588         if (!fd) return;
589
590         if (fd->selected == value) return;
591         fd->selected = value;
592
593         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
594         if (iter)
595                 {
596                 gtk_tree_model_get(store, iter, FILE_COLUMN_POINTER, &list, -1);
597                 if (list) gtk_list_store_set(GTK_LIST_STORE(store), iter, FILE_COLUMN_POINTER, list, -1);
598                 }
599         else
600                 {
601                 GtkTreeIter row;
602
603                 if (vficon_find_iter(vf, fd, &row, nullptr))
604                         {
605                         gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &list, -1);
606                         if (list) gtk_list_store_set(GTK_LIST_STORE(store), &row, FILE_COLUMN_POINTER, list, -1);
607                         }
608                 }
609 }
610
611 static void vficon_selection_add(ViewFile *vf, FileData *fd, SelectionType mask, GtkTreeIter *iter)
612 {
613         if (!fd) return;
614
615         vficon_selection_set(vf, fd, static_cast<SelectionType>(fd->selected | mask), iter);
616 }
617
618 static void vficon_selection_remove(ViewFile *vf, FileData *fd, SelectionType mask, GtkTreeIter *iter)
619 {
620         if (!fd) return;
621
622         vficon_selection_set(vf, fd, static_cast<SelectionType>(fd->selected & ~mask), iter);
623 }
624
625 void vficon_marks_set(ViewFile *vf, gint)
626 {
627         GtkAllocation allocation;
628         gtk_widget_get_allocation(vf->listview, &allocation);
629         vficon_populate_at_new_size(vf, allocation.width, allocation.height, TRUE);
630 }
631
632 void vficon_star_rating_set(ViewFile *vf, gint)
633 {
634         GtkAllocation allocation;
635         gtk_widget_get_allocation(vf->listview, &allocation);
636         vficon_populate_at_new_size(vf, allocation.width, allocation.height, TRUE);
637 }
638
639 /*
640  *-------------------------------------------------------------------
641  * selections
642  *-------------------------------------------------------------------
643  */
644
645 static void vficon_verify_selections(ViewFile *vf)
646 {
647         GList *work;
648
649         work = VFICON(vf)->selection;
650         while (work)
651                 {
652                 auto fd = static_cast<FileData *>(work->data);
653                 work = work->next;
654
655                 if (vficon_index_by_fd(vf, fd) >= 0) continue;
656
657                 VFICON(vf)->selection = g_list_remove(VFICON(vf)->selection, fd);
658                 }
659 }
660
661 void vficon_select_all(ViewFile *vf)
662 {
663         GList *work;
664
665         g_list_free(VFICON(vf)->selection);
666         VFICON(vf)->selection = nullptr;
667
668         work = vf->list;
669         while (work)
670                 {
671                 auto fd = static_cast<FileData *>(work->data);
672                 work = work->next;
673
674                 VFICON(vf)->selection = g_list_append(VFICON(vf)->selection, fd);
675                 vficon_selection_add(vf, fd, SELECTION_SELECTED, nullptr);
676                 }
677
678         vf_send_update(vf);
679 }
680
681 void vficon_select_none(ViewFile *vf)
682 {
683         GList *work;
684
685         work = VFICON(vf)->selection;
686         while (work)
687                 {
688                 auto fd = static_cast<FileData *>(work->data);
689                 work = work->next;
690
691                 vficon_selection_remove(vf, fd, SELECTION_SELECTED, nullptr);
692                 }
693
694         g_list_free(VFICON(vf)->selection);
695         VFICON(vf)->selection = nullptr;
696
697         vf_send_update(vf);
698 }
699
700 void vficon_select_invert(ViewFile *vf)
701 {
702         GList *work;
703
704         work = vf->list;
705         while (work)
706                 {
707                 auto fd = static_cast<FileData *>(work->data);
708                 work = work->next;
709
710                 if (fd->selected & SELECTION_SELECTED)
711                         {
712                         VFICON(vf)->selection = g_list_remove(VFICON(vf)->selection, fd);
713                         vficon_selection_remove(vf, fd, SELECTION_SELECTED, nullptr);
714                         }
715                 else
716                         {
717                         VFICON(vf)->selection = g_list_append(VFICON(vf)->selection, fd);
718                         vficon_selection_add(vf, fd, SELECTION_SELECTED, nullptr);
719                         }
720                 }
721
722         vf_send_update(vf);
723 }
724
725 static void vficon_select(ViewFile *vf, FileData *fd)
726 {
727         VFICON(vf)->prev_selection = fd;
728
729         if (!fd || fd->selected & SELECTION_SELECTED) return;
730
731         VFICON(vf)->selection = g_list_append(VFICON(vf)->selection, fd);
732         vficon_selection_add(vf, fd, SELECTION_SELECTED, nullptr);
733
734         vf_send_update(vf);
735 }
736
737 static void vficon_unselect(ViewFile *vf, FileData *fd)
738 {
739         VFICON(vf)->prev_selection = fd;
740
741         if (!fd || !(fd->selected & SELECTION_SELECTED) ) return;
742
743         VFICON(vf)->selection = g_list_remove(VFICON(vf)->selection, fd);
744         vficon_selection_remove(vf, fd, SELECTION_SELECTED, nullptr);
745
746         vf_send_update(vf);
747 }
748
749 static void vficon_select_util(ViewFile *vf, FileData *fd, gboolean select)
750 {
751         if (select)
752                 {
753                 vficon_select(vf, fd);
754                 }
755         else
756                 {
757                 vficon_unselect(vf, fd);
758                 }
759 }
760
761 static void vficon_select_region_util(ViewFile *vf, FileData *start, FileData *end, gboolean select)
762 {
763         gint row1;
764         gint col1;
765         gint row2;
766         gint col2;
767         gint i;
768         gint j;
769
770         if (!vficon_find_position(vf, start, &row1, &col1) ||
771             !vficon_find_position(vf, end, &row2, &col2) ) return;
772
773         VFICON(vf)->prev_selection = end;
774
775         if (!options->collections.rectangular_selection)
776                 {
777                 GList *work;
778
779                 if (g_list_index(vf->list, start) > g_list_index(vf->list, end))
780                         {
781                         std::swap(start, end);
782                         }
783
784                 work = g_list_find(vf->list, start);
785                 while (work)
786                         {
787                         auto fd = static_cast<FileData *>(work->data);
788                         vficon_select_util(vf, fd, select);
789
790                         if (work->data != end)
791                                 work = work->next;
792                         else
793                                 work = nullptr;
794                         }
795                 return;
796                 }
797
798         // rectangular_selection==true.
799         if (row2 < row1)
800                 {
801                 std::swap(row1, row2);
802                 }
803         if (col2 < col1)
804                 {
805                 std::swap(col1, col2);
806                 }
807
808         DEBUG_1("table: %d x %d to %d x %d", row1, col1, row2, col2);
809
810         for (i = row1; i <= row2; i++)
811                 {
812                 for (j = col1; j <= col2; j++)
813                         {
814                         FileData *fd = vficon_find_data(vf, i, j, nullptr);
815                         if (fd) vficon_select_util(vf, fd, select);
816                         }
817                 }
818 }
819
820 #pragma GCC diagnostic push
821 #pragma GCC diagnostic ignored "-Wunused-function"
822 gboolean vficon_index_is_selected_unused(ViewFile *vf, gint row)
823 {
824         auto *fd = static_cast<FileData *>(g_list_nth_data(vf->list, row));
825
826         if (!fd) return FALSE;
827
828         return (fd->selected & SELECTION_SELECTED);
829 }
830 #pragma GCC diagnostic pop
831
832 guint vficon_selection_count(ViewFile *vf, gint64 *bytes)
833 {
834         if (bytes)
835                 {
836                 gint64 b = 0;
837                 GList *work;
838
839                 work = VFICON(vf)->selection;
840                 while (work)
841                         {
842                         auto fd = static_cast<FileData *>(work->data);
843                         g_assert(fd->magick == FD_MAGICK);
844                         b += fd->size;
845
846                         work = work->next;
847                         }
848
849                 *bytes = b;
850                 }
851
852         return g_list_length(VFICON(vf)->selection);
853 }
854
855 GList *vficon_selection_get_list(ViewFile *vf)
856 {
857         GList *list = nullptr;
858
859         for (GList *work = g_list_last(VFICON(vf)->selection); work; work = work->prev)
860                 {
861                 auto fd = static_cast<FileData *>(work->data);
862                 g_assert(fd->magick == FD_MAGICK);
863
864                 list = g_list_concat(filelist_copy(fd->sidecar_files), list);
865                 list = g_list_prepend(list, file_data_ref(fd));
866                 }
867
868         return list;
869 }
870
871 GList *vficon_selection_get_list_by_index(ViewFile *vf)
872 {
873         GList *list = nullptr;
874         GList *work;
875
876         work = VFICON(vf)->selection;
877         while (work)
878                 {
879                 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, work->data)));
880                 work = work->next;
881                 }
882
883         return g_list_reverse(list);
884 }
885
886 void vficon_select_by_fd(ViewFile *vf, FileData *fd)
887 {
888         if (!fd) return;
889         if (!g_list_find(vf->list, fd)) return;
890
891         if (!(fd->selected & SELECTION_SELECTED))
892                 {
893                 vf_select_none(vf);
894                 vficon_select(vf, fd);
895                 }
896
897         vficon_set_focus(vf, fd);
898 }
899
900 void vficon_select_list(ViewFile *vf, GList *list)
901 {
902         GList *work;
903         FileData *fd;
904
905         if (!list) return;
906
907         work = list;
908         while (work)
909                 {
910                 fd = static_cast<FileData *>(work->data);
911                 if (g_list_find(vf->list, fd))
912                         {
913                         VFICON(vf)->selection = g_list_append(VFICON(vf)->selection, fd);
914                         vficon_selection_add(vf, fd, SELECTION_SELECTED, nullptr);
915                         }
916                 work = work->next;
917                 }
918 }
919
920 void vficon_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
921 {
922         GList *work;
923         gint n = mark - 1;
924
925         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
926
927         work = vf->list;
928         while (work)
929                 {
930                 auto fd = static_cast<FileData *>(work->data);
931                 gboolean mark_val;
932                 gboolean selected;
933
934                 g_assert(fd->magick == FD_MAGICK);
935
936                 mark_val = file_data_get_mark(fd, n);
937                 selected = fd->selected & SELECTION_SELECTED;
938
939                 switch (mode)
940                         {
941                         case MTS_MODE_SET: selected = mark_val;
942                                 break;
943                         case MTS_MODE_OR: selected = mark_val || selected;
944                                 break;
945                         case MTS_MODE_AND: selected = mark_val && selected;
946                                 break;
947                         case MTS_MODE_MINUS: selected = !mark_val && selected;
948                                 break;
949                         }
950
951                 vficon_select_util(vf, fd, selected);
952
953                 work = work->next;
954                 }
955 }
956
957 void vficon_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
958 {
959         GList *slist;
960         GList *work;
961         gint n = mark -1;
962
963         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
964
965         slist = vf_selection_get_list(vf);
966         work = slist;
967         while (work)
968                 {
969                 auto fd = static_cast<FileData *>(work->data);
970
971                 switch (mode)
972                         {
973                         case STM_MODE_SET: file_data_set_mark(fd, n, 1);
974                                 break;
975                         case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
976                                 break;
977                         case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
978                                 break;
979                         }
980                 work = work->next;
981                 }
982         filelist_free(slist);
983 }
984
985 static void vficon_select_closest(ViewFile *vf, FileData *sel_fd)
986 {
987         GList *work;
988         FileData *fd = nullptr;
989
990         if (sel_fd->parent) sel_fd = sel_fd->parent;
991         work = vf->list;
992
993         while (work)
994                 {
995                 gint match;
996
997                 fd = static_cast<FileData *>(work->data);
998                 work = work->next;
999
1000                 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1001
1002                 if (match >= 0) break;
1003                 }
1004
1005         if (fd)
1006                 {
1007                 vficon_select(vf, fd);
1008                 vficon_send_layout_select(vf, fd);
1009                 }
1010 }
1011
1012
1013 /*
1014  *-------------------------------------------------------------------
1015  * focus
1016  *-------------------------------------------------------------------
1017  */
1018
1019 static void vficon_move_focus(ViewFile *vf, gint row, gint col, gboolean relative)
1020 {
1021         gint new_row;
1022         gint new_col;
1023
1024         if (relative)
1025                 {
1026                 new_row = VFICON(vf)->focus_row;
1027                 new_col = VFICON(vf)->focus_column;
1028
1029                 new_row += row;
1030                 if (new_row < 0) new_row = 0;
1031                 if (new_row >= VFICON(vf)->rows) new_row = VFICON(vf)->rows - 1;
1032
1033                 while (col != 0)
1034                         {
1035                         if (col < 0)
1036                                 {
1037                                 new_col--;
1038                                 col++;
1039                                 }
1040                         else
1041                                 {
1042                                 new_col++;
1043                                 col--;
1044                                 }
1045
1046                         if (new_col < 0)
1047                                 {
1048                                 if (new_row > 0)
1049                                         {
1050                                         new_row--;
1051                                         new_col = VFICON(vf)->columns - 1;
1052                                         }
1053                                 else
1054                                         {
1055                                         new_col = 0;
1056                                         }
1057                                 }
1058                         if (new_col >= VFICON(vf)->columns)
1059                                 {
1060                                 if (new_row < VFICON(vf)->rows - 1)
1061                                         {
1062                                         new_row++;
1063                                         new_col = 0;
1064                                         }
1065                                 else
1066                                         {
1067                                         new_col = VFICON(vf)->columns - 1;
1068                                         }
1069                                 }
1070                         }
1071                 }
1072         else
1073                 {
1074                 new_row = row;
1075                 new_col = col;
1076
1077                 if (new_row >= VFICON(vf)->rows)
1078                         {
1079                         if (VFICON(vf)->rows > 0)
1080                                 new_row = VFICON(vf)->rows - 1;
1081                         else
1082                                 new_row = 0;
1083                         new_col = VFICON(vf)->columns - 1;
1084                         }
1085                 if (new_col >= VFICON(vf)->columns) new_col = VFICON(vf)->columns - 1;
1086                 }
1087
1088         if (new_row == VFICON(vf)->rows - 1)
1089                 {
1090                 gint l;
1091
1092                 /* if we moved beyond the last image, go to the last image */
1093
1094                 l = g_list_length(vf->list);
1095                 if (VFICON(vf)->rows > 1) l -= (VFICON(vf)->rows - 1) * VFICON(vf)->columns;
1096                 if (new_col >= l) new_col = l - 1;
1097                 }
1098
1099         vficon_set_focus(vf, vficon_find_data(vf, new_row, new_col, nullptr));
1100 }
1101
1102 static void vficon_set_focus(ViewFile *vf, FileData *fd)
1103 {
1104         GtkTreeIter iter;
1105         gint row;
1106         gint col;
1107
1108         if (g_list_find(vf->list, VFICON(vf)->focus_fd))
1109                 {
1110                 if (fd == VFICON(vf)->focus_fd)
1111                         {
1112                         /* ensure focus row col are correct */
1113                         vficon_find_position(vf, VFICON(vf)->focus_fd, &VFICON(vf)->focus_row, &VFICON(vf)->focus_column);
1114
1115 /** @FIXME Refer to issue #467 on Github. The thumbnail position is not
1116  * preserved when the icon view is refreshed. Caused by an unknown call from
1117  * the idle loop. This patch hides the problem.
1118  */
1119                         if (vficon_find_iter(vf, VFICON(vf)->focus_fd, &iter, nullptr))
1120                                 {
1121                                 tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, FALSE);
1122                                 }
1123
1124                         return;
1125                         }
1126                 vficon_selection_remove(vf, VFICON(vf)->focus_fd, SELECTION_FOCUS, nullptr);
1127                 }
1128
1129         if (!vficon_find_position(vf, fd, &row, &col))
1130                 {
1131                 VFICON(vf)->focus_fd = nullptr;
1132                 VFICON(vf)->focus_row = -1;
1133                 VFICON(vf)->focus_column = -1;
1134                 return;
1135                 }
1136
1137         VFICON(vf)->focus_fd = fd;
1138         VFICON(vf)->focus_row = row;
1139         VFICON(vf)->focus_column = col;
1140         vficon_selection_add(vf, VFICON(vf)->focus_fd, SELECTION_FOCUS, nullptr);
1141
1142         if (vficon_find_iter(vf, VFICON(vf)->focus_fd, &iter, nullptr))
1143                 {
1144                 GtkTreePath *tpath;
1145                 GtkTreeViewColumn *column;
1146                 GtkTreeModel *store;
1147
1148                 tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, FALSE);
1149
1150                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1151                 tpath = gtk_tree_model_get_path(store, &iter);
1152                 /* focus is set to an extra column with 0 width to hide focus, we draw it ourself */
1153                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(vf->listview), VFICON_MAX_COLUMNS);
1154                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, column, FALSE);
1155                 gtk_tree_path_free(tpath);
1156                 }
1157 }
1158
1159 /* used to figure the page up/down distances */
1160 static gint page_height(ViewFile *vf)
1161 {
1162         GtkAdjustment *adj;
1163         gint page_size;
1164         gint row_height;
1165         gint ret;
1166
1167         adj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vf->listview));
1168         page_size = static_cast<gint>(gtk_adjustment_get_page_increment(adj));
1169
1170         row_height = options->thumbnails.max_height + THUMB_BORDER_PADDING * 2;
1171         if (VFICON(vf)->show_text) row_height += options->thumbnails.max_height / 3;
1172
1173         ret = page_size / row_height;
1174         if (ret < 1) ret = 1;
1175
1176         return ret;
1177 }
1178
1179 /*
1180  *-------------------------------------------------------------------
1181  * keyboard
1182  *-------------------------------------------------------------------
1183  */
1184
1185 gboolean vficon_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1186 {
1187         auto vf = static_cast<ViewFile *>(data);
1188         gint focus_row = 0;
1189         gint focus_col = 0;
1190         FileData *fd;
1191         gboolean stop_signal;
1192
1193         stop_signal = TRUE;
1194         switch (event->keyval)
1195                 {
1196                 case GDK_KEY_Left: case GDK_KEY_KP_Left:
1197                         focus_col = -1;
1198                         break;
1199                 case GDK_KEY_Right: case GDK_KEY_KP_Right:
1200                         focus_col = 1;
1201                         break;
1202                 case GDK_KEY_Up: case GDK_KEY_KP_Up:
1203                         focus_row = -1;
1204                         break;
1205                 case GDK_KEY_Down: case GDK_KEY_KP_Down:
1206                         focus_row = 1;
1207                         break;
1208                 case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up:
1209                         focus_row = -page_height(vf);
1210                         break;
1211                 case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down:
1212                         focus_row = page_height(vf);
1213                         break;
1214                 case GDK_KEY_Home: case GDK_KEY_KP_Home:
1215                         focus_row = -VFICON(vf)->focus_row;
1216                         focus_col = -VFICON(vf)->focus_column;
1217                         break;
1218                 case GDK_KEY_End: case GDK_KEY_KP_End:
1219                         focus_row = VFICON(vf)->rows - 1 - VFICON(vf)->focus_row;
1220                         focus_col = VFICON(vf)->columns - 1 - VFICON(vf)->focus_column;
1221                         break;
1222                 case GDK_KEY_space:
1223                         fd = vficon_find_data(vf, VFICON(vf)->focus_row, VFICON(vf)->focus_column, nullptr);
1224                         if (fd)
1225                                 {
1226                                 VFICON(vf)->click_fd = fd;
1227                                 if (event->state & GDK_CONTROL_MASK)
1228                                         {
1229                                         gint selected;
1230
1231                                         selected = fd->selected & SELECTION_SELECTED;
1232                                         if (selected)
1233                                                 {
1234                                                 vficon_unselect(vf, fd);
1235                                                 }
1236                                         else
1237                                                 {
1238                                                 vficon_select(vf, fd);
1239                                                 vficon_send_layout_select(vf, fd);
1240                                                 }
1241                                         }
1242                                 else
1243                                         {
1244                                         vf_select_none(vf);
1245                                         vficon_select(vf, fd);
1246                                         vficon_send_layout_select(vf, fd);
1247                                         }
1248                                 }
1249                         break;
1250                 case GDK_KEY_Menu:
1251                         fd = vficon_find_data(vf, VFICON(vf)->focus_row, VFICON(vf)->focus_column, nullptr);
1252                         VFICON(vf)->click_fd = fd;
1253
1254                         vficon_selection_add(vf, VFICON(vf)->click_fd, SELECTION_PRELIGHT, nullptr);
1255                         tip_unschedule(vf);
1256
1257                         vf->popup = vf_pop_menu(vf);
1258                         gtk_menu_popup_at_widget(GTK_MENU(vf->popup), widget, GDK_GRAVITY_EAST, GDK_GRAVITY_CENTER, nullptr);
1259                         break;
1260                 default:
1261                         stop_signal = FALSE;
1262                         break;
1263                 }
1264
1265         if (focus_row != 0 || focus_col != 0)
1266                 {
1267                 FileData *new_fd;
1268                 FileData *old_fd;
1269
1270                 old_fd = vficon_find_data(vf, VFICON(vf)->focus_row, VFICON(vf)->focus_column, nullptr);
1271                 vficon_move_focus(vf, focus_row, focus_col, TRUE);
1272                 new_fd = vficon_find_data(vf, VFICON(vf)->focus_row, VFICON(vf)->focus_column, nullptr);
1273
1274                 if (new_fd != old_fd)
1275                         {
1276                         if (event->state & GDK_SHIFT_MASK)
1277                                 {
1278                                 if (!options->collections.rectangular_selection)
1279                                         {
1280                                         vficon_select_region_util(vf, old_fd, new_fd, FALSE);
1281                                         }
1282                                 else
1283                                         {
1284                                         vficon_select_region_util(vf, VFICON(vf)->click_fd, old_fd, FALSE);
1285                                         }
1286                                 vficon_select_region_util(vf, VFICON(vf)->click_fd, new_fd, TRUE);
1287                                 vficon_send_layout_select(vf, new_fd);
1288                                 }
1289                         else if (event->state & GDK_CONTROL_MASK)
1290                                 {
1291                                 VFICON(vf)->click_fd = new_fd;
1292                                 }
1293                         else
1294                                 {
1295                                 VFICON(vf)->click_fd = new_fd;
1296                                 vf_select_none(vf);
1297                                 vficon_select(vf, new_fd);
1298                                 vficon_send_layout_select(vf, new_fd);
1299                                 }
1300                         }
1301                 }
1302
1303         if (stop_signal)
1304                 {
1305                 tip_unschedule(vf);
1306                 }
1307
1308         return stop_signal;
1309 }
1310
1311 /*
1312  *-------------------------------------------------------------------
1313  * mouse
1314  *-------------------------------------------------------------------
1315  */
1316
1317 static gboolean vficon_motion_cb(GtkWidget *, GdkEventMotion *event, gpointer data)
1318 {
1319         auto vf = static_cast<ViewFile *>(data);
1320         FileData *fd;
1321
1322         fd = vficon_find_data_by_coord(vf, static_cast<gint>(event->x), static_cast<gint>(event->y), nullptr);
1323         tip_update(vf, fd);
1324
1325         return FALSE;
1326 }
1327
1328 gboolean vficon_press_cb(GtkWidget *, GdkEventButton *bevent, gpointer data)
1329 {
1330         auto vf = static_cast<ViewFile *>(data);
1331         GtkTreeIter iter;
1332         FileData *fd;
1333
1334         tip_unschedule(vf);
1335
1336         fd = vficon_find_data_by_coord(vf, static_cast<gint>(bevent->x), static_cast<gint>(bevent->y), &iter);
1337
1338         if (fd)
1339                 {
1340                 VFICON(vf)->click_fd = fd;
1341                 vficon_selection_add(vf, VFICON(vf)->click_fd, SELECTION_PRELIGHT, &iter);
1342
1343                 switch (bevent->button)
1344                         {
1345                         case MOUSE_BUTTON_LEFT:
1346                                 if (!gtk_widget_has_focus(vf->listview))
1347                                         {
1348                                         gtk_widget_grab_focus(vf->listview);
1349                                         }
1350
1351                                 if (bevent->type == GDK_2BUTTON_PRESS && vf->layout)
1352                                         {
1353                                         if (VFICON(vf)->click_fd->format_class == FORMAT_CLASS_COLLECTION)
1354                                                 {
1355                                                 collection_window_new(VFICON(vf)->click_fd->path);
1356                                                 }
1357                                         else
1358                                                 {
1359                                                 vficon_selection_remove(vf, VFICON(vf)->click_fd, SELECTION_PRELIGHT, &iter);
1360                                                 layout_image_full_screen_start(vf->layout);
1361                                                 }
1362                                         }
1363                                 break;
1364                         case MOUSE_BUTTON_RIGHT:
1365                                 vf->popup = vf_pop_menu(vf);
1366                                 gtk_menu_popup_at_pointer(GTK_MENU(vf->popup), nullptr);
1367                                 break;
1368                         default:
1369                                 break;
1370                         }
1371                 }
1372
1373         return FALSE;
1374 }
1375
1376 gboolean vficon_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1377 {
1378         auto vf = static_cast<ViewFile *>(data);
1379         GtkTreeIter iter;
1380         FileData *fd = nullptr;
1381         gboolean was_selected;
1382
1383         tip_schedule(vf);
1384
1385         if (defined_mouse_buttons(widget, bevent, vf->layout))
1386                 {
1387                 return TRUE;
1388                 }
1389
1390         if (static_cast<gint>(bevent->x) != 0 || static_cast<gint>(bevent->y) != 0)
1391                 {
1392                 fd = vficon_find_data_by_coord(vf, static_cast<gint>(bevent->x), static_cast<gint>(bevent->y), &iter);
1393                 }
1394
1395         if (VFICON(vf)->click_fd)
1396                 {
1397                 vficon_selection_remove(vf, VFICON(vf)->click_fd, SELECTION_PRELIGHT, nullptr);
1398                 }
1399
1400         if (!fd || VFICON(vf)->click_fd != fd) return TRUE;
1401
1402         was_selected = !!(fd->selected & SELECTION_SELECTED);
1403
1404         switch (bevent->button)
1405                 {
1406                 case MOUSE_BUTTON_LEFT:
1407                         {
1408                         vficon_set_focus(vf, fd);
1409
1410                         if (bevent->state & GDK_CONTROL_MASK)
1411                                 {
1412                                 gboolean select;
1413
1414                                 select = !(fd->selected & SELECTION_SELECTED);
1415                                 if ((bevent->state & GDK_SHIFT_MASK) && VFICON(vf)->prev_selection)
1416                                         {
1417                                         vficon_select_region_util(vf, VFICON(vf)->prev_selection, fd, select);
1418                                         }
1419                                 else
1420                                         {
1421                                         vficon_select_util(vf, fd, select);
1422                                         }
1423                                 }
1424                         else
1425                                 {
1426                                 vf_select_none(vf);
1427
1428                                 if ((bevent->state & GDK_SHIFT_MASK) && VFICON(vf)->prev_selection)
1429                                         {
1430                                         vficon_select_region_util(vf, VFICON(vf)->prev_selection, fd, TRUE);
1431                                         }
1432                                 else
1433                                         {
1434                                         vficon_select_util(vf, fd, TRUE);
1435                                         was_selected = FALSE;
1436                                         }
1437                                 }
1438                         }
1439                         break;
1440                 case MOUSE_BUTTON_MIDDLE:
1441                         {
1442                         vficon_select_util(vf, fd, !(fd->selected & SELECTION_SELECTED));
1443                         }
1444                         break;
1445                 default:
1446                         break;
1447                 }
1448
1449         if (!was_selected && (fd->selected & SELECTION_SELECTED))
1450                 {
1451                 vficon_send_layout_select(vf, fd);
1452                 }
1453
1454         return TRUE;
1455 }
1456
1457 static gboolean vficon_leave_cb(GtkWidget *, GdkEventCrossing *, gpointer data)
1458 {
1459         auto vf = static_cast<ViewFile *>(data);
1460
1461         tip_unschedule(vf);
1462         return FALSE;
1463 }
1464
1465 /*
1466  *-------------------------------------------------------------------
1467  * population
1468  *-------------------------------------------------------------------
1469  */
1470
1471 static gboolean vficon_destroy_node_cb(GtkTreeModel *store, GtkTreePath *, GtkTreeIter *iter, gpointer)
1472 {
1473         GList *list;
1474
1475         gtk_tree_model_get(store, iter, FILE_COLUMN_POINTER, &list, -1);
1476
1477         /* it seems that gtk_list_store_clear may call some callbacks
1478            that use the column. Set the pointer to NULL to be safe. */
1479         gtk_list_store_set(GTK_LIST_STORE(store), iter, FILE_COLUMN_POINTER, NULL, -1);
1480         g_list_free(list);
1481
1482         return FALSE;
1483 }
1484
1485 static void vficon_clear_store(ViewFile *vf)
1486 {
1487         GtkTreeModel *store;
1488
1489         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1490         gtk_tree_model_foreach(store, vficon_destroy_node_cb, nullptr);
1491
1492         gtk_list_store_clear(GTK_LIST_STORE(store));
1493 }
1494
1495 static GList *vficon_add_row(ViewFile *vf, GtkTreeIter *iter)
1496 {
1497         GtkListStore *store;
1498         GList *list = nullptr;
1499         gint i;
1500
1501         for (i = 0; i < VFICON(vf)->columns; i++) list = g_list_prepend(list, nullptr);
1502
1503         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1504         gtk_list_store_append(store, iter);
1505         gtk_list_store_set(store, iter, FILE_COLUMN_POINTER, list, -1);
1506
1507         return list;
1508 }
1509
1510 static void vficon_populate(ViewFile *vf, gboolean resize, gboolean keep_position)
1511 {
1512         GtkTreeModel *store;
1513         GtkTreePath *tpath;
1514         GList *work;
1515         FileData *visible_fd = nullptr;
1516         gint r;
1517         gboolean valid;
1518         GtkTreeIter iter;
1519
1520         vficon_verify_selections(vf);
1521
1522         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1523
1524         if (keep_position && gtk_widget_get_realized(vf->listview) &&
1525             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1526                 {
1527                 GtkTreeIter iter;
1528                 GList *list;
1529
1530                 gtk_tree_model_get_iter(store, &iter, tpath);
1531                 gtk_tree_path_free(tpath);
1532
1533                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1534                 if (list) visible_fd = static_cast<FileData *>(list->data);
1535                 }
1536
1537
1538         if (resize)
1539                 {
1540                 gint i;
1541                 gint thumb_width;
1542
1543                 vficon_clear_store(vf);
1544
1545                 thumb_width = vficon_get_icon_width(vf);
1546
1547                 for (i = 0; i < VFICON_MAX_COLUMNS; i++)
1548                         {
1549                         GtkTreeViewColumn *column;
1550                         GtkCellRenderer *cell;
1551                         GList *list;
1552
1553                         column = gtk_tree_view_get_column(GTK_TREE_VIEW(vf->listview), i);
1554                         gtk_tree_view_column_set_visible(column, (i < VFICON(vf)->columns));
1555                         gtk_tree_view_column_set_fixed_width(column, thumb_width + (THUMB_BORDER_PADDING * 6));
1556
1557                         list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1558                         cell = static_cast<GtkCellRenderer *>((list) ? list->data : nullptr);
1559                         g_list_free(list);
1560
1561                         if (cell && GQV_IS_CELL_RENDERER_ICON(cell))
1562                                 {
1563                                 g_object_set(G_OBJECT(cell), "fixed_width", thumb_width,
1564                                                              "fixed_height", options->thumbnails.max_height,
1565                                                              "show_text", VFICON(vf)->show_text || options->show_star_rating,
1566                                                              "show_marks", vf->marks_enabled,
1567                                                              "num_marks", FILEDATA_MARKS_SIZE,
1568                                                              NULL);
1569                                 }
1570                         }
1571                 if (gtk_widget_get_realized(vf->listview)) gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
1572                 }
1573
1574         r = -1;
1575
1576         valid = gtk_tree_model_iter_children(store, &iter, nullptr);
1577
1578         work = vf->list;
1579         while (work)
1580                 {
1581                 GList *list;
1582                 r++;
1583                 if (valid)
1584                         {
1585                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1586                         gtk_list_store_set(GTK_LIST_STORE(store), &iter, FILE_COLUMN_POINTER, list, -1);
1587                         }
1588                 else
1589                         {
1590                         list = vficon_add_row(vf, &iter);
1591                         }
1592
1593                 while (list)
1594                         {
1595                         FileData *fd;
1596
1597                         if (work)
1598                                 {
1599                                 fd = static_cast<FileData *>(work->data);
1600                                 work = work->next;
1601                                 }
1602                         else
1603                                 {
1604                                 fd = nullptr;
1605                                 }
1606
1607                         list->data = fd;
1608                         list = list->next;
1609                         }
1610                 if (valid) valid = gtk_tree_model_iter_next(store, &iter);
1611                 }
1612
1613         r++;
1614         while (valid)
1615                 {
1616                 GList *list;
1617
1618                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1619                 valid = gtk_list_store_remove(GTK_LIST_STORE(store), &iter);
1620                 g_list_free(list);
1621                 }
1622
1623         VFICON(vf)->rows = r;
1624
1625         if (visible_fd &&
1626             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1627                 {
1628                 GtkTreeIter iter;
1629                 GList *list;
1630
1631                 gtk_tree_model_get_iter(store, &iter, tpath);
1632                 gtk_tree_path_free(tpath);
1633
1634                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1635                 if (g_list_find(list, visible_fd) == nullptr &&
1636                     vficon_find_iter(vf, visible_fd, &iter, nullptr))
1637                         {
1638                         tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, FALSE);
1639                         }
1640                 }
1641
1642
1643         vf_send_update(vf);
1644         vf_thumb_update(vf);
1645         vf_star_update(vf);
1646 }
1647
1648 static void vficon_populate_at_new_size(ViewFile *vf, gint w, gint, gboolean force)
1649 {
1650         gint new_cols;
1651         gint thumb_width;
1652
1653         thumb_width = vficon_get_icon_width(vf);
1654
1655         new_cols = w / (thumb_width + (THUMB_BORDER_PADDING * 6));
1656         if (new_cols < 1) new_cols = 1;
1657
1658         if (!force && new_cols == VFICON(vf)->columns) return;
1659
1660         VFICON(vf)->columns = new_cols;
1661
1662         vficon_populate(vf, TRUE, TRUE);
1663
1664         DEBUG_1("col tab pop cols=%d rows=%d", VFICON(vf)->columns, VFICON(vf)->rows);
1665 }
1666
1667 static void vficon_sized_cb(GtkWidget *, GtkAllocation *allocation, gpointer data)
1668 {
1669         auto vf = static_cast<ViewFile *>(data);
1670
1671         vficon_populate_at_new_size(vf, allocation->width, allocation->height, FALSE);
1672 }
1673
1674 /*
1675  *-----------------------------------------------------------------------------
1676  * misc
1677  *-----------------------------------------------------------------------------
1678  */
1679
1680 void vficon_sort_set(ViewFile *vf, SortType type, gboolean ascend, gboolean case_sensitive)
1681 {
1682         if (vf->sort_method == type && vf->sort_ascend == ascend && vf->sort_case == case_sensitive) return;
1683
1684         vf->sort_method = type;
1685         vf->sort_ascend = ascend;
1686         vf->sort_case = case_sensitive;
1687
1688         if (!vf->list) return;
1689
1690         vf_refresh(vf);
1691 }
1692
1693 /*
1694  *-----------------------------------------------------------------------------
1695  * thumb updates
1696  *-----------------------------------------------------------------------------
1697  */
1698
1699 void vficon_thumb_progress_count(GList *list, gint *count, gint *done)
1700 {
1701         GList *work = list;
1702         while (work)
1703                 {
1704                 auto fd = static_cast<FileData *>(work->data);
1705                 work = work->next;
1706
1707                 if (fd->thumb_pixbuf) (*done)++;
1708                 (*count)++;
1709                 }
1710 }
1711
1712 void vficon_read_metadata_progress_count(GList *list, gint *count, gint *done)
1713 {
1714         GList *work = list;
1715         while (work)
1716                 {
1717                 auto fd = static_cast<FileData *>(work->data);
1718                 work = work->next;
1719
1720                 if (fd->metadata_in_idle_loaded) (*done)++;
1721                 (*count)++;
1722                 }
1723 }
1724
1725 void vficon_set_thumb_fd(ViewFile *vf, FileData *fd)
1726 {
1727         GtkTreeModel *store;
1728         GtkTreeIter iter;
1729         GList *list;
1730
1731         if (!g_list_find(vf->list, fd)) return;
1732         if (!vficon_find_iter(vf, fd, &iter, nullptr)) return;
1733
1734         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1735
1736         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1737         gtk_list_store_set(GTK_LIST_STORE(store), &iter, FILE_COLUMN_POINTER, list, -1);
1738 }
1739
1740 /* Returns the next fd without a loaded pixbuf, so the thumb-loader can load the pixbuf for it. */
1741 FileData *vficon_thumb_next_fd(ViewFile *vf)
1742 {
1743         GtkTreePath *tpath;
1744
1745         /* First see if there are visible files that don't have a loaded thumb... */
1746         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1747                 {
1748                 GtkTreeModel *store;
1749                 GtkTreeIter iter;
1750                 gboolean valid = TRUE;
1751
1752                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1753                 gtk_tree_model_get_iter(store, &iter, tpath);
1754                 gtk_tree_path_free(tpath);
1755                 tpath = nullptr;
1756
1757                 while (valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1758                         {
1759                         GList *list;
1760                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1761
1762                         /** @todo (xsdg): for loop here. */
1763                         for (; list; list = list->next)
1764                                 {
1765                                 auto fd = static_cast<FileData *>(list->data);
1766                                 if (fd && !fd->thumb_pixbuf) return fd;
1767                                 }
1768
1769                         valid = gtk_tree_model_iter_next(store, &iter);
1770                         }
1771                 }
1772
1773         /* Then iterate through the entire list to load all of them. */
1774         GList *work;
1775         for (work = vf->list; work; work = work->next)
1776                 {
1777                 auto fd = static_cast<FileData *>(work->data);
1778
1779                 // Note: This implementation differs from view-file-list.cc because sidecar files are not
1780                 // distinct list elements here, as they are in the list view.
1781                 if (!fd->thumb_pixbuf) return fd;
1782                 }
1783
1784         return nullptr;
1785 }
1786
1787 void vficon_set_star_fd(ViewFile *vf, FileData *fd)
1788 {
1789         GtkTreeModel *store;
1790         GtkTreeIter iter;
1791         GList *list;
1792
1793         if (!g_list_find(vf->list, fd)) return;
1794         if (!vficon_find_iter(vf, fd, &iter, nullptr)) return;
1795
1796         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1797
1798         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1799         gtk_list_store_set(GTK_LIST_STORE(store), &iter, FILE_COLUMN_POINTER, list, -1);
1800 }
1801
1802 FileData *vficon_star_next_fd(ViewFile *vf)
1803 {
1804         GtkTreePath *tpath;
1805
1806         /* first check the visible files */
1807
1808         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, nullptr, nullptr, nullptr))
1809                 {
1810                 GtkTreeModel *store;
1811                 GtkTreeIter iter;
1812                 gboolean valid = TRUE;
1813
1814                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1815                 gtk_tree_model_get_iter(store, &iter, tpath);
1816                 gtk_tree_path_free(tpath);
1817                 tpath = nullptr;
1818
1819                 while (valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1820                         {
1821                         GList *list;
1822                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1823
1824                         for (; list; list = list->next)
1825                                 {
1826                                 auto fd = static_cast<FileData *>(list->data);
1827                                 if (fd && fd->rating == STAR_RATING_NOT_READ)
1828                                         {
1829                                         vf->stars_filedata = fd;
1830
1831                                         if (vf->stars_id == 0)
1832                                                 {
1833                                                 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1834                                                 }
1835
1836                                         return fd;
1837                                         }
1838                                 }
1839
1840                         valid = gtk_tree_model_iter_next(store, &iter);
1841                         }
1842                 }
1843
1844         /* Then iterate through the entire list to load all of them. */
1845
1846         GList *work;
1847         for (work = vf->list; work; work = work->next)
1848                 {
1849                 auto fd = static_cast<FileData *>(work->data);
1850
1851                 if (fd && fd->rating == STAR_RATING_NOT_READ)
1852                         {
1853                         vf->stars_filedata = fd;
1854
1855                         if (vf->stars_id == 0)
1856                                 {
1857                                 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, nullptr);
1858                                 }
1859
1860                         return fd;
1861                         }
1862                 }
1863
1864         return nullptr;
1865 }
1866
1867 /*
1868  *-----------------------------------------------------------------------------
1869  * row stuff
1870  *-----------------------------------------------------------------------------
1871  */
1872
1873 gint vficon_index_by_fd(ViewFile *vf, FileData *in_fd)
1874 {
1875         gint p = 0;
1876         GList *work;
1877
1878         if (!in_fd) return -1;
1879
1880         work = vf->list;
1881         while (work)
1882                 {
1883                 auto fd = static_cast<FileData *>(work->data);
1884                 if (fd == in_fd) return p;
1885                 work = work->next;
1886                 p++;
1887                 }
1888
1889         return -1;
1890 }
1891
1892 /*
1893  *-----------------------------------------------------------------------------
1894  *
1895  *-----------------------------------------------------------------------------
1896  */
1897
1898 static gboolean vficon_refresh_real(ViewFile *vf, gboolean keep_position)
1899 {
1900         gboolean ret = TRUE;
1901         GList *work;
1902         GList *new_work;
1903         FileData *first_selected = nullptr;
1904         GList *new_filelist = nullptr;
1905         GList *new_fd_list = nullptr;
1906         GList *old_selected = nullptr;
1907         GtkTreePath *end_path = nullptr;
1908         GtkTreePath *start_path = nullptr;
1909
1910         gtk_tree_view_get_visible_range(GTK_TREE_VIEW(vf->listview), &start_path, &end_path);
1911
1912         if (vf->dir_fd)
1913                 {
1914                 ret = filelist_read(vf->dir_fd, &new_filelist, nullptr);
1915                 new_filelist = file_data_filter_marks_list(new_filelist, vf_marks_get_filter(vf));
1916                 new_filelist = g_list_first(new_filelist);
1917                 new_filelist = file_data_filter_file_filter_list(new_filelist, vf_file_filter_get_filter(vf));
1918
1919                 new_filelist = g_list_first(new_filelist);
1920                 new_filelist = file_data_filter_class_list(new_filelist, vf_class_get_filter(vf));
1921
1922                 }
1923
1924         vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend, vf->sort_case); /* the list might not be sorted if there were renames */
1925         new_filelist = filelist_sort(new_filelist, vf->sort_method, vf->sort_ascend, vf->sort_case);
1926
1927         if (VFICON(vf)->selection)
1928                 {
1929                 old_selected = g_list_copy(VFICON(vf)->selection);
1930                 first_selected = static_cast<FileData *>(VFICON(vf)->selection->data);
1931                 file_data_ref(first_selected);
1932                 g_list_free(VFICON(vf)->selection);
1933                 VFICON(vf)->selection = nullptr;
1934                 }
1935
1936         /* iterate old list and new list, looking for differences */
1937         work = vf->list;
1938         new_work = new_filelist;
1939         while (work || new_work)
1940                 {
1941                 FileData *fd = nullptr;
1942                 FileData *new_fd = nullptr;
1943                 gint match;
1944
1945                 if (work && new_work)
1946                         {
1947                         fd = static_cast<FileData *>(work->data);
1948                         new_fd = static_cast<FileData *>(new_work->data);
1949
1950                         if (fd == new_fd)
1951                                 {
1952                                 /* not changed, go to next */
1953                                 work = work->next;
1954                                 new_work = new_work->next;
1955                                 if (fd->selected & SELECTION_SELECTED)
1956                                         {
1957                                         VFICON(vf)->selection = g_list_prepend(VFICON(vf)->selection, fd);
1958                                         }
1959                                 continue;
1960                                 }
1961
1962                         match = filelist_sort_compare_filedata_full(fd, new_fd, vf->sort_method, vf->sort_ascend);
1963                         if (match == 0) g_warning("multiple fd for the same path");
1964                         }
1965                 else if (work)
1966                         {
1967                         /* old item was deleted */
1968                         fd = static_cast<FileData *>(work->data);
1969                         match = -1;
1970                         }
1971                 else
1972                         {
1973                         /* new item was added */
1974                         new_fd = static_cast<FileData *>(new_work->data);
1975                         match = 1;
1976                         }
1977
1978                 if (match < 0)
1979                         {
1980                         /* file no longer exists, delete from vf->list */
1981                         GList *to_delete = work;
1982                         work = work->next;
1983                         if (fd == VFICON(vf)->prev_selection) VFICON(vf)->prev_selection = nullptr;
1984                         if (fd == VFICON(vf)->click_fd) VFICON(vf)->click_fd = nullptr;
1985                         file_data_unref(fd);
1986                         vf->list = g_list_delete_link(vf->list, to_delete);
1987                         }
1988                 else
1989                         {
1990                         /* new file, add to vf->list */
1991                         file_data_ref(new_fd);
1992                         new_fd->selected = SELECTION_NONE;
1993                         if (work)
1994                                 {
1995                                 vf->list = g_list_insert_before(vf->list, work, new_fd);
1996                                 }
1997                         else
1998                                 {
1999                                 /* it is faster to append all new entries together later */
2000                                 new_fd_list = g_list_prepend(new_fd_list, new_fd);
2001                                 }
2002
2003                         new_work = new_work->next;
2004                         }
2005                 }
2006
2007         if (new_fd_list)
2008                 {
2009                 vf->list = g_list_concat(vf->list, g_list_reverse(new_fd_list));
2010                 }
2011
2012         VFICON(vf)->selection = g_list_reverse(VFICON(vf)->selection);
2013
2014         /* Preserve the original selection order */
2015         if (old_selected)
2016                 {
2017                 GList *reversed_old_selected;
2018
2019                 reversed_old_selected = g_list_reverse(old_selected);
2020                 while (reversed_old_selected)
2021                         {
2022                         GList *tmp;
2023                         tmp = g_list_find(VFICON(vf)->selection, reversed_old_selected->data);
2024                         if (tmp)
2025                                 {
2026                                 VFICON(vf)->selection = g_list_remove_link(VFICON(vf)->selection, tmp);
2027                                 VFICON(vf)->selection = g_list_concat(tmp, VFICON(vf)->selection);
2028                                 }
2029                         reversed_old_selected = reversed_old_selected->next;
2030                         }
2031                 g_list_free(old_selected);
2032                 }
2033
2034         filelist_free(new_filelist);
2035
2036         vficon_populate(vf, TRUE, keep_position);
2037
2038         if (first_selected && !VFICON(vf)->selection)
2039                 {
2040                 /* all selected files disappeared */
2041                 vficon_select_closest(vf, first_selected);
2042                 }
2043         file_data_unref(first_selected);
2044
2045         if (start_path)
2046                 {
2047                 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(vf->listview), start_path, nullptr, FALSE, 0.0, 0.0);
2048                 }
2049
2050         gtk_tree_path_free(start_path);
2051         gtk_tree_path_free(end_path);
2052
2053         return ret;
2054 }
2055
2056 gboolean vficon_refresh(ViewFile *vf)
2057 {
2058         return vficon_refresh_real(vf, TRUE);
2059 }
2060
2061 /*
2062  *-----------------------------------------------------------------------------
2063  * draw, etc.
2064  *-----------------------------------------------------------------------------
2065  */
2066
2067 static void vficon_cell_data_cb(GtkTreeViewColumn *, GtkCellRenderer *cell,
2068                                 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
2069 {
2070         auto cd = static_cast<ColumnData *>(data);
2071         FileData *fd;
2072         gchar *star_rating;
2073         GList *list;
2074         ViewFile *vf = cd->vf;
2075
2076         if (!GQV_IS_CELL_RENDERER_ICON(cell)) return;
2077
2078         gtk_tree_model_get(tree_model, iter, FILE_COLUMN_POINTER, &list, -1);
2079
2080         fd = static_cast<FileData *>(g_list_nth_data(list, cd->number));
2081
2082         if (fd)
2083                 {
2084                 const gchar *link;
2085                 gchar *name_sidecars = nullptr;
2086                 GdkRGBA color_bg;
2087                 GdkRGBA color_fg;
2088                 GtkStateType state = GTK_STATE_NORMAL;
2089                 GtkStyle *style;
2090
2091                 g_assert(fd->magick == FD_MAGICK);
2092
2093                 if (options->show_star_rating && fd->rating != STAR_RATING_NOT_READ)
2094                         {
2095                         star_rating = convert_rating_to_stars(fd->rating);
2096                         }
2097                 else
2098                         {
2099                         star_rating = nullptr;
2100                         }
2101
2102                 link = islink(fd->path) ? GQ_LINK_STR : "";
2103                 if (fd->sidecar_files)
2104                         {
2105                         gchar *sidecars = file_data_sc_list_to_string(fd);
2106                         if (options->show_star_rating && VFICON(vf)->show_text)
2107                                 {
2108                                 name_sidecars = g_strdup_printf("%s%s %s\n%s", link, fd->name, sidecars, star_rating);
2109                                 }
2110                         else if (options->show_star_rating)
2111                                 {
2112                                 name_sidecars = g_strdup_printf("%s", star_rating);
2113                                 }
2114                         else if (VFICON(vf)->show_text)
2115                                 {
2116                                 name_sidecars = g_strdup_printf("%s%s %s", link, fd->name, sidecars);
2117                                 }
2118                         g_free(sidecars);
2119                         }
2120                 else
2121                         {
2122                         const gchar *disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
2123                         if (options->show_star_rating && VFICON(vf)->show_text)
2124                                 {
2125                                 name_sidecars = g_strdup_printf("%s%s%s\n%s", link, fd->name, disabled_grouping, star_rating);
2126                                 }
2127                         else if (options->show_star_rating)
2128                                 {
2129                                 name_sidecars = g_strdup_printf("%s", star_rating);
2130                                 }
2131                         else if (VFICON(vf)->show_text)
2132                                 {
2133                                 name_sidecars = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
2134                                 }
2135                         }
2136                 g_free(star_rating);
2137
2138                 style = gtk_widget_get_style(vf->listview);
2139                 if (fd->selected & SELECTION_SELECTED)
2140                         {
2141                         state = GTK_STATE_SELECTED;
2142                         }
2143
2144                 convert_gdkcolor_to_gdkrgba(&style->text[state], &color_fg);
2145                 convert_gdkcolor_to_gdkrgba(&style->base[state], &color_bg);
2146
2147                 if (fd->selected & SELECTION_PRELIGHT)
2148                         {
2149                         shift_color(&color_bg, -1, 0);
2150                         }
2151
2152                 g_object_set(cell, "pixbuf", fd->thumb_pixbuf,
2153                                         "text", name_sidecars,
2154                                         "marks", file_data_get_marks(fd),
2155                                         "show_marks", vf->marks_enabled,
2156                                         "cell-background-rgba", &color_bg,
2157                                         "cell-background-set", TRUE,
2158                                         "foreground-rgba", &color_fg,
2159                                         "foreground-set", TRUE,
2160                                         "has-focus", (VFICON(vf)->focus_fd == fd), NULL);
2161                 g_free(name_sidecars);
2162                 }
2163         else
2164                 {
2165                 g_object_set(cell,      "pixbuf", NULL,
2166                                         "text", NULL,
2167                                         "show_marks", FALSE,
2168                                         "cell-background-set", FALSE,
2169                                         "foreground-set", FALSE,
2170                                         "has-focus", FALSE, NULL);
2171                 }
2172 }
2173
2174 static void vficon_append_column(ViewFile *vf, gint n)
2175 {
2176         GtkTreeViewColumn *column;
2177         GtkCellRenderer *renderer;
2178
2179         column = gtk_tree_view_column_new();
2180         gtk_tree_view_column_set_min_width(column, 0);
2181
2182         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2183         gtk_tree_view_column_set_alignment(column, 0.5);
2184
2185         renderer = gqv_cell_renderer_icon_new();
2186         gtk_tree_view_column_pack_start(column, renderer, FALSE);
2187         g_object_set(G_OBJECT(renderer), "xpad", THUMB_BORDER_PADDING * 2,
2188                                          "ypad", THUMB_BORDER_PADDING,
2189                                          "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
2190
2191         g_object_set_data(G_OBJECT(column), "column_number", GINT_TO_POINTER(n));
2192         g_object_set_data(G_OBJECT(renderer), "column_number", GINT_TO_POINTER(n));
2193
2194         auto cd = g_new0(ColumnData, 1);
2195         cd->vf = vf;
2196         cd->number = n;
2197         gtk_tree_view_column_set_cell_data_func(column, renderer, vficon_cell_data_cb, cd, g_free);
2198
2199         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2200
2201         g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vficon_mark_toggled_cb), vf);
2202 }
2203
2204 /*
2205  *-----------------------------------------------------------------------------
2206  * base
2207  *-----------------------------------------------------------------------------
2208  */
2209
2210 gboolean vficon_set_fd(ViewFile *vf, FileData *dir_fd)
2211 {
2212         gboolean ret;
2213
2214         if (!dir_fd) return FALSE;
2215         if (vf->dir_fd == dir_fd) return TRUE;
2216
2217         file_data_unref(vf->dir_fd);
2218         vf->dir_fd = file_data_ref(dir_fd);
2219
2220         g_list_free(VFICON(vf)->selection);
2221         VFICON(vf)->selection = nullptr;
2222
2223         g_list_free(vf->list);
2224         vf->list = nullptr;
2225
2226         /* NOTE: populate will clear the store for us */
2227         ret = vficon_refresh_real(vf, FALSE);
2228
2229         VFICON(vf)->focus_fd = nullptr;
2230         vficon_move_focus(vf, 0, 0, FALSE);
2231
2232         return ret;
2233 }
2234
2235 void vficon_destroy_cb(GtkWidget *, gpointer data)
2236 {
2237         auto vf = static_cast<ViewFile *>(data);
2238
2239         vf_refresh_idle_cancel(vf);
2240
2241         file_data_unregister_notify_func(vf_notify_cb, vf);
2242
2243         tip_unschedule(vf);
2244
2245         vf_thumb_cleanup(vf);
2246         vf_star_cleanup(vf);
2247
2248         g_list_free(vf->list);
2249         g_list_free(VFICON(vf)->selection);
2250 }
2251
2252 ViewFile *vficon_new(ViewFile *vf, FileData *)
2253 {
2254         GtkListStore *store;
2255         GtkTreeSelection *selection;
2256         gint i;
2257
2258         vf->info = g_new0(ViewFileInfoIcon, 1);
2259
2260         VFICON(vf)->show_text = options->show_icon_names;
2261
2262         store = gtk_list_store_new(1, G_TYPE_POINTER);
2263         vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2264         g_object_unref(store);
2265
2266         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2267         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_NONE);
2268
2269         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2270         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2271
2272         for (i = 0; i < VFICON_MAX_COLUMNS; i++)
2273                 {
2274                 vficon_append_column(vf, i);
2275                 }
2276
2277         /* zero width column to hide tree view focus, we draw it ourselves */
2278         vficon_append_column(vf, i);
2279         /* end column to fill white space */
2280         vficon_append_column(vf, i);
2281
2282         g_signal_connect(G_OBJECT(vf->listview), "size_allocate",
2283                          G_CALLBACK(vficon_sized_cb), vf);
2284
2285         gtk_widget_set_events(vf->listview, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK |
2286                               static_cast<GdkEventMask>(GDK_BUTTON_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK));
2287
2288         g_signal_connect(G_OBJECT(vf->listview),"motion_notify_event",
2289                          G_CALLBACK(vficon_motion_cb), vf);
2290         g_signal_connect(G_OBJECT(vf->listview), "leave_notify_event",
2291                          G_CALLBACK(vficon_leave_cb), vf);
2292
2293         /* force VFICON(vf)->columns to be at least 1 (sane) - this will be corrected in the size_cb */
2294         vficon_populate_at_new_size(vf, 1, 1, FALSE);
2295
2296         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2297
2298         return vf;
2299 }
2300
2301 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */