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