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