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