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