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