2d39d4163ececb3708749ef6474912ba3c69ad47
[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                         GList *work;
1327
1328                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &nfd, -1);
1329
1330                         if (nfd && nfd->rating == STAR_RATING_NOT_READ)
1331                                 {
1332                                 fd = nfd;
1333                                 }
1334
1335                         valid = gtk_tree_model_iter_next(store, &iter);
1336                         }
1337
1338                 if (fd)
1339                         {
1340                         vf->stars_filedata = fd;
1341
1342                         if (vf->stars_id == 0)
1343                                 {
1344                                 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, NULL);
1345                                 }
1346                         }
1347                 }
1348
1349         /* then find first undone */
1350
1351         if (!fd)
1352                 {
1353                 GList *work = vf->list;
1354
1355                 while (work && !fd)
1356                         {
1357                         FileData *fd_p = work->data;
1358
1359                         if (fd_p && fd_p->rating == STAR_RATING_NOT_READ)
1360                                 {
1361                                 fd = fd_p;
1362                                 }
1363                         else
1364                                 {
1365                                 fd = NULL;
1366                                 }
1367
1368                         work = work->next;
1369                         }
1370
1371                 if (fd)
1372                         {
1373                         vf->stars_filedata = fd;
1374
1375                         if (vf->stars_id == 0)
1376                                 {
1377                                 vf->stars_id = g_idle_add_full(G_PRIORITY_LOW, vf_stars_cb, vf, NULL);
1378                                 }
1379                         }
1380                 }
1381
1382         return fd;
1383 }
1384
1385 /*
1386  *-----------------------------------------------------------------------------
1387  * row stuff
1388  *-----------------------------------------------------------------------------
1389  */
1390
1391 gint vflist_index_by_fd(ViewFile *vf, FileData *fd)
1392 {
1393         gint p = 0;
1394         GList *work, *work2;
1395
1396         work = vf->list;
1397         while (work)
1398                 {
1399                 FileData *list_fd = work->data;
1400                 if (list_fd == fd) return p;
1401
1402                 work2 = list_fd->sidecar_files;
1403                 while (work2)
1404                         {
1405                         /* FIXME: return the same index also for sidecars
1406                            it is sufficient for next/prev navigation but it should be rewritten
1407                            without using indexes at all
1408                         */
1409                         FileData *sidecar_fd = work2->data;
1410                         if (sidecar_fd == fd) return p;
1411                         work2 = work2->next;
1412                         }
1413
1414                 work = work->next;
1415                 p++;
1416                 }
1417
1418         return -1;
1419 }
1420
1421 /*
1422  *-----------------------------------------------------------------------------
1423  * selections
1424  *-----------------------------------------------------------------------------
1425  */
1426
1427 static gboolean vflist_row_is_selected(ViewFile *vf, FileData *fd)
1428 {
1429         GtkTreeModel *store;
1430         GtkTreeSelection *selection;
1431         GList *slist;
1432         GList *work;
1433         gboolean found = FALSE;
1434
1435         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1436         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1437         work = slist;
1438         while (!found && work)
1439                 {
1440                 GtkTreePath *tpath = work->data;
1441                 FileData *fd_n;
1442                 GtkTreeIter iter;
1443
1444                 gtk_tree_model_get_iter(store, &iter, tpath);
1445                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1446                 if (fd_n == fd) found = TRUE;
1447                 work = work->next;
1448                 }
1449         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1450         g_list_free(slist);
1451
1452         return found;
1453 }
1454
1455 gboolean vflist_index_is_selected(ViewFile *vf, gint row)
1456 {
1457         FileData *fd;
1458
1459         fd = vf_index_get_data(vf, row);
1460         return vflist_row_is_selected(vf, fd);
1461 }
1462
1463 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1464 {
1465         GtkTreeModel *store;
1466         GtkTreeSelection *selection;
1467         GList *slist;
1468         guint count;
1469
1470         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1471         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1472
1473         if (bytes)
1474                 {
1475                 gint64 b = 0;
1476                 GList *work;
1477
1478                 work = slist;
1479                 while (work)
1480                         {
1481                         GtkTreePath *tpath = work->data;
1482                         GtkTreeIter iter;
1483                         FileData *fd;
1484
1485                         gtk_tree_model_get_iter(store, &iter, tpath);
1486                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1487                         b += fd->size;
1488
1489                         work = work->next;
1490                         }
1491
1492                 *bytes = b;
1493                 }
1494
1495         count = g_list_length(slist);
1496         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1497         g_list_free(slist);
1498
1499         return count;
1500 }
1501
1502 GList *vflist_selection_get_list(ViewFile *vf)
1503 {
1504         GtkTreeModel *store;
1505         GtkTreeSelection *selection;
1506         GList *slist;
1507         GList *list = NULL;
1508         GList *work;
1509
1510         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1511         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1512         work = slist;
1513         while (work)
1514                 {
1515                 GtkTreePath *tpath = work->data;
1516                 FileData *fd;
1517                 GtkTreeIter iter;
1518
1519                 gtk_tree_model_get_iter(store, &iter, tpath);
1520                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1521
1522                 list = g_list_prepend(list, file_data_ref(fd));
1523
1524                 if (!fd->parent && !gtk_tree_view_row_expanded(GTK_TREE_VIEW(vf->listview), tpath))
1525                         {
1526                         /* unexpanded - add whole group */
1527                         GList *work2 = fd->sidecar_files;
1528                         while (work2)
1529                                 {
1530                                 FileData *sfd = work2->data;
1531                                 list = g_list_prepend(list, file_data_ref(sfd));
1532                                 work2 = work2->next;
1533                                 }
1534                         }
1535
1536                 work = work->next;
1537                 }
1538         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1539         g_list_free(slist);
1540
1541         return g_list_reverse(list);
1542 }
1543
1544 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1545 {
1546         GtkTreeModel *store;
1547         GtkTreeSelection *selection;
1548         GList *slist;
1549         GList *list = NULL;
1550         GList *work;
1551
1552         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1553         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1554         work = slist;
1555         while (work)
1556                 {
1557                 GtkTreePath *tpath = work->data;
1558                 FileData *fd;
1559                 GtkTreeIter iter;
1560
1561                 gtk_tree_model_get_iter(store, &iter, tpath);
1562                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1563
1564                 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1565
1566                 work = work->next;
1567                 }
1568         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1569         g_list_free(slist);
1570
1571         return g_list_reverse(list);
1572 }
1573
1574 void vflist_select_all(ViewFile *vf)
1575 {
1576         GtkTreeSelection *selection;
1577
1578         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1579         gtk_tree_selection_select_all(selection);
1580
1581         VFLIST(vf)->select_fd = NULL;
1582 }
1583
1584 void vflist_select_none(ViewFile *vf)
1585 {
1586         GtkTreeSelection *selection;
1587
1588         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1589         gtk_tree_selection_unselect_all(selection);
1590 }
1591
1592 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1593 {
1594         GtkTreePath *tpath;
1595         gboolean result;
1596
1597         tpath = gtk_tree_model_get_path(store, iter);
1598         result = gtk_tree_path_prev(tpath);
1599         if (result)
1600                 gtk_tree_model_get_iter(store, iter, tpath);
1601
1602         gtk_tree_path_free(tpath);
1603
1604         return result;
1605 }
1606
1607 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1608 {
1609         if (!gtk_tree_model_get_iter_first(store, iter))
1610                 return FALSE;
1611
1612         while (TRUE)
1613                 {
1614                 GtkTreeIter next = *iter;
1615
1616                 if (gtk_tree_model_iter_next(store, &next))
1617                         *iter = next;
1618                 else
1619                         break;
1620                 }
1621
1622         return TRUE;
1623 }
1624
1625 void vflist_select_invert(ViewFile *vf)
1626 {
1627         GtkTreeIter iter;
1628         GtkTreeSelection *selection;
1629         GtkTreeModel *store;
1630         gboolean valid;
1631
1632         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1633         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1634
1635         /* Backward iteration prevents scrolling to the end of the list,
1636          * it scrolls to the first selected row instead. */
1637         valid = tree_model_get_iter_last(store, &iter);
1638
1639         while (valid)
1640                 {
1641                 gboolean selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1642
1643                 if (selected)
1644                         gtk_tree_selection_unselect_iter(selection, &iter);
1645                 else
1646                         gtk_tree_selection_select_iter(selection, &iter);
1647
1648                 valid = tree_model_iter_prev(store, &iter);
1649                 }
1650 }
1651
1652 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1653 {
1654         GtkTreeIter iter;
1655
1656         if (vflist_find_row(vf, fd, &iter) < 0) return;
1657
1658         tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1659
1660         if (!vflist_row_is_selected(vf, fd))
1661                 {
1662                 GtkTreeSelection *selection;
1663                 GtkTreeModel *store;
1664                 GtkTreePath *tpath;
1665
1666                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1667                 gtk_tree_selection_unselect_all(selection);
1668                 gtk_tree_selection_select_iter(selection, &iter);
1669                 vflist_move_cursor(vf, &iter);
1670
1671                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1672                 tpath = gtk_tree_model_get_path(store, &iter);
1673                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, NULL, FALSE);
1674                 gtk_tree_path_free(tpath);
1675                 }
1676 }
1677
1678 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1679 {
1680         GList *work;
1681         FileData *fd = NULL;
1682
1683         if (sel_fd->parent) sel_fd = sel_fd->parent;
1684         work = vf->list;
1685
1686         while (work)
1687                 {
1688                 gint match;
1689                 fd = work->data;
1690                 work = work->next;
1691
1692                 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1693
1694                 if (match >= 0) break;
1695                 }
1696
1697         if (fd) vflist_select_by_fd(vf, fd);
1698
1699 }
1700
1701 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1702 {
1703         GtkTreeModel *store;
1704         GtkTreeIter iter;
1705         GtkTreeSelection *selection;
1706         gboolean valid;
1707         gint n = mark - 1;
1708
1709         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1710
1711         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1712         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1713
1714         valid = gtk_tree_model_get_iter_first(store, &iter);
1715         while (valid)
1716                 {
1717                 FileData *fd;
1718                 gboolean mark_val, selected;
1719                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1720
1721                 mark_val = file_data_get_mark(fd, n);
1722                 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1723
1724                 switch (mode)
1725                         {
1726                         case MTS_MODE_SET: selected = mark_val;
1727                                 break;
1728                         case MTS_MODE_OR: selected = mark_val || selected;
1729                                 break;
1730                         case MTS_MODE_AND: selected = mark_val && selected;
1731                                 break;
1732                         case MTS_MODE_MINUS: selected = !mark_val && selected;
1733                                 break;
1734                         }
1735
1736                 if (selected)
1737                         gtk_tree_selection_select_iter(selection, &iter);
1738                 else
1739                         gtk_tree_selection_unselect_iter(selection, &iter);
1740
1741                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1742                 }
1743 }
1744
1745 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1746 {
1747         GtkTreeModel *store;
1748         GtkTreeSelection *selection;
1749         GList *slist;
1750         GList *work;
1751         gint n = mark - 1;
1752
1753         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1754
1755         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1756         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1757         work = slist;
1758         while (work)
1759                 {
1760                 GtkTreePath *tpath = work->data;
1761                 FileData *fd;
1762                 GtkTreeIter iter;
1763
1764                 gtk_tree_model_get_iter(store, &iter, tpath);
1765                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1766
1767                 /* the change has a very limited range and the standard notification would trigger
1768                    complete re-read of the directory - try to do only minimal update instead */
1769                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1770
1771                 switch (mode)
1772                         {
1773                         case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1774                                 break;
1775                         case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1776                                 break;
1777                         case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1778                                 break;
1779                         }
1780
1781                 if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
1782                         {
1783                         vf_refresh_idle(vf);
1784                         }
1785                 else
1786                         {
1787                         /* mark functions can have various side effects - update all columns to be sure */
1788                         vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
1789                         /* mark functions can change sidecars too */
1790                         vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, NULL, FALSE);
1791                         }
1792
1793
1794                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1795
1796                 work = work->next;
1797                 }
1798         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1799         g_list_free(slist);
1800 }
1801
1802 /*
1803  *-----------------------------------------------------------------------------
1804  * core (population)
1805  *-----------------------------------------------------------------------------
1806  */
1807
1808 static void vflist_listview_set_columns(GtkWidget *listview, gboolean thumb, gboolean multiline)
1809 {
1810         GtkTreeViewColumn *column;
1811         GtkCellRenderer *cell;
1812         GList *list;
1813
1814         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_THUMB);
1815         if (!column) return;
1816
1817         gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 4);
1818
1819         list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1820         if (!list) return;
1821         cell = list->data;
1822         g_list_free(list);
1823
1824         g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1825         gtk_tree_view_column_set_visible(column, thumb);
1826
1827         if (options->show_star_rating)
1828                 {
1829                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1830                 if (!column) return;
1831                 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1832                 gtk_tree_view_column_set_visible(column, TRUE);
1833
1834                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1835                 if (!column) return;
1836                 gtk_tree_view_column_set_visible(column, FALSE);
1837                 }
1838         else
1839                 {
1840                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED);
1841                 if (!column) return;
1842                 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(listview), column);
1843                 gtk_tree_view_column_set_visible(column, TRUE);
1844
1845                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
1846                 if (!column) return;
1847                 gtk_tree_view_column_set_visible(column, FALSE);
1848                 }
1849
1850         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_STAR_RATING);
1851         if (!column) return;
1852         gtk_tree_view_column_set_visible(column, !multiline && options->show_star_rating);
1853
1854         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_SIZE);
1855         if (!column) return;
1856         gtk_tree_view_column_set_visible(column, !multiline);
1857
1858         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), FILE_VIEW_COLUMN_DATE);
1859         if (!column) return;
1860         gtk_tree_view_column_set_visible(column, !multiline);
1861 }
1862
1863 static gboolean vflist_is_multiline(ViewFile *vf)
1864 {
1865         return (VFLIST(vf)->thumbs_enabled && options->thumbnails.max_height >= 48);
1866 }
1867
1868
1869 static void vflist_populate_view(ViewFile *vf, gboolean force)
1870 {
1871         GtkTreeStore *store;
1872         GList *selected;
1873
1874         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1875
1876         vf_thumb_stop(vf);
1877         vf_star_stop(vf);
1878
1879         if (!vf->list)
1880                 {
1881                 vflist_store_clear(vf, FALSE);
1882                 vf_send_update(vf);
1883                 return;
1884                 }
1885
1886         vflist_listview_set_columns(vf->listview, VFLIST(vf)->thumbs_enabled, vflist_is_multiline(vf));
1887
1888         selected = vflist_selection_get_list(vf);
1889
1890         vflist_setup_iter_recursive(vf, store, NULL, vf->list, selected, force);
1891
1892         if (selected && vflist_selection_count(vf, NULL) == 0)
1893                 {
1894                 /* all selected files disappeared */
1895                 vflist_select_closest(vf, selected->data);
1896                 }
1897
1898         filelist_free(selected);
1899
1900         vf_send_update(vf);
1901         vf_thumb_update(vf);
1902         vf_star_update(vf);
1903 }
1904
1905 gboolean vflist_refresh(ViewFile *vf)
1906 {
1907         GList *old_list;
1908         gboolean ret = TRUE;
1909
1910         old_list = vf->list;
1911         vf->list = NULL;
1912
1913         DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1914         if (vf->dir_fd)
1915                 {
1916                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1917
1918                 ret = filelist_read(vf->dir_fd, &vf->list, NULL);
1919
1920                 if (vf->marks_enabled)
1921                         {
1922                         // When marks are enabled, lock FileDatas so that we don't end up re-parsing XML
1923                         // each time a mark is changed.
1924                         file_data_lock_list(vf->list);
1925                         }
1926                 else
1927                         {
1928                         // FIXME: only do this when needed (aka when we just switched from
1929                         // FIXME: marks-enabled to marks-disabled)
1930                         file_data_unlock_list(vf->list);
1931                         }
1932
1933                 vf->list = file_data_filter_marks_list(vf->list, vf_marks_get_filter(vf));
1934                 vf->list = g_list_first(vf->list);
1935                 vf->list = file_data_filter_file_filter_list(vf->list, vf_file_filter_get_filter(vf));
1936
1937                 vf->list = g_list_first(vf->list);
1938                 vf->list = file_data_filter_class_list(vf->list, vf_class_get_filter(vf));
1939
1940                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1941
1942                 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1943                 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1944                 }
1945
1946         DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1947
1948         vflist_populate_view(vf, FALSE);
1949
1950         DEBUG_1("%s vflist_refresh: free filelist", get_exec_time());
1951
1952         filelist_free(old_list);
1953         DEBUG_1("%s vflist_refresh: done", get_exec_time());
1954
1955         return ret;
1956 }
1957
1958
1959
1960 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1961
1962 #define CELL_HEIGHT_OVERRIDE 512
1963
1964 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1965 {
1966         GParamSpec *spec;
1967
1968         spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1969         if (spec && G_IS_PARAM_SPEC_INT(spec))
1970                 {
1971                 GParamSpecInt *spec_int;
1972
1973                 spec_int = G_PARAM_SPEC_INT(spec);
1974                 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1975                 }
1976 }
1977
1978 static GdkColor *vflist_listview_color_shifted(GtkWidget *widget)
1979 {
1980         static GdkColor color;
1981         static GtkWidget *done = NULL;
1982
1983         if (done != widget)
1984                 {
1985                 GtkStyle *style;
1986
1987                 style = gtk_widget_get_style(widget);
1988                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
1989                 shift_color(&color, -1, 0);
1990                 done = widget;
1991                 }
1992
1993         return &color;
1994 }
1995
1996 static void vflist_listview_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1997                                      GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1998 {
1999         ViewFile *vf = data;
2000         gboolean set;
2001
2002         gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
2003         g_object_set(G_OBJECT(cell),
2004                      "cell-background-gdk", vflist_listview_color_shifted(vf->listview),
2005                      "cell-background-set", set, NULL);
2006 }
2007
2008 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gboolean image, gboolean right_justify, gboolean expand)
2009 {
2010         GtkTreeViewColumn *column;
2011         GtkCellRenderer *renderer;
2012
2013         column = gtk_tree_view_column_new();
2014         gtk_tree_view_column_set_title(column, title);
2015         gtk_tree_view_column_set_min_width(column, 4);
2016
2017         if (!image)
2018                 {
2019                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2020                 renderer = gtk_cell_renderer_text_new();
2021                 if (right_justify)
2022                         {
2023                         g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2024                         }
2025                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2026                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2027                 if (expand)
2028                         gtk_tree_view_column_set_expand(column, TRUE);
2029                 }
2030         else
2031                 {
2032                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2033                 renderer = gtk_cell_renderer_pixbuf_new();
2034                 cell_renderer_height_override(renderer);
2035                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2036                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2037                 }
2038
2039         gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, NULL);
2040         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2041         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2042
2043         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2044 }
2045
2046 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
2047 {
2048         ViewFile *vf = data;
2049         GtkTreeStore *store;
2050         GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
2051         GtkTreeIter iter;
2052         FileData *fd;
2053         gboolean marked;
2054         guint col_idx;
2055
2056         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
2057         if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
2058                 return;
2059
2060         col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
2061
2062         g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
2063
2064         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &marked, -1);
2065         marked = !marked;
2066
2067         /* the change has a very limited range and the standard notification would trigger
2068            complete re-read of the directory - try to do only minimal update instead */
2069         file_data_unregister_notify_func(vf_notify_cb, vf);
2070         file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, marked);
2071         if (!file_data_filter_marks(fd, vf_marks_get_filter(vf))) /* file no longer matches the filter -> remove it */
2072                 {
2073                 vf_refresh_idle(vf);
2074                 }
2075         else
2076                 {
2077                 /* mark functions can have various side effects - update all columns to be sure */
2078                 vflist_setup_iter(vf, GTK_TREE_STORE(store), &iter, fd);
2079                 /* mark functions can change sidecars too */
2080                 vflist_setup_iter_recursive(vf, GTK_TREE_STORE(store), &iter, fd->sidecar_files, NULL, FALSE);
2081                 }
2082         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2083
2084         gtk_tree_path_free(path);
2085 }
2086
2087 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
2088 {
2089         GtkTreeViewColumn *column;
2090         GtkCellRenderer *renderer;
2091
2092         renderer = gtk_cell_renderer_toggle_new();
2093         column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
2094
2095         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2096         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
2097         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
2098
2099         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
2100         gtk_tree_view_column_set_fixed_width(column, 22);
2101         gtk_tree_view_column_set_visible(column, vf->marks_enabled);
2102
2103
2104         g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
2105 }
2106
2107 /*
2108  *-----------------------------------------------------------------------------
2109  * base
2110  *-----------------------------------------------------------------------------
2111  */
2112
2113 gboolean vflist_set_fd(ViewFile *vf, FileData *dir_fd)
2114 {
2115         gboolean ret;
2116         if (!dir_fd) return FALSE;
2117         if (vf->dir_fd == dir_fd) return TRUE;
2118
2119         file_data_unref(vf->dir_fd);
2120         vf->dir_fd = file_data_ref(dir_fd);
2121
2122         /* force complete reload */
2123         vflist_store_clear(vf, TRUE);
2124
2125         filelist_free(vf->list);
2126         vf->list = NULL;
2127
2128         ret = vf_refresh(vf);
2129         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2130         return ret;
2131 }
2132
2133 void vflist_destroy_cb(GtkWidget *widget, gpointer data)
2134 {
2135         ViewFile *vf = data;
2136
2137         file_data_unregister_notify_func(vf_notify_cb, vf);
2138
2139         vflist_select_idle_cancel(vf);
2140         vf_refresh_idle_cancel(vf);
2141         vf_thumb_stop(vf);
2142         vf_star_stop(vf);
2143
2144         filelist_free(vf->list);
2145 }
2146
2147 ViewFile *vflist_new(ViewFile *vf, FileData *dir_fd)
2148 {
2149         GtkTreeStore *store;
2150         GtkTreeSelection *selection;
2151         GType flist_types[FILE_COLUMN_COUNT];
2152         gint i;
2153         gint column;
2154
2155         vf->info = g_new0(ViewFileInfoList, 1);
2156
2157         flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
2158         flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
2159         flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
2160         flist_types[FILE_COLUMN_FORMATTED] = G_TYPE_STRING;
2161         flist_types[FILE_COLUMN_FORMATTED_WITH_STARS] = G_TYPE_STRING;
2162         flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
2163         flist_types[FILE_COLUMN_STAR_RATING] = G_TYPE_STRING;
2164         flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
2165         flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
2166         flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
2167         flist_types[FILE_COLUMN_EXPANDED] = G_TYPE_BOOLEAN;
2168         flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
2169         for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
2170                 flist_types[i] = G_TYPE_BOOLEAN;
2171
2172         store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
2173
2174         vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2175         g_object_unref(store);
2176
2177         g_signal_connect(G_OBJECT(vf->listview), "row-expanded",
2178                          G_CALLBACK(vflist_expand_cb), vf);
2179
2180         g_signal_connect(G_OBJECT(vf->listview), "row-collapsed",
2181                          G_CALLBACK(vflist_collapse_cb), vf);
2182
2183         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
2184         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
2185         gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, NULL);
2186
2187         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
2188         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
2189
2190         gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(vf->listview), -1);
2191
2192         column = 0;
2193
2194         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
2195                 {
2196                 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
2197                 g_assert(column == FILE_VIEW_COLUMN_MARKS + i);
2198                 column++;
2199                 }
2200
2201         vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
2202         g_assert(column == FILE_VIEW_COLUMN_THUMB);
2203         column++;
2204
2205         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED, _("Name"), FALSE, FALSE, TRUE);
2206         g_assert(column == FILE_VIEW_COLUMN_FORMATTED);
2207         column++;
2208
2209         vflist_listview_add_column(vf, FILE_COLUMN_FORMATTED_WITH_STARS, _("NameStars"), FALSE, FALSE, TRUE);
2210         g_assert(column == FILE_VIEW_COLUMN_FORMATTED_WITH_STARS);
2211         column++;
2212
2213         vflist_listview_add_column(vf, FILE_COLUMN_STAR_RATING, _("Stars"), FALSE, FALSE, FALSE);
2214         g_assert(column == FILE_VIEW_COLUMN_STAR_RATING);
2215         column++;
2216
2217         vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
2218         g_assert(column == FILE_VIEW_COLUMN_SIZE);
2219         column++;
2220
2221         vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
2222         g_assert(column == FILE_VIEW_COLUMN_DATE);
2223         column++;
2224
2225         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
2226         return vf;
2227 }
2228
2229 void vflist_thumb_set(ViewFile *vf, gboolean enable)
2230 {
2231         if (VFLIST(vf)->thumbs_enabled == enable) return;
2232
2233         VFLIST(vf)->thumbs_enabled = enable;
2234
2235         /* vflist_populate_view is better than vf_refresh:
2236            - no need to re-read the directory
2237            - force update because the formatted string has changed
2238         */
2239         if (vf->layout)
2240                 {
2241                 vflist_populate_view(vf, TRUE);
2242                 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vf->listview));
2243                 }
2244 }
2245
2246 void vflist_marks_set(ViewFile *vf, gboolean enable)
2247 {
2248         GList *columns, *work;
2249
2250         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
2251
2252         work = columns;
2253         while (work)
2254                 {
2255                 GtkTreeViewColumn *column = work->data;
2256                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
2257                 work = work->next;
2258
2259                 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
2260                         gtk_tree_view_column_set_visible(column, enable);
2261                 }
2262
2263         if (enable)
2264                 {
2265                 // Previously disabled, which means that vf->list is complete
2266                 file_data_lock_list(vf->list);
2267                 }
2268         else
2269                 {
2270                 // Previously enabled, which means that vf->list is incomplete
2271                 }
2272
2273         g_list_free(columns);
2274 }
2275
2276 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */