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