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