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