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