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