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