Convert the minority of while() to while ().
[geeqie.git] / src / view_file_icon.c
1 /*
2  * Geeqie
3  * (C) 2006 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_icon.h"
15
16 #include "cellrenderericon.h"
17 #include "collect.h"
18 #include "collect-io.h"
19 #include "collect-table.h"
20 #include "debug.h"
21 #include "dnd.h"
22 #include "editors.h"
23 #include "img-view.h"
24 #include "info.h"
25 #include "filelist.h"
26 #include "layout.h"
27 #include "layout_image.h"
28 #include "menu.h"
29 #include "thumb.h"
30 #include "utilops.h"
31 #include "ui_bookmark.h"
32 #include "ui_fileops.h"
33 #include "ui_menu.h"
34 #include "ui_tree_edit.h"
35
36
37 #include <gdk/gdkkeysyms.h> /* for keyboard values */
38
39 /* between these, the icon width is increased by thumb_max_width / 2 */
40 #define THUMB_MIN_ICON_WIDTH 128
41 #define THUMB_MAX_ICON_WIDTH 150
42
43 #define VFICON_MAX_COLUMNS 32
44 #define THUMB_BORDER_PADDING 2
45
46 #define VFICON_TIP_DELAY 500
47
48 enum {
49         FILE_COLUMN_POINTER = 0,
50         FILE_COLUMN_COUNT
51 };
52
53 typedef enum {
54         SELECTION_NONE          = 0,
55         SELECTION_SELECTED      = 1 << 0,
56         SELECTION_PRELIGHT      = 1 << 1,
57         SELECTION_FOCUS         = 1 << 2
58 } SelectionType;
59
60 typedef struct _IconData IconData;
61 struct _IconData
62 {
63         SelectionType selected;
64         gint row;
65         FileData *fd;
66 };
67
68 static gint vficon_index_by_id(ViewFileIcon *vfi, IconData *in_id);
69
70 static IconData *vficon_icon_data(ViewFileIcon *vfi, FileData *fd)
71 {
72         IconData *id = NULL;
73         GList *work;
74
75         if (!fd) return NULL;
76         work = vfi->list;
77         while (work && !id)
78                 {
79                 IconData *chk = work->data;
80                 work = work->next;
81                 if (chk->fd == fd) id = chk;
82                 }
83         return id;
84 }
85
86
87 static gint iconlist_read(const gchar *path, GList **list)
88 {
89         GList *temp;
90         GList *work;
91
92         if (!filelist_read(path, &temp, NULL)) return FALSE;
93
94         work = temp;
95         while (work)
96                 {
97                 FileData *fd;
98                 IconData *id;
99
100                 fd = work->data;
101                 g_assert(fd->magick == 0x12345678);
102                 id = g_new0(IconData, 1);
103
104                 id->selected = SELECTION_NONE;
105                 id->row = -1;
106                 id->fd = fd;
107
108                 work->data = id;
109                 work = work->next;
110                 }
111
112         *list = temp;
113
114         return TRUE;
115 }
116
117 static void iconlist_free(GList *list)
118 {
119         GList *work = list;
120         while (work)
121                 {
122                 IconData *id = work->data;
123                 file_data_unref(id->fd);
124                 g_free(id);
125                 work = work->next;
126                 }
127
128         g_list_free(list);
129
130 }
131
132 gint iconlist_sort_file_cb(void *a, void *b)
133 {
134         IconData *ida = a;
135         IconData *idb = b;
136         return filelist_sort_compare_filedata(ida->fd, idb->fd);
137 }
138 GList *iconlist_sort(GList *list, SortType method, gint ascend)
139 {
140         return filelist_sort_full(list, method, ascend, (GCompareFunc) iconlist_sort_file_cb);
141 }
142
143 GList *iconlist_insert_sort(GList *list, IconData *id, SortType method, gint ascend)
144 {
145         return filelist_insert_sort_full(list, id, method, ascend, (GCompareFunc) iconlist_sort_file_cb);
146 }
147
148
149 static void vficon_toggle_filenames(ViewFileIcon *vfi);
150 static void vficon_selection_remove(ViewFileIcon *vfi, IconData *id, SelectionType mask, GtkTreeIter *iter);
151 static void vficon_move_focus(ViewFileIcon *vfi, gint row, gint col, gint relative);
152 static void vficon_set_focus(ViewFileIcon *vfi, IconData *id);
153 static void vficon_thumb_update(ViewFileIcon *vfi);
154 static void vficon_populate_at_new_size(ViewFileIcon *vfi, gint w, gint h, gint force);
155
156
157 /*
158  *-----------------------------------------------------------------------------
159  * pop-up menu
160  *-----------------------------------------------------------------------------
161  */
162
163 static GList *vficon_pop_menu_file_list(ViewFileIcon *vfi)
164 {
165         if (!vfi->click_id) return NULL;
166
167         if (vfi->click_id->selected & SELECTION_SELECTED)
168                 {
169                 return vficon_selection_get_list(vfi);
170                 }
171
172         return g_list_append(NULL, file_data_ref(vfi->click_id->fd));
173 }
174
175 static void vficon_pop_menu_edit_cb(GtkWidget *widget, gpointer data)
176 {
177         ViewFileIcon *vfi;
178         gint n;
179         GList *list;
180
181         vfi = submenu_item_get_data(widget);
182         n = GPOINTER_TO_INT(data);
183
184         if (!vfi) return;
185
186         list = vficon_pop_menu_file_list(vfi);
187         start_editor_from_filelist(n, list);
188         filelist_free(list);
189 }
190
191 static void vficon_pop_menu_info_cb(GtkWidget *widget, gpointer data)
192 {
193         ViewFileIcon *vfi = data;
194
195         info_window_new(NULL, vficon_pop_menu_file_list(vfi), NULL);
196 }
197
198 static void vficon_pop_menu_view_cb(GtkWidget *widget, gpointer data)
199 {
200         ViewFileIcon *vfi = data;
201
202         if (!vfi->click_id) return;
203
204         if (vfi->click_id->selected & SELECTION_SELECTED)
205                 {
206                 GList *list;
207
208                 list = vficon_selection_get_list(vfi);
209                 view_window_new_from_list(list);
210                 filelist_free(list);
211                 }
212         else
213                 {
214                 view_window_new(vfi->click_id->fd);
215                 }
216 }
217
218 static void vficon_pop_menu_copy_cb(GtkWidget *widget, gpointer data)
219 {
220         ViewFileIcon *vfi = data;
221
222         file_util_copy(NULL, vficon_pop_menu_file_list(vfi), NULL, vfi->listview);
223 }
224
225 static void vficon_pop_menu_move_cb(GtkWidget *widget, gpointer data)
226 {
227         ViewFileIcon *vfi = data;
228
229         file_util_move(NULL, vficon_pop_menu_file_list(vfi), NULL, vfi->listview);
230 }
231
232 static void vficon_pop_menu_rename_cb(GtkWidget *widget, gpointer data)
233 {
234         ViewFileIcon *vfi = data;
235
236         file_util_rename(NULL, vficon_pop_menu_file_list(vfi), vfi->listview);
237 }
238
239 static void vficon_pop_menu_delete_cb(GtkWidget *widget, gpointer data)
240 {
241         ViewFileIcon *vfi = data;
242
243         file_util_delete(NULL, vficon_pop_menu_file_list(vfi), vfi->listview);
244 }
245
246 static void vficon_pop_menu_copy_path_cb(GtkWidget *widget, gpointer data)
247 {
248         ViewFileIcon *vfi = data;
249
250         file_util_copy_path_list_to_clipboard(vficon_pop_menu_file_list(vfi));
251 }
252
253 static void vficon_pop_menu_sort_cb(GtkWidget *widget, gpointer data)
254 {
255         ViewFileIcon *vfi;
256         SortType type;
257
258         if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) return;
259
260         vfi = submenu_item_get_data(widget);
261         if (!vfi) return;
262
263         type = (SortType)GPOINTER_TO_INT(data);
264
265         if (vfi->layout)
266                 {
267                 layout_sort_set(vfi->layout, type, vfi->sort_ascend);
268                 }
269         else
270                 {
271                 vficon_sort_set(vfi, type, vfi->sort_ascend);
272                 }
273 }
274
275 static void vficon_pop_menu_sort_ascend_cb(GtkWidget *widget, gpointer data)
276 {
277         ViewFileIcon *vfi = data;
278
279         if (vfi->layout)
280                 {
281                 layout_sort_set(vfi->layout, vfi->sort_method, !vfi->sort_ascend);
282                 }
283         else
284                 {
285                 vficon_sort_set(vfi, vfi->sort_method, !vfi->sort_ascend);
286                 }
287 }
288
289 static void vficon_pop_menu_list_cb(GtkWidget *widget, gpointer data)
290 {
291         ViewFileIcon *vfi = data;
292
293         if (vfi->layout) layout_views_set(vfi->layout, vfi->layout->dir_view_type, FALSE);
294 }
295
296 static void vficon_pop_menu_show_names_cb(GtkWidget *widget, gpointer data)
297 {
298         ViewFileIcon *vfi = data;
299
300         vficon_toggle_filenames(vfi);
301 }
302
303 static void vficon_pop_menu_refresh_cb(GtkWidget *widget, gpointer data)
304 {
305         ViewFileIcon *vfi = data;
306
307         vficon_refresh(vfi);
308 }
309
310 static void vficon_popup_destroy_cb(GtkWidget *widget, gpointer data)
311 {
312         ViewFileIcon *vfi = data;
313         vficon_selection_remove(vfi, vfi->click_id, SELECTION_PRELIGHT, NULL);
314         vfi->click_id = NULL;
315         vfi->popup = NULL;
316 }
317
318 static GtkWidget *vficon_pop_menu(ViewFileIcon *vfi, gint active)
319 {
320         GtkWidget *menu;
321         GtkWidget *item;
322         GtkWidget *submenu;
323
324         menu = popup_menu_short_lived();
325
326         g_signal_connect(G_OBJECT(menu), "destroy",
327                          G_CALLBACK(vficon_popup_destroy_cb), vfi);
328
329         submenu_add_edit(menu, &item, G_CALLBACK(vficon_pop_menu_edit_cb), vfi);
330         gtk_widget_set_sensitive(item, active);
331
332         menu_item_add_stock_sensitive(menu, _("_Properties"), GTK_STOCK_PROPERTIES, active,
333                                       G_CALLBACK(vficon_pop_menu_info_cb), vfi);
334
335         menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, active,
336                                       G_CALLBACK(vficon_pop_menu_view_cb), vfi);
337         menu_item_add_divider(menu);
338
339         menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, active,
340                                       G_CALLBACK(vficon_pop_menu_copy_cb), vfi);
341         menu_item_add_sensitive(menu, _("_Move..."), active,
342                                 G_CALLBACK(vficon_pop_menu_move_cb), vfi);
343         menu_item_add_sensitive(menu, _("_Rename..."), active,
344                                 G_CALLBACK(vficon_pop_menu_rename_cb), vfi);
345         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, active,
346                                       G_CALLBACK(vficon_pop_menu_delete_cb), vfi);
347         if (options->show_copy_path)
348                 menu_item_add_sensitive(menu, _("_Copy path"), active,
349                                         G_CALLBACK(vficon_pop_menu_copy_path_cb), vfi);
350         menu_item_add_divider(menu);
351
352         submenu = submenu_add_sort(NULL, G_CALLBACK(vficon_pop_menu_sort_cb), vfi,
353                                    FALSE, FALSE, TRUE, vfi->sort_method);
354         menu_item_add_divider(submenu);
355         menu_item_add_check(submenu, _("Ascending"), vfi->sort_ascend,
356                             G_CALLBACK(vficon_pop_menu_sort_ascend_cb), vfi);
357
358         item = menu_item_add(menu, _("_Sort"), NULL, NULL);
359         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
360
361         menu_item_add_check(menu, _("View as _icons"), TRUE,
362                             G_CALLBACK(vficon_pop_menu_list_cb), vfi);
363         menu_item_add_check(menu, _("Show filename _text"), vfi->show_text,
364                             G_CALLBACK(vficon_pop_menu_show_names_cb), vfi);
365         menu_item_add_stock(menu, _("Re_fresh"), GTK_STOCK_REFRESH, G_CALLBACK(vficon_pop_menu_refresh_cb), vfi);
366
367         return menu;
368 }
369
370 /*
371  *-------------------------------------------------------------------
372  * signals
373  *-------------------------------------------------------------------
374  */
375
376 static void vficon_send_update(ViewFileIcon *vfi)
377 {
378         if (vfi->func_status) vfi->func_status(vfi, vfi->data_status);
379 }
380
381 static void vficon_send_layout_select(ViewFileIcon *vfi, IconData *id)
382 {
383         FileData *read_ahead_fd = NULL;
384         FileData *sel_fd;
385         FileData *cur_fd;
386
387         if (!vfi->layout || !id || !id->fd) return;
388
389         sel_fd = id->fd;
390
391         cur_fd = layout_image_get_fd(vfi->layout);
392         if (sel_fd == cur_fd) return; /* no change */
393
394         if (options->image.enable_read_ahead)
395                 {
396                 gint row;
397
398                 row = g_list_index(vfi->list, id);
399                 if (row > vficon_index_by_fd(vfi, cur_fd) &&
400                     row + 1 < vficon_count(vfi, NULL))
401                         {
402                         read_ahead_fd = vficon_index_get_data(vfi, row + 1);
403                         }
404                 else if (row > 0)
405                         {
406                         read_ahead_fd = vficon_index_get_data(vfi, row - 1);
407                         }
408                 }
409
410         layout_image_set_with_ahead(vfi->layout, sel_fd, read_ahead_fd);
411 }
412
413 static void vficon_toggle_filenames(ViewFileIcon *vfi)
414 {
415         vfi->show_text = !vfi->show_text;
416         options->show_icon_names = vfi->show_text;
417
418         vficon_populate_at_new_size(vfi, vfi->listview->allocation.width, vfi->listview->allocation.height, TRUE);
419 }
420
421 static gint vficon_get_icon_width(ViewFileIcon *vfi)
422 {
423         gint width;
424
425         if (!vfi->show_text) return options->thumbnails.max_width;
426
427         width = options->thumbnails.max_width + options->thumbnails.max_width / 2;
428         if (width < THUMB_MIN_ICON_WIDTH) width = THUMB_MIN_ICON_WIDTH;
429         if (width > THUMB_MAX_ICON_WIDTH) width = options->thumbnails.max_width;
430
431         return width;
432 }
433
434 /*
435  *-------------------------------------------------------------------
436  * misc utils
437  *-------------------------------------------------------------------
438  */
439
440 static gint vficon_find_position(ViewFileIcon *vfi, IconData *id, gint *row, gint *col)
441 {
442         gint n;
443
444         n = g_list_index(vfi->list, id);
445
446         if (n < 0) return FALSE;
447
448         *row = n / vfi->columns;
449         *col = n - (*row * vfi->columns);
450
451         return TRUE;
452 }
453
454 static gint vficon_find_iter(ViewFileIcon *vfi, IconData *id, GtkTreeIter *iter, gint *column)
455 {
456         GtkTreeModel *store;
457         gint row, col;
458
459         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview));
460         if (!vficon_find_position(vfi, id, &row, &col)) return FALSE;
461         if (!gtk_tree_model_iter_nth_child(store, iter, NULL, row)) return FALSE;
462         if (column) *column = col;
463
464         return TRUE;
465 }
466
467 static IconData *vficon_find_data(ViewFileIcon *vfi, gint row, gint col, GtkTreeIter *iter)
468 {
469         GtkTreeModel *store;
470         GtkTreeIter p;
471
472         if (row < 0 || col < 0) return NULL;
473
474         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview));
475         if (gtk_tree_model_iter_nth_child(store, &p, NULL, row))
476                 {
477                 GList *list;
478
479                 gtk_tree_model_get(store, &p, FILE_COLUMN_POINTER, &list, -1);
480                 if (!list) return NULL;
481
482                 if (iter) *iter = p;
483
484                 return g_list_nth_data(list, col);
485                 }
486
487         return NULL;
488 }
489
490 static IconData *vficon_find_data_by_coord(ViewFileIcon *vfi, gint x, gint y, GtkTreeIter *iter)
491 {
492         GtkTreePath *tpath;
493         GtkTreeViewColumn *column;
494
495         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vfi->listview), x, y,
496                                           &tpath, &column, NULL, NULL))
497                 {
498                 GtkTreeModel *store;
499                 GtkTreeIter row;
500                 GList *list;
501                 gint n;
502
503                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview));
504                 gtk_tree_model_get_iter(store, &row, tpath);
505                 gtk_tree_path_free(tpath);
506
507                 gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &list, -1);
508
509                 n = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_number"));
510                 if (list)
511                         {
512                         if (iter) *iter = row;
513                         return g_list_nth_data(list, n);
514                         }
515                 }
516
517         return NULL;
518 }
519
520 /*
521  *-------------------------------------------------------------------
522  * tooltip type window
523  *-------------------------------------------------------------------
524  */
525
526 static void tip_show(ViewFileIcon *vfi)
527 {
528         GtkWidget *label;
529         gint x, y;
530
531         if (vfi->tip_window) return;
532
533         gdk_window_get_pointer(gtk_tree_view_get_bin_window(GTK_TREE_VIEW(vfi->listview)), &x, &y, NULL);
534
535         vfi->tip_id = vficon_find_data_by_coord(vfi, x, y, NULL);
536         if (!vfi->tip_id) return;
537
538         vfi->tip_window = gtk_window_new(GTK_WINDOW_POPUP);
539         gtk_window_set_resizable(GTK_WINDOW(vfi->tip_window), FALSE);
540         gtk_container_set_border_width(GTK_CONTAINER(vfi->tip_window), 2);
541
542         label = gtk_label_new(vfi->tip_id->fd->name);
543
544         g_object_set_data(G_OBJECT(vfi->tip_window), "tip_label", label);
545         gtk_container_add(GTK_CONTAINER(vfi->tip_window), label);
546         gtk_widget_show(label);
547
548         gdk_window_get_pointer(NULL, &x, &y, NULL);
549
550         if (!GTK_WIDGET_REALIZED(vfi->tip_window)) gtk_widget_realize(vfi->tip_window);
551         gtk_window_move(GTK_WINDOW(vfi->tip_window), x + 16, y + 16);
552         gtk_widget_show(vfi->tip_window);
553 }
554
555 static void tip_hide(ViewFileIcon *vfi)
556 {
557         if (vfi->tip_window) gtk_widget_destroy(vfi->tip_window);
558         vfi->tip_window = NULL;
559 }
560
561 static gint tip_schedule_cb(gpointer data)
562 {
563         ViewFileIcon *vfi = data;
564         GtkWidget *window;
565
566         if (vfi->tip_delay_id == -1) return FALSE;
567
568         window = gtk_widget_get_toplevel(vfi->listview);
569
570         if (GTK_WIDGET_SENSITIVE(window) &&
571             GTK_WINDOW(window)->has_focus)
572                 {
573                 tip_show(vfi);
574                 }
575
576         vfi->tip_delay_id = -1;
577         return FALSE;
578 }
579
580 static void tip_schedule(ViewFileIcon *vfi)
581 {
582         tip_hide(vfi);
583
584         if (vfi->tip_delay_id != -1)
585                 {
586                 g_source_remove(vfi->tip_delay_id);
587                 vfi->tip_delay_id = -1;
588                 }
589
590         if (!vfi->show_text)
591                 {
592                 vfi->tip_delay_id = g_timeout_add(VFICON_TIP_DELAY, tip_schedule_cb, vfi);
593                 }
594 }
595
596 static void tip_unschedule(ViewFileIcon *vfi)
597 {
598         tip_hide(vfi);
599
600         if (vfi->tip_delay_id != -1) g_source_remove(vfi->tip_delay_id);
601         vfi->tip_delay_id = -1;
602 }
603
604 static void tip_update(ViewFileIcon *vfi, IconData *id)
605 {
606         if (vfi->tip_window)
607                 {
608                 gint x, y;
609
610                 gdk_window_get_pointer(NULL, &x, &y, NULL);
611                 gtk_window_move(GTK_WINDOW(vfi->tip_window), x + 16, y + 16);
612
613                 if (id != vfi->tip_id)
614                         {
615                         GtkWidget *label;
616
617                         vfi->tip_id = id;
618
619                         if (!vfi->tip_id)
620                                 {
621                                 tip_hide(vfi);
622                                 tip_schedule(vfi);
623                                 return;
624                                 }
625
626                         label = g_object_get_data(G_OBJECT(vfi->tip_window), "tip_label");
627                         gtk_label_set_text(GTK_LABEL(label), vfi->tip_id->fd->name);
628                         }
629                 }
630         else
631                 {
632                 tip_schedule(vfi);
633                 }
634 }
635
636 /*
637  *-------------------------------------------------------------------
638  * dnd
639  *-------------------------------------------------------------------
640  */
641
642 static void vficon_dnd_get(GtkWidget *widget, GdkDragContext *context,
643                            GtkSelectionData *selection_data, guint info,
644                            guint time, gpointer data)
645 {
646         ViewFileIcon *vfi = data;
647         GList *list = NULL;
648         gchar *uri_text = NULL;
649         gint total;
650
651         if (!vfi->click_id) return;
652
653         if (vfi->click_id->selected & SELECTION_SELECTED)
654                 {
655                 list = vficon_selection_get_list(vfi);
656                 }
657         else
658                 {
659                 list = g_list_append(NULL, file_data_ref(vfi->click_id->fd));
660                 }
661
662         if (!list) return;
663         uri_text = uri_text_from_filelist(list, &total, (info == TARGET_TEXT_PLAIN));
664         filelist_free(list);
665
666         DEBUG_1(uri_text);
667
668         gtk_selection_data_set(selection_data, selection_data->target,
669                                8, (guchar *)uri_text, total);
670         g_free(uri_text);
671 }
672
673 static void vficon_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
674 {
675         ViewFileIcon *vfi = data;
676
677         tip_unschedule(vfi);
678
679         if (vfi->click_id && vfi->click_id->fd->pixbuf)
680                 {
681                 gint items;
682
683                 if (vfi->click_id->selected & SELECTION_SELECTED)
684                         items = g_list_length(vfi->selection);
685                 else
686                         items = 1;
687
688                 dnd_set_drag_icon(widget, context, vfi->click_id->fd->pixbuf, items);
689                 }
690 }
691
692 static void vficon_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
693 {
694         ViewFileIcon *vfi = data;
695
696         vficon_selection_remove(vfi, vfi->click_id, SELECTION_PRELIGHT, NULL);
697
698         if (context->action == GDK_ACTION_MOVE)
699                 {
700                 vficon_refresh(vfi);
701                 }
702
703         tip_unschedule(vfi);
704 }
705
706 static void vficon_dnd_init(ViewFileIcon *vfi)
707 {
708         gtk_drag_source_set(vfi->listview, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
709                             dnd_file_drag_types, dnd_file_drag_types_count,
710                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
711         g_signal_connect(G_OBJECT(vfi->listview), "drag_data_get",
712                          G_CALLBACK(vficon_dnd_get), vfi);
713         g_signal_connect(G_OBJECT(vfi->listview), "drag_begin",
714                          G_CALLBACK(vficon_dnd_begin), vfi);
715         g_signal_connect(G_OBJECT(vfi->listview), "drag_end",
716                          G_CALLBACK(vficon_dnd_end), vfi);
717 }
718
719 /*
720  *-------------------------------------------------------------------
721  * cell updates
722  *-------------------------------------------------------------------
723  */
724
725 static void vficon_selection_set(ViewFileIcon *vfi, IconData *id, SelectionType value, GtkTreeIter *iter)
726 {
727         GtkTreeModel *store;
728         GList *list;
729
730         if (!id) return;
731
732
733         if (id->selected == value) return;
734         id->selected = value;
735
736         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview));
737         if (iter)
738                 {
739                 gtk_tree_model_get(store, iter, FILE_COLUMN_POINTER, &list, -1);
740                 if (list) gtk_list_store_set(GTK_LIST_STORE(store), iter, FILE_COLUMN_POINTER, list, -1);
741                 }
742         else
743                 {
744                 GtkTreeIter row;
745
746                 if (vficon_find_iter(vfi, id, &row, NULL))
747                         {
748                         gtk_tree_model_get(store, &row, FILE_COLUMN_POINTER, &list, -1);
749                         if (list) gtk_list_store_set(GTK_LIST_STORE(store), &row, FILE_COLUMN_POINTER, list, -1);
750                         }
751                 }
752 }
753
754 static void vficon_selection_add(ViewFileIcon *vfi, IconData *id, SelectionType mask, GtkTreeIter *iter)
755 {
756         if (!id) return;
757
758         vficon_selection_set(vfi, id, id->selected | mask, iter);
759 }
760
761 static void vficon_selection_remove(ViewFileIcon *vfi, IconData *id, SelectionType mask, GtkTreeIter *iter)
762 {
763         if (!id) return;
764
765         vficon_selection_set(vfi, id, id->selected & ~mask, iter);
766 }
767
768 /*
769  *-------------------------------------------------------------------
770  * selections
771  *-------------------------------------------------------------------
772  */
773
774 static void vficon_verify_selections(ViewFileIcon *vfi)
775 {
776         GList *work;
777
778         work = vfi->selection;
779         while (work)
780                 {
781                 IconData *id = work->data;
782                 work = work->next;
783                 if (vficon_index_by_id(vfi, id) < 0)
784                         {
785                         vfi->selection = g_list_remove(vfi->selection, id);
786                         }
787                 }
788 }
789
790 void vficon_select_all(ViewFileIcon *vfi)
791 {
792         GList *work;
793
794         g_list_free(vfi->selection);
795         vfi->selection = NULL;
796
797         work = vfi->list;
798         while (work)
799                 {
800                 IconData *id = work->data;
801                 vfi->selection = g_list_append(vfi->selection, id);
802                 vficon_selection_add(vfi, work->data, SELECTION_SELECTED, NULL);
803                 work = work->next;
804                 }
805
806         vficon_send_update(vfi);
807 }
808
809 void vficon_select_none(ViewFileIcon *vfi)
810 {
811         GList *work;
812
813         work = vfi->selection;
814         while (work)
815                 {
816                 vficon_selection_remove(vfi, work->data, SELECTION_SELECTED, NULL);
817                 work = work->next;
818                 }
819
820         g_list_free(vfi->selection);
821         vfi->selection = NULL;
822
823         vficon_send_update(vfi);
824 }
825
826 static void vficon_select(ViewFileIcon *vfi, IconData *id)
827 {
828         vfi->prev_selection = id;
829
830         if (!id || id->selected & SELECTION_SELECTED) return;
831
832         vfi->selection = g_list_append(vfi->selection, id);
833         vficon_selection_add(vfi, id, SELECTION_SELECTED, NULL);
834
835         vficon_send_update(vfi);
836 }
837
838 static void vficon_unselect(ViewFileIcon *vfi, IconData *id)
839 {
840         vfi->prev_selection = id;
841
842         if (!id || !(id->selected & SELECTION_SELECTED) ) return;
843
844         vfi->selection = g_list_remove(vfi->selection, id);
845         vficon_selection_remove(vfi, id, SELECTION_SELECTED, NULL);
846
847         vficon_send_update(vfi);
848 }
849
850 static void vficon_select_util(ViewFileIcon *vfi, IconData *id, gint select)
851 {
852         if (select)
853                 {
854                 vficon_select(vfi, id);
855                 }
856         else
857                 {
858                 vficon_unselect(vfi, id);
859                 }
860 }
861
862 static void vficon_select_region_util(ViewFileIcon *vfi, IconData *start, IconData *end, gint select)
863 {
864         gint row1, col1;
865         gint row2, col2;
866         gint t;
867         gint i, j;
868
869         if (!vficon_find_position(vfi, start, &row1, &col1) ||
870             !vficon_find_position(vfi, end, &row2, &col2) ) return;
871
872         vfi->prev_selection = end;
873
874         if (!options->collections.rectangular_selection)
875                 {
876                 GList *work;
877                 IconData *id;
878
879                 if (g_list_index(vfi->list, start) > g_list_index(vfi->list, end))
880                         {
881                         id = start;
882                         start = end;
883                         end = id;
884                         }
885
886                 work = g_list_find(vfi->list, start);
887                 while (work)
888                         {
889                         id = work->data;
890                         vficon_select_util(vfi, id, select);
891
892                         if (work->data != end)
893                                 work = work->next;
894                         else
895                                 work = NULL;
896                         }
897                 return;
898                 }
899
900         if (row2 < row1)
901                 {
902                 t = row1;
903                 row1 = row2;
904                 row2 = t;
905                 }
906         if (col2 < col1)
907                 {
908                 t = col1;
909                 col1 = col2;
910                 col2 = t;
911                 }
912
913         DEBUG_1("table: %d x %d to %d x %d", row1, col1, row2, col2);
914
915         for (i = row1; i <= row2; i++)
916                 {
917                 for (j = col1; j <= col2; j++)
918                         {
919                         IconData *id = vficon_find_data(vfi, i, j, NULL);
920                         if (id) vficon_select_util(vfi, id, select);
921                         }
922                 }
923 }
924
925 gint vficon_index_is_selected(ViewFileIcon *vfi, gint row)
926 {
927         IconData *id = g_list_nth_data(vfi->list, row);
928
929         if (!id) return FALSE;
930
931         return (id->selected & SELECTION_SELECTED);
932 }
933
934 gint vficon_selection_count(ViewFileIcon *vfi, gint64 *bytes)
935 {
936         if (bytes)
937                 {
938                 gint64 b = 0;
939                 GList *work;
940
941                 work = vfi->selection;
942                 while (work)
943                         {
944                         IconData *id = work->data;
945                         FileData *fd = id->fd;
946                         g_assert(fd->magick == 0x12345678);
947                         b += fd->size;
948
949                         work = work->next;
950                         }
951
952                 *bytes = b;
953                 }
954
955         return g_list_length(vfi->selection);
956 }
957
958 GList *vficon_selection_get_list(ViewFileIcon *vfi)
959 {
960         GList *list = NULL;
961         GList *work;
962
963         work = vfi->selection;
964         while (work)
965                 {
966                 IconData *id = work->data;
967                 FileData *fd = id->fd;
968                 g_assert(fd->magick == 0x12345678);
969
970                 list = g_list_prepend(list, file_data_ref(fd));
971
972                 work = work->next;
973                 }
974
975         list = g_list_reverse(list);
976
977         return list;
978 }
979
980 GList *vficon_selection_get_list_by_index(ViewFileIcon *vfi)
981 {
982         GList *list = NULL;
983         GList *work;
984
985         work = vfi->selection;
986         while (work)
987                 {
988                 list = g_list_prepend(list, GINT_TO_POINTER(g_list_index(vfi->list, work->data)));
989                 work = work->next;
990                 }
991
992         return g_list_reverse(list);
993 }
994
995 static void vficon_select_by_id(ViewFileIcon *vfi, IconData *id)
996 {
997         if (!id) return;
998
999         if (!(id->selected & SELECTION_SELECTED))
1000                 {
1001                 vficon_select_none(vfi);
1002                 vficon_select(vfi, id);
1003                 }
1004
1005         vficon_set_focus(vfi, id);
1006 }
1007
1008
1009 void vficon_select_by_path(ViewFileIcon *vfi, const gchar *path)
1010 {
1011         IconData *id = NULL;
1012         GList *work;
1013
1014         if (!path) return;
1015
1016         work = vfi->list;
1017         while (work && !id)
1018                 {
1019                 IconData *chk = work->data;
1020                 work = work->next;
1021                 if (strcmp(chk->fd->path, path) == 0) id = chk;
1022                 }
1023         vficon_select_by_id(vfi, id);
1024 }
1025
1026 void vficon_select_by_fd(ViewFileIcon *vfi, FileData *fd)
1027 {
1028         IconData *id = NULL;
1029         GList *work;
1030
1031         if (!fd) return;
1032         work = vfi->list;
1033         while (work && !id)
1034                 {
1035                 IconData *chk = work->data;
1036                 work = work->next;
1037                 if (chk->fd == fd) id = chk;
1038                 }
1039         vficon_select_by_id(vfi, id);
1040 }
1041
1042 void vficon_mark_to_selection(ViewFileIcon *vfi, gint mark, MarkToSelectionMode mode)
1043 {
1044         GList *work;
1045
1046         work = vfi->list;
1047         while (work)
1048                 {
1049                 IconData *id = work->data;
1050                 FileData *fd = id->fd;
1051                 gboolean mark_val, selected;
1052
1053                 g_assert(fd->magick == 0x12345678);
1054
1055                 mark_val = fd->marks[mark];
1056                 selected = (id->selected & SELECTION_SELECTED);
1057
1058                 switch (mode)
1059                         {
1060                         case MTS_MODE_SET: selected = mark_val;
1061                                 break;
1062                         case MTS_MODE_OR: selected = mark_val | selected;
1063                                 break;
1064                         case MTS_MODE_AND: selected = mark_val & selected;
1065                                 break;
1066                         case MTS_MODE_MINUS: selected = !mark_val & selected;
1067                                 break;
1068                         }
1069
1070                 vficon_select_util(vfi, id, selected);
1071
1072                 work = work->next;
1073                 }
1074 }
1075
1076 void vficon_selection_to_mark(ViewFileIcon *vfi, gint mark, SelectionToMarkMode mode)
1077 {
1078         GList *slist;
1079         GList *work;
1080
1081         g_assert(mark >= 0 && mark < FILEDATA_MARKS_SIZE);
1082
1083         slist = vficon_selection_get_list(vfi);
1084         work = slist;
1085         while (work)
1086                 {
1087                 FileData *fd = work->data;
1088
1089                 switch (mode)
1090                         {
1091                         case STM_MODE_SET: fd->marks[mark] = 1;
1092                                 break;
1093                         case STM_MODE_RESET: fd->marks[mark] = 0;
1094                                 break;
1095                         case STM_MODE_TOGGLE: fd->marks[mark] = !fd->marks[mark];
1096                                 break;
1097                         }
1098
1099                 work = work->next;
1100                 }
1101         filelist_free(slist);
1102 }
1103
1104
1105 /*
1106  *-------------------------------------------------------------------
1107  * focus
1108  *-------------------------------------------------------------------
1109  */
1110
1111 static void vficon_move_focus(ViewFileIcon *vfi, gint row, gint col, gint relative)
1112 {
1113         gint new_row;
1114         gint new_col;
1115
1116         if (relative)
1117                 {
1118                 new_row = vfi->focus_row;
1119                 new_col = vfi->focus_column;
1120
1121                 new_row += row;
1122                 if (new_row < 0) new_row = 0;
1123                 if (new_row >= vfi->rows) new_row = vfi->rows - 1;
1124
1125                 while (col != 0)
1126                         {
1127                         if (col < 0)
1128                                 {
1129                                 new_col--;
1130                                 col++;
1131                                 }
1132                         else
1133                                 {
1134                                 new_col++;
1135                                 col--;
1136                                 }
1137
1138                         if (new_col < 0)
1139                                 {
1140                                 if (new_row > 0)
1141                                         {
1142                                         new_row--;
1143                                         new_col = vfi->columns - 1;
1144                                         }
1145                                 else
1146                                         {
1147                                         new_col = 0;
1148                                         }
1149                                 }
1150                         if (new_col >= vfi->columns)
1151                                 {
1152                                 if (new_row < vfi->rows - 1)
1153                                         {
1154                                         new_row++;
1155                                         new_col = 0;
1156                                         }
1157                                 else
1158                                         {
1159                                         new_col = vfi->columns - 1;
1160                                         }
1161                                 }
1162                         }
1163                 }
1164         else
1165                 {
1166                 new_row = row;
1167                 new_col = col;
1168
1169                 if (new_row >= vfi->rows)
1170                         {
1171                         if (vfi->rows > 0)
1172                                 new_row = vfi->rows - 1;
1173                         else
1174                                 new_row = 0;
1175                         new_col = vfi->columns - 1;
1176                         }
1177                 if (new_col >= vfi->columns) new_col = vfi->columns - 1;
1178                 }
1179
1180         if (new_row == vfi->rows - 1)
1181                 {
1182                 gint l;
1183
1184                 /* if we moved beyond the last image, go to the last image */
1185
1186                 l = g_list_length(vfi->list);
1187                 if (vfi->rows > 1) l -= (vfi->rows - 1) * vfi->columns;
1188                 if (new_col >= l) new_col = l - 1;
1189                 }
1190
1191         vficon_set_focus(vfi, vficon_find_data(vfi, new_row, new_col, NULL));
1192 }
1193
1194 static void vficon_set_focus(ViewFileIcon *vfi, IconData *id)
1195 {
1196         GtkTreeIter iter;
1197         gint row, col;
1198
1199         if (g_list_find(vfi->list, vfi->focus_id))
1200                 {
1201                 if (id == vfi->focus_id)
1202                         {
1203                         /* ensure focus row col are correct */
1204                         vficon_find_position(vfi, vfi->focus_id, &vfi->focus_row, &vfi->focus_column);
1205                         return;
1206                         }
1207                 vficon_selection_remove(vfi, vfi->focus_id, SELECTION_FOCUS, NULL);
1208                 }
1209
1210         if (!vficon_find_position(vfi, id, &row, &col))
1211                 {
1212                 vfi->focus_id = NULL;
1213                 vfi->focus_row = -1;
1214                 vfi->focus_column = -1;
1215                 return;
1216                 }
1217
1218         vfi->focus_id = id;
1219         vfi->focus_row = row;
1220         vfi->focus_column = col;
1221         vficon_selection_add(vfi, vfi->focus_id, SELECTION_FOCUS, NULL);
1222
1223         if (vficon_find_iter(vfi, vfi->focus_id, &iter, NULL))
1224                 {
1225                 GtkTreePath *tpath;
1226                 GtkTreeViewColumn *column;
1227                 GtkTreeModel *store;
1228
1229                 tree_view_row_make_visible(GTK_TREE_VIEW(vfi->listview), &iter, FALSE);
1230
1231                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview));
1232                 tpath = gtk_tree_model_get_path(store, &iter);
1233                 /* focus is set to an extra column with 0 width to hide focus, we draw it ourself */
1234                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(vfi->listview), VFICON_MAX_COLUMNS);
1235                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vfi->listview), tpath, column, FALSE);
1236                 gtk_tree_path_free(tpath);
1237                 }
1238 }
1239
1240 static void vficon_update_focus(ViewFileIcon *vfi)
1241 {
1242         gint new_row = 0;
1243         gint new_col = 0;
1244
1245         if (vfi->focus_id && vficon_find_position(vfi, vfi->focus_id, &new_row, &new_col))
1246                 {
1247                 /* first find the old focus, if it exists and is valid */
1248                 }
1249         else
1250                 {
1251                 /* (try to) stay where we were */
1252                 new_row = vfi->focus_row;
1253                 new_col = vfi->focus_column;
1254                 }
1255
1256         vficon_move_focus(vfi, new_row, new_col, FALSE);
1257 }
1258
1259 /* used to figure the page up/down distances */
1260 static gint page_height(ViewFileIcon *vfi)
1261 {
1262         GtkAdjustment *adj;
1263         gint page_size;
1264         gint row_height;
1265         gint ret;
1266
1267         adj = gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(vfi->listview));
1268         page_size = (gint)adj->page_increment;
1269
1270         row_height = options->thumbnails.max_height + THUMB_BORDER_PADDING * 2;
1271         if (vfi->show_text) row_height += options->thumbnails.max_height / 3;
1272
1273         ret = page_size / row_height;
1274         if (ret < 1) ret = 1;
1275
1276         return ret;
1277 }
1278
1279 /*
1280  *-------------------------------------------------------------------
1281  * keyboard
1282  *-------------------------------------------------------------------
1283  */
1284
1285 static void vfi_menu_position_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
1286 {
1287         ViewFileIcon *vfi = data;
1288         GtkTreeModel *store;
1289         GtkTreeIter iter;
1290         gint column;
1291         GtkTreePath *tpath;
1292         gint cw, ch;
1293
1294         if (!vficon_find_iter(vfi, vfi->click_id, &iter, &column)) return;
1295         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview));
1296         tpath = gtk_tree_model_get_path(store, &iter);
1297         tree_view_get_cell_clamped(GTK_TREE_VIEW(vfi->listview), tpath, column, FALSE, x, y, &cw, &ch);
1298         gtk_tree_path_free(tpath);
1299         *y += ch;
1300         popup_menu_position_clamp(menu, x, y, 0);
1301 }
1302
1303 static gint vficon_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1304 {
1305         ViewFileIcon *vfi = data;
1306         gint focus_row = 0;
1307         gint focus_col = 0;
1308         IconData *id;
1309         gint stop_signal;
1310
1311         stop_signal = TRUE;
1312         switch (event->keyval)
1313                 {
1314                 case GDK_Left: case GDK_KP_Left:
1315                         focus_col = -1;
1316                         break;
1317                 case GDK_Right: case GDK_KP_Right:
1318                         focus_col = 1;
1319                         break;
1320                 case GDK_Up: case GDK_KP_Up:
1321                         focus_row = -1;
1322                         break;
1323                 case GDK_Down: case GDK_KP_Down:
1324                         focus_row = 1;
1325                         break;
1326                 case GDK_Page_Up: case GDK_KP_Page_Up:
1327                         focus_row = -page_height(vfi);
1328                         break;
1329                 case GDK_Page_Down: case GDK_KP_Page_Down:
1330                         focus_row = page_height(vfi);
1331                         break;
1332                 case GDK_Home: case GDK_KP_Home:
1333                         focus_row = -vfi->focus_row;
1334                         focus_col = -vfi->focus_column;
1335                         break;
1336                 case GDK_End: case GDK_KP_End:
1337                         focus_row = vfi->rows - 1 - vfi->focus_row;
1338                         focus_col = vfi->columns - 1 - vfi->focus_column;
1339                         break;
1340                 case GDK_space:
1341                         id = vficon_find_data(vfi, vfi->focus_row, vfi->focus_column, NULL);
1342                         if (id)
1343                                 {
1344                                 vfi->click_id = id;
1345                                 if (event->state & GDK_CONTROL_MASK)
1346                                         {
1347                                         gint selected;
1348
1349                                         selected = id->selected & SELECTION_SELECTED;
1350                                         if (selected)
1351                                                 {
1352                                                 vficon_unselect(vfi, id);
1353                                                 }
1354                                         else
1355                                                 {
1356                                                 vficon_select(vfi, id);
1357                                                 vficon_send_layout_select(vfi, id);
1358                                                 }
1359                                         }
1360                                 else
1361                                         {
1362                                         vficon_select_none(vfi);
1363                                         vficon_select(vfi, id);
1364                                         vficon_send_layout_select(vfi, id);
1365                                         }
1366                                 }
1367                         break;
1368                 case GDK_Menu:
1369                         id = vficon_find_data(vfi, vfi->focus_row, vfi->focus_column, NULL);
1370                         vfi->click_id = id;
1371
1372                         vficon_selection_add(vfi, vfi->click_id, SELECTION_PRELIGHT, NULL);
1373                         tip_unschedule(vfi);
1374
1375                         vfi->popup = vficon_pop_menu(vfi, (id != NULL));
1376                         gtk_menu_popup(GTK_MENU(vfi->popup), NULL, NULL, vfi_menu_position_cb, vfi, 0, GDK_CURRENT_TIME);
1377                         break;
1378                 default:
1379                         stop_signal = FALSE;
1380                         break;
1381                 }
1382
1383         if (focus_row != 0 || focus_col != 0)
1384                 {
1385                 IconData *new_id;
1386                 IconData *old_id;
1387
1388                 old_id = vficon_find_data(vfi, vfi->focus_row, vfi->focus_column, NULL);
1389                 vficon_move_focus(vfi, focus_row, focus_col, TRUE);
1390                 new_id = vficon_find_data(vfi, vfi->focus_row, vfi->focus_column, NULL);
1391
1392                 if (new_id != old_id)
1393                         {
1394                         if (event->state & GDK_SHIFT_MASK)
1395                                 {
1396                                 if (!options->collections.rectangular_selection)
1397                                         {
1398                                         vficon_select_region_util(vfi, old_id, new_id, FALSE);
1399                                         }
1400                                 else
1401                                         {
1402                                         vficon_select_region_util(vfi, vfi->click_id, old_id, FALSE);
1403                                         }
1404                                 vficon_select_region_util(vfi, vfi->click_id, new_id, TRUE);
1405                                 vficon_send_layout_select(vfi, new_id);
1406                                 }
1407                         else if (event->state & GDK_CONTROL_MASK)
1408                                 {
1409                                 vfi->click_id = new_id;
1410                                 }
1411                         else
1412                                 {
1413                                 vfi->click_id = new_id;
1414                                 vficon_select_none(vfi);
1415                                 vficon_select(vfi, new_id);
1416                                 vficon_send_layout_select(vfi, new_id);
1417                                 }
1418                         }
1419                 }
1420
1421         if (stop_signal)
1422                 {
1423 #if 0
1424                 g_signal_stop_emission_by_name(GTK_OBJECT(widget), "key_press_event");
1425 #endif
1426                 tip_unschedule(vfi);
1427                 }
1428
1429         return stop_signal;
1430 }
1431
1432 /*
1433  *-------------------------------------------------------------------
1434  * mouse
1435  *-------------------------------------------------------------------
1436  */
1437
1438 static gint vficon_motion_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1439 {
1440         ViewFileIcon *vfi = data;
1441         IconData *id;
1442
1443         id = vficon_find_data_by_coord(vfi, (gint)bevent->x, (gint)bevent->y, NULL);
1444         tip_update(vfi, id);
1445
1446         return FALSE;
1447 }
1448
1449 static gint vficon_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1450 {
1451         ViewFileIcon *vfi = data;
1452         GtkTreeIter iter;
1453         IconData *id;
1454
1455         tip_unschedule(vfi);
1456
1457         id = vficon_find_data_by_coord(vfi, (gint)bevent->x, (gint)bevent->y, &iter);
1458
1459         vfi->click_id = id;
1460         vficon_selection_add(vfi, vfi->click_id, SELECTION_PRELIGHT, &iter);
1461
1462         switch (bevent->button)
1463                 {
1464                 case MOUSE_BUTTON_LEFT:
1465                         if (!GTK_WIDGET_HAS_FOCUS(vfi->listview))
1466                                 {
1467                                 gtk_widget_grab_focus(vfi->listview);
1468                                 }
1469 #if 0
1470                         if (bevent->type == GDK_2BUTTON_PRESS &&
1471                             vfi->layout)
1472                                 {
1473                                 vficon_selection_remove(vfi, vfi->click_id, SELECTION_PRELIGHT, &iter);
1474                                 layout_image_full_screen_start(vfi->layout);
1475                                 }
1476 #endif
1477                         break;
1478                 case MOUSE_BUTTON_RIGHT:
1479                         vfi->popup = vficon_pop_menu(vfi, (id != NULL));
1480                         gtk_menu_popup(GTK_MENU(vfi->popup), NULL, NULL, NULL, NULL, bevent->button, bevent->time);
1481                         break;
1482                 default:
1483                         break;
1484                 }
1485
1486         return TRUE;
1487 }
1488
1489 static gint vficon_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1490 {
1491         ViewFileIcon *vfi = data;
1492         GtkTreeIter iter;
1493         IconData *id = NULL;
1494         gint was_selected = FALSE;
1495
1496         tip_schedule(vfi);
1497
1498         if ((gint)bevent->x != 0 || (gint) bevent->y != 0)
1499                 {
1500                 id = vficon_find_data_by_coord(vfi, (gint)bevent->x, (gint)bevent->y, &iter);
1501                 }
1502
1503         if (vfi->click_id)
1504                 {
1505                 vficon_selection_remove(vfi, vfi->click_id, SELECTION_PRELIGHT, NULL);
1506                 }
1507
1508         if (id) was_selected = (id->selected & SELECTION_SELECTED);
1509
1510         if (bevent->button == MOUSE_BUTTON_LEFT &&
1511             id && vfi->click_id == id)
1512                 {
1513                 vficon_set_focus(vfi, id);
1514
1515                 if (bevent->state & GDK_CONTROL_MASK)
1516                         {
1517                         gint select;
1518
1519                         select = !(id->selected & SELECTION_SELECTED);
1520                         if ((bevent->state & GDK_SHIFT_MASK) && vfi->prev_selection)
1521                                 {
1522                                 vficon_select_region_util(vfi, vfi->prev_selection, id, select);
1523                                 }
1524                         else
1525                                 {
1526                                 vficon_select_util(vfi, id, select);
1527                                 }
1528                         }
1529                 else
1530                         {
1531                         vficon_select_none(vfi);
1532
1533                         if ((bevent->state & GDK_SHIFT_MASK) &&
1534                             vfi->prev_selection)
1535                                 {
1536                                 vficon_select_region_util(vfi, vfi->prev_selection, id, TRUE);
1537                                 }
1538                         else
1539                                 {
1540                                 vficon_select_util(vfi, id, TRUE);
1541                                 was_selected = FALSE;
1542                                 }
1543                         }
1544                 }
1545         else if (bevent->button == MOUSE_BUTTON_MIDDLE &&
1546                  id && vfi->click_id == id)
1547                 {
1548                 vficon_select_util(vfi, id, !(id->selected & SELECTION_SELECTED));
1549                 }
1550
1551         if (id && !was_selected &&
1552             (id->selected & SELECTION_SELECTED))
1553                 {
1554                 vficon_send_layout_select(vfi, id);
1555                 }
1556
1557         return TRUE;
1558 }
1559
1560 static gint vficon_leave_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer data)
1561 {
1562         ViewFileIcon *vfi = data;
1563
1564         tip_unschedule(vfi);
1565         return FALSE;
1566 }
1567
1568 /*
1569  *-------------------------------------------------------------------
1570  * population
1571  *-------------------------------------------------------------------
1572  */
1573
1574 static gboolean vficon_destroy_node_cb(GtkTreeModel *store, GtkTreePath *tpath, GtkTreeIter *iter, gpointer data)
1575 {
1576         GList *list;
1577
1578         gtk_tree_model_get(store, iter, FILE_COLUMN_POINTER, &list, -1);
1579         g_list_free(list);
1580
1581         return FALSE;
1582 }
1583
1584 static void vficon_clear_store(ViewFileIcon *vfi)
1585 {
1586         GtkTreeModel *store;
1587
1588         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview));
1589         gtk_tree_model_foreach(store, vficon_destroy_node_cb, NULL);
1590
1591         gtk_list_store_clear(GTK_LIST_STORE(store));
1592 }
1593
1594 static void vficon_set_thumb(ViewFileIcon *vfi, FileData *fd, GdkPixbuf *pb)
1595 {
1596         GtkTreeModel *store;
1597         GtkTreeIter iter;
1598         GList *list;
1599
1600         if (!vficon_find_iter(vfi, vficon_icon_data(vfi, fd), &iter, NULL)) return;
1601
1602         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview));
1603
1604         if (pb) g_object_ref(pb);
1605         if (fd->pixbuf) g_object_unref(fd->pixbuf);
1606         fd->pixbuf = pb;
1607
1608         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1609         gtk_list_store_set(GTK_LIST_STORE(store), &iter, FILE_COLUMN_POINTER, list, -1);
1610 }
1611
1612 static GList *vficon_add_row(ViewFileIcon *vfi, GtkTreeIter *iter)
1613 {
1614         GtkListStore *store;
1615         GList *list = NULL;
1616         gint i;
1617
1618         for (i = 0; i < vfi->columns; i++) list = g_list_prepend(list, NULL);
1619
1620         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview)));
1621         gtk_list_store_append(store, iter);
1622         gtk_list_store_set(store, iter, FILE_COLUMN_POINTER, list, -1);
1623
1624         return list;
1625 }
1626
1627 static void vficon_populate(ViewFileIcon *vfi, gint resize, gint keep_position)
1628 {
1629         GtkTreeModel *store;
1630         GtkTreePath *tpath;
1631         gint row;
1632         GList *work;
1633         IconData *visible_id = NULL;
1634
1635         vficon_verify_selections(vfi);
1636
1637         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview));
1638
1639         if (keep_position && GTK_WIDGET_REALIZED(vfi->listview) &&
1640             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vfi->listview), 0, 0, &tpath, NULL, NULL, NULL))
1641                 {
1642                 GtkTreeIter iter;
1643                 GList *list;
1644
1645                 gtk_tree_model_get_iter(store, &iter, tpath);
1646                 gtk_tree_path_free(tpath);
1647
1648                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1649                 if (list) visible_id = list->data;
1650                 }
1651
1652         vficon_clear_store(vfi);
1653
1654         if (resize)
1655                 {
1656                 gint i;
1657                 gint thumb_width;
1658
1659                 thumb_width = vficon_get_icon_width(vfi);
1660
1661                 for (i = 0; i < VFICON_MAX_COLUMNS; i++)
1662                         {
1663                         GtkTreeViewColumn *column;
1664                         GtkCellRenderer *cell;
1665                         GList *list;
1666
1667                         column = gtk_tree_view_get_column(GTK_TREE_VIEW(vfi->listview), i);
1668                         gtk_tree_view_column_set_visible(column, (i < vfi->columns));
1669                         gtk_tree_view_column_set_fixed_width(column, thumb_width + (THUMB_BORDER_PADDING * 6));
1670
1671                         list = gtk_tree_view_column_get_cell_renderers(column);
1672                         cell = (list) ? list->data : NULL;
1673                         g_list_free(list);
1674
1675                         if (cell && GQV_IS_CELL_RENDERER_ICON(cell))
1676                                 {
1677                                 g_object_set(G_OBJECT(cell), "fixed_width", thumb_width,
1678                                                              "fixed_height", options->thumbnails.max_height,
1679                                                              "show_text", vfi->show_text, NULL);
1680                                 }
1681                         }
1682                 if (GTK_WIDGET_REALIZED(vfi->listview)) gtk_tree_view_columns_autosize(GTK_TREE_VIEW(vfi->listview));
1683                 }
1684
1685         row = -1;
1686         work = vfi->list;
1687         while (work)
1688                 {
1689                 GList *list;
1690                 GtkTreeIter iter;
1691
1692                 row++;
1693
1694                 list = vficon_add_row(vfi, &iter);
1695                 while (work && list)
1696                         {
1697                         IconData *id;
1698
1699                         id = work->data;
1700                         id->row = row;
1701
1702                         list->data = work->data;
1703                         list = list->next;
1704                         work = work->next;
1705                         }
1706                 }
1707
1708         if (visible_id &&
1709             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vfi->listview), 0, 0, &tpath, NULL, NULL, NULL))
1710                 {
1711                 GtkTreeIter iter;
1712                 GList *list;
1713
1714                 gtk_tree_model_get_iter(store, &iter, tpath);
1715                 gtk_tree_path_free(tpath);
1716
1717                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1718                 if (g_list_find(list, visible_id) == NULL &&
1719                     vficon_find_iter(vfi, visible_id, &iter, NULL))
1720                         {
1721                         tree_view_row_make_visible(GTK_TREE_VIEW(vfi->listview), &iter, FALSE);
1722                         }
1723                 }
1724
1725         vfi->rows = row + 1;
1726
1727         vficon_send_update(vfi);
1728         vficon_thumb_update(vfi);
1729 }
1730
1731 static void vficon_populate_at_new_size(ViewFileIcon *vfi, gint w, gint h, gint force)
1732 {
1733         gint new_cols;
1734         gint thumb_width;
1735
1736         thumb_width = vficon_get_icon_width(vfi);
1737
1738         new_cols = w / (thumb_width + (THUMB_BORDER_PADDING * 6));
1739         if (new_cols < 1) new_cols = 1;
1740
1741         if (!force && new_cols == vfi->columns) return;
1742
1743         vfi->columns = new_cols;
1744
1745         vficon_populate(vfi, TRUE, TRUE);
1746
1747         DEBUG_1("col tab pop cols=%d rows=%d", vfi->columns, vfi->rows);
1748 }
1749
1750 static void vficon_sync(ViewFileIcon *vfi)
1751 {
1752         GtkTreeModel *store;
1753         GtkTreeIter iter;
1754         GList *work;
1755         gint r, c;
1756
1757         if (vfi->rows == 0) return;
1758
1759         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview));
1760
1761         r = -1;
1762         c = 0;
1763
1764         work = vfi->list;
1765         while (work)
1766                 {
1767                 GList *list;
1768                 r++;
1769                 c = 0;
1770                 if (gtk_tree_model_iter_nth_child(store, &iter, NULL, r))
1771                         {
1772                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1773                         gtk_list_store_set(GTK_LIST_STORE(store), &iter, FILE_COLUMN_POINTER, list, -1);
1774                         }
1775                 else
1776                         {
1777                         list = vficon_add_row(vfi, &iter);
1778                         }
1779
1780                 while (list)
1781                         {
1782                         IconData *id;
1783
1784                         if (work)
1785                                 {
1786                                 id = work->data;
1787                                 work = work->next;
1788                                 c++;
1789
1790                                 id->row = r;
1791                                 }
1792                         else
1793                                 {
1794                                 id = NULL;
1795                                 }
1796
1797                         list->data = id;
1798                         list = list->next;
1799                         }
1800                 }
1801
1802         r++;
1803         while (gtk_tree_model_iter_nth_child(store, &iter, NULL, r))
1804                 {
1805                 GList *list;
1806
1807                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1808                 gtk_list_store_remove(GTK_LIST_STORE(store), &iter);
1809                 g_list_free(list);
1810                 }
1811
1812         vfi->rows = r;
1813
1814         vficon_update_focus(vfi);
1815 }
1816
1817 static gint vficon_sync_idle_cb(gpointer data)
1818 {
1819         ViewFileIcon *vfi = data;
1820
1821         if (vfi->sync_idle_id == -1) return FALSE;
1822         vfi->sync_idle_id = -1;
1823
1824         vficon_sync(vfi);
1825         return FALSE;
1826 }
1827
1828 static void vficon_sync_idle(ViewFileIcon *vfi)
1829 {
1830         if (vfi->sync_idle_id == -1)
1831                 {
1832                 /* high priority, the view needs to be resynced before a redraw
1833                  * may contain invalid pointers at this time
1834                  */
1835                 vfi->sync_idle_id = g_idle_add_full(G_PRIORITY_HIGH, vficon_sync_idle_cb, vfi, NULL);
1836                 }
1837 }
1838
1839 static void vficon_sized_cb(GtkWidget *widget, GtkAllocation *allocation, gpointer data)
1840 {
1841         ViewFileIcon *vfi = data;
1842
1843         vficon_populate_at_new_size(vfi, allocation->width, allocation->height, FALSE);
1844 }
1845
1846 /*
1847  *-----------------------------------------------------------------------------
1848  * misc
1849  *-----------------------------------------------------------------------------
1850  */
1851
1852 void vficon_sort_set(ViewFileIcon *vfi, SortType type, gint ascend)
1853 {
1854         if (vfi->sort_method == type && vfi->sort_ascend == ascend) return;
1855
1856         vfi->sort_method = type;
1857         vfi->sort_ascend = ascend;
1858
1859         if (!vfi->list) return;
1860
1861         vfi->list = iconlist_sort(vfi->list, vfi->sort_method, vfi->sort_ascend);
1862         vficon_sync(vfi);
1863 }
1864
1865 /*
1866  *-----------------------------------------------------------------------------
1867  * thumb updates
1868  *-----------------------------------------------------------------------------
1869  */
1870
1871 static gint vficon_thumb_next(ViewFileIcon *vfi);
1872
1873 static void vficon_thumb_status(ViewFileIcon *vfi, gdouble val, const gchar *text)
1874 {
1875         if (vfi->func_thumb_status)
1876                 {
1877                 vfi->func_thumb_status(vfi, val, text, vfi->data_thumb_status);
1878                 }
1879 }
1880
1881 static void vficon_thumb_cleanup(ViewFileIcon *vfi)
1882 {
1883         vficon_thumb_status(vfi, 0.0, NULL);
1884
1885         g_list_free(vfi->thumbs_list);
1886         vfi->thumbs_list = NULL;
1887         vfi->thumbs_count = 0;
1888         vfi->thumbs_running = FALSE;
1889
1890         thumb_loader_free(vfi->thumbs_loader);
1891         vfi->thumbs_loader = NULL;
1892
1893         vfi->thumbs_fd = NULL;
1894 }
1895
1896 static void vficon_thumb_stop(ViewFileIcon *vfi)
1897 {
1898         if (vfi->thumbs_running) vficon_thumb_cleanup(vfi);
1899 }
1900
1901 static void vficon_thumb_do(ViewFileIcon *vfi, ThumbLoader *tl, FileData *fd)
1902 {
1903         GdkPixbuf *pixbuf;
1904
1905         if (!fd) return;
1906
1907         pixbuf = thumb_loader_get_pixbuf(tl, TRUE);
1908         vficon_set_thumb(vfi, fd, pixbuf);
1909         g_object_unref(pixbuf);
1910
1911         vficon_thumb_status(vfi, (gdouble)(vfi->thumbs_count) / g_list_length(vfi->list), _("Loading thumbs..."));
1912 }
1913
1914 static void vficon_thumb_error_cb(ThumbLoader *tl, gpointer data)
1915 {
1916         ViewFileIcon *vfi = data;
1917
1918         if (vfi->thumbs_fd && vfi->thumbs_loader == tl)
1919                 {
1920                 vficon_thumb_do(vfi, tl, vfi->thumbs_fd);
1921                 }
1922
1923         while (vficon_thumb_next(vfi));
1924 }
1925
1926 static void vficon_thumb_done_cb(ThumbLoader *tl, gpointer data)
1927 {
1928         ViewFileIcon *vfi = data;
1929
1930         if (vfi->thumbs_fd && vfi->thumbs_loader == tl)
1931                 {
1932                 vficon_thumb_do(vfi, tl, vfi->thumbs_fd);
1933                 }
1934
1935         while (vficon_thumb_next(vfi));
1936 }
1937
1938 static gint vficon_thumb_next(ViewFileIcon *vfi)
1939 {
1940         GtkTreePath *tpath;
1941         FileData *fd = NULL;
1942
1943         if (!GTK_WIDGET_REALIZED(vfi->listview))
1944                 {
1945                 vficon_thumb_status(vfi, 0.0, NULL);
1946                 return FALSE;
1947                 }
1948
1949         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vfi->listview), 0, 0, &tpath, NULL, NULL, NULL))
1950                 {
1951                 GtkTreeModel *store;
1952                 GtkTreeIter iter;
1953                 gint valid = TRUE;
1954
1955                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview));
1956                 gtk_tree_model_get_iter(store, &iter, tpath);
1957                 gtk_tree_path_free(tpath);
1958
1959                 while (!fd && valid && tree_view_row_get_visibility(GTK_TREE_VIEW(vfi->listview), &iter, FALSE) == 0)
1960                         {
1961                         GList *list;
1962
1963                         gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
1964
1965                         while (!fd && list)
1966                                 {
1967                                 IconData *id = list->data;
1968                                 if (id && !id->fd->pixbuf) fd = id->fd;
1969                                 list = list->next;
1970                                 }
1971
1972                         valid = gtk_tree_model_iter_next(store, &iter);
1973                         }
1974                 }
1975
1976         /* then find first undone */
1977
1978         if (!fd)
1979                 {
1980                 GList *work = vfi->list;
1981                 while (work && !fd)
1982                         {
1983                         IconData *id = work->data;
1984                         FileData *fd_p = id->fd;
1985                         work = work->next;
1986
1987                         if (!fd_p->pixbuf) fd = fd_p;
1988                         }
1989                 }
1990
1991         if (!fd)
1992                 {
1993                 /* done */
1994                 vficon_thumb_cleanup(vfi);
1995                 return FALSE;
1996                 }
1997
1998         vfi->thumbs_count++;
1999
2000         vfi->thumbs_fd = fd;
2001
2002         thumb_loader_free(vfi->thumbs_loader);
2003
2004         vfi->thumbs_loader = thumb_loader_new(options->thumbnails.max_width, options->thumbnails.max_height);
2005         thumb_loader_set_callbacks(vfi->thumbs_loader,
2006                                    vficon_thumb_done_cb,
2007                                    vficon_thumb_error_cb,
2008                                    NULL,
2009                                    vfi);
2010
2011         if (!thumb_loader_start(vfi->thumbs_loader, fd->path))
2012                 {
2013                 /* set icon to unknown, continue */
2014                 DEBUG_1("thumb loader start failed %s", vfi->thumbs_loader->path);
2015                 vficon_thumb_do(vfi, vfi->thumbs_loader, fd);
2016
2017                 return TRUE;
2018                 }
2019
2020         return FALSE;
2021 }
2022
2023 static void vficon_thumb_update(ViewFileIcon *vfi)
2024 {
2025         vficon_thumb_stop(vfi);
2026
2027         vficon_thumb_status(vfi, 0.0, _("Loading thumbs..."));
2028         vfi->thumbs_running = TRUE;
2029
2030         while (vficon_thumb_next(vfi));
2031 }
2032
2033 /*
2034  *-----------------------------------------------------------------------------
2035  * row stuff
2036  *-----------------------------------------------------------------------------
2037  */
2038
2039 FileData *vficon_index_get_data(ViewFileIcon *vfi, gint row)
2040 {
2041         IconData *id;
2042
2043         id = g_list_nth_data(vfi->list, row);
2044         return id ? id->fd : NULL;
2045 }
2046
2047 gchar *vficon_index_get_path(ViewFileIcon *vfi, gint row)
2048 {
2049         FileData *fd;
2050         IconData *id;
2051
2052         id = g_list_nth_data(vfi->list, row);
2053         fd = id ? id->fd : NULL;
2054
2055         return (fd ? fd->path : NULL);
2056 }
2057
2058 gint vficon_index_by_path(ViewFileIcon *vfi, const gchar *path)
2059 {
2060         gint p = 0;
2061         GList *work;
2062
2063         if (!path) return -1;
2064
2065         work = vfi->list;
2066         while (work)
2067                 {
2068                 IconData *id = work->data;
2069                 FileData *fd = id->fd;
2070                 if (strcmp(path, fd->path) == 0) return p;
2071                 work = work->next;
2072                 p++;
2073                 }
2074
2075         return -1;
2076 }
2077
2078 gint vficon_index_by_fd(ViewFileIcon *vfi, FileData *in_fd)
2079 {
2080         gint p = 0;
2081         GList *work;
2082
2083         if (!in_fd) return -1;
2084
2085         work = vfi->list;
2086         while (work)
2087                 {
2088                 IconData *id = work->data;
2089                 FileData *fd = id->fd;
2090                 if (fd == in_fd) return p;
2091                 work = work->next;
2092                 p++;
2093                 }
2094
2095         return -1;
2096 }
2097
2098 static gint vficon_index_by_id(ViewFileIcon *vfi, IconData *in_id)
2099 {
2100         gint p = 0;
2101         GList *work;
2102
2103         if (!in_id) return -1;
2104
2105         work = vfi->list;
2106         while (work)
2107                 {
2108                 IconData *id = work->data;
2109                 if (id == in_id) return p;
2110                 work = work->next;
2111                 p++;
2112                 }
2113
2114         return -1;
2115 }
2116
2117 gint vficon_count(ViewFileIcon *vfi, gint64 *bytes)
2118 {
2119         if (bytes)
2120                 {
2121                 gint64 b = 0;
2122                 GList *work;
2123
2124                 work = vfi->list;
2125                 while (work)
2126                         {
2127
2128                         IconData *id = work->data;
2129                         FileData *fd = id->fd;
2130                         work = work->next;
2131                         b += fd->size;
2132                         }
2133
2134                 *bytes = b;
2135                 }
2136
2137         return g_list_length(vfi->list);
2138 }
2139
2140 GList *vficon_get_list(ViewFileIcon *vfi)
2141 {
2142         GList *list = NULL;
2143         GList *work;
2144
2145         work = vfi->list;
2146         while (work)
2147                 {
2148                 IconData *id = work->data;
2149                 FileData *fd = id->fd;
2150                 work = work->next;
2151
2152                 list = g_list_prepend(list, file_data_ref(fd));
2153                 }
2154
2155         return g_list_reverse(list);
2156 }
2157
2158 /*
2159  *-----------------------------------------------------------------------------
2160  *
2161  *-----------------------------------------------------------------------------
2162  */
2163
2164 static gint vficon_refresh_real(ViewFileIcon *vfi, gint keep_position)
2165 {
2166         gint ret = TRUE;
2167         GList *old_list;
2168         GList *work;
2169         IconData *focus_id;
2170
2171         focus_id = vfi->focus_id;
2172
2173         old_list = vfi->list;
2174         vfi->list = NULL;
2175
2176         if (vfi->path)
2177                 {
2178                 ret = iconlist_read(vfi->path, &vfi->list);
2179                 }
2180
2181         /* check for same files from old_list */
2182         work = old_list;
2183         while (work)
2184                 {
2185                 IconData *id;
2186                 FileData *fd;
2187
2188                 GList *needle;
2189
2190                 id = work->data;
2191                 fd = id->fd;
2192
2193                 needle = vfi->list;
2194                 while (needle)
2195                         {
2196                         IconData *idn = needle->data;
2197                         FileData *fdn = idn->fd;
2198                         if (fdn == fd)
2199                                 {
2200                                 /* swap, to retain old thumb, selection */
2201                                 needle->data = id;
2202                                 work->data = idn;
2203                                 needle = NULL;
2204                                 }
2205                         else
2206                                 {
2207                                 needle = needle->next;
2208                                 }
2209                         }
2210
2211                 work = work->next;
2212                 }
2213
2214         vfi->list = iconlist_sort(vfi->list, vfi->sort_method, vfi->sort_ascend);
2215
2216         work = old_list;
2217         while (work)
2218                 {
2219                 IconData *id = work->data;
2220                 work = work->next;
2221
2222                 if (id == vfi->prev_selection) vfi->prev_selection = NULL;
2223                 if (id == vfi->click_id) vfi->click_id = NULL;
2224                 }
2225
2226         vficon_populate(vfi, TRUE, keep_position);
2227
2228         /* attempt to keep focus on same icon when refreshing */
2229         if (focus_id && g_list_find(vfi->list, focus_id))
2230                 {
2231                 vficon_set_focus(vfi, focus_id);
2232                 }
2233
2234         iconlist_free(old_list);
2235
2236         return ret;
2237 }
2238
2239 gint vficon_refresh(ViewFileIcon *vfi)
2240 {
2241         return vficon_refresh_real(vfi, TRUE);
2242 }
2243
2244 /*
2245  *-----------------------------------------------------------------------------
2246  * draw, etc.
2247  *-----------------------------------------------------------------------------
2248  */
2249
2250 typedef struct _ColumnData ColumnData;
2251 struct _ColumnData
2252 {
2253         ViewFileIcon *vfi;
2254         gint number;
2255 };
2256
2257 static void vficon_cell_data_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
2258                                 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
2259 {
2260         ColumnData *cd = data;
2261         ViewFileIcon *vfi;
2262         GtkStyle *style;
2263         GList *list;
2264         GdkColor color_fg;
2265         GdkColor color_bg;
2266         IconData *id;
2267
2268         vfi = cd->vfi;
2269
2270         gtk_tree_model_get(tree_model, iter, FILE_COLUMN_POINTER, &list, -1);
2271
2272         id = g_list_nth_data(list, cd->number);
2273
2274         if (id) g_assert(id->fd->magick == 0x12345678);
2275
2276         style = gtk_widget_get_style(vfi->listview);
2277         if (id && id->selected & SELECTION_SELECTED)
2278                 {
2279                 memcpy(&color_fg, &style->text[GTK_STATE_SELECTED], sizeof(color_fg));
2280                 memcpy(&color_bg, &style->base[GTK_STATE_SELECTED], sizeof(color_bg));
2281                 }
2282         else
2283                 {
2284                 memcpy(&color_fg, &style->text[GTK_STATE_NORMAL], sizeof(color_fg));
2285                 memcpy(&color_bg, &style->base[GTK_STATE_NORMAL], sizeof(color_bg));
2286                 }
2287
2288         if (id && id->selected & SELECTION_PRELIGHT)
2289                 {
2290 #if 0
2291                 shift_color(&color_fg, -1, 0);
2292 #endif
2293                 shift_color(&color_bg, -1, 0);
2294                 }
2295
2296         if (GQV_IS_CELL_RENDERER_ICON(cell))
2297                 {
2298                 if (id)
2299                         {
2300                         g_object_set(cell,      "pixbuf", id->fd->pixbuf,
2301                                                 "text", id->fd->name,
2302                                                 "cell-background-gdk", &color_bg,
2303                                                 "cell-background-set", TRUE,
2304                                                 "foreground-gdk", &color_fg,
2305                                                 "foreground-set", TRUE,
2306                                                 "has-focus", (vfi->focus_id == id), NULL);
2307                         }
2308                 else
2309                         {
2310                         g_object_set(cell,      "pixbuf", NULL,
2311                                                 "text", NULL,
2312                                                 "cell-background-set", FALSE,
2313                                                 "foreground-set", FALSE,
2314                                                 "has-focus", FALSE, NULL);
2315                         }
2316                 }
2317 }
2318
2319 static void vficon_append_column(ViewFileIcon *vfi, gint n)
2320 {
2321         ColumnData *cd;
2322         GtkTreeViewColumn *column;
2323         GtkCellRenderer *renderer;
2324
2325         column = gtk_tree_view_column_new();
2326         gtk_tree_view_column_set_min_width(column, 0);
2327
2328         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2329         gtk_tree_view_column_set_alignment(column, 0.5);
2330
2331         renderer = gqv_cell_renderer_icon_new();
2332         gtk_tree_view_column_pack_start(column, renderer, FALSE);
2333         g_object_set(G_OBJECT(renderer), "xpad", THUMB_BORDER_PADDING * 2,
2334                                          "ypad", THUMB_BORDER_PADDING,
2335                                          "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
2336
2337         g_object_set_data(G_OBJECT(column), "column_number", GINT_TO_POINTER(n));
2338
2339         cd = g_new0(ColumnData, 1);
2340         cd->vfi = vfi;
2341         cd->number = n;
2342         gtk_tree_view_column_set_cell_data_func(column, renderer, vficon_cell_data_cb, cd, g_free);
2343
2344         gtk_tree_view_append_column(GTK_TREE_VIEW(vfi->listview), column);
2345 }
2346
2347 /*
2348  *-----------------------------------------------------------------------------
2349  * base
2350  *-----------------------------------------------------------------------------
2351  */
2352
2353 gint vficon_set_path(ViewFileIcon *vfi, const gchar *path)
2354 {
2355         gint ret;
2356
2357         if (!path) return FALSE;
2358         if (vfi->path && strcmp(path, vfi->path) == 0) return TRUE;
2359
2360         g_free(vfi->path);
2361         vfi->path = g_strdup(path);
2362
2363         g_list_free(vfi->selection);
2364         vfi->selection = NULL;
2365
2366         iconlist_free(vfi->list);
2367         vfi->list = NULL;
2368
2369         /* NOTE: populate will clear the store for us */
2370         ret = vficon_refresh_real(vfi, FALSE);
2371
2372         vfi->focus_id = NULL;
2373         vficon_move_focus(vfi, 0, 0, FALSE);
2374
2375         return ret;
2376 }
2377
2378 static void vficon_destroy_cb(GtkWidget *widget, gpointer data)
2379 {
2380         ViewFileIcon *vfi = data;
2381
2382         if (vfi->popup)
2383                 {
2384                 g_signal_handlers_disconnect_matched(G_OBJECT(vfi->popup), G_SIGNAL_MATCH_DATA,
2385                                                      0, 0, 0, NULL, vfi);
2386                 gtk_widget_destroy(vfi->popup);
2387                 }
2388
2389         if (vfi->sync_idle_id != -1) g_source_remove(vfi->sync_idle_id);
2390
2391         tip_unschedule(vfi);
2392
2393         vficon_thumb_cleanup(vfi);
2394
2395         g_free(vfi->path);
2396
2397         iconlist_free(vfi->list);
2398         g_list_free(vfi->selection);
2399         g_free(vfi);
2400 }
2401
2402 ViewFileIcon *vficon_new(const gchar *path)
2403 {
2404         ViewFileIcon *vfi;
2405         GtkListStore *store;
2406         GtkTreeSelection *selection;
2407         gint i;
2408
2409         vfi = g_new0(ViewFileIcon, 1);
2410
2411         vfi->path = NULL;
2412         vfi->sort_method = SORT_NAME;
2413         vfi->sort_ascend = TRUE;
2414
2415         vfi->selection = NULL;
2416         vfi->prev_selection = NULL;
2417
2418         vfi->tip_window = NULL;
2419         vfi->tip_delay_id = -1;
2420
2421         vfi->focus_row = 0;
2422         vfi->focus_column = 0;
2423         vfi->focus_id = NULL;
2424
2425         vfi->show_text = options->show_icon_names;
2426
2427         vfi->sync_idle_id = -1;
2428
2429         vfi->popup = NULL;
2430
2431         vfi->widget = gtk_scrolled_window_new(NULL, NULL);
2432         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(vfi->widget), GTK_SHADOW_IN);
2433         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vfi->widget),
2434                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2435         g_signal_connect(G_OBJECT(vfi->widget), "destroy",
2436                          G_CALLBACK(vficon_destroy_cb), vfi);
2437
2438         store = gtk_list_store_new(1, G_TYPE_POINTER);
2439         vfi->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2440         g_object_unref(store);
2441
2442         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vfi->listview));
2443         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_NONE);
2444
2445         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vfi->listview), FALSE);
2446         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vfi->listview), FALSE);
2447
2448         for (i = 0; i < VFICON_MAX_COLUMNS; i++)
2449                 {
2450                 vficon_append_column(vfi, i);
2451                 }
2452
2453         /* zero width column to hide tree view focus, we draw it ourselves */
2454         vficon_append_column(vfi, i);
2455         /* end column to fill white space */
2456         vficon_append_column(vfi, i);
2457
2458         g_signal_connect(G_OBJECT(vfi->listview), "size_allocate",
2459                          G_CALLBACK(vficon_sized_cb), vfi);
2460         g_signal_connect(G_OBJECT(vfi->listview), "key_press_event",
2461                          G_CALLBACK(vficon_press_key_cb), vfi);
2462
2463         gtk_container_add(GTK_CONTAINER(vfi->widget), vfi->listview);
2464         gtk_widget_show(vfi->listview);
2465
2466         vficon_dnd_init(vfi);
2467
2468         gtk_widget_set_events(vfi->listview, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK |
2469                               GDK_BUTTON_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK);
2470         g_signal_connect(G_OBJECT(vfi->listview), "button_press_event",
2471                          G_CALLBACK(vficon_press_cb), vfi);
2472         g_signal_connect(G_OBJECT(vfi->listview), "button_release_event",
2473                          G_CALLBACK(vficon_release_cb), vfi);
2474         g_signal_connect(G_OBJECT(vfi->listview),"motion_notify_event",
2475                          G_CALLBACK(vficon_motion_cb), vfi);
2476         g_signal_connect(G_OBJECT(vfi->listview), "leave_notify_event",
2477                          G_CALLBACK(vficon_leave_cb), vfi);
2478
2479         /* force vfi->columns to be at least 1 (sane) - this will be corrected in the size_cb */
2480         vficon_populate_at_new_size(vfi, 1, 1, FALSE);
2481
2482         if (path) vficon_set_path(vfi, path);
2483
2484         return vfi;
2485 }
2486
2487 void vficon_set_status_func(ViewFileIcon *vfi,
2488                             void (*func)(ViewFileIcon *vfi, gpointer data), gpointer data)
2489 {
2490         vfi->func_status = func;
2491         vfi->data_status = data;
2492 }
2493
2494 void vficon_set_thumb_status_func(ViewFileIcon *vfi,
2495                                   void (*func)(ViewFileIcon *vfi, gdouble val, const gchar *text, gpointer data),
2496                                   gpointer data)
2497 {
2498         vfi->func_thumb_status = func;
2499         vfi->data_thumb_status = data;
2500 }
2501
2502 void vficon_set_layout(ViewFileIcon *vfi, LayoutWindow *layout)
2503 {
2504         vfi->layout = layout;
2505 }
2506
2507 /*
2508  *-----------------------------------------------------------------------------
2509  * maintenance (for rename, move, remove)
2510  *-----------------------------------------------------------------------------
2511  */
2512
2513 static gint vficon_maint_find_closest(ViewFileIcon *vfi, gint row, gint count, GList *ignore_list)
2514 {
2515         GList *list = NULL;
2516         GList *work;
2517         gint rev = row - 1;
2518         row ++;
2519
2520         work = ignore_list;
2521         while (work)
2522                 {
2523                 FileData *fd = work->data;
2524                 gint f = vficon_index_by_fd(vfi, work->data);
2525                 g_assert(fd->magick == 0x12345678);
2526                 if (f >= 0) list = g_list_prepend(list, GINT_TO_POINTER(f));
2527                 work = work->next;
2528                 }
2529
2530         while (list)
2531                 {
2532                 gint c = TRUE;
2533                 work = list;
2534                 while (work && c)
2535                         {
2536                         gpointer p = work->data;
2537                         work = work->next;
2538                         if (row == GPOINTER_TO_INT(p))
2539                                 {
2540                                 row++;
2541                                 c = FALSE;
2542                                 }
2543                         if (rev == GPOINTER_TO_INT(p))
2544                                 {
2545                                 rev--;
2546                                 c = FALSE;
2547                                 }
2548                         if (!c) list = g_list_remove(list, p);
2549                         }
2550                 if (c && list)
2551                         {
2552                         g_list_free(list);
2553                         list = NULL;
2554                         }
2555                 }
2556         if (row > count - 1)
2557                 {
2558                 if (rev < 0)
2559                         return -1;
2560                 else
2561                         return rev;
2562                 }
2563         else
2564                 {
2565                 return row;
2566                 }
2567 }
2568
2569 gint vficon_maint_renamed(ViewFileIcon *vfi, FileData *fd)
2570 {
2571         gint ret = FALSE;
2572         gint row;
2573         gchar *source_base;
2574         gchar *dest_base;
2575         IconData *id = vficon_icon_data(vfi, fd);
2576
2577         if (!id) return FALSE;
2578
2579         row = vficon_index_by_id(vfi, id);
2580         if (row < 0) return FALSE;
2581
2582         source_base = remove_level_from_path(fd->change->source);
2583         dest_base = remove_level_from_path(fd->change->dest);
2584
2585         if (strcmp(source_base, dest_base) == 0)
2586                 {
2587                 vfi->list = g_list_remove(vfi->list, id);
2588                 vfi->list = iconlist_insert_sort(vfi->list, id, vfi->sort_method, vfi->sort_ascend);
2589
2590                 vficon_sync_idle(vfi);
2591                 ret = TRUE;
2592                 }
2593         else
2594                 {
2595                 ret = vficon_maint_removed(vfi, fd, NULL);
2596                 }
2597
2598         g_free(source_base);
2599         g_free(dest_base);
2600
2601         return ret;
2602 }
2603
2604 gint vficon_maint_removed(ViewFileIcon *vfi, FileData *fd, GList *ignore_list)
2605 {
2606         gint row;
2607         gint new_row = -1;
2608         GtkTreeModel *store;
2609         GtkTreeIter iter;
2610         IconData *id = vficon_icon_data(vfi, fd);
2611
2612         if (!id) return FALSE;
2613
2614         row = g_list_index(vfi->list, id);
2615         if (row < 0) return FALSE;
2616
2617         if ((id->selected & SELECTION_SELECTED) &&
2618             layout_image_get_collection(vfi->layout, NULL) == NULL)
2619                 {
2620                 vficon_unselect(vfi, id);
2621
2622                 if (!vfi->selection)
2623                         {
2624                         gint n;
2625
2626                         n = vficon_count(vfi, NULL);
2627                         if (ignore_list)
2628                                 {
2629                                 new_row = vficon_maint_find_closest(vfi, row, n, ignore_list);
2630                                 DEBUG_1("row = %d, closest is %d", row, new_row);
2631                                 }
2632                         else
2633                                 {
2634                                 if (row + 1 < n)
2635                                         {
2636                                         new_row = row + 1;
2637                                         }
2638                                 else if (row > 0)
2639                                         {
2640                                         new_row = row - 1;
2641                                         }
2642                                 }
2643                         }
2644                 else if (ignore_list)
2645                         {
2646                         GList *work;
2647
2648                         work = vfi->selection;
2649                         while (work)
2650                                 {
2651                                 IconData *ignore_id;
2652                                 FileData *ignore_fd;
2653                                 GList *tmp;
2654                                 gint match = FALSE;
2655
2656                                 ignore_id = work->data;
2657                                 ignore_fd = ignore_id->fd;
2658                                 g_assert(ignore_fd->magick == 0x12345678);
2659                                 work = work->next;
2660
2661                                 tmp = ignore_list;
2662                                 while (tmp && !match)
2663                                         {
2664                                         FileData *ignore_list_fd = tmp->data;
2665                                         g_assert(ignore_list_fd->magick == 0x12345678);
2666                                         tmp = tmp->next;
2667
2668                                         if (ignore_list_fd == ignore_fd)
2669                                                 {
2670                                                 match = TRUE;
2671                                                 }
2672                                         }
2673                                 if (!match)
2674                                         {
2675                                         new_row = g_list_index(vfi->list, ignore_id);
2676                                         work = NULL;
2677                                         }
2678                                 }
2679                         if (new_row == -1)
2680                                 {
2681                                 /* selection all ignored, use closest */
2682                                 new_row = vficon_maint_find_closest(vfi, row, vficon_count(vfi, NULL), ignore_list);
2683                                 }
2684                         }
2685                 else
2686                         {
2687                         new_row = g_list_index(vfi->list, vfi->selection->data);
2688                         }
2689                 if (new_row >= 0)
2690                         {
2691                         IconData *idn = g_list_nth_data(vfi->list, new_row);
2692
2693                         vficon_select(vfi, idn);
2694                         vficon_send_layout_select(vfi, idn);
2695                         }
2696                 }
2697
2698         /* Thumb loader check */
2699         if (fd == vfi->thumbs_fd) vfi->thumbs_fd = NULL;
2700         if (vfi->thumbs_count > 0) vfi->thumbs_count--;
2701
2702         if (vfi->prev_selection == id) vfi->prev_selection = NULL;
2703         if (vfi->click_id == id) vfi->click_id = NULL;
2704
2705         /* remove pointer to this fd from grid */
2706         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vfi->listview));
2707         if (id->row >= 0 &&
2708             gtk_tree_model_iter_nth_child(store, &iter, NULL, id->row))
2709                 {
2710                 GList *list;
2711
2712                 gtk_tree_model_get(store, &iter, FILE_COLUMN_POINTER, &list, -1);
2713                 list = g_list_find(list, id);
2714                 if (list) list->data = NULL;
2715                 }
2716
2717         vfi->list = g_list_remove(vfi->list, id);
2718         file_data_unref(fd);
2719         g_free(id);
2720
2721         vficon_sync_idle(vfi);
2722         vficon_send_update(vfi);
2723
2724         return TRUE;
2725 }
2726
2727 gint vficon_maint_moved(ViewFileIcon *vfi, FileData *fd, GList *ignore_list)
2728 {
2729         gint ret = FALSE;
2730         gchar *buf;
2731
2732         if (!fd->change->source || !vfi->path) return FALSE;
2733
2734         buf = remove_level_from_path(fd->change->source);
2735
2736         if (strcmp(buf, vfi->path) == 0)
2737                 {
2738                 ret = vficon_maint_removed(vfi, fd, ignore_list);
2739                 }
2740
2741         g_free(buf);
2742
2743         return ret;
2744 }