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