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