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