Addl fix #488: Issues using Geeqie to compare pictures
[geeqie.git] / src / view_file / view_file_list.c
1 /*
2  * Copyright (C) 2004 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_list.h"
24
25 #include "bar.h"
26 #include "cache_maint.h"
27 #include "dnd.h"
28 #include "editors.h"
29 #include "img-view.h"
30 #include "layout.h"
31 #include "layout_image.h"
32 #include "menu.h"
33 #include "metadata.h"
34 #include "thumb.h"
35 #include "utilops.h"
36 #include "ui_fileops.h"
37 #include "ui_menu.h"
38 #include "ui_tree_edit.h"
39 #include "uri_utils.h"
40 #include "view_file.h"
41
42 #include <gdk/gdkkeysyms.h> /* for keyboard values */
43
44 /* Index to tree store */
45 enum {
46         FILE_COLUMN_POINTER = 0,
47         FILE_COLUMN_VERSION,
48         FILE_COLUMN_THUMB,
49         FILE_COLUMN_FORMATTED,
50         FILE_COLUMN_NAME,
51         FILE_COLUMN_SIDECARS,
52         FILE_COLUMN_SIZE,
53         FILE_COLUMN_DATE,
54         FILE_COLUMN_EXPANDED,
55         FILE_COLUMN_COLOR,
56         FILE_COLUMN_MARKS,
57         FILE_COLUMN_MARKS_LAST = FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
58         FILE_COLUMN_COUNT
59 };
60
61
62 /* Index to tree view */
63 enum {
64         FILE_VIEW_COLUMN_MARKS = 0,
65         FILE_VIEW_COLUMN_MARKS_LAST = FILE_VIEW_COLUMN_MARKS + FILEDATA_MARKS_SIZE - 1,
66         FILE_VIEW_COLUMN_THUMB,
67         FILE_VIEW_COLUMN_FORMATTED,
68         FILE_VIEW_COLUMN_SIZE,
69         FILE_VIEW_COLUMN_DATE,
70         FILE_VIEW_COLUMN_COUNT
71 };
72
73
74
75 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd);
76 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old, const gchar *new, gpointer data);
77 static void vflist_populate_view(ViewFile *vf, gboolean force);
78 static gboolean vflist_is_multiline(ViewFile *vf);
79 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded);
80
81
82 /*
83  *-----------------------------------------------------------------------------
84  * misc
85  *-----------------------------------------------------------------------------
86  */
87 typedef struct {
88         FileData *fd;
89         GtkTreeIter *iter;
90         gboolean found;
91         gint row;
92 } ViewFileFindRowData;
93
94 static gboolean vflist_find_row_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
95 {
96         ViewFileFindRowData *find = data;
97         FileData *fd;
98         gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
99         if (fd == find->fd)
100                 {
101                 *find->iter = *iter;
102                 find->found = TRUE;
103                 return TRUE;
104                 }
105         find->row++;
106         return FALSE;
107 }
108
109 static gint vflist_find_row(ViewFile *vf, FileData *fd, GtkTreeIter *iter)
110 {
111         GtkTreeModel *store;
112         ViewFileFindRowData data = {fd, iter, FALSE, 0};
113
114         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
115         gtk_tree_model_foreach(store, vflist_find_row_cb, &data);
116
117         if (data.found)
118                 {
119                 return data.row;
120                 }
121
122         return -1;
123 }
124
125 static FileData *vflist_find_data_by_coord(ViewFile *vf, gint x, gint y, GtkTreeIter *iter)
126 {
127         GtkTreePath *tpath;
128         GtkTreeViewColumn *column;
129
130         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), x, y,
131                                           &tpath, &column, NULL, NULL))
132                 {
133                 GtkTreeModel *store;
134                 GtkTreeIter row;
135                 FileData *fd;
136
137                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
138                 gtk_tree_model_get_iter(store, &row, tpath);
139                 gtk_tree_path_free(tpath);
140                 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &fd, -1);
141
142                 return fd;
143                 }
144
145         return NULL;
146 }
147
148 static gboolean vflist_store_clear_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
149 {
150         FileData *fd;
151         gtk_tree_model_get(model, iter, FILE_COLUMN_POINTER, &fd, -1);
152
153         /* it seems that gtk_tree_store_clear may call some callbacks
154            that use the column. Set the pointer to NULL to be safe. */
155         gtk_tree_store_set(GTK_TREE_STORE(model), iter, FILE_COLUMN_POINTER, NULL, -1);
156         file_data_unref(fd);
157         return FALSE;
158 }
159
160 static void vflist_store_clear(ViewFile *vf, gboolean unlock_files)
161 {
162         GtkTreeModel *store;
163         GList *files = NULL;
164
165         if (unlock_files && vf->marks_enabled)
166                 {
167                 // unlock locked files in this directory
168                 filelist_read(vf->dir_fd, &files, NULL);
169                 while (files)
170                         {
171                         FileData *fd = files->data;
172                         files = files->next;
173                         file_data_unlock(fd);
174                         file_data_unref(fd);  // undo the ref that got added in filelist_read
175                         }
176                 }
177
178         g_list_free(files);
179         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
180         gtk_tree_model_foreach(store, vflist_store_clear_cb, NULL);
181         gtk_tree_store_clear(GTK_TREE_STORE(store));
182 }
183
184 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set)
185 {
186         GtkTreeModel *store;
187         GtkTreeIter iter;
188
189         if (vflist_find_row(vf, fd, &iter) < 0) return;
190         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
191         gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_COLOR, color_set, -1);
192 }
193
194 static void vflist_move_cursor(ViewFile *vf, GtkTreeIter *iter)
195 {
196         GtkTreeModel *store;
197         GtkTreePath *tpath;
198
199         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
200
201         tpath = gtk_tree_model_get_path(store, iter);
202         gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, NULL, FALSE);
203         gtk_tree_path_free(tpath);
204 }
205
206
207 /*
208  *-----------------------------------------------------------------------------
209  * dnd
210  *-----------------------------------------------------------------------------
211  */
212
213 static void vflist_dnd_get(GtkWidget *widget, GdkDragContext *context,
214                            GtkSelectionData *selection_data, guint info,
215                            guint time, gpointer data)
216 {
217         ViewFile *vf = data;
218         GList *list = NULL;
219
220         if (!VFLIST(vf)->click_fd) return;
221
222         if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
223                 {
224                 list = vf_selection_get_list(vf);
225                 }
226         else
227                 {
228                 list = g_list_append(NULL, file_data_ref(VFLIST(vf)->click_fd));
229                 }
230
231         if (!list) return;
232         uri_selection_data_set_uris_from_filelist(selection_data, list);
233         filelist_free(list);
234 }
235
236 static void vflist_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
237 {
238         ViewFile *vf = data;
239
240         vflist_color_set(vf, VFLIST(vf)->click_fd, TRUE);
241
242         if (VFLIST(vf)->thumbs_enabled &&
243             VFLIST(vf)->click_fd && VFLIST(vf)->click_fd->thumb_pixbuf)
244                 {
245                 guint items;
246
247                 if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
248                         items = vf_selection_count(vf, NULL);
249                 else
250                         items = 1;
251
252                 dnd_set_drag_icon(widget, context, VFLIST(vf)->click_fd->thumb_pixbuf, items);
253                 }
254 }
255
256 static void vflist_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
257 {
258         ViewFile *vf = data;
259
260         vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
261
262         if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_MOVE)
263                 {
264                 vf_refresh(vf);
265                 }
266 }
267
268 static void vflist_drag_data_received(GtkWidget *entry_widget, GdkDragContext *context,
269                                       int x, int y, GtkSelectionData *selection,
270                                       guint info, guint time, gpointer data)
271 {
272         ViewFile *vf = data;
273
274         if (info == TARGET_TEXT_PLAIN) {
275                 FileData *fd = vflist_find_data_by_coord(vf, x, y, NULL);
276
277                 if (fd) {
278                         /* Add keywords to file */
279                         gchar *str = (gchar *) gtk_selection_data_get_text(selection);
280                         GList *kw_list = string_to_keywords_list(str);
281
282                         metadata_append_list(fd, KEYWORD_KEY, kw_list);
283                         string_list_free(kw_list);
284                         g_free(str);
285                 }
286         }
287 }
288
289 void vflist_dnd_init(ViewFile *vf)
290 {
291         gtk_drag_source_set(vf->listview, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
292                             dnd_file_drag_types, dnd_file_drag_types_count,
293                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
294         gtk_drag_dest_set(vf->listview, GTK_DEST_DEFAULT_ALL,
295                             dnd_file_drag_types, dnd_file_drag_types_count,
296                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
297
298         g_signal_connect(G_OBJECT(vf->listview), "drag_data_get",
299                          G_CALLBACK(vflist_dnd_get), vf);
300         g_signal_connect(G_OBJECT(vf->listview), "drag_begin",
301                          G_CALLBACK(vflist_dnd_begin), vf);
302         g_signal_connect(G_OBJECT(vf->listview), "drag_end",
303                          G_CALLBACK(vflist_dnd_end), vf);
304         g_signal_connect(G_OBJECT(vf->listview), "drag_data_received",
305                          G_CALLBACK(vflist_drag_data_received), vf);
306 }
307
308 /*
309  *-----------------------------------------------------------------------------
310  * pop-up menu
311  *-----------------------------------------------------------------------------
312  */
313
314 GList *vflist_selection_get_one(ViewFile *vf, FileData *fd)
315 {
316         GList *list = g_list_append(NULL, file_data_ref(fd));
317
318         if (fd->sidecar_files)
319                 {
320                 /* check if the row is expanded */
321                 GtkTreeModel *store;
322                 GtkTreeIter iter;
323
324                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
325                 if (vflist_find_row(vf, fd, &iter) >= 0)
326                         {
327                         GtkTreePath *tpath;
328
329                         tpath = gtk_tree_model_get_path(store, &iter);
330                         if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
331                                 {
332                                 /* unexpanded - add whole group */
333                                 GList *work = fd->sidecar_files;
334                                 while (work)
335                                         {
336                                         FileData *sfd = work->data;
337                                         list = g_list_prepend(list, file_data_ref(sfd));
338                                         work = work->next;
339                                         }
340                                 }
341                         gtk_tree_path_free(tpath);
342                         }
343                 list = g_list_reverse(list);
344                 }
345
346         return list;
347 }
348
349 GList *vflist_pop_menu_file_list(ViewFile *vf)
350 {
351         if (!VFLIST(vf)->click_fd) return NULL;
352
353         if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
354                 {
355                 return vf_selection_get_list(vf);
356                 }
357         return vflist_selection_get_one(vf, VFLIST(vf)->click_fd);
358 }
359
360
361 void vflist_pop_menu_view_cb(GtkWidget *widget, gpointer data)
362 {
363         ViewFile *vf = data;
364
365         if (vflist_row_is_selected(vf, VFLIST(vf)->click_fd))
366                 {
367                 GList *list;
368
369                 list = vf_selection_get_list(vf);
370                 view_window_new_from_list(list);
371                 filelist_free(list);
372                 }
373         else
374                 {
375                 view_window_new(VFLIST(vf)->click_fd);
376                 }
377 }
378
379 void vflist_pop_menu_rename_cb(GtkWidget *widget, gpointer data)
380 {
381         ViewFile *vf = data;
382         GList *list;
383
384         list = vf_pop_menu_file_list(vf);
385         if (options->file_ops.enable_in_place_rename &&
386             list && !list->next && VFLIST(vf)->click_fd)
387                 {
388                 GtkTreeModel *store;
389                 GtkTreeIter iter;
390
391                 filelist_free(list);
392
393                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
394                 if (vflist_find_row(vf, VFLIST(vf)->click_fd, &iter) >= 0)
395                         {
396                         GtkTreePath *tpath;
397
398                         tpath = gtk_tree_model_get_path(store, &iter);
399                         tree_edit_by_path(GTK_TREE_VIEW(vf->listview), tpath,
400                                           FILE_VIEW_COLUMN_FORMATTED, VFLIST(vf)->click_fd->name,
401                                           vflist_row_rename_cb, vf);
402                         gtk_tree_path_free(tpath);
403                         }
404                 return;
405                 }
406
407         file_util_rename(NULL, list, vf->listview);
408 }
409
410 void vflist_pop_menu_thumbs_cb(GtkWidget *widget, gpointer data)
411 {
412         ViewFile *vf = data;
413
414         vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
415         if (vf->layout)
416                 {
417                 layout_thumb_set(vf->layout, !VFLIST(vf)->thumbs_enabled);
418                 }
419         else
420                 {
421                 vflist_thumb_set(vf, !VFLIST(vf)->thumbs_enabled);
422                 }
423 }
424
425 void vflist_pop_menu_refresh_cb(GtkWidget *widget, gpointer data)
426 {
427         ViewFile *vf = data;
428
429         vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
430         vf_refresh(vf);
431         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
432 }
433
434 void vflist_popup_destroy_cb(GtkWidget *widget, gpointer data)
435 {
436         ViewFile *vf = data;
437         vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
438         VFLIST(vf)->click_fd = NULL;
439         vf->popup = NULL;
440 }
441
442
443 /*
444  *-----------------------------------------------------------------------------
445  * callbacks
446  *-----------------------------------------------------------------------------
447  */
448
449 static gboolean vflist_row_rename_cb(TreeEditData *td, const gchar *old, const gchar *new, gpointer data)
450 {
451         ViewFile *vf = data;
452         gchar *new_path;
453
454         if (!new || !new[0]) return FALSE;
455
456         new_path = g_build_filename(vf->dir_fd->path, new, NULL);
457
458         if (strchr(new, G_DIR_SEPARATOR) != NULL)
459                 {
460                 gchar *text = g_strdup_printf(_("Invalid file name:\n%s"), new);
461                 file_util_warning_dialog(_("Error renaming file"), text, GTK_STOCK_DIALOG_ERROR, vf->listview);
462                 g_free(text);
463                 }
464         else
465                 {
466                 gchar *old_path = g_build_filename(vf->dir_fd->path, old, NULL);
467                 FileData *fd = file_data_new_group(old_path); /* get the fd from cache */
468                 file_util_rename_simple(fd, new_path, vf->listview);
469                 file_data_unref(fd);
470                 g_free(old_path);
471                 }
472
473         g_free(new_path);
474
475         return FALSE;
476 }
477
478 static void vflist_menu_position_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
479 {
480         ViewFile *vf = data;
481         GtkTreeModel *store;
482         GtkTreeIter iter;
483         GtkTreePath *tpath;
484         gint cw, ch;
485
486         if (vflist_find_row(vf, VFLIST(vf)->click_fd, &iter) < 0) return;
487         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
488         tpath = gtk_tree_model_get_path(store, &iter);
489         tree_view_get_cell_clamped(GTK_TREE_VIEW(vf->listview), tpath, FILE_COLUMN_NAME - 1, TRUE, x, y, &cw, &ch);
490         gtk_tree_path_free(tpath);
491         *y += ch;
492         popup_menu_position_clamp(menu, x, y, 0);
493 }
494
495 gboolean vflist_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
496 {
497         ViewFile *vf = data;
498         GtkTreePath *tpath;
499
500         if (event->keyval != GDK_KEY_Menu) return FALSE;
501
502         gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &tpath, NULL);
503         if (tpath)
504                 {
505                 GtkTreeModel *store;
506                 GtkTreeIter iter;
507
508                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
509                 gtk_tree_model_get_iter(store, &iter, tpath);
510                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->click_fd, -1);
511                 gtk_tree_path_free(tpath);
512                 }
513         else
514                 {
515                 VFLIST(vf)->click_fd = NULL;
516                 }
517
518         vf->popup = vf_pop_menu(vf);
519         gtk_menu_popup(GTK_MENU(vf->popup), NULL, NULL, vflist_menu_position_cb, vf, 0, GDK_CURRENT_TIME);
520
521         return TRUE;
522 }
523
524 gboolean vflist_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
525 {
526         ViewFile *vf = data;
527         GtkTreePath *tpath;
528         GtkTreeIter iter;
529         FileData *fd = NULL;
530         GtkTreeViewColumn *column;
531
532         vf->clicked_mark = 0;
533
534         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
535                                           &tpath, &column, NULL, NULL))
536                 {
537                 GtkTreeModel *store;
538                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
539
540                 if (bevent->button == MOUSE_BUTTON_LEFT &&
541                     col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
542                         return FALSE;
543
544                 if (col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST)
545                         vf->clicked_mark = 1 + (col_idx - FILE_COLUMN_MARKS);
546
547                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
548
549                 gtk_tree_model_get_iter(store, &iter, tpath);
550                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
551                 gtk_tree_path_free(tpath);
552                 }
553
554         VFLIST(vf)->click_fd = fd;
555
556         if (bevent->button == MOUSE_BUTTON_RIGHT)
557                 {
558                 vf->popup = vf_pop_menu(vf);
559                 gtk_menu_popup(GTK_MENU(vf->popup), NULL, NULL, NULL, NULL,
560                                 bevent->button, bevent->time);
561                 return TRUE;
562                 }
563
564         if (!fd) return FALSE;
565
566         if (bevent->button == MOUSE_BUTTON_MIDDLE)
567                 {
568                 if (!vflist_row_is_selected(vf, fd))
569                         {
570                         vflist_color_set(vf, fd, TRUE);
571                         }
572                 return TRUE;
573                 }
574
575
576         if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_BUTTON_PRESS &&
577             !(bevent->state & GDK_SHIFT_MASK ) &&
578             !(bevent->state & GDK_CONTROL_MASK ) &&
579             vflist_row_is_selected(vf, fd))
580                 {
581                 GtkTreeSelection *selection;
582
583                 gtk_widget_grab_focus(widget);
584
585
586                 /* returning FALSE and further processing of the event is needed for
587                    correct operation of the expander, to show the sidecar files.
588                    It however resets the selection of multiple files. With this condition
589                    it should work for both cases */
590                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
591                 return (gtk_tree_selection_count_selected_rows(selection) > 1);
592                 }
593
594         if (bevent->button == MOUSE_BUTTON_LEFT && bevent->type == GDK_2BUTTON_PRESS)
595                 {
596                 if (vf->layout) layout_image_full_screen_start(vf->layout);
597                 }
598
599         return FALSE;
600 }
601
602 gboolean vflist_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
603 {
604         ViewFile *vf = data;
605         GtkTreePath *tpath;
606         GtkTreeIter iter;
607         FileData *fd = NULL;
608
609         if (bevent->button == MOUSE_BUTTON_MIDDLE)
610                 {
611                 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
612                 }
613
614         if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
615                 {
616                 return TRUE;
617                 }
618
619         if ((bevent->x != 0 || bevent->y != 0) &&
620             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
621                                           &tpath, NULL, NULL, NULL))
622                 {
623                 GtkTreeModel *store;
624
625                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
626                 gtk_tree_model_get_iter(store, &iter, tpath);
627                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
628                 gtk_tree_path_free(tpath);
629                 }
630
631         if (bevent->button == MOUSE_BUTTON_MIDDLE)
632                 {
633                 if (fd && VFLIST(vf)->click_fd == fd)
634                         {
635                         GtkTreeSelection *selection;
636
637                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
638                         if (vflist_row_is_selected(vf, fd))
639                                 {
640                                 gtk_tree_selection_unselect_iter(selection, &iter);
641                                 }
642                         else
643                                 {
644                                 gtk_tree_selection_select_iter(selection, &iter);
645                                 }
646                         }
647                 return TRUE;
648                 }
649
650         if (fd && VFLIST(vf)->click_fd == fd &&
651             !(bevent->state & GDK_SHIFT_MASK ) &&
652             !(bevent->state & GDK_CONTROL_MASK ) &&
653             vflist_row_is_selected(vf, fd))
654                 {
655                 GtkTreeSelection *selection;
656
657                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
658                 gtk_tree_selection_unselect_all(selection);
659                 gtk_tree_selection_select_iter(selection, &iter);
660                 vflist_move_cursor(vf, &iter);
661                 }
662
663         return FALSE;
664 }
665
666 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
667 {
668         FileData *read_ahead_fd = NULL;
669         gint row;
670         FileData *cur_fd;
671
672         if (!sel_fd) return;
673
674         cur_fd = layout_image_get_fd(vf->layout);
675         if (sel_fd == cur_fd) return; /* no change */
676
677         row = g_list_index(vf->list, sel_fd);
678         // FIXME sidecar data
679
680         if (sel_fd && options->image.enable_read_ahead && row >= 0)
681                 {
682                 if (row > g_list_index(vf->list, cur_fd) &&
683                     (guint) (row + 1) < vf_count(vf, NULL))
684                         {
685                         read_ahead_fd = vf_index_get_data(vf, row + 1);
686                         }
687                 else if (row > 0)
688                         {
689                         read_ahead_fd = vf_index_get_data(vf, row - 1);
690                         }
691                 }
692
693         layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
694 }
695
696 static gboolean vflist_select_idle_cb(gpointer data)
697 {
698         ViewFile *vf = data;
699
700         if (!vf->layout)
701                 {
702                 VFLIST(vf)->select_idle_id = 0;
703                 return FALSE;
704                 }
705
706         vf_send_update(vf);
707
708         if (VFLIST(vf)->select_fd)
709                 {
710                 vflist_select_image(vf, VFLIST(vf)->select_fd);
711                 VFLIST(vf)->select_fd = NULL;
712                 }
713
714         VFLIST(vf)->select_idle_id = 0;
715         return FALSE;
716 }
717
718 static void vflist_select_idle_cancel(ViewFile *vf)
719 {
720         if (VFLIST(vf)->select_idle_id)
721                 {
722                 g_source_remove(VFLIST(vf)->select_idle_id);
723                 VFLIST(vf)->select_idle_id = 0;
724                 }
725 }
726
727 static gboolean vflist_select_cb(GtkTreeSelection *selection, GtkTreeModel *store, GtkTreePath *tpath,
728                                  gboolean path_currently_selected, gpointer data)
729 {
730         ViewFile *vf = data;
731         GtkTreeIter iter;
732
733         if (!path_currently_selected &&
734             gtk_tree_model_get_iter(store, &iter, tpath))
735                 {
736                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
737                 }
738         else
739                 {
740                 VFLIST(vf)->select_fd = NULL;
741                 }
742
743         if (vf->layout &&
744             !VFLIST(vf)->select_idle_id)
745                 {
746                 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
747                 }
748
749         return TRUE;
750 }
751
752 static void vflist_expand_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
753 {
754         ViewFile *vf = data;
755         vflist_set_expanded(vf, iter, TRUE);
756 }
757
758 static void vflist_collapse_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
759 {
760         ViewFile *vf = data;
761         vflist_set_expanded(vf, iter, FALSE);
762 }
763
764 /*
765  *-----------------------------------------------------------------------------
766  * misc
767  *-----------------------------------------------------------------------------
768  */
769
770
771 static gchar* vflist_get_formatted(ViewFile *vf, const gchar *name, const gchar *sidecars, const gchar *size, const gchar *time, gboolean expanded)
772  {
773         gboolean multiline = vflist_is_multiline(vf);
774         gchar *text;
775
776         if (multiline)
777                 {
778                 text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
779                 }
780         else
781                 {
782                 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
783                 }
784         return text;
785 }
786
787 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
788 {
789         GtkTreeStore *store;
790         gchar *name;
791         gchar *sidecars;
792         gchar *size;
793         gchar *time;
794         gchar *formatted;
795
796         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
797
798         gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
799                                         FILE_COLUMN_NAME, &name,
800                                         FILE_COLUMN_SIDECARS, &sidecars,
801                                         FILE_COLUMN_SIZE, &size,
802                                         FILE_COLUMN_DATE, &time,
803                                         -1);
804         formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded);
805
806         gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
807                                         FILE_COLUMN_EXPANDED, expanded,
808                                         -1);
809         g_free(time);
810         g_free(size);
811         g_free(sidecars);
812         g_free(name);
813         g_free(formatted);
814 }
815
816 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
817 {
818         gchar *size;
819         gchar *sidecars = NULL;
820         gchar *name;
821         const gchar *time = text_from_time(fd->date);
822         gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
823         const gchar *disabled_grouping;
824         gchar *formatted;
825         gboolean expanded = FALSE;
826
827         if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
828                 {
829                 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
830                 }
831
832         sidecars = file_data_sc_list_to_string(fd);
833
834         disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
835         name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
836         size = text_from_size(fd->size);
837
838         formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded);
839
840         gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
841                                         FILE_COLUMN_VERSION, fd->version,
842                                         FILE_COLUMN_THUMB, fd->thumb_pixbuf,
843                                         FILE_COLUMN_FORMATTED, formatted,
844                                         FILE_COLUMN_SIDECARS, sidecars,
845                                         FILE_COLUMN_NAME, name,
846                                         FILE_COLUMN_SIZE, size,
847                                         FILE_COLUMN_DATE, time,
848 #define STORE_SET_IS_SLOW 1
849 #if STORE_SET_IS_SLOW
850 /* this is 3x faster on a directory with 20000 files */
851                                         FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
852                                         FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
853                                         FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
854                                         FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
855                                         FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
856                                         FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
857 #if FILEDATA_MARKS_SIZE != 6
858 #error this needs to be updated
859 #endif
860 #endif
861                                         FILE_COLUMN_COLOR, FALSE, -1);
862
863 #if !STORE_SET_IS_SLOW
864         {
865         gint i;
866         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
867                 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
868         }
869 #endif
870         g_free(size);
871         g_free(sidecars);
872         g_free(name);
873         g_free(formatted);
874 }
875
876 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
877 {
878         GList *work;
879         GtkTreeIter iter;
880         gboolean valid;
881         gint num_ordered = 0;
882         gint num_prepended = 0;
883
884         valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
885
886         work = list;
887         while (work)
888                 {
889                 gint match;
890                 FileData *fd = work->data;
891                 gboolean done = FALSE;
892
893                 while (!done)
894                         {
895                         FileData *old_fd = NULL;
896                         gint old_version = 0;
897
898                         if (valid)
899                                 {
900                                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
901                                                    FILE_COLUMN_POINTER, &old_fd,
902                                                    FILE_COLUMN_VERSION, &old_version,
903                                                    -1);
904
905                                 if (fd == old_fd)
906                                         {
907                                         match = 0;
908                                         }
909                                 else
910                                         {
911                                         if (parent_iter)
912                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
913                                         else
914                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
915
916                                         if (match == 0) g_warning("multiple fd for the same path");
917                                         }
918
919                                 }
920                         else
921                                 {
922                                 match = -1;
923                                 }
924
925                         if (match < 0)
926                                 {
927                                 GtkTreeIter new;
928
929                                 if (valid)
930                                         {
931                                         num_ordered++;
932                                         gtk_tree_store_insert_before(store, &new, parent_iter, &iter);
933                                         }
934                                 else
935                                         {
936                                         /*
937                                             here should be used gtk_tree_store_append, but this function seems to be O(n)
938                                             and it seems to be much faster to add new entries to the beginning and reorder later
939                                         */
940                                         num_prepended++;
941                                         gtk_tree_store_prepend(store, &new, parent_iter);
942                                         }
943
944                                 vflist_setup_iter(vf, store, &new, file_data_ref(fd));
945                                 vflist_setup_iter_recursive(vf, store, &new, fd->sidecar_files, selected, force);
946
947                                 if (g_list_find(selected, fd))
948                                         {
949                                         /* renamed files - the same fd appears at different position - select it again*/
950                                         GtkTreeSelection *selection;
951                                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
952                                         gtk_tree_selection_select_iter(selection, &new);
953                                         }
954
955                                 done = TRUE;
956                                 }
957                         else if (match > 0)
958                                 {
959                                 file_data_unref(old_fd);
960                                 valid = gtk_tree_store_remove(store, &iter);
961                                 }
962                         else
963                                 {
964                                 num_ordered++;
965                                 if (fd->version != old_version || force)
966                                         {
967                                         vflist_setup_iter(vf, store, &iter, fd);
968                                         vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
969                                         }
970
971                                 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
972
973                                 done = TRUE;
974                                 }
975                         }
976                 work = work->next;
977                 }
978
979         while (valid)
980                 {
981                 FileData *old_fd;
982                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
983                 file_data_unref(old_fd);
984
985                 valid = gtk_tree_store_remove(store, &iter);
986                 }
987
988         /* move the prepended entries to the correct position */
989         if (num_prepended)
990                 {
991                 gint i;
992                 gint num_total = num_prepended + num_ordered;
993                 gint *new_order = g_malloc(num_total * sizeof(gint));
994
995                 for (i = 0; i < num_total; i++)
996                         {
997                         if (i < num_ordered)
998                                 new_order[i] = num_prepended + i;
999                         else
1000                                 new_order[i] = num_total - 1 - i;
1001                         }
1002                 gtk_tree_store_reorder(store, parent_iter, new_order);
1003
1004                 g_free(new_order);
1005                 }
1006 }
1007
1008 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend)
1009 {
1010         gint i;
1011         GHashTable *fd_idx_hash = g_hash_table_new(NULL, NULL);
1012         gint *new_order;
1013         GtkTreeStore *store;
1014         GList *work;
1015
1016         if (vf->sort_method == type && vf->sort_ascend == ascend) return;
1017         if (!vf->list) return;
1018
1019         work = vf->list;
1020         i = 0;
1021         while (work)
1022                 {
1023                 FileData *fd = work->data;
1024                 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1025                 i++;
1026                 work = work->next;
1027                 }
1028
1029         vf->sort_method = type;
1030         vf->sort_ascend = ascend;
1031
1032         vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1033
1034         new_order = g_malloc(i * sizeof(gint));
1035
1036         work = vf->list;
1037         i = 0;
1038         while (work)
1039                 {
1040                 FileData *fd = work->data;
1041                 new_order[i] = GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd));
1042                 i++;
1043                 work = work->next;
1044                 }
1045
1046         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1047         gtk_tree_store_reorder(store, NULL, new_order);
1048
1049         g_free(new_order);
1050         g_hash_table_destroy(fd_idx_hash);
1051 }
1052
1053 /*
1054  *-----------------------------------------------------------------------------
1055  * thumb updates
1056  *-----------------------------------------------------------------------------
1057  */
1058
1059
1060 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1061 {
1062         GList *work = list;
1063         while (work)
1064                 {
1065                 FileData *fd = work->data;
1066                 work = work->next;
1067
1068                 if (fd->thumb_pixbuf) (*done)++;
1069
1070                 if (fd->sidecar_files)
1071                         {
1072                         vflist_thumb_progress_count(fd->sidecar_files, count, done);
1073                         }
1074                 (*count)++;
1075                 }
1076 }
1077
1078 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1079 {
1080         GtkTreeStore *store;
1081         GtkTreeIter iter;
1082
1083         if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1084
1085         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1086         gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1087 }
1088
1089 FileData *vflist_thumb_next_fd(ViewFile *vf)
1090 {
1091         GtkTreePath *tpath;
1092         FileData *fd = NULL;
1093
1094         /* first check the visible files */
1095
1096         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, NULL, NULL, NULL))
1097                 {
1098                 GtkTreeModel *store;
1099                 GtkTreeIter iter;
1100                 gboolean valid = TRUE;
1101
1102                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1103                 gtk_tree_model_get_iter(store, &iter, tpath);
1104                 gtk_tree_path_free(tpath);
1105
1106                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1107                         {
1108                         FileData *nfd;
1109
1110                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1111
1112                         if (!nfd->thumb_pixbuf) fd = nfd;
1113
1114                         valid = gtk_tree_model_iter_next(store, &iter);
1115                         }
1116                 }
1117
1118         /* then find first undone */
1119
1120         if (!fd)
1121                 {
1122                 GList *work = vf->list;
1123                 while (work && !fd)
1124                         {
1125                         FileData *fd_p = work->data;
1126                         if (!fd_p->thumb_pixbuf)
1127                                 fd = fd_p;
1128                         else
1129                                 {
1130                                 GList *work2 = fd_p->sidecar_files;
1131
1132                                 while (work2 && !fd)
1133                                         {
1134                                         fd_p = work2->data;
1135                                         if (!fd_p->thumb_pixbuf) fd = fd_p;
1136                                         work2 = work2->next;
1137                                         }
1138                                 }
1139                         work = work->next;
1140                         }
1141                 }
1142
1143         return fd;
1144 }
1145
1146
1147 void vflist_thumb_reset_all(ViewFile *vf)
1148 {
1149         GList *work = vf->list;
1150         while (work)
1151                 {
1152                 FileData *fd = work->data;
1153                 if (fd->thumb_pixbuf)
1154                         {
1155                         g_object_unref(fd->thumb_pixbuf);
1156                         fd->thumb_pixbuf = NULL;
1157                         }
1158                 work = work->next;
1159                 }
1160 }
1161
1162 /*
1163  *-----------------------------------------------------------------------------
1164  * row stuff
1165  *-----------------------------------------------------------------------------
1166  */
1167
1168 FileData *vflist_index_get_data(ViewFile *vf, gint row)
1169 {
1170         return g_list_nth_data(vf->list, row);
1171 }
1172
1173 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1174 {
1175         gint p = 0;
1176         GList *work, *work2;
1177
1178         work = vf->list;
1179         while (work)
1180                 {
1181                 FileData *list_fd = work->data;
1182                 if (list_fd == fd) return p;
1183
1184                 work2 = list_fd->sidecar_files;
1185                 while (work2)
1186                         {
1187                         /* FIXME: return the same index also for sidecars
1188                            it is sufficient for next/prev navigation but it should be rewritten
1189                            without using indexes at all
1190                         */
1191                         FileData *sidecar_fd = work2->data;
1192                         if (sidecar_fd == fd) return p;
1193                         work2 = work2->next;
1194                         }
1195
1196                 work = work->next;
1197                 p++;
1198                 }
1199
1200         return -1;
1201 }
1202
1203 guint vflist_count(ViewFile *vf, gint64 *bytes)
1204 {
1205         if (bytes)
1206                 {
1207                 gint64 b = 0;
1208                 GList *work;
1209
1210                 work = vf->list;
1211                 while (work)
1212                         {
1213                         FileData *fd = work->data;
1214                         work = work->next;
1215                         b += fd->size;
1216                         }
1217
1218                 *bytes = b;
1219                 }
1220
1221         return g_list_length(vf->list);
1222 }
1223
1224 GList *vflist_get_list(ViewFile *vf)
1225 {
1226         GList *list = NULL;
1227         GList *work;
1228
1229         work = vf->list;
1230         while (work)
1231                 {
1232                 FileData *fd = work->data;
1233                 work = work->next;
1234
1235                 list = g_list_prepend(list, file_data_ref(fd));
1236                 }
1237
1238         return g_list_reverse(list);
1239 }
1240
1241 /*
1242  *-----------------------------------------------------------------------------
1243  * selections
1244  *-----------------------------------------------------------------------------
1245  */
1246
1247 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1248 {
1249         GtkTreeModel *store;
1250         GtkTreeSelection *selection;
1251         GList *slist;
1252         GList *work;
1253         gboolean found = FALSE;
1254
1255         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1256         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1257         work = slist;
1258         while (!found && work)
1259                 {
1260                 GtkTreePath *tpath = work->data;
1261                 FileData *fd_n;
1262                 GtkTreeIter iter;
1263
1264                 gtk_tree_model_get_iter(store, &iter, tpath);
1265                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1266                 if (fd_n == fd) found = TRUE;
1267                 work = work->next;
1268                 }
1269         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1270         g_list_free(slist);
1271
1272         return found;
1273 }
1274
1275 gboolean vflist_index_is_selected(ViewFile *vf, gint row)
1276 {
1277         FileData *fd;
1278
1279         fd = vf_index_get_data(vf, row);
1280         return vflist_row_is_selected(vf, fd);
1281 }
1282
1283 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1284 {
1285         GtkTreeModel *store;
1286         GtkTreeSelection *selection;
1287         GList *slist;
1288         guint count;
1289
1290         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1291         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1292
1293         if (bytes)
1294                 {
1295                 gint64 b = 0;
1296                 GList *work;
1297
1298                 work = slist;
1299                 while (work)
1300                         {
1301                         GtkTreePath *tpath = work->data;
1302                         GtkTreeIter iter;
1303                         FileData *fd;
1304
1305                         gtk_tree_model_get_iter(store, &iter, tpath);
1306                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1307                         b += fd->size;
1308
1309                         work = work->next;
1310                         }
1311
1312                 *bytes = b;
1313                 }
1314
1315         count = g_list_length(slist);
1316         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1317         g_list_free(slist);
1318
1319         return count;
1320 }
1321
1322 GList *vflist_selection_get_list(ViewFile *vf)
1323 {
1324         GtkTreeModel *store;
1325         GtkTreeSelection *selection;
1326         GList *slist;
1327         GList *list = NULL;
1328         GList *work;
1329
1330         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1331         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1332         work = slist;
1333         while (work)
1334                 {
1335                 GtkTreePath *tpath = work->data;
1336                 FileData *fd;
1337                 GtkTreeIter iter;
1338
1339                 gtk_tree_model_get_iter(store, &iter, tpath);
1340                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1341
1342                 list = g_list_prepend(list, file_data_ref(fd));
1343
1344                 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1345                         {
1346                         /* unexpanded - add whole group */
1347                         GList *work2 = fd->sidecar_files;
1348                         while (work2)
1349                                 {
1350                                 FileData *sfd = work2->data;
1351                                 list = g_list_prepend(list, file_data_ref(sfd));
1352                                 work2 = work2->next;
1353                                 }
1354                         }
1355
1356                 work = work->next;
1357                 }
1358         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1359         g_list_free(slist);
1360
1361         return g_list_reverse(list);
1362 }
1363
1364 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1365 {
1366         GtkTreeModel *store;
1367         GtkTreeSelection *selection;
1368         GList *slist;
1369         GList *list = NULL;
1370         GList *work;
1371
1372         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1373         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1374         work = slist;
1375         while (work)
1376                 {
1377                 GtkTreePath *tpath = work->data;
1378                 FileData *fd;
1379                 GtkTreeIter iter;
1380
1381                 gtk_tree_model_get_iter(store, &iter, tpath);
1382                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1383
1384                 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1385
1386                 work = work->next;
1387                 }
1388         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1389         g_list_free(slist);
1390
1391         return g_list_reverse(list);
1392 }
1393
1394 void vflist_select_all(ViewFile *vf)
1395 {
1396         GtkTreeSelection *selection;
1397
1398         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1399         gtk_tree_selection_select_all(selection);
1400
1401         VFLIST(vf)->select_fd = NULL;
1402 }
1403
1404 void vflist_select_none(ViewFile *vf)
1405 {
1406         GtkTreeSelection *selection;
1407
1408         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1409         gtk_tree_selection_unselect_all(selection);
1410 }
1411
1412 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1413 {
1414         GtkTreePath *tpath;
1415         gboolean result;
1416
1417         tpath = gtk_tree_model_get_path(store, iter);
1418         result = gtk_tree_path_prev(tpath);
1419         if (result)
1420                 gtk_tree_model_get_iter(store, iter, tpath);
1421
1422         gtk_tree_path_free(tpath);
1423
1424         return result;
1425 }
1426
1427 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1428 {
1429         if (!gtk_tree_model_get_iter_first(store, iter))
1430                 return FALSE;
1431
1432         while (TRUE)
1433                 {
1434                 GtkTreeIter next = *iter;
1435
1436                 if (gtk_tree_model_iter_next(store, &next))
1437                         *iter = next;
1438                 else
1439                         break;
1440                 }
1441
1442         return TRUE;
1443 }
1444
1445 void vflist_select_invert(ViewFile *vf)
1446 {
1447         GtkTreeIter iter;
1448         GtkTreeSelection *selection;
1449         GtkTreeModel *store;
1450         gboolean valid;
1451
1452         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1453         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1454
1455         /* Backward iteration prevents scrolling to the end of the list,
1456          * it scrolls to the first selected row instead. */
1457         valid = tree_model_get_iter_last(store, &iter);
1458
1459         while (valid)
1460                 {
1461                 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1462
1463                 if (selected)
1464                         gtk_tree_selection_unselect_iter(selection, &iter);
1465                 else
1466                         gtk_tree_selection_select_iter(selection, &iter);
1467
1468                 valid = tree_model_iter_prev(store, &iter);
1469                 }
1470 }
1471
1472 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1473 {
1474         GtkTreeIter iter;
1475
1476         if (vflist_find_row(vf, fd, &iter) < 0) return;
1477
1478         tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1479
1480         if (!vflist_row_is_selected(vf, fd))
1481                 {
1482                 GtkTreeSelection *selection;
1483                 GtkTreeModel *store;
1484                 GtkTreePath *tpath;
1485
1486                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1487                 gtk_tree_selection_unselect_all(selection);
1488                 gtk_tree_selection_select_iter(selection, &iter);
1489                 vflist_move_cursor(vf, &iter);
1490
1491                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1492                 tpath = gtk_tree_model_get_path(store, &iter);
1493                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, NULL, FALSE);
1494                 gtk_tree_path_free(tpath);
1495                 }
1496 }
1497
1498 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1499 {
1500         GList *work;
1501         FileData *fd = NULL;
1502
1503         if (sel_fd->parent) sel_fd = sel_fd->parent;
1504         work = vf->list;
1505
1506         while (work)
1507                 {
1508                 gint match;
1509                 fd = work->data;
1510                 work = work->next;
1511
1512                 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1513
1514                 if (match >= 0) break;
1515                 }
1516
1517         if (fd) vflist_select_by_fd(vf, fd);
1518
1519 }
1520
1521 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1522 {
1523         GtkTreeModel *store;
1524         GtkTreeIter iter;
1525         GtkTreeSelection *selection;
1526         gboolean valid;
1527         gint n = mark - 1;
1528
1529         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1530
1531         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1532         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1533
1534         valid = gtk_tree_model_get_iter_first(store, &iter);
1535         while (valid)
1536                 {
1537                 FileData *fd;
1538                 gboolean mark_val, selected;
1539                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1540
1541                 mark_val = file_data_get_mark(fd, n);
1542                 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1543
1544                 switch (mode)
1545                         {
1546                         case MTS_MODE_SET: selected = mark_val;
1547                                 break;
1548                         case MTS_MODE_OR: selected = mark_val || selected;
1549                                 break;
1550                         case MTS_MODE_AND: selected = mark_val && selected;
1551                                 break;
1552                         case MTS_MODE_MINUS: selected = !mark_val && selected;
1553                                 break;
1554                         }
1555
1556                 if (selected)
1557                         gtk_tree_selection_select_iter(selection, &iter);
1558                 else
1559                         gtk_tree_selection_unselect_iter(selection, &iter);
1560
1561                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1562                 }
1563 }
1564
1565 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1566 {
1567         GtkTreeModel *store;
1568         GtkTreeSelection *selection;
1569         GList *slist;
1570         GList *work;
1571         gint n = mark - 1;
1572
1573         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1574
1575         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1576         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1577         work = slist;
1578         while (work)
1579                 {
1580                 GtkTreePath *tpath = work->data;
1581                 FileData *fd;
1582                 GtkTreeIter iter;
1583
1584                 gtk_tree_model_get_iter(store, &iter, tpath);
1585                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1586
1587                 /* the change has a very limited range and the standard notification would trigger
1588                    complete re-read of the directory - try to do only minimal update instead */
1589                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1590
1591                 switch (mode)
1592                         {
1593                         case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1594                                 break;
1595                         case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1596                                 break;
1597                         case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1598                                 break;
1599                         }
1600
1601                 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1602                         {
1603                         vf_refresh_idle(vf);
1604                         }
1605                 else
1606                         {
1607                         /* mark functions can have various side effects - update all columns to be sure */
1608                         vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1609                         /* mark functions can change sidecars too */
1610                         vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, NULL, FALSE);
1611                         }
1612
1613
1614                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1615
1616                 work = work->next;
1617                 }
1618         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1619         g_list_free(slist);
1620 }
1621
1622 /*
1623  *-----------------------------------------------------------------------------
1624  * core (population)
1625  *-----------------------------------------------------------------------------
1626  */
1627
1628 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1629 {
1630         GtkTreeViewColumn *column;
1631         GtkCellRenderer *cell;
1632         GList *list;
1633
1634         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1635         if (!column) return;
1636
1637         gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1638
1639         list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1640         if (!list) return;
1641         cell = list->data;
1642         g_list_free(list);
1643
1644         g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1645         gtk_tree_view_column_set_visible(column, thumb);
1646
1647         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1648         if (!column) return;
1649         gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1650
1651         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1652         if (!column) return;
1653         gtk_tree_view_column_set_visible(column, !multiline);
1654
1655         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1656         if (!column) return;
1657         gtk_tree_view_column_set_visible(column, !multiline);
1658 }
1659
1660 static gboolean vflist_is_multiline(ViewFile *vf)
1661 {
1662         return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1663 }
1664
1665
1666 static void vflist_populate_view(ViewFile *vf, gboolean force)
1667 {
1668         GtkTreeStore *store;
1669         GList *selected;
1670
1671         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1672
1673         vf_thumb_stop(vf);
1674
1675         if (!vf->list)
1676                 {
1677                 vflist_store_clear(vf, FALSE);
1678                 vf_send_update(vf);
1679                 return;
1680                 }
1681
1682         vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1683
1684         selected = vflist_selection_get_list(vf);
1685
1686         vflist_setup_iter_recursive(vf, store, NULL, vf->list, selected, force);
1687
1688         if (selected && vflist_selection_count(vf, NULL) == 0)
1689                 {
1690                 /* all selected files disappeared */
1691                 vflist_select_closest(vf, selected->data);
1692                 }
1693
1694         filelist_free(selected);
1695
1696         vf_send_update(vf);
1697         vf_thumb_update(vf);
1698 }
1699
1700 gboolean vflist_refresh(ViewFile *vf)
1701 {
1702         GList *old_list;
1703         gboolean ret = TRUE;
1704
1705         old_list = vf->list;
1706         vf->list = NULL;
1707
1708         DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1709         if (vf->dir_fd)
1710                 {
1711                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1712
1713                 ret = filelist_read(vf->dir_fd, &vf->list, NULL);
1714
1715                 if (vf->marks_enabled)
1716                         {
1717                         // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1718                         // each time a mark is changed.
1719                         file_data_lock_list(vf->list);
1720                         }
1721                 else
1722                         {
1723                         // FIXME: only do this when needed (aka when we just switched from
1724                         // FIXME: marks-enabled to marks-disabled)
1725                         file_data_unlock_list(vf->list);
1726                         }
1727
1728                 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1729                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1730
1731                 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1732                 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1733                 }
1734
1735         DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1736
1737         vflist_populate_view(vf, FALSE);
1738
1739         DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1740
1741         filelist_free(old_list);
1742         DEBUG_1("%s vflist_refresh: done", get_exec_time());
1743
1744         return ret;
1745 }
1746
1747
1748
1749 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1750
1751 #define CELL_HEIGHT_OVERRIDE 512
1752
1753 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1754 {
1755         GParamSpec *spec;
1756
1757         spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1758         if (spec && G_IS_PARAM_SPEC_INT(spec))
1759                 {
1760                 GParamSpecInt *spec_int;
1761
1762                 spec_int = G_PARAM_SPEC_INT(spec);
1763                 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1764                 }
1765 }
1766
1767 static GdkColor *vflist_listview_color_shifted(GtkWidget *widget)
1768 {
1769         static GdkColor color;
1770         static GtkWidget *done = NULL;
1771
1772         if (done != widget)
1773                 {
1774                 GtkStyle *style;
1775
1776                 style = gtk_widget_get_style(widget);
1777                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
1778                 shift_color(&color, -1, 0);
1779                 done = widget;
1780                 }
1781
1782         return &color;
1783 }
1784
1785 static void vflist_listview_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1786                                      GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1787 {
1788         ViewFile *vf = data;
1789         gboolean set;
1790
1791         gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1792         g_object_set(G_OBJECT(cell),
1793                      "cell-background-gdk", vflist_listview_color_shifted(vf->listview),
1794                      "cell-background-set", set, NULL);
1795 }
1796
1797 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1798 {
1799         GtkTreeViewColumn *column;
1800         GtkCellRenderer *renderer;
1801
1802         column = gtk_tree_view_column_new();
1803         gtk_tree_view_column_set_title(column, title);
1804         gtk_tree_view_column_set_min_width(column, 4);
1805
1806         if (!image)
1807                 {
1808                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1809                 renderer = gtk_cell_renderer_text_new();
1810                 if (right_justify)
1811                         {
1812                         g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
1813                         }
1814                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1815                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
1816                 if (expand)
1817                         gtk_tree_view_column_set_expand(column, TRUE);
1818                 }
1819         else
1820                 {
1821                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1822                 renderer = gtk_cell_renderer_pixbuf_new();
1823                 cell_renderer_height_override(renderer);
1824                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1825                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
1826                 }
1827
1828         gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, NULL);
1829         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1830         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1831
1832         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1833 }
1834
1835 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
1836 {
1837         ViewFile *vf = data;
1838         GtkTreeStore *store;
1839         GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
1840         GtkTreeIter iter;
1841         FileData *fd;
1842         gboolean marked;
1843         guint col_idx;
1844
1845         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1846         if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
1847                 return;
1848
1849         col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
1850
1851         g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
1852
1853         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
1854         marked = !marked;
1855
1856         /* the change has a very limited range and the standard notification would trigger
1857            complete re-read of the directory - try to do only minimal update instead */
1858         file_data_unregister_notify_func(vf_notify_cb, vf);
1859         file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
1860         if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1861                 {
1862                 vf_refresh_idle(vf);
1863                 }
1864         else
1865                 {
1866                 /* mark functions can have various side effects - update all columns to be sure */
1867                 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1868                 /* mark functions can change sidecars too */
1869                 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, NULL, FALSE);
1870                 }
1871         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1872
1873         gtk_tree_path_free(path);
1874 }
1875
1876 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
1877 {
1878         GtkTreeViewColumn *column;
1879         GtkCellRenderer *renderer;
1880
1881         renderer = gtk_cell_renderer_toggle_new();
1882         column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
1883
1884         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1885         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1886         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1887
1888         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1889         gtk_tree_view_column_set_fixed_width(column, 22);
1890         gtk_tree_view_column_set_visible(column, vf->marks_enabled);
1891
1892
1893         g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
1894 }
1895
1896 /*
1897  *-----------------------------------------------------------------------------
1898  * base
1899  *-----------------------------------------------------------------------------
1900  */
1901
1902 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
1903 {
1904         gboolean ret;
1905         if (!dir_fd) return FALSE;
1906         if (vf->dir_fd == dir_fd) return TRUE;
1907
1908         file_data_unref(vf->dir_fd);
1909         vf->dir_fd = file_data_ref(dir_fd);
1910
1911         /* force complete reload */
1912         vflist_store_clear(vf, TRUE);
1913
1914         filelist_free(vf->list);
1915         vf->list = NULL;
1916
1917         ret = vf_refresh(vf);
1918         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
1919         return ret;
1920 }
1921
1922 void vflist_destroy_cb(GtkWidget *widget, gpointer data)
1923 {
1924         ViewFile *vf = data;
1925
1926         file_data_unregister_notify_func(vf_notify_cb, vf);
1927
1928         vflist_select_idle_cancel(vf);
1929         vf_refresh_idle_cancel(vf);
1930         vf_thumb_stop(vf);
1931
1932         filelist_free(vf->list);
1933 }
1934
1935 ViewFile *vflist_new(ViewFile *vf, FileData *dir_fd)
1936 {
1937         GtkTreeStore *store;
1938         GtkTreeSelection *selection;
1939         GType flist_types[FILE_COLUMN_COUNT];
1940         gint i;
1941         gint column;
1942
1943         vf->info = g_new0(ViewFileInfoList, 1);
1944
1945         flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
1946         flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
1947         flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
1948         flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
1949         flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
1950         flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
1951         flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
1952         flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
1953         flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
1954         flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
1955         for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
1956                 flist_types[i] = G_TYPE_BOOLEAN;
1957
1958         store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
1959
1960         vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1961         g_object_unref(store);
1962
1963         g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
1964                          G_CALLBACK(vflist_expand_cb), vf);
1965
1966         g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
1967                          G_CALLBACK(vflist_collapse_cb), vf);
1968
1969         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1970         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1971         gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, NULL);
1972
1973         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
1974         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
1975
1976         column = 0;
1977
1978         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1979                 {
1980                 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
1981                 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
1982                 column++;
1983                 }
1984
1985         vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
1986         g_assert(column == FILE_VIEW_COLUMN_THUMB);
1987         column++;
1988
1989         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
1990         g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
1991         column++;
1992
1993         vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
1994         g_assert(column == FILE_VIEW_COLUMN_SIZE);
1995         column++;
1996
1997         vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
1998         g_assert(column == FILE_VIEW_COLUMN_DATE);
1999         column++;
2000
2001         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2002         return vf;
2003 }
2004
2005 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2006 {
2007         if (VFLIST(vf)->thumbs_enabled == enable) return;
2008
2009         VFLIST(vf)->thumbs_enabled = enable;
2010
2011         /* vflist_populate_view is better than vf_refresh:
2012            - no need to re-read the directory
2013            - force update because the formatted string has changed
2014         */
2015         if (vf->layout)
2016                 {
2017                 vflist_populate_view(vf, TRUE);
2018                 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2019                 }
2020 }
2021
2022 void vflist_marks_set(ViewFile *vf, gboolean enable)
2023 {
2024         GList *columns, *work;
2025
2026         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2027
2028         work = columns;
2029         while (work)
2030                 {
2031                 GtkTreeViewColumn *column = work->data;
2032                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2033                 work = work->next;
2034
2035                 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2036                         gtk_tree_view_column_set_visible(column, enable);
2037                 }
2038
2039         if (enable)
2040                 {
2041                 // Previously disabled, which means that vf->list is complete
2042                 file_data_lock_list(vf->list);
2043                 }
2044         else
2045                 {
2046                 // Previously enabled, which means that vf->list is incomplete
2047                 }
2048
2049         g_list_free(columns);
2050 }
2051
2052 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */