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