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