3ffd42dc1c6cfb55ff506424a155f009ed147f8f
[claws.git] / src / addrduplicates.c
1 /* Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
2  * Copyright (C) 2007 Holger Berndt <hb@claws-mail.org> 
3  * and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>. 
17  */
18  
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #endif
22
23 #include <gtk/gtk.h>
24 #include <glib/gi18n.h>
25 #include <string.h>
26
27 #include "defs.h"
28
29 #include "addrduplicates.h"
30 #include "addrindex.h"
31 #include "alertpanel.h"
32 #include "gtkutils.h"
33
34 typedef struct
35 {
36         ItemPerson *person;
37         gchar *book;
38 }
39 AddrDupListEntry;
40
41 enum {
42     COL_BOOK = 0,
43     COL_NAME,
44     NUM_COLS
45 };
46
47 static gboolean create_dialog(void);
48 static void create_addr_hash(void);
49 static void present_finder_results(void);
50 static void cb_finder_results_dialog_destroy(GtkWindow*, gpointer);
51 static void destroy_addr_hash_val(gpointer);
52 static GSList* deep_copy_hash_val(GSList*);
53 static void fill_hash_table();
54 static gint collect_emails(ItemPerson*, const gchar*);
55 static gboolean is_not_duplicate(gpointer, gpointer, gpointer);
56 static gint books_compare(gconstpointer, gconstpointer);
57 static GtkWidget* create_email_view(GtkListStore*);
58 static GtkWidget* create_detail_view(GtkListStore*);
59 static void append_to_email_store(gpointer,gpointer,gpointer);
60 static void email_selection_changed(GtkTreeSelection*,gpointer);
61
62 static GHashTable *addr_hash;
63 static gboolean include_same_book = TRUE;
64 static gboolean include_other_books = TRUE;
65 static GtkListStore *detail_store;
66
67 void addrduplicates_find(void)
68 {
69         if(create_dialog()) {
70                 create_addr_hash();
71                 present_finder_results();
72         }
73 }
74
75 static gboolean create_dialog(void)
76 {
77         gboolean want_search;
78         GtkWidget *vbox;
79         GtkWidget *check_same_book;
80         GtkWidget *check_other_book;
81         AlertValue val;
82
83         want_search = FALSE;
84
85         vbox = gtk_vbox_new(FALSE, 0);
86         check_same_book = gtk_check_button_new_with_label(_("Show duplicates in "
87                           "the same book"));
88         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_same_book),
89                                      include_same_book);
90         gtk_box_pack_start(GTK_BOX(vbox), check_same_book, FALSE, FALSE, 0);
91         gtk_widget_show(check_same_book);
92         check_other_book = gtk_check_button_new_with_label(_("Show duplicates in "
93                            "different books"));
94         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_other_book),
95                                      include_other_books);
96         gtk_box_pack_start(GTK_BOX(vbox), check_other_book, FALSE, FALSE, 0);
97         gtk_widget_show(check_other_book);
98
99         /* prevent checkboxes from being destroyed on dialog close */
100         g_object_ref(check_same_book);
101         g_object_ref(check_other_book);
102
103         val = alertpanel_full(_("Find address book email duplicates"),
104                               _("Claws-Mail will now search for duplicate email "
105                                 "addresses in the addressbook."),
106                               _("Cancel"),_("Search"),NULL, FALSE, vbox, ALERT_NOTICE,
107                               G_ALERTALTERNATE);
108         if(val == G_ALERTALTERNATE) {
109                 want_search = TRUE;
110
111                 /* save options */
112                 include_same_book =
113                     gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check_same_book));
114                 include_other_books =
115                     gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check_other_book));
116
117         }
118
119         g_object_unref(check_same_book);
120         g_object_unref(check_other_book);
121         return want_search;
122 }
123
124 static void create_addr_hash(void)
125 {
126         if(addr_hash)
127                 g_hash_table_destroy(addr_hash);
128         addr_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
129                                           g_free, destroy_addr_hash_val);
130         fill_hash_table();
131 }
132
133 static void destroy_addr_hash_val(gpointer value)
134 {
135         GSList *list = (GSList*) value;
136         GSList *walk;
137
138         for(walk = list; walk; walk = walk->next) {
139                 AddrDupListEntry *entry = (AddrDupListEntry*) walk->data;
140                 if(entry->book)
141                         g_free(entry->book);
142                 if(entry)
143                         g_free(entry);
144         }
145         if(list)
146                 g_slist_free(list);
147 }
148
149 static GSList* deep_copy_hash_val(GSList *in)
150 {
151         GSList *walk;
152         GSList *out = NULL;
153
154         out = g_slist_copy(in);
155         for(walk = out; walk; walk = walk->next) {
156                 AddrDupListEntry *out_entry;
157                 AddrDupListEntry *in_entry = walk->data;
158
159                 out_entry = g_new0(AddrDupListEntry,1);
160                 out_entry->person = in_entry->person;
161                 out_entry->book   = g_strdup(in_entry->book);
162                 walk->data = out_entry;
163         }
164
165         return out;
166 }
167
168 static void fill_hash_table()
169 {
170         addrindex_load_person_attribute(NULL, collect_emails);
171         g_hash_table_foreach_remove(addr_hash,is_not_duplicate, NULL);
172 }
173
174 static gboolean is_not_duplicate(gpointer key, gpointer value,
175                                  gpointer user_data)
176 {
177         gboolean is_in_same_book;
178         gboolean is_in_other_books;
179         GSList *books;
180         GSList *walk;
181         gboolean retval;
182         GSList *list = value;
183
184         /* remove everything that is just in one book */
185         if(g_slist_length(list) <= 1)
186                 return TRUE;
187
188         /* work on a shallow copy */
189         books = g_slist_copy(list);
190
191         /* sorting the list makes it easier to check for books */
192         books = g_slist_sort(books, books_compare);
193
194         /* check if a book appears twice */
195         is_in_same_book = FALSE;
196         for(walk = books; walk && walk->next; walk = walk->next) {
197                 if(books_compare(walk->data, walk->next->data) == 0) {
198                         is_in_same_book = TRUE;
199                         break;
200                 }
201         }
202
203         /* check is at least two different books appear in the list */
204         is_in_other_books = FALSE;
205         if(books && books->next) {
206                 for(walk = books->next; walk; walk = walk->next) {
207                         if(books_compare(walk->data, books->data) != 0) {
208                                 is_in_other_books = TRUE;
209                                 break;
210                         }
211                 }
212         }
213
214         /* delete the shallow copy */
215         g_slist_free(books);
216
217         retval = FALSE;
218         if(is_in_same_book && include_same_book)
219                 retval = TRUE;
220         if(is_in_other_books && include_other_books)
221                 retval = TRUE;
222         retval = !retval;
223
224         return retval;
225 }
226
227 static gint collect_emails(ItemPerson *itemperson, const gchar *book)
228 {
229         gchar *addr;
230         GList *nodeM;
231         GSList *old_val;
232         GSList *new_val;
233         AddrDupListEntry *entry;
234
235         /* Process each E-Mail address */
236         nodeM = itemperson->listEMail;
237         while(nodeM) {
238                 ItemEMail *email = nodeM->data;
239
240                 addr = g_strdup(email->address);
241                 g_strdown(addr);
242                 old_val = g_hash_table_lookup(addr_hash, addr);
243                 if(old_val)
244                         new_val = deep_copy_hash_val(old_val);
245                 else
246                         new_val = NULL;
247
248                 entry = g_new0(AddrDupListEntry,1);
249                 entry->person = itemperson;
250                 entry->book = g_strdup(book);
251
252                 new_val = g_slist_prepend(new_val, entry);
253                 g_hash_table_insert(addr_hash, addr, new_val);
254
255                 nodeM = g_list_next(nodeM);
256         }
257         return 0;
258 }
259
260 static gint books_compare(gconstpointer a, gconstpointer b)
261 {
262         const AddrDupListEntry *entry1;
263         const AddrDupListEntry *entry2;
264         entry1 = a;
265         entry2 = b;
266         return strcmp(entry1->book,entry2->book);
267 }
268
269 static void present_finder_results(void)
270 {
271         GtkListStore *email_store;
272         GtkWidget *email_view;
273         GtkWidget *detail_view;
274         GtkWidget *dialog = NULL;
275         GtkWidget *vbox;
276         GtkWidget *hbox;
277         GtkWidget *hpaned;
278         GtkWidget *close;
279         GtkTreeSelection *email_select;
280         GtkTreeSelection *detail_select;
281         static GdkGeometry geometry;
282
283         if(g_hash_table_size(addr_hash) == 0) {
284                 alertpanel_notice(_("No duplicate email addresses in the addressbook found"));
285                 return;
286         }
287
288         email_store = gtk_list_store_new(1, G_TYPE_STRING);
289         g_hash_table_foreach(addr_hash,append_to_email_store,email_store);
290         email_view = create_email_view(email_store);
291         email_select = gtk_tree_view_get_selection(GTK_TREE_VIEW(email_view));
292         gtk_tree_selection_set_mode(email_select,GTK_SELECTION_SINGLE);
293
294         g_signal_connect(email_select, "changed",
295                          (GCallback)email_selection_changed, NULL);
296
297         detail_store = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING);
298         detail_view = create_detail_view(detail_store);
299         detail_select = gtk_tree_view_get_selection(GTK_TREE_VIEW(detail_view));
300         gtk_tree_selection_set_mode(email_select,GTK_SELECTION_SINGLE);
301
302         dialog = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "address_dupes_finder");
303         if(!geometry.min_height) {
304                 geometry.min_width = 600;
305                 geometry.min_height = 400;
306         }
307         gtk_window_set_geometry_hints(GTK_WINDOW(dialog), NULL, &geometry,
308                                       GDK_HINT_MIN_SIZE);
309         gtk_window_set_title(GTK_WINDOW(dialog), _("Duplicate email addresses"));
310
311
312         vbox = gtk_vbox_new(FALSE, 0);
313         gtk_container_add(GTK_CONTAINER(dialog), vbox);
314
315         hpaned = gtk_hpaned_new();
316         gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0);
317
318         gtk_paned_add1(GTK_PANED(hpaned), email_view);
319         gtk_paned_add2(GTK_PANED(hpaned), detail_view);
320
321         hbox = gtk_hbox_new (FALSE, 0);
322         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
323
324         close = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
325         gtk_box_pack_end(GTK_BOX(hbox), close, FALSE, FALSE, 0);
326
327         g_signal_connect(dialog, "destroy",
328                          G_CALLBACK(cb_finder_results_dialog_destroy), NULL);
329         g_signal_connect_swapped(close, "clicked",
330                                  G_CALLBACK(gtk_widget_destroy), dialog);
331
332         gtk_widget_show_all(dialog);
333 }
334
335 static void cb_finder_results_dialog_destroy(GtkWindow *win, gpointer data)
336 {
337         g_hash_table_destroy(addr_hash);
338         addr_hash = NULL;
339
340 }
341
342 static GtkWidget* create_email_view(GtkListStore *store)
343 {
344         GtkWidget *view;
345         GtkCellRenderer *renderer;
346
347         view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
348         renderer = gtk_cell_renderer_text_new();
349         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
350                 -1,
351                 _("Address"),
352                 renderer,
353                 "text", 0,
354                 NULL);
355         g_object_unref(store);
356         return view;
357 }
358
359 static GtkWidget* create_detail_view(GtkListStore *store)
360 {
361         GtkWidget *view;
362         GtkCellRenderer *renderer;
363
364         view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
365         renderer = gtk_cell_renderer_text_new();
366
367         /* col 1 */
368         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
369                 -1,
370                 _("Bookname"),
371                 renderer,
372                 "text", COL_BOOK,
373                 NULL);
374         /* col 2 */
375         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
376                 -1,
377                 _("Name"),
378                 renderer,
379                 "text", COL_NAME,
380                 NULL);
381
382         return view;
383 }
384
385 static void append_to_email_store(gpointer key,gpointer value,gpointer data)
386 {
387         GtkTreeIter iter;
388         GtkListStore *store = (GtkListStore*) data;
389
390         gtk_list_store_append(store, &iter);
391         gtk_list_store_set(store, &iter, 0, (gchar*) key, -1);
392 }
393
394 static void email_selection_changed(GtkTreeSelection *selection, gpointer data)
395 {
396         GtkTreeIter iter;
397         GtkTreeModel *model;
398         gchar *email;
399
400         if(gtk_tree_selection_get_selected(selection, &model, &iter)) {
401                 GSList *hashval;
402                 GSList *walk;
403
404                 gtk_tree_model_get(model, &iter, 0, &email, -1);
405
406                 hashval = g_hash_table_lookup(addr_hash, email);
407                 gtk_list_store_clear(detail_store);
408                 for(walk = hashval; walk; walk = walk->next) {
409                         AddrDupListEntry *entry = walk->data;
410                         if(!entry)
411                                 continue;
412                         gtk_list_store_append(detail_store, &iter);
413                         gtk_list_store_set(detail_store, &iter,
414                                            COL_BOOK, entry->book,
415                                            COL_NAME, ADDRITEM_NAME(entry->person),
416                                            -1);
417                 }
418                 g_free(email);
419         }
420 }