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