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