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