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