talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

addrduplicates.c (25615B)


      1 /* Claws Mail -- a GTK based, lightweight, and fast e-mail client
      2  * Copyright (C) 2007-2025 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 #include <gdk/gdk.h>
     20 #include <gdk/gdkkeysyms.h>
     21 #include <glib/gi18n.h>
     22 #include <string.h>
     23 
     24 #include "defs.h"
     25 
     26 #include "addrduplicates.h"
     27 #include "addrbook.h"
     28 #include "addressbook.h"
     29 #include "editaddress.h"
     30 #include "alertpanel.h"
     31 #include "gtkutils.h"
     32 #include "inc.h"
     33 #include "utils.h"
     34 #include "prefs_common.h"
     35 
     36 typedef struct
     37 {
     38 	ItemPerson        *person;
     39 	AddressDataSource *ds;
     40 	gchar             *book_path;
     41 }
     42 AddrDupListEntry;
     43 
     44 enum {
     45     COL_BOOKPATH = 0,
     46     COL_NAME,
     47     COL_ITEM,
     48     COL_DS,
     49     NUM_COLS
     50 };
     51 
     52 static gboolean create_dialog();
     53 static void refresh_addr_hash(void);
     54 static void refresh_stores(gchar*,GSList*);
     55 static void present_finder_results(GtkWindow*);
     56 static void cb_finder_results_dialog_destroy(GtkWindow*, gpointer);
     57 static gboolean cb_finder_results_dialog_key_pressed(GtkWidget*, GdkEventKey*,
     58         gpointer);
     59 static void destroy_addr_hash_val(gpointer);
     60 static AddrDupListEntry *copy_hash_val(AddrDupListEntry *);
     61 static void fill_hash_table();
     62 static gint collect_emails(ItemPerson*, AddressDataSource*);
     63 static gboolean is_not_duplicate(gpointer, gpointer, gpointer);
     64 static gint books_compare(gconstpointer, gconstpointer);
     65 static GtkWidget* create_email_view(GtkListStore*);
     66 static GtkWidget* create_detail_view(GtkListStore*);
     67 static void append_to_email_store(gpointer,gpointer,gpointer);
     68 static void email_selection_changed(GtkTreeSelection*,gpointer);
     69 static void detail_selection_changed(GtkTreeSelection*,gpointer);
     70 static void detail_row_activated(GtkTreeView*,GtkTreePath*,
     71                                  GtkTreeViewColumn*,
     72                                  gpointer);
     73 static gboolean detail_focus_in(GtkWidget*,GdkEventFocus*,gpointer);
     74 static gboolean detail_focus_out(GtkWidget*,GdkEventFocus*,gpointer);
     75 
     76 static void cb_del_btn_clicked(GtkButton *, gpointer);
     77 static void cb_edit_btn_clicked(GtkButton *, gpointer);
     78 static gchar* get_bookpath(ItemPerson*,AddressDataSource*);
     79 static gboolean is_editing_entry_only_selection(void);
     80 static void edit_post_update_cb(ItemPerson*);
     81 
     82 static GHashTable *addr_hash;
     83 static gboolean include_same_book = TRUE;
     84 static gboolean include_other_books = TRUE;
     85 
     86 static GtkListStore *email_store;
     87 static GtkListStore *detail_store;
     88 static GtkWidget    *email_view;
     89 static GtkWidget    *detail_view;
     90 static GtkWidget    *inline_edit_vbox;
     91 
     92 static GtkWidget *del_btn;
     93 static GtkWidget *edit_btn;
     94 
     95 static GtkWidget *dialog;
     96 static gchar *editing_uid;
     97 static gboolean detail_view_has_focus;
     98 
     99 void addrduplicates_find(GtkWindow *parent)
    100 {
    101 	if(create_dialog()) {
    102 		refresh_addr_hash();
    103 		present_finder_results(parent);
    104 	}
    105 }
    106 
    107 static gboolean create_dialog()
    108 {
    109 	gboolean want_search;
    110 	GtkWidget *vbox;
    111 	GtkWidget *check_same_book;
    112 	GtkWidget *check_other_book;
    113 	AlertValue val;
    114 
    115 	want_search = FALSE;
    116 
    117 	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    118 	check_same_book = gtk_check_button_new_with_label(_("Show duplicates in "
    119 	                  "the same book"));
    120 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_same_book),
    121 	                             include_same_book);
    122 	gtk_box_pack_start(GTK_BOX(vbox), check_same_book, FALSE, FALSE, 0);
    123 	gtk_widget_show(check_same_book);
    124 	check_other_book = gtk_check_button_new_with_label(_("Show duplicates in "
    125 	                   "different books"));
    126 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_other_book),
    127 	                             include_other_books);
    128 	gtk_box_pack_start(GTK_BOX(vbox), check_other_book, FALSE, FALSE, 0);
    129 	gtk_widget_show(check_other_book);
    130 
    131 	/* prevent checkboxes from being destroyed on dialog close */
    132 	g_object_ref(check_same_book);
    133 	g_object_ref(check_other_book);
    134 
    135 	val = alertpanel_full(_("Find address book email duplicates"),
    136 	                      _("Claws Mail will now search for duplicate email "
    137 	                        "addresses in the address book."),
    138 	                      NULL, _("_Cancel"), "edit-find-symbolic", _("_Find"), NULL, NULL,
    139 			     ALERTFOCUS_SECOND, FALSE, vbox, ALERT_NOTICE);
    140 	if(val == G_ALERTALTERNATE) {
    141 		want_search = TRUE;
    142 
    143 		/* save options */
    144 		include_same_book =
    145 		    gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check_same_book));
    146 		include_other_books =
    147 		    gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check_other_book));
    148 
    149 	}
    150 
    151 	g_object_unref(check_same_book);
    152 	g_object_unref(check_other_book);
    153 	return want_search;
    154 }
    155 
    156 static void refresh_addr_hash(void)
    157 {
    158 	if(addr_hash)
    159 		g_hash_table_destroy(addr_hash);
    160 	addr_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
    161 	                                  g_free, destroy_addr_hash_val);
    162 	fill_hash_table();
    163 }
    164 
    165 static void destroy_addr_hash_val(gpointer value)
    166 {
    167 	GSList *list = (GSList*) value;
    168 	GSList *walk;
    169 
    170 	for(walk = list; walk; walk = walk->next) {
    171 		AddrDupListEntry *entry = (AddrDupListEntry*) walk->data;
    172 		if(entry && entry->book_path)
    173 			g_free(entry->book_path);
    174 		if(entry)
    175 			g_free(entry);
    176 	}
    177 	if(list)
    178 		g_slist_free(list);
    179 }
    180 
    181 static AddrDupListEntry *copy_hash_val(AddrDupListEntry *entry)
    182 {
    183 	AddrDupListEntry *new = g_new0(AddrDupListEntry, 1);
    184 	new->person = entry->person;
    185 	new->ds = entry->ds;
    186 	new->book_path = g_strdup(entry->book_path);
    187 
    188 	return new;
    189 }
    190 
    191 static void fill_hash_table()
    192 {
    193 	addrindex_load_person_ds(collect_emails);
    194 	g_hash_table_foreach_remove(addr_hash,is_not_duplicate, NULL);
    195 }
    196 
    197 static gboolean is_not_duplicate(gpointer key, gpointer value,
    198                                  gpointer user_data)
    199 {
    200 	gboolean is_in_same_book;
    201 	gboolean is_in_other_books;
    202 	GSList *books;
    203 	GSList *walk;
    204 	gboolean retval;
    205 	GSList *list = value;
    206 
    207 	/* remove everything that is just in one book */
    208 	if(g_slist_length(list) <= 1)
    209 		return TRUE;
    210 
    211 	/* work on a shallow copy */
    212 	books = g_slist_copy(list);
    213 
    214 	/* sorting the list makes it easier to check for books */
    215 	books = g_slist_sort(books, books_compare);
    216 
    217 	/* check if a book appears twice */
    218 	is_in_same_book = FALSE;
    219 	for(walk = books; walk && walk->next; walk = walk->next) {
    220 		if(books_compare(walk->data, walk->next->data) == 0) {
    221 			is_in_same_book = TRUE;
    222 			break;
    223 		}
    224 	}
    225 
    226 	/* check is at least two different books appear in the list */
    227 	is_in_other_books = FALSE;
    228 	if(books && books->next) {
    229 		for(walk = books->next; walk; walk = walk->next) {
    230 			if(books_compare(walk->data, books->data) != 0) {
    231 				is_in_other_books = TRUE;
    232 				break;
    233 			}
    234 		}
    235 	}
    236 
    237 	/* delete the shallow copy */
    238 	g_slist_free(books);
    239 
    240 	retval = FALSE;
    241 	if(is_in_same_book && include_same_book)
    242 		retval = TRUE;
    243 	if(is_in_other_books && include_other_books)
    244 		retval = TRUE;
    245 	retval = !retval;
    246 
    247 	return retval;
    248 }
    249 
    250 static gint collect_emails(ItemPerson *itemperson, AddressDataSource *ds)
    251 {
    252 	gchar *addr;
    253 	GList *nodeM;
    254 	GSList *old_val;
    255 	GSList *new_val;
    256 	AddrDupListEntry *entry;
    257 
    258 	/* Process each E-Mail address */
    259 	nodeM = itemperson->listEMail;
    260 	while(nodeM) {
    261 		ItemEMail *email = nodeM->data;
    262 
    263 		addr = g_utf8_strdown(email->address, -1);
    264 		old_val = g_hash_table_lookup(addr_hash, addr);
    265 		if(old_val)
    266 			new_val = g_slist_copy_deep(old_val, (GCopyFunc)copy_hash_val, NULL);
    267 		else
    268 			new_val = NULL;
    269 
    270 		entry = g_new0(AddrDupListEntry,1);
    271 		entry->person = itemperson;
    272 		entry->ds     = ds;
    273 		entry->book_path = get_bookpath(itemperson, ds);
    274 
    275 		new_val = g_slist_prepend(new_val, entry);
    276 		g_hash_table_insert(addr_hash, addr, new_val);
    277 
    278 		nodeM = g_list_next(nodeM);
    279 	}
    280 	return 0;
    281 }
    282 
    283 static gint books_compare(gconstpointer a, gconstpointer b)
    284 {
    285 	const AddrDupListEntry *entry1;
    286 	const AddrDupListEntry *entry2;
    287 	entry1 = a;
    288 	entry2 = b;
    289 	return strcmp(entry1->book_path, entry2->book_path);
    290 }
    291 
    292 static void present_finder_results(GtkWindow *parent)
    293 {
    294 	GtkWidget *scrolled_win;
    295 	GtkWidget *vbox;
    296 	GtkWidget *hbox;
    297 	GtkWidget *hpaned;
    298 	GtkWidget *vpaned;
    299 	GtkWidget *close;
    300 	gint pos;
    301 	GtkTreeSelection *email_select;
    302 	GtkTreeSelection *detail_select;
    303 	static GdkGeometry geometry;
    304 
    305 	if(g_hash_table_size(addr_hash) == 0) {
    306 		alertpanel_notice(_("No duplicate email addresses found in the address book"));
    307 		return;
    308 	}
    309 
    310 	email_store = gtk_list_store_new(1, G_TYPE_STRING);
    311 	refresh_stores(NULL,NULL);
    312 	email_view = create_email_view(email_store);
    313 	email_select = gtk_tree_view_get_selection(GTK_TREE_VIEW(email_view));
    314 	gtk_tree_selection_set_mode(email_select,GTK_SELECTION_SINGLE);
    315 
    316 	g_signal_connect(email_select, "changed",
    317 	                 (GCallback)email_selection_changed, NULL);
    318 
    319 	detail_store = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING,
    320 	                                  G_TYPE_POINTER, G_TYPE_POINTER);
    321 	detail_view = create_detail_view(detail_store);
    322 	detail_select = gtk_tree_view_get_selection(GTK_TREE_VIEW(detail_view));
    323 	gtk_tree_selection_set_mode(detail_select,GTK_SELECTION_MULTIPLE);
    324 
    325 	g_signal_connect(detail_select, "changed",
    326 	                 (GCallback)detail_selection_changed, NULL);
    327 
    328 	dialog = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "address_dupes_finder");
    329 	gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
    330 	gtk_window_set_transient_for(GTK_WINDOW(dialog),parent);
    331 	gtk_window_set_modal(GTK_WINDOW(dialog),TRUE);
    332 	if(!geometry.min_height) {
    333 		geometry.min_width = 600;
    334 		geometry.min_height = 400;
    335 	}
    336 	gtk_window_set_geometry_hints(GTK_WINDOW(dialog), NULL, &geometry,
    337 	                              GDK_HINT_MIN_SIZE);
    338 	gtk_window_set_title(GTK_WINDOW(dialog), _("Duplicate email addresses"));
    339 
    340 	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    341 	gtk_container_add(GTK_CONTAINER(dialog), vbox);
    342 
    343 	hpaned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
    344 	gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0);
    345 
    346 	scrolled_win = gtk_scrolled_window_new(NULL,NULL);
    347 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_win),
    348 	                               GTK_POLICY_AUTOMATIC,
    349 	                               GTK_POLICY_AUTOMATIC);
    350 	gtk_container_add(GTK_CONTAINER(scrolled_win), email_view);
    351 
    352 	gtk_paned_add1(GTK_PANED(hpaned), scrolled_win);
    353 
    354 	scrolled_win = gtk_scrolled_window_new(NULL,NULL);
    355 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_win),
    356 	                               GTK_POLICY_AUTOMATIC,
    357 	                               GTK_POLICY_AUTOMATIC);
    358 	gtk_container_add(GTK_CONTAINER(scrolled_win), detail_view);
    359 
    360 	if (prefs_common.addressbook_use_editaddress_dialog) {
    361 		gtk_paned_add2(GTK_PANED(hpaned), scrolled_win);
    362 		inline_edit_vbox = NULL;
    363 	} else {
    364 		inline_edit_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
    365 		vpaned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
    366 		gtk_paned_pack1(GTK_PANED(vpaned), scrolled_win, FALSE, FALSE);
    367 		gtk_paned_pack2(GTK_PANED(vpaned), inline_edit_vbox, TRUE, FALSE);
    368 		gtk_paned_pack2(GTK_PANED(hpaned), vpaned, TRUE, FALSE);
    369 	}
    370 
    371 	g_object_get(G_OBJECT(hpaned),
    372 	             "position", &pos, NULL);
    373 	if(pos < 200)
    374 		gtk_paned_set_position(GTK_PANED(hpaned), 200);
    375 
    376 	hbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
    377 	gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
    378 	gtk_box_set_spacing(GTK_BOX(hbox), 2);
    379 	gtk_container_set_border_width(GTK_CONTAINER(hbox), 4);
    380 	gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    381 
    382 	edit_btn = gtk_button_new_with_mnemonic(_("_Edit"));
    383 	gtk_box_pack_start(GTK_BOX(hbox), edit_btn, TRUE, TRUE, 0);
    384 	gtk_widget_set_sensitive(edit_btn, FALSE);
    385 
    386 	del_btn = gtkut_stock_button("edit-delete", _("D_elete"));
    387 	gtk_box_pack_start(GTK_BOX(hbox), del_btn, TRUE, TRUE, 0);
    388 	gtk_widget_set_sensitive(del_btn, FALSE);
    389 
    390 	close = gtkut_stock_button("window-close", _("_Close"));
    391 	gtk_box_pack_start(GTK_BOX(hbox), close, TRUE, TRUE, 0);
    392 
    393 	g_signal_connect(dialog, "destroy",
    394 	                 G_CALLBACK(cb_finder_results_dialog_destroy), NULL);
    395 	g_signal_connect(G_OBJECT(dialog), "key-press-event",
    396 	                 G_CALLBACK(cb_finder_results_dialog_key_pressed), NULL);
    397 	g_signal_connect_swapped(close, "clicked",
    398 	                         G_CALLBACK(gtk_widget_destroy), dialog);
    399 	g_signal_connect(del_btn, "clicked",
    400 	                 G_CALLBACK(cb_del_btn_clicked), detail_view);
    401 	g_signal_connect(edit_btn, "clicked",
    402 	                 G_CALLBACK(cb_edit_btn_clicked), detail_view);
    403 
    404 	inc_lock();
    405 	gtk_widget_show_all(dialog);
    406 }
    407 
    408 static void cb_finder_results_dialog_destroy(GtkWindow *win, gpointer data)
    409 {
    410 	email_store = NULL;
    411 	detail_store = NULL;
    412 	email_view = NULL;
    413 	inline_edit_vbox = NULL;
    414 
    415 	if(addr_hash) {
    416 		g_hash_table_destroy(addr_hash);
    417 		addr_hash = NULL;
    418 	}
    419 	dialog = NULL;
    420 	addressbook_refresh();
    421 	inc_unlock();
    422 }
    423 
    424 static GtkWidget* create_email_view(GtkListStore *store)
    425 {
    426 	GtkWidget *view;
    427 	GtkCellRenderer *renderer;
    428 
    429 	view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
    430 	renderer = gtk_cell_renderer_text_new();
    431 	gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
    432 	        -1,
    433 	        _("Address"),
    434 	        renderer,
    435 	        "text", 0,
    436 	        NULL);
    437 	g_object_unref(store);
    438 	return view;
    439 }
    440 
    441 static GtkWidget* create_detail_view(GtkListStore *store)
    442 {
    443 	GtkWidget *view;
    444 	GtkCellRenderer *renderer;
    445 	GList *cols;
    446 	GList *walk;
    447 
    448 	view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
    449 	renderer = gtk_cell_renderer_text_new();
    450 
    451 	/* col 1 */
    452 	gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
    453 	        -1,
    454 	        _("Address book path"),
    455 	        renderer,
    456 	        "text", COL_BOOKPATH,
    457 	        NULL);
    458 	/* col 2 */
    459 	gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
    460 	        -1,
    461 	        _("Name"),
    462 	        renderer,
    463 	        "text", COL_NAME,
    464 	        NULL);
    465 
    466 	cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
    467 	for(walk = cols; walk; walk = walk->next)
    468 		gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(walk->data),
    469 		                                   TRUE);
    470 	g_list_free(cols);
    471 
    472 	g_signal_connect(view, "row-activated",
    473 	                 G_CALLBACK(detail_row_activated), NULL);
    474 
    475 	g_signal_connect(view, "focus-in-event",
    476 	                 G_CALLBACK(detail_focus_in), NULL);
    477 	g_signal_connect(view, "focus-out-event",
    478 	                 G_CALLBACK(detail_focus_out), NULL);
    479 
    480 
    481 	return view;
    482 }
    483 
    484 static void append_to_email_store(gpointer key,gpointer value,gpointer data)
    485 {
    486 	GtkTreeIter iter;
    487 	GtkListStore *store = (GtkListStore*) data;
    488 
    489 	gtk_list_store_append(store, &iter);
    490 	gtk_list_store_set(store, &iter, 0, (gchar*) key, -1);
    491 }
    492 
    493 static gboolean is_editing_entry_only_selection(void)
    494 {
    495 	GtkTreeSelection *sel_detail;
    496 	GtkTreeIter iter;
    497 	GList *selected;
    498 	GtkTreeModel *model;
    499 	ItemPerson *item;
    500 
    501 	sel_detail = gtk_tree_view_get_selection(GTK_TREE_VIEW(detail_view));
    502 
    503 	if(gtk_tree_selection_count_selected_rows(sel_detail) > 1)
    504 		return FALSE;
    505 
    506 	selected = gtk_tree_selection_get_selected_rows(sel_detail,&model);
    507 	cm_return_val_if_fail(selected, FALSE);
    508 
    509 	gtk_tree_model_get_iter(model, &iter, (GtkTreePath*)selected->data);
    510 	g_list_foreach(selected, (GFunc)gtk_tree_path_free, NULL);
    511 	g_list_free(selected);
    512 
    513 	gtk_tree_model_get(model, &iter, COL_ITEM, &item,-1);
    514 	if(ADDRITEM_ID(item) && editing_uid &&
    515 	        strcmp(ADDRITEM_ID(item),editing_uid) == 0)
    516 		return TRUE;
    517 	else
    518 		return FALSE;
    519 }
    520 
    521 static void detail_selection_changed(GtkTreeSelection *selection, gpointer data)
    522 {
    523 	gint num_selected;
    524 	num_selected = gtk_tree_selection_count_selected_rows(selection);
    525 
    526 	if(num_selected > 0)
    527 		gtk_widget_set_sensitive(del_btn,TRUE);
    528 	else
    529 		gtk_widget_set_sensitive(del_btn,FALSE);
    530 
    531 	if(num_selected == 1)
    532 		gtk_widget_set_sensitive(edit_btn,TRUE);
    533 	else
    534 		gtk_widget_set_sensitive(edit_btn,FALSE);
    535 
    536 	if(!is_editing_entry_only_selection())
    537 		addressbook_edit_person_widgetset_hide();
    538 }
    539 
    540 static void email_selection_changed(GtkTreeSelection *selection, gpointer data)
    541 {
    542 	GtkTreeIter iter;
    543 	GtkTreeModel *model;
    544 	gchar *email;
    545 
    546 	if(gtk_tree_selection_get_selected(selection, &model, &iter)) {
    547 		GSList *hashval;
    548 		GSList *walk;
    549 
    550 		gtk_tree_model_get(model, &iter, 0, &email, -1);
    551 
    552 		hashval = g_hash_table_lookup(addr_hash, email);
    553 		gtk_list_store_clear(detail_store);
    554 		for(walk = hashval; walk; walk = walk->next) {
    555 			AddrDupListEntry *entry = walk->data;
    556 			if(!entry)
    557 				continue;
    558 			gtk_list_store_append(detail_store, &iter);
    559 			gtk_list_store_set(detail_store, &iter,
    560 			                   COL_BOOKPATH, entry->book_path,
    561 			                   COL_NAME, addressbook_set_col_name_guard(ADDRITEM_NAME(entry->person)),
    562 			                   COL_ITEM, entry->person,
    563 			                   COL_DS, entry->ds,
    564 			                   -1);
    565 		}
    566 		g_free(email);
    567 	}
    568 }
    569 
    570 static gchar* get_bookpath(ItemPerson *itemPerson, AddressDataSource *ds)
    571 {
    572 	gchar *path;
    573 	gchar *tmp;
    574 	AddrItemObject *item;
    575 
    576 	item = (AddrItemObject*)itemPerson;
    577 	path = g_strdup("");
    578 	while((item = ADDRITEM_PARENT(item)) != NULL) {
    579 
    580 		if(ADDRITEM_TYPE(item) == ITEMTYPE_FOLDER) {
    581 			ItemFolder *folder = (ItemFolder*) item;
    582 			tmp = path;
    583 			path = g_strdup_printf("%s%s%s",
    584 			                       folder->isRoot ? addrindex_ds_get_name(ds) :
    585 			                       ADDRITEM_NAME(folder),
    586 			                       (*tmp == '\0') ? "" : "/", tmp);
    587 			g_free(tmp);
    588 		}
    589 
    590 	}
    591 
    592 	/* prepend bookpath */
    593 	if(ds && ds->interface && ds->interface->name) {
    594 		tmp = path;
    595 		path = g_strdup_printf("%s%s%s", ds->interface->name,
    596 		                       (*tmp == '\0') ? "" : "/", tmp);
    597 		g_free(tmp);
    598 	}
    599 
    600 	return path;
    601 }
    602 
    603 static void refresh_stores(gchar *email_to_select, GSList *detail_to_select)
    604 {
    605 	refresh_addr_hash();
    606 	if(email_store)
    607 		gtk_list_store_clear(email_store);
    608 	if(detail_store)
    609 		gtk_list_store_clear(detail_store);
    610 	g_hash_table_foreach(addr_hash,append_to_email_store,email_store);
    611 
    612 	/* sort the email store */
    613 	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(email_store),
    614 	                                     0, GTK_SORT_ASCENDING);
    615 
    616 	/* try to select email address */
    617 	if(email_to_select) {
    618 		/* Search email in email store */
    619 		GtkTreeIter iter;
    620 		GtkTreeSelection *selection;
    621 
    622 		if(!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(email_store), &iter))
    623 			return;
    624 		selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(email_view));
    625 
    626 		do {
    627 			gint retVal;
    628 			gchar *email;
    629 
    630 			gtk_tree_model_get(GTK_TREE_MODEL(email_store), &iter, 0, &email, -1);
    631 			retVal = g_ascii_strncasecmp(email,email_to_select,strlen(email));
    632 			g_free(email);
    633 			if(retVal == 0) {
    634 				gtk_tree_selection_select_iter(selection,&iter);
    635 				break;
    636 			}
    637 		} while(gtk_tree_model_iter_next(GTK_TREE_MODEL(email_store), &iter));
    638 
    639 	}
    640 
    641 	/* try to select detail rows */
    642 	if(detail_to_select) {
    643 		GtkTreeIter iter;
    644 		GtkTreeSelection *sel;
    645 		if(!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(detail_store), &iter))
    646 			return;
    647 		sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(detail_view));
    648 
    649 		do {
    650 			GSList *walk;
    651 			ItemPerson *person;
    652 			gtk_tree_model_get(GTK_TREE_MODEL(detail_store), &iter,
    653 			                   COL_ITEM, &person, -1);
    654 			for(walk = detail_to_select; walk; walk = walk->next) {
    655 				gchar *uid = walk->data;
    656 				if(uid && ADDRITEM_ID(person) &&
    657 				        (strcmp(uid,ADDRITEM_ID(person)) == 0))
    658 					gtk_tree_selection_select_iter(sel,&iter);
    659 			}
    660 		} while(gtk_tree_model_iter_next(GTK_TREE_MODEL(detail_store), &iter));
    661 	}
    662 }
    663 
    664 static void detail_row_activated(GtkTreeView       *tree_view,
    665                                  GtkTreePath       *path,
    666                                  GtkTreeViewColumn *column,
    667                                  gpointer           user_data)
    668 {
    669 	GtkTreeIter iter;
    670 	ItemPerson *person;
    671 	AddressDataSource *ds;
    672 	GtkTreeModel *model;
    673 	AddressBookFile *abf;
    674 
    675 	model = gtk_tree_view_get_model(tree_view);
    676 
    677 	if(!gtk_tree_model_get_iter(model,&iter,path))
    678 		return;
    679 
    680 	gtk_tree_model_get(model, &iter, COL_ITEM, &person, COL_DS, &ds, -1);
    681 
    682 
    683 	if(!(ds->type == ADDR_IF_BOOK)) {
    684 		debug_print("Unsupported address datasource type for editing\n");
    685 		return;
    686 	}
    687 
    688 	abf = ds->rawDataSource;
    689 	if(inline_edit_vbox)
    690 		gtk_widget_show_all(inline_edit_vbox);
    691 	if(editing_uid)
    692 		g_free(editing_uid);
    693 	editing_uid = g_strdup(ADDRITEM_ID(person));
    694 	addressbook_edit_person(abf,NULL,person,FALSE,inline_edit_vbox,
    695 	                        edit_post_update_cb,FALSE);
    696 }
    697 
    698 static void edit_post_update_cb(ItemPerson *item)
    699 {
    700 	GtkTreeSelection *sel;
    701 	gchar *email;
    702 	GList *detail_sel;
    703 	GList *walk;
    704 	GSList *detail;
    705 	GtkTreeIter iter;
    706 	GtkTreeModel *model;
    707 	ItemPerson *person;
    708 
    709 	/* save selection for after the update */
    710 
    711 	/* email -> string of email address */
    712 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(email_view));
    713 	if(gtk_tree_selection_get_selected(sel,NULL,&iter))
    714 		gtk_tree_model_get(GTK_TREE_MODEL(email_store), &iter, 0, &email, -1);
    715 	else
    716 		email = NULL;
    717 
    718 	/* detail -> GSList of ItemPerson UIDs */
    719 	detail = NULL;
    720 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(detail_view));
    721 	detail_sel = gtk_tree_selection_get_selected_rows(sel, &model);
    722 	for(walk = detail_sel; walk; walk = walk->next) {
    723 		GtkTreePath *path = walk->data;
    724 		if(!gtk_tree_model_get_iter(model,&iter,path))
    725 			continue;
    726 		gtk_tree_model_get(model, &iter, COL_ITEM, &person,-1);
    727 		detail = g_slist_prepend(detail, g_strdup(ADDRITEM_ID(person)));
    728 	}
    729 	g_list_foreach(detail_sel, (GFunc)gtk_tree_path_free, NULL);
    730 	g_list_free(detail_sel);
    731 
    732 	/* now refresh the stores, trying to keep the selections active */
    733 	refresh_stores(email,detail);
    734 
    735 	/* cleanup */
    736 	if(email)
    737 		g_free(email);
    738 	g_slist_foreach(detail, (GFunc)g_free, NULL);
    739 	g_slist_free(detail);
    740 }
    741 
    742 static void cb_edit_btn_clicked(GtkButton *button, gpointer data)
    743 {
    744 	GtkTreeSelection *selection;
    745 	GList *selected;
    746 	GtkTreeModel *model;
    747 
    748 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(detail_view));
    749 	selected = gtk_tree_selection_get_selected_rows(selection,&model);
    750 	cm_return_if_fail(selected);
    751 
    752 	/* we are guaranteed to have exactly one row selected */
    753 	gtk_tree_view_row_activated(GTK_TREE_VIEW(detail_view),(GtkTreePath*)selected->data,
    754 	                            gtk_tree_view_get_column(GTK_TREE_VIEW(detail_view),0));
    755 
    756 	g_list_foreach(selected, (GFunc)gtk_tree_path_free, NULL);
    757 	g_list_free(selected);
    758 }
    759 
    760 static void cb_del_btn_clicked(GtkButton *button, gpointer data)
    761 {
    762 	GtkTreeIter iter;
    763 	GtkTreeModel *model;
    764 	GtkTreeSelection *selection;
    765 	ItemPerson *item;
    766 	AddressDataSource *ds;
    767 	GList *list;
    768 	GList *ref_list;
    769 	GList *walk;
    770 	GtkTreeRowReference *ref;
    771 	AlertValue aval;
    772 	GtkTreeSelection *sel;
    773 	gchar *email;
    774 
    775 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(detail_view));
    776 
    777 	list = gtk_tree_selection_get_selected_rows(selection, &model);
    778 	cm_return_if_fail(list);
    779 
    780 	aval = alertpanel(_("Delete address(es)"),
    781 	                  _("Really delete the address(es)?"),
    782 	                  NULL, _("_Cancel"), "edit-delete", _("D_elete"),
    783 			  NULL, NULL, ALERTFOCUS_SECOND);
    784 	if(aval != G_ALERTALTERNATE)
    785 		return;
    786 
    787 	ref_list = NULL;
    788 	for(walk = list; walk; walk = walk->next) {
    789 		ref = gtk_tree_row_reference_new(model,(GtkTreePath*)(walk->data));
    790 		ref_list = g_list_prepend(ref_list, ref);
    791 	}
    792 	g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL);
    793 	g_list_free(list);
    794 
    795 	for(walk = ref_list; walk; walk = walk->next) {
    796 		GtkTreePath *path;
    797 		ref = walk->data;
    798 		if(!gtk_tree_row_reference_valid(ref))
    799 			continue;
    800 		path = gtk_tree_row_reference_get_path(ref);
    801 		if(gtk_tree_model_get_iter(model, &iter, path)) {
    802 			gtk_tree_model_get(model, &iter, COL_ITEM, &item, COL_DS, &ds, -1);
    803 			addrduplicates_delete_item_person(item,ds);
    804 		}
    805 		gtk_tree_path_free(path);
    806 	}
    807 
    808 	g_list_foreach(ref_list, (GFunc)gtk_tree_row_reference_free, NULL);
    809 	g_list_free(ref_list);
    810 
    811 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(email_view));
    812 	if(gtk_tree_selection_get_selected(sel,NULL,&iter))
    813 		gtk_tree_model_get(GTK_TREE_MODEL(email_store), &iter, 0, &email, -1);
    814 	else
    815 		email = NULL;
    816 	refresh_stores(email,NULL);
    817 	if(email)
    818 		g_free(email);
    819 }
    820 
    821 gboolean addrduplicates_delete_item_person(ItemPerson *item, AddressDataSource *ds)
    822 {
    823 	AddressBookFile *abf;
    824 	AddressInterface *iface;
    825 	if (!ds)
    826 		return FALSE;
    827 	/* Test for read only */
    828 	iface = ds->interface;
    829 	if( iface && iface->readOnly ) {
    830 		alertpanel(_("Delete address"),
    831 			   _("This address data is read-only and cannot be deleted."),
    832 			   "window-close", _("_Close"), NULL, NULL, NULL, NULL,
    833 			   ALERTFOCUS_FIRST );
    834 		return FALSE;
    835 	}
    836 
    837 	if(!(abf = ds->rawDataSource))
    838 		return FALSE;
    839 
    840 	item->status = DELETE_ENTRY;
    841 	item = addrbook_remove_person(abf, item);
    842 
    843 	if(item) {
    844 		addritem_person_remove_picture(item);
    845 		addritem_free_item_person(item);
    846 	}
    847 	return TRUE;
    848 }
    849 
    850 static gboolean cb_finder_results_dialog_key_pressed(GtkWidget *widget,
    851         GdkEventKey *event,
    852         gpointer data)
    853 {
    854 	if(event->keyval == GDK_KEY_Delete && detail_view_has_focus)
    855 		cb_del_btn_clicked(NULL,NULL);
    856 	return FALSE;
    857 }
    858 
    859 static gboolean detail_focus_in(GtkWidget *widget,
    860                                 GdkEventFocus *event,gpointer data)
    861 {
    862 	detail_view_has_focus = TRUE;
    863 	return FALSE;
    864 }
    865 
    866 static gboolean detail_focus_out(GtkWidget *widget,
    867                                  GdkEventFocus *event,gpointer data)
    868 {
    869 	detail_view_has_focus = FALSE;
    870 	return FALSE;
    871 }