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