Fix building with --enable-debug-flags
[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 #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 (defined_mouse_buttons(widget, bevent, vf->layout))
670                 {
671                 return TRUE;
672                 }
673
674         if (bevent->button == MOUSE_BUTTON_MIDDLE)
675                 {
676                 vflist_color_set(vf, VFLIST(vf)->click_fd, FALSE);
677                 }
678
679         if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE)
680                 {
681                 return TRUE;
682                 }
683
684         if ((bevent->x != 0 || bevent->y != 0) &&
685             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
686                                           &tpath, NULL, NULL, NULL))
687                 {
688                 GtkTreeModel *store;
689
690                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
691                 gtk_tree_model_get_iter(store, &iter, tpath);
692                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
693                 gtk_tree_path_free(tpath);
694                 }
695
696         if (bevent->button == MOUSE_BUTTON_MIDDLE)
697                 {
698                 if (fd && VFLIST(vf)->click_fd == fd)
699                         {
700                         GtkTreeSelection *selection;
701
702                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
703                         if (vflist_row_is_selected(vf, fd))
704                                 {
705                                 gtk_tree_selection_unselect_iter(selection, &iter);
706                                 }
707                         else
708                                 {
709                                 gtk_tree_selection_select_iter(selection, &iter);
710                                 }
711                         }
712                 return TRUE;
713                 }
714
715         if (fd && VFLIST(vf)->click_fd == fd &&
716             !(bevent->state & GDK_SHIFT_MASK ) &&
717             !(bevent->state & GDK_CONTROL_MASK ) &&
718             vflist_row_is_selected(vf, fd))
719                 {
720                 GtkTreeSelection *selection;
721
722                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
723                 gtk_tree_selection_unselect_all(selection);
724                 gtk_tree_selection_select_iter(selection, &iter);
725                 vflist_move_cursor(vf, &iter);
726                 }
727
728         return FALSE;
729 }
730
731 static void vflist_select_image(ViewFile *vf, FileData *sel_fd)
732 {
733         FileData *read_ahead_fd = NULL;
734         gint row;
735         FileData *cur_fd;
736
737         if (!sel_fd) return;
738
739         cur_fd = layout_image_get_fd(vf->layout);
740         if (sel_fd == cur_fd) return; /* no change */
741
742         row = g_list_index(vf->list, sel_fd);
743         // FIXME sidecar data
744
745         if (sel_fd && options->image.enable_read_ahead && row >= 0)
746                 {
747                 if (row > g_list_index(vf->list, cur_fd) &&
748                     (guint) (row + 1) < vf_count(vf, NULL))
749                         {
750                         read_ahead_fd = vf_index_get_data(vf, row + 1);
751                         }
752                 else if (row > 0)
753                         {
754                         read_ahead_fd = vf_index_get_data(vf, row - 1);
755                         }
756                 }
757
758         layout_image_set_with_ahead(vf->layout, sel_fd, read_ahead_fd);
759 }
760
761 static gboolean vflist_select_idle_cb(gpointer data)
762 {
763         ViewFile *vf = data;
764
765         if (!vf->layout)
766                 {
767                 VFLIST(vf)->select_idle_id = 0;
768                 return FALSE;
769                 }
770
771         vf_send_update(vf);
772
773         if (VFLIST(vf)->select_fd)
774                 {
775                 vflist_select_image(vf, VFLIST(vf)->select_fd);
776                 VFLIST(vf)->select_fd = NULL;
777                 }
778
779         VFLIST(vf)->select_idle_id = 0;
780         return FALSE;
781 }
782
783 static void vflist_select_idle_cancel(ViewFile *vf)
784 {
785         if (VFLIST(vf)->select_idle_id)
786                 {
787                 g_source_remove(VFLIST(vf)->select_idle_id);
788                 VFLIST(vf)->select_idle_id = 0;
789                 }
790 }
791
792 static gboolean vflist_select_cb(GtkTreeSelection *selection, GtkTreeModel *store, GtkTreePath *tpath,
793                                  gboolean path_currently_selected, gpointer data)
794 {
795         ViewFile *vf = data;
796         GtkTreeIter iter;
797         GtkTreePath *cursor_path;
798
799         gtk_tree_view_get_cursor(GTK_TREE_VIEW(vf->listview), &cursor_path, NULL);
800         if (cursor_path)
801                 {
802                 gtk_tree_model_get_iter(store, &iter, cursor_path);
803                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &VFLIST(vf)->select_fd, -1);
804                 gtk_tree_path_free(cursor_path);
805                 }
806         else
807                 {
808                 VFLIST(vf)->select_fd = NULL;
809                 }
810
811         if (vf->layout &&
812             !VFLIST(vf)->select_idle_id)
813                 {
814                 VFLIST(vf)->select_idle_id = g_idle_add(vflist_select_idle_cb, vf);
815                 }
816
817         return TRUE;
818 }
819
820 static void vflist_expand_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
821 {
822         ViewFile *vf = data;
823         vflist_set_expanded(vf, iter, TRUE);
824 }
825
826 static void vflist_collapse_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
827 {
828         ViewFile *vf = data;
829         vflist_set_expanded(vf, iter, FALSE);
830 }
831
832 /*
833  *-----------------------------------------------------------------------------
834  * misc
835  *-----------------------------------------------------------------------------
836  */
837
838
839 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)
840  {
841         gboolean multiline = vflist_is_multiline(vf);
842         gchar *text;
843
844         if (multiline)
845                 {
846                 if (with_stars)
847                         {
848                                         text = g_strdup_printf("%s %s\n%s\n%s\n%s", name, expanded ? "" : sidecars, size, time, star_rating);
849                         }
850                 else
851                         {
852                         text = g_strdup_printf("%s %s\n%s\n%s", name, expanded ? "" : sidecars, size, time);
853                         }
854                 }
855         else
856                 {
857                 text = g_strdup_printf("%s %s", name, expanded ? "" : sidecars);
858                 }
859         return text;
860 }
861
862 static void vflist_set_expanded(ViewFile *vf, GtkTreeIter *iter, gboolean expanded)
863 {
864         GtkTreeStore *store;
865         gchar *name;
866         gchar *sidecars;
867         gchar *size;
868         gchar *time;
869         gchar *formatted;
870         gchar *formatted_with_stars;
871         gchar *star_rating;
872         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
873
874         gtk_tree_model_get(GTK_TREE_MODEL(store), iter,
875                                         FILE_COLUMN_NAME, &name,
876                                         FILE_COLUMN_SIDECARS, &sidecars,
877                                         FILE_COLUMN_SIZE, &size,
878                                         FILE_COLUMN_DATE, &time,
879                                         FILE_COLUMN_STAR_RATING, &star_rating,
880                                         -1);
881
882         formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, NULL);
883         formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
884
885         gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED, formatted,
886                                         FILE_COLUMN_EXPANDED, expanded,
887                                         -1);
888         gtk_tree_store_set(store, iter, FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
889                                         FILE_COLUMN_EXPANDED, expanded,
890                                         -1);
891         g_free(time);
892         g_free(size);
893         g_free(sidecars);
894         g_free(name);
895         g_free(formatted);
896 }
897
898 static void vflist_setup_iter(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *iter, FileData *fd)
899 {
900         gchar *size;
901         gchar *sidecars = NULL;
902         gchar *name;
903         const gchar *time = text_from_time(fd->date);
904         gchar *link = islink(fd->path) ? GQ_LINK_STR : "";
905         const gchar *disabled_grouping;
906         gchar *formatted;
907         gchar *formatted_with_stars;
908         gboolean expanded = FALSE;
909         gchar *star_rating;
910
911         if (options->show_star_rating)
912                 {
913                 star_rating = metadata_read_rating_stars(fd);
914                 }
915         else
916                 {
917                 star_rating = NULL;
918                 }
919
920         if (fd->sidecar_files) /* expanded has no effect on files without sidecars */
921                 {
922                 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, FILE_COLUMN_EXPANDED, &expanded, -1);
923                 }
924
925         sidecars = file_data_sc_list_to_string(fd);
926
927         disabled_grouping = fd->disable_grouping ? _(" [NO GROUPING]") : "";
928         name = g_strdup_printf("%s%s%s", link, fd->name, disabled_grouping);
929         size = text_from_size(fd->size);
930
931         formatted = vflist_get_formatted(vf, name, sidecars, size, time, expanded, FALSE, NULL);
932         formatted_with_stars = vflist_get_formatted(vf, name, sidecars, size, time, expanded, TRUE, star_rating);
933
934         gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
935                                         FILE_COLUMN_VERSION, fd->version,
936                                         FILE_COLUMN_THUMB, fd->thumb_pixbuf,
937                                         FILE_COLUMN_FORMATTED, formatted,
938                                         FILE_COLUMN_FORMATTED_WITH_STARS, formatted_with_stars,
939                                         FILE_COLUMN_SIDECARS, sidecars,
940                                         FILE_COLUMN_NAME, name,
941                                         FILE_COLUMN_STAR_RATING, star_rating,
942                                         FILE_COLUMN_SIZE, size,
943                                         FILE_COLUMN_DATE, time,
944 #define STORE_SET_IS_SLOW 1
945 #if STORE_SET_IS_SLOW
946 /* this is 3x faster on a directory with 20000 files */
947                                         FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
948                                         FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
949                                         FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
950                                         FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
951                                         FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
952                                         FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
953                                         FILE_COLUMN_MARKS + 6, file_data_get_mark(fd, 6),
954                                         FILE_COLUMN_MARKS + 7, file_data_get_mark(fd, 7),
955                                         FILE_COLUMN_MARKS + 8, file_data_get_mark(fd, 8),
956                                         FILE_COLUMN_MARKS + 9, file_data_get_mark(fd, 9),
957 #if FILEDATA_MARKS_SIZE != 10
958 #error this needs to be updated
959 #endif
960 #endif
961                                         FILE_COLUMN_COLOR, FALSE, -1);
962
963 #if !STORE_SET_IS_SLOW
964         {
965         gint i;
966         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
967                 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
968         }
969 #endif
970         g_free(size);
971         g_free(sidecars);
972         g_free(name);
973         g_free(formatted);
974 }
975
976 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected, gboolean force)
977 {
978         GList *work;
979         GtkTreeIter iter;
980         gboolean valid;
981         gint num_ordered = 0;
982         gint num_prepended = 0;
983
984         valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
985
986         work = list;
987         while (work)
988                 {
989                 gint match;
990                 FileData *fd = work->data;
991                 gboolean done = FALSE;
992
993                 while (!done)
994                         {
995                         FileData *old_fd = NULL;
996                         gint old_version = 0;
997
998                         if (valid)
999                                 {
1000                                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1001                                                    FILE_COLUMN_POINTER, &old_fd,
1002                                                    FILE_COLUMN_VERSION, &old_version,
1003                                                    -1);
1004
1005                                 if (fd == old_fd)
1006                                         {
1007                                         match = 0;
1008                                         }
1009                                 else
1010                                         {
1011                                         if (parent_iter)
1012                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
1013                                         else
1014                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
1015
1016                                         if (match == 0) g_warning("multiple fd for the same path");
1017                                         }
1018
1019                                 }
1020                         else
1021                                 {
1022                                 match = -1;
1023                                 }
1024
1025                         if (match < 0)
1026                                 {
1027                                 GtkTreeIter new;
1028
1029                                 if (valid)
1030                                         {
1031                                         num_ordered++;
1032                                         gtk_tree_store_insert_before(store, &new, parent_iter, &iter);
1033                                         }
1034                                 else
1035                                         {
1036                                         /*
1037                                             here should be used gtk_tree_store_append, but this function seems to be O(n)
1038                                             and it seems to be much faster to add new entries to the beginning and reorder later
1039                                         */
1040                                         num_prepended++;
1041                                         gtk_tree_store_prepend(store, &new, parent_iter);
1042                                         }
1043
1044                                 vflist_setup_iter(vf, store, &new, file_data_ref(fd));
1045                                 vflist_setup_iter_recursive(vf, store, &new, fd->sidecar_files, selected, force);
1046
1047                                 if (g_list_find(selected, fd))
1048                                         {
1049                                         /* renamed files - the same fd appears at different position - select it again*/
1050                                         GtkTreeSelection *selection;
1051                                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1052                                         gtk_tree_selection_select_iter(selection, &new);
1053                                         }
1054
1055                                 done = TRUE;
1056                                 }
1057                         else if (match > 0)
1058                                 {
1059                                 file_data_unref(old_fd);
1060                                 valid = gtk_tree_store_remove(store, &iter);
1061                                 }
1062                         else
1063                                 {
1064                                 num_ordered++;
1065                                 if (fd->version != old_version || force)
1066                                         {
1067                                         vflist_setup_iter(vf, store, &iter, fd);
1068                                         vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected, force);
1069                                         }
1070
1071                                 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1072
1073                                 done = TRUE;
1074                                 }
1075                         }
1076                 work = work->next;
1077                 }
1078
1079         while (valid)
1080                 {
1081                 FileData *old_fd;
1082                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
1083                 file_data_unref(old_fd);
1084
1085                 valid = gtk_tree_store_remove(store, &iter);
1086                 }
1087
1088         /* move the prepended entries to the correct position */
1089         if (num_prepended)
1090                 {
1091                 gint i;
1092                 gint num_total = num_prepended + num_ordered;
1093                 gint *new_order = g_malloc(num_total * sizeof(gint));
1094
1095                 for (i = 0; i < num_total; i++)
1096                         {
1097                         if (i < num_ordered)
1098                                 new_order[i] = num_prepended + i;
1099                         else
1100                                 new_order[i] = num_total - 1 - i;
1101                         }
1102                 gtk_tree_store_reorder(store, parent_iter, new_order);
1103
1104                 g_free(new_order);
1105                 }
1106 }
1107
1108 void vflist_sort_set(ViewFile *vf, SortType type, gboolean ascend)
1109 {
1110         gint i;
1111         GHashTable *fd_idx_hash = g_hash_table_new(NULL, NULL);
1112         gint *new_order;
1113         GtkTreeStore *store;
1114         GList *work;
1115
1116         if (vf->sort_method == type && vf->sort_ascend == ascend) return;
1117         if (!vf->list) return;
1118
1119         work = vf->list;
1120         i = 0;
1121         while (work)
1122                 {
1123                 FileData *fd = work->data;
1124                 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
1125                 i++;
1126                 work = work->next;
1127                 }
1128
1129         vf->sort_method = type;
1130         vf->sort_ascend = ascend;
1131
1132         vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1133
1134         new_order = g_malloc(i * sizeof(gint));
1135
1136         work = vf->list;
1137         i = 0;
1138         while (work)
1139                 {
1140                 FileData *fd = work->data;
1141                 new_order[i] = GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd));
1142                 i++;
1143                 work = work->next;
1144                 }
1145
1146         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1147         gtk_tree_store_reorder(store, NULL, new_order);
1148
1149         g_free(new_order);
1150         g_hash_table_destroy(fd_idx_hash);
1151 }
1152
1153 /*
1154  *-----------------------------------------------------------------------------
1155  * thumb updates
1156  *-----------------------------------------------------------------------------
1157  */
1158
1159
1160 void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
1161 {
1162         GList *work = list;
1163         while (work)
1164                 {
1165                 FileData *fd = work->data;
1166                 work = work->next;
1167
1168                 if (fd->thumb_pixbuf) (*done)++;
1169
1170                 if (fd->sidecar_files)
1171                         {
1172                         vflist_thumb_progress_count(fd->sidecar_files, count, done);
1173                         }
1174                 (*count)++;
1175                 }
1176 }
1177
1178 void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
1179 {
1180         GList *work = list;
1181         while (work)
1182                 {
1183                 FileData *fd = work->data;
1184                 work = work->next;
1185
1186                 if (fd->metadata_in_idle_loaded) (*done)++;
1187
1188                 if (fd->sidecar_files)
1189                         {
1190                         vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
1191                         }
1192                 (*count)++;
1193                 }
1194 }
1195
1196 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
1197 {
1198         GtkTreeStore *store;
1199         GtkTreeIter iter;
1200
1201         if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
1202
1203         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1204         gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->thumb_pixbuf, -1);
1205 }
1206
1207 FileData *vflist_thumb_next_fd(ViewFile *vf)
1208 {
1209         GtkTreePath *tpath;
1210         FileData *fd = NULL;
1211
1212         /* first check the visible files */
1213
1214         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, NULL, NULL, NULL))
1215                 {
1216                 GtkTreeModel *store;
1217                 GtkTreeIter iter;
1218                 gboolean valid = TRUE;
1219
1220                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1221                 gtk_tree_model_get_iter(store, &iter, tpath);
1222                 gtk_tree_path_free(tpath);
1223                 tpath = NULL;
1224
1225                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1226                         {
1227                         FileData *nfd;
1228
1229                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1230
1231                         if (!nfd->thumb_pixbuf) fd = nfd;
1232
1233                         valid = gtk_tree_model_iter_next(store, &iter);
1234                         }
1235                 }
1236
1237         /* then find first undone */
1238
1239         if (!fd)
1240                 {
1241                 GList *work = vf->list;
1242                 while (work && !fd)
1243                         {
1244                         FileData *fd_p = work->data;
1245                         if (!fd_p->thumb_pixbuf)
1246                                 fd = fd_p;
1247                         else
1248                                 {
1249                                 GList *work2 = fd_p->sidecar_files;
1250
1251                                 while (work2 && !fd)
1252                                         {
1253                                         fd_p = work2->data;
1254                                         if (!fd_p->thumb_pixbuf) fd = fd_p;
1255                                         work2 = work2->next;
1256                                         }
1257                                 }
1258                         work = work->next;
1259                         }
1260                 }
1261
1262         return fd;
1263 }
1264
1265 /*
1266  *-----------------------------------------------------------------------------
1267  * row stuff
1268  *-----------------------------------------------------------------------------
1269  */
1270
1271 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1272 {
1273         gint p = 0;
1274         GList *work, *work2;
1275
1276         work = vf->list;
1277         while (work)
1278                 {
1279                 FileData *list_fd = work->data;
1280                 if (list_fd == fd) return p;
1281
1282                 work2 = list_fd->sidecar_files;
1283                 while (work2)
1284                         {
1285                         /* FIXME: return the same index also for sidecars
1286                            it is sufficient for next/prev navigation but it should be rewritten
1287                            without using indexes at all
1288                         */
1289                         FileData *sidecar_fd = work2->data;
1290                         if (sidecar_fd == fd) return p;
1291                         work2 = work2->next;
1292                         }
1293
1294                 work = work->next;
1295                 p++;
1296                 }
1297
1298         return -1;
1299 }
1300
1301 /*
1302  *-----------------------------------------------------------------------------
1303  * selections
1304  *-----------------------------------------------------------------------------
1305  */
1306
1307 void gtk_tree_path_free_wrapper_file_list(void *data, void *useradata)
1308 {
1309         gtk_tree_path_free(data);
1310 }
1311
1312
1313 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1314 {
1315         GtkTreeModel *store;
1316         GtkTreeSelection *selection;
1317         GList *slist;
1318         GList *work;
1319         gboolean found = FALSE;
1320
1321         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1322         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1323         work = slist;
1324         while (!found && work)
1325                 {
1326                 GtkTreePath *tpath = work->data;
1327                 FileData *fd_n;
1328                 GtkTreeIter iter;
1329
1330                 gtk_tree_model_get_iter(store, &iter, tpath);
1331                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1332                 if (fd_n == fd) found = TRUE;
1333                 work = work->next;
1334                 }
1335         g_list_foreach(slist, (GFunc)gtk_tree_path_free_wrapper_file_list, NULL);
1336         g_list_free(slist);
1337
1338         return found;
1339 }
1340
1341 gboolean vflist_index_is_selected(ViewFile *vf, gint row)
1342 {
1343         FileData *fd;
1344
1345         fd = vf_index_get_data(vf, row);
1346         return vflist_row_is_selected(vf, fd);
1347 }
1348
1349 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1350 {
1351         GtkTreeModel *store;
1352         GtkTreeSelection *selection;
1353         GList *slist;
1354         guint count;
1355
1356         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1357         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1358
1359         if (bytes)
1360                 {
1361                 gint64 b = 0;
1362                 GList *work;
1363
1364                 work = slist;
1365                 while (work)
1366                         {
1367                         GtkTreePath *tpath = work->data;
1368                         GtkTreeIter iter;
1369                         FileData *fd;
1370
1371                         gtk_tree_model_get_iter(store, &iter, tpath);
1372                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1373                         b += fd->size;
1374
1375                         work = work->next;
1376                         }
1377
1378                 *bytes = b;
1379                 }
1380
1381         count = g_list_length(slist);
1382         g_list_foreach(slist, (GFunc)gtk_tree_path_free_wrapper_file_list, NULL);
1383         g_list_free(slist);
1384
1385         return count;
1386 }
1387
1388 GList *vflist_selection_get_list(ViewFile *vf)
1389 {
1390         GtkTreeModel *store;
1391         GtkTreeSelection *selection;
1392         GList *slist;
1393         GList *list = NULL;
1394         GList *work;
1395
1396         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1397         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1398         work = slist;
1399         while (work)
1400                 {
1401                 GtkTreePath *tpath = work->data;
1402                 FileData *fd;
1403                 GtkTreeIter iter;
1404
1405                 gtk_tree_model_get_iter(store, &iter, tpath);
1406                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1407
1408                 list = g_list_prepend(list, file_data_ref(fd));
1409
1410                 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1411                         {
1412                         /* unexpanded - add whole group */
1413                         GList *work2 = fd->sidecar_files;
1414                         while (work2)
1415                                 {
1416                                 FileData *sfd = work2->data;
1417                                 list = g_list_prepend(list, file_data_ref(sfd));
1418                                 work2 = work2->next;
1419                                 }
1420                         }
1421
1422                 work = work->next;
1423                 }
1424         g_list_foreach(slist, (GFunc)gtk_tree_path_free_wrapper_file_list, NULL);
1425         g_list_free(slist);
1426
1427         return g_list_reverse(list);
1428 }
1429
1430 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1431 {
1432         GtkTreeModel *store;
1433         GtkTreeSelection *selection;
1434         GList *slist;
1435         GList *list = NULL;
1436         GList *work;
1437
1438         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1439         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1440         work = slist;
1441         while (work)
1442                 {
1443                 GtkTreePath *tpath = work->data;
1444                 FileData *fd;
1445                 GtkTreeIter iter;
1446
1447                 gtk_tree_model_get_iter(store, &iter, tpath);
1448                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1449
1450                 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1451
1452                 work = work->next;
1453                 }
1454         g_list_foreach(slist, (GFunc)gtk_tree_path_free_wrapper_file_list, NULL);
1455         g_list_free(slist);
1456
1457         return g_list_reverse(list);
1458 }
1459
1460 void vflist_select_all(ViewFile *vf)
1461 {
1462         GtkTreeSelection *selection;
1463
1464         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1465         gtk_tree_selection_select_all(selection);
1466
1467         VFLIST(vf)->select_fd = NULL;
1468 }
1469
1470 void vflist_select_none(ViewFile *vf)
1471 {
1472         GtkTreeSelection *selection;
1473
1474         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1475         gtk_tree_selection_unselect_all(selection);
1476 }
1477
1478 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1479 {
1480         GtkTreePath *tpath;
1481         gboolean result;
1482
1483         tpath = gtk_tree_model_get_path(store, iter);
1484         result = gtk_tree_path_prev(tpath);
1485         if (result)
1486                 gtk_tree_model_get_iter(store, iter, tpath);
1487
1488         gtk_tree_path_free(tpath);
1489
1490         return result;
1491 }
1492
1493 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1494 {
1495         if (!gtk_tree_model_get_iter_first(store, iter))
1496                 return FALSE;
1497
1498         while (TRUE)
1499                 {
1500                 GtkTreeIter next = *iter;
1501
1502                 if (gtk_tree_model_iter_next(store, &next))
1503                         *iter = next;
1504                 else
1505                         break;
1506                 }
1507
1508         return TRUE;
1509 }
1510
1511 void vflist_select_invert(ViewFile *vf)
1512 {
1513         GtkTreeIter iter;
1514         GtkTreeSelection *selection;
1515         GtkTreeModel *store;
1516         gboolean valid;
1517
1518         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1519         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1520
1521         /* Backward iteration prevents scrolling to the end of the list,
1522          * it scrolls to the first selected row instead. */
1523         valid = tree_model_get_iter_last(store, &iter);
1524
1525         while (valid)
1526                 {
1527                 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1528
1529                 if (selected)
1530                         gtk_tree_selection_unselect_iter(selection, &iter);
1531                 else
1532                         gtk_tree_selection_select_iter(selection, &iter);
1533
1534                 valid = tree_model_iter_prev(store, &iter);
1535                 }
1536 }
1537
1538 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1539 {
1540         GtkTreeIter iter;
1541
1542         if (vflist_find_row(vf, fd, &iter) < 0) return;
1543
1544         tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1545
1546         if (!vflist_row_is_selected(vf, fd))
1547                 {
1548                 GtkTreeSelection *selection;
1549                 GtkTreeModel *store;
1550                 GtkTreePath *tpath;
1551
1552                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1553                 gtk_tree_selection_unselect_all(selection);
1554                 gtk_tree_selection_select_iter(selection, &iter);
1555                 vflist_move_cursor(vf, &iter);
1556
1557                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1558                 tpath = gtk_tree_model_get_path(store, &iter);
1559                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, NULL, FALSE);
1560                 gtk_tree_path_free(tpath);
1561                 }
1562 }
1563
1564 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1565 {
1566         GList *work;
1567         FileData *fd = NULL;
1568
1569         if (sel_fd->parent) sel_fd = sel_fd->parent;
1570         work = vf->list;
1571
1572         while (work)
1573                 {
1574                 gint match;
1575                 fd = work->data;
1576                 work = work->next;
1577
1578                 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1579
1580                 if (match >= 0) break;
1581                 }
1582
1583         if (fd) vflist_select_by_fd(vf, fd);
1584
1585 }
1586
1587 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1588 {
1589         GtkTreeModel *store;
1590         GtkTreeIter iter;
1591         GtkTreeSelection *selection;
1592         gboolean valid;
1593         gint n = mark - 1;
1594
1595         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1596
1597         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1598         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1599
1600         valid = gtk_tree_model_get_iter_first(store, &iter);
1601         while (valid)
1602                 {
1603                 FileData *fd;
1604                 gboolean mark_val, selected;
1605                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1606
1607                 mark_val = file_data_get_mark(fd, n);
1608                 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1609
1610                 switch (mode)
1611                         {
1612                         case MTS_MODE_SET: selected = mark_val;
1613                                 break;
1614                         case MTS_MODE_OR: selected = mark_val || selected;
1615                                 break;
1616                         case MTS_MODE_AND: selected = mark_val && selected;
1617                                 break;
1618                         case MTS_MODE_MINUS: selected = !mark_val && selected;
1619                                 break;
1620                         }
1621
1622                 if (selected)
1623                         gtk_tree_selection_select_iter(selection, &iter);
1624                 else
1625                         gtk_tree_selection_unselect_iter(selection, &iter);
1626
1627                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1628                 }
1629 }
1630
1631 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1632 {
1633         GtkTreeModel *store;
1634         GtkTreeSelection *selection;
1635         GList *slist;
1636         GList *work;
1637         gint n = mark - 1;
1638
1639         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1640
1641         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1642         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1643         work = slist;
1644         while (work)
1645                 {
1646                 GtkTreePath *tpath = work->data;
1647                 FileData *fd;
1648                 GtkTreeIter iter;
1649
1650                 gtk_tree_model_get_iter(store, &iter, tpath);
1651                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1652
1653                 /* the change has a very limited range and the standard notification would trigger
1654                    complete re-read of the directory - try to do only minimal update instead */
1655                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1656
1657                 switch (mode)
1658                         {
1659                         case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1660                                 break;
1661                         case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1662                                 break;
1663                         case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1664                                 break;
1665                         }
1666
1667                 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1668                         {
1669                         vf_refresh_idle(vf);
1670                         }
1671                 else
1672                         {
1673                         /* mark functions can have various side effects - update all columns to be sure */
1674                         vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1675                         /* mark functions can change sidecars too */
1676                         vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, NULL, FALSE);
1677                         }
1678
1679
1680                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1681
1682                 work = work->next;
1683                 }
1684         g_list_foreach(slist, (GFunc)gtk_tree_path_free_wrapper_file_list, NULL);
1685         g_list_free(slist);
1686 }
1687
1688 /*
1689  *-----------------------------------------------------------------------------
1690  * core (population)
1691  *-----------------------------------------------------------------------------
1692  */
1693
1694 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1695 {
1696         GtkTreeViewColumn *column;
1697         GtkCellRenderer *cell;
1698         GList *list;
1699
1700         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1701         if (!column) return;
1702
1703         gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1704
1705         list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1706         if (!list) return;
1707         cell = list->data;
1708         g_list_free(list);
1709
1710         g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1711         gtk_tree_view_column_set_visible(column, thumb);
1712
1713         if (options->show_star_rating)
1714                 {
1715                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1716                 if (!column) return;
1717                 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1718                 gtk_tree_view_column_set_visible(column, TRUE);
1719
1720                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1721                 if (!column) return;
1722                 gtk_tree_view_column_set_visible(column, FALSE);
1723                 }
1724         else
1725                 {
1726                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1727                 if (!column) return;
1728                 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1729                 gtk_tree_view_column_set_visible(column, TRUE);
1730
1731                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1732                 if (!column) return;
1733                 gtk_tree_view_column_set_visible(column, FALSE);
1734                 }
1735
1736         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1737         if (!column) return;
1738         gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1739
1740         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1741         if (!column) return;
1742         gtk_tree_view_column_set_visible(column, !multiline);
1743
1744         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1745         if (!column) return;
1746         gtk_tree_view_column_set_visible(column, !multiline);
1747 }
1748
1749 static gboolean vflist_is_multiline(ViewFile *vf)
1750 {
1751         return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1752 }
1753
1754
1755 static void vflist_populate_view(ViewFile *vf, gboolean force)
1756 {
1757         GtkTreeStore *store;
1758         GList *selected;
1759
1760         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1761
1762         vf_thumb_stop(vf);
1763
1764         if (!vf->list)
1765                 {
1766                 vflist_store_clear(vf, FALSE);
1767                 vf_send_update(vf);
1768                 return;
1769                 }
1770
1771         vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1772
1773         selected = vflist_selection_get_list(vf);
1774
1775         vflist_setup_iter_recursive(vf, store, NULL, vf->list, selected, force);
1776
1777         if (selected && vflist_selection_count(vf, NULL) == 0)
1778                 {
1779                 /* all selected files disappeared */
1780                 vflist_select_closest(vf, selected->data);
1781                 }
1782
1783         filelist_free(selected);
1784
1785         vf_send_update(vf);
1786         vf_thumb_update(vf);
1787 }
1788
1789 gboolean vflist_refresh(ViewFile *vf)
1790 {
1791         GList *old_list;
1792         gboolean ret = TRUE;
1793
1794         old_list = vf->list;
1795         vf->list = NULL;
1796
1797         DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1798         if (vf->dir_fd)
1799                 {
1800                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1801
1802                 ret = filelist_read(vf->dir_fd, &vf->list, NULL);
1803
1804                 if (vf->marks_enabled)
1805                         {
1806                         // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1807                         // each time a mark is changed.
1808                         file_data_lock_list(vf->list);
1809                         }
1810                 else
1811                         {
1812                         // FIXME: only do this when needed (aka when we just switched from
1813                         // FIXME: marks-enabled to marks-disabled)
1814                         file_data_unlock_list(vf->list);
1815                         }
1816
1817                 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1818                 vf->list = g_list_first(vf->list);
1819                 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1820
1821                 vf->list = g_list_first(vf->list);
1822                 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1823
1824                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1825
1826                 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1827                 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1828                 }
1829
1830         DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1831
1832         vflist_populate_view(vf, FALSE);
1833
1834         DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1835
1836         filelist_free(old_list);
1837         DEBUG_1("%s vflist_refresh: done", get_exec_time());
1838
1839         return ret;
1840 }
1841
1842
1843
1844 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1845
1846 #define CELL_HEIGHT_OVERRIDE 512
1847
1848 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1849 {
1850         GParamSpec *spec;
1851
1852         spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1853         if (spec && G_IS_PARAM_SPEC_INT(spec))
1854                 {
1855                 GParamSpecInt *spec_int;
1856
1857                 spec_int = G_PARAM_SPEC_INT(spec);
1858                 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1859                 }
1860 }
1861
1862 static GdkColor *vflist_listview_color_shifted(GtkWidget *widget)
1863 {
1864         static GdkColor color;
1865         static GtkWidget *done = NULL;
1866
1867         if (done != widget)
1868                 {
1869                 GtkStyle *style;
1870
1871                 style = gtk_widget_get_style(widget);
1872                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
1873                 shift_color(&color, -1, 0);
1874                 done = widget;
1875                 }
1876
1877         return &color;
1878 }
1879
1880 static void vflist_listview_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1881                                      GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1882 {
1883         ViewFile *vf = data;
1884         gboolean set;
1885
1886         gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1887         g_object_set(G_OBJECT(cell),
1888                      "cell-background-gdk", vflist_listview_color_shifted(vf->listview),
1889                      "cell-background-set", set, NULL);
1890 }
1891
1892 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
1893 {
1894         GtkTreeViewColumn *column;
1895         GtkCellRenderer *renderer;
1896
1897         column = gtk_tree_view_column_new();
1898         gtk_tree_view_column_set_title(column, title);
1899         gtk_tree_view_column_set_min_width(column, 4);
1900
1901         if (!image)
1902                 {
1903                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1904                 renderer = gtk_cell_renderer_text_new();
1905                 if (right_justify)
1906                         {
1907                         g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
1908                         }
1909                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1910                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
1911                 if (expand)
1912                         gtk_tree_view_column_set_expand(column, TRUE);
1913                 }
1914         else
1915                 {
1916                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1917                 renderer = gtk_cell_renderer_pixbuf_new();
1918                 cell_renderer_height_override(renderer);
1919                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1920                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
1921                 }
1922
1923         gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, NULL);
1924         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1925         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1926
1927         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1928 }
1929
1930 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
1931 {
1932         ViewFile *vf = data;
1933         GtkTreeStore *store;
1934         GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
1935         GtkTreeIter iter;
1936         FileData *fd;
1937         gboolean marked;
1938         guint col_idx;
1939
1940         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1941         if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
1942                 return;
1943
1944         col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
1945
1946         g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
1947
1948         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
1949         marked = !marked;
1950
1951         /* the change has a very limited range and the standard notification would trigger
1952            complete re-read of the directory - try to do only minimal update instead */
1953         file_data_unregister_notify_func(vf_notify_cb, vf);
1954         file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
1955         if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1956                 {
1957                 vf_refresh_idle(vf);
1958                 }
1959         else
1960                 {
1961                 /* mark functions can have various side effects - update all columns to be sure */
1962                 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1963                 /* mark functions can change sidecars too */
1964                 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, NULL, FALSE);
1965                 }
1966         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1967
1968         gtk_tree_path_free(path);
1969 }
1970
1971 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
1972 {
1973         GtkTreeViewColumn *column;
1974         GtkCellRenderer *renderer;
1975
1976         renderer = gtk_cell_renderer_toggle_new();
1977         column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
1978
1979         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1980         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1981         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1982
1983         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1984         gtk_tree_view_column_set_fixed_width(column, 22);
1985         gtk_tree_view_column_set_visible(column, vf->marks_enabled);
1986
1987
1988         g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
1989 }
1990
1991 /*
1992  *-----------------------------------------------------------------------------
1993  * base
1994  *-----------------------------------------------------------------------------
1995  */
1996
1997 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
1998 {
1999         gboolean ret;
2000         if (!dir_fd) return FALSE;
2001         if (vf->dir_fd == dir_fd) return TRUE;
2002
2003         file_data_unref(vf->dir_fd);
2004         vf->dir_fd = file_data_ref(dir_fd);
2005
2006         /* force complete reload */
2007         vflist_store_clear(vf, TRUE);
2008
2009         filelist_free(vf->list);
2010         vf->list = NULL;
2011
2012         ret = vf_refresh(vf);
2013         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2014         return ret;
2015 }
2016
2017 void vflist_destroy_cb(GtkWidget *widget, gpointer data)
2018 {
2019         ViewFile *vf = data;
2020
2021         file_data_unregister_notify_func(vf_notify_cb, vf);
2022
2023         vflist_select_idle_cancel(vf);
2024         vf_refresh_idle_cancel(vf);
2025         vf_thumb_stop(vf);
2026
2027         filelist_free(vf->list);
2028 }
2029
2030 ViewFile *vflist_new(ViewFile *vf, FileData *dir_fd)
2031 {
2032         GtkTreeStore *store;
2033         GtkTreeSelection *selection;
2034         GType flist_types[FILE_COLUMN_COUNT];
2035         gint i;
2036         gint column;
2037
2038         vf->info = g_new0(ViewFileInfoList, 1);
2039
2040         flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2041         flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2042         flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2043         flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2044         flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2045         flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2046         flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2047         flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2048         flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2049         flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2050         flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2051         flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2052         for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2053                 flist_types[i] = G_TYPE_BOOLEAN;
2054
2055         store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2056
2057         vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2058         g_object_unref(store);
2059
2060         g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2061                          G_CALLBACK(vflist_expand_cb), vf);
2062
2063         g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2064                          G_CALLBACK(vflist_collapse_cb), vf);
2065
2066         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2067         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2068         gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, NULL);
2069
2070         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2071         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2072
2073         gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), FILE_COLUMN_FORMATTED);
2074
2075         column = 0;
2076
2077         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2078                 {
2079                 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2080                 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2081                 column++;
2082                 }
2083
2084         vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2085         g_assert(column == FILE_VIEW_COLUMN_THUMB);
2086         column++;
2087
2088         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2089         g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2090         column++;
2091
2092         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2093         g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2094         column++;
2095
2096         vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2097         g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2098         column++;
2099
2100         vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2101         g_assert(column == FILE_VIEW_COLUMN_SIZE);
2102         column++;
2103
2104         vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2105         g_assert(column == FILE_VIEW_COLUMN_DATE);
2106         column++;
2107
2108         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2109         return vf;
2110 }
2111
2112 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2113 {
2114         if (VFLIST(vf)->thumbs_enabled == enable) return;
2115
2116         VFLIST(vf)->thumbs_enabled = enable;
2117
2118         /* vflist_populate_view is better than vf_refresh:
2119            - no need to re-read the directory
2120            - force update because the formatted string has changed
2121         */
2122         if (vf->layout)
2123                 {
2124                 vflist_populate_view(vf, TRUE);
2125                 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2126                 }
2127 }
2128
2129 void vflist_marks_set(ViewFile *vf, gboolean enable)
2130 {
2131         GList *columns, *work;
2132
2133         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2134
2135         work = columns;
2136         while (work)
2137                 {
2138                 GtkTreeViewColumn *column = work->data;
2139                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2140                 work = work->next;
2141
2142                 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2143                         gtk_tree_view_column_set_visible(column, enable);
2144                 }
2145
2146         if (enable)
2147                 {
2148                 // Previously disabled, which means that vf->list is complete
2149                 file_data_lock_list(vf->list);
2150                 }
2151         else
2152                 {
2153                 // Previously enabled, which means that vf->list is incomplete
2154                 }
2155
2156         g_list_free(columns);
2157 }
2158
2159 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */