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
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.
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.
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/>.
23 #include <glib/gi18n.h>
29 #include "ldapserver.h"
30 #include "ldapupdate.h"
32 #include "addrduplicates.h"
33 #include "addrindex.h"
35 #include "addressbook.h"
36 #include "editaddress.h"
37 #include "alertpanel.h"
45 AddressDataSource *ds;
57 static gboolean create_dialog();
58 static void refresh_addr_hash(void);
59 static void refresh_stores(void);
60 static void present_finder_results(GtkWindow*);
61 static void cb_finder_results_dialog_destroy(GtkWindow*, gpointer);
62 static void destroy_addr_hash_val(gpointer);
63 static GSList* deep_copy_hash_val(GSList*);
64 static void fill_hash_table();
65 static gint collect_emails(ItemPerson*, AddressDataSource*);
66 static gboolean is_not_duplicate(gpointer, gpointer, gpointer);
67 static gint books_compare(gconstpointer, gconstpointer);
68 static GtkWidget* create_email_view(GtkListStore*);
69 static GtkWidget* create_detail_view(GtkListStore*);
70 static void append_to_email_store(gpointer,gpointer,gpointer);
71 static void email_selection_changed(GtkTreeSelection*,gpointer);
72 static void detail_selection_changed(GtkTreeSelection*,gpointer);
73 static void detail_row_activated(GtkTreeView*,GtkTreePath*,
76 static void cb_del_btn_clicked(GtkButton *, gpointer);
77 static gboolean delete_item(ItemPerson*, AddressDataSource*);
79 static GHashTable *addr_hash;
80 static gboolean include_same_book = TRUE;
81 static gboolean include_other_books = TRUE;
83 static GtkListStore *email_store;
84 static GtkListStore *detail_store;
86 static GtkWidget *del_btn;
88 void addrduplicates_find(GtkWindow *parent)
92 present_finder_results(parent);
96 static gboolean create_dialog()
100 GtkWidget *check_same_book;
101 GtkWidget *check_other_book;
106 vbox = gtk_vbox_new(FALSE, 0);
107 check_same_book = gtk_check_button_new_with_label(_("Show duplicates in "
109 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_same_book),
111 gtk_box_pack_start(GTK_BOX(vbox), check_same_book, FALSE, FALSE, 0);
112 gtk_widget_show(check_same_book);
113 check_other_book = gtk_check_button_new_with_label(_("Show duplicates in "
115 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_other_book),
116 include_other_books);
117 gtk_box_pack_start(GTK_BOX(vbox), check_other_book, FALSE, FALSE, 0);
118 gtk_widget_show(check_other_book);
120 /* prevent checkboxes from being destroyed on dialog close */
121 g_object_ref(check_same_book);
122 g_object_ref(check_other_book);
124 val = alertpanel_full(_("Find address book email duplicates"),
125 _("Claws-Mail will now search for duplicate email "
126 "addresses in the addressbook."),
127 _("Cancel"),_("Search"),NULL, FALSE, vbox, ALERT_NOTICE,
129 if(val == G_ALERTALTERNATE) {
134 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check_same_book));
135 include_other_books =
136 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check_other_book));
140 g_object_unref(check_same_book);
141 g_object_unref(check_other_book);
145 static void refresh_addr_hash(void)
148 g_hash_table_destroy(addr_hash);
149 addr_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
150 g_free, destroy_addr_hash_val);
154 static void destroy_addr_hash_val(gpointer value)
156 GSList *list = (GSList*) value;
159 for(walk = list; walk; walk = walk->next) {
160 AddrDupListEntry *entry = (AddrDupListEntry*) walk->data;
168 static GSList* deep_copy_hash_val(GSList *in)
173 out = g_slist_copy(in);
174 for(walk = out; walk; walk = walk->next) {
175 AddrDupListEntry *out_entry;
176 AddrDupListEntry *in_entry = walk->data;
178 out_entry = g_new0(AddrDupListEntry,1);
179 out_entry->person = in_entry->person;
180 out_entry->ds = in_entry->ds;
181 walk->data = out_entry;
187 static void fill_hash_table()
189 addrindex_load_person_ds(collect_emails);
190 g_hash_table_foreach_remove(addr_hash,is_not_duplicate, NULL);
193 static gboolean is_not_duplicate(gpointer key, gpointer value,
196 gboolean is_in_same_book;
197 gboolean is_in_other_books;
201 GSList *list = value;
203 /* remove everything that is just in one book */
204 if(g_slist_length(list) <= 1)
207 /* work on a shallow copy */
208 books = g_slist_copy(list);
210 /* sorting the list makes it easier to check for books */
211 books = g_slist_sort(books, books_compare);
213 /* check if a book appears twice */
214 is_in_same_book = FALSE;
215 for(walk = books; walk && walk->next; walk = walk->next) {
216 if(books_compare(walk->data, walk->next->data) == 0) {
217 is_in_same_book = TRUE;
222 /* check is at least two different books appear in the list */
223 is_in_other_books = FALSE;
224 if(books && books->next) {
225 for(walk = books->next; walk; walk = walk->next) {
226 if(books_compare(walk->data, books->data) != 0) {
227 is_in_other_books = TRUE;
233 /* delete the shallow copy */
237 if(is_in_same_book && include_same_book)
239 if(is_in_other_books && include_other_books)
246 static gint collect_emails(ItemPerson *itemperson, AddressDataSource *ds)
252 AddrDupListEntry *entry;
254 /* Process each E-Mail address */
255 nodeM = itemperson->listEMail;
257 ItemEMail *email = nodeM->data;
259 addr = g_strdup(email->address);
261 old_val = g_hash_table_lookup(addr_hash, addr);
263 new_val = deep_copy_hash_val(old_val);
267 entry = g_new0(AddrDupListEntry,1);
268 entry->person = itemperson;
271 new_val = g_slist_prepend(new_val, entry);
272 g_hash_table_insert(addr_hash, addr, new_val);
274 nodeM = g_list_next(nodeM);
279 static gint books_compare(gconstpointer a, gconstpointer b)
281 const AddrDupListEntry *entry1;
282 const AddrDupListEntry *entry2;
285 return strcmp(addrindex_ds_get_name(entry1->ds),
286 addrindex_ds_get_name(entry2->ds));
289 static void present_finder_results(GtkWindow *parent)
291 GtkWidget *email_view;
292 GtkWidget *detail_view;
293 GtkWidget *dialog = NULL;
298 GtkTreeSelection *email_select;
299 GtkTreeSelection *detail_select;
300 static GdkGeometry geometry;
302 if(g_hash_table_size(addr_hash) == 0) {
303 alertpanel_notice(_("No duplicate email addresses in the addressbook found"));
307 email_store = gtk_list_store_new(1, G_TYPE_STRING);
309 email_view = create_email_view(email_store);
310 email_select = gtk_tree_view_get_selection(GTK_TREE_VIEW(email_view));
311 gtk_tree_selection_set_mode(email_select,GTK_SELECTION_SINGLE);
313 g_signal_connect(email_select, "changed",
314 (GCallback)email_selection_changed, NULL);
316 detail_store = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING,
317 G_TYPE_POINTER, G_TYPE_POINTER);
318 detail_view = create_detail_view(detail_store);
319 detail_select = gtk_tree_view_get_selection(GTK_TREE_VIEW(detail_view));
320 gtk_tree_selection_set_mode(detail_select,GTK_SELECTION_MULTIPLE);
322 g_signal_connect(detail_select, "changed",
323 (GCallback)detail_selection_changed, NULL);
325 dialog = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "address_dupes_finder");
326 gtk_window_set_transient_for(GTK_WINDOW(dialog),parent);
327 gtk_window_set_modal(GTK_WINDOW(dialog),TRUE);
328 if(!geometry.min_height) {
329 geometry.min_width = 600;
330 geometry.min_height = 400;
332 gtk_window_set_geometry_hints(GTK_WINDOW(dialog), NULL, &geometry,
334 gtk_window_set_title(GTK_WINDOW(dialog), _("Duplicate email addresses"));
337 vbox = gtk_vbox_new(FALSE, 0);
338 gtk_container_add(GTK_CONTAINER(dialog), vbox);
340 hpaned = gtk_hpaned_new();
341 gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0);
343 gtk_paned_add1(GTK_PANED(hpaned), email_view);
344 gtk_paned_add2(GTK_PANED(hpaned), detail_view);
346 hbox = gtk_hbox_new (FALSE, 0);
347 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
349 close = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
350 gtk_box_pack_end(GTK_BOX(hbox), close, FALSE, FALSE, 5);
352 del_btn = gtk_button_new_from_stock(GTK_STOCK_DELETE);
353 gtk_box_pack_end(GTK_BOX(hbox), del_btn, FALSE, FALSE, 5);
354 gtk_widget_set_sensitive(del_btn, FALSE);
356 g_signal_connect(dialog, "destroy",
357 G_CALLBACK(cb_finder_results_dialog_destroy), NULL);
358 g_signal_connect_swapped(close, "clicked",
359 G_CALLBACK(gtk_widget_destroy), dialog);
360 g_signal_connect(del_btn, "clicked",
361 G_CALLBACK(cb_del_btn_clicked), detail_view);
363 gtk_widget_show_all(dialog);
366 static void cb_finder_results_dialog_destroy(GtkWindow *win, gpointer data)
372 g_hash_table_destroy(addr_hash);
375 addressbook_refresh();
379 static GtkWidget* create_email_view(GtkListStore *store)
382 GtkCellRenderer *renderer;
384 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
385 renderer = gtk_cell_renderer_text_new();
386 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
392 g_object_unref(store);
396 static GtkWidget* create_detail_view(GtkListStore *store)
399 GtkCellRenderer *renderer;
401 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
402 renderer = gtk_cell_renderer_text_new();
405 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
412 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
419 g_signal_connect(view, "row-activated",
420 (GCallback)detail_row_activated, NULL);
425 static void append_to_email_store(gpointer key,gpointer value,gpointer data)
428 GtkListStore *store = (GtkListStore*) data;
430 gtk_list_store_append(store, &iter);
431 gtk_list_store_set(store, &iter, 0, (gchar*) key, -1);
434 static void detail_selection_changed(GtkTreeSelection *selection, gpointer data)
436 if(gtk_tree_selection_count_selected_rows(selection) > 0)
437 gtk_widget_set_sensitive(del_btn,TRUE);
439 gtk_widget_set_sensitive(del_btn,FALSE);
442 static void email_selection_changed(GtkTreeSelection *selection, gpointer data)
448 if(gtk_tree_selection_get_selected(selection, &model, &iter)) {
452 gtk_tree_model_get(model, &iter, 0, &email, -1);
454 hashval = g_hash_table_lookup(addr_hash, email);
455 gtk_list_store_clear(detail_store);
456 for(walk = hashval; walk; walk = walk->next) {
457 AddrDupListEntry *entry = walk->data;
460 gtk_list_store_append(detail_store, &iter);
461 gtk_list_store_set(detail_store, &iter,
462 COL_BOOK, addrindex_ds_get_name(entry->ds),
463 COL_NAME, ADDRITEM_NAME(entry->person),
464 COL_ITEM, entry->person,
472 static void refresh_stores(void)
476 gtk_list_store_clear(email_store);
478 gtk_list_store_clear(detail_store);
479 g_hash_table_foreach(addr_hash,append_to_email_store,email_store);
482 static void detail_row_activated(GtkTreeView *tree_view,
484 GtkTreeViewColumn *column,
489 AddressDataSource *ds;
491 AddressBookFile *abf;
493 model = gtk_tree_view_get_model(tree_view);
495 if(!gtk_tree_model_get_iter(model,&iter,path))
498 gtk_tree_model_get(model, &iter, COL_ITEM, &person, COL_DS, &ds, -1);
501 if(!((ds->type == ADDR_IF_BOOK) || ds->type == ADDR_IF_LDAP)) {
502 debug_print("Unsupported address datasource type for editing\n");
506 abf = ds->rawDataSource;
507 if(addressbook_edit_person(abf,NULL,person,FALSE,NULL,NULL,TRUE))
511 void cb_del_btn_clicked(GtkButton *button, gpointer data)
513 GtkTreeView *detail_view;
516 GtkTreeSelection *selection;
518 AddressDataSource *ds;
522 GtkTreeRowReference *ref;
527 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(detail_view));
529 list = gtk_tree_selection_get_selected_rows(selection, &model);
534 aval = alertpanel(_("Delete address(es)"),
535 _("Really delete the address(es)?"),
536 GTK_STOCK_CANCEL, "+"GTK_STOCK_DELETE, NULL);
537 if(aval != G_ALERTALTERNATE)
541 for(walk = list; walk; walk = walk->next) {
542 ref = gtk_tree_row_reference_new(model,(GtkTreePath*)(walk->data));
543 ref_list = g_list_prepend(ref_list, ref);
545 g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL);
548 for(walk = ref_list; walk; walk = walk->next) {
551 if(!gtk_tree_row_reference_valid(ref))
553 path = gtk_tree_row_reference_get_path(ref);
554 if(gtk_tree_model_get_iter(model, &iter, path)) {
555 gtk_tree_model_get(model, &iter, COL_ITEM, &item, COL_DS, &ds, -1);
556 delete_item(item,ds);
558 gtk_tree_path_free(path);
561 g_list_foreach(ref_list, (GFunc)gtk_tree_row_reference_free, NULL);
562 g_list_free(ref_list);
567 static gboolean delete_item(ItemPerson *item, AddressDataSource *ds)
569 AddressBookFile *abf;
570 AddressInterface *iface;
572 /* Test for read only */
573 iface = ds->interface;
574 if( iface->readOnly ) {
575 alertpanel( _("Delete address"),
576 _("This address data is readonly and cannot be deleted."),
577 GTK_STOCK_CLOSE, NULL, NULL );
581 if(!(abf = ds->rawDataSource))
584 item->status = DELETE_ENTRY;
585 item = addrbook_remove_person(abf, item);
589 if (ds && ds->type == ADDR_IF_LDAP) {
590 LdapServer *server = ds->rawDataSource;
591 ldapsvr_set_modified(server, TRUE);
592 ldapsvr_update_book(server, item);
598 addritem_free_item_person(item);