join name and sidecars to one string
[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         gchar *name_sidecars;
729         
730         name_sidecars = (gchar *)fd->name;
731
732         if (fd->sidecar_files)
733                 {
734                 sidecars = file_data_sc_list_to_string(fd);
735                 name_sidecars = g_strdup_printf("%s %s", fd->name, sidecars);
736                 }
737         size = text_from_size(fd->size);
738
739         gtk_tree_store_set(store, iter, FILE_COLUMN_POINTER, fd,
740                                         FILE_COLUMN_VERSION, fd->version,
741                                         FILE_COLUMN_THUMB, fd->pixbuf,
742                                         FILE_COLUMN_NAME, name_sidecars,
743                                         FILE_COLUMN_SIZE, size,
744                                         FILE_COLUMN_DATE, text_from_time(fd->date),
745 #define STORE_SET_IS_SLOW 1
746 #if STORE_SET_IS_SLOW   
747 /* this is 3x faster on a directory with 20000 files */
748                                         FILE_COLUMN_MARKS + 0, file_data_get_mark(fd, 0),
749                                         FILE_COLUMN_MARKS + 1, file_data_get_mark(fd, 1),
750                                         FILE_COLUMN_MARKS + 2, file_data_get_mark(fd, 2),
751                                         FILE_COLUMN_MARKS + 3, file_data_get_mark(fd, 3),
752                                         FILE_COLUMN_MARKS + 4, file_data_get_mark(fd, 4),
753                                         FILE_COLUMN_MARKS + 5, file_data_get_mark(fd, 5),
754 #if FILEDATA_MARKS_SIZE != 6
755 #error this needs to be updated  
756 #endif
757 #endif
758                                         FILE_COLUMN_COLOR, FALSE, -1);
759
760 #if !STORE_SET_IS_SLOW                                  
761         {
762         gint i;
763         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
764                 gtk_tree_store_set(store, iter, FILE_COLUMN_MARKS + i, file_data_get_mark(fd, i), -1);
765         }
766 #endif
767         g_free(size);
768         if (sidecars)
769                 {
770                 g_free(sidecars);
771                 g_free(name_sidecars);
772                 }
773 }
774
775 static void vflist_setup_iter_recursive(ViewFile *vf, GtkTreeStore *store, GtkTreeIter *parent_iter, GList *list, GList *selected)
776 {
777         GList *work;
778         GtkTreeIter iter;
779         gint valid;
780         gint num_ordered = 0;
781         gint num_prepended = 0;
782
783         valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent_iter);
784
785         work = list;
786         while (work)
787                 {
788                 gint match;
789                 FileData *fd = work->data;
790                 gint done = FALSE;
791
792                 while (!done)
793                         {
794                         FileData *old_fd = NULL;
795                         gint old_version = 0;
796
797                         if (valid)
798                                 {
799                                 num_ordered++;
800                                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
801                                                    FILE_COLUMN_POINTER, &old_fd,
802                                                    FILE_COLUMN_VERSION, &old_version,
803                                                    -1);
804
805                                 if (fd == old_fd)
806                                         {
807                                         match = 0;
808                                         }
809                                 else
810                                         {
811                                         if (parent_iter)
812                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, SORT_NAME, TRUE); /* always sort sidecars by name */
813                                         else
814                                                 match = filelist_sort_compare_filedata_full(fd, old_fd, vf->sort_method, vf->sort_ascend);
815
816                                         if (match == 0) g_warning("multiple fd for the same path");
817                                         }
818                                         
819                                 }
820                         else
821                                 {
822                                 match = -1;
823                                 }
824
825                         if (match < 0)
826                                 {
827                                 GtkTreeIter new;
828
829                                 if (valid)
830                                         {
831                                         gtk_tree_store_insert_before(store, &new, parent_iter, &iter);
832                                         }
833                                 else
834                                         {
835                                         /*
836                                             here should be used gtk_tree_store_append, but this function seems to be O(n)
837                                             and it seems to be much faster to add new entries to the beginning and reorder later
838                                         */
839                                         num_prepended++;
840                                         gtk_tree_store_prepend(store, &new, parent_iter);
841                                         }
842
843                                 vflist_setup_iter(vf, store, &new, file_data_ref(fd));
844                                 vflist_setup_iter_recursive(vf, store, &new, fd->sidecar_files, selected);
845                                 
846                                 if (g_list_find(selected, fd))
847                                         {
848                                         /* renamed files - the same fd appears at different position - select it again*/
849                                         GtkTreeSelection *selection;
850                                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
851                                         gtk_tree_selection_select_iter(selection, &new);
852                                         }
853
854                                 done = TRUE;
855                                 }
856                         else if (match > 0)
857                                 {
858                                 file_data_unref(old_fd);
859                                 valid = gtk_tree_store_remove(store, &iter);
860                                 }
861                         else
862                                 {
863                                 if (fd->version != old_version)
864                                         {
865                                         vflist_setup_iter(vf, store, &iter, fd);
866                                         vflist_setup_iter_recursive(vf, store, &iter, fd->sidecar_files, selected);
867                                         }
868
869                                 if (valid) valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
870
871                                 done = TRUE;
872                                 }
873                         }
874                 work = work->next;
875                 }
876
877         while (valid)
878                 {
879                 FileData *old_fd;
880                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &old_fd, -1);
881                 file_data_unref(old_fd);
882
883                 valid = gtk_tree_store_remove(store, &iter);
884                 }
885                 
886         /* move the prepended entries to the correct position */
887         if (num_prepended)
888                 {
889                 gint i;
890                 gint num_total = num_prepended + num_ordered;
891                 gint *new_order = g_malloc(num_total * sizeof(gint));
892                 
893                 for (i = 0; i < num_total; i++)
894                         {
895                         if (i < num_ordered)
896                                 new_order[i] = num_prepended + i;
897                         else
898                                 new_order[i] = num_total - 1 - i;
899                         }
900                 gtk_tree_store_reorder(store, parent_iter, new_order);
901
902                 g_free(new_order);
903                 }
904 }
905
906 void vflist_sort_set(ViewFile *vf, SortType type, gint ascend)
907 {
908         gint i;
909         GHashTable *fd_idx_hash = g_hash_table_new(NULL, NULL);
910         gint *new_order;
911         GtkTreeStore *store;
912         GList *work;
913
914         if (vf->sort_method == type && vf->sort_ascend == ascend) return;
915         if (!vf->list) return;
916
917         work = vf->list;
918         i = 0;
919         while (work)
920                 {
921                 FileData *fd = work->data;
922                 g_hash_table_insert(fd_idx_hash, fd, GINT_TO_POINTER(i));
923                 i++;
924                 work = work->next;
925                 }
926
927         vf->sort_method = type;
928         vf->sort_ascend = ascend;
929
930         vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
931
932         new_order = g_malloc(i * sizeof(gint));
933
934         work = vf->list;
935         i = 0;
936         while (work)
937                 {
938                 FileData *fd = work->data;
939                 new_order[i] = GPOINTER_TO_INT(g_hash_table_lookup(fd_idx_hash, fd));
940                 i++;
941                 work = work->next;
942                 }
943
944         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
945         gtk_tree_store_reorder(store, NULL, new_order);
946
947         g_free(new_order);
948         g_hash_table_destroy(fd_idx_hash);
949 }
950
951 /*
952  *-----------------------------------------------------------------------------
953  * thumb updates
954  *-----------------------------------------------------------------------------
955  */
956
957 static gint vflist_thumb_next(ViewFile *vf);
958
959 static void vflist_thumb_status(ViewFile *vf, gdouble val, const gchar *text)
960 {
961         if (vf->func_thumb_status)
962                 {
963                 vf->func_thumb_status(vf, val, text, vf->data_thumb_status);
964                 }
965 }
966
967 static void vflist_thumb_cleanup(ViewFile *vf)
968 {
969         vflist_thumb_status(vf, 0.0, NULL);
970
971         vf->thumbs_count = 0;
972         vf->thumbs_running = FALSE;
973
974         thumb_loader_free(vf->thumbs_loader);
975         vf->thumbs_loader = NULL;
976
977         vf->thumbs_filedata = NULL;
978 }
979
980 static void vflist_thumb_stop(ViewFile *vf)
981 {
982         if (vf->thumbs_running) vflist_thumb_cleanup(vf);
983 }
984
985 static void vflist_thumb_do(ViewFile *vf, ThumbLoader *tl, FileData *fd)
986 {
987         GtkTreeStore *store;
988         GtkTreeIter iter;
989
990         if (!fd || vflist_find_row(vf, fd, &iter) < 0) return;
991
992         if (fd->pixbuf) g_object_unref(fd->pixbuf);
993         fd->pixbuf = thumb_loader_get_pixbuf(tl, TRUE);
994
995         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
996         gtk_tree_store_set(store, &iter, FILE_COLUMN_THUMB, fd->pixbuf, -1);
997
998         vflist_thumb_status(vf, (gdouble)(vf->thumbs_count) / vflist_sidecar_list_count(vf->list), _("Loading thumbs..."));
999 }
1000
1001 static void vflist_thumb_error_cb(ThumbLoader *tl, gpointer data)
1002 {
1003         ViewFile *vf = data;
1004
1005         if (vf->thumbs_filedata && vf->thumbs_loader == tl)
1006                 {
1007                 vflist_thumb_do(vf, tl, vf->thumbs_filedata);
1008                 }
1009
1010         while (vflist_thumb_next(vf));
1011 }
1012
1013 static void vflist_thumb_done_cb(ThumbLoader *tl, gpointer data)
1014 {
1015         ViewFile *vf = data;
1016
1017         if (vf->thumbs_filedata && vf->thumbs_loader == tl)
1018                 {
1019                 vflist_thumb_do(vf, tl, vf->thumbs_filedata);
1020                 }
1021
1022         while (vflist_thumb_next(vf));
1023 }
1024
1025 static gint vflist_thumb_next(ViewFile *vf)
1026 {
1027         GtkTreePath *tpath;
1028         FileData *fd = NULL;
1029
1030         /* first check the visible files */
1031
1032         if (GTK_WIDGET_REALIZED(vf->listview) &&
1033             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, NULL, NULL, NULL))
1034                 {
1035                 GtkTreeModel *store;
1036                 GtkTreeIter iter;
1037                 gint valid = TRUE;
1038
1039                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1040                 gtk_tree_model_get_iter(store, &iter, tpath);
1041                 gtk_tree_path_free(tpath);
1042
1043                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vf->listview), &iter, FALSE) == 0)
1044                         {
1045                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1046                         if (fd->pixbuf) fd = NULL;
1047
1048                         valid = gtk_tree_model_iter_next(store, &iter);
1049                         }
1050                 }
1051
1052         /* then find first undone */
1053
1054         if (!fd)
1055                 {
1056                 GList *work = vf->list;
1057                 while (work && !fd)
1058                         {
1059                         FileData *fd_p = work->data;
1060                         if (!fd_p->pixbuf)
1061                                 fd = fd_p;
1062                         else
1063                                 {
1064                                 GList *work2 = fd_p->sidecar_files;
1065
1066                                 while (work2 && !fd)
1067                                         {
1068                                         fd_p = work2->data;
1069                                         if (!fd_p->pixbuf) fd = fd_p;
1070                                         work2 = work2->next;
1071                                         }
1072                                 }
1073                         work = work->next;
1074                         }
1075                 }
1076
1077         if (!fd)
1078                 {
1079                 /* done */
1080                 vflist_thumb_cleanup(vf);
1081                 return FALSE;
1082                 }
1083
1084         vf->thumbs_count++;
1085
1086         vf->thumbs_filedata = fd;
1087
1088         thumb_loader_free(vf->thumbs_loader);
1089
1090         vf->thumbs_loader = thumb_loader_new(options->thumbnails.max_width, options->thumbnails.max_height);
1091         thumb_loader_set_callbacks(vf->thumbs_loader,
1092                                    vflist_thumb_done_cb,
1093                                    vflist_thumb_error_cb,
1094                                    NULL,
1095                                    vf);
1096
1097         if (!thumb_loader_start(vf->thumbs_loader, fd->path))
1098                 {
1099                 /* set icon to unknown, continue */
1100                 DEBUG_1("thumb loader start failed %s", vf->thumbs_loader->path);
1101                 vflist_thumb_do(vf, vf->thumbs_loader, fd);
1102
1103                 return TRUE;
1104                 }
1105
1106         return FALSE;
1107 }
1108
1109 static void vflist_thumb_update(ViewFile *vf)
1110 {
1111         vflist_thumb_stop(vf);
1112         if (!VFLIST_INFO(vf, thumbs_enabled)) return;
1113
1114         vflist_thumb_status(vf, 0.0, _("Loading thumbs..."));
1115         vf->thumbs_running = TRUE;
1116
1117         while (vflist_thumb_next(vf));
1118 }
1119
1120 /*
1121  *-----------------------------------------------------------------------------
1122  * row stuff
1123  *-----------------------------------------------------------------------------
1124  */
1125
1126 FileData *vflist_index_get_data(ViewFile *vf, gint row)
1127 {
1128         return g_list_nth_data(vf->list, row);
1129 }
1130
1131 gint vflist_index_by_path(ViewFile *vf, const gchar *path)
1132 {
1133         gint p = 0;
1134         GList *work;
1135
1136         if (!path) return -1;
1137
1138         work = vf->list;
1139         while (work)
1140                 {
1141                 FileData *fd = work->data;
1142                 if (strcmp(path, fd->path) == 0) return p;
1143                 
1144                 work = work->next;
1145                 p++;
1146                 }
1147
1148         return -1;
1149 }
1150
1151 guint vflist_count(ViewFile *vf, gint64 *bytes)
1152 {
1153         if (bytes)
1154                 {
1155                 gint64 b = 0;
1156                 GList *work;
1157
1158                 work = vf->list;
1159                 while (work)
1160                         {
1161                         FileData *fd = work->data;
1162                         work = work->next;
1163                         b += fd->size;
1164                         }
1165
1166                 *bytes = b;
1167                 }
1168
1169         return g_list_length(vf->list);
1170 }
1171
1172 GList *vflist_get_list(ViewFile *vf)
1173 {
1174         GList *list = NULL;
1175         GList *work;
1176
1177         work = vf->list;
1178         while (work)
1179                 {
1180                 FileData *fd = work->data;
1181                 work = work->next;
1182
1183                 list = g_list_prepend(list, file_data_ref(fd));
1184                 }
1185
1186         return g_list_reverse(list);
1187 }
1188
1189 /*
1190  *-----------------------------------------------------------------------------
1191  * selections
1192  *-----------------------------------------------------------------------------
1193  */
1194
1195 static gint vflist_row_is_selected(ViewFile *vf, FileData *fd)
1196 {
1197         GtkTreeModel *store;
1198         GtkTreeSelection *selection;
1199         GList *slist;
1200         GList *work;
1201         gint found = FALSE;
1202
1203         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1204         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1205         work = slist;
1206         while (!found && work)
1207                 {
1208                 GtkTreePath *tpath = work->data;
1209                 FileData *fd_n;
1210                 GtkTreeIter iter;
1211
1212                 gtk_tree_model_get_iter(store, &iter, tpath);
1213                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd_n, -1);
1214                 if (fd_n == fd) found = TRUE;
1215                 work = work->next;
1216                 }
1217         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1218         g_list_free(slist);
1219
1220         return found;
1221 }
1222
1223 gint vflist_index_is_selected(ViewFile *vf, gint row)
1224 {
1225         FileData *fd;
1226
1227         fd = vf_index_get_data(vf, row);
1228         return vflist_row_is_selected(vf, fd);
1229 }
1230
1231 guint vflist_selection_count(ViewFile *vf, gint64 *bytes)
1232 {
1233         GtkTreeModel *store;
1234         GtkTreeSelection *selection;
1235         GList *slist;
1236         guint count;
1237
1238         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1239         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1240
1241         if (bytes)
1242                 {
1243                 gint64 b = 0;
1244                 GList *work;
1245
1246                 work = slist;
1247                 while (work)
1248                         {
1249                         GtkTreePath *tpath = work->data;
1250                         GtkTreeIter iter;
1251                         FileData *fd;
1252
1253                         gtk_tree_model_get_iter(store, &iter, tpath);
1254                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1255                         b += fd->size;
1256
1257                         work = work->next;
1258                         }
1259
1260                 *bytes = b;
1261                 }
1262
1263         count = g_list_length(slist);
1264         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1265         g_list_free(slist);
1266
1267         return count;
1268 }
1269
1270 GList *vflist_selection_get_list(ViewFile *vf)
1271 {
1272         GtkTreeModel *store;
1273         GtkTreeSelection *selection;
1274         GList *slist;
1275         GList *list = NULL;
1276         GList *work;
1277
1278         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1279         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1280         work = slist;
1281         while (work)
1282                 {
1283                 GtkTreePath *tpath = work->data;
1284                 FileData *fd;
1285                 GtkTreeIter iter;
1286
1287                 gtk_tree_model_get_iter(store, &iter, tpath);
1288                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1289
1290                 list = g_list_prepend(list, file_data_ref(fd));
1291
1292                 work = work->next;
1293                 }
1294         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1295         g_list_free(slist);
1296
1297         return g_list_reverse(list);
1298 }
1299
1300 GList *vflist_selection_get_list_by_index(ViewFile *vf)
1301 {
1302         GtkTreeModel *store;
1303         GtkTreeSelection *selection;
1304         GList *slist;
1305         GList *list = NULL;
1306         GList *work;
1307
1308         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1309         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1310         work = slist;
1311         while (work)
1312                 {
1313                 GtkTreePath *tpath = work->data;
1314                 FileData *fd;
1315                 GtkTreeIter iter;
1316
1317                 gtk_tree_model_get_iter(store, &iter, tpath);
1318                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1319
1320                 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vf->list, fd)));
1321
1322                 work = work->next;
1323                 }
1324         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1325         g_list_free(slist);
1326
1327         return g_list_reverse(list);
1328 }
1329
1330 void vflist_select_all(ViewFile *vf)
1331 {
1332         GtkTreeSelection *selection;
1333
1334         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1335         gtk_tree_selection_select_all(selection);
1336
1337         VFLIST_INFO(vf, select_fd) = NULL;
1338 }
1339
1340 void vflist_select_none(ViewFile *vf)
1341 {
1342         GtkTreeSelection *selection;
1343
1344         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1345         gtk_tree_selection_unselect_all(selection);
1346 }
1347
1348 static gboolean tree_model_iter_prev(GtkTreeModel *store, GtkTreeIter *iter)
1349 {
1350         GtkTreePath *tpath;
1351         gboolean result;
1352
1353         tpath = gtk_tree_model_get_path(store, iter);
1354         result = gtk_tree_path_prev(tpath);
1355         if (result)
1356                 gtk_tree_model_get_iter(store, iter, tpath);
1357
1358         gtk_tree_path_free(tpath);
1359
1360         return result;
1361 }
1362
1363 static gboolean tree_model_get_iter_last(GtkTreeModel *store, GtkTreeIter *iter)
1364 {
1365         if (!gtk_tree_model_get_iter_first(store, iter))
1366                 return FALSE;
1367
1368         while (TRUE)
1369                 {
1370                 GtkTreeIter next = *iter;
1371                 
1372                 if (gtk_tree_model_iter_next(store, &next))
1373                         *iter = next;
1374                 else
1375                         break;
1376                 }
1377         
1378         return TRUE;
1379 }
1380
1381 void vflist_select_invert(ViewFile *vf)
1382 {
1383         GtkTreeIter iter;
1384         GtkTreeSelection *selection;
1385         GtkTreeModel *store;
1386         gboolean valid;
1387
1388         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1389         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1390
1391         /* Backward iteration prevents scrolling to the end of the list,
1392          * it scrolls to the first selected row instead. */
1393         valid = tree_model_get_iter_last(store, &iter);
1394
1395         while (valid)
1396                 {
1397                 gint selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1398
1399                 if (selected)
1400                         gtk_tree_selection_unselect_iter(selection, &iter);
1401                 else
1402                         gtk_tree_selection_select_iter(selection, &iter);
1403                                 
1404                 valid = tree_model_iter_prev(store, &iter);
1405                 }
1406 }
1407
1408 void vflist_select_by_fd(ViewFile *vf, FileData *fd)
1409 {
1410         GtkTreeIter iter;
1411
1412         if (vflist_find_row(vf, fd, &iter) < 0) return;
1413
1414         tree_view_row_make_visible(GTK_TREE_VIEW(vf->listview), &iter, TRUE);
1415
1416         if (!vflist_row_is_selected(vf, fd))
1417                 {
1418                 GtkTreeSelection *selection;
1419                 GtkTreeModel *store;
1420                 GtkTreePath *tpath;
1421
1422                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1423                 gtk_tree_selection_unselect_all(selection);
1424                 gtk_tree_selection_select_iter(selection, &iter);
1425                 vflist_move_cursor(vf, &iter);
1426
1427                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1428                 tpath = gtk_tree_model_get_path(store, &iter);
1429                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vf->listview), tpath, NULL, FALSE);
1430                 gtk_tree_path_free(tpath);
1431                 }
1432 }
1433
1434 static void vflist_select_closest(ViewFile *vf, FileData *sel_fd)
1435 {
1436         GList *work;
1437         
1438         if (sel_fd->parent) sel_fd = sel_fd->parent;
1439         work = vf->list;
1440         
1441         while (work)
1442                 {
1443                 gint match;
1444                 FileData *fd = work->data;
1445                 work = work->next;
1446                 
1447
1448                 match = filelist_sort_compare_filedata_full(fd, sel_fd, vf->sort_method, vf->sort_ascend);
1449                 
1450                 if (match >= 0)
1451                         {
1452                         vflist_select_by_fd(vf, fd);
1453                         break;
1454                         }
1455                 }
1456
1457 }
1458
1459 void vflist_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
1460 {
1461         GtkTreeModel *store;
1462         GtkTreeIter iter;
1463         GtkTreeSelection *selection;
1464         gint valid;
1465         gint n = mark - 1;
1466
1467         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1468
1469         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview));
1470         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1471
1472         valid = gtk_tree_model_get_iter_first(store, &iter);
1473         while (valid)
1474                 {
1475                 FileData *fd;
1476                 gboolean mark_val, selected;
1477                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, -1);
1478
1479                 mark_val = file_data_get_mark(fd, n);
1480                 selected = gtk_tree_selection_iter_is_selected(selection, &iter);
1481
1482                 switch (mode)
1483                         {
1484                         case MTS_MODE_SET: selected = mark_val;
1485                                 break;
1486                         case MTS_MODE_OR: selected = mark_val | selected;
1487                                 break;
1488                         case MTS_MODE_AND: selected = mark_val & selected;
1489                                 break;
1490                         case MTS_MODE_MINUS: selected = !mark_val & selected;
1491                                 break;
1492                         }
1493
1494                 if (selected)
1495                         gtk_tree_selection_select_iter(selection, &iter);
1496                 else
1497                         gtk_tree_selection_unselect_iter(selection, &iter);
1498
1499                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1500                 }
1501 }
1502
1503 void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
1504 {
1505         GtkTreeModel *store;
1506         GtkTreeSelection *selection;
1507         GList *slist;
1508         GList *work;
1509         gint n = mark - 1;
1510
1511         g_assert(mark >= 1 && mark <= FILEDATA_MARKS_SIZE);
1512
1513         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1514         slist = gtk_tree_selection_get_selected_rows(selection, &store);
1515         work = slist;
1516         while (work)
1517                 {
1518                 GtkTreePath *tpath = work->data;
1519                 FileData *fd;
1520                 GtkTreeIter iter;
1521
1522                 gtk_tree_model_get_iter(store, &iter, tpath);
1523                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &fd, -1);
1524
1525                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1526
1527                 switch (mode)
1528                         {
1529                         case STM_MODE_SET: file_data_set_mark(fd, n, 1);
1530                                 break;
1531                         case STM_MODE_RESET: file_data_set_mark(fd, n, 0);
1532                                 break;
1533                         case STM_MODE_TOGGLE: file_data_set_mark(fd, n, !file_data_get_mark(fd, n));
1534                                 break;
1535                         }
1536                 
1537                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1538
1539                 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, FILE_COLUMN_MARKS + n, file_data_get_mark(fd, n), -1);
1540
1541                 work = work->next;
1542                 }
1543         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
1544         g_list_free(slist);
1545 }
1546
1547 /*
1548  *-----------------------------------------------------------------------------
1549  * core (population)
1550  *-----------------------------------------------------------------------------
1551  */
1552
1553 static void vflist_listview_set_height(GtkWidget *listview, gint thumb)
1554 {
1555         GtkTreeViewColumn *column;
1556         GtkCellRenderer *cell;
1557         GList *list;
1558
1559         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), 0); /* first column is thumbnail */
1560         if (!column) return;
1561
1562         gtk_tree_view_column_set_visible(column, thumb);
1563
1564         gtk_tree_view_column_set_fixed_width(column, options->thumbnails.max_width + 40);
1565
1566         list = gtk_tree_view_column_get_cell_renderers(column);
1567         if (!list) return;
1568         cell = list->data;
1569         g_list_free(list);
1570
1571         g_object_set(G_OBJECT(cell), "height", options->thumbnails.max_height, NULL);
1572         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(listview));
1573 }
1574
1575 static void vflist_populate_view(ViewFile *vf)
1576 {
1577         GtkTreeStore *store;
1578         gint thumbs;
1579         GtkTreeRowReference *visible_row = NULL;
1580         GtkTreePath *tpath;
1581         GList *selected;
1582
1583         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1584         thumbs = VFLIST_INFO(vf, thumbs_enabled);
1585
1586         vflist_thumb_stop(vf);
1587
1588         if (!vf->list)
1589                 {
1590                 vflist_store_clear(vf);
1591                 vf_send_update(vf);
1592                 return;
1593                 }
1594
1595         if (GTK_WIDGET_REALIZED(vf->listview) &&
1596             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vf->listview), 0, 0, &tpath, NULL, NULL, NULL))
1597                 {
1598                 visible_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(store), tpath);
1599                 gtk_tree_path_free(tpath);
1600                 }
1601
1602         vflist_listview_set_height(vf->listview, thumbs);
1603
1604         selected = vflist_selection_get_list(vf);
1605         
1606         vflist_setup_iter_recursive(vf, store, NULL, vf->list, selected);
1607
1608         if (selected && vflist_selection_count(vf, NULL) == 0)
1609                 {
1610                 /* all selected files disappeared */
1611                 vflist_select_closest(vf, selected->data);
1612                 }       
1613
1614         filelist_free(selected);
1615         
1616         if (visible_row)
1617                 {
1618                 if (gtk_tree_row_reference_valid(visible_row))
1619                         {
1620                         tpath = gtk_tree_row_reference_get_path(visible_row);
1621                         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(vf->listview), tpath, NULL, TRUE, 0.0, 0.0);
1622                         gtk_tree_path_free(tpath);
1623                         }
1624                 gtk_tree_row_reference_free(visible_row);
1625                 }
1626
1627         vf_send_update(vf);
1628         vflist_thumb_update(vf);
1629 }
1630
1631 gint vflist_refresh(ViewFile *vf)
1632 {
1633         GList *old_list;
1634         gint ret = TRUE;
1635
1636         old_list = vf->list;
1637         vf->list = NULL;
1638
1639         DEBUG_1("%s vflist_refresh: read dir", get_exec_time());
1640         if (vf->dir_fd)
1641                 {
1642                 file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification of changes detected by filelist_read */
1643
1644                 ret = filelist_read(vf->dir_fd, &vf->list, NULL);
1645
1646                 file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1647
1648                 DEBUG_1("%s vflist_refresh: sort", get_exec_time());
1649                 vf->list = filelist_sort(vf->list, vf->sort_method, vf->sort_ascend);
1650                 }
1651
1652         DEBUG_1("%s vflist_refresh: populate view", get_exec_time());
1653
1654         vflist_populate_view(vf);
1655
1656         filelist_free(old_list);
1657         DEBUG_1("%s vflist_refresh: done", get_exec_time());
1658
1659         return ret;
1660 }
1661
1662
1663
1664 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
1665
1666 #define CELL_HEIGHT_OVERRIDE 512
1667
1668 static void cell_renderer_height_override(GtkCellRenderer *renderer)
1669 {
1670         GParamSpec *spec;
1671
1672         spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
1673         if (spec && G_IS_PARAM_SPEC_INT(spec))
1674                 {
1675                 GParamSpecInt *spec_int;
1676
1677                 spec_int = G_PARAM_SPEC_INT(spec);
1678                 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
1679                 }
1680 }
1681
1682 static GdkColor *vflist_listview_color_shifted(GtkWidget *widget)
1683 {
1684         static GdkColor color;
1685         static GtkWidget *done = NULL;
1686
1687         if (done != widget)
1688                 {
1689                 GtkStyle *style;
1690
1691                 style = gtk_widget_get_style(widget);
1692                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
1693                 shift_color(&color, -1, 0);
1694                 done = widget;
1695                 }
1696
1697         return &color;
1698 }
1699
1700 static void vflist_listview_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1701                                      GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
1702 {
1703         ViewFile *vf = data;
1704         gboolean set;
1705
1706         gtk_tree_model_get(tree_model, iter, FILE_COLUMN_COLOR, &set, -1);
1707         g_object_set(G_OBJECT(cell),
1708                      "cell-background-gdk", vflist_listview_color_shifted(vf->listview),
1709                      "cell-background-set", set, NULL);
1710 }
1711
1712 static void vflist_listview_add_column(ViewFile *vf, gint n, const gchar *title, gint image, gint right_justify, gint expand)
1713 {
1714         GtkTreeViewColumn *column;
1715         GtkCellRenderer *renderer;
1716
1717         column = gtk_tree_view_column_new();
1718         gtk_tree_view_column_set_title(column, title);
1719         gtk_tree_view_column_set_min_width(column, 4);
1720
1721         if (!image)
1722                 {
1723                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1724                 renderer = gtk_cell_renderer_text_new();
1725                 if (right_justify)
1726                         {
1727                         g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
1728                         }
1729                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1730                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
1731                 if (expand)
1732                         gtk_tree_view_column_set_expand(column, TRUE);
1733                 }
1734         else
1735                 {
1736                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1737                 renderer = gtk_cell_renderer_pixbuf_new();
1738                 cell_renderer_height_override(renderer);
1739                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1740                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
1741                 }
1742
1743         gtk_tree_view_column_set_cell_data_func(column, renderer, vflist_listview_color_cb, vf, NULL);
1744         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1745         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1746
1747         gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1748 }
1749
1750 static void vflist_listview_mark_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
1751 {
1752         ViewFile *vf = data;
1753         GtkTreeStore *store;
1754         GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
1755         GtkTreeIter iter;
1756         FileData *fd;
1757         gboolean mark;
1758         guint col_idx;
1759
1760         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1761         if (!path || !gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
1762                 return;
1763
1764         col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), "column_store_idx"));
1765
1766         g_assert(col_idx >= FILE_COLUMN_MARKS && col_idx <= FILE_COLUMN_MARKS_LAST);
1767
1768         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, FILE_COLUMN_POINTER, &fd, col_idx, &mark, -1);
1769         mark = !mark;
1770         file_data_unregister_notify_func(vf_notify_cb, vf); /* we don't need the notification */
1771         file_data_set_mark(fd, col_idx - FILE_COLUMN_MARKS, mark);
1772         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1773
1774         gtk_tree_store_set(store, &iter, col_idx, mark, -1);
1775         gtk_tree_path_free(path);
1776 }
1777
1778 static void vflist_listview_add_column_toggle(ViewFile *vf, gint n, const gchar *title)
1779 {
1780         GtkTreeViewColumn *column;
1781         GtkCellRenderer *renderer;
1782         GtkTreeStore *store;
1783         gint index;
1784
1785         store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vf->listview)));
1786
1787         renderer = gtk_cell_renderer_toggle_new();
1788         column = gtk_tree_view_column_new_with_attributes(title, renderer, "active", n, NULL);
1789
1790         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
1791         g_object_set_data(G_OBJECT(column), "column_store_idx", GUINT_TO_POINTER(n));
1792         g_object_set_data(G_OBJECT(renderer), "column_store_idx", GUINT_TO_POINTER(n));
1793
1794         index = gtk_tree_view_append_column(GTK_TREE_VIEW(vf->listview), column);
1795         gtk_tree_view_column_set_fixed_width(column, 16);
1796         gtk_tree_view_column_set_visible(column, vf->marks_enabled);
1797
1798
1799         g_signal_connect(G_OBJECT(renderer), "toggled", G_CALLBACK(vflist_listview_mark_toggled_cb), vf);
1800 }
1801
1802 /*
1803  *-----------------------------------------------------------------------------
1804  * base
1805  *-----------------------------------------------------------------------------
1806  */
1807
1808 gint vflist_set_fd(ViewFile *vf, FileData *dir_fd)
1809 {
1810         if (!dir_fd) return FALSE;
1811         if (vf->dir_fd == dir_fd) return TRUE;
1812
1813         file_data_unref(vf->dir_fd);
1814         vf->dir_fd = file_data_ref(dir_fd);
1815
1816         /* force complete reload */
1817         vflist_store_clear(vf);
1818
1819         filelist_free(vf->list);
1820         vf->list = NULL;
1821
1822         return vf_refresh(vf);
1823 }
1824
1825 void vflist_destroy_cb(GtkWidget *widget, gpointer data)
1826 {
1827         ViewFile *vf = data;
1828
1829         file_data_unregister_notify_func(vf_notify_cb, vf);
1830
1831         vflist_select_idle_cancel(vf);
1832         vf_refresh_idle_cancel(vf);
1833         vflist_thumb_stop(vf);
1834
1835         filelist_free(vf->list);
1836 }
1837
1838 ViewFile *vflist_new(ViewFile *vf, FileData *dir_fd)
1839 {
1840         GtkTreeStore *store;
1841         GtkTreeSelection *selection;
1842
1843         GType flist_types[FILE_COLUMN_COUNT];
1844         int i;
1845
1846         vf->info = g_new0(ViewFileInfoList, 1);
1847         
1848         VFLIST_INFO(vf, click_fd) = NULL;
1849         VFLIST_INFO(vf, select_fd) = NULL;
1850         VFLIST_INFO(vf, thumbs_enabled) = FALSE;
1851
1852         VFLIST_INFO(vf, select_idle_id) = -1;
1853
1854         flist_types[FILE_COLUMN_POINTER] = G_TYPE_POINTER;
1855         flist_types[FILE_COLUMN_VERSION] = G_TYPE_INT;
1856         flist_types[FILE_COLUMN_THUMB] = GDK_TYPE_PIXBUF;
1857         flist_types[FILE_COLUMN_NAME] = G_TYPE_STRING;
1858         flist_types[FILE_COLUMN_SIDECARS] = G_TYPE_STRING;
1859         flist_types[FILE_COLUMN_SIZE] = G_TYPE_STRING;
1860         flist_types[FILE_COLUMN_DATE] = G_TYPE_STRING;
1861         flist_types[FILE_COLUMN_COLOR] = G_TYPE_BOOLEAN;
1862         for (i = FILE_COLUMN_MARKS; i < FILE_COLUMN_MARKS + FILEDATA_MARKS_SIZE; i++)
1863                 flist_types[i] = G_TYPE_BOOLEAN;
1864
1865         store = gtk_tree_store_newv(FILE_COLUMN_COUNT, flist_types);
1866
1867         vf->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1868         g_object_unref(store);
1869
1870         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vf->listview));
1871         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1872         gtk_tree_selection_set_select_function(selection, vflist_select_cb, vf, NULL);
1873
1874         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vf->listview), FALSE);
1875         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vf->listview), FALSE);
1876
1877         vflist_listview_add_column(vf, FILE_COLUMN_THUMB, "", TRUE, FALSE, FALSE);
1878
1879         for (i = 0; i < FILEDATA_MARKS_SIZE; i++)
1880                 vflist_listview_add_column_toggle(vf, i + FILE_COLUMN_MARKS, "");
1881
1882         vflist_listview_add_column(vf, FILE_COLUMN_NAME, _("Name"), FALSE, FALSE, TRUE);
1883         vflist_listview_add_column(vf, FILE_COLUMN_SIDECARS, _("SC"), FALSE, FALSE, FALSE);
1884
1885         vflist_listview_add_column(vf, FILE_COLUMN_SIZE, _("Size"), FALSE, TRUE, FALSE);
1886         vflist_listview_add_column(vf, FILE_COLUMN_DATE, _("Date"), FALSE, TRUE, FALSE);
1887
1888         file_data_register_notify_func(vf_notify_cb, vf, NOTIFY_PRIORITY_MEDIUM);
1889         return vf;
1890 }
1891
1892 void vflist_thumb_set(ViewFile *vf, gint enable)
1893 {
1894         if (VFLIST_INFO(vf, thumbs_enabled) == enable) return;
1895
1896         VFLIST_INFO(vf, thumbs_enabled) = enable;
1897         if (vf->layout) vf_refresh(vf);
1898 }
1899
1900 void vflist_marks_set(ViewFile *vf, gint enable)
1901 {
1902         GList *columns, *work;
1903
1904         if (vf->marks_enabled == enable) return;
1905
1906         vf->marks_enabled = enable;
1907
1908         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(vf->listview));
1909
1910         work = columns;
1911         while (work)
1912                 {
1913                 GtkTreeViewColumn *column = work->data;
1914                 gint col_idx = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_store_idx"));
1915                 work = work->next;
1916
1917                 if (col_idx <= FILE_COLUMN_MARKS_LAST && col_idx >= FILE_COLUMN_MARKS)
1918                         gtk_tree_view_column_set_visible(column, enable);
1919                 }
1920
1921         g_list_free(columns);
1922         //vf_refresh(vf);
1923 }
1924