Read metadata in the idle loop
[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                                         FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
858                                         FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
859                                         FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
860                                         FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
861 #if FILEDATA_MARKS_SIZE != 10
862 #error this needs to be updated
863 #endif
864 #endif
865                                         FILE_COLUMN_COLOR, FALSE, -1);
866
867 #if !STORE_SET_IS_SLOW
868         {
869         gint i;
870         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
871                 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
872         }
873 #endif
874         g_free(size);
875         g_free(sidecars);
876         g_free(name);
877         g_free(formatted);
878 }
879
880 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
881 {
882         GList *work;
883         GtkTreeIter iter;
884         gboolean valid;
885         gint num_ordered = 0;
886         gint num_prepended = 0;
887
888         valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
889
890         work = list;
891         while (work)
892                 {
893                 gint match;
894                 FileData *fd = work->data;
895                 gboolean done = FALSE;
896
897                 while (!done)
898                         {
899                         FileData *old_fd = NULL;
900                         gint old_version = 0;
901
902                         if (valid)
903                                 {
904                                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
905                                                    FILE_COLUMN_POINTER, &old_fd,
906                                                    FILE_COLUMN_VERSION, &old_version,
907                                                    -1);
908
909                                 if (fd == old_fd)
910                                         {
911                                         match = 0;
912                                         }
913                                 else
914                                         {
915                                         if (parent_iter)
916                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
917                                         else
918                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
919
920                                         if (match == 0) g_warning("multiple fd for the same path");
921                                         }
922
923                                 }
924                         else
925                                 {
926                                 match = -1;
927                                 }
928
929                         if (match < 0)
930                                 {
931                                 GtkTreeIter new;
932
933                                 if (valid)
934                                         {
935                                         num_ordered++;
936                                         gtk_tree_store_insert_before(store, &new, parent_iter, &iter);
937                                         }
938                                 else
939                                         {
940                                         /*
941                                             here should be used gtk_tree_store_append, but this function seems to be O(n)
942                                             and it seems to be much faster to add new entries to the beginning and reorder later
943                                         */
944                                         num_prepended++;
945                                         gtk_tree_store_prepend(store, &new, parent_iter);
946                                         }
947
948                                 vflist_setup_iter(vf, store, &new, file_data_ref(fd));
949                                 vflist_setup_iter_recursive(vf, store, &new, fd->sidecar_files, selected, force);
950
951                                 if (g_list_find(selected, fd))
952                                         {
953                                         /* renamed files - the same fd appears at different position - select it again*/
954                                         GtkTreeSelection *selection;
955                                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
956                                         gtk_tree_selection_select_iter(selection, &new);
957                                         }
958
959                                 done = TRUE;
960                                 }
961                         else if (match > 0)
962                                 {
963                                 file_data_unref(old_fd);
964                                 valid = gtk_tree_store_remove(store, &iter);
965                                 }
966                         else
967                                 {
968                                 num_ordered++;
969                                 if (fd->version != old_version || force)
970                                         {
971                                         vflist_setup_iter(vf, store, &iter, fd);
972                                         vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
973                                         }
974
975                                 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
976
977                                 done = TRUE;
978                                 }
979                         }
980                 work = work->next;
981                 }
982
983         while (valid)
984                 {
985                 FileData *old_fd;
986                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
987                 file_data_unref(old_fd);
988
989                 valid = gtk_tree_store_remove(store, &iter);
990                 }
991
992         /* move the prepended entries to the correct position */
993         if (num_prepended)
994                 {
995                 gint i;
996                 gint num_total = num_prepended + num_ordered;
997                 gint *new_order = g_malloc(num_total * sizeof(gint));
998
999                 for (i = 0; i < num_total; i++)
1000                         {
1001                         if (i < num_ordered)
1002                                 new_order[i] = num_prepended + i;
1003                         else
1004                                 new_order[i] = num_total - 1 - i;
1005                         }
1006                 gtk_tree_store_reorder(store, parent_iter, new_order);
1007
1008                 g_free(new_order);
1009                 }
1010 }
1011
1012 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend)
1013 {
1014         gint i;
1015         GHashTable *fd_idx_hash = g_hash_table_new(NULL, NULL);
1016         gint *new_order;
1017         GtkTreeStore *store;
1018         GList *work;
1019
1020         if (vf->sort_method == type && vf->sort_ascend == ascend) return;
1021         if (!vf->list) return;
1022
1023         work = vf->list;
1024         i = 0;
1025         while (work)
1026                 {
1027                 FileData *fd = work->data;
1028                 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1029                 i++;
1030                 work = work->next;
1031                 }
1032
1033         vf->sort_method = type;
1034         vf->sort_ascend = ascend;
1035
1036         vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1037
1038         new_order = g_malloc(i * sizeof(gint));
1039
1040         work = vf->list;
1041         i = 0;
1042         while (work)
1043                 {
1044                 FileData *fd = work->data;
1045                 new_order[i] = GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd));
1046                 i++;
1047                 work = work->next;
1048                 }
1049
1050         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1051         gtk_tree_store_reorder(store, NULL, new_order);
1052
1053         g_free(new_order);
1054         g_hash_table_destroy(fd_idx_hash);
1055 }
1056
1057 /*
1058  *-----------------------------------------------------------------------------
1059  * thumb updates
1060  *-----------------------------------------------------------------------------
1061  */
1062
1063
1064 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1065 {
1066         GList *work = list;
1067         while (work)
1068                 {
1069                 FileData *fd = work->data;
1070                 work = work->next;
1071
1072                 if (fd->thumb_pixbuf) (*done)++;
1073
1074                 if (fd->sidecar_files)
1075                         {
1076                         vflist_thumb_progress_count(fd->sidecar_files, count, done);
1077                         }
1078                 (*count)++;
1079                 }
1080 }
1081
1082 void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
1083 {
1084         GList *work = list;
1085         while (work)
1086                 {
1087                 FileData *fd = work->data;
1088                 work = work->next;
1089
1090                 if (fd->metadata_in_idle_loaded) (*done)++;
1091
1092                 if (fd->sidecar_files)
1093                         {
1094                         vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1095                         }
1096                 (*count)++;
1097                 }
1098 }
1099
1100 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1101 {
1102         GtkTreeStore *store;
1103         GtkTreeIter iter;
1104
1105         if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1106
1107         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1108         gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1109 }
1110
1111 FileData *vflist_thumb_next_fd(ViewFile *vf)
1112 {
1113         GtkTreePath *tpath;
1114         FileData *fd = NULL;
1115
1116         /* first check the visible files */
1117
1118         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, NULL, NULL, NULL))
1119                 {
1120                 GtkTreeModel *store;
1121                 GtkTreeIter iter;
1122                 gboolean valid = TRUE;
1123
1124                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1125                 gtk_tree_model_get_iter(store, &iter, tpath);
1126                 gtk_tree_path_free(tpath);
1127                 tpath = NULL;
1128
1129                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1130                         {
1131                         FileData *nfd;
1132
1133                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1134
1135                         if (!nfd->thumb_pixbuf) fd = nfd;
1136
1137                         valid = gtk_tree_model_iter_next(store, &iter);
1138                         }
1139                 }
1140
1141         /* then find first undone */
1142
1143         if (!fd)
1144                 {
1145                 GList *work = vf->list;
1146                 while (work && !fd)
1147                         {
1148                         FileData *fd_p = work->data;
1149                         if (!fd_p->thumb_pixbuf)
1150                                 fd = fd_p;
1151                         else
1152                                 {
1153                                 GList *work2 = fd_p->sidecar_files;
1154
1155                                 while (work2 && !fd)
1156                                         {
1157                                         fd_p = work2->data;
1158                                         if (!fd_p->thumb_pixbuf) fd = fd_p;
1159                                         work2 = work2->next;
1160                                         }
1161                                 }
1162                         work = work->next;
1163                         }
1164                 }
1165
1166         return fd;
1167 }
1168
1169 /*
1170  *-----------------------------------------------------------------------------
1171  * row stuff
1172  *-----------------------------------------------------------------------------
1173  */
1174
1175 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1176 {
1177         gint p = 0;
1178         GList *work, *work2;
1179
1180         work = vf->list;
1181         while (work)
1182                 {
1183                 FileData *list_fd = work->data;
1184                 if (list_fd == fd) return p;
1185
1186                 work2 = list_fd->sidecar_files;
1187                 while (work2)
1188                         {
1189                         /* FIXME: return the same index also for sidecars
1190                            it is sufficient for next/prev navigation but it should be rewritten
1191                            without using indexes at all
1192                         */
1193                         FileData *sidecar_fd = work2->data;
1194                         if (sidecar_fd == fd) return p;
1195                         work2 = work2->next;
1196                         }
1197
1198                 work = work->next;
1199                 p++;
1200                 }
1201
1202         return -1;
1203 }
1204
1205 /*
1206  *-----------------------------------------------------------------------------
1207  * selections
1208  *-----------------------------------------------------------------------------
1209  */
1210
1211 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1212 {
1213         GtkTreeModel *store;
1214         GtkTreeSelection *selection;
1215         GList *slist;
1216         GList *work;
1217         gboolean found = FALSE;
1218
1219         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1220         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1221         work = slist;
1222         while (!found && work)
1223                 {
1224                 GtkTreePath *tpath = work->data;
1225                 FileData *fd_n;
1226                 GtkTreeIter iter;
1227
1228                 gtk_tree_model_get_iter(store, &iter, tpath);
1229                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1230                 if (fd_n == fd) found = TRUE;
1231                 work = work->next;
1232                 }
1233         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1234         g_list_free(slist);
1235
1236         return found;
1237 }
1238
1239 gboolean vflist_index_is_selected(ViewFile *vf, gint row)
1240 {
1241         FileData *fd;
1242
1243         fd = vf_index_get_data(vf, row);
1244         return vflist_row_is_selected(vf, fd);
1245 }
1246
1247 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1248 {
1249         GtkTreeModel *store;
1250         GtkTreeSelection *selection;
1251         GList *slist;
1252         guint count;
1253
1254         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1255         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1256
1257         if (bytes)
1258                 {
1259                 gint64 b = 0;
1260                 GList *work;
1261
1262                 work = slist;
1263                 while (work)
1264                         {
1265                         GtkTreePath *tpath = work->data;
1266                         GtkTreeIter iter;
1267                         FileData *fd;
1268
1269                         gtk_tree_model_get_iter(store, &iter, tpath);
1270                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1271                         b += fd->size;
1272
1273                         work = work->next;
1274                         }
1275
1276                 *bytes = b;
1277                 }
1278
1279         count = g_list_length(slist);
1280         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1281         g_list_free(slist);
1282
1283         return count;
1284 }
1285
1286 GList *vflist_selection_get_list(ViewFile *vf)
1287 {
1288         GtkTreeModel *store;
1289         GtkTreeSelection *selection;
1290         GList *slist;
1291         GList *list = NULL;
1292         GList *work;
1293
1294         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1295         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1296         work = slist;
1297         while (work)
1298                 {
1299                 GtkTreePath *tpath = work->data;
1300                 FileData *fd;
1301                 GtkTreeIter iter;
1302
1303                 gtk_tree_model_get_iter(store, &iter, tpath);
1304                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1305
1306                 list = g_list_prepend(list, file_data_ref(fd));
1307
1308                 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1309                         {
1310                         /* unexpanded - add whole group */
1311                         GList *work2 = fd->sidecar_files;
1312                         while (work2)
1313                                 {
1314                                 FileData *sfd = work2->data;
1315                                 list = g_list_prepend(list, file_data_ref(sfd));
1316                                 work2 = work2->next;
1317                                 }
1318                         }
1319
1320                 work = work->next;
1321                 }
1322         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1323         g_list_free(slist);
1324
1325         return g_list_reverse(list);
1326 }
1327
1328 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1329 {
1330         GtkTreeModel *store;
1331         GtkTreeSelection *selection;
1332         GList *slist;
1333         GList *list = NULL;
1334         GList *work;
1335
1336         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1337         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1338         work = slist;
1339         while (work)
1340                 {
1341                 GtkTreePath *tpath = work->data;
1342                 FileData *fd;
1343                 GtkTreeIter iter;
1344
1345                 gtk_tree_model_get_iter(store, &iter, tpath);
1346                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1347
1348                 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1349
1350                 work = work->next;
1351                 }
1352         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1353         g_list_free(slist);
1354
1355         return g_list_reverse(list);
1356 }
1357
1358 void vflist_select_all(ViewFile *vf)
1359 {
1360         GtkTreeSelection *selection;
1361
1362         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1363         gtk_tree_selection_select_all(selection);
1364
1365         VFLIST(vf)->select_fd = NULL;
1366 }
1367
1368 void vflist_select_none(ViewFile *vf)
1369 {
1370         GtkTreeSelection *selection;
1371
1372         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1373         gtk_tree_selection_unselect_all(selection);
1374 }
1375
1376 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1377 {
1378         GtkTreePath *tpath;
1379         gboolean result;
1380
1381         tpath = gtk_tree_model_get_path(store, iter);
1382         result = gtk_tree_path_prev(tpath);
1383         if (result)
1384                 gtk_tree_model_get_iter(store, iter, tpath);
1385
1386         gtk_tree_path_free(tpath);
1387
1388         return result;
1389 }
1390
1391 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1392 {
1393         if (!gtk_tree_model_get_iter_first(store, iter))
1394                 return FALSE;
1395
1396         while (TRUE)
1397                 {
1398                 GtkTreeIter next = *iter;
1399
1400                 if (gtk_tree_model_iter_next(store, &next))
1401                         *iter = next;
1402                 else
1403                         break;
1404                 }
1405
1406         return TRUE;
1407 }
1408
1409 void vflist_select_invert(ViewFile *vf)
1410 {
1411         GtkTreeIter iter;
1412         GtkTreeSelection *selection;
1413         GtkTreeModel *store;
1414         gboolean valid;
1415
1416         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1417         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1418
1419         /* Backward iteration prevents scrolling to the end of the list,
1420          * it scrolls to the first selected row instead. */
1421         valid = tree_model_get_iter_last(store, &iter);
1422
1423         while (valid)
1424                 {
1425                 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1426
1427                 if (selected)
1428                         gtk_tree_selection_unselect_iter(selection, &iter);
1429                 else
1430                         gtk_tree_selection_select_iter(selection, &iter);
1431
1432                 valid = tree_model_iter_prev(store, &iter);
1433                 }
1434 }
1435
1436 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1437 {
1438         GtkTreeIter iter;
1439
1440         if (vflist_find_row(vf, fd, &iter) < 0) return;
1441
1442         tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1443
1444         if (!vflist_row_is_selected(vf, fd))
1445                 {
1446                 GtkTreeSelection *selection;
1447                 GtkTreeModel *store;
1448                 GtkTreePath *tpath;
1449
1450                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1451                 gtk_tree_selection_unselect_all(selection);
1452                 gtk_tree_selection_select_iter(selection, &iter);
1453                 vflist_move_cursor(vf, &iter);
1454
1455                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1456                 tpath = gtk_tree_model_get_path(store, &iter);
1457                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, NULL, FALSE);
1458                 gtk_tree_path_free(tpath);
1459                 }
1460 }
1461
1462 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1463 {
1464         GList *work;
1465         FileData *fd = NULL;
1466
1467         if (sel_fd->parent) sel_fd = sel_fd->parent;
1468         work = vf->list;
1469
1470         while (work)
1471                 {
1472                 gint match;
1473                 fd = work->data;
1474                 work = work->next;
1475
1476                 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1477
1478                 if (match >= 0) break;
1479                 }
1480
1481         if (fd) vflist_select_by_fd(vf, fd);
1482
1483 }
1484
1485 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1486 {
1487         GtkTreeModel *store;
1488         GtkTreeIter iter;
1489         GtkTreeSelection *selection;
1490         gboolean valid;
1491         gint n = mark - 1;
1492
1493         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1494
1495         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1496         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1497
1498         valid = gtk_tree_model_get_iter_first(store, &iter);
1499         while (valid)
1500                 {
1501                 FileData *fd;
1502                 gboolean mark_val, selected;
1503                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1504
1505                 mark_val = file_data_get_mark(fd, n);
1506                 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1507
1508                 switch (mode)
1509                         {
1510                         case MTS_MODE_SET: selected = mark_val;
1511                                 break;
1512                         case MTS_MODE_OR: selected = mark_val || selected;
1513                                 break;
1514                         case MTS_MODE_AND: selected = mark_val && selected;
1515                                 break;
1516                         case MTS_MODE_MINUS: selected = !mark_val && selected;
1517                                 break;
1518                         }
1519
1520                 if (selected)
1521                         gtk_tree_selection_select_iter(selection, &iter);
1522                 else
1523                         gtk_tree_selection_unselect_iter(selection, &iter);
1524
1525                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1526                 }
1527 }
1528
1529 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1530 {
1531         GtkTreeModel *store;
1532         GtkTreeSelection *selection;
1533         GList *slist;
1534         GList *work;
1535         gint n = mark - 1;
1536
1537         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1538
1539         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1540         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1541         work = slist;
1542         while (work)
1543                 {
1544                 GtkTreePath *tpath = work->data;
1545                 FileData *fd;
1546                 GtkTreeIter iter;
1547
1548                 gtk_tree_model_get_iter(store, &iter, tpath);
1549                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1550
1551                 /* the change has a very limited range and the standard notification would trigger
1552                    complete re-read of the directory - try to do only minimal update instead */
1553                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1554
1555                 switch (mode)
1556                         {
1557                         case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1558                                 break;
1559                         case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1560                                 break;
1561                         case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1562                                 break;
1563                         }
1564
1565                 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1566                         {
1567                         vf_refresh_idle(vf);
1568                         }
1569                 else
1570                         {
1571                         /* mark functions can have various side effects - update all columns to be sure */
1572                         vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1573                         /* mark functions can change sidecars too */
1574                         vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, NULL, FALSE);
1575                         }
1576
1577
1578                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1579
1580                 work = work->next;
1581                 }
1582         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1583         g_list_free(slist);
1584 }
1585
1586 /*
1587  *-----------------------------------------------------------------------------
1588  * core (population)
1589  *-----------------------------------------------------------------------------
1590  */
1591
1592 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1593 {
1594         GtkTreeViewColumn *column;
1595         GtkCellRenderer *cell;
1596         GList *list;
1597
1598         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1599         if (!column) return;
1600
1601         gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1602
1603         list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1604         if (!list) return;
1605         cell = list->data;
1606         g_list_free(list);
1607
1608         g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1609         gtk_tree_view_column_set_visible(column, thumb);
1610
1611         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1612         if (!column) return;
1613         gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1614
1615         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1616         if (!column) return;
1617         gtk_tree_view_column_set_visible(column, !multiline);
1618
1619         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1620         if (!column) return;
1621         gtk_tree_view_column_set_visible(column, !multiline);
1622 }
1623
1624 static gboolean vflist_is_multiline(ViewFile *vf)
1625 {
1626         return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1627 }
1628
1629
1630 static void vflist_populate_view(ViewFile *vf, gboolean force)
1631 {
1632         GtkTreeStore *store;
1633         GList *selected;
1634
1635         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1636
1637         vf_thumb_stop(vf);
1638
1639         if (!vf->list)
1640                 {
1641                 vflist_store_clear(vf, FALSE);
1642                 vf_send_update(vf);
1643                 return;
1644                 }
1645
1646         vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1647
1648         selected = vflist_selection_get_list(vf);
1649
1650         vflist_setup_iter_recursive(vf, store, NULL, vf->list, selected, force);
1651
1652         if (selected && vflist_selection_count(vf, NULL) == 0)
1653                 {
1654                 /* all selected files disappeared */
1655                 vflist_select_closest(vf, selected->data);
1656                 }
1657
1658         filelist_free(selected);
1659
1660         vf_send_update(vf);
1661         vf_thumb_update(vf);
1662 }
1663
1664 gboolean vflist_refresh(ViewFile *vf)
1665 {
1666         GList *old_list;
1667         gboolean ret = TRUE;
1668
1669         old_list = vf->list;
1670         vf->list = NULL;
1671
1672         DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1673         if (vf->dir_fd)
1674                 {
1675                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1676
1677                 ret = filelist_read(vf->dir_fd, &vf->list, NULL);
1678
1679                 if (vf->marks_enabled)
1680                         {
1681                         // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1682                         // each time a mark is changed.
1683                         file_data_lock_list(vf->list);
1684                         }
1685                 else
1686                         {
1687                         // FIXME: only do this when needed (aka when we just switched from
1688                         // FIXME: marks-enabled to marks-disabled)
1689                         file_data_unlock_list(vf->list);
1690                         }
1691
1692                 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1693                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1694
1695                 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1696                 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1697                 }
1698
1699         DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1700
1701         vflist_populate_view(vf, FALSE);
1702
1703         DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1704
1705         filelist_free(old_list);
1706         DEBUG_1("%s vflist_refresh: done", get_exec_time());
1707
1708         return ret;
1709 }
1710
1711
1712
1713 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1714
1715 #define CELL_HEIGHT_OVERRIDE 512
1716
1717 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1718 {
1719         GParamSpec *spec;
1720
1721         spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1722         if (spec && G_IS_PARAM_SPEC_INT(spec))
1723                 {
1724                 GParamSpecInt *spec_int;
1725
1726                 spec_int = G_PARAM_SPEC_INT(spec);
1727                 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1728                 }
1729 }
1730
1731 static GdkColor *vflist_listview_color_shifted(GtkWidget *widget)
1732 {
1733         static GdkColor color;
1734         static GtkWidget *done = NULL;
1735
1736         if (done != widget)
1737                 {
1738                 GtkStyle *style;
1739
1740                 style = gtk_widget_get_style(widget);
1741                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
1742                 shift_color(&color, -1, 0);
1743                 done = widget;
1744                 }
1745
1746         return &color;
1747 }
1748
1749 static void vflist_listview_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1750                                      GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1751 {
1752         ViewFile *vf = data;
1753         gboolean set;
1754
1755         gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1756         g_object_set(G_OBJECT(cell),
1757                      "cell-background-gdk", vflist_listview_color_shifted(vf->listview),
1758                      "cell-background-set", set, NULL);
1759 }
1760
1761 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1762 {
1763         GtkTreeViewColumn *column;
1764         GtkCellRenderer *renderer;
1765
1766         column = gtk_tree_view_column_new();
1767         gtk_tree_view_column_set_title(column, title);
1768         gtk_tree_view_column_set_min_width(column, 4);
1769
1770         if (!image)
1771                 {
1772                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1773                 renderer = gtk_cell_renderer_text_new();
1774                 if (right_justify)
1775                         {
1776                         g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
1777                         }
1778                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1779                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
1780                 if (expand)
1781                         gtk_tree_view_column_set_expand(column, TRUE);
1782                 }
1783         else
1784                 {
1785                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1786                 renderer = gtk_cell_renderer_pixbuf_new();
1787                 cell_renderer_height_override(renderer);
1788                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1789                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
1790                 }
1791
1792         gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, NULL);
1793         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1794         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1795
1796         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1797 }
1798
1799 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
1800 {
1801         ViewFile *vf = data;
1802         GtkTreeStore *store;
1803         GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
1804         GtkTreeIter iter;
1805         FileData *fd;
1806         gboolean marked;
1807         guint col_idx;
1808
1809         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1810         if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
1811                 return;
1812
1813         col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
1814
1815         g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
1816
1817         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
1818         marked = !marked;
1819
1820         /* the change has a very limited range and the standard notification would trigger
1821            complete re-read of the directory - try to do only minimal update instead */
1822         file_data_unregister_notify_func(vf_notify_cb, vf);
1823         file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
1824         if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1825                 {
1826                 vf_refresh_idle(vf);
1827                 }
1828         else
1829                 {
1830                 /* mark functions can have various side effects - update all columns to be sure */
1831                 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1832                 /* mark functions can change sidecars too */
1833                 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, NULL, FALSE);
1834                 }
1835         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1836
1837         gtk_tree_path_free(path);
1838 }
1839
1840 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
1841 {
1842         GtkTreeViewColumn *column;
1843         GtkCellRenderer *renderer;
1844
1845         renderer = gtk_cell_renderer_toggle_new();
1846         column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
1847
1848         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1849         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1850         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1851
1852         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1853         gtk_tree_view_column_set_fixed_width(column, 22);
1854         gtk_tree_view_column_set_visible(column, vf->marks_enabled);
1855
1856
1857         g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
1858 }
1859
1860 /*
1861  *-----------------------------------------------------------------------------
1862  * base
1863  *-----------------------------------------------------------------------------
1864  */
1865
1866 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
1867 {
1868         gboolean ret;
1869         if (!dir_fd) return FALSE;
1870         if (vf->dir_fd == dir_fd) return TRUE;
1871
1872         file_data_unref(vf->dir_fd);
1873         vf->dir_fd = file_data_ref(dir_fd);
1874
1875         /* force complete reload */
1876         vflist_store_clear(vf, TRUE);
1877
1878         filelist_free(vf->list);
1879         vf->list = NULL;
1880
1881         ret = vf_refresh(vf);
1882         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
1883         return ret;
1884 }
1885
1886 void vflist_destroy_cb(GtkWidget *widget, gpointer data)
1887 {
1888         ViewFile *vf = data;
1889
1890         file_data_unregister_notify_func(vf_notify_cb, vf);
1891
1892         vflist_select_idle_cancel(vf);
1893         vf_refresh_idle_cancel(vf);
1894         vf_thumb_stop(vf);
1895
1896         filelist_free(vf->list);
1897 }
1898
1899 ViewFile *vflist_new(ViewFile *vf, FileData *dir_fd)
1900 {
1901         GtkTreeStore *store;
1902         GtkTreeSelection *selection;
1903         GType flist_types[FILE_COLUMN_COUNT];
1904         gint i;
1905         gint column;
1906
1907         vf->info = g_new0(ViewFileInfoList, 1);
1908
1909         flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
1910         flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
1911         flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
1912         flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
1913         flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
1914         flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
1915         flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
1916         flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
1917         flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
1918         flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
1919         for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
1920                 flist_types[i] = G_TYPE_BOOLEAN;
1921
1922         store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
1923
1924         vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1925         g_object_unref(store);
1926
1927         g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
1928                          G_CALLBACK(vflist_expand_cb), vf);
1929
1930         g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
1931                          G_CALLBACK(vflist_collapse_cb), vf);
1932
1933         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1934         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1935         gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, NULL);
1936
1937         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
1938         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
1939
1940         column = 0;
1941
1942         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1943                 {
1944                 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
1945                 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
1946                 column++;
1947                 }
1948
1949         vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
1950         g_assert(column == FILE_VIEW_COLUMN_THUMB);
1951         column++;
1952
1953         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
1954         g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
1955         column++;
1956
1957         vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
1958         g_assert(column == FILE_VIEW_COLUMN_SIZE);
1959         column++;
1960
1961         vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
1962         g_assert(column == FILE_VIEW_COLUMN_DATE);
1963         column++;
1964
1965         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1966         return vf;
1967 }
1968
1969 void vflist_thumb_set(ViewFile *vf, gboolean enable)
1970 {
1971         if (VFLIST(vf)->thumbs_enabled == enable) return;
1972
1973         VFLIST(vf)->thumbs_enabled = enable;
1974
1975         /* vflist_populate_view is better than vf_refresh:
1976            - no need to re-read the directory
1977            - force update because the formatted string has changed
1978         */
1979         if (vf->layout)
1980                 {
1981                 vflist_populate_view(vf, TRUE);
1982                 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
1983                 }
1984 }
1985
1986 void vflist_marks_set(ViewFile *vf, gboolean enable)
1987 {
1988         GList *columns, *work;
1989
1990         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
1991
1992         work = columns;
1993         while (work)
1994                 {
1995                 GtkTreeViewColumn *column = work->data;
1996                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
1997                 work = work->next;
1998
1999                 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2000                         gtk_tree_view_column_set_visible(column, enable);
2001                 }
2002
2003         if (enable)
2004                 {
2005                 // Previously disabled, which means that vf->list is complete
2006                 file_data_lock_list(vf->list);
2007                 }
2008         else
2009                 {
2010                 // Previously enabled, which means that vf->list is incomplete
2011                 }
2012
2013         g_list_free(columns);
2014 }
2015
2016 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */