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