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