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