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