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