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