Introduce macros to display debug messages.
[geeqie.git] / src / collect-table.c
1 /*
2  * Geeqie
3  * (C) 2004 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "collect-table.h"
16
17 #include "cellrenderericon.h"
18 #include "collect-dlg.h"
19 #include "collect-io.h"
20 #include "dnd.h"
21 #include "dupe.h"
22 #include "editors.h"
23 #include "filelist.h"
24 #include "img-view.h"
25 #include "info.h"
26 #include "layout.h"
27 #include "layout_image.h"
28 #include "menu.h"
29 #include "print.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 #include "icons/marker.xpm"
37 #define MARKER_WIDTH 26
38 #define MARKER_HEIGHT 32
39
40 #include <gdk/gdkkeysyms.h> /* for keyboard values */
41
42 /* between these, the icon width is increased by thumb_max_width / 2 */
43 #define THUMB_MIN_ICON_WIDTH 128
44 #define THUMB_MAX_ICON_WIDTH 150
45
46 #define COLLECT_TABLE_MAX_COLUMNS 32
47 #define THUMB_BORDER_PADDING 2
48
49 #define COLLECT_TABLE_TIP_DELAY 500
50 #define COLLECT_TABLE_TIP_DELAY_PATH (COLLECT_TABLE_TIP_DELAY * 1.7)
51
52
53 enum {
54         CTABLE_COLUMN_POINTER = 0,
55         CTABLE_COLUMN_COUNT
56 };
57
58 typedef enum {
59         SELECTION_NONE          = 0,
60         SELECTION_SELECTED      = 1 << 0,
61         SELECTION_PRELIGHT      = 1 << 1,
62         SELECTION_FOCUS         = 1 << 2
63 } SelectionType;
64
65
66 #define INFO_SELECTED(x) (x->flag_mask & SELECTION_SELECTED)
67
68
69 static void collection_table_populate_at_new_size(CollectTable *ct, gint w, gint h, gint force);
70
71
72 /*
73  *-------------------------------------------------------------------
74  * more misc
75  *-------------------------------------------------------------------
76  */
77
78 static gint collection_table_find_position(CollectTable *ct, CollectInfo *info, gint *row, gint *col)
79 {
80         gint n;
81
82         n = g_list_index(ct->cd->list, info);
83
84         if (n < 0) return FALSE;
85
86         *row = n / ct->columns;
87         *col = n - (*row * ct->columns);
88
89         return TRUE;
90 }
91
92 static gint collection_table_find_iter(CollectTable *ct, CollectInfo *info, GtkTreeIter *iter, gint *column)
93 {
94         GtkTreeModel *store;
95         gint row, col;
96
97         store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview));
98         if (!collection_table_find_position(ct, info, &row, &col)) return FALSE;
99         if (!gtk_tree_model_iter_nth_child(store, iter, NULL, row)) return FALSE;
100         if (column) *column = col;
101
102         return TRUE;
103 }
104
105 static CollectInfo *collection_table_find_data(CollectTable *ct, gint row, gint col, GtkTreeIter *iter)
106 {
107         GtkTreeModel *store;
108         GtkTreeIter p;
109
110         if (row < 0 || col < 0) return NULL;
111
112         store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview));
113         if (gtk_tree_model_iter_nth_child(store, &p, NULL, row))
114                 {
115                 GList *list;
116
117                 gtk_tree_model_get(store, &p, CTABLE_COLUMN_POINTER, &list, -1);
118                 if (!list) return NULL;
119
120                 if (iter) *iter = p;
121
122                 return g_list_nth_data(list, col);
123                 }
124
125         return NULL;
126 }
127
128 static CollectInfo *collection_table_find_data_by_coord(CollectTable *ct, gint x, gint y, GtkTreeIter *iter)
129 {
130         GtkTreePath *tpath;
131         GtkTreeViewColumn *column;
132
133         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(ct->listview), x, y,
134                                           &tpath, &column, NULL, NULL))
135                 {
136                 GtkTreeModel *store;
137                 GtkTreeIter row;
138                 GList *list;
139                 gint n;
140
141                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview));
142                 gtk_tree_model_get_iter(store, &row, tpath);
143                 gtk_tree_path_free(tpath);
144
145                 gtk_tree_model_get(store, &row, CTABLE_COLUMN_POINTER, &list, -1);
146
147                 n = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_number"));
148                 if (list)
149                         {
150                         if (iter) *iter = row;
151                         return g_list_nth_data(list, n);
152                         }
153                 }
154
155         return NULL;
156 }
157
158 static void collection_table_update_status(CollectTable *ct)
159 {
160         if (ct->status_label)
161                 {
162                 gchar *buf;
163
164                 if (!ct->cd->list)
165                         {
166                         buf = g_strdup(_("Empty"));
167                         }
168                 else if (ct->selection)
169                         {
170                         buf = g_strdup_printf(_("%d images (%d)"), g_list_length(ct->cd->list), g_list_length(ct->selection));
171                         }
172                 else
173                         {
174                         buf = g_strdup_printf(_("%d images"), g_list_length(ct->cd->list));
175                         }
176
177                 gtk_label_set_text(GTK_LABEL(ct->status_label), buf);
178                 g_free(buf);
179                 }
180 }
181
182 static void collection_table_update_extras(CollectTable *ct, gint loading, gdouble value)
183 {
184         if (ct->extra_label)
185                 {
186                 gchar *text;
187                 if (loading)
188                         text = _("Loading thumbs...");
189                 else
190                         text = " ";
191
192                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ct->extra_label), value);
193                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ct->extra_label), text);
194                 }
195 }
196
197 static void collection_table_toggle_filenames(CollectTable *ct)
198 {
199         ct->show_text = !ct->show_text;
200         options->show_icon_names = ct->show_text;
201
202         collection_table_populate_at_new_size(ct, ct->listview->allocation.width, ct->listview->allocation.height, TRUE);
203 }
204
205 static gint collection_table_get_icon_width(CollectTable *ct)
206 {
207         gint width;
208
209         if (!ct->show_text) return options->thumbnails.max_width;
210
211         width = options->thumbnails.max_width + options->thumbnails.max_width / 2;
212         if (width < THUMB_MIN_ICON_WIDTH) width = THUMB_MIN_ICON_WIDTH;
213         if (width > THUMB_MAX_ICON_WIDTH) width = options->thumbnails.max_width;
214
215         return width;
216 }
217
218 /*
219  *-------------------------------------------------------------------
220  * cell updates
221  *-------------------------------------------------------------------
222  */
223
224 static void collection_table_selection_set(CollectTable *ct, CollectInfo *info, SelectionType value, GtkTreeIter *iter)
225 {
226         GtkTreeModel *store;
227         GList *list;
228
229         if (!info) return;
230
231         if (info->flag_mask == value) return;
232         info->flag_mask = value;
233
234         store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview));
235         if (iter)
236                 {
237                 gtk_tree_model_get(store, iter, CTABLE_COLUMN_POINTER, &list, -1);
238                 if (list) gtk_list_store_set(GTK_LIST_STORE(store), iter, CTABLE_COLUMN_POINTER, list, -1);
239                 }
240         else
241                 {
242                 GtkTreeIter row;
243
244                 if (collection_table_find_iter(ct, info, &row, NULL))
245                         {
246                         gtk_tree_model_get(store, &row, CTABLE_COLUMN_POINTER, &list, -1);
247                         if (list) gtk_list_store_set(GTK_LIST_STORE(store), &row, CTABLE_COLUMN_POINTER, list, -1);
248                         }
249                 }
250 }
251
252 static void collection_table_selection_add(CollectTable *ct, CollectInfo *info, SelectionType mask, GtkTreeIter *iter)
253 {
254         if (!info) return;
255
256         collection_table_selection_set(ct, info, info->flag_mask | mask, iter);
257 }
258
259 static void collection_table_selection_remove(CollectTable *ct, CollectInfo *info, SelectionType mask, GtkTreeIter *iter)
260 {
261         if (!info) return;
262
263         collection_table_selection_set(ct, info, info->flag_mask & ~mask, iter);
264 }
265 /*
266  *-------------------------------------------------------------------
267  * selections
268  *-------------------------------------------------------------------
269  */
270
271 static void collection_table_verify_selections(CollectTable *ct)
272 {
273         GList *work;
274
275         work = ct->selection;
276         while (work)
277                 {
278                 CollectInfo *info = work->data;
279                 work = work->next;
280                 if (!g_list_find(ct->cd->list, info))
281                         {
282                         ct->selection = g_list_remove(ct->selection, info);
283                         }
284                 }
285 }
286
287 void collection_table_select_all(CollectTable *ct)
288 {
289         GList *work;
290
291         g_list_free(ct->selection);
292         ct->selection = NULL;
293
294         work = ct->cd->list;
295         while(work)
296                 {
297                 ct->selection = g_list_append(ct->selection, work->data);
298                 collection_table_selection_add(ct, work->data, SELECTION_SELECTED, NULL);
299                 work = work->next;
300                 }
301
302         collection_table_update_status(ct);
303 }
304
305 void collection_table_unselect_all(CollectTable *ct)
306 {
307         GList *work;
308
309         work = ct->selection;
310         while (work)
311                 {
312                 collection_table_selection_remove(ct, work->data, SELECTION_SELECTED, NULL);
313                 work = work->next;
314                 }
315
316         g_list_free(ct->selection);
317         ct->selection = NULL;
318
319         collection_table_update_status(ct);
320 }
321
322 static void collection_table_select(CollectTable *ct, CollectInfo *info)
323 {
324         ct->prev_selection = info;
325
326         if (!info || INFO_SELECTED(info)) return;
327
328         ct->selection = g_list_append(ct->selection, info);
329         collection_table_selection_add(ct, info, SELECTION_SELECTED, NULL);
330
331         collection_table_update_status(ct);
332 }
333
334 static void collection_table_unselect(CollectTable *ct, CollectInfo *info)
335 {
336         ct->prev_selection = info;
337
338         if (!info || !INFO_SELECTED(info) ) return;
339
340         ct->selection = g_list_remove(ct->selection, info);
341         collection_table_selection_remove(ct, info, SELECTION_SELECTED, NULL);
342
343         collection_table_update_status(ct);
344 }
345
346 static void collection_table_select_util(CollectTable *ct, CollectInfo *info, gint select)
347 {
348         if (select)
349                 {
350                 collection_table_select(ct, info);
351                 }
352         else
353                 {
354                 collection_table_unselect(ct, info);
355                 }
356 }
357
358 static void collection_table_select_region_util(CollectTable *ct, CollectInfo *start, CollectInfo *end, gint select)
359 {
360         gint row1, col1;
361         gint row2, col2;
362         gint t;
363         gint i, j;
364
365         if (!collection_table_find_position(ct, start, &row1, &col1) ||
366             !collection_table_find_position(ct, end, &row2, &col2) ) return;
367
368         ct->prev_selection = end;
369
370         if (!options->collections.rectangular_selection)
371                 {
372                 GList *work;
373                 CollectInfo *info;
374
375                 if (g_list_index(ct->cd->list, start) > g_list_index(ct->cd->list, end))
376                         {
377                         info = start;
378                         start = end;
379                         end = info;
380                         }
381
382                 work = g_list_find(ct->cd->list, start);
383                 while (work)
384                         {
385                         info = work->data;
386                         collection_table_select_util(ct, info, select);
387
388                         if (work->data != end)
389                                 work = work->next;
390                         else
391                                 work = NULL;
392                         }
393                 return;
394                 }
395
396         if (row2 < row1)
397                 {
398                 t = row1;
399                 row1 = row2;
400                 row2 = t;
401                 }
402         if (col2 < col1)
403                 {
404                 t = col1;
405                 col1 = col2;
406                 col2 = t;
407                 }
408
409         DEBUG_1("table: %d x %d to %d x %d\n", row1, col1, row2, col2);
410
411         for (i = row1; i <= row2; i++)
412                 {
413                 for (j = col1; j <= col2; j++)
414                         {
415                         CollectInfo *info = collection_table_find_data(ct, i, j, NULL);
416                         if (info) collection_table_select_util(ct, info, select);
417                         }
418                 }
419 }
420
421 GList *collection_table_selection_get_list(CollectTable *ct)
422 {
423         return collection_list_to_filelist(ct->selection);
424 }
425
426 static GList *collection_table_get_list(CollectTable *ct)
427 {
428         return collection_list_to_filelist(ct->cd->list);
429 }
430
431 /*
432  *-------------------------------------------------------------------
433  * tooltip type window
434  *-------------------------------------------------------------------
435  */
436
437 static void tip_show(CollectTable *ct)
438 {
439         GtkWidget *label;
440         gint x, y;
441
442         if (ct->tip_window) return;
443
444         gdk_window_get_pointer(ct->listview->window, &x, &y, NULL);
445
446         ct->tip_info = collection_table_find_data_by_coord(ct, x, y, NULL);
447         if (!ct->tip_info) return;
448
449         ct->tip_window = gtk_window_new(GTK_WINDOW_POPUP);
450         gtk_window_set_resizable(GTK_WINDOW(ct->tip_window), FALSE);
451         gtk_container_set_border_width(GTK_CONTAINER(ct->tip_window), 2);
452
453         label = gtk_label_new(ct->show_text ? ct->tip_info->fd->path : ct->tip_info->fd->name);
454
455         g_object_set_data(G_OBJECT(ct->tip_window), "tip_label", label);
456         gtk_container_add(GTK_CONTAINER(ct->tip_window), label);
457         gtk_widget_show(label);
458
459         gdk_window_get_pointer(NULL, &x, &y, NULL);
460
461         if (!GTK_WIDGET_REALIZED(ct->tip_window)) gtk_widget_realize(ct->tip_window);
462         gtk_window_move(GTK_WINDOW(ct->tip_window), x + 16, y + 16);
463         gtk_widget_show(ct->tip_window);
464 }
465
466 static void tip_hide(CollectTable *ct)
467 {
468         if (ct->tip_window) gtk_widget_destroy(ct->tip_window);
469         ct->tip_window = NULL;
470 }
471
472 static gint tip_schedule_cb(gpointer data)
473 {
474         CollectTable *ct = data;
475
476         if (ct->tip_delay_id == -1) return FALSE;
477
478         tip_show(ct);
479
480         ct->tip_delay_id = -1;
481         return FALSE;
482 }
483
484 static void tip_schedule(CollectTable *ct)
485 {
486         tip_hide(ct);
487
488         if (ct->tip_delay_id != -1)
489                 {
490                 g_source_remove(ct->tip_delay_id);
491                 ct->tip_delay_id = -1;
492                 }
493
494         ct->tip_delay_id = g_timeout_add(ct->show_text ? COLLECT_TABLE_TIP_DELAY_PATH : COLLECT_TABLE_TIP_DELAY, tip_schedule_cb, ct);
495 }
496
497 static void tip_unschedule(CollectTable *ct)
498 {
499         tip_hide(ct);
500
501         if (ct->tip_delay_id != -1) g_source_remove(ct->tip_delay_id);
502         ct->tip_delay_id = -1;
503 }
504
505 static void tip_update(CollectTable *ct, CollectInfo *info)
506 {
507         tip_schedule(ct);
508
509         if (ct->tip_window)
510                 {
511                 gint x, y;
512
513                 gdk_window_get_pointer(NULL, &x, &y, NULL);
514                 gtk_window_move(GTK_WINDOW(ct->tip_window), x + 16, y + 16);
515
516                 if (info != ct->tip_info)
517                         {
518                         GtkWidget *label;
519
520                         ct->tip_info = info;
521
522                         if (!ct->tip_info)
523                                 {
524                                 return;
525                                 }
526
527                         label = g_object_get_data(G_OBJECT(ct->tip_window), "tip_label");
528                         gtk_label_set_text(GTK_LABEL(label), ct->show_text ? ct->tip_info->fd->path : ct->tip_info->fd->name);
529                         }
530                 }
531 }
532
533 /*
534  *-------------------------------------------------------------------
535  * popup menus
536  *-------------------------------------------------------------------
537  */
538
539 static void collection_table_popup_save_as_cb(GtkWidget *widget, gpointer data)
540 {
541         CollectTable *ct = data;
542
543         collection_dialog_save_as(NULL, ct->cd);
544 }
545
546 static void collection_table_popup_save_cb(GtkWidget *widget, gpointer data)
547 {
548         CollectTable *ct = data;
549
550         if (!ct->cd->path)
551                 {
552                 collection_table_popup_save_as_cb(widget, data);
553                 return;
554                 }
555
556         if (!collection_save(ct->cd, ct->cd->path))
557                 {
558                 printf("failed saving to collection path: %s\n", ct->cd->path);
559                 }
560 }
561
562 static GList *collection_table_popup_file_list(CollectTable *ct)
563 {
564         if (!ct->click_info) return NULL;
565
566         if (INFO_SELECTED(ct->click_info))
567                 {
568                 return collection_table_selection_get_list(ct);
569                 }
570
571         return g_list_append(NULL, g_strdup(ct->click_info->fd->path));
572 }
573
574 static void collection_table_popup_edit_cb(GtkWidget *widget, gpointer data)
575 {
576         CollectTable *ct;
577         gint n;
578         GList *list;
579
580         ct = submenu_item_get_data(widget);
581
582         if (!ct) return;
583         n = GPOINTER_TO_INT(data);
584
585         list = collection_table_popup_file_list(ct);
586         if (list)
587                 {
588                 start_editor_from_filelist(n, list);
589                 filelist_free(list);
590                 }
591 }
592
593 static void collection_table_popup_info_cb(GtkWidget *widget, gpointer data)
594 {
595         CollectTable *ct = data;
596
597         info_window_new(NULL, collection_table_popup_file_list(ct), NULL);
598 }
599
600 static void collection_table_popup_copy_cb(GtkWidget *widget, gpointer data)
601 {
602         CollectTable *ct = data;
603
604         file_util_copy(NULL, collection_table_popup_file_list(ct), NULL, ct->listview);
605 }
606
607 static void collection_table_popup_move_cb(GtkWidget *widget, gpointer data)
608 {
609         CollectTable *ct = data;
610
611         file_util_move(NULL, collection_table_popup_file_list(ct), NULL, ct->listview);
612 }
613
614 static void collection_table_popup_rename_cb(GtkWidget *widget, gpointer data)
615 {
616         CollectTable *ct = data;
617
618         file_util_rename(NULL, collection_table_popup_file_list(ct), ct->listview);
619 }
620
621 static void collection_table_popup_delete_cb(GtkWidget *widget, gpointer data)
622 {
623         CollectTable *ct = data;
624
625         file_util_delete(NULL, collection_table_popup_file_list(ct), ct->listview);
626 }
627
628 static void collection_table_popup_sort_cb(GtkWidget *widget, gpointer data)
629 {
630         CollectTable *ct;
631         SortType type;
632
633         ct = submenu_item_get_data(widget);
634
635         if (!ct) return;
636
637         type = (SortType)GPOINTER_TO_INT(data);
638
639         collection_set_sort_method(ct->cd, type);
640 }
641
642 static void collection_table_popup_view_new_cb(GtkWidget *widget, gpointer data)
643 {
644         CollectTable *ct = data;
645
646         if (ct->click_info && g_list_find(ct->cd->list, ct->click_info))
647                 {
648                 view_window_new_from_collection(ct->cd, ct->click_info);
649                 }
650 }
651
652 static void collection_table_popup_view_cb(GtkWidget *widget, gpointer data)
653 {
654         CollectTable *ct = data;
655
656         if (ct->click_info && g_list_find(ct->cd->list, ct->click_info))
657                 {
658                 layout_image_set_collection(NULL, ct->cd, ct->click_info);
659                 }
660 }
661
662 static void collection_table_popup_selectall_cb(GtkWidget *widget, gpointer data)
663 {
664         CollectTable *ct = data;
665
666         collection_table_select_all(ct);
667         ct->prev_selection= ct->click_info;
668 }
669
670 static void collection_table_popup_unselectall_cb(GtkWidget *widget, gpointer data)
671 {
672         CollectTable *ct = data;
673
674         collection_table_unselect_all(ct);
675         ct->prev_selection= ct->click_info;
676 }
677
678 static void collection_table_popup_remove_cb(GtkWidget *widget, gpointer data)
679 {
680         CollectTable *ct = data;
681         GList *list;
682
683         if (!ct->click_info) return;
684
685         if (INFO_SELECTED(ct->click_info))
686                 {
687                 list = g_list_copy(ct->selection);
688                 }
689         else
690                 {
691                 list = g_list_append(NULL, ct->click_info);
692                 }
693
694         collection_remove_by_info_list(ct->cd, list);
695         g_list_free(list);
696 }
697
698 static void collection_table_popup_add_filelist_cb(GtkWidget *widget, gpointer data)
699 {
700         CollectTable *ct = data;
701         GList *list;
702
703         list = layout_list(NULL);
704
705         if (list)
706                 {
707                 collection_table_add_filelist(ct, list);
708                 filelist_free(list);
709                 }
710 }
711
712 static void collection_table_popup_add_collection_cb(GtkWidget *widget, gpointer data)
713 {
714         CollectTable *ct = data;
715
716         collection_dialog_append(NULL, ct->cd);
717 }
718
719 static void collection_table_popup_find_dupes_cb(GtkWidget *widget, gpointer data)
720 {
721         CollectTable *ct = data;
722         DupeWindow *dw;
723
724         dw = dupe_window_new(DUPE_MATCH_NAME);
725         dupe_window_add_collection(dw, ct->cd);
726 }
727
728 static void collection_table_popup_print_cb(GtkWidget *widget, gpointer data)
729 {
730         CollectTable *ct = data;
731         FileData *fd;
732
733         fd = (ct->click_info) ? ct->click_info->fd : NULL;
734
735         print_window_new(fd, collection_table_selection_get_list(ct), collection_table_get_list(ct), ct->listview);
736 }
737
738 static void collection_table_popup_show_names_cb(GtkWidget *widget, gpointer data)
739 {
740         CollectTable *ct = data;
741
742         collection_table_toggle_filenames(ct);
743 }
744
745 static void collection_table_popup_destroy_cb(GtkWidget *widget, gpointer data)
746 {
747         CollectTable *ct = data;
748
749         collection_table_selection_remove(ct, ct->click_info, SELECTION_PRELIGHT, NULL);
750         ct->click_info = NULL;
751         ct->popup = NULL;
752
753         path_list_free(ct->drop_list);
754         ct->drop_list = NULL;
755         ct->drop_info = NULL;
756 }
757
758 static GtkWidget *collection_table_popup_menu(CollectTable *ct, gint over_icon)
759 {
760         GtkWidget *menu;
761         GtkWidget *item;
762
763         menu = popup_menu_short_lived();
764
765         g_signal_connect(G_OBJECT(menu), "destroy",
766                          G_CALLBACK(collection_table_popup_destroy_cb), ct);
767
768         menu_item_add_sensitive(menu, _("_View"), over_icon,
769                         G_CALLBACK(collection_table_popup_view_cb), ct);
770         menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, over_icon,
771                         G_CALLBACK(collection_table_popup_view_new_cb), ct);
772         menu_item_add_divider(menu);
773         menu_item_add_stock_sensitive(menu, _("Rem_ove"), GTK_STOCK_REMOVE, over_icon,
774                         G_CALLBACK(collection_table_popup_remove_cb), ct);
775
776         menu_item_add_stock(menu, _("Append from file list"), GTK_STOCK_ADD,
777                         G_CALLBACK(collection_table_popup_add_filelist_cb), ct);
778         menu_item_add_stock(menu, _("Append from collection..."), GTK_STOCK_OPEN,
779                         G_CALLBACK(collection_table_popup_add_collection_cb), ct);
780         menu_item_add_divider(menu);
781         menu_item_add(menu, _("Select all"),
782                         G_CALLBACK(collection_table_popup_selectall_cb), ct);
783         menu_item_add(menu, _("Select none"),
784                         G_CALLBACK(collection_table_popup_unselectall_cb), ct);
785         menu_item_add_divider(menu);
786
787         submenu_add_edit(menu, &item,
788                         G_CALLBACK(collection_table_popup_edit_cb), ct);
789         gtk_widget_set_sensitive(item, over_icon);
790
791         menu_item_add_sensitive(menu, _("_Properties"), over_icon,
792                         G_CALLBACK(collection_table_popup_info_cb), ct);
793         menu_item_add_divider(menu);
794         menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, over_icon,
795                         G_CALLBACK(collection_table_popup_copy_cb), ct);
796         menu_item_add_sensitive(menu, _("_Move..."), over_icon,
797                         G_CALLBACK(collection_table_popup_move_cb), ct);
798         menu_item_add_sensitive(menu, _("_Rename..."), over_icon,
799                         G_CALLBACK(collection_table_popup_rename_cb), ct);
800         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, over_icon,
801                         G_CALLBACK(collection_table_popup_delete_cb), ct);
802         menu_item_add_divider(menu);
803
804         submenu_add_sort(menu, G_CALLBACK(collection_table_popup_sort_cb), ct, FALSE, TRUE, FALSE, 0);
805         menu_item_add_check(menu, _("Show filename _text"), ct->show_text,
806                         G_CALLBACK(collection_table_popup_show_names_cb), ct);
807         menu_item_add_divider(menu);
808         menu_item_add_stock(menu, _("_Save collection"), GTK_STOCK_SAVE,
809                         G_CALLBACK(collection_table_popup_save_cb), ct);
810         menu_item_add_stock(menu, _("Save collection _as..."), GTK_STOCK_SAVE_AS,
811                         G_CALLBACK(collection_table_popup_save_as_cb), ct);
812         menu_item_add_divider(menu);
813         menu_item_add_stock(menu, _("_Find duplicates..."), GTK_STOCK_FIND,
814                         G_CALLBACK(collection_table_popup_find_dupes_cb), ct);
815         menu_item_add_stock_sensitive(menu, _("Print..."), GTK_STOCK_PRINT, over_icon,
816                         G_CALLBACK(collection_table_popup_print_cb), ct);
817
818         return menu;
819 }
820 /*
821  *-------------------------------------------------------------------
822  * keyboard callbacks
823  *-------------------------------------------------------------------
824  */
825
826 static void collection_table_set_focus(CollectTable *ct, CollectInfo *info)
827 {
828         GtkTreeIter iter;
829         gint row, col;
830
831         if (g_list_find(ct->cd->list, ct->focus_info))
832                 {
833                 if (info == ct->focus_info)
834                         {
835                         /* ensure focus row col are correct */
836                         collection_table_find_position(ct, ct->focus_info,
837                                                        &ct->focus_row, &ct->focus_column);
838                         return;
839                         }
840                 collection_table_selection_remove(ct, ct->focus_info, SELECTION_FOCUS, NULL);
841                 }
842
843         if (!collection_table_find_position(ct, info, &row, &col))
844                 {
845                 ct->focus_info = NULL;
846                 ct->focus_row = -1;
847                 ct->focus_column = -1;
848                 return;
849                 }
850
851         ct->focus_info = info;
852         ct->focus_row = row;
853         ct->focus_column = col;
854         collection_table_selection_add(ct, ct->focus_info, SELECTION_FOCUS, NULL);
855
856         if (collection_table_find_iter(ct, ct->focus_info, &iter, NULL))
857                 {
858                 GtkTreePath *tpath;
859                 GtkTreeViewColumn *column;
860                 GtkTreeModel *store;
861
862                 tree_view_row_make_visible(GTK_TREE_VIEW(ct->listview), &iter, FALSE);
863
864                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview));
865                 tpath = gtk_tree_model_get_path(store, &iter);
866                 /* focus is set to an extra column with 0 width to hide focus, we draw it ourself */
867                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(ct->listview), COLLECT_TABLE_MAX_COLUMNS);
868                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(ct->listview), tpath, column, FALSE);
869                 gtk_tree_path_free(tpath);
870                 }
871 }
872
873 static void collection_table_move_focus(CollectTable *ct, gint row, gint col, gint relative)
874 {
875         gint new_row;
876         gint new_col;
877
878         if (relative)
879                 {
880                 new_row = ct->focus_row;
881                 new_col = ct->focus_column;
882
883                 new_row += row;
884                 if (new_row < 0) new_row = 0;
885                 if (new_row >= ct->rows) new_row = ct->rows - 1;
886
887                 while(col != 0)
888                         {
889                         if (col < 0)
890                                 {
891                                 new_col--;
892                                 col++;
893                                 }
894                         else
895                                 {
896                                 new_col++;
897                                 col--;
898                                 }
899
900                         if (new_col < 0)
901                                 {
902                                 if (new_row > 0)
903                                         {
904                                         new_row--;
905                                         new_col = ct->columns - 1;
906                                         }
907                                 else
908                                         {
909                                         new_col = 0;
910                                         }
911                                 }
912                         if (new_col >= ct->columns)
913                                 {
914                                 if (new_row < ct->rows - 1)
915                                         {
916                                         new_row++;
917                                         new_col = 0;
918                                         }
919                                 else
920                                         {
921                                         new_col = ct->columns - 1;
922                                         }
923                                 }
924                         }
925                 }
926         else
927                 {
928                 new_row = row;
929                 new_col = col;
930
931                 if (new_row >= ct->rows)
932                         {
933                         if (ct->rows > 0)
934                                 new_row = ct->rows - 1;
935                         else
936                                 new_row = 0;
937                         new_col = ct->columns - 1;
938                         }
939                 if (new_col >= ct->columns) new_col = ct->columns - 1;
940                 }
941
942         if (new_row == ct->rows - 1)
943                 {
944                 gint l;
945
946                 /* if we moved beyond the last image, go to the last image */
947
948                 l = g_list_length(ct->cd->list);
949                 if (ct->rows > 1) l -= (ct->rows - 1) * ct->columns;
950                 if (new_col >= l) new_col = l - 1;
951                 }
952
953         if (new_row == -1 || new_col == -1)
954                 {
955                 if (!ct->cd->list) return;
956                 new_row = new_col = 0;
957                 }
958
959         collection_table_set_focus(ct, collection_table_find_data(ct, new_row, new_col, NULL));
960 }
961
962 static void collection_table_update_focus(CollectTable *ct)
963 {
964         gint new_row = 0;
965         gint new_col = 0;
966
967         if (ct->focus_info && collection_table_find_position(ct, ct->focus_info, &new_row, &new_col))
968                 {
969                 /* first find the old focus, if it exists and is valid */
970                 }
971         else
972                 {
973                 /* (try to) stay where we were */
974                 new_row = ct->focus_row;
975                 new_col = ct->focus_column;
976                 }
977
978         collection_table_move_focus(ct, new_row, new_col, FALSE);
979 }
980
981 /* used to figure the page up/down distances */
982 static gint page_height(CollectTable *ct)
983 {
984         GtkAdjustment *adj;
985         gint page_size;
986         gint row_height;
987         gint ret;
988
989         adj = gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(ct->listview));
990         page_size = (gint)adj->page_increment;
991
992         row_height = options->thumbnails.max_height + THUMB_BORDER_PADDING * 2;
993         if (ct->show_text) row_height += options->thumbnails.max_height / 3;
994
995         ret = page_size / row_height;
996         if (ret < 1) ret = 1;
997
998         return ret;
999 }
1000
1001 static void collection_table_menu_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
1002 {
1003         CollectTable *ct = data;
1004         GtkTreeModel *store;
1005         GtkTreeIter iter;
1006         gint column;
1007         GtkTreePath *tpath;
1008         gint cw, ch;
1009
1010         if (!collection_table_find_iter(ct, ct->click_info, &iter, &column)) return;
1011         store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview));
1012         tpath = gtk_tree_model_get_path(store, &iter);
1013         tree_view_get_cell_clamped(GTK_TREE_VIEW(ct->listview), tpath, column, FALSE, x, y, &cw, &ch);
1014         gtk_tree_path_free(tpath);
1015         *y += ch;
1016         popup_menu_position_clamp(menu, x, y, 0);
1017 }
1018
1019 static gint collection_table_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1020 {
1021         CollectTable *ct = data;
1022         gint focus_row = 0;
1023         gint focus_col = 0;
1024         CollectInfo *info;
1025         gint stop_signal;
1026
1027         stop_signal = TRUE;
1028         switch (event->keyval)
1029                 {
1030                 case GDK_Left: case GDK_KP_Left:
1031                         focus_col = -1;
1032                         break;
1033                 case GDK_Right: case GDK_KP_Right:
1034                         focus_col = 1;
1035                         break;
1036                 case GDK_Up: case GDK_KP_Up:
1037                         focus_row = -1;
1038                         break;
1039                 case GDK_Down: case GDK_KP_Down:
1040                         focus_row = 1;
1041                         break;
1042                 case GDK_Page_Up: case GDK_KP_Page_Up:
1043                         focus_row = -page_height(ct);
1044                         break;
1045                 case GDK_Page_Down: case GDK_KP_Page_Down:
1046                         focus_row = page_height(ct);
1047                         break;
1048                 case GDK_Home: case GDK_KP_Home:
1049                         focus_row = -ct->focus_row;
1050                         focus_col = -ct->focus_column;
1051                         break;
1052                 case GDK_End: case GDK_KP_End:
1053                         focus_row = ct->rows - 1 - ct->focus_row;
1054                         focus_col = ct->columns - 1 - ct->focus_column;
1055                         break;
1056                 case GDK_space:
1057                         info = collection_table_find_data(ct, ct->focus_row, ct->focus_column, NULL);
1058                         if (info)
1059                                 {
1060                                 ct->click_info = info;
1061                                 if (event->state & GDK_CONTROL_MASK)
1062                                         {
1063                                         collection_table_select_util(ct, info, !INFO_SELECTED(info));
1064                                         }
1065                                 else
1066                                         {
1067                                         collection_table_unselect_all(ct);
1068                                         collection_table_select(ct, info);
1069                                         }
1070                                 }
1071                         break;
1072                 case 'T': case 't':
1073                         if (event->state & GDK_CONTROL_MASK) collection_table_toggle_filenames(ct);
1074                         break;
1075                 case GDK_Menu:
1076                 case GDK_F10:
1077                         info = collection_table_find_data(ct, ct->focus_row, ct->focus_column, NULL);
1078                         ct->click_info = info;
1079
1080                         collection_table_selection_add(ct, ct->click_info, SELECTION_PRELIGHT, NULL);
1081                         tip_unschedule(ct);
1082
1083                         ct->popup = collection_table_popup_menu(ct, (info != NULL));
1084                         gtk_menu_popup(GTK_MENU(ct->popup), NULL, NULL, collection_table_menu_pos_cb, ct, 0, GDK_CURRENT_TIME);
1085                         break;
1086                 default:
1087                         stop_signal = FALSE;
1088                         break;
1089                 }
1090
1091         if (focus_row != 0 || focus_col != 0)
1092                 {
1093                 CollectInfo *new_info;
1094                 CollectInfo *old_info;
1095
1096                 old_info = collection_table_find_data(ct, ct->focus_row, ct->focus_column, NULL);
1097                 collection_table_move_focus(ct, focus_row, focus_col, TRUE);
1098                 new_info = collection_table_find_data(ct, ct->focus_row, ct->focus_column, NULL);
1099
1100                 if (new_info != old_info)
1101                         {
1102                         if (event->state & GDK_SHIFT_MASK)
1103                                 {
1104                                 if (!options->collections.rectangular_selection)
1105                                         {
1106                                         collection_table_select_region_util(ct, old_info, new_info, FALSE);
1107                                         }
1108                                 else
1109                                         {
1110                                         collection_table_select_region_util(ct, ct->click_info, old_info, FALSE);
1111                                         }
1112                                 collection_table_select_region_util(ct, ct->click_info, new_info, TRUE);
1113                                 }
1114                         else if (event->state & GDK_CONTROL_MASK)
1115                                 {
1116                                 ct->click_info = new_info;
1117                                 }
1118                         else
1119                                 {
1120                                 ct->click_info = new_info;
1121                                 collection_table_unselect_all(ct);
1122                                 collection_table_select(ct, new_info);
1123                                 }
1124                         }
1125                 }
1126
1127         if (stop_signal)
1128                 {
1129 #if 0
1130                 g_signal_stop_emission_by_name(GTK_OBJECT(widget), "key_press_event");
1131 #endif
1132                 tip_unschedule(ct);
1133                 }
1134
1135         return stop_signal;
1136 }
1137
1138 /*
1139  *-------------------------------------------------------------------
1140  * insert marker
1141  *-------------------------------------------------------------------
1142  */
1143
1144 static CollectInfo *collection_table_insert_find(CollectTable *ct, CollectInfo *source, gint *after, GdkRectangle *cell,
1145                                                  gint use_coord, gint x, gint y)
1146 {
1147         CollectInfo *info = NULL;
1148         GtkTreeModel *store;
1149         GtkTreeIter iter;
1150         GtkTreePath *tpath;
1151         GtkTreeViewColumn *column;
1152
1153         store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview));
1154
1155         if (!use_coord) gdk_window_get_pointer(ct->listview->window, &x, &y, NULL);
1156
1157         if (source)
1158                 {
1159                 gint col;
1160                 if (collection_table_find_iter(ct, source, &iter, &col))
1161                         {
1162                         tpath = gtk_tree_model_get_path(store, &iter);
1163                         column = gtk_tree_view_get_column(GTK_TREE_VIEW(ct->listview), col);
1164                         gtk_tree_view_get_background_area(GTK_TREE_VIEW(ct->listview), tpath, column, cell);
1165                         gtk_tree_path_free(tpath);
1166
1167                         info = source;
1168                         *after = (x > cell->x + (cell->width / 2));
1169                         }
1170                 return info;
1171                 }
1172
1173         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(ct->listview), x, y,
1174                                           &tpath, &column, NULL, NULL))
1175                 {
1176                 GList *list;
1177                 gint n;
1178
1179                 gtk_tree_model_get_iter(store, &iter, tpath);
1180                 gtk_tree_model_get(store, &iter, CTABLE_COLUMN_POINTER, &list, -1);
1181
1182                 n = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_number"));
1183                 info = g_list_nth_data(list, n);
1184
1185                 if (info)
1186                         {
1187                         gtk_tree_view_get_background_area(GTK_TREE_VIEW(ct->listview), tpath, column, cell);
1188                         *after = (x > cell->x + (cell->width / 2));
1189                         }
1190
1191                 gtk_tree_path_free(tpath);
1192                 }
1193
1194         if (info == NULL)
1195                 {
1196                 GList *work;
1197
1198                 work = g_list_last(ct->cd->list);
1199                 if (work)
1200                         {
1201                         gint col;
1202
1203                         info = work->data;
1204                         *after = TRUE;
1205
1206                         if (collection_table_find_iter(ct, info, &iter, &col))
1207                                 {
1208                                 tpath = gtk_tree_model_get_path(store, &iter);
1209                                 column = gtk_tree_view_get_column(GTK_TREE_VIEW(ct->listview), col);
1210                                 gtk_tree_view_get_background_area(GTK_TREE_VIEW(ct->listview), tpath, column, cell);
1211                                 gtk_tree_path_free(tpath);
1212                                 }
1213                         }
1214                 }
1215
1216         return info;
1217 }
1218
1219 static CollectInfo *collection_table_insert_point(CollectTable *ct, gint x, gint y)
1220 {
1221         CollectInfo *info;
1222         GdkRectangle cell;
1223         gint after = FALSE;
1224
1225         info = collection_table_insert_find(ct, NULL, &after, &cell, TRUE, x, y);
1226
1227         if (info && after)
1228                 {
1229                 GList *work;
1230
1231                 work = g_list_find(ct->cd->list, info);
1232                 if (work && work->next)
1233                         {
1234                         info = work->next->data;
1235                         }
1236                 else
1237                         {
1238                         info = NULL;
1239                         }
1240                 }
1241
1242         return info;
1243 }
1244
1245 static void collection_table_insert_marker(CollectTable *ct, CollectInfo *info, gint enable)
1246 {
1247         gint row, col;
1248         gint after = FALSE;
1249         GdkRectangle cell;
1250
1251         if (!enable)
1252                 {
1253                 if (ct->marker_window) gdk_window_destroy(ct->marker_window);
1254                 ct->marker_window = NULL;
1255
1256                 return;
1257                 }
1258
1259         info = collection_table_insert_find(ct, info, &after, &cell, FALSE, 0, 0);
1260
1261         /* this setting does not take into account (after), but since it is not really used... */
1262         ct->marker_info = info;
1263
1264         row = -1;
1265         col = -1;
1266
1267         if (!ct->marker_window)
1268                 {
1269                 GdkWindow *parent;
1270                 GdkWindowAttr attributes;
1271                 gint attributes_mask;
1272                 GdkPixmap *pixmap;
1273                 GdkBitmap *mask;
1274                 GdkPixbuf *pb;
1275                 gint w, h;
1276
1277                 parent = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(ct->listview));
1278
1279                 pb = gdk_pixbuf_new_from_xpm_data((const char **)marker_xpm);
1280                 gdk_pixbuf_render_pixmap_and_mask(pb, &pixmap, &mask, 128);
1281                 gdk_pixbuf_unref(pb);
1282
1283                 gdk_drawable_get_size(pixmap, &w, &h);
1284
1285                 attributes.window_type = GDK_WINDOW_CHILD;
1286                 attributes.wclass = GDK_INPUT_OUTPUT;
1287                 attributes.width = w;
1288                 attributes.height = h;
1289                 attributes.event_mask = gtk_widget_get_events(ct->listview);
1290                 attributes_mask = 0;
1291
1292                 ct->marker_window = gdk_window_new(parent, &attributes, attributes_mask);
1293                 gdk_window_set_back_pixmap(ct->marker_window, pixmap, FALSE);
1294                 gdk_window_shape_combine_mask(ct->marker_window, mask, 0, 0);
1295
1296                 g_object_unref(pixmap);
1297                 if (mask) g_object_unref(mask);
1298                 }
1299
1300         if (info)
1301                 {
1302                 gint x, y;
1303                 gint w, h;
1304
1305                 gdk_drawable_get_size(ct->marker_window, &w, &h);
1306
1307                 if (!after)
1308                         {
1309                         x = cell.x;
1310                         }
1311                 else
1312                         {
1313                         x = cell.x + cell.width;
1314                         }
1315                 x -= (w / 2);
1316                 y = cell.y + (cell.height / 2) - (h / 2);
1317
1318                 gdk_window_move(ct->marker_window, x, y);
1319                 gdk_window_clear(ct->marker_window);
1320                 if (!gdk_window_is_visible(ct->marker_window)) gdk_window_show(ct->marker_window);
1321                 }
1322         else
1323                 {
1324                 if (gdk_window_is_visible(ct->marker_window)) gdk_window_hide(ct->marker_window);
1325                 }
1326 }
1327
1328 /*
1329  *-------------------------------------------------------------------
1330  * mouse drag auto-scroll
1331  *-------------------------------------------------------------------
1332  */
1333
1334 static void collection_table_motion_update(CollectTable *ct, gint x, gint y, gint drop_event)
1335 {
1336         CollectInfo *info;
1337
1338         info = collection_table_find_data_by_coord(ct, x, y, NULL);
1339
1340         if (drop_event)
1341                 {
1342                 tip_unschedule(ct);
1343                 collection_table_insert_marker(ct, info, TRUE);
1344                 }
1345         else
1346                 {
1347                 tip_update(ct, info);
1348                 }
1349 }
1350
1351 static gint collection_table_auto_scroll_idle_cb(gpointer data)
1352 {
1353         CollectTable *ct = data;
1354         GdkWindow *window;
1355         gint x, y;
1356         gint w, h;
1357
1358         if (ct->drop_idle_id == -1) return FALSE;
1359
1360         window = ct->listview->window;
1361         gdk_window_get_pointer(window, &x, &y, NULL);
1362         gdk_drawable_get_size(window, &w, &h);
1363         if (x >= 0 && x < w && y >= 0 && y < h)
1364                 {
1365                 collection_table_motion_update(ct, x, y, TRUE);
1366                 }
1367
1368         ct->drop_idle_id = -1;
1369         return FALSE;
1370 }
1371
1372 static gint collection_table_auto_scroll_notify_cb(GtkWidget *widget, gint x, gint y, gpointer data)
1373 {
1374         CollectTable *ct = data;
1375
1376         if (ct->drop_idle_id == -1) ct->drop_idle_id = g_idle_add(collection_table_auto_scroll_idle_cb, ct);
1377
1378         return TRUE;
1379 }
1380
1381 static void collection_table_scroll(CollectTable *ct, gint scroll)
1382 {
1383         if (!scroll)
1384                 {
1385                 if (ct->drop_idle_id != -1)
1386                         {
1387                         g_source_remove(ct->drop_idle_id);
1388                         ct->drop_idle_id = -1;
1389                         }
1390                 widget_auto_scroll_stop(ct->listview);
1391                 collection_table_insert_marker(ct, NULL, FALSE);
1392                 }
1393         else
1394                 {
1395                 GtkAdjustment *adj = gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(ct->listview));
1396                 widget_auto_scroll_start(ct->listview, adj, -1, options->thumbnails.max_height / 2,
1397                                          collection_table_auto_scroll_notify_cb, ct);
1398                 }
1399 }
1400
1401 /*
1402  *-------------------------------------------------------------------
1403  * mouse callbacks
1404  *-------------------------------------------------------------------
1405  */
1406
1407 static gint collection_table_motion_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1408 {
1409         CollectTable *ct = data;
1410
1411         collection_table_motion_update(ct, (gint)bevent->x, (gint)bevent->y, FALSE);
1412
1413         return FALSE;
1414 }
1415
1416 static gint collection_table_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1417 {
1418         CollectTable *ct = data;
1419         GtkTreeIter iter;
1420         CollectInfo *info;
1421
1422         tip_unschedule(ct);
1423
1424         info = collection_table_find_data_by_coord(ct, (gint)bevent->x, (gint)bevent->y, &iter);
1425
1426         ct->click_info = info;
1427         collection_table_selection_add(ct, ct->click_info, SELECTION_PRELIGHT, &iter);
1428
1429         switch (bevent->button)
1430                 {
1431                 case MOUSE_BUTTON_LEFT:
1432                         if (bevent->type == GDK_2BUTTON_PRESS)
1433                                 {
1434                                 if (info)
1435                                         {
1436                                         layout_image_set_collection(NULL, ct->cd, info);
1437                                         }
1438                                 }
1439                         else if (!GTK_WIDGET_HAS_FOCUS(ct->listview))
1440                                 {
1441                                 gtk_widget_grab_focus(ct->listview);
1442                                 }
1443                         break;
1444                 case MOUSE_BUTTON_RIGHT:
1445                         ct->popup = collection_table_popup_menu(ct, (info != NULL));
1446                         gtk_menu_popup(GTK_MENU(ct->popup), NULL, NULL, NULL, NULL, bevent->button, bevent->time);
1447                         break;
1448                 default:
1449                         break;
1450                 }
1451
1452         return TRUE;
1453 }
1454
1455 static gint collection_table_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1456 {
1457         CollectTable *ct = data;
1458         GtkTreeIter iter;
1459         CollectInfo *info = NULL;
1460
1461         tip_schedule(ct);
1462
1463         if ((gint)bevent->x != 0 || (gint) bevent->y != 0)
1464                 {
1465                 info = collection_table_find_data_by_coord(ct, (gint)bevent->x, (gint)bevent->y, &iter);
1466                 }
1467
1468         if (ct->click_info)
1469                 {
1470                 collection_table_selection_remove(ct, ct->click_info, SELECTION_PRELIGHT, NULL);
1471                 }
1472
1473         if (bevent->button == MOUSE_BUTTON_LEFT &&
1474             info && ct->click_info == info)
1475                 {
1476                 collection_table_set_focus(ct, info);
1477
1478                 if (bevent->state & GDK_CONTROL_MASK)
1479                         {
1480                         gint select;
1481
1482                         select = !INFO_SELECTED(info);
1483                         if ((bevent->state & GDK_SHIFT_MASK) && ct->prev_selection)
1484                                 {
1485                                 collection_table_select_region_util(ct, ct->prev_selection, info, select);
1486                                 }
1487                         else
1488                                 {
1489                                 collection_table_select_util(ct, info, select);
1490                                 }
1491                         }
1492                 else
1493                         {
1494                         collection_table_unselect_all(ct);
1495
1496                         if ((bevent->state & GDK_SHIFT_MASK) &&
1497                             ct->prev_selection)
1498                                 {
1499                                 collection_table_select_region_util(ct, ct->prev_selection, info, TRUE);
1500                                 }
1501                         else
1502                                 {
1503                                 collection_table_select_util(ct, info, TRUE);
1504                                 }
1505                         }
1506                 }
1507         else if (bevent->button == MOUSE_BUTTON_MIDDLE &&
1508                  info && ct->click_info == info)
1509                 {
1510                 collection_table_select_util(ct, info, !INFO_SELECTED(info));
1511                 }
1512
1513         return TRUE;
1514 }
1515
1516 static gint collection_table_leave_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer data)
1517 {
1518         CollectTable *ct = data;
1519
1520         tip_unschedule(ct);
1521         return FALSE;
1522 }
1523
1524 /*
1525  *-------------------------------------------------------------------
1526  * populate, add, insert, etc.
1527  *-------------------------------------------------------------------
1528  */
1529
1530 static gboolean collection_table_destroy_node_cb(GtkTreeModel *store, GtkTreePath *tpath, GtkTreeIter *iter, gpointer data)
1531 {
1532         GList *list;
1533
1534         gtk_tree_model_get(store, iter, CTABLE_COLUMN_POINTER, &list, -1);
1535         g_list_free(list);
1536
1537         return FALSE;
1538 }
1539
1540 static void collection_table_clear_store(CollectTable *ct)
1541 {
1542         GtkTreeModel *store;
1543
1544         store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview));
1545         gtk_tree_model_foreach(store, collection_table_destroy_node_cb, NULL);
1546
1547         gtk_list_store_clear(GTK_LIST_STORE(store));
1548 }
1549
1550 static GList *collection_table_add_row(CollectTable *ct, GtkTreeIter *iter)
1551 {
1552         GtkListStore *store;
1553         GList *list = NULL;
1554         gint i;
1555
1556         for (i = 0; i < ct->columns; i++) list = g_list_prepend(list, NULL);
1557
1558         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview)));
1559         gtk_list_store_append(store, iter);
1560         gtk_list_store_set(store, iter, CTABLE_COLUMN_POINTER, list, -1);
1561
1562         return list;
1563 }
1564
1565 static void collection_table_populate(CollectTable *ct, gint resize)
1566 {
1567         gint row;
1568         GList *work;
1569
1570         collection_table_verify_selections(ct);
1571
1572         collection_table_clear_store(ct);
1573
1574         if (resize)
1575                 {
1576                 gint i;
1577                 gint thumb_width;
1578
1579                 thumb_width = collection_table_get_icon_width(ct);
1580
1581                 for (i = 0; i < COLLECT_TABLE_MAX_COLUMNS; i++)
1582                         {
1583                         GtkTreeViewColumn *column;
1584                         GtkCellRenderer *cell;
1585                         GList *list;
1586
1587                         column = gtk_tree_view_get_column(GTK_TREE_VIEW(ct->listview), i);
1588                         gtk_tree_view_column_set_visible(column, (i < ct->columns));
1589                         gtk_tree_view_column_set_fixed_width(column, thumb_width + (THUMB_BORDER_PADDING * 6));
1590
1591                         list = gtk_tree_view_column_get_cell_renderers(column);
1592                         cell = (list) ? list->data : NULL;
1593                         g_list_free(list);
1594
1595                         if (cell && GQV_IS_CELL_RENDERER_ICON(cell))
1596                                 {
1597                                 g_object_set(G_OBJECT(cell), "fixed_width", thumb_width,
1598                                                              "fixed_height", options->thumbnails.max_height,
1599                                                              "show_text", ct->show_text, NULL);
1600                                 }
1601                         }
1602                 if (GTK_WIDGET_REALIZED(ct->listview)) gtk_tree_view_columns_autosize(GTK_TREE_VIEW(ct->listview));
1603                 }
1604
1605         row = -1;
1606         work = ct->cd->list;
1607         while (work)
1608                 {
1609                 GList *list;
1610                 GtkTreeIter iter;
1611
1612                 row++;
1613
1614                 list = collection_table_add_row(ct, &iter);
1615                 while (work && list)
1616                         {
1617                         list->data = work->data;
1618                         list = list->next;
1619                         work = work->next;
1620                         }
1621                 }
1622
1623         ct->rows = row + 1;
1624
1625         collection_table_update_focus(ct);
1626         collection_table_update_status(ct);
1627 }
1628
1629 static void collection_table_populate_at_new_size(CollectTable *ct, gint w, gint h, gint force)
1630 {
1631         gint new_cols;
1632         gint thumb_width;
1633
1634         thumb_width = collection_table_get_icon_width(ct);
1635
1636         new_cols = w / (thumb_width + (THUMB_BORDER_PADDING * 6));
1637         if (new_cols < 1) new_cols = 1;
1638
1639         if (!force && new_cols == ct->columns) return;
1640
1641         ct->columns = new_cols;
1642
1643         collection_table_populate(ct, TRUE);
1644
1645         DEBUG_1("col tab pop cols=%d rows=%d\n", ct->columns, ct->rows);
1646 }
1647
1648 static void collection_table_sync(CollectTable *ct)
1649 {
1650         GtkTreeModel *store;
1651         GtkTreeIter iter;
1652         GList *work;
1653         gint r, c;
1654
1655         store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview));
1656
1657         r = -1;
1658         c = 0;
1659
1660         work = ct->cd->list;
1661         while (work)
1662                 {
1663                 GList *list;
1664                 r++;
1665                 c = 0;
1666                 if (gtk_tree_model_iter_nth_child(store, &iter, NULL, r))
1667                         {
1668                         gtk_tree_model_get(store, &iter, CTABLE_COLUMN_POINTER, &list, -1);
1669                         gtk_list_store_set(GTK_LIST_STORE(store), &iter, CTABLE_COLUMN_POINTER, list, -1);
1670                         }
1671                 else
1672                         {
1673                         list = collection_table_add_row(ct, &iter);
1674                         }
1675
1676                 while (list)
1677                         {
1678                         CollectInfo *info;
1679                         if (work)
1680                                 {
1681                                 info = work->data;
1682                                 work = work->next;
1683                                 c++;
1684                                 }
1685                         else
1686                                 {
1687                                 info = NULL;
1688                                 }
1689                         if (list)
1690                                 {
1691                                 list->data = info;
1692                                 list = list->next;
1693                                 }
1694                         }
1695                 }
1696
1697         r++;
1698         while (gtk_tree_model_iter_nth_child(store, &iter, NULL, r))
1699                 {
1700                 GList *list;
1701
1702                 gtk_tree_model_get(store, &iter, CTABLE_COLUMN_POINTER, &list, -1);
1703                 gtk_list_store_remove(GTK_LIST_STORE(store), &iter);
1704                 g_list_free(list);
1705                 }
1706
1707         ct->rows = r;
1708
1709         collection_table_update_focus(ct);
1710         collection_table_update_status(ct);
1711 }
1712
1713 static gint collection_table_sync_idle_cb(gpointer data)
1714 {
1715         CollectTable *ct = data;
1716
1717         if (ct->sync_idle_id == -1) return FALSE;
1718         ct->sync_idle_id = -1;
1719
1720         collection_table_sync(ct);
1721         return FALSE;
1722 }
1723
1724 static void collection_table_sync_idle(CollectTable *ct)
1725 {
1726         if (ct->sync_idle_id == -1)
1727                 {
1728                 /* high priority, the view needs to be resynced before a redraw
1729                  * may contain invalid pointers at this time
1730                  */
1731                 ct->sync_idle_id = g_idle_add_full(G_PRIORITY_HIGH, collection_table_sync_idle_cb, ct, NULL);
1732                 }
1733 }
1734
1735 void collection_table_add_filelist(CollectTable *ct, GList *list)
1736 {
1737         GList *work;
1738
1739         if (!list) return;
1740
1741         work = list;
1742         while (work)
1743                 {
1744                 collection_add(ct->cd, (FileData *)work->data, FALSE);
1745                 work = work->next;
1746                 }
1747 }
1748
1749 static void collection_table_insert_filelist(CollectTable *ct, GList *list, CollectInfo *insert_info)
1750 {
1751         GList *work;
1752
1753         if (!list) return;
1754
1755         work = list;
1756         while (work)
1757                 {
1758                 collection_insert(ct->cd, (FileData *)work->data, insert_info, FALSE);
1759                 work = work->next;
1760                 }
1761
1762         collection_table_sync_idle(ct);
1763 }
1764
1765 static void collection_table_move_by_info_list(CollectTable *ct, GList *info_list, gint row, gint col)
1766 {
1767         GList *work;
1768         GList *insert_pos = NULL;
1769         GList *temp;
1770         CollectInfo *info;
1771
1772         if (!info_list) return;
1773
1774         info = collection_table_find_data(ct, row, col, NULL);
1775
1776         if (!info_list->next && info_list->data == info) return;
1777
1778         if (info) insert_pos = g_list_find(ct->cd->list, info);
1779
1780         /* FIXME: this may get slow for large lists */
1781         work = info_list;
1782         while (insert_pos && work)
1783                 {
1784                 if (insert_pos->data == work->data)
1785                         {
1786                         insert_pos = insert_pos->next;
1787                         work = info_list;
1788                         }
1789                 else
1790                         {
1791                         work = work->next;
1792                         }
1793                 }
1794
1795         work = info_list;
1796         while (work)
1797                 {
1798                 ct->cd->list = g_list_remove(ct->cd->list, work->data);
1799                 work = work->next;
1800                 }
1801
1802         /* place them back in */
1803         temp = g_list_copy(info_list);
1804
1805         if (insert_pos)
1806                 {
1807                 ct->cd->list = uig_list_insert_list(ct->cd->list, insert_pos, temp);
1808                 }
1809         else if (info)
1810                 {
1811                 ct->cd->list = g_list_concat(temp, ct->cd->list);
1812                 }
1813         else
1814                 {
1815                 ct->cd->list = g_list_concat(ct->cd->list, temp);
1816                 }
1817
1818         ct->cd->changed = TRUE;
1819
1820         collection_table_sync_idle(ct);
1821 }
1822
1823
1824 /*
1825  *-------------------------------------------------------------------
1826  * updating
1827  *-------------------------------------------------------------------
1828  */
1829
1830 void collection_table_file_update(CollectTable *ct, CollectInfo *info)
1831 {
1832         GtkTreeIter iter;
1833         gint row, col;
1834         gdouble value;
1835
1836         if (!info)
1837                 {
1838                 collection_table_update_extras(ct, FALSE, 0.0);
1839                 return;
1840                 }
1841
1842         if (!collection_table_find_position(ct, info, &row, &col)) return;
1843
1844         if (ct->columns != 0 && ct->rows != 0)
1845                 {
1846                 value = (gdouble)(row * ct->columns + col) / (ct->columns * ct->rows);
1847                 }
1848         else
1849                 {
1850                 value = 0.0;
1851                 }
1852
1853         collection_table_update_extras(ct, TRUE, value);
1854
1855         if (collection_table_find_iter(ct, info, &iter, NULL))
1856                 {
1857                 GtkTreeModel *store;
1858                 GList *list;
1859
1860                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview));
1861                 gtk_tree_model_get(store, &iter, CTABLE_COLUMN_POINTER, &list, -1);
1862                 gtk_list_store_set(GTK_LIST_STORE(store), &iter, CTABLE_COLUMN_POINTER, list, -1);
1863                 }
1864 }
1865
1866 void collection_table_file_add(CollectTable *ct, CollectInfo *info)
1867 {
1868         collection_table_sync_idle(ct);
1869 }
1870
1871 void collection_table_file_insert(CollectTable *ct, CollectInfo *ci)
1872 {
1873         collection_table_sync_idle(ct);
1874 }
1875
1876 void collection_table_file_remove(CollectTable *ct, CollectInfo *ci)
1877 {
1878         if (ci && INFO_SELECTED(ci))
1879                 {
1880                 ct->selection = g_list_remove(ct->selection, ci);
1881                 }
1882
1883         collection_table_sync_idle(ct);
1884 }
1885
1886 void collection_table_refresh(CollectTable *ct)
1887 {
1888         collection_table_populate(ct, FALSE);
1889 }
1890
1891 /*
1892  *-------------------------------------------------------------------
1893  * dnd
1894  *-------------------------------------------------------------------
1895  */
1896
1897 static void collection_table_add_dir_recursive(CollectTable *ct, gchar *path, gint recursive)
1898 {
1899         GList *d = NULL;
1900         GList *f = NULL;
1901
1902         if (filelist_read(path, &f, recursive ? &d : NULL))
1903                 {
1904                 GList *work;
1905
1906                 f = filelist_filter(f, FALSE);
1907                 d = filelist_filter(d, TRUE);
1908
1909                 f = filelist_sort_path(f);
1910                 d = filelist_sort_path(d);
1911
1912                 collection_table_insert_filelist(ct, f, ct->marker_info);
1913
1914                 work = g_list_last(d);
1915                 while (work)
1916                         {
1917                         collection_table_add_dir_recursive(ct, ((FileData *)work->data)->path, TRUE);
1918                         work = work->prev;
1919                         }
1920                 filelist_free(f);
1921                 filelist_free(d);
1922                 }
1923 }
1924
1925 static void confirm_dir_list_do(CollectTable *ct, GList *list, gint recursive)
1926 {
1927         GList *work = list;
1928         while (work)
1929                 {
1930                 FileData *fd = work->data;
1931                 work = work->next;
1932                 if (isdir(fd->path)) collection_table_add_dir_recursive(ct, fd->path, recursive);
1933                 }
1934         collection_table_insert_filelist(ct, list, ct->marker_info);
1935 }
1936
1937
1938 static void confirm_dir_list_add(GtkWidget *widget, gpointer data)
1939 {
1940         CollectTable *ct = data;
1941
1942         confirm_dir_list_do(ct, ct->drop_list, FALSE);
1943 }
1944
1945 static void confirm_dir_list_recurse(GtkWidget *widget, gpointer data)
1946 {
1947         CollectTable *ct = data;
1948
1949         confirm_dir_list_do(ct, ct->drop_list, TRUE);
1950 }
1951
1952 static void confirm_dir_list_skip(GtkWidget *widget, gpointer data)
1953 {
1954         CollectTable *ct = data;
1955
1956         collection_table_insert_filelist(ct, ct->drop_list, ct->marker_info);
1957 }
1958
1959 static GtkWidget *collection_table_drop_menu(CollectTable *ct)
1960 {
1961         GtkWidget *menu;
1962
1963         menu = popup_menu_short_lived();
1964         g_signal_connect(G_OBJECT(menu), "destroy",
1965                          G_CALLBACK(collection_table_popup_destroy_cb), ct);
1966
1967         menu_item_add_stock(menu, _("Dropped list includes folders."), GTK_STOCK_DND_MULTIPLE, NULL, NULL);
1968         menu_item_add_divider(menu);
1969         menu_item_add_stock(menu, _("_Add contents"), GTK_STOCK_OK,
1970                             G_CALLBACK(confirm_dir_list_add), ct);
1971         menu_item_add_stock(menu, _("Add contents _recursive"), GTK_STOCK_ADD,
1972                             G_CALLBACK(confirm_dir_list_recurse), ct);
1973         menu_item_add_stock(menu, _("_Skip folders"), GTK_STOCK_REMOVE,
1974                             G_CALLBACK(confirm_dir_list_skip), ct);
1975         menu_item_add_divider(menu);
1976         menu_item_add_stock(menu, _("Cancel"), GTK_STOCK_CANCEL, NULL, ct);
1977
1978         return menu;
1979 }
1980
1981 /*
1982  *-------------------------------------------------------------------
1983  * dnd
1984  *-------------------------------------------------------------------
1985  */
1986
1987 static GtkTargetEntry collection_drag_types[] = {
1988         { TARGET_APP_COLLECTION_MEMBER_STRING, 0, TARGET_APP_COLLECTION_MEMBER },
1989         { "text/uri-list", 0, TARGET_URI_LIST },
1990         { "text/plain", 0, TARGET_TEXT_PLAIN }
1991 };
1992 static gint n_collection_drag_types = 3;
1993
1994 static GtkTargetEntry collection_drop_types[] = {
1995         { TARGET_APP_COLLECTION_MEMBER_STRING, 0, TARGET_APP_COLLECTION_MEMBER },
1996         { "text/uri-list", 0, TARGET_URI_LIST }
1997 };
1998 static gint n_collection_drop_types = 2;
1999
2000
2001 static void collection_table_dnd_get(GtkWidget *widget, GdkDragContext *context,
2002                                      GtkSelectionData *selection_data, guint info,
2003                                      guint time, gpointer data)
2004 {
2005         CollectTable *ct = data;
2006         gint selected;
2007         GList *list = NULL;
2008         gchar *uri_text = NULL;
2009         gint total;
2010
2011         if (!ct->click_info) return;
2012
2013         selected = INFO_SELECTED(ct->click_info);
2014
2015         switch (info)
2016                 {
2017                 case TARGET_APP_COLLECTION_MEMBER:
2018                         if (selected)
2019                                 {
2020                                 uri_text = collection_info_list_to_dnd_data(ct->cd, ct->selection, &total);
2021                                 }
2022                         else
2023                                 {
2024                                 list = g_list_append(NULL, ct->click_info);
2025                                 uri_text = collection_info_list_to_dnd_data(ct->cd, list, &total);
2026                                 g_list_free(list);
2027                                 }
2028                         break;
2029                 case TARGET_URI_LIST:
2030                 case TARGET_TEXT_PLAIN:
2031                 default:
2032                         if (selected)
2033                                 {
2034                                 list = collection_table_selection_get_list(ct);
2035                                 }
2036                         else
2037                                 {
2038                                 list = g_list_append(NULL, file_data_ref(ct->click_info->fd));
2039                                 }
2040                         if (!list) return;
2041
2042                         uri_text = uri_text_from_filelist(list, &total, (info == TARGET_TEXT_PLAIN));
2043                         filelist_free(list);
2044                         break;
2045                 }
2046
2047         gtk_selection_data_set(selection_data, selection_data->target,
2048                                8, (guchar *)uri_text, total);
2049         g_free(uri_text);
2050 }
2051
2052
2053 static void collection_table_dnd_receive(GtkWidget *widget, GdkDragContext *context,
2054                                           gint x, gint y,
2055                                           GtkSelectionData *selection_data, guint info,
2056                                           guint time, gpointer data)
2057 {
2058         CollectTable *ct = data;
2059         GList *list = NULL;
2060         GList *info_list = NULL;
2061         CollectionData *source;
2062         CollectInfo *drop_info;
2063         GList *work;
2064
2065         DEBUG_1("%s\n", selection_data->data);
2066
2067         collection_table_scroll(ct, FALSE);
2068         collection_table_insert_marker(ct, NULL, FALSE);
2069
2070         drop_info = collection_table_insert_point(ct, x, y);
2071
2072         switch (info)
2073                 {
2074                 case TARGET_APP_COLLECTION_MEMBER:
2075                         source = collection_from_dnd_data((gchar *)selection_data->data, &list, &info_list);
2076                         if (source)
2077                                 {
2078                                 if (source == ct->cd)
2079                                         {
2080                                         gint row = -1;
2081                                         gint col = -1;
2082
2083                                         /* it is a move within a collection */
2084                                         filelist_free(list);
2085                                         list = NULL;
2086
2087                                         if (!drop_info)
2088                                                 {
2089                                                 collection_table_move_by_info_list(ct, info_list, -1, -1);
2090                                                 }
2091                                         else if (collection_table_find_position(ct, drop_info, &row, &col))
2092                                                 {
2093                                                 collection_table_move_by_info_list(ct, info_list, row, col);
2094                                                 }
2095                                         }
2096                                 else
2097                                         {
2098                                         /* it is a move/copy across collections */
2099                                         if (context->action == GDK_ACTION_MOVE)
2100                                                 {
2101                                                 collection_remove_by_info_list(source, info_list);
2102                                                 }
2103                                         }
2104                                 g_list_free(info_list);
2105                                 }
2106                         break;
2107                 case TARGET_URI_LIST:
2108                         list = uri_filelist_from_text((gchar *)selection_data->data, TRUE);
2109                         work = list;
2110                         while (work)
2111                                 {
2112                                 FileData *fd = work->data;
2113                                 if (isdir(fd->path))
2114                                         {
2115                                         GtkWidget *menu;
2116
2117                                         ct->drop_list = list;
2118                                         ct->drop_info = drop_info;
2119                                         menu = collection_table_drop_menu(ct);
2120                                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, time);
2121                                         return;
2122                                         }
2123                                 work = work->next;
2124                                 }
2125                         break;
2126                 default:
2127                         list = NULL;
2128                         break;
2129                 }
2130
2131         if (list)
2132                 {
2133                 collection_table_insert_filelist(ct, list, drop_info);
2134                 filelist_free(list);
2135                 }
2136 }
2137
2138 static void collection_table_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
2139 {
2140         CollectTable *ct = data;
2141
2142         if (ct->click_info && ct->click_info->pixbuf)
2143                 {
2144                 gint items;
2145
2146                 if (INFO_SELECTED(ct->click_info))
2147                         items = g_list_length(ct->selection);
2148                 else
2149                         items = 1;
2150                 dnd_set_drag_icon(widget, context, ct->click_info->pixbuf, items);
2151                 }
2152 }
2153
2154 static void collection_table_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
2155 {
2156         CollectTable *ct = data;
2157
2158         /* apparently a leave event is not generated on a drop */
2159         tip_unschedule(ct);
2160
2161         collection_table_scroll(ct, FALSE);
2162 }
2163
2164 static gint collection_table_dnd_motion(GtkWidget *widget, GdkDragContext *context,
2165                                         gint x, gint y, guint time, gpointer data)
2166 {
2167         CollectTable *ct = data;
2168
2169         collection_table_motion_update(ct, x, y, TRUE);
2170         collection_table_scroll(ct, TRUE);
2171
2172         return FALSE;
2173 }
2174
2175 static void collection_table_dnd_leave(GtkWidget *widget, GdkDragContext *context, guint time, gpointer data)
2176 {
2177         CollectTable *ct = data;
2178
2179         collection_table_scroll(ct, FALSE);
2180 }
2181
2182 static void collection_table_dnd_init(CollectTable *ct)
2183 {
2184         gtk_drag_source_set(ct->listview, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
2185                             collection_drag_types, n_collection_drag_types,
2186                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
2187         g_signal_connect(G_OBJECT(ct->listview), "drag_data_get",
2188                          G_CALLBACK(collection_table_dnd_get), ct);
2189         g_signal_connect(G_OBJECT(ct->listview), "drag_begin",
2190                          G_CALLBACK(collection_table_dnd_begin), ct);
2191         g_signal_connect(G_OBJECT(ct->listview), "drag_end",
2192                          G_CALLBACK(collection_table_dnd_end), ct);
2193
2194         gtk_drag_dest_set(ct->listview,
2195                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
2196                           collection_drop_types, n_collection_drop_types,
2197                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK);
2198         g_signal_connect(G_OBJECT(ct->listview), "drag_motion",
2199                          G_CALLBACK(collection_table_dnd_motion), ct);
2200         g_signal_connect(G_OBJECT(ct->listview), "drag_leave",
2201                          G_CALLBACK(collection_table_dnd_leave), ct);
2202         g_signal_connect(G_OBJECT(ct->listview), "drag_data_received",
2203                          G_CALLBACK(collection_table_dnd_receive), ct);
2204 }
2205
2206 /*
2207  *-----------------------------------------------------------------------------
2208  * draw, etc.
2209  *-----------------------------------------------------------------------------
2210  */
2211
2212 typedef struct _ColumnData ColumnData;
2213 struct _ColumnData
2214 {
2215         CollectTable *ct;
2216         gint number;
2217 };
2218
2219 static void collection_table_cell_data_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
2220                                           GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
2221 {
2222         ColumnData *cd = data;
2223         CollectTable *ct;
2224         GtkStyle *style;
2225         GList *list;
2226         CollectInfo *info;
2227         GdkColor color_fg;
2228         GdkColor color_bg;
2229
2230         ct = cd->ct;
2231
2232         gtk_tree_model_get(tree_model, iter, CTABLE_COLUMN_POINTER, &list, -1);
2233         info = g_list_nth_data(list, cd->number);
2234
2235         style = gtk_widget_get_style(ct->listview);
2236         if (info && (info->flag_mask & SELECTION_SELECTED) )
2237                 {
2238                 memcpy(&color_fg, &style->text[GTK_STATE_SELECTED], sizeof(color_fg));
2239                 memcpy(&color_bg, &style->base[GTK_STATE_SELECTED], sizeof(color_bg));
2240                 }
2241         else
2242                 {
2243                 memcpy(&color_fg, &style->text[GTK_STATE_NORMAL], sizeof(color_fg));
2244                 memcpy(&color_bg, &style->base[GTK_STATE_NORMAL], sizeof(color_bg));
2245                 }
2246
2247         if (info && (info->flag_mask & SELECTION_PRELIGHT))
2248                 {
2249 #if 0
2250                 shift_color(&color_fg, -1, 0);
2251 #endif
2252                 shift_color(&color_bg, -1, 0);
2253                 }
2254
2255         if (GQV_IS_CELL_RENDERER_ICON(cell))
2256                 {
2257                 if (info)
2258                         {
2259                         g_object_set(cell,      "pixbuf", info->pixbuf,
2260                                                 "text", info->fd->name,
2261                                                 "cell-background-gdk", &color_bg,
2262                                                 "cell-background-set", TRUE,
2263                                                 "foreground-gdk", &color_fg,
2264                                                 "foreground-set", TRUE,
2265                                                 "has-focus", (ct->focus_info == info), NULL);
2266                         }
2267                 else
2268                         {
2269                         g_object_set(cell,      "pixbuf", NULL,
2270                                                 "text", NULL,
2271                                                 "cell-background-set", FALSE,
2272                                                 "foreground-set", FALSE,
2273                                                 "has-focus", FALSE,  NULL);
2274                         }
2275                 }
2276 }
2277
2278 static void collection_table_append_column(CollectTable *ct, gint n)
2279 {
2280         ColumnData *cd;
2281         GtkTreeViewColumn *column;
2282         GtkCellRenderer *renderer;
2283
2284         column = gtk_tree_view_column_new();
2285         gtk_tree_view_column_set_min_width(column, 0);
2286
2287         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2288         gtk_tree_view_column_set_alignment(column, 0.5);
2289
2290         renderer = gqv_cell_renderer_icon_new();
2291         gtk_tree_view_column_pack_start(column, renderer, FALSE);
2292         g_object_set(G_OBJECT(renderer), "xpad", THUMB_BORDER_PADDING * 2,
2293                                          "ypad", THUMB_BORDER_PADDING,
2294                                          "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
2295
2296         g_object_set_data(G_OBJECT(column), "column_number", GINT_TO_POINTER(n));
2297
2298         cd = g_new0(ColumnData, 1);
2299         cd->ct = ct;
2300         cd->number = n;
2301         gtk_tree_view_column_set_cell_data_func(column, renderer, collection_table_cell_data_cb, cd, g_free);
2302
2303         gtk_tree_view_append_column(GTK_TREE_VIEW(ct->listview), column);
2304 }
2305
2306 /*
2307  *-------------------------------------------------------------------
2308  * init, destruction
2309  *-------------------------------------------------------------------
2310  */
2311
2312 static void collection_table_destroy(GtkWidget *widget, gpointer data)
2313 {
2314         CollectTable *ct = data;
2315
2316         if (ct->popup)
2317                 {
2318                 g_signal_handlers_disconnect_matched(GTK_OBJECT(ct->popup), G_SIGNAL_MATCH_DATA,
2319                                                      0, 0, 0, NULL, ct);
2320                 gtk_widget_destroy(ct->popup);
2321                 }
2322
2323         if (ct->sync_idle_id != -1) g_source_remove(ct->sync_idle_id);
2324
2325         tip_unschedule(ct);
2326         collection_table_scroll(ct, FALSE);
2327
2328         g_free(ct);
2329 }
2330
2331 static void collection_table_sized(GtkWidget *widget, GtkAllocation *allocation, gpointer data)
2332 {
2333         CollectTable *ct = data;
2334
2335         collection_table_populate_at_new_size(ct, allocation->width, allocation->height, FALSE);
2336 }
2337
2338 CollectTable *collection_table_new(CollectionData *cd)
2339 {
2340         CollectTable *ct;
2341         GtkListStore *store;
2342         GtkTreeSelection *selection;
2343         gint i;
2344
2345         ct = g_new0(CollectTable, 1);
2346         ct->cd = cd;
2347         ct->columns = 0;
2348         ct->rows = 0;
2349
2350         ct->selection = NULL;
2351         ct->prev_selection = NULL;
2352
2353         ct->tip_window = NULL;
2354         ct->tip_delay_id = -1;
2355
2356         ct->marker_window = NULL;
2357         ct->marker_info = NULL;
2358
2359         ct->status_label = NULL;
2360         ct->extra_label = NULL;
2361
2362         ct->focus_row = 0;
2363         ct->focus_column = 0;
2364         ct->focus_info = NULL;
2365
2366         ct->show_text = options->show_icon_names;
2367
2368         ct->sync_idle_id = -1;
2369         ct->drop_idle_id = -1;
2370
2371         ct->popup = NULL;
2372         ct->drop_info = NULL;
2373         ct->drop_list = NULL;
2374
2375         ct->scrolled = gtk_scrolled_window_new(NULL, NULL);
2376         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(ct->scrolled), GTK_SHADOW_IN);
2377         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (ct->scrolled),
2378                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2379
2380         store = gtk_list_store_new(1, G_TYPE_POINTER);
2381         ct->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
2382         g_object_unref(store);
2383
2384         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ct->listview));
2385         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_NONE);
2386
2387         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(ct->listview), FALSE);
2388         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(ct->listview), FALSE);
2389
2390         for (i = 0; i < COLLECT_TABLE_MAX_COLUMNS; i++)
2391                 {
2392                 collection_table_append_column(ct, i);
2393                 }
2394
2395         /* zero width column to hide tree view focus, we draw it ourselves */
2396         collection_table_append_column(ct, i);
2397         /* end column to fill white space */
2398         collection_table_append_column(ct, i);
2399
2400         g_signal_connect(G_OBJECT(ct->listview), "destroy",
2401                          G_CALLBACK(collection_table_destroy), ct);
2402         g_signal_connect(G_OBJECT(ct->listview), "size_allocate",
2403                          G_CALLBACK(collection_table_sized), ct);
2404         g_signal_connect(G_OBJECT(ct->listview), "key_press_event",
2405                          G_CALLBACK(collection_table_press_key_cb), ct);
2406
2407         gtk_container_add(GTK_CONTAINER(ct->scrolled), ct->listview);
2408         gtk_widget_show(ct->listview);
2409
2410         collection_table_dnd_init(ct);
2411
2412         gtk_widget_set_events(ct->listview, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK |
2413                               GDK_BUTTON_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK);
2414         g_signal_connect(G_OBJECT(ct->listview),"button_press_event",
2415                          G_CALLBACK(collection_table_press_cb), ct);
2416         g_signal_connect(G_OBJECT(ct->listview),"button_release_event",
2417                          G_CALLBACK(collection_table_release_cb), ct);
2418         g_signal_connect(G_OBJECT(ct->listview),"motion_notify_event",
2419                          G_CALLBACK(collection_table_motion_cb), ct);
2420         g_signal_connect(G_OBJECT(ct->listview), "leave_notify_event",
2421                          G_CALLBACK(collection_table_leave_cb), ct);
2422
2423         return ct;
2424 }
2425
2426 void collection_table_set_labels(CollectTable *ct, GtkWidget *status, GtkWidget *extra)
2427 {
2428         ct->status_label = status;
2429         ct->extra_label = extra;
2430         collection_table_update_status(ct);
2431         collection_table_update_extras(ct, FALSE, 0.0);
2432 }
2433
2434 CollectInfo *collection_table_get_focus_info(CollectTable *ct)
2435 {
2436         return collection_table_find_data(ct, ct->focus_row, ct->focus_column, NULL);
2437 }