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