Add a basic contact merging feature to the address book, thanks to
authorwwp <wwp@free.fr>
Mon, 9 Apr 2018 22:23:14 +0000 (00:23 +0200)
committerwwp <wwp@free.fr>
Mon, 9 Apr 2018 22:23:14 +0000 (00:23 +0200)
Charles Lehner <cel@celehner.com>.
Closes bug 3346: Contact merging

src/Makefile.am
src/addressbook.c
src/addressbook.h
src/addrmerge.c [new file with mode: 0644]

index d4eafbcb532cd5561e06f146be0a5a32317d9c47..460c1823dc1999525940c5030df446b1bb3b7801 100644 (file)
@@ -45,6 +45,7 @@ orig_abook_source = \
        addrquery.c \
        addrselect.c \
        addrduplicates.c \
+       addrmerge.c \
        browseldap.c \
        editaddress.c \
        editaddress_other_attributes_ldap.c \
@@ -93,6 +94,7 @@ abook_headers = \
        addrquery.h \
        addrselect.h \
        addrduplicates.h \
+       addrmerge.h \
        browseldap.h \
        editaddress.h \
        editaddress_other_attributes_ldap.h \
index 77c3b65a672293fb2544a36ec2e8a098f6640244..30adcce3d1d5fe9f811928ce859618ea73809a91 100644 (file)
@@ -57,6 +57,7 @@
 #include "addrcache.h"
 #include "addrbook.h"
 #include "addrindex.h"
+#include "addrmerge.h"
 #include "addressadd.h"
 #include "addrduplicates.h"
 #include "addressbook_foldersel.h"
@@ -306,10 +307,6 @@ static void addressbook_folder_load_one_person     (GtkCMCTree *clist,
                                                 ItemPerson *person,  
                                                 AddressTypeControlItem *atci, 
                                                 AddressTypeControlItem *atciMail);
-static void addressbook_folder_refresh_one_person(GtkCMCTree *clist, 
-                                                 ItemPerson *person);
-static void addressbook_folder_remove_one_person(GtkCMCTree *clist, 
-                                                ItemPerson *person);
 static void addressbook_folder_remove_node     (GtkCMCTree *clist, 
                                                 GtkCMCTreeNode *node);
 
@@ -352,6 +349,7 @@ static void addressbook_treenode_copy_cb    ( GtkAction *action, gpointer data );
 static void addressbook_treenode_paste_cb      ( GtkAction *action, gpointer data );
 
 static void addressbook_mail_to_cb             ( GtkAction *action, gpointer data );
+static void addressbook_merge_cb               ( GtkAction *action, gpointer data );
 
 #ifdef USE_LDAP
 static void addressbook_browse_entry_cb                ( GtkAction *action, gpointer data );
@@ -442,6 +440,7 @@ static GtkActionEntry addressbook_entries[] =
        {"Address/NewGroup",            NULL, N_("New _Group"), "<control>G", NULL, G_CALLBACK(addressbook_new_group_cb) },
        /* {"Address/---",                      NULL, "---", NULL, NULL, NULL }, */
        {"Address/Mailto",              NULL, N_("_Mail To"), "<control>M", NULL, G_CALLBACK(addressbook_mail_to_cb) },
+       {"Address/Merge",               NULL, N_("_Merge"), "<control>E", NULL, G_CALLBACK(addressbook_merge_cb) },
 
 
 /* Tools menu */
@@ -494,6 +493,7 @@ static GtkActionEntry addressbook_list_popup_entries[] =
 #ifdef USE_LDAP
        {"ABListPopup/BrowseEntry",     NULL, N_("_Browse Entry"), NULL, NULL, G_CALLBACK(addressbook_browse_entry_cb) },
 #endif
+       {"ABListPopup/Merge",           NULL, N_("_Merge"), NULL, NULL, G_CALLBACK(addressbook_merge_cb) },
 };
 
 /**
@@ -978,6 +978,7 @@ static void addressbook_create(void)
        MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Address", "NewGroup", "Address/NewGroup", GTK_UI_MANAGER_MENUITEM)
        MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Address", "Separator4", "Address/---", GTK_UI_MANAGER_SEPARATOR)
        MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Address", "Mailto", "Address/Mailto", GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Address", "Merge", "Address/Merge", GTK_UI_MANAGER_MENUITEM)
 
 /* Tools menu */
        MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Tools", "ImportLDIF", "Tools/ImportLDIF", GTK_UI_MANAGER_MENUITEM)
@@ -1281,6 +1282,7 @@ static void addressbook_create(void)
 #ifdef USE_LDAP
        MENUITEM_ADDUI_MANAGER(ui_manager, "/Popups/ABListPopup", "BrowseEntry", "ABListPopup/BrowseEntry", GTK_UI_MANAGER_MENUITEM)
 #endif
+       MENUITEM_ADDUI_MANAGER(ui_manager, "/Popups/ABListPopup", "Merge", "ABListPopup/Merge", GTK_UI_MANAGER_MENUITEM)
        list_popup = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
                                gtk_ui_manager_get_widget(ui_manager, "/Popups/ABListPopup")));
 
@@ -1797,6 +1799,7 @@ static void addressbook_menubar_set_sensitive( gboolean sensitive ) {
        cm_menu_set_sensitive_full( addrbook.ui_manager, "Menu/Address/NewAddress", sensitive );
        cm_menu_set_sensitive_full( addrbook.ui_manager, "Menu/Address/NewGroup",   sensitive );
        cm_menu_set_sensitive_full( addrbook.ui_manager, "Menu/Address/Mailto",     sensitive );
+       cm_menu_set_sensitive_full( addrbook.ui_manager, "Menu/Address/Merge",      sensitive );
        gtk_widget_set_sensitive( addrbook.edit_btn, sensitive );
        gtk_widget_set_sensitive( addrbook.del_btn, sensitive );
 }
@@ -1985,12 +1988,16 @@ static void addressbook_list_menu_setup( void ) {
        AdapterDSource *ads = NULL;
        AddressInterface *iface = NULL;
        AddressDataSource *ds = NULL;
+       GList *list;
+       AddrItemObject *aio;
+       AddrSelectItem *item;
        gboolean canEdit = FALSE;
        gboolean canDelete = FALSE;
        gboolean canCut = FALSE;
        gboolean canCopy = FALSE;
        gboolean canPaste = FALSE;
        gboolean canBrowse = FALSE;
+       gboolean canMerge = FALSE;
 
        pobj = gtk_cmctree_node_get_row_data( GTK_CMCTREE(addrbook.ctree), addrbook.treeSelected );
        if( pobj == NULL ) return;
@@ -2068,11 +2075,23 @@ static void addressbook_list_menu_setup( void ) {
                canBrowse = FALSE;
        }
 
+       /* Allow merging persons or emails are selected */
+       list = _addressSelect_->listSelect;
+       if (list && list->next ) {
+               item = list->data;
+               aio = ( AddrItemObject * ) item->addressItem;
+               if( aio->type == ITEMTYPE_EMAIL ||
+                               aio->type == ITEMTYPE_PERSON ) {
+                       canMerge = TRUE;
+               }
+       }
+
        /* Forbid write changes when read-only */
        if( iface && iface->readOnly ) {
                canCut = FALSE;
                canDelete = FALSE;
                canPaste = FALSE;
+               canMerge = FALSE;
        }
 
        /* Now go finalize menu items */
@@ -2084,6 +2103,7 @@ static void addressbook_list_menu_setup( void ) {
        cm_menu_set_sensitive_full( addrbook.ui_manager, "Popups/ABListPopup/Paste",         canPaste );
 
        cm_menu_set_sensitive_full( addrbook.ui_manager, "Popups/ABListPopup/Mailto",       canCopy );
+       cm_menu_set_sensitive_full( addrbook.ui_manager, "Popups/ABListPopup/Merge",        canMerge );
 
        cm_menu_set_sensitive_full( addrbook.ui_manager, "Menu/Address/Cut",           canCut );
        cm_menu_set_sensitive_full( addrbook.ui_manager, "Menu/Address/Copy",          canCopy );
@@ -2092,6 +2112,7 @@ static void addressbook_list_menu_setup( void ) {
        cm_menu_set_sensitive_full( addrbook.ui_manager, "Menu/Address/Edit",    canEdit );
        cm_menu_set_sensitive_full( addrbook.ui_manager, "Menu/Address/Delete",  canDelete );
        cm_menu_set_sensitive_full( addrbook.ui_manager, "Menu/Address/Mailto", canCopy );
+       cm_menu_set_sensitive_full( addrbook.ui_manager, "Menu/Address/Merge",  canMerge );
 
        gtk_widget_set_sensitive( addrbook.edit_btn, canEdit );
        gtk_widget_set_sensitive( addrbook.del_btn, canDelete );
@@ -2371,6 +2392,31 @@ static void addressbook_mail_to_cb( GtkAction *action, gpointer data ) {
        }
 }
 
+static void addressbook_merge_list( AddrSelectList *list ) {
+       GtkCMCTree *ctree = GTK_CMCTREE(addrbook.ctree);
+       GtkCMCTree *clist = GTK_CMCTREE(addrbook.clist);
+       AddressObject *pobj;
+       AddressDataSource *ds = NULL;
+
+       pobj = gtk_cmctree_node_get_row_data(ctree, addrbook.opened );
+       cm_return_if_fail(pobj != NULL);
+
+       ds = addressbook_find_datasource( addrbook.treeSelected );
+       if( ds == NULL ) return;
+
+       addrmerge_merge(clist, pobj, ds, list);
+}
+
+/**
+ * Merge selected entries in the address list
+ */
+static void addressbook_merge_cb( GtkAction *action, gpointer data ) {
+       if( addrselect_test_empty( _addressSelect_ ) )
+               return;
+
+       addressbook_merge_list( _addressSelect_ );
+}
+
 static void addressbook_list_row_selected( GtkCMCTree *clist,
                                           GtkCMCTreeNode *node,
                                           gint column,
@@ -3739,7 +3785,7 @@ static void addressbook_folder_remove_node( GtkCMCTree *clist, GtkCMCTreeNode *n
                addrbook.treeSelected );
 }
 
-static void addressbook_folder_refresh_one_person( GtkCMCTree *clist, ItemPerson *person ) {
+void addressbook_folder_refresh_one_person( GtkCMCTree *clist, ItemPerson *person ) {
        AddressTypeControlItem *atci = addrbookctl_lookup( ADDR_ITEM_PERSON );
        AddressTypeControlItem *atciMail = addrbookctl_lookup( ADDR_ITEM_EMAIL );
        GtkCMCTreeNode *node;
@@ -3761,7 +3807,7 @@ static void addressbook_folder_refresh_one_person( GtkCMCTree *clist, ItemPerson
        }
 }
 
-static void addressbook_folder_remove_one_person( GtkCMCTree *clist, ItemPerson *person ) {
+void addressbook_folder_remove_one_person( GtkCMCTree *clist, ItemPerson *person ) {
        GtkCMCTreeNode *node;
        
        if( person == NULL ) return;
index ce5c7079be5fd4039d67deb138849faeec4e9ed9..beb92cdf83d51355e18e0d35934664083fd55f51 100644 (file)
@@ -60,5 +60,10 @@ void addressbook_refresh( void );
 gchar *addressbook_set_col_name_guard(gchar *value);
 void addressbook_reflect_prefs_pixmap_theme(void);
 
+void addressbook_folder_refresh_one_person(GtkCMCTree *clist,
+                                                 ItemPerson *person);
+void addressbook_folder_remove_one_person(GtkCMCTree *clist,
+                                                ItemPerson *person);
+
 #endif /* __ADDRESSBOOK_H__ */
 
diff --git a/src/addrmerge.c b/src/addrmerge.c
new file mode 100644 (file)
index 0000000..363bab7
--- /dev/null
@@ -0,0 +1,496 @@
+/* Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2014 Charles Lehner and the Claws Mail team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <string.h>
+
+#include "defs.h"
+
+#ifdef USE_LDAP
+#include "ldapserver.h"
+#include "ldapupdate.h"
+#endif
+#include "addrbook.h"
+#include "addressbook.h"
+#include "addressitem.h"
+#include "addrmerge.h"
+#include "alertpanel.h"
+#include "gtkutils.h"
+#include "utils.h"
+#include "prefs_common.h"
+
+enum
+{
+       COL_DISPLAYNAME,
+       COL_FIRSTNAME,
+       COL_LASTNAME,
+       COL_NICKNAME,
+       N_NAME_COLS
+};
+
+enum
+{
+       SET_ICON,
+       SET_PERSON,
+       N_SET_COLUMNS
+};
+
+static void addrmerge_done(struct AddrMergePage *page)
+{
+       g_list_free(page->emails);
+       g_list_free(page->persons);
+       gtk_widget_destroy(GTK_WIDGET(page->dialog));
+       g_free(page);
+}
+
+static void addrmerge_do_merge(struct AddrMergePage *page)
+{
+       GList *node;
+       ItemEMail *email;
+       ItemPerson *person;
+       ItemPerson *target = page->target;
+       ItemPerson *nameTarget = page->nameTarget;
+
+       gtk_cmclist_freeze(GTK_CMCLIST(page->clist));
+
+       /* Update target name */
+       if (nameTarget && nameTarget != target) {
+               target->status = UPDATE_ENTRY;
+               addritem_person_set_first_name( target, nameTarget->firstName );
+               addritem_person_set_last_name( target, nameTarget->lastName );
+               addritem_person_set_nick_name( target, nameTarget->nickName );
+               addritem_person_set_common_name( target, ADDRITEM_NAME(nameTarget ));
+       }
+
+       /* Merge emails into target */
+       for (node = page->emails; node; node = node->next) {
+               email = node->data;
+               person = ( ItemPerson * ) ADDRITEM_PARENT(email);
+               /* Remove the email from the person */
+               email = addrbook_person_remove_email( page->abf, person, email );
+               if( email ) {
+                       addrcache_remove_email( page->abf->addressCache, email );
+                       /* Add the email to the target */
+                       addrcache_person_add_email( page->abf->addressCache, target, email );
+               }
+               person->status = UPDATE_ENTRY;
+               addressbook_folder_refresh_one_person( page->clist, person );
+       }
+
+       /* Merge persons into target */
+       for (node = page->persons; node; node = node->next) {
+               GList *nodeE, *nodeA;
+               person = node->data;
+
+               if (person == target) continue;
+               person->status = DELETE_ENTRY;
+
+               /* Move all emails to the target */
+               for (nodeE = person->listEMail; nodeE; nodeE = nodeE->next) {
+                       email = nodeE->data;
+                       addritem_person_add_email( target, email );
+               }
+               g_list_free( person->listEMail );
+               person->listEMail = NULL;
+
+               /* Move all attributes to the target */
+               for (nodeA = person->listAttrib; nodeA; nodeA = nodeA->next) {
+                       UserAttribute *attrib = nodeA->data;
+                       addritem_person_add_attribute( target, attrib );
+               }
+               g_list_free( person->listAttrib );
+               person->listAttrib = NULL;
+
+               /* Remove the person */
+               addrselect_list_remove( page->addressSelect, (AddrItemObject *)person );
+               addressbook_folder_remove_one_person( page->clist, person );
+               if (page->pobj->type == ADDR_ITEM_FOLDER)
+                       addritem_folder_remove_person(ADAPTER_FOLDER(page->pobj)->itemFolder, person);
+               person = addrbook_remove_person( page->abf, person );
+
+               if( person ) {
+                       gchar *filename = addritem_person_get_picture(person);
+                       if ((strcmp2(person->picture, target->picture) &&
+                                       filename && is_file_exist(filename)))
+                               claws_unlink(filename);
+                       if (filename)
+                               g_free(filename);
+                       addritem_free_item_person( person );
+               }
+       }
+
+       addressbook_folder_refresh_one_person( page->clist, target );
+
+       addrbook_set_dirty( page->abf, TRUE );
+       addressbook_export_to_file();
+
+#ifdef USE_LDAP
+       if (page->ds && page->ds->type == ADDR_IF_LDAP) {
+               LdapServer *server = page->ds->rawDataSource;
+               ldapsvr_set_modified(server, TRUE);
+               ldapsvr_update_book(server, NULL);
+       }
+#endif
+       gtk_cmclist_thaw(GTK_CMCLIST(page->clist));
+
+       addrmerge_done(page);
+}
+
+static void addrmerge_dialog_cb(GtkWidget* widget, gint action, gpointer data) {
+       struct AddrMergePage* page = data;
+
+       if (action != GTK_RESPONSE_ACCEPT)
+               return addrmerge_done(page);
+
+       addrmerge_do_merge(page);
+}
+
+static void addrmerge_update_dialog_sensitive( struct AddrMergePage *page )
+{
+       gboolean canMerge = (page->target && page->nameTarget);
+       gtk_dialog_set_response_sensitive( GTK_DIALOG(page->dialog),
+                       GTK_RESPONSE_ACCEPT, canMerge );
+}
+
+static void addrmerge_name_selected( GtkCMCList *clist, gint row, gint column, GdkEvent *event, struct AddrMergePage *page )
+{
+       ItemPerson *person = gtk_cmclist_get_row_data( clist, row );
+       page->nameTarget = person;
+       addrmerge_update_dialog_sensitive(page);
+}
+
+static void addrmerge_picture_selected(GtkTreeView *treeview,
+               struct AddrMergePage *page)
+{
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       GList *list;
+       ItemPerson *pictureTarget;
+
+       /* Get selected picture target */ 
+       model = gtk_icon_view_get_model(GTK_ICON_VIEW(page->iconView));
+       list = gtk_icon_view_get_selected_items(GTK_ICON_VIEW(page->iconView));
+       page->target = NULL;
+       if (list != NULL) {
+               if (gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)list->data)) {
+                       gtk_tree_model_get(model, &iter,
+                                       SET_PERSON, &pictureTarget,
+                                       -1);
+                       page->target = pictureTarget;
+               }
+
+               gtk_tree_path_free(list->data);
+               g_list_free(list);
+       }
+       addrmerge_update_dialog_sensitive(page);
+}
+
+static void addrmerge_prompt( struct AddrMergePage *page )
+{
+       GtkWidget *dialog;
+       GtkWidget *frame;
+       GtkWidget *mvbox, *vbox, *hbox;
+       GtkWidget *label;
+       GtkWidget *iconView = NULL;
+       GtkWidget *namesList = NULL;
+       MainWindow *mainwin = mainwindow_get_mainwindow();
+       GtkListStore *store = NULL;
+       GtkTreeIter iter;
+       GList *node;
+       ItemPerson *person;
+       GError *error = NULL;
+       gchar *msg, *label_msg;
+
+       dialog = page->dialog = gtk_dialog_new_with_buttons (
+                       _("Merge addresses"),
+                       GTK_WINDOW(mainwin->window),
+                       GTK_DIALOG_DESTROY_WITH_PARENT,
+                       GTK_STOCK_CANCEL,
+                       GTK_RESPONSE_CANCEL,
+                       "_Merge",
+                       GTK_RESPONSE_ACCEPT,
+                       NULL);
+
+       g_signal_connect ( dialog, "response",
+                       G_CALLBACK(addrmerge_dialog_cb), page);
+
+       mvbox = gtk_vbox_new(FALSE, 4);
+       gtk_container_add(GTK_CONTAINER(
+                       gtk_dialog_get_content_area(GTK_DIALOG(dialog))), mvbox);
+       gtk_container_set_border_width(GTK_CONTAINER(mvbox), 8);
+       hbox = gtk_hbox_new(FALSE, 4);
+       gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
+       gtk_box_pack_start(GTK_BOX(mvbox),
+                       hbox, FALSE, FALSE, 0);
+
+       msg = page->pickPicture || page->pickName ?
+               _("Merging %u contacts." ) :
+               _("Really merge these %u contacts?" );
+       label_msg = g_strdup_printf(msg,
+                       g_list_length(page->addressSelect->listSelect));
+       label = gtk_label_new( label_msg );
+       gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
+       gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       g_free(label_msg);
+
+       if (page->pickPicture) {
+               GtkWidget *scrollwinPictures;
+
+               store = gtk_list_store_new(N_SET_COLUMNS,
+                                       GDK_TYPE_PIXBUF,
+                                       G_TYPE_POINTER,
+                                       -1);
+               gtk_list_store_clear(store);
+
+               vbox = gtkut_get_options_frame(mvbox, &frame,
+                               _("Keep which picture?"));
+               gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
+
+               scrollwinPictures = gtk_scrolled_window_new(NULL, NULL);
+               gtk_container_set_border_width(GTK_CONTAINER(scrollwinPictures), 1);
+               gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwinPictures),
+                               GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+               gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwinPictures),
+                               GTK_SHADOW_IN);
+               gtk_box_pack_start (GTK_BOX (vbox), scrollwinPictures, FALSE, FALSE, 0);
+               gtk_widget_set_size_request(scrollwinPictures, 464, 192);
+
+               iconView = gtk_icon_view_new_with_model(GTK_TREE_MODEL(store));
+               gtk_icon_view_set_selection_mode(GTK_ICON_VIEW(iconView), GTK_SELECTION_SINGLE);
+               gtk_icon_view_set_pixbuf_column(GTK_ICON_VIEW(iconView), SET_ICON);
+               gtk_container_add(GTK_CONTAINER(scrollwinPictures), GTK_WIDGET(iconView));
+               g_signal_connect(G_OBJECT(iconView), "selection-changed",
+                               G_CALLBACK(addrmerge_picture_selected), page);
+
+               /* Add pictures from persons */
+               for (node = page->persons; node; node = node->next) {
+                       gchar *filename;
+                       person = node->data;
+                       filename = addritem_person_get_picture(person);
+                       if (filename && is_file_exist(filename)) {
+                               GdkPixbuf *pixbuf;
+                               GtkWidget *image;
+
+                               pixbuf = gdk_pixbuf_new_from_file(filename, &error);
+                               if (error) {
+                                       debug_print("Failed to read image: \n%s",
+                                                       error->message);
+                                       g_error_free(error);
+                                       continue;
+                               }
+
+                               image = gtk_image_new();
+                               gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
+
+                               gtk_list_store_append(store, &iter);
+                               gtk_list_store_set(store, &iter,
+                                               SET_ICON, pixbuf,
+                                               SET_PERSON, person,
+                                               -1);
+                       }
+                       if (filename)
+                               g_free(filename);
+               }
+       }
+
+       if (page->pickName) {
+               GtkWidget *scrollwinNames;
+               gchar *name_titles[N_NAME_COLS];
+
+               name_titles[COL_DISPLAYNAME] = _("Display Name");
+               name_titles[COL_FIRSTNAME] = _("First Name");
+               name_titles[COL_LASTNAME] = _("Last Name");
+               name_titles[COL_NICKNAME] = _("Nickname");
+
+               store = gtk_list_store_new(N_SET_COLUMNS,
+                                       GDK_TYPE_PIXBUF,
+                                       G_TYPE_POINTER,
+                                       -1);
+               gtk_list_store_clear(store);
+
+               vbox = gtkut_get_options_frame(mvbox, &frame,
+                               _("Keep which name?"));
+               gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
+
+               scrollwinNames = gtk_scrolled_window_new(NULL, NULL);
+               gtk_container_set_border_width(GTK_CONTAINER(scrollwinNames), 1);
+               gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwinNames),
+                               GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+               gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwinNames),
+                               GTK_SHADOW_IN);
+               gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(scrollwinNames), FALSE, FALSE, 0);
+
+               namesList = gtk_cmclist_new_with_titles(N_NAME_COLS, name_titles);
+               gtk_widget_set_can_focus(GTK_CMCLIST(namesList)->column[0].button, FALSE);
+               gtk_cmclist_set_selection_mode(GTK_CMCLIST(namesList), GTK_SELECTION_BROWSE);
+               gtk_cmclist_set_column_width(GTK_CMCLIST(namesList), COL_DISPLAYNAME, 164);
+
+               gtk_container_add(GTK_CONTAINER(scrollwinNames), namesList);
+
+               /* Add names from persons */
+               for (node = page->persons; node; node = node->next) {
+                       int row;
+                       person = node->data;
+                       gchar *text[N_NAME_COLS];
+                       text[COL_DISPLAYNAME] = ADDRITEM_NAME(person);
+                       text[COL_FIRSTNAME] = person->firstName;
+                       text[COL_LASTNAME] = person->lastName;
+                       text[COL_NICKNAME] = person->nickName;
+                       row = gtk_cmclist_insert( GTK_CMCLIST(namesList), -1, text );
+                       gtk_cmclist_set_row_data( GTK_CMCLIST(namesList), row, person );
+               }
+
+               g_signal_connect(G_OBJECT(namesList), "select_row",
+                               G_CALLBACK(addrmerge_name_selected), page);
+       }
+
+       page->iconView = iconView;
+       page->namesList = namesList;
+
+       addrmerge_update_dialog_sensitive(page);
+       gtk_widget_show_all(dialog);
+}
+
+void addrmerge_merge(
+               GtkCMCTree *clist,
+               AddressObject *pobj,
+               AddressDataSource *ds,
+               AddrSelectList *list)
+{
+       struct AddrMergePage* page;
+       AdapterDSource *ads = NULL;
+       AddressBookFile *abf;
+       gboolean procFlag;
+       GList *node;
+       AddrSelectItem *item;
+       AddrItemObject *aio;
+       ItemPerson *person, *target = NULL, *nameTarget = NULL;
+       GList *persons = NULL, *emails = NULL;
+       gboolean pickPicture = FALSE, pickName = FALSE;
+
+       /* Test for read only */
+       if( ds->interface->readOnly ) {
+               alertpanel( _("Merge addresses"),
+                       _("This address data is readonly and cannot be deleted."),
+                       GTK_STOCK_CLOSE, NULL, NULL, ALERTFOCUS_FIRST );
+               return;
+       }
+
+       /* Test whether Ok to proceed */
+       procFlag = FALSE;
+       if( pobj->type == ADDR_DATASOURCE ) {
+               ads = ADAPTER_DSOURCE(pobj);
+               if( ads->subType == ADDR_BOOK ) procFlag = TRUE;
+       }
+       else if( pobj->type == ADDR_ITEM_FOLDER ) {
+               procFlag = TRUE;
+       }
+       else if( pobj->type == ADDR_ITEM_GROUP ) {
+               procFlag = TRUE;
+       }
+       if( ! procFlag ) return;
+       abf = ds->rawDataSource;
+       if( abf == NULL ) return;
+
+       /* Gather selected persons and emails */
+       for (node = list->listSelect; node; node = node->next) {
+               item = node->data;
+               aio = ( AddrItemObject * ) item->addressItem;
+               if( aio->type == ITEMTYPE_EMAIL ) {
+                       emails = g_list_prepend(emails, aio);
+               } else if( aio->type == ITEMTYPE_PERSON ) {
+                       persons = g_list_prepend(persons, aio);
+               }
+       }
+
+       /* Check if more than one person has a picture */
+       for (node = persons; node; node = node->next) {
+               gchar *filename;
+               person = node->data;
+               filename = addritem_person_get_picture(person);
+               if (filename && is_file_exist(filename)) {
+                       if (target == NULL) {
+                               target = person;
+                       } else {
+                               pickPicture = TRUE;
+                               target = NULL;
+                               break;
+                       }
+               }
+               if (filename)
+                       g_free(filename);
+       }
+       if (pickPicture || target) {
+               /* At least one person had a picture */
+       } else if (persons && persons->data) {
+               /* No person had a picture. Use the first person as target */
+               target = persons->data;
+       } else {
+               /* No persons in list. Abort */
+               goto abort;
+       }
+
+       /* Pick which name to keep */
+       for (node = persons; node; node = node->next) {
+               person = node->data;
+               if (nameTarget == NULL) {
+                       nameTarget = person;
+               } else if (nameTarget == person) {
+                       continue;
+               } else if (strcmp2(person->firstName, nameTarget->firstName) ||
+                               strcmp2(person->lastName, nameTarget->lastName) ||
+                               strcmp2(person->nickName, nameTarget->nickName) ||
+                               strcmp2(ADDRITEM_NAME(person), ADDRITEM_NAME(nameTarget))) {
+                       pickName = TRUE;
+                       break;
+               }
+       }
+       if (!nameTarget) {
+               /* No persons in list */
+               goto abort;
+       }
+
+       /* Create object */
+       page = g_new0(struct AddrMergePage, 1);
+       page->pickPicture = pickPicture;
+       page->pickName = pickName;
+       page->target = target;
+       page->nameTarget = nameTarget;
+       page->addressSelect = list;
+       page->persons = persons;
+       page->emails = emails;
+       page->clist = clist;
+       page->pobj = pobj;
+       page->abf = abf;
+       page->ds = ds;
+
+       addrmerge_prompt(page);
+       return;
+
+abort:
+       g_list_free( emails );
+       g_list_free( persons );
+}