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