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