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 }