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