Merge commit 'refs/merge-requests/1' of git://gitorious.org/geeqie/geeqie into merge...
[geeqie.git] / src / dupe.c
1 /*
2  * Geeqie
3  * (C) 2005 John Ellis
4  * Copyright (C) 2008 - 2010 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "dupe.h"
16
17 #include "cache.h"
18 #include "collect.h"
19 #include "collect-table.h"
20 #include "dnd.h"
21 #include "editors.h"
22 #include "filedata.h"
23 #include "image-load.h"
24 #include "img-view.h"
25 #include "layout.h"
26 #include "layout_image.h"
27 #include "md5-util.h"
28 #include "menu.h"
29 #include "misc.h"
30 #include "print.h"
31 #include "thumb.h"
32 #include "ui_fileops.h"
33 #include "ui_menu.h"
34 #include "ui_misc.h"
35 #include "ui_tree_edit.h"
36 #include "uri_utils.h"
37 #include "utilops.h"
38 #include "window.h"
39
40 #include <gdk/gdkkeysyms.h> /* for keyboard values */
41
42
43 #include <math.h>
44
45
46 #define DUPE_DEF_WIDTH 800
47 #define DUPE_DEF_HEIGHT 400
48
49 /* column assignment order (simply change them here) */
50 enum {
51         DUPE_COLUMN_POINTER = 0,
52         DUPE_COLUMN_RANK,
53         DUPE_COLUMN_THUMB,
54         DUPE_COLUMN_NAME,
55         DUPE_COLUMN_SIZE,
56         DUPE_COLUMN_DATE,
57         DUPE_COLUMN_DIMENSIONS,
58         DUPE_COLUMN_PATH,
59         DUPE_COLUMN_COLOR,
60         DUPE_COLUMN_COUNT       /* total columns */
61 };
62
63
64 static GList *dupe_window_list = NULL;  /* list of open DupeWindow *s */
65
66 /*
67  * Well, after adding the 'compare two sets' option things got a little sloppy in here
68  * because we have to account for two 'modes' everywhere. (be careful).
69  */
70
71 static void dupe_match_unlink(DupeItem *a, DupeItem *b);
72 static DupeItem *dupe_match_find_parent(DupeWindow *dw, DupeItem *child);
73
74 static gint dupe_match(DupeItem *a, DupeItem *b, DupeMatchType mask, gdouble *rank, gint fast);
75
76 static void dupe_thumb_step(DupeWindow *dw);
77 static gint dupe_check_cb(gpointer data);
78
79 static void dupe_second_add(DupeWindow *dw, DupeItem *di);
80 static void dupe_second_remove(DupeWindow *dw, DupeItem *di);
81 static GtkWidget *dupe_menu_popup_second(DupeWindow *dw, DupeItem *di);
82
83 static void dupe_dnd_init(DupeWindow *dw);
84
85 static void dupe_notify_cb(FileData *fd, NotifyType type, gpointer data);
86
87 /*
88  * ------------------------------------------------------------------
89  * Window updates
90  * ------------------------------------------------------------------
91  */
92
93
94 static void dupe_window_update_count(DupeWindow *dw, gboolean count_only)
95 {
96         gchar *text;
97
98         if (!dw->list)
99                 {
100                 text = g_strdup(_("Drop files to compare them."));
101                 }
102         else if (count_only)
103                 {
104                 text = g_strdup_printf(_("%d files"), g_list_length(dw->list));
105                 }
106         else
107                 {
108                 text = g_strdup_printf(_("%d matches found in %d files"), g_list_length(dw->dupes), g_list_length(dw->list));
109                 }
110
111         if (dw->second_set)
112                 {
113                 gchar *buf = g_strconcat(text, " ", _("[set 1]"), NULL);
114                 g_free(text);
115                 text = buf;
116                 }
117         gtk_label_set_text(GTK_LABEL(dw->status_label), text);
118
119         g_free(text);
120 }
121
122 static guint64 msec_time(void)
123 {
124         struct timeval tv;
125
126         if (gettimeofday(&tv, NULL) == -1) return 0;
127
128         return (guint64)tv.tv_sec * 1000000 + (guint64)tv.tv_usec;
129 }
130
131 static gint dupe_iterations(gint n)
132 {
133         return (n * ((n + 1) / 2));
134 }
135
136 static void dupe_window_update_progress(DupeWindow *dw, const gchar *status, gdouble value, gboolean force)
137 {
138         const gchar *status_text;
139
140         if (status)
141                 {
142                 guint64 new_time = 0;
143
144                 if (dw->setup_n % 10 == 0)
145                         {
146                         new_time = msec_time() - dw->setup_time;
147                         }
148
149                 if (!force &&
150                     value != 0.0 &&
151                     dw->setup_count > 0 &&
152                     new_time > 2000000)
153                         {
154                         gchar *buf;
155                         gint t;
156                         gint d;
157                         guint32 rem;
158
159                         if (new_time - dw->setup_time_count < 250000) return;
160                         dw->setup_time_count = new_time;
161
162                         if (dw->setup_done)
163                                 {
164                                 if (dw->second_set)
165                                         {
166                                         t = dw->setup_count;
167                                         d = dw->setup_count - dw->setup_n;
168                                         }
169                                 else
170                                         {
171                                         t = dupe_iterations(dw->setup_count);
172                                         d = dupe_iterations(dw->setup_count - dw->setup_n);
173                                         }
174                                 }
175                         else
176                                 {
177                                 t = dw->setup_count;
178                                 d = dw->setup_count - dw->setup_n;
179                                 }
180
181                         rem = (t - d) ? ((gdouble)(dw->setup_time_count / 1000000) / (t - d)) * d : 0;
182
183                         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dw->extra_label), value);
184
185                         buf = g_strdup_printf("%s %d:%02d ", status, rem / 60, rem % 60);
186                         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(dw->extra_label), buf);
187                         g_free(buf);
188
189                         return;
190                         }
191                 else if (force ||
192                          value == 0.0 ||
193                          dw->setup_count == 0 ||
194                          dw->setup_time_count == 0 ||
195                          (new_time > 0 && new_time - dw->setup_time_count >= 250000))
196                         {
197                         if (dw->setup_time_count == 0) dw->setup_time_count = 1;
198                         if (new_time > 0) dw->setup_time_count = new_time;
199                         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dw->extra_label), value);
200                         status_text = status;
201                         }
202                 else
203                         {
204                         status_text = NULL;
205                         }
206                 }
207         else
208                 {
209                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dw->extra_label), 0.0);
210                 status_text = " ";
211                 }
212
213         if (status_text) gtk_progress_bar_set_text(GTK_PROGRESS_BAR(dw->extra_label), status_text);
214 }
215
216 static void widget_set_cursor(GtkWidget *widget, gint icon)
217 {
218         GdkCursor *cursor;
219
220         if (!widget->window) return;
221
222         if (icon == -1)
223                 {
224                 cursor = NULL;
225                 }
226         else
227                 {
228                 cursor = gdk_cursor_new(icon);
229                 }
230
231         gdk_window_set_cursor(widget->window, cursor);
232
233         if (cursor) gdk_cursor_unref(cursor);
234 }
235
236 /*
237  * ------------------------------------------------------------------
238  * row color utils
239  * ------------------------------------------------------------------
240  */
241
242 static void dupe_listview_realign_colors(DupeWindow *dw)
243 {
244         GtkTreeModel *store;
245         GtkTreeIter iter;
246         gboolean color_set = TRUE;
247         DupeItem *parent = NULL;
248         gboolean valid;
249
250         store = gtk_tree_view_get_model(GTK_TREE_VIEW(dw->listview));
251         valid = gtk_tree_model_get_iter_first(store, &iter);
252         while (valid)
253                 {
254                 DupeItem *child;
255                 DupeItem *child_parent;
256
257                 gtk_tree_model_get(store, &iter, DUPE_COLUMN_POINTER, &child, -1);
258                 child_parent = dupe_match_find_parent(dw, child);
259                 if (!parent || parent != child_parent)
260                         {
261                         if (!parent)
262                                 {
263                                 /* keep the first row as it is */
264                                 gtk_tree_model_get(store, &iter, DUPE_COLUMN_COLOR, &color_set, -1);
265                                 }
266                         else
267                                 {
268                                 color_set = !color_set;
269                                 }
270                         parent = dupe_match_find_parent(dw, child);
271                         }
272                 gtk_list_store_set(GTK_LIST_STORE(store), &iter, DUPE_COLUMN_COLOR, color_set, -1);
273
274                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
275                 }
276 }
277
278 /*
279  * ------------------------------------------------------------------
280  * Dupe item utils
281  * ------------------------------------------------------------------
282  */
283
284 static DupeItem *dupe_item_new(FileData *fd)
285 {
286         DupeItem *di;
287
288         di = g_new0(DupeItem, 1);
289
290         di->fd = file_data_ref(fd);
291         di->group_rank = 0.0;
292
293         return di;
294 }
295
296 static void dupe_item_free(DupeItem *di)
297 {
298         file_data_unref(di->fd);
299         image_sim_free(di->simd);
300         g_free(di->md5sum);
301         if (di->pixbuf) g_object_unref(di->pixbuf);
302
303         g_free(di);
304 }
305
306 static void dupe_list_free(GList *list)
307 {
308         GList *work = list;
309         while (work)
310                 {
311                 DupeItem *di = work->data;
312                 work = work->next;
313                 dupe_item_free(di);
314                 }
315         g_list_free(list);
316 }
317
318 /*
319 static DupeItem *dupe_item_find_fd_by_list(FileData *fd, GList *work)
320 {
321         while (work)
322                 {
323                 DupeItem *di = work->data;
324
325                 if (di->fd == fd) return di;
326
327                 work = work->next;
328                 }
329
330         return NULL;
331 }
332 */
333
334 /*
335 static DupeItem *dupe_item_find_fd(DupeWindow *dw, FileData *fd)
336 {
337         DupeItem *di;
338
339         di = dupe_item_find_fd_by_list(fd, dw->list);
340         if (!di && dw->second_set) di = dupe_item_find_fd_by_list(fd, dw->second_list);
341
342         return di;
343 }
344 */
345
346 static DupeItem *dupe_item_find_path_by_list(const gchar *path, GList *work)
347 {
348         while (work)
349                 {
350                 DupeItem *di = work->data;
351
352                 if (strcmp(di->fd->path, path) == 0) return di;
353
354                 work = work->next;
355                 }
356
357         return NULL;
358 }
359
360 static DupeItem *dupe_item_find_path(DupeWindow *dw, const gchar *path)
361 {
362         DupeItem *di;
363
364         di = dupe_item_find_path_by_list(path, dw->list);
365         if (!di && dw->second_set) di = dupe_item_find_path_by_list(path, dw->second_list);
366
367         return di;
368 }
369
370 /*
371  * ------------------------------------------------------------------
372  * Image property cache
373  * ------------------------------------------------------------------
374  */
375
376 static void dupe_item_read_cache(DupeItem *di)
377 {
378         gchar *path;
379         CacheData *cd;
380
381         if (!di) return;
382
383         path = cache_find_location(CACHE_TYPE_SIM, di->fd->path);
384         if (!path) return;
385
386         if (filetime(di->fd->path) != filetime(path))
387                 {
388                 g_free(path);
389                 return;
390                 }
391
392         cd = cache_sim_data_load(path);
393         g_free(path);
394
395         if (cd)
396                 {
397                 if (!di->simd && cd->sim)
398                         {
399                         di->simd = cd->sim;
400                         cd->sim = NULL;
401                         }
402                 if (di->width == 0 && di->height == 0 && cd->dimensions)
403                         {
404                         di->width = cd->width;
405                         di->height = cd->height;
406                         }
407                 if (di->checksum == 0 && cd->have_checksum)
408                         {
409                         di->checksum = cd->checksum;
410                         }
411                 if (!di->md5sum && cd->have_md5sum)
412                         {
413                         di->md5sum = md5_digest_to_text(cd->md5sum);
414                         }
415                 cache_sim_data_free(cd);
416                 }
417 }
418
419 static void dupe_item_write_cache(DupeItem *di)
420 {
421         gchar *base;
422         mode_t mode = 0755;
423
424         if (!di) return;
425
426         base = cache_get_location(CACHE_TYPE_SIM, di->fd->path, FALSE, &mode);
427         if (recursive_mkdir_if_not_exists(base, mode))
428                 {
429                 CacheData *cd;
430
431                 cd = cache_sim_data_new();
432                 cd->path = cache_get_location(CACHE_TYPE_SIM, di->fd->path, TRUE, NULL);
433
434                 if (di->width != 0) cache_sim_data_set_dimensions(cd, di->width, di->height);
435                 if (di->checksum != 0) cache_sim_data_set_checksum(cd, di->checksum);
436                 if (di->md5sum)
437                         {
438                         guchar digest[16];
439                         if (md5_digest_from_text(di->md5sum, digest)) cache_sim_data_set_md5sum(cd, digest);
440                         }
441                 if (di->simd) cache_sim_data_set_similarity(cd, di->simd);
442
443                 if (cache_sim_data_save(cd))
444                         {
445                         filetime_set(cd->path, filetime(di->fd->path));
446                         }
447                 cache_sim_data_free(cd);
448                 }
449         g_free(base);
450 }
451
452 /*
453  * ------------------------------------------------------------------
454  * Window list utils
455  * ------------------------------------------------------------------
456  */
457
458 static gint dupe_listview_find_item(GtkListStore *store, DupeItem *item, GtkTreeIter *iter)
459 {
460         gboolean valid;
461         gint row = 0;
462
463         valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), iter);
464         while (valid)
465                 {
466                 DupeItem *item_n;
467                 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, DUPE_COLUMN_POINTER, &item_n, -1);
468                 if (item_n == item) return row;
469
470                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), iter);
471                 row++;
472                 }
473
474         return -1;
475 }
476
477 static void dupe_listview_add(DupeWindow *dw, DupeItem *parent, DupeItem *child)
478 {
479         DupeItem *di;
480         gint row;
481         gchar *text[DUPE_COLUMN_COUNT];
482         GtkListStore *store;
483         GtkTreeIter iter;
484         gboolean color_set = FALSE;
485         gint rank;
486
487         if (!parent) return;
488
489         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(dw->listview)));
490
491         if (child)
492                 {
493                 DupeMatch *dm;
494
495                 row = dupe_listview_find_item(store, parent, &iter);
496                 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, DUPE_COLUMN_COLOR, &color_set, -1);
497
498                 row++;
499
500                 if (child->group)
501                         {
502                         dm = child->group->data;
503                         rank = (gint)floor(dm->rank);
504                         }
505                 else
506                         {
507                         rank = 1;
508                         log_printf("NULL group in item!\n");
509                         }
510                 }
511         else
512                 {
513                 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter))
514                         {
515                         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, DUPE_COLUMN_COLOR, &color_set, -1);
516                         color_set = !color_set;
517                         }
518                 else
519                         {
520                         color_set = FALSE;
521                         }
522                 row = 0;
523                 rank = 0;
524                 }
525
526         di = (child) ? child : parent;
527
528         if (!child && dw->second_set)
529                 {
530                 text[DUPE_COLUMN_RANK] = g_strdup("[1]");
531                 }
532         else if (rank == 0)
533                 {
534                 text[DUPE_COLUMN_RANK] = g_strdup((di->second) ? "(2)" : "");
535                 }
536         else
537                 {
538                 text[DUPE_COLUMN_RANK] = g_strdup_printf("%d%s", rank, (di->second) ? " (2)" : "");
539                 }
540
541         text[DUPE_COLUMN_THUMB] = "";
542         text[DUPE_COLUMN_NAME] = (gchar *)di->fd->name;
543         text[DUPE_COLUMN_SIZE] = text_from_size(di->fd->size);
544         text[DUPE_COLUMN_DATE] = (gchar *)text_from_time(di->fd->date);
545         if (di->width > 0 && di->height > 0)
546                 {
547                 text[DUPE_COLUMN_DIMENSIONS] = g_strdup_printf("%d x %d", di->width, di->height);
548                 }
549         else
550                 {
551                 text[DUPE_COLUMN_DIMENSIONS] = g_strdup("");
552                 }
553         text[DUPE_COLUMN_PATH] = di->fd->path;
554         text[DUPE_COLUMN_COLOR] = NULL;
555
556         gtk_list_store_insert(store, &iter, row);
557         gtk_list_store_set(store, &iter,
558                                 DUPE_COLUMN_POINTER, di,
559                                 DUPE_COLUMN_RANK, text[DUPE_COLUMN_RANK],
560                                 DUPE_COLUMN_THUMB, NULL,
561                                 DUPE_COLUMN_NAME, text[DUPE_COLUMN_NAME],
562                                 DUPE_COLUMN_SIZE, text[DUPE_COLUMN_SIZE],
563                                 DUPE_COLUMN_DATE, text[DUPE_COLUMN_DATE],
564                                 DUPE_COLUMN_DIMENSIONS, text[DUPE_COLUMN_DIMENSIONS],
565                                 DUPE_COLUMN_PATH, text[DUPE_COLUMN_PATH],
566                                 DUPE_COLUMN_COLOR, color_set,
567                                 -1);
568
569         g_free(text[DUPE_COLUMN_RANK]);
570         g_free(text[DUPE_COLUMN_SIZE]);
571         g_free(text[DUPE_COLUMN_DIMENSIONS]);
572 }
573
574 static void dupe_listview_populate(DupeWindow *dw)
575 {
576         GtkListStore *store;
577         GList *work;
578
579         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(dw->listview)));
580         gtk_list_store_clear(store);
581
582         work = g_list_last(dw->dupes);
583         while (work)
584                 {
585                 DupeItem *parent = work->data;
586                 GList *temp;
587
588                 dupe_listview_add(dw, parent, NULL);
589
590                 temp = g_list_last(parent->group);
591                 while (temp)
592                         {
593                         DupeMatch *dm = temp->data;
594                         DupeItem *child;
595
596                         child = dm->di;
597
598                         dupe_listview_add(dw, parent, child);
599
600                         temp = temp->prev;
601                         }
602
603                 work = work->prev;
604                 }
605
606         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(dw->listview));
607 }
608
609 static void dupe_listview_remove(DupeWindow *dw, DupeItem *di)
610 {
611         GtkListStore *store;
612         GtkTreeIter iter;
613         gint row;
614
615         if (!di) return;
616
617         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(dw->listview)));
618         row = dupe_listview_find_item(store, di, &iter);
619         if (row < 0) return;
620
621         tree_view_move_cursor_away(GTK_TREE_VIEW(dw->listview), &iter, TRUE);
622         gtk_list_store_remove(store, &iter);
623
624         if (g_list_find(dw->dupes, di) != NULL)
625                 {
626                 if (!dw->color_frozen) dupe_listview_realign_colors(dw);
627                 }
628 }
629
630
631 static GList *dupe_listview_get_filelist(DupeWindow *dw, GtkWidget *listview)
632 {
633         GtkTreeModel *store;
634         GtkTreeIter iter;
635         gboolean valid;
636         GList *list = NULL;
637
638         store = gtk_tree_view_get_model(GTK_TREE_VIEW(listview));
639         valid = gtk_tree_model_get_iter_first(store, &iter);
640         while (valid)
641                 {
642                 DupeItem *di;
643                 gtk_tree_model_get(store, &iter, DUPE_COLUMN_POINTER, &di, -1);
644                 list = g_list_prepend(list, file_data_ref(di->fd));
645
646                 valid = gtk_tree_model_iter_next(store, &iter);
647                 }
648
649         return g_list_reverse(list);
650 }
651
652
653 static GList *dupe_listview_get_selection(DupeWindow *dw, GtkWidget *listview)
654 {
655         GtkTreeModel *store;
656         GtkTreeSelection *selection;
657         GList *slist;
658         GList *list = NULL;
659         GList *work;
660
661         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(listview));
662         slist = gtk_tree_selection_get_selected_rows(selection, &store);
663         work = slist;
664         while (work)
665                 {
666                 GtkTreePath *tpath = work->data;
667                 DupeItem *di = NULL;
668                 GtkTreeIter iter;
669
670                 gtk_tree_model_get_iter(store, &iter, tpath);
671                 gtk_tree_model_get(store, &iter, DUPE_COLUMN_POINTER, &di, -1);
672                 if (di)
673                         {
674                         list = g_list_prepend(list, file_data_ref(di->fd));
675                         }
676                 work = work->next;
677                 }
678         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
679         g_list_free(slist);
680
681         return g_list_reverse(list);
682 }
683
684 static gboolean dupe_listview_item_is_selected(DupeWindow *dw, DupeItem *di, GtkWidget *listview)
685 {
686         GtkTreeModel *store;
687         GtkTreeSelection *selection;
688         GList *slist;
689         GList *work;
690         gboolean found = FALSE;
691
692         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(listview));
693         slist = gtk_tree_selection_get_selected_rows(selection, &store);
694         work = slist;
695         while (!found && work)
696                 {
697                 GtkTreePath *tpath = work->data;
698                 DupeItem *di_n;
699                 GtkTreeIter iter;
700
701                 gtk_tree_model_get_iter(store, &iter, tpath);
702                 gtk_tree_model_get(store, &iter, DUPE_COLUMN_POINTER, &di_n, -1);
703                 if (di_n == di) found = TRUE;
704                 work = work->next;
705                 }
706         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
707         g_list_free(slist);
708
709         return found;
710 }
711
712 static void dupe_listview_select_dupes(DupeWindow *dw, gint parents)
713 {
714         GtkTreeModel *store;
715         GtkTreeSelection *selection;
716         GtkTreeIter iter;
717         gboolean valid;
718
719         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dw->listview));
720         gtk_tree_selection_unselect_all(selection);
721
722         store = gtk_tree_view_get_model(GTK_TREE_VIEW(dw->listview));
723         valid = gtk_tree_model_get_iter_first(store, &iter);
724         while (valid)
725                 {
726                 DupeItem *di;
727
728                 gtk_tree_model_get(store, &iter, DUPE_COLUMN_POINTER, &di, -1);
729                 if ( (dupe_match_find_parent(dw, di) == di) == (parents) )
730                         {
731                         gtk_tree_selection_select_iter(selection, &iter);
732                         }
733                 valid = gtk_tree_model_iter_next(store, &iter);
734                 }
735 }
736
737 /*
738  * ------------------------------------------------------------------
739  * Match group manipulation
740  * ------------------------------------------------------------------
741  */
742
743 static DupeMatch *dupe_match_find_match(DupeItem *child, DupeItem *parent)
744 {
745         GList *work;
746
747         work = parent->group;
748         while (work)
749                 {
750                 DupeMatch *dm = work->data;
751                 if (dm->di == child) return dm;
752                 work = work->next;
753                 }
754         return NULL;
755 }
756
757 static void dupe_match_link_child(DupeItem *child, DupeItem *parent, gdouble rank)
758 {
759         DupeMatch *dm;
760
761         dm = g_new0(DupeMatch, 1);
762         dm->di = child;
763         dm->rank = rank;
764         parent->group = g_list_append(parent->group, dm);
765 }
766
767 static void dupe_match_link(DupeItem *a, DupeItem *b, gdouble rank)
768 {
769         dupe_match_link_child(a, b, rank);
770         dupe_match_link_child(b, a, rank);
771 }
772
773 static void dupe_match_unlink_child(DupeItem *child, DupeItem *parent)
774 {
775         DupeMatch *dm;
776
777         dm = dupe_match_find_match(child, parent);
778         if (dm)
779                 {
780                 parent->group = g_list_remove(parent->group, dm);
781                 g_free(dm);
782                 }
783 }
784
785 static void dupe_match_unlink(DupeItem *a, DupeItem *b)
786 {
787         dupe_match_unlink_child(a, b);
788         dupe_match_unlink_child(b, a);
789 }
790
791 static void dupe_match_link_clear(DupeItem *parent, gboolean unlink_children)
792 {
793         GList *work;
794
795         work = parent->group;
796         while (work)
797                 {
798                 DupeMatch *dm = work->data;
799                 work = work->next;
800
801                 if (unlink_children) dupe_match_unlink_child(parent, dm->di);
802
803                 g_free(dm);
804                 }
805
806         g_list_free(parent->group);
807         parent->group = NULL;
808         parent->group_rank = 0.0;
809 }
810
811 static gint dupe_match_link_exists(DupeItem *child, DupeItem *parent)
812 {
813         return (dupe_match_find_match(child, parent) != NULL);
814 }
815
816 static gdouble dupe_match_link_rank(DupeItem *child, DupeItem *parent)
817 {
818         DupeMatch *dm;
819
820         dm = dupe_match_find_match(child, parent);
821         if (dm) return dm->rank;
822
823         return 0.0;
824 }
825
826 static DupeItem *dupe_match_highest_rank(DupeItem *child)
827 {
828         DupeMatch *dr;
829         GList *work;
830
831         dr = NULL;
832         work = child->group;
833         while (work)
834                 {
835                 DupeMatch *dm = work->data;
836                 if (!dr || dm->rank > dr->rank) dr = dm;
837                 work = work->next;
838                 }
839
840         return (dr) ? dr->di : NULL;
841 }
842
843 static void dupe_match_rank_update(DupeItem *parent)
844 {
845         GList *work;
846         gdouble rank = 0.0;
847         gint c = 0;
848
849         work = parent->group;
850         while (work)
851                 {
852                 DupeMatch *dm = work->data;
853                 work = work->next;
854                 rank += dm->rank;
855                 c++;
856                 }
857
858         if (c > 0)
859                 {
860                 parent->group_rank = rank / c;
861                 }
862         else
863                 {
864                 parent->group_rank = 0.0;
865                 }
866 }
867
868 static DupeItem *dupe_match_find_parent(DupeWindow *dw, DupeItem *child)
869 {
870         GList *work;
871
872         if (g_list_find(dw->dupes, child)) return child;
873
874         work = child->group;
875         while (work)
876                 {
877                 DupeMatch *dm = work->data;
878                 if (g_list_find(dw->dupes, dm->di)) return dm->di;
879                 work = work->next;
880                 }
881
882         return NULL;
883 }
884
885 static void dupe_match_reset_list(GList *work)
886 {
887         while (work)
888                 {
889                 DupeItem *di = work->data;
890                 work = work->next;
891
892                 dupe_match_link_clear(di, FALSE);
893                 }
894 }
895
896 static void dupe_match_reparent(DupeWindow *dw, DupeItem *old, DupeItem *new)
897 {
898         GList *work;
899
900         if (!old || !new || !dupe_match_link_exists(old, new)) return;
901
902         dupe_match_link_clear(new, TRUE);
903         work = old->group;
904         while (work)
905                 {
906                 DupeMatch *dm = work->data;
907                 dupe_match_unlink_child(old, dm->di);
908                 dupe_match_link_child(new, dm->di, dm->rank);
909                 work = work->next;
910                 }
911
912         new->group = old->group;
913         old->group = NULL;
914
915         work = g_list_find(dw->dupes, old);
916         if (work) work->data = new;
917 }
918
919 static void dupe_match_print_group(DupeItem *di)
920 {
921         GList *work;
922
923         log_printf("+ %f %s\n", di->group_rank, di->fd->name);
924
925         work = di->group;
926         while (work)
927                 {
928                 DupeMatch *dm = work->data;
929                 work = work->next;
930
931                 log_printf("  %f %s\n", dm->rank, dm->di->fd->name);
932                 }
933
934         log_printf("\n");
935 }
936
937 static void dupe_match_print_list(GList *list)
938 {
939         GList *work;
940
941         work = list;
942         while (work)
943                 {
944                 DupeItem *di = work->data;
945                 dupe_match_print_group(di);
946                 work = work->next;
947                 }
948 }
949
950 /* level 3, unlinking and orphan handling */
951 static GList *dupe_match_unlink_by_rank(DupeItem *child, DupeItem *parent, GList *list, DupeWindow *dw)
952 {
953         DupeItem *best;
954
955         best = dupe_match_highest_rank(parent);
956         if (best == child || dupe_match_highest_rank(child) == parent)
957                 {
958                 GList *work;
959                 gdouble rank;
960
961                 DEBUG_2("link found %s to %s [%d]", child->fd->name, parent->fd->name, g_list_length(parent->group));
962
963                 work = parent->group;
964                 while (work)
965                         {
966                         DupeMatch *dm = work->data;
967                         DupeItem *orphan;
968
969                         work = work->next;
970                         orphan = dm->di;
971                         if (orphan != child && g_list_length(orphan->group) < 2)
972                                 {
973                                 dupe_match_link_clear(orphan, TRUE);
974                                 if (!dw->second_set || orphan->second)
975                                         {
976                                         dupe_match(orphan, child, dw->match_mask, &rank, FALSE);
977                                         dupe_match_link(orphan, child, rank);
978                                         }
979                                 list = g_list_remove(list, orphan);
980                                 }
981                         }
982
983                 rank = dupe_match_link_rank(child, parent);
984                 dupe_match_link_clear(parent, TRUE);
985                 dupe_match_link(child, parent, rank);
986                 list = g_list_remove(list, parent);
987                 }
988         else
989                 {
990                 DEBUG_2("unlinking %s and %s", child->fd->name, parent->fd->name);
991
992                 dupe_match_unlink(child, parent);
993                 }
994
995         return list;
996 }
997
998 /* level 2 */
999 static GList *dupe_match_group_filter(GList *list, DupeItem *di, DupeWindow *dw)
1000 {
1001         GList *work;
1002
1003         work = g_list_last(di->group);
1004         while (work)
1005                 {
1006                 DupeMatch *dm = work->data;
1007                 work = work->prev;
1008                 list = dupe_match_unlink_by_rank(di, dm->di, list, dw);
1009                 }
1010
1011         return list;
1012 }
1013
1014 /* level 1 (top) */
1015 static GList *dupe_match_group_trim(GList *list, DupeWindow *dw)
1016 {
1017         GList *work;
1018
1019         work = list;
1020         while (work)
1021                 {
1022                 DupeItem *di = work->data;
1023                 if (!di->second) list = dupe_match_group_filter(list, di, dw);
1024                 work = work->next;
1025                 if (di->second) list = g_list_remove(list, di);
1026                 }
1027
1028         return list;
1029 }
1030
1031 static gint dupe_match_sort_groups_cb(gconstpointer a, gconstpointer b)
1032 {
1033         DupeMatch *da = (DupeMatch *)a;
1034         DupeMatch *db = (DupeMatch *)b;
1035
1036         if (da->rank > db->rank) return -1;
1037         if (da->rank < db->rank) return 1;
1038         return 0;
1039 }
1040
1041 static void dupe_match_sort_groups(GList *list)
1042 {
1043         GList *work;
1044
1045         work = list;
1046         while (work)
1047                 {
1048                 DupeItem *di = work->data;
1049                 di->group = g_list_sort(di->group, dupe_match_sort_groups_cb);
1050                 work = work->next;
1051                 }
1052 }
1053
1054 static gint dupe_match_rank_sort_cb(gconstpointer a, gconstpointer b)
1055 {
1056         DupeItem *da = (DupeItem *)a;
1057         DupeItem *db = (DupeItem *)b;
1058
1059         if (da->group_rank > db->group_rank) return -1;
1060         if (da->group_rank < db->group_rank) return 1;
1061         return 0;
1062 }
1063
1064 /* returns allocated GList of dupes sorted by rank */
1065 static GList *dupe_match_rank_sort(GList *source_list)
1066 {
1067         GList *list = NULL;
1068         GList *work;
1069
1070         work = source_list;
1071         while (work)
1072                 {
1073                 DupeItem *di = work->data;
1074
1075                 if (di->group)
1076                         {
1077                         dupe_match_rank_update(di);
1078                         list = g_list_prepend(list, di);
1079                         }
1080
1081                 work = work->next;
1082                 }
1083
1084         return g_list_sort(list, dupe_match_rank_sort_cb);
1085 }
1086
1087 static void dupe_match_rank(DupeWindow *dw)
1088 {
1089         GList *list;
1090
1091         list = dupe_match_rank_sort(dw->list);
1092
1093         if (required_debug_level(2)) dupe_match_print_list(list);
1094
1095         DEBUG_1("Similar items: %d", g_list_length(list));
1096         list = dupe_match_group_trim(list, dw);
1097         DEBUG_1("Unique groups: %d", g_list_length(list));
1098
1099         dupe_match_sort_groups(list);
1100
1101         if (required_debug_level(2)) dupe_match_print_list(list);
1102
1103         list = dupe_match_rank_sort(list);
1104
1105         g_list_free(dw->dupes);
1106         dw->dupes = list;
1107 }
1108
1109 /*
1110  * ------------------------------------------------------------------
1111  * Match group tests
1112  * ------------------------------------------------------------------
1113  */
1114
1115 static gboolean dupe_match(DupeItem *a, DupeItem *b, DupeMatchType mask, gdouble *rank, gint fast)
1116 {
1117         *rank = 0.0;
1118
1119         if (a->fd->path == b->fd->path) return FALSE;
1120
1121         if (mask & DUPE_MATCH_PATH)
1122                 {
1123                 if (utf8_compare(a->fd->path, b->fd->path, TRUE) != 0) return FALSE;
1124                 }
1125         if (mask & DUPE_MATCH_NAME)
1126                 {
1127                 if (strcmp(a->fd->collate_key_name, b->fd->collate_key_name) != 0) return FALSE;
1128                 }
1129         if (mask & DUPE_MATCH_NAME_CI)
1130                 {
1131                 if (strcmp(a->fd->collate_key_name_nocase, b->fd->collate_key_name_nocase) != 0) return FALSE;
1132                 }
1133         if (mask & DUPE_MATCH_SIZE)
1134                 {
1135                 if (a->fd->size != b->fd->size) return FALSE;
1136                 }
1137         if (mask & DUPE_MATCH_DATE)
1138                 {
1139                 if (a->fd->date != b->fd->date) return FALSE;
1140                 }
1141         if (mask & DUPE_MATCH_SUM)
1142                 {
1143                 if (!a->md5sum) a->md5sum = md5_text_from_file_utf8(a->fd->path, "");
1144                 if (!b->md5sum) b->md5sum = md5_text_from_file_utf8(b->fd->path, "");
1145                 if (a->md5sum[0] == '\0' ||
1146                     b->md5sum[0] == '\0' ||
1147                     strcmp(a->md5sum, b->md5sum) != 0) return FALSE;
1148                 }
1149         if (mask & DUPE_MATCH_DIM)
1150                 {
1151                 if (a->width == 0) image_load_dimensions(a->fd, &a->width, &a->height);
1152                 if (b->width == 0) image_load_dimensions(b->fd, &b->width, &b->height);
1153                 if (a->width != b->width || a->height != b->height) return FALSE;
1154                 }
1155         if (mask & DUPE_MATCH_SIM_HIGH ||
1156             mask & DUPE_MATCH_SIM_MED ||
1157             mask & DUPE_MATCH_SIM_LOW ||
1158             mask & DUPE_MATCH_SIM_CUSTOM)
1159                 {
1160                 gdouble f;
1161                 gdouble m;
1162
1163                 if (mask & DUPE_MATCH_SIM_HIGH) m = 0.95;
1164                 else if (mask & DUPE_MATCH_SIM_MED) m = 0.90;
1165                 else if (mask & DUPE_MATCH_SIM_CUSTOM) m = (gdouble)options->duplicates_similarity_threshold / 100.0;
1166                 else m = 0.85;
1167
1168                 if (fast)
1169                         {
1170                         f = image_sim_compare_fast(a->simd, b->simd, m);
1171                         }
1172                 else
1173                         {
1174                         f = image_sim_compare(a->simd, b->simd);
1175                         }
1176
1177                 *rank = f * 100.0;
1178
1179                 if (f < m) return FALSE;
1180
1181                 DEBUG_3("similar: %32s %32s = %f", a->fd->name, b->fd->name, f);
1182                 }
1183
1184         return TRUE;
1185 }
1186
1187 static void dupe_list_check_match(DupeWindow *dw, DupeItem *needle, GList *start)
1188 {
1189         GList *work;
1190
1191         if (dw->second_set)
1192                 {
1193                 work = dw->second_list;
1194                 }
1195         else if (start)
1196                 {
1197                 work = start;
1198                 }
1199         else
1200                 {
1201                 work = g_list_last(dw->list);
1202                 }
1203
1204         while (work)
1205                 {
1206                 DupeItem *di = work->data;
1207
1208                 /* speed opt: forward for second set, back for simple compare */
1209                 if (dw->second_set)
1210                         work = work->next;
1211                 else
1212                         work = work->prev;
1213
1214                 if (!dupe_match_link_exists(needle, di))
1215                         {
1216                         gdouble rank;
1217
1218                         if (dupe_match(di, needle, dw->match_mask, &rank, TRUE))
1219                                 {
1220                                 dupe_match_link(di, needle, rank);
1221                                 }
1222                         }
1223                 }
1224 }
1225
1226 /*
1227  * ------------------------------------------------------------------
1228  * Thumbnail handling
1229  * ------------------------------------------------------------------
1230  */
1231
1232 static void dupe_listview_set_thumb(DupeWindow *dw, DupeItem *di, GtkTreeIter *iter)
1233 {
1234         GtkListStore *store;
1235         GtkTreeIter iter_n;
1236
1237         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(dw->listview)));
1238         if (!iter)
1239                 {
1240                 if (dupe_listview_find_item(store, di, &iter_n) >= 0)
1241                         {
1242                         iter = &iter_n;
1243                         }
1244                 }
1245
1246         if (iter) gtk_list_store_set(store, iter, DUPE_COLUMN_THUMB, di->pixbuf, -1);
1247 }
1248
1249 static void dupe_thumb_do(DupeWindow *dw)
1250 {
1251         DupeItem *di;
1252
1253         if (!dw->thumb_loader || !dw->thumb_item) return;
1254         di = dw->thumb_item;
1255
1256         if (di->pixbuf) g_object_unref(di->pixbuf);
1257         di->pixbuf = thumb_loader_get_pixbuf(dw->thumb_loader);
1258
1259         dupe_listview_set_thumb(dw, di, NULL);
1260 }
1261
1262 static void dupe_thumb_error_cb(ThumbLoader *tl, gpointer data)
1263 {
1264         DupeWindow *dw = data;
1265
1266         dupe_thumb_do(dw);
1267         dupe_thumb_step(dw);
1268 }
1269
1270 static void dupe_thumb_done_cb(ThumbLoader *tl, gpointer data)
1271 {
1272         DupeWindow *dw = data;
1273
1274         dupe_thumb_do(dw);
1275         dupe_thumb_step(dw);
1276 }
1277
1278 static void dupe_thumb_step(DupeWindow *dw)
1279 {
1280         GtkTreeModel *store;
1281         GtkTreeIter iter;
1282         DupeItem *di = NULL;
1283         gboolean valid;
1284         gint row = 0;
1285         gint length = 0;
1286
1287         store = gtk_tree_view_get_model(GTK_TREE_VIEW(dw->listview));
1288         valid = gtk_tree_model_get_iter_first(store, &iter);
1289
1290         while (!di && valid)
1291                 {
1292                 GdkPixbuf *pixbuf;
1293
1294                 length++;
1295                 gtk_tree_model_get(store, &iter, DUPE_COLUMN_POINTER, &di, DUPE_COLUMN_THUMB, &pixbuf, -1);
1296                 if (pixbuf || di->pixbuf)
1297                         {
1298                         if (!pixbuf) gtk_list_store_set(GTK_LIST_STORE(store), &iter, DUPE_COLUMN_THUMB, di->pixbuf, -1);
1299                         row++;
1300                         di = NULL;
1301                         }
1302                 valid = gtk_tree_model_iter_next(store, &iter);
1303                 }
1304         if (valid)
1305                 {
1306                 while (gtk_tree_model_iter_next(store, &iter)) length++;
1307                 }
1308
1309         if (!di)
1310                 {
1311                 dw->thumb_item = NULL;
1312                 thumb_loader_free(dw->thumb_loader);
1313                 dw->thumb_loader = NULL;
1314
1315                 dupe_window_update_progress(dw, NULL, 0.0, FALSE);
1316                 return;
1317                 }
1318
1319         dupe_window_update_progress(dw, _("Loading thumbs..."),
1320                                     length == 0 ? 0.0 : (gdouble)(row) / length, FALSE);
1321
1322         dw->thumb_item = di;
1323         thumb_loader_free(dw->thumb_loader);
1324         dw->thumb_loader = thumb_loader_new(options->thumbnails.max_width, options->thumbnails.max_height);
1325
1326         thumb_loader_set_callbacks(dw->thumb_loader,
1327                                    dupe_thumb_done_cb,
1328                                    dupe_thumb_error_cb,
1329                                    NULL,
1330                                    dw);
1331
1332         /* start it */
1333         if (!thumb_loader_start(dw->thumb_loader, di->fd))
1334                 {
1335                 /* error, handle it, do next */
1336                 DEBUG_1("error loading thumb for %s", di->fd->path);
1337                 dupe_thumb_do(dw);
1338                 dupe_thumb_step(dw);
1339                 }
1340 }
1341
1342 /*
1343  * ------------------------------------------------------------------
1344  * Dupe checking loop
1345  * ------------------------------------------------------------------
1346  */
1347
1348 static void dupe_check_stop(DupeWindow *dw)
1349 {
1350         if (dw->idle_id || dw->img_loader || dw->thumb_loader)
1351                 {
1352                 g_source_remove(dw->idle_id);
1353                 dw->idle_id = 0;
1354                 dupe_window_update_progress(dw, NULL, 0.0, FALSE);
1355                 widget_set_cursor(dw->listview, -1);
1356                 }
1357
1358         thumb_loader_free(dw->thumb_loader);
1359         dw->thumb_loader = NULL;
1360
1361         image_loader_free(dw->img_loader);
1362         dw->img_loader = NULL;
1363 }
1364
1365 static void dupe_loader_done_cb(ImageLoader *il, gpointer data)
1366 {
1367         DupeWindow *dw = data;
1368         GdkPixbuf *pixbuf;
1369
1370         pixbuf = image_loader_get_pixbuf(il);
1371
1372         if (dw->setup_point)
1373                 {
1374                 DupeItem *di = dw->setup_point->data;
1375
1376                 if (!di->simd)
1377                         {
1378                         di->simd = image_sim_new_from_pixbuf(pixbuf);
1379                         }
1380                 else
1381                         {
1382                         image_sim_fill_data(di->simd, pixbuf);
1383                         }
1384
1385                 if (di->width == 0 && di->height == 0)
1386                         {
1387                         di->width = gdk_pixbuf_get_width(pixbuf);
1388                         di->height = gdk_pixbuf_get_height(pixbuf);
1389                         }
1390                 if (options->thumbnails.enable_caching)
1391                         {
1392                         dupe_item_write_cache(di);
1393                         }
1394
1395                 image_sim_alternate_processing(di->simd);
1396                 }
1397
1398         image_loader_free(dw->img_loader);
1399         dw->img_loader = NULL;
1400
1401         dw->idle_id = g_idle_add(dupe_check_cb, dw);
1402 }
1403
1404 static void dupe_setup_reset(DupeWindow *dw)
1405 {
1406         dw->setup_point = NULL;
1407         dw->setup_n = 0;
1408         dw->setup_time = msec_time();
1409         dw->setup_time_count = 0;
1410 }
1411
1412 static GList *dupe_setup_point_step(DupeWindow *dw, GList *p)
1413 {
1414         if (!p) return NULL;
1415
1416         if (p->next) return p->next;
1417
1418         if (dw->second_set && g_list_first(p) == dw->list) return dw->second_list;
1419
1420         return NULL;
1421 }
1422
1423 static gboolean dupe_check_cb(gpointer data)
1424 {
1425         DupeWindow *dw = data;
1426
1427         if (!dw->idle_id) return FALSE;
1428
1429         if (!dw->setup_done)
1430                 {
1431                 if ((dw->match_mask & DUPE_MATCH_SUM) &&
1432                     !(dw->setup_mask & DUPE_MATCH_SUM) )
1433                         {
1434                         if (!dw->setup_point) dw->setup_point = dw->list;
1435
1436                         while (dw->setup_point)
1437                                 {
1438                                 DupeItem *di = dw->setup_point->data;
1439
1440                                 dw->setup_point = dupe_setup_point_step(dw, dw->setup_point);
1441                                 dw->setup_n++;
1442
1443                                 if (!di->md5sum)
1444                                         {
1445                                         dupe_window_update_progress(dw, _("Reading checksums..."),
1446                                                 dw->setup_count == 0 ? 0.0 : (gdouble)(dw->setup_n - 1) / dw->setup_count, FALSE);
1447
1448                                         if (options->thumbnails.enable_caching)
1449                                                 {
1450                                                 dupe_item_read_cache(di);
1451                                                 if (di->md5sum) return TRUE;
1452                                                 }
1453
1454                                         di->md5sum = md5_text_from_file_utf8(di->fd->path, "");
1455                                         if (options->thumbnails.enable_caching)
1456                                                 {
1457                                                 dupe_item_write_cache(di);
1458                                                 }
1459                                         return TRUE;
1460                                         }
1461                                 }
1462                         dw->setup_mask |= DUPE_MATCH_SUM;
1463                         dupe_setup_reset(dw);
1464                         }
1465                 if ((dw->match_mask & DUPE_MATCH_DIM) &&
1466                     !(dw->setup_mask & DUPE_MATCH_DIM) )
1467                         {
1468                         if (!dw->setup_point) dw->setup_point = dw->list;
1469
1470                         while (dw->setup_point)
1471                                 {
1472                                 DupeItem *di = dw->setup_point->data;
1473
1474                                 dw->setup_point = dupe_setup_point_step(dw, dw->setup_point);
1475                                 dw->setup_n++;
1476                                 if (di->width == 0 && di->height == 0)
1477                                         {
1478                                         dupe_window_update_progress(dw, _("Reading dimensions..."),
1479                                                 dw->setup_count == 0 ? 0.0 : (gdouble)(dw->setup_n - 1) / dw->setup_count, FALSE);
1480
1481                                         if (options->thumbnails.enable_caching)
1482                                                 {
1483                                                 dupe_item_read_cache(di);
1484                                                 if (di->width != 0 || di->height != 0) return TRUE;
1485                                                 }
1486
1487                                         image_load_dimensions(di->fd, &di->width, &di->height);
1488                                         if (options->thumbnails.enable_caching)
1489                                                 {
1490                                                 dupe_item_write_cache(di);
1491                                                 }
1492                                         return TRUE;
1493                                         }
1494                                 }
1495                         dw->setup_mask |= DUPE_MATCH_DIM;
1496                         dupe_setup_reset(dw);
1497                         }
1498                 if ((dw->match_mask & DUPE_MATCH_SIM_HIGH ||
1499                      dw->match_mask & DUPE_MATCH_SIM_MED ||
1500                      dw->match_mask & DUPE_MATCH_SIM_LOW ||
1501                      dw->match_mask & DUPE_MATCH_SIM_CUSTOM) &&
1502                     !(dw->setup_mask & DUPE_MATCH_SIM_MED) )
1503                         {
1504                         if (!dw->setup_point) dw->setup_point = dw->list;
1505
1506                         while (dw->setup_point)
1507                                 {
1508                                 DupeItem *di = dw->setup_point->data;
1509
1510                                 if (!di->simd)
1511                                         {
1512                                         dupe_window_update_progress(dw, _("Reading similarity data..."),
1513                                                 dw->setup_count == 0 ? 0.0 : (gdouble)dw->setup_n / dw->setup_count, FALSE);
1514
1515                                         if (options->thumbnails.enable_caching)
1516                                                 {
1517                                                 dupe_item_read_cache(di);
1518                                                 if (cache_sim_data_filled(di->simd))
1519                                                         {
1520                                                         image_sim_alternate_processing(di->simd);
1521                                                         return TRUE;
1522                                                         }
1523                                                 }
1524
1525                                         dw->img_loader = image_loader_new(di->fd);
1526                                         image_loader_set_buffer_size(dw->img_loader, 8);
1527                                         g_signal_connect(G_OBJECT(dw->img_loader), "error", (GCallback)dupe_loader_done_cb, dw);
1528                                         g_signal_connect(G_OBJECT(dw->img_loader), "done", (GCallback)dupe_loader_done_cb, dw);
1529
1530                                         if (!image_loader_start(dw->img_loader))
1531                                                 {
1532                                                 image_sim_free(di->simd);
1533                                                 di->simd = image_sim_new();
1534                                                 image_loader_free(dw->img_loader);
1535                                                 dw->img_loader = NULL;
1536                                                 return TRUE;
1537                                                 }
1538                                         dw->idle_id = 0;
1539                                         return FALSE;
1540                                         }
1541
1542                                 dw->setup_point = dupe_setup_point_step(dw, dw->setup_point);
1543                                 dw->setup_n++;
1544                                 }
1545                         dw->setup_mask |= DUPE_MATCH_SIM_MED;
1546                         dupe_setup_reset(dw);
1547                         }
1548                 dupe_window_update_progress(dw, _("Comparing..."), 0.0, FALSE);
1549                 dw->setup_done = TRUE;
1550                 dupe_setup_reset(dw);
1551                 dw->setup_count = g_list_length(dw->list);
1552                 }
1553
1554         if (!dw->working)
1555                 {
1556                 if (dw->setup_count > 0)
1557                         {
1558                         dw->setup_count = 0;
1559                         dupe_window_update_progress(dw, _("Sorting..."), 1.0, TRUE);
1560                         return TRUE;
1561                         }
1562                 dw->idle_id = 0;
1563                 dupe_window_update_progress(dw, NULL, 0.0, FALSE);
1564
1565                 dupe_match_rank(dw);
1566                 dupe_window_update_count(dw, FALSE);
1567
1568                 dupe_listview_populate(dw);
1569
1570                 /* check thumbs */
1571                 if (dw->show_thumbs) dupe_thumb_step(dw);
1572
1573                 widget_set_cursor(dw->listview, -1);
1574
1575                 return FALSE;
1576                 }
1577
1578         dupe_list_check_match(dw, (DupeItem *)dw->working->data, dw->working);
1579         dupe_window_update_progress(dw, _("Comparing..."), dw->setup_count == 0 ? 0.0 : (gdouble) dw->setup_n / dw->setup_count, FALSE);
1580         dw->setup_n++;
1581
1582         dw->working = dw->working->prev;
1583
1584         return TRUE;
1585 }
1586
1587 static void dupe_check_start(DupeWindow *dw)
1588 {
1589         dw->setup_done = FALSE;
1590
1591         dw->setup_count = g_list_length(dw->list);
1592         if (dw->second_set) dw->setup_count += g_list_length(dw->second_list);
1593
1594         dw->setup_mask = 0;
1595         dupe_setup_reset(dw);
1596
1597         dw->working = g_list_last(dw->list);
1598
1599         dupe_window_update_count(dw, TRUE);
1600         widget_set_cursor(dw->listview, GDK_WATCH);
1601
1602         if (dw->idle_id) return;
1603
1604         dw->idle_id = g_idle_add(dupe_check_cb, dw);
1605 }
1606
1607 /*
1608  * ------------------------------------------------------------------
1609  * Item addition, removal
1610  * ------------------------------------------------------------------
1611  */
1612
1613 static void dupe_item_remove(DupeWindow *dw, DupeItem *di)
1614 {
1615         if (!di) return;
1616
1617         /* handle things that may be in progress... */
1618         if (dw->working && dw->working->data == di)
1619                 {
1620                 dw->working = dw->working->prev;
1621                 }
1622         if (dw->thumb_loader && dw->thumb_item == di)
1623                 {
1624                 dupe_thumb_step(dw);
1625                 }
1626         if (dw->setup_point && dw->setup_point->data == di)
1627                 {
1628                 dw->setup_point = dupe_setup_point_step(dw, dw->setup_point);
1629                 if (dw->img_loader)
1630                         {
1631                         image_loader_free(dw->img_loader);
1632                         dw->img_loader = NULL;
1633                         dw->idle_id = g_idle_add(dupe_check_cb, dw);
1634                         }
1635                 }
1636
1637         if (di->group && dw->dupes)
1638                 {
1639                 /* is a dupe, must remove from group/reset children if a parent */
1640                 DupeItem *parent;
1641
1642                 parent = dupe_match_find_parent(dw, di);
1643                 if (di == parent)
1644                         {
1645                         if (g_list_length(parent->group) < 2)
1646                                 {
1647                                 DupeItem *child;
1648
1649                                 child = dupe_match_highest_rank(parent);
1650                                 dupe_match_link_clear(child, TRUE);
1651                                 dupe_listview_remove(dw, child);
1652
1653                                 dupe_match_link_clear(parent, TRUE);
1654                                 dupe_listview_remove(dw, parent);
1655                                 dw->dupes = g_list_remove(dw->dupes, parent);
1656                                 }
1657                         else
1658                                 {
1659                                 DupeItem *new_parent;
1660                                 DupeMatch *dm;
1661
1662                                 dm = parent->group->data;
1663                                 new_parent = dm->di;
1664                                 dupe_match_reparent(dw, parent, new_parent);
1665                                 dupe_listview_remove(dw, parent);
1666                                 }
1667                         }
1668                 else
1669                         {
1670                         if (g_list_length(parent->group) < 2)
1671                                 {
1672                                 dupe_match_link_clear(parent, TRUE);
1673                                 dupe_listview_remove(dw, parent);
1674                                 dw->dupes = g_list_remove(dw->dupes, parent);
1675                                 }
1676                         dupe_match_link_clear(di, TRUE);
1677                         dupe_listview_remove(dw, di);
1678                         }
1679                 }
1680         else
1681                 {
1682                 /* not a dupe, or not sorted yet, simply reset */
1683                 dupe_match_link_clear(di, TRUE);
1684                 }
1685
1686         if (dw->second_list && g_list_find(dw->second_list, di))
1687                 {
1688                 dupe_second_remove(dw, di);
1689                 }
1690         else
1691                 {
1692                 dw->list = g_list_remove(dw->list, di);
1693                 }
1694         dupe_item_free(di);
1695
1696         dupe_window_update_count(dw, FALSE);
1697 }
1698
1699 static gboolean dupe_item_remove_by_path(DupeWindow *dw, const gchar *path)
1700 {
1701         DupeItem *di;
1702
1703         di = dupe_item_find_path(dw, path);
1704         if (!di) return FALSE;
1705
1706         dupe_item_remove(dw, di);
1707
1708         return TRUE;
1709 }
1710
1711 static void dupe_files_add(DupeWindow *dw, CollectionData *collection, CollectInfo *info,
1712                            FileData *fd, gboolean recurse)
1713 {
1714         DupeItem *di = NULL;
1715
1716         if (info)
1717                 {
1718                 di = dupe_item_new(info->fd);
1719                 }
1720         else if (fd)
1721                 {
1722                 if (isfile(fd->path))
1723                         {
1724                         di = dupe_item_new(fd);
1725                         }
1726                 else if (isdir(fd->path) && recurse)
1727                         {
1728                         GList *f, *d;
1729                         if (filelist_read(fd, &f, &d))
1730                                 {
1731                                 GList *work;
1732
1733                                 f = filelist_filter(f, FALSE);
1734                                 d = filelist_filter(d, TRUE);
1735
1736                                 work = f;
1737                                 while (work)
1738                                         {
1739                                         dupe_files_add(dw, NULL, NULL, (FileData *)work->data, TRUE);
1740                                         work = work->next;
1741                                         }
1742                                 filelist_free(f);
1743                                 work = d;
1744                                 while (work)
1745                                         {
1746                                         dupe_files_add(dw, NULL, NULL, (FileData *)work->data, TRUE);
1747                                         work = work->next;
1748                                         }
1749                                 filelist_free(d);
1750                                 }
1751                         }
1752                 }
1753
1754         if (!di) return;
1755
1756         if (dw->second_drop)
1757                 {
1758                 dupe_second_add(dw, di);
1759                 }
1760         else
1761                 {
1762                 dw->list = g_list_prepend(dw->list, di);
1763                 }
1764 }
1765
1766 void dupe_window_add_collection(DupeWindow *dw, CollectionData *collection)
1767 {
1768         CollectInfo *info;
1769
1770         info = collection_get_first(collection);
1771         while (info)
1772                 {
1773                 dupe_files_add(dw, collection, info, NULL, FALSE);
1774                 info = collection_next_by_info(collection, info);
1775                 }
1776
1777         dupe_check_start(dw);
1778 }
1779
1780 void dupe_window_add_files(DupeWindow *dw, GList *list, gboolean recurse)
1781 {
1782         GList *work;
1783
1784         work = list;
1785         while (work)
1786                 {
1787                 FileData *fd = work->data;
1788                 work = work->next;
1789
1790                 dupe_files_add(dw, NULL, NULL, fd, recurse);
1791                 }
1792
1793         dupe_check_start(dw);
1794 }
1795
1796 static void dupe_item_update(DupeWindow *dw, DupeItem *di)
1797 {
1798         if ( (dw->match_mask & DUPE_MATCH_NAME) || (dw->match_mask & DUPE_MATCH_PATH || (dw->match_mask & DUPE_MATCH_NAME_CI)) )
1799                 {
1800                 /* only effects matches on name or path */
1801 /*
1802                 FileData *fd = file_data_ref(di->fd);
1803                 gint second;
1804
1805                 second = di->second;
1806                 dupe_item_remove(dw, di);
1807
1808                 dw->second_drop = second;
1809                 dupe_files_add(dw, NULL, NULL, fd, FALSE);
1810                 dw->second_drop = FALSE;
1811
1812                 file_data_unref(fd);
1813 */
1814                 dupe_check_start(dw);
1815                 }
1816         else
1817                 {
1818                 GtkListStore *store;
1819                 GtkTreeIter iter;
1820                 gint row;
1821                 /* update the listview(s) */
1822
1823                 store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(dw->listview)));
1824                 row = dupe_listview_find_item(store, di, &iter);
1825                 if (row >= 0)
1826                         {
1827                         gtk_list_store_set(store, &iter,
1828                                            DUPE_COLUMN_NAME, di->fd->name,
1829                                            DUPE_COLUMN_PATH, di->fd->path, -1);
1830                         }
1831
1832                 if (dw->second_listview)
1833                         {
1834                         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(dw->second_listview)));
1835                         row = dupe_listview_find_item(store, di, &iter);
1836                         if (row >= 0)
1837                                 {
1838                                 gtk_list_store_set(store, &iter, 1, di->fd->path, -1);
1839                                 }
1840                         }
1841                 }
1842
1843 }
1844
1845 static void dupe_item_update_fd_in_list(DupeWindow *dw, FileData *fd, GList *work)
1846 {
1847         while (work)
1848                 {
1849                 DupeItem *di = work->data;
1850
1851                 if (di->fd == fd)
1852                         dupe_item_update(dw, di);
1853
1854                 work = work->next;
1855                 }
1856 }
1857
1858 static void dupe_item_update_fd(DupeWindow *dw, FileData *fd)
1859 {
1860         dupe_item_update_fd_in_list(dw, fd, dw->list);
1861         if (dw->second_set) dupe_item_update_fd_in_list(dw, fd, dw->second_list);
1862 }
1863
1864
1865 /*
1866  * ------------------------------------------------------------------
1867  * Misc.
1868  * ------------------------------------------------------------------
1869  */
1870
1871 static GtkWidget *dupe_display_label(GtkWidget *vbox, const gchar *description, const gchar *text)
1872 {
1873         GtkWidget *hbox;
1874         GtkWidget *label;
1875
1876         hbox = gtk_hbox_new(FALSE, 10);
1877
1878         label = gtk_label_new(description);
1879         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1880         gtk_widget_show(label);
1881
1882         label = gtk_label_new(text);
1883         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1884         gtk_widget_show(label);
1885
1886         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1887         gtk_widget_show(hbox);
1888
1889         return label;
1890 }
1891
1892 static void dupe_display_stats(DupeWindow *dw, DupeItem *di)
1893 {
1894         GenericDialog *gd;
1895         gchar *buf;
1896
1897         if (!di) return;
1898
1899         gd = file_util_gen_dlg("Image thumbprint debug info", "thumbprint",
1900                                dw->window, TRUE,
1901                                NULL, NULL);
1902         generic_dialog_add_button(gd, GTK_STOCK_CLOSE, NULL, NULL, TRUE);
1903
1904         dupe_display_label(gd->vbox, "name:", di->fd->name);
1905         buf = text_from_size(di->fd->size);
1906         dupe_display_label(gd->vbox, "size:", buf);
1907         g_free(buf);
1908         dupe_display_label(gd->vbox, "date:", text_from_time(di->fd->date));
1909         buf = g_strdup_printf("%d x %d", di->width, di->height);
1910         dupe_display_label(gd->vbox, "dimensions:", buf);
1911         g_free(buf);
1912         dupe_display_label(gd->vbox, "md5sum:", (di->md5sum) ? di->md5sum : "not generated");
1913
1914         dupe_display_label(gd->vbox, "thumbprint:", (di->simd) ? "" : "not generated");
1915         if (di->simd)
1916                 {
1917                 GtkWidget *image;
1918                 GdkPixbuf *pixbuf;
1919                 gint x, y;
1920                 guchar *d_pix;
1921                 guchar *dp;
1922                 gint rs;
1923                 gint sp;
1924
1925                 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 32, 32);
1926                 rs = gdk_pixbuf_get_rowstride(pixbuf);
1927                 d_pix = gdk_pixbuf_get_pixels(pixbuf);
1928
1929                 for (y = 0; y < 32; y++)
1930                         {
1931                         dp = d_pix + (y * rs);
1932                         sp = y * 32;
1933                         for (x = 0; x < 32; x++)
1934                                 {
1935                                 *(dp++) = di->simd->avg_r[sp + x];
1936                                 *(dp++) = di->simd->avg_g[sp + x];
1937                                 *(dp++) = di->simd->avg_b[sp + x];
1938                                 }
1939                         }
1940
1941                 image = gtk_image_new_from_pixbuf(pixbuf);
1942                 gtk_box_pack_start(GTK_BOX(gd->vbox), image, FALSE, FALSE, 0);
1943                 gtk_widget_show(image);
1944
1945                 g_object_unref(pixbuf);
1946                 }
1947
1948         gtk_widget_show(gd->dialog);
1949 }
1950
1951 static void dupe_window_recompare(DupeWindow *dw)
1952 {
1953         GtkListStore *store;
1954
1955         dupe_check_stop(dw);
1956
1957         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(dw->listview)));
1958         gtk_list_store_clear(store);
1959
1960         g_list_free(dw->dupes);
1961         dw->dupes = NULL;
1962
1963         dupe_match_reset_list(dw->list);
1964         dupe_match_reset_list(dw->second_list);
1965
1966         dupe_check_start(dw);
1967 }
1968
1969 static void dupe_menu_view(DupeWindow *dw, DupeItem *di, GtkWidget *listview, gint new_window)
1970 {
1971         if (!di) return;
1972
1973         if (di->collection && collection_info_valid(di->collection, di->info))
1974                 {
1975                 if (new_window)
1976                         {
1977                         view_window_new_from_collection(di->collection, di->info);
1978                         }
1979                 else
1980                         {
1981                         layout_image_set_collection(NULL, di->collection, di->info);
1982                         }
1983                 }
1984         else
1985                 {
1986                 if (new_window)
1987                         {
1988                         GList *list;
1989
1990                         list = dupe_listview_get_selection(dw, listview);
1991                         view_window_new_from_list(list);
1992                         filelist_free(list);
1993                         }
1994                 else
1995                         {
1996                         layout_set_fd(NULL, di->fd);
1997                         }
1998                 }
1999 }
2000
2001 static void dupe_window_remove_selection(DupeWindow *dw, GtkWidget *listview)
2002 {
2003         GtkTreeSelection *selection;
2004         GtkTreeModel *store;
2005         GtkTreeIter iter;
2006         GList *slist;
2007         GList *list = NULL;
2008         GList *work;
2009
2010         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(listview));
2011         slist = gtk_tree_selection_get_selected_rows(selection, &store);
2012         work = slist;
2013         while (work)
2014                 {
2015                 GtkTreePath *tpath = work->data;
2016                 DupeItem *di = NULL;
2017
2018                 gtk_tree_model_get_iter(store, &iter, tpath);
2019                 gtk_tree_model_get(store, &iter, DUPE_COLUMN_POINTER, &di, -1);
2020                 if (di) list = g_list_prepend(list, di);
2021                 work = work->next;
2022                 }
2023         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
2024         g_list_free(slist);
2025
2026         dw->color_frozen = TRUE;
2027         work = list;
2028         while (work)
2029                 {
2030                 DupeItem *di;
2031
2032                 di = work->data;
2033                 work = work->next;
2034                 dupe_item_remove(dw, di);
2035                 }
2036         dw->color_frozen = FALSE;
2037
2038         g_list_free(list);
2039
2040         dupe_listview_realign_colors(dw);
2041 }
2042
2043 static void dupe_window_edit_selected(DupeWindow *dw, const gchar *key)
2044 {
2045         file_util_start_editor_from_filelist(key, dupe_listview_get_selection(dw, dw->listview), NULL, dw->window);
2046 }
2047
2048 static void dupe_window_collection_from_selection(DupeWindow *dw)
2049 {
2050         CollectWindow *w;
2051         GList *list;
2052
2053         list = dupe_listview_get_selection(dw, dw->listview);
2054         w = collection_window_new(NULL);
2055         collection_table_add_filelist(w->table, list);
2056         filelist_free(list);
2057 }
2058
2059 static void dupe_window_append_file_list(DupeWindow *dw, gint on_second)
2060 {
2061         GList *list;
2062
2063         dw->second_drop = (dw->second_set && on_second);
2064
2065         list = layout_list(NULL);
2066         dupe_window_add_files(dw, list, FALSE);
2067         filelist_free(list);
2068 }
2069
2070 /*
2071  *-------------------------------------------------------------------
2072  * main pop-up menu callbacks
2073  *-------------------------------------------------------------------
2074  */
2075
2076 static void dupe_menu_view_cb(GtkWidget *widget, gpointer data)
2077 {
2078         DupeWindow *dw = data;
2079
2080         if (dw->click_item) dupe_menu_view(dw, dw->click_item, dw->listview, FALSE);
2081 }
2082
2083 static void dupe_menu_viewnew_cb(GtkWidget *widget, gpointer data)
2084 {
2085         DupeWindow *dw = data;
2086
2087         if (dw->click_item) dupe_menu_view(dw, dw->click_item, dw->listview, TRUE);
2088 }
2089
2090 static void dupe_menu_select_all_cb(GtkWidget *widget, gpointer data)
2091 {
2092         DupeWindow *dw = data;
2093         GtkTreeSelection *selection;
2094
2095         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dw->listview));
2096         gtk_tree_selection_select_all(selection);
2097 }
2098
2099 static void dupe_menu_select_none_cb(GtkWidget *widget, gpointer data)
2100 {
2101         DupeWindow *dw = data;
2102         GtkTreeSelection *selection;
2103
2104         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dw->listview));
2105         gtk_tree_selection_unselect_all(selection);
2106 }
2107
2108 static void dupe_menu_select_dupes_set1_cb(GtkWidget *widget, gpointer data)
2109 {
2110         DupeWindow *dw = data;
2111
2112         dupe_listview_select_dupes(dw, TRUE);
2113 }
2114
2115 static void dupe_menu_select_dupes_set2_cb(GtkWidget *widget, gpointer data)
2116 {
2117         DupeWindow *dw = data;
2118
2119         dupe_listview_select_dupes(dw, FALSE);
2120 }
2121
2122 static void dupe_menu_edit_cb(GtkWidget *widget, gpointer data)
2123 {
2124         DupeWindow *dw;
2125         const gchar *key = data;
2126
2127         dw = submenu_item_get_data(widget);
2128         if (!dw) return;
2129
2130         dupe_window_edit_selected(dw, key);
2131 }
2132
2133 static void dupe_menu_collection_cb(GtkWidget *widget, gpointer data)
2134 {
2135         DupeWindow *dw = data;
2136
2137         dupe_window_collection_from_selection(dw);
2138 }
2139
2140 static void dupe_menu_print_cb(GtkWidget *widget, gpointer data)
2141 {
2142         DupeWindow *dw = data;
2143         FileData *fd;
2144
2145         fd = (dw->click_item) ? dw->click_item->fd : NULL;
2146
2147         print_window_new(fd,
2148                          dupe_listview_get_selection(dw, dw->listview),
2149                          dupe_listview_get_filelist(dw, dw->listview), dw->window);
2150 }
2151
2152 static void dupe_menu_copy_cb(GtkWidget *widget, gpointer data)
2153 {
2154         DupeWindow *dw = data;
2155
2156         file_util_copy(NULL, dupe_listview_get_selection(dw, dw->listview), NULL, dw->window);
2157 }
2158
2159 static void dupe_menu_move_cb(GtkWidget *widget, gpointer data)
2160 {
2161         DupeWindow *dw = data;
2162
2163         file_util_move(NULL, dupe_listview_get_selection(dw, dw->listview), NULL, dw->window);
2164 }
2165
2166 static void dupe_menu_rename_cb(GtkWidget *widget, gpointer data)
2167 {
2168         DupeWindow *dw = data;
2169
2170         file_util_rename(NULL, dupe_listview_get_selection(dw, dw->listview), dw->window);
2171 }
2172
2173 static void dupe_menu_delete_cb(GtkWidget *widget, gpointer data)
2174 {
2175         DupeWindow *dw = data;
2176
2177         file_util_delete(NULL, dupe_listview_get_selection(dw, dw->listview), dw->window);
2178 }
2179
2180 static void dupe_menu_copy_path_cb(GtkWidget *widget, gpointer data)
2181 {
2182         DupeWindow *dw = data;
2183
2184         file_util_copy_path_list_to_clipboard(dupe_listview_get_selection(dw, dw->listview));
2185 }
2186
2187 static void dupe_menu_remove_cb(GtkWidget *widget, gpointer data)
2188 {
2189         DupeWindow *dw = data;
2190
2191         dupe_window_remove_selection(dw, dw->listview);
2192 }
2193
2194 static void dupe_menu_clear_cb(GtkWidget *widget, gpointer data)
2195 {
2196         DupeWindow *dw = data;
2197
2198         dupe_window_clear(dw);
2199 }
2200
2201 static void dupe_menu_close_cb(GtkWidget *widget, gpointer data)
2202 {
2203         DupeWindow *dw = data;
2204
2205         dupe_window_close(dw);
2206 }
2207
2208 static void dupe_menu_popup_destroy_cb(GtkWidget *widget, gpointer data)
2209 {
2210         GList *editmenu_fd_list = data;
2211
2212         filelist_free(editmenu_fd_list);
2213 }       
2214
2215 static GList *dupe_window_get_fd_list(DupeWindow *dw)
2216 {
2217         GList *list;
2218
2219 #if GTK_CHECK_VERSION(2,20,0)
2220         if (gtk_widget_has_focus(dw->second_listview))
2221 #else
2222         if (GTK_WIDGET_HAS_FOCUS(dw->second_listview))
2223 #endif
2224                 {
2225                 list = dupe_listview_get_selection(dw, dw->second_listview);
2226                 }
2227         else
2228                 {
2229                 list = dupe_listview_get_selection(dw, dw->listview);
2230                 }
2231
2232         return list;
2233 }
2234
2235 static GtkWidget *dupe_menu_popup_main(DupeWindow *dw, DupeItem *di)
2236 {
2237         GtkWidget *menu;
2238         GtkWidget *item;
2239         gint on_row;
2240         GList *editmenu_fd_list;
2241
2242         on_row = (di != NULL);
2243
2244         menu = popup_menu_short_lived();
2245
2246         menu_item_add_sensitive(menu, _("_View"), on_row,
2247                                 G_CALLBACK(dupe_menu_view_cb), dw);
2248         menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, on_row,
2249                                 G_CALLBACK(dupe_menu_viewnew_cb), dw);
2250         menu_item_add_divider(menu);
2251         menu_item_add_sensitive(menu, _("Select all"), (dw->dupes != NULL),
2252                                 G_CALLBACK(dupe_menu_select_all_cb), dw);
2253         menu_item_add_sensitive(menu, _("Select none"), (dw->dupes != NULL),
2254                                 G_CALLBACK(dupe_menu_select_none_cb), dw);
2255         menu_item_add_sensitive(menu, _("Select group _1 duplicates"), (dw->dupes != NULL),
2256                                 G_CALLBACK(dupe_menu_select_dupes_set1_cb), dw);
2257         menu_item_add_sensitive(menu, _("Select group _2 duplicates"), (dw->dupes != NULL),
2258                                 G_CALLBACK(dupe_menu_select_dupes_set2_cb), dw);
2259         menu_item_add_divider(menu);
2260         
2261         editmenu_fd_list = dupe_window_get_fd_list(dw);
2262         g_signal_connect(G_OBJECT(menu), "destroy",
2263                          G_CALLBACK(dupe_menu_popup_destroy_cb), editmenu_fd_list);
2264         submenu_add_edit(menu, &item, G_CALLBACK(dupe_menu_edit_cb), dw, editmenu_fd_list);
2265         if (!on_row) gtk_widget_set_sensitive(item, FALSE);
2266         menu_item_add_stock_sensitive(menu, _("Add to new collection"), GTK_STOCK_INDEX, on_row,
2267                                 G_CALLBACK(dupe_menu_collection_cb), dw);
2268         menu_item_add_stock_sensitive(menu, _("Print..."), GTK_STOCK_PRINT, on_row,
2269                                 G_CALLBACK(dupe_menu_print_cb), dw);
2270         menu_item_add_divider(menu);
2271         menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, on_row,
2272                                 G_CALLBACK(dupe_menu_copy_cb), dw);
2273         menu_item_add_sensitive(menu, _("_Move..."), on_row,
2274                                 G_CALLBACK(dupe_menu_move_cb), dw);
2275         menu_item_add_sensitive(menu, _("_Rename..."), on_row,
2276                                 G_CALLBACK(dupe_menu_rename_cb), dw);
2277         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, on_row,
2278                                 G_CALLBACK(dupe_menu_delete_cb), dw);
2279         menu_item_add_sensitive(menu, _("_Copy path"), on_row,
2280                                 G_CALLBACK(dupe_menu_copy_path_cb), dw);
2281         menu_item_add_divider(menu);
2282         menu_item_add_stock_sensitive(menu, _("Rem_ove"), GTK_STOCK_REMOVE, on_row,
2283                                 G_CALLBACK(dupe_menu_remove_cb), dw);
2284         menu_item_add_stock_sensitive(menu, _("C_lear"), GTK_STOCK_CLEAR, (dw->list != NULL),
2285                                 G_CALLBACK(dupe_menu_clear_cb), dw);
2286         menu_item_add_divider(menu);
2287         menu_item_add_stock(menu, _("Close _window"), GTK_STOCK_CLOSE,
2288                             G_CALLBACK(dupe_menu_close_cb), dw);
2289
2290         return menu;
2291 }
2292
2293 static gboolean dupe_listview_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
2294 {
2295         DupeWindow *dw = data;
2296         GtkTreeModel *store;
2297         GtkTreePath *tpath;
2298         GtkTreeIter iter;
2299         DupeItem *di = NULL;
2300
2301         store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
2302
2303         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
2304                                           &tpath, NULL, NULL, NULL))
2305                 {
2306                 gtk_tree_model_get_iter(store, &iter, tpath);
2307                 gtk_tree_model_get(store, &iter, DUPE_COLUMN_POINTER, &di, -1);
2308                 gtk_tree_path_free(tpath);
2309                 }
2310
2311         dw->click_item = di;
2312
2313         if (bevent->button == MOUSE_BUTTON_RIGHT)
2314                 {
2315                 /* right click menu */
2316                 GtkWidget *menu;
2317
2318                 if (bevent->state & GDK_CONTROL_MASK && bevent->state & GDK_SHIFT_MASK)
2319                         {
2320                         dupe_display_stats(dw, di);
2321                         return TRUE;
2322                         }
2323                 if (widget == dw->listview)
2324                         {
2325                         menu = dupe_menu_popup_main(dw, di);
2326                         }
2327                 else
2328                         {
2329                         menu = dupe_menu_popup_second(dw, di);
2330                         }
2331                 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, bevent->button, bevent->time);
2332                 }
2333
2334         if (!di) return FALSE;
2335
2336         if (bevent->button == MOUSE_BUTTON_LEFT &&
2337             bevent->type == GDK_2BUTTON_PRESS)
2338                 {
2339                 dupe_menu_view(dw, di, widget, FALSE);
2340                 }
2341
2342         if (bevent->button == MOUSE_BUTTON_MIDDLE) return TRUE;
2343
2344         if (bevent->button == MOUSE_BUTTON_RIGHT)
2345                 {
2346                 if (!dupe_listview_item_is_selected(dw, di, widget))
2347                         {
2348                         GtkTreeSelection *selection;
2349
2350                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
2351                         gtk_tree_selection_unselect_all(selection);
2352                         gtk_tree_selection_select_iter(selection, &iter);
2353
2354                         tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
2355                         gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tpath, NULL, FALSE);
2356                         gtk_tree_path_free(tpath);
2357                         }
2358
2359                 return TRUE;
2360                 }
2361
2362         if (bevent->button == MOUSE_BUTTON_LEFT &&
2363             bevent->type == GDK_BUTTON_PRESS &&
2364             !(bevent->state & GDK_SHIFT_MASK ) &&
2365             !(bevent->state & GDK_CONTROL_MASK ) &&
2366             dupe_listview_item_is_selected(dw, di, widget))
2367                 {
2368                 /* this selection handled on release_cb */
2369                 gtk_widget_grab_focus(widget);
2370                 return TRUE;
2371                 }
2372
2373         return FALSE;
2374 }
2375
2376 static gboolean dupe_listview_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
2377 {
2378         DupeWindow *dw = data;
2379         GtkTreeModel *store;
2380         GtkTreePath *tpath;
2381         GtkTreeIter iter;
2382         DupeItem *di = NULL;
2383
2384         if (bevent->button != MOUSE_BUTTON_LEFT && bevent->button != MOUSE_BUTTON_MIDDLE) return TRUE;
2385
2386         store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
2387
2388         if ((bevent->x != 0 || bevent->y != 0) &&
2389             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
2390                                           &tpath, NULL, NULL, NULL))
2391                 {
2392                 gtk_tree_model_get_iter(store, &iter, tpath);
2393                 gtk_tree_model_get(store, &iter, DUPE_COLUMN_POINTER, &di, -1);
2394                 gtk_tree_path_free(tpath);
2395                 }
2396
2397         if (bevent->button == MOUSE_BUTTON_MIDDLE)
2398                 {
2399                 if (di && dw->click_item == di)
2400                         {
2401                         GtkTreeSelection *selection;
2402
2403                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
2404                         if (dupe_listview_item_is_selected(dw, di, widget))
2405                                 {
2406                                 gtk_tree_selection_unselect_iter(selection, &iter);
2407                                 }
2408                         else
2409                                 {
2410                                 gtk_tree_selection_select_iter(selection, &iter);
2411                                 }
2412                         }
2413                 return TRUE;
2414                 }
2415
2416         if (di && dw->click_item == di &&
2417             !(bevent->state & GDK_SHIFT_MASK ) &&
2418             !(bevent->state & GDK_CONTROL_MASK ) &&
2419             dupe_listview_item_is_selected(dw, di, widget))
2420                 {
2421                 GtkTreeSelection *selection;
2422
2423                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
2424                 gtk_tree_selection_unselect_all(selection);
2425                 gtk_tree_selection_select_iter(selection, &iter);
2426
2427                 tpath = gtk_tree_model_get_path(store, &iter);
2428                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tpath, NULL, FALSE);
2429                 gtk_tree_path_free(tpath);
2430
2431                 return TRUE;
2432                 }
2433
2434         return FALSE;
2435 }
2436
2437 /*
2438  *-------------------------------------------------------------------
2439  * second set stuff
2440  *-------------------------------------------------------------------
2441  */
2442
2443 static void dupe_second_update_status(DupeWindow *dw)
2444 {
2445         gchar *buf;
2446
2447         buf = g_strdup_printf(_("%d files (set 2)"), g_list_length(dw->second_list));
2448         gtk_label_set_text(GTK_LABEL(dw->second_status_label), buf);
2449         g_free(buf);
2450 }
2451
2452 static void dupe_second_add(DupeWindow *dw, DupeItem *di)
2453 {
2454         GtkListStore *store;
2455         GtkTreeIter iter;
2456
2457         if (!di) return;
2458
2459         di->second = TRUE;
2460         dw->second_list = g_list_prepend(dw->second_list, di);
2461
2462         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(dw->second_listview)));
2463         gtk_list_store_append(store, &iter);
2464         gtk_list_store_set(store, &iter, DUPE_COLUMN_POINTER, di, 1, di->fd->path, -1);
2465
2466         dupe_second_update_status(dw);
2467 }
2468
2469 static void dupe_second_remove(DupeWindow *dw, DupeItem *di)
2470 {
2471         GtkListStore *store;
2472         GtkTreeIter iter;
2473
2474         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(dw->second_listview)));
2475         if (dupe_listview_find_item(store, di, &iter) >= 0)
2476                 {
2477                 tree_view_move_cursor_away(GTK_TREE_VIEW(dw->second_listview), &iter, TRUE);
2478                 gtk_list_store_remove(store, &iter);
2479                 }
2480
2481         dw->second_list = g_list_remove(dw->second_list, di);
2482
2483         dupe_second_update_status(dw);
2484 }
2485
2486 static void dupe_second_clear(DupeWindow *dw)
2487 {
2488         GtkListStore *store;
2489
2490         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(dw->second_listview)));
2491         gtk_list_store_clear(store);
2492         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(dw->second_listview));
2493
2494         g_list_free(dw->dupes);
2495         dw->dupes = NULL;
2496
2497         dupe_list_free(dw->second_list);
2498         dw->second_list = NULL;
2499
2500         dupe_match_reset_list(dw->list);
2501
2502         dupe_second_update_status(dw);
2503 }
2504
2505 static void dupe_second_menu_view_cb(GtkWidget *widget, gpointer data)
2506 {
2507         DupeWindow *dw = data;
2508
2509         if (dw->click_item) dupe_menu_view(dw, dw->click_item, dw->second_listview, FALSE);
2510 }
2511
2512 static void dupe_second_menu_viewnew_cb(GtkWidget *widget, gpointer data)
2513 {
2514         DupeWindow *dw = data;
2515
2516         if (dw->click_item) dupe_menu_view(dw, dw->click_item, dw->second_listview, TRUE);
2517 }
2518
2519 static void dupe_second_menu_select_all_cb(GtkWidget *widget, gpointer data)
2520 {
2521         GtkTreeSelection *selection;
2522         DupeWindow *dw = data;
2523
2524         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dw->second_listview));
2525         gtk_tree_selection_select_all(selection);
2526 }
2527
2528 static void dupe_second_menu_select_none_cb(GtkWidget *widget, gpointer data)
2529 {
2530         GtkTreeSelection *selection;
2531         DupeWindow *dw = data;
2532
2533         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dw->second_listview));
2534         gtk_tree_selection_unselect_all(selection);
2535 }
2536
2537 static void dupe_second_menu_remove_cb(GtkWidget *widget, gpointer data)
2538 {
2539         DupeWindow *dw = data;
2540
2541         dupe_window_remove_selection(dw, dw->second_listview);
2542 }
2543
2544 static void dupe_second_menu_clear_cb(GtkWidget *widget, gpointer data)
2545 {
2546         DupeWindow *dw = data;
2547
2548         dupe_second_clear(dw);
2549         dupe_window_recompare(dw);
2550 }
2551
2552 static GtkWidget *dupe_menu_popup_second(DupeWindow *dw, DupeItem *di)
2553 {
2554         GtkWidget *menu;
2555         gboolean notempty = (dw->second_list != NULL);
2556         gboolean on_row = (di != NULL);
2557
2558         menu = popup_menu_short_lived();
2559         menu_item_add_sensitive(menu, _("_View"), on_row,
2560                                 G_CALLBACK(dupe_second_menu_view_cb), dw);
2561         menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, on_row,
2562                                 G_CALLBACK(dupe_second_menu_viewnew_cb), dw);
2563         menu_item_add_divider(menu);
2564         menu_item_add_sensitive(menu, _("Select all"), notempty,
2565                                 G_CALLBACK(dupe_second_menu_select_all_cb), dw);
2566         menu_item_add_sensitive(menu, _("Select none"), notempty,
2567                                 G_CALLBACK(dupe_second_menu_select_none_cb), dw);
2568         menu_item_add_divider(menu);
2569         menu_item_add_stock_sensitive(menu, _("Rem_ove"), GTK_STOCK_REMOVE, on_row,
2570                                       G_CALLBACK(dupe_second_menu_remove_cb), dw);
2571         menu_item_add_stock_sensitive(menu, _("C_lear"), GTK_STOCK_CLEAR, notempty,
2572                                    G_CALLBACK(dupe_second_menu_clear_cb), dw);
2573         menu_item_add_divider(menu);
2574         menu_item_add_stock(menu, _("Close _window"), GTK_STOCK_CLOSE,
2575                             G_CALLBACK(dupe_menu_close_cb), dw);
2576
2577         return menu;
2578 }
2579
2580 static void dupe_second_set_toggle_cb(GtkWidget *widget, gpointer data)
2581 {
2582         DupeWindow *dw = data;
2583
2584         dw->second_set = GTK_TOGGLE_BUTTON(widget)->active;
2585
2586         if (dw->second_set)
2587                 {
2588                 dupe_second_update_status(dw);
2589                 gtk_table_set_col_spacings(GTK_TABLE(dw->table), PREF_PAD_GAP);
2590                 gtk_widget_show(dw->second_vbox);
2591                 }
2592         else
2593                 {
2594                 gtk_table_set_col_spacings(GTK_TABLE(dw->table), 0);
2595                 gtk_widget_hide(dw->second_vbox);
2596                 dupe_second_clear(dw);
2597                 }
2598
2599         dupe_window_recompare(dw);
2600 }
2601
2602 /*
2603  *-------------------------------------------------------------------
2604  * match type menu
2605  *-------------------------------------------------------------------
2606  */
2607
2608 enum {
2609         DUPE_MENU_COLUMN_NAME = 0,
2610         DUPE_MENU_COLUMN_MASK
2611 };
2612
2613 static void dupe_menu_type_cb(GtkWidget *combo, gpointer data)
2614 {
2615         DupeWindow *dw = data;
2616         GtkTreeModel *store;
2617         GtkTreeIter iter;
2618
2619         store = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
2620         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) return;
2621         gtk_tree_model_get(store, &iter, DUPE_MENU_COLUMN_MASK, &dw->match_mask, -1);
2622
2623         dupe_window_recompare(dw);
2624 }
2625
2626 static void dupe_menu_add_item(GtkListStore *store, const gchar *text, DupeMatchType type, DupeWindow *dw)
2627 {
2628         GtkTreeIter iter;
2629
2630         gtk_list_store_append(store, &iter);
2631         gtk_list_store_set(store, &iter, DUPE_MENU_COLUMN_NAME, text,
2632                                          DUPE_MENU_COLUMN_MASK, type, -1);
2633
2634         if (dw->match_mask == type) gtk_combo_box_set_active_iter(GTK_COMBO_BOX(dw->combo), &iter);
2635 }
2636
2637 static void dupe_menu_setup(DupeWindow *dw)
2638 {
2639         GtkListStore *store;
2640         GtkCellRenderer *renderer;
2641
2642         store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
2643         dw->combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
2644         g_object_unref(store);
2645
2646         renderer = gtk_cell_renderer_text_new();
2647         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dw->combo), renderer, TRUE);
2648         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(dw->combo), renderer,
2649                                        "text", DUPE_MENU_COLUMN_NAME, NULL);
2650
2651         dupe_menu_add_item(store, _("Name"), DUPE_MATCH_NAME, dw);
2652         dupe_menu_add_item(store, _("Name case-insensitive"), DUPE_MATCH_NAME_CI, dw);
2653         dupe_menu_add_item(store, _("Size"), DUPE_MATCH_SIZE, dw);
2654         dupe_menu_add_item(store, _("Date"), DUPE_MATCH_DATE, dw);
2655         dupe_menu_add_item(store, _("Dimensions"), DUPE_MATCH_DIM, dw);
2656         dupe_menu_add_item(store, _("Checksum"), DUPE_MATCH_SUM, dw);
2657         dupe_menu_add_item(store, _("Path"), DUPE_MATCH_PATH, dw);
2658         dupe_menu_add_item(store, _("Similarity (high)"), DUPE_MATCH_SIM_HIGH, dw);
2659         dupe_menu_add_item(store, _("Similarity"), DUPE_MATCH_SIM_MED, dw);
2660         dupe_menu_add_item(store, _("Similarity (low)"), DUPE_MATCH_SIM_LOW, dw);
2661         dupe_menu_add_item(store, _("Similarity (custom)"), DUPE_MATCH_SIM_CUSTOM, dw);
2662
2663         g_signal_connect(G_OBJECT(dw->combo), "changed",
2664                          G_CALLBACK(dupe_menu_type_cb), dw);
2665 }
2666
2667 /*
2668  *-------------------------------------------------------------------
2669  * list view columns
2670  *-------------------------------------------------------------------
2671  */
2672
2673 /* this overrides the low default of a GtkCellRenderer from 100 to CELL_HEIGHT_OVERRIDE, something sane for our purposes */
2674
2675 #define CELL_HEIGHT_OVERRIDE 512
2676
2677 void cell_renderer_height_override(GtkCellRenderer *renderer)
2678 {
2679         GParamSpec *spec;
2680
2681         spec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(renderer)), "height");
2682         if (spec && G_IS_PARAM_SPEC_INT(spec))
2683                 {
2684                 GParamSpecInt *spec_int;
2685
2686                 spec_int = G_PARAM_SPEC_INT(spec);
2687                 if (spec_int->maximum < CELL_HEIGHT_OVERRIDE) spec_int->maximum = CELL_HEIGHT_OVERRIDE;
2688                 }
2689 }
2690
2691 static GdkColor *dupe_listview_color_shifted(GtkWidget *widget)
2692 {
2693         static GdkColor color;
2694         static GtkWidget *done = NULL;
2695
2696         if (done != widget)
2697                 {
2698                 GtkStyle *style;
2699
2700                 style = gtk_widget_get_style(widget);
2701                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
2702                 shift_color(&color, -1, 0);
2703                 done = widget;
2704                 }
2705
2706         return &color;
2707 }
2708
2709 static void dupe_listview_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
2710                                    GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
2711 {
2712         DupeWindow *dw = data;
2713         gboolean set;
2714
2715         gtk_tree_model_get(tree_model, iter, DUPE_COLUMN_COLOR, &set, -1);
2716         g_object_set(G_OBJECT(cell),
2717                      "cell-background-gdk", dupe_listview_color_shifted(dw->listview),
2718                      "cell-background-set", set, NULL);
2719 }
2720
2721 static void dupe_listview_add_column(DupeWindow *dw, GtkWidget *listview, gint n, const gchar *title, gboolean image, gboolean right_justify)
2722 {
2723         GtkTreeViewColumn *column;
2724         GtkCellRenderer *renderer;
2725
2726         column = gtk_tree_view_column_new();
2727         gtk_tree_view_column_set_title(column, title);
2728         gtk_tree_view_column_set_min_width(column, 4);
2729
2730         if (n != DUPE_COLUMN_RANK &&
2731             n != DUPE_COLUMN_THUMB)
2732                 {
2733                 gtk_tree_view_column_set_resizable(column, TRUE);
2734                 }
2735
2736         if (!image)
2737                 {
2738                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
2739                 renderer = gtk_cell_renderer_text_new();
2740                 if (right_justify)
2741                         {
2742                         g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2743                         }
2744                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2745                 gtk_tree_view_column_add_attribute(column, renderer, "text", n);
2746                 }
2747         else
2748                 {
2749                 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
2750                 renderer = gtk_cell_renderer_pixbuf_new();
2751                 cell_renderer_height_override(renderer);
2752                 gtk_tree_view_column_pack_start(column, renderer, TRUE);
2753                 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", n);
2754                 }
2755
2756         if (listview == dw->listview)
2757                 {
2758                 /* sets background before rendering */
2759                 gtk_tree_view_column_set_cell_data_func(column, renderer, dupe_listview_color_cb, dw, NULL);
2760                 }
2761
2762         gtk_tree_view_append_column(GTK_TREE_VIEW(listview), column);
2763 }
2764
2765 static void dupe_listview_set_height(GtkWidget *listview, gboolean thumb)
2766 {
2767         GtkTreeViewColumn *column;
2768         GtkCellRenderer *cell;
2769         GList *list;
2770
2771         column = gtk_tree_view_get_column(GTK_TREE_VIEW(listview), DUPE_COLUMN_THUMB - 1);
2772         if (!column) return;
2773
2774         gtk_tree_view_column_set_fixed_width(column, (thumb) ? options->thumbnails.max_width : 4);
2775
2776 #if GTK_CHECK_VERSION(2,18,0)
2777         list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
2778 #else
2779         list = gtk_tree_view_column_get_cell_renderers(column);
2780 #endif
2781         if (!list) return;
2782         cell = list->data;
2783         g_list_free(list);
2784
2785         g_object_set(G_OBJECT(cell), "height", (thumb) ? options->thumbnails.max_height : -1, NULL);
2786         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(listview));
2787 }
2788
2789
2790 /*
2791  *-------------------------------------------------------------------
2792  * misc cb
2793  *-------------------------------------------------------------------
2794  */
2795
2796 static void dupe_window_show_thumb_cb(GtkWidget *widget, gpointer data)
2797 {
2798         DupeWindow *dw = data;
2799
2800         dw->show_thumbs = GTK_TOGGLE_BUTTON(widget)->active;
2801
2802         if (dw->show_thumbs)
2803                 {
2804                 if (!dw->working) dupe_thumb_step(dw);
2805                 }
2806         else
2807                 {
2808                 GtkTreeModel *store;
2809                 GtkTreeIter iter;
2810                 gboolean valid;
2811
2812                 thumb_loader_free(dw->thumb_loader);
2813                 dw->thumb_loader = NULL;
2814
2815                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(dw->listview));
2816                 valid = gtk_tree_model_get_iter_first(store, &iter);
2817
2818                 while (valid)
2819                         {
2820                         gtk_list_store_set(GTK_LIST_STORE(store), &iter, DUPE_COLUMN_THUMB, NULL, -1);
2821                         valid = gtk_tree_model_iter_next(store, &iter);
2822                         }
2823                 dupe_window_update_progress(dw, NULL, 0.0, FALSE);
2824                 }
2825
2826         dupe_listview_set_height(dw->listview, dw->show_thumbs);
2827 }
2828
2829 static void dupe_popup_menu_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
2830 {
2831         GtkWidget *view = data;
2832         GtkTreePath *tpath;
2833         gint cx, cy, cw, ch;
2834         gint column;
2835
2836         gtk_tree_view_get_cursor(GTK_TREE_VIEW(view), &tpath, NULL);
2837         if (!tpath) return;
2838
2839         if (gtk_tree_view_get_column(GTK_TREE_VIEW(view), DUPE_COLUMN_NAME - 1) != NULL)
2840                 {
2841                 column = DUPE_COLUMN_NAME - 1;
2842                 }
2843         else
2844                 {
2845                 /* dw->second_listview */
2846                 column = 0;
2847                 }
2848         tree_view_get_cell_clamped(GTK_TREE_VIEW(view), tpath, column, TRUE, &cx, &cy, &cw, &ch);
2849         gtk_tree_path_free(tpath);
2850         cy += ch;
2851         popup_menu_position_clamp(menu, &cx, &cy, 0);
2852         *x = cx;
2853         *y = cy;
2854 }
2855
2856 static gboolean dupe_window_keypress_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
2857 {
2858         DupeWindow *dw = data;
2859         gboolean stop_signal = FALSE;
2860         gboolean on_second;
2861         GtkWidget *listview;
2862         GtkTreeModel *store;
2863         GtkTreeSelection *selection;
2864         GList *slist;
2865         DupeItem *di = NULL;
2866
2867 #if GTK_CHECK_VERSION(2,20,0)
2868         on_second = gtk_widget_has_focus(dw->second_listview);
2869 #else
2870         on_second = GTK_WIDGET_HAS_FOCUS(dw->second_listview);
2871 #endif
2872
2873         if (on_second)
2874                 {
2875                 listview = dw->second_listview;
2876                 }
2877         else
2878                 {
2879                 listview = dw->listview;
2880                 }
2881
2882         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(listview));
2883         slist = gtk_tree_selection_get_selected_rows(selection, &store);
2884         if (slist)
2885                 {
2886                 GtkTreePath *tpath;
2887                 GtkTreeIter iter;
2888                 GList *last;
2889
2890                 last = g_list_last(slist);
2891                 tpath = last->data;
2892
2893                 /* last is newest selected file */
2894                 gtk_tree_model_get_iter(store, &iter, tpath);
2895                 gtk_tree_model_get(store, &iter, DUPE_COLUMN_POINTER, &di, -1);
2896                 }
2897         g_list_foreach(slist, (GFunc)gtk_tree_path_free, NULL);
2898         g_list_free(slist);
2899
2900         if (event->state & GDK_CONTROL_MASK)
2901                 {
2902                 gint edit_val = -1;
2903
2904                 if (!on_second)
2905                         {
2906                         stop_signal = TRUE;
2907                         switch (event->keyval)
2908                                 {
2909                                 case '1':
2910                                         edit_val = 0;
2911                                         break;
2912                                 case '2':
2913                                         edit_val = 1;
2914                                         break;
2915                                 case '3':
2916                                         edit_val = 2;
2917                                         break;
2918                                 case '4':
2919                                         edit_val = 3;
2920                                         break;
2921                                 case '5':
2922                                         edit_val = 4;
2923                                         break;
2924                                 case '6':
2925                                         edit_val = 5;
2926                                         break;
2927                                 case '7':
2928                                         edit_val = 6;
2929                                         break;
2930                                 case '8':
2931                                         edit_val = 7;
2932                                         break;
2933                                 case '9':
2934                                         edit_val = 8;
2935                                         break;
2936                                 case '0':
2937                                         edit_val = 9;
2938                                         break;
2939                                 case 'C': case 'c':
2940                                         file_util_copy(NULL, dupe_listview_get_selection(dw, listview),
2941                                                        NULL, dw->window);
2942                                         break;
2943                                 case 'M': case 'm':
2944                                         file_util_move(NULL, dupe_listview_get_selection(dw, listview),
2945                                                        NULL, dw->window);
2946                                         break;
2947                                 case 'R': case 'r':
2948                                         file_util_rename(NULL, dupe_listview_get_selection(dw, listview), dw->window);
2949                                         break;
2950                                 case 'D': case 'd':
2951                                         file_util_delete(NULL, dupe_listview_get_selection(dw, listview), dw->window);
2952                                         break;
2953                                 default:
2954                                         stop_signal = FALSE;
2955                                         break;
2956                                 }
2957                         }
2958
2959                 if (!stop_signal)
2960                         {
2961                         stop_signal = TRUE;
2962                         switch (event->keyval)
2963                                 {
2964                                 case 'A': case 'a':
2965                                         if (event->state & GDK_SHIFT_MASK)
2966                                                 {
2967                                                 gtk_tree_selection_unselect_all(selection);
2968                                                 }
2969                                         else
2970                                                 {
2971                                                 gtk_tree_selection_select_all(selection);
2972                                                 }
2973                                         break;
2974                                 case GDK_Delete: case GDK_KP_Delete:
2975                                         if (on_second)
2976                                                 {
2977                                                 dupe_second_clear(dw);
2978                                                 dupe_window_recompare(dw);
2979                                                 }
2980                                         else
2981                                                 {
2982                                                 dupe_window_clear(dw);
2983                                                 }
2984                                         break;
2985                                 case 'L': case 'l':
2986                                         dupe_window_append_file_list(dw, FALSE);
2987                                         break;
2988                                 case 'T': case 't':
2989                                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dw->button_thumbs),
2990                                                 !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dw->button_thumbs)));
2991                                         break;
2992                                 case 'W': case 'w':
2993                                         dupe_window_close(dw);
2994                                         break;
2995                                 default:
2996                                         stop_signal = FALSE;
2997                                         break;
2998                                 }
2999                         }
3000 #if 0
3001                 if (edit_val >= 0)
3002                         {
3003                         dupe_window_edit_selected(dw, edit_val);
3004                         }
3005 #endif
3006                 }
3007         else
3008                 {
3009                 stop_signal = TRUE;
3010                 switch (event->keyval)
3011                         {
3012                         case GDK_Return: case GDK_KP_Enter:
3013                                 dupe_menu_view(dw, di, listview, FALSE);
3014                                 break;
3015                         case 'V': case 'v':
3016                                 dupe_menu_view(dw, di, listview, TRUE);
3017                                 break;
3018                         case GDK_Delete: case GDK_KP_Delete:
3019                                 dupe_window_remove_selection(dw, listview);
3020                                 break;
3021                         case 'C': case 'c':
3022                                 if (!on_second)
3023                                         {
3024                                         dupe_window_collection_from_selection(dw);
3025                                         }
3026                                 break;
3027                         case '1':
3028                                 dupe_listview_select_dupes(dw, TRUE);
3029                                 break;
3030                         case '2':
3031                                 dupe_listview_select_dupes(dw, FALSE);
3032                                 break;
3033                         case GDK_Menu:
3034                         case GDK_F10:
3035                                 if (!on_second)
3036                                         {
3037                                         GtkWidget *menu;
3038
3039                                         menu = dupe_menu_popup_main(dw, di);
3040                                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
3041                                                        dupe_popup_menu_pos_cb, listview, 0, GDK_CURRENT_TIME);
3042                                         }
3043                                 else
3044                                         {
3045                                         GtkWidget *menu;
3046
3047                                         menu = dupe_menu_popup_second(dw, di);
3048                                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
3049                                                        dupe_popup_menu_pos_cb, listview, 0, GDK_CURRENT_TIME);
3050                                         }
3051                                 break;
3052                         default:
3053                                 stop_signal = FALSE;
3054                                 break;
3055                         }
3056                 }
3057
3058         return stop_signal;
3059 }
3060
3061
3062 void dupe_window_clear(DupeWindow *dw)
3063 {
3064         GtkListStore *store;
3065
3066         dupe_check_stop(dw);
3067
3068         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(dw->listview)));
3069         gtk_list_store_clear(store);
3070         gtk_tree_view_columns_autosize(GTK_TREE_VIEW(dw->listview));
3071
3072         g_list_free(dw->dupes);
3073         dw->dupes = NULL;
3074
3075         dupe_list_free(dw->list);
3076         dw->list = NULL;
3077
3078         dupe_match_reset_list(dw->second_list);
3079
3080         dupe_window_update_count(dw, FALSE);
3081         dupe_window_update_progress(dw, NULL, 0.0, FALSE);
3082 }
3083
3084 void dupe_window_close(DupeWindow *dw)
3085 {
3086         dupe_check_stop(dw);
3087
3088         dupe_window_list = g_list_remove(dupe_window_list, dw);
3089         gtk_widget_destroy(dw->window);
3090
3091         g_list_free(dw->dupes);
3092         dupe_list_free(dw->list);
3093
3094         dupe_list_free(dw->second_list);
3095
3096         file_data_unregister_notify_func(dupe_notify_cb, dw);
3097
3098         g_free(dw);
3099 }
3100
3101 static gint dupe_window_delete(GtkWidget *widget, GdkEvent *event, gpointer data)
3102 {
3103         DupeWindow *dw = data;
3104         dupe_window_close(dw);
3105
3106         return TRUE;
3107 }
3108
3109 /* collection and files can be NULL */
3110 DupeWindow *dupe_window_new(DupeMatchType match_mask)
3111 {
3112         DupeWindow *dw;
3113         GtkWidget *vbox;
3114         GtkWidget *scrolled;
3115         GtkWidget *frame;
3116         GtkWidget *status_box;
3117         GtkWidget *label;
3118         GtkWidget *button;
3119         GtkListStore *store;
3120         GtkTreeSelection *selection;
3121         GdkGeometry geometry;
3122
3123         dw = g_new0(DupeWindow, 1);
3124
3125         dw->match_mask = match_mask;
3126
3127         dw->window = window_new(GTK_WINDOW_TOPLEVEL, "dupe", NULL, NULL, _("Find duplicates"));
3128
3129         geometry.min_width = DEFAULT_MINIMAL_WINDOW_SIZE;
3130         geometry.min_height = DEFAULT_MINIMAL_WINDOW_SIZE;
3131         geometry.base_width = DUPE_DEF_WIDTH;
3132         geometry.base_height = DUPE_DEF_HEIGHT;
3133         gtk_window_set_geometry_hints(GTK_WINDOW(dw->window), NULL, &geometry,
3134                                       GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE);
3135
3136         gtk_window_set_default_size(GTK_WINDOW(dw->window), DUPE_DEF_WIDTH, DUPE_DEF_HEIGHT);
3137
3138         gtk_window_set_resizable(GTK_WINDOW(dw->window), TRUE);
3139         gtk_container_set_border_width(GTK_CONTAINER(dw->window), 0);
3140
3141         g_signal_connect(G_OBJECT(dw->window), "delete_event",
3142                          G_CALLBACK(dupe_window_delete), dw);
3143         g_signal_connect(G_OBJECT(dw->window), "key_press_event",
3144                          G_CALLBACK(dupe_window_keypress_cb), dw);
3145
3146         vbox = gtk_vbox_new(FALSE, 0);
3147         gtk_container_add(GTK_CONTAINER(dw->window), vbox);
3148         gtk_widget_show(vbox);
3149
3150         dw->table = gtk_table_new(1, 3, FALSE);
3151         gtk_box_pack_start(GTK_BOX(vbox), dw->table, TRUE, TRUE, 0);
3152         gtk_widget_show(dw->table);
3153
3154         scrolled = gtk_scrolled_window_new(NULL, NULL);
3155         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
3156         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
3157         gtk_table_attach_defaults(GTK_TABLE(dw->table), scrolled, 0, 2, 0, 1);
3158         gtk_widget_show(scrolled);
3159
3160         store = gtk_list_store_new(9, G_TYPE_POINTER, G_TYPE_STRING, GDK_TYPE_PIXBUF,
3161                                    G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
3162                                    G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
3163         dw->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
3164         g_object_unref(store);
3165
3166         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dw->listview));
3167         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
3168         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(dw->listview), TRUE);
3169         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(dw->listview), FALSE);
3170
3171         dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_RANK, "", FALSE, TRUE);
3172         dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_THUMB, "", TRUE, FALSE);
3173         dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_NAME, _("Name"), FALSE, FALSE);
3174         dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_SIZE, _("Size"), FALSE, TRUE);
3175         dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_DATE, _("Date"), FALSE, TRUE);
3176         dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_DIMENSIONS, _("Dimensions"), FALSE, FALSE);
3177         dupe_listview_add_column(dw, dw->listview, DUPE_COLUMN_PATH, _("Path"), FALSE, FALSE);
3178
3179         gtk_container_add(GTK_CONTAINER(scrolled), dw->listview);
3180         gtk_widget_show(dw->listview);
3181
3182         dw->second_vbox = gtk_vbox_new(FALSE, 0);
3183         gtk_table_attach_defaults(GTK_TABLE(dw->table), dw->second_vbox, 2, 3, 0, 1);
3184         if (dw->second_set)
3185                 {
3186                 gtk_table_set_col_spacings(GTK_TABLE(dw->table), PREF_PAD_GAP);
3187                 gtk_widget_show(dw->second_vbox);
3188                 }
3189         else
3190                 {
3191                 gtk_table_set_col_spacings(GTK_TABLE(dw->table), 0);
3192                 }
3193
3194         scrolled = gtk_scrolled_window_new(NULL, NULL);
3195         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
3196         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
3197         gtk_box_pack_start(GTK_BOX(dw->second_vbox), scrolled, TRUE, TRUE, 0);
3198         gtk_widget_show(scrolled);
3199
3200         store = gtk_list_store_new(2, G_TYPE_POINTER, G_TYPE_STRING);
3201         dw->second_listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
3202
3203         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dw->second_listview));
3204         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
3205
3206         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(dw->second_listview), TRUE);
3207         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(dw->second_listview), FALSE);
3208
3209         dupe_listview_add_column(dw, dw->second_listview, 1, _("Compare to:"), FALSE, FALSE);
3210
3211         gtk_container_add(GTK_CONTAINER(scrolled), dw->second_listview);
3212         gtk_widget_show(dw->second_listview);
3213
3214         dw->second_status_label = gtk_label_new("");
3215         gtk_box_pack_start(GTK_BOX(dw->second_vbox), dw->second_status_label, FALSE, FALSE, 0);
3216         gtk_widget_show(dw->second_status_label);
3217
3218         pref_line(dw->second_vbox, GTK_ORIENTATION_HORIZONTAL);
3219
3220         status_box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
3221
3222         label = gtk_label_new(_("Compare by:"));
3223         gtk_box_pack_start(GTK_BOX(status_box), label, FALSE, FALSE, PREF_PAD_SPACE);
3224         gtk_widget_show(label);
3225
3226         dupe_menu_setup(dw);
3227         gtk_box_pack_start(GTK_BOX(status_box), dw->combo, FALSE, FALSE, 0);
3228         gtk_widget_show(dw->combo);
3229
3230         dw->button_thumbs = gtk_check_button_new_with_label(_("Thumbnails"));
3231         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dw->button_thumbs), dw->show_thumbs);
3232         g_signal_connect(G_OBJECT(dw->button_thumbs), "toggled",
3233                          G_CALLBACK(dupe_window_show_thumb_cb), dw);
3234         gtk_box_pack_start(GTK_BOX(status_box), dw->button_thumbs, FALSE, FALSE, PREF_PAD_SPACE);
3235         gtk_widget_show(dw->button_thumbs);
3236
3237         button = gtk_check_button_new_with_label(_("Compare two file sets"));
3238         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), dw->second_set);
3239         g_signal_connect(G_OBJECT(button), "toggled",
3240                          G_CALLBACK(dupe_second_set_toggle_cb), dw);
3241         gtk_box_pack_end(GTK_BOX(status_box), button, FALSE, FALSE, PREF_PAD_SPACE);
3242         gtk_widget_show(button);
3243
3244         status_box = gtk_hbox_new(FALSE, 0);
3245         gtk_box_pack_start(GTK_BOX(vbox), status_box, FALSE, FALSE, 0);
3246         gtk_widget_show(status_box);
3247
3248         frame = gtk_frame_new(NULL);
3249         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
3250         gtk_box_pack_start(GTK_BOX(status_box), frame, TRUE, TRUE, 0);
3251         gtk_widget_show(frame);
3252
3253         dw->status_label = gtk_label_new("");
3254         gtk_container_add(GTK_CONTAINER(frame), dw->status_label);
3255         gtk_widget_show(dw->status_label);
3256
3257         dw->extra_label = gtk_progress_bar_new();
3258         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dw->extra_label), 0.0);
3259         gtk_box_pack_end(GTK_BOX(status_box), dw->extra_label, FALSE, FALSE, 0);
3260         gtk_widget_show(dw->extra_label);
3261
3262         dupe_dnd_init(dw);
3263
3264         /* order is important here, dnd_init should be seeing mouse
3265          * presses before we possibly handle (and stop) the signal
3266          */
3267         g_signal_connect(G_OBJECT(dw->listview), "button_press_event",
3268                          G_CALLBACK(dupe_listview_press_cb), dw);
3269         g_signal_connect(G_OBJECT(dw->listview), "button_release_event",
3270                          G_CALLBACK(dupe_listview_release_cb), dw);
3271         g_signal_connect(G_OBJECT(dw->second_listview), "button_press_event",
3272                          G_CALLBACK(dupe_listview_press_cb), dw);
3273         g_signal_connect(G_OBJECT(dw->second_listview), "button_release_event",
3274                          G_CALLBACK(dupe_listview_release_cb), dw);
3275
3276         gtk_widget_show(dw->window);
3277
3278         dupe_window_update_count(dw, TRUE);
3279         dupe_window_update_progress(dw, NULL, 0.0, FALSE);
3280
3281         dupe_window_list = g_list_append(dupe_window_list, dw);
3282
3283         file_data_register_notify_func(dupe_notify_cb, dw, NOTIFY_PRIORITY_MEDIUM);
3284
3285         return dw;
3286 }
3287
3288 /*
3289  *-------------------------------------------------------------------
3290  * dnd confirm dir
3291  *-------------------------------------------------------------------
3292  */
3293
3294 typedef struct {
3295         DupeWindow *dw;
3296         GList *list;
3297 } CDupeConfirmD;
3298
3299 static void confirm_dir_list_cancel(GtkWidget *widget, gpointer data)
3300 {
3301         /* do nothing */
3302 }
3303
3304 static void confirm_dir_list_add(GtkWidget *widget, gpointer data)
3305 {
3306         CDupeConfirmD *d = data;
3307         GList *work;
3308
3309         dupe_window_add_files(d->dw, d->list, FALSE);
3310
3311         work = d->list;
3312         while (work)
3313                 {
3314                 FileData *fd = work->data;
3315                 work = work->next;
3316                 if (isdir(fd->path))
3317                         {
3318                         GList *list;
3319
3320                         filelist_read(fd, &list, NULL);
3321                         list = filelist_filter(list, FALSE);
3322                         if (list)
3323                                 {
3324                                 dupe_window_add_files(d->dw, list, FALSE);
3325                                 filelist_free(list);
3326                                 }
3327                         }
3328                 }
3329 }
3330
3331 static void confirm_dir_list_recurse(GtkWidget *widget, gpointer data)
3332 {
3333         CDupeConfirmD *d = data;
3334         dupe_window_add_files(d->dw, d->list, TRUE);
3335 }
3336
3337 static void confirm_dir_list_skip(GtkWidget *widget, gpointer data)
3338 {
3339         CDupeConfirmD *d = data;
3340         dupe_window_add_files(d->dw, d->list, FALSE);
3341 }
3342
3343 static void confirm_dir_list_destroy(GtkWidget *widget, gpointer data)
3344 {
3345         CDupeConfirmD *d = data;
3346         filelist_free(d->list);
3347         g_free(d);
3348 }
3349
3350 static GtkWidget *dupe_confirm_dir_list(DupeWindow *dw, GList *list)
3351 {
3352         GtkWidget *menu;
3353         CDupeConfirmD *d;
3354
3355         d = g_new0(CDupeConfirmD, 1);
3356         d->dw = dw;
3357         d->list = list;
3358
3359         menu = popup_menu_short_lived();
3360         g_signal_connect(G_OBJECT(menu), "destroy",
3361                          G_CALLBACK(confirm_dir_list_destroy), d);
3362
3363         menu_item_add_stock(menu, _("Dropped list includes folders."), GTK_STOCK_DND_MULTIPLE, NULL, NULL);
3364         menu_item_add_divider(menu);
3365         menu_item_add_stock(menu, _("_Add contents"), GTK_STOCK_OK, G_CALLBACK(confirm_dir_list_add), d);
3366         menu_item_add_stock(menu, _("Add contents _recursive"), GTK_STOCK_ADD, G_CALLBACK(confirm_dir_list_recurse), d);
3367         menu_item_add_stock(menu, _("_Skip folders"), GTK_STOCK_REMOVE, G_CALLBACK(confirm_dir_list_skip), d);
3368         menu_item_add_divider(menu);
3369         menu_item_add_stock(menu, _("Cancel"), GTK_STOCK_CANCEL, G_CALLBACK(confirm_dir_list_cancel), d);
3370
3371         return menu;
3372 }
3373
3374 /*
3375  *-------------------------------------------------------------------
3376  * dnd
3377  *-------------------------------------------------------------------
3378  */
3379
3380 static GtkTargetEntry dupe_drag_types[] = {
3381         { "text/uri-list", 0, TARGET_URI_LIST },
3382         { "text/plain", 0, TARGET_TEXT_PLAIN }
3383 };
3384 static gint n_dupe_drag_types = 2;
3385
3386 static GtkTargetEntry dupe_drop_types[] = {
3387         { TARGET_APP_COLLECTION_MEMBER_STRING, 0, TARGET_APP_COLLECTION_MEMBER },
3388         { "text/uri-list", 0, TARGET_URI_LIST }
3389 };
3390 static gint n_dupe_drop_types = 2;
3391
3392 static void dupe_dnd_data_set(GtkWidget *widget, GdkDragContext *context,
3393                               GtkSelectionData *selection_data, guint info,
3394                               guint time, gpointer data)
3395 {
3396         DupeWindow *dw = data;
3397         gchar *uri_text;
3398         gint length;
3399         GList *list;
3400
3401         switch (info)
3402                 {
3403                 case TARGET_URI_LIST:
3404                 case TARGET_TEXT_PLAIN:
3405                         list = dupe_listview_get_selection(dw, widget);
3406                         if (!list) return;
3407                         uri_text = uri_text_from_filelist(list, &length, (info == TARGET_TEXT_PLAIN));
3408                         filelist_free(list);
3409                         break;
3410                 default:
3411                         uri_text = NULL;
3412                         break;
3413                 }
3414
3415         if (uri_text) gtk_selection_data_set(selection_data, selection_data->target,
3416                                              8, (guchar *)uri_text, length);
3417         g_free(uri_text);
3418 }
3419
3420 static void dupe_dnd_data_get(GtkWidget *widget, GdkDragContext *context,
3421                               gint x, gint y,
3422                               GtkSelectionData *selection_data, guint info,
3423                               guint time, gpointer data)
3424 {
3425         DupeWindow *dw = data;
3426         GtkWidget *source;
3427         GList *list = NULL;
3428         GList *work;
3429
3430         source = gtk_drag_get_source_widget(context);
3431         if (source == dw->listview || source == dw->second_listview) return;
3432
3433         dw->second_drop = (dw->second_set && widget == dw->second_listview);
3434
3435         switch (info)
3436                 {
3437                 case TARGET_APP_COLLECTION_MEMBER:
3438                         collection_from_dnd_data((gchar *)selection_data->data, &list, NULL);
3439                         break;
3440                 case TARGET_URI_LIST:
3441                         list = uri_filelist_from_text((gchar *)selection_data->data, TRUE);
3442                         work = list;
3443                         while (work)
3444                                 {
3445                                 FileData *fd = work->data;
3446                                 if (isdir(fd->path))
3447                                         {
3448                                         GtkWidget *menu;
3449                                         menu = dupe_confirm_dir_list(dw, list);
3450                                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, time);
3451                                         return;
3452                                         }
3453                                 work = work->next;
3454                                 }
3455                         break;
3456                 default:
3457                         list = NULL;
3458                         break;
3459                 }
3460
3461         if (list)
3462                 {
3463                 dupe_window_add_files(dw, list, FALSE);
3464                 filelist_free(list);
3465                 }
3466 }
3467
3468 static void dupe_dest_set(GtkWidget *widget, gboolean enable)
3469 {
3470         if (enable)
3471                 {
3472                 gtk_drag_dest_set(widget,
3473                         GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
3474                         dupe_drop_types, n_dupe_drop_types,
3475                         GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK);
3476
3477                 }
3478         else
3479                 {
3480                 gtk_drag_dest_unset(widget);
3481                 }
3482 }
3483
3484 static void dupe_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
3485 {
3486         DupeWindow *dw = data;
3487         dupe_dest_set(dw->listview, FALSE);
3488         dupe_dest_set(dw->second_listview, FALSE);
3489
3490         if (dw->click_item && !dupe_listview_item_is_selected(dw, dw->click_item, widget))
3491                 {
3492                 GtkListStore *store;
3493                 GtkTreeIter iter;
3494
3495                 store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(widget)));
3496                 if (dupe_listview_find_item(store, dw->click_item, &iter) >= 0)
3497                         {
3498                         GtkTreeSelection *selection;
3499                         GtkTreePath *tpath;
3500
3501                         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
3502                         gtk_tree_selection_unselect_all(selection);
3503                         gtk_tree_selection_select_iter(selection, &iter);
3504
3505                         tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
3506                         gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tpath, NULL, FALSE);
3507                         gtk_tree_path_free(tpath);
3508                         }
3509                 }
3510
3511         if (dw->show_thumbs &&
3512             widget == dw->listview &&
3513             dw->click_item && dw->click_item->pixbuf)
3514                 {
3515                 GtkTreeSelection *selection;
3516                 gint items;
3517
3518                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
3519                 items = gtk_tree_selection_count_selected_rows(selection);
3520                 dnd_set_drag_icon(widget, context, dw->click_item->pixbuf, items);
3521                 }
3522 }
3523
3524 static void dupe_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
3525 {
3526         DupeWindow *dw = data;
3527         dupe_dest_set(dw->listview, TRUE);
3528         dupe_dest_set(dw->second_listview, TRUE);
3529 }
3530
3531 static void dupe_dnd_init(DupeWindow *dw)
3532 {
3533         gtk_drag_source_set(dw->listview, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
3534                             dupe_drag_types, n_dupe_drag_types,
3535                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
3536         g_signal_connect(G_OBJECT(dw->listview), "drag_data_get",
3537                          G_CALLBACK(dupe_dnd_data_set), dw);
3538         g_signal_connect(G_OBJECT(dw->listview), "drag_begin",
3539                          G_CALLBACK(dupe_dnd_begin), dw);
3540         g_signal_connect(G_OBJECT(dw->listview), "drag_end",
3541                          G_CALLBACK(dupe_dnd_end), dw);
3542
3543         dupe_dest_set(dw->listview, TRUE);
3544         g_signal_connect(G_OBJECT(dw->listview), "drag_data_received",
3545                          G_CALLBACK(dupe_dnd_data_get), dw);
3546
3547         gtk_drag_source_set(dw->second_listview, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
3548                             dupe_drag_types, n_dupe_drag_types,
3549                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
3550         g_signal_connect(G_OBJECT(dw->second_listview), "drag_data_get",
3551                          G_CALLBACK(dupe_dnd_data_set), dw);
3552         g_signal_connect(G_OBJECT(dw->second_listview), "drag_begin",
3553                          G_CALLBACK(dupe_dnd_begin), dw);
3554         g_signal_connect(G_OBJECT(dw->second_listview), "drag_end",
3555                          G_CALLBACK(dupe_dnd_end), dw);
3556
3557         dupe_dest_set(dw->second_listview, TRUE);
3558         g_signal_connect(G_OBJECT(dw->second_listview), "drag_data_received",
3559                          G_CALLBACK(dupe_dnd_data_get), dw);
3560 }
3561
3562 /*
3563  *-------------------------------------------------------------------
3564  * maintenance (move, delete, etc.)
3565  *-------------------------------------------------------------------
3566  */
3567
3568 static void dupe_notify_cb(FileData *fd, NotifyType type, gpointer data)
3569 {
3570         DupeWindow *dw = data;
3571
3572         if (!(type & NOTIFY_CHANGE) || !fd->change) return;
3573
3574         DEBUG_1("Notify dupe: %s %04x", fd->path, type);
3575         
3576         switch (fd->change->type)
3577                 {
3578                 case FILEDATA_CHANGE_MOVE:
3579                 case FILEDATA_CHANGE_RENAME:
3580                         dupe_item_update_fd(dw, fd);
3581                         break;
3582                 case FILEDATA_CHANGE_COPY:
3583                         break;
3584                 case FILEDATA_CHANGE_DELETE:
3585                         while (dupe_item_remove_by_path(dw, fd->path));
3586                         break;
3587                 case FILEDATA_CHANGE_UNSPECIFIED:
3588                 case FILEDATA_CHANGE_WRITE_METADATA:
3589                         break;
3590                 }
3591
3592 }
3593 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */