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