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