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