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