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