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