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