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