aboutsummaryrefslogtreecommitdiffstats
path: root/addressbook/gui/widgets
diff options
context:
space:
mode:
authorMilan Crha <mcrha@redhat.com>2013-09-24 15:21:34 +0800
committerMilan Crha <mcrha@redhat.com>2013-09-24 15:21:34 +0800
commita3b2e00c235aecaaaad14881d0444e840089cf7e (patch)
treee0b310e510873e7edc56f1424f7e41ba62cd6a30 /addressbook/gui/widgets
parent0055354699ac7e1306b6dac1f5e16710370519af (diff)
downloadgsoc2013-evolution-a3b2e00c235aecaaaad14881d0444e840089cf7e.tar
gsoc2013-evolution-a3b2e00c235aecaaaad14881d0444e840089cf7e.tar.gz
gsoc2013-evolution-a3b2e00c235aecaaaad14881d0444e840089cf7e.tar.bz2
gsoc2013-evolution-a3b2e00c235aecaaaad14881d0444e840089cf7e.tar.lz
gsoc2013-evolution-a3b2e00c235aecaaaad14881d0444e840089cf7e.tar.xz
gsoc2013-evolution-a3b2e00c235aecaaaad14881d0444e840089cf7e.tar.zst
gsoc2013-evolution-a3b2e00c235aecaaaad14881d0444e840089cf7e.zip
Bug #659890 - Merge addressbook 'widgets' and 'merging' libraries
These two cross-referenced each other, which could cause runtime issues, thus rather merge them into one.
Diffstat (limited to 'addressbook/gui/widgets')
-rw-r--r--addressbook/gui/widgets/Makefile.am21
-rw-r--r--addressbook/gui/widgets/eab-contact-commit-duplicate-detected.ui185
-rw-r--r--addressbook/gui/widgets/eab-contact-compare.c839
-rw-r--r--addressbook/gui/widgets/eab-contact-compare.h101
-rw-r--r--addressbook/gui/widgets/eab-contact-duplicate-detected.ui219
-rw-r--r--addressbook/gui/widgets/eab-contact-merging.c797
-rw-r--r--addressbook/gui/widgets/eab-contact-merging.h66
7 files changed, 2222 insertions, 6 deletions
diff --git a/addressbook/gui/widgets/Makefile.am b/addressbook/gui/widgets/Makefile.am
index f9fea781a8..1c7e8f090e 100644
--- a/addressbook/gui/widgets/Makefile.am
+++ b/addressbook/gui/widgets/Makefile.am
@@ -11,9 +11,9 @@ libeabwidgets_la_CPPFLAGS = \
-DEVOLUTION_RULEDIR=\"$(ruledir)\" \
-DEVOLUTION_IMAGESDIR=\"${imagesdir}\" \
-DEVOLUTION_PRIVDATADIR=\"${privdatadir}\" \
+ -DEVOLUTION_UIDIR=\""$(uidir)"\" \
-I$(top_srcdir) \
-I$(top_srcdir)/addressbook \
- -I$(top_srcdir)/addressbook/gui/merging \
-I$(top_srcdir)/addressbook/util \
-I$(top_builddir)/shell \
$(EVOLUTION_DATA_SERVER_CFLAGS) \
@@ -29,10 +29,14 @@ eabinclude_HEADERS = \
libeabwidgets_la_SOURCES = \
eab-config.c \
+ eab-contact-compare.c \
+ eab-contact-compare.h \
eab-contact-display.c \
eab-contact-display.h \
eab-contact-formatter.c \
eab-contact-formatter.h \
+ eab-contact-merging.c \
+ eab-contact-merging.h \
eab-gui-util.c \
eab-gui-util.h \
e-contact-map.c \
@@ -61,12 +65,12 @@ libeabwidgets_la_SOURCES = \
e-addressbook-view.h \
gal-view-minicard.c \
gal-view-minicard.h \
- ea-minicard.c \
- ea-minicard.h \
+ ea-minicard.c \
+ ea-minicard.h \
ea-minicard-view.c \
ea-minicard-view.h \
- ea-addressbook-view.c \
- ea-addressbook-view.h \
+ ea-addressbook-view.c \
+ ea-addressbook-view.h \
ea-addressbook.c \
ea-addressbook.h
@@ -85,8 +89,13 @@ dist-hook:
etspec_DATA= e-addressbook-view.etspec
+ui_DATA = \
+ eab-contact-duplicate-detected.ui \
+ eab-contact-commit-duplicate-detected.ui
+
EXTRA_DIST = \
$(etspec_DATA) \
- $(rule_DATA)
+ $(rule_DATA) \
+ $(ui_DATA)
-include $(top_srcdir)/git.mk
diff --git a/addressbook/gui/widgets/eab-contact-commit-duplicate-detected.ui b/addressbook/gui/widgets/eab-contact-commit-duplicate-detected.ui
new file mode 100644
index 0000000000..04ed728a56
--- /dev/null
+++ b/addressbook/gui/widgets/eab-contact-commit-duplicate-detected.ui
@@ -0,0 +1,185 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkDialog" id="dialog-duplicate-contact">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Duplicate Contact Detected</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="button3">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button4">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-save</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="n_rows">5</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="EABContactDisplay" type-func="eab_contact_display_get_type" id="custom-old-contact">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">The name or email of this contact already exists in this folder. Would you like to save the changes anyway?</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes" comments="Translators: Heading of the contact which has same name or email address in this folder already.">Conflicting Contact:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Changed Contact:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="EABContactDisplay" type-func="eab_contact_display_get_type" id="custom-new-contact">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0</property>
+ <property name="xscale">1</property>
+ <property name="yscale">0</property>
+ <child>
+ <object class="GtkImage" id="custom2">
+ <property name="visible">True</property>
+ <property name="icon-name">avatar-default</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="1">button3</action-widget>
+ <action-widget response="0">button4</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/addressbook/gui/widgets/eab-contact-compare.c b/addressbook/gui/widgets/eab-contact-compare.c
new file mode 100644
index 0000000000..bfca37cbff
--- /dev/null
+++ b/addressbook/gui/widgets/eab-contact-compare.c
@@ -0,0 +1,839 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Jon Trowbridge <trow@ximian.com>
+ * Chris Toshok <toshok@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <string.h>
+
+#include "e-util/e-util.h"
+
+#include "addressbook/util/eab-book-util.h"
+#include "eab-contact-compare.h"
+
+/* This is an "optimistic" combiner: the best of the two outcomes is
+ * selected. */
+static EABContactMatchType
+combine_comparisons (EABContactMatchType prev,
+ EABContactMatchType new_info)
+{
+ if (new_info == EAB_CONTACT_MATCH_NOT_APPLICABLE)
+ return prev;
+ return (EABContactMatchType) MAX ((gint) prev, (gint) new_info);
+}
+
+/*** Name comparisons ***/
+
+/* This *so* doesn't belong here... at least not implemented in a
+ * sucky way like this. But it can be fixed later. */
+
+/* This is very Anglocentric. */
+static const gchar *name_synonyms[][2] = {
+ { "jon", "john" }, /* Ah, the hacker's perogative */
+ { "joseph", "joe" },
+ { "robert", "bob" },
+ { "gene", "jean" },
+ { "jesse", "jessie" },
+ { "ian", "iain" },
+ { "richard", "dick" },
+ { "william", "bill" },
+ { "william", "will" },
+ { "anthony", "tony" },
+ { "michael", "mike" },
+ { "eric", "erik" },
+ { "elizabeth", "liz" },
+ { "jeff", "geoff" },
+ { "jeff", "geoffrey" },
+ { "tom", "thomas" },
+ { "dave", "david" },
+ { "jim", "james" },
+ { "abigal", "abby" },
+ { "amanda", "amy" },
+ { "amanda", "manda" },
+ { "jennifer", "jenny" },
+ { "christopher", "chris" },
+ { "rebecca", "becca" },
+ { "rebecca", "becky" },
+ { "anderson", "andersen" },
+ { "johnson", "johnsen" },
+ /* We could go on and on... */
+ /* We should add soundex here. */
+ { NULL, NULL }
+};
+
+static gboolean
+name_fragment_match_with_synonyms (const gchar *a,
+ const gchar *b,
+ gboolean strict)
+{
+ gint i;
+
+ if (!(a && b && *a && *b))
+ return FALSE;
+
+ if (!e_utf8_casefold_collate (a, b))
+ return TRUE;
+
+ /* Check for nicknames. Yes, the linear search blows. */
+ for (i = 0; name_synonyms[i][0]; ++i) {
+
+ if (!e_utf8_casefold_collate (name_synonyms[i][0], a)
+ && !e_utf8_casefold_collate (name_synonyms[i][1], b))
+ return TRUE;
+
+ if (!e_utf8_casefold_collate (name_synonyms[i][0], b)
+ && !e_utf8_casefold_collate (name_synonyms[i][1], a))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+EABContactMatchType
+eab_contact_compare_name_to_string (EContact *contact,
+ const gchar *str)
+{
+ return eab_contact_compare_name_to_string_full (contact, str, FALSE, NULL, NULL, NULL);
+}
+
+EABContactMatchType
+eab_contact_compare_name_to_string_full (EContact *contact,
+ const gchar *str,
+ gboolean allow_partial_matches,
+ gint *matched_parts_out,
+ EABContactMatchPart *first_matched_part_out,
+ gint *matched_character_count_out)
+{
+ gchar **namev, **givenv = NULL, **addv = NULL, **familyv = NULL;
+
+ gint matched_parts = EAB_CONTACT_MATCH_PART_NONE;
+ EABContactMatchPart first_matched_part = EAB_CONTACT_MATCH_PART_NONE;
+ EABContactMatchPart this_part_match = EAB_CONTACT_MATCH_PART_NOT_APPLICABLE;
+ EABContactMatchType match_type;
+ EContactName *contact_name;
+
+ gint match_count = 0, matched_character_count = 0, fragment_count;
+ gint i, j;
+ gchar *str_cpy, *s;
+
+ g_return_val_if_fail (E_IS_CONTACT (contact), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+
+ if (!e_contact_get_const (contact, E_CONTACT_FULL_NAME))
+ return EAB_CONTACT_MATCH_NOT_APPLICABLE;
+ if (str == NULL)
+ return EAB_CONTACT_MATCH_NOT_APPLICABLE;
+
+ str_cpy = s = g_strdup (str);
+ while (*s) {
+ if (*s == ',' || *s == '"')
+ *s = ' ';
+ ++s;
+ }
+ namev = g_strsplit (str_cpy, " ", 0);
+ g_free (str_cpy);
+
+ contact_name = e_contact_get (contact, E_CONTACT_NAME);
+
+ if (contact_name->given)
+ givenv = g_strsplit (contact_name->given, " ", 0);
+ if (contact_name->additional)
+ addv = g_strsplit (contact_name->additional, " ", 0);
+ if (contact_name->family)
+ familyv = g_strsplit (contact_name->family, " ", 0);
+
+ e_contact_name_free (contact_name);
+
+ fragment_count = 0;
+ for (i = 0; givenv && givenv[i]; ++i)
+ ++fragment_count;
+ for (i = 0; addv && addv[i]; ++i)
+ ++fragment_count;
+ for (i = 0; familyv && familyv[i]; ++i)
+ ++fragment_count;
+
+ for (i = 0; namev[i] && this_part_match != EAB_CONTACT_MATCH_PART_NONE; ++i) {
+
+ if (*namev[i]) {
+
+ this_part_match = EAB_CONTACT_MATCH_PART_NONE;
+
+ /* When we are allowing partials, we are strict about the matches we allow.
+ * Does this make sense? Not really, but it does the right thing for the purposes
+ * of completion. */
+
+ if (givenv && this_part_match == EAB_CONTACT_MATCH_PART_NONE) {
+ for (j = 0; givenv[j]; ++j) {
+ if (name_fragment_match_with_synonyms (givenv[j], namev[i], allow_partial_matches)) {
+
+ this_part_match = EAB_CONTACT_MATCH_PART_GIVEN_NAME;
+
+ /* We remove a piece of a name once it has been matched against, so
+ * that "john john" won't match "john doe". */
+ g_free (givenv[j]);
+ givenv[j] = g_strdup ("");
+ break;
+ }
+ }
+ }
+
+ if (addv && this_part_match == EAB_CONTACT_MATCH_PART_NONE) {
+ for (j = 0; addv[j]; ++j) {
+ if (name_fragment_match_with_synonyms (addv[j], namev[i], allow_partial_matches)) {
+
+ this_part_match = EAB_CONTACT_MATCH_PART_ADDITIONAL_NAME;
+
+ g_free (addv[j]);
+ addv[j] = g_strdup ("");
+ break;
+ }
+ }
+ }
+
+ if (familyv && this_part_match == EAB_CONTACT_MATCH_PART_NONE) {
+ for (j = 0; familyv[j]; ++j) {
+ if (allow_partial_matches ? name_fragment_match_with_synonyms (familyv[j], namev[i], allow_partial_matches)
+ : !e_utf8_casefold_collate (familyv[j], namev[i])) {
+
+ this_part_match = EAB_CONTACT_MATCH_PART_FAMILY_NAME;
+
+ g_free (familyv[j]);
+ familyv[j] = g_strdup ("");
+ break;
+ }
+ }
+ }
+
+ if (this_part_match != EAB_CONTACT_MATCH_PART_NONE) {
+ ++match_count;
+ matched_character_count += g_utf8_strlen (namev[i], -1);
+ matched_parts |= this_part_match;
+ if (first_matched_part == EAB_CONTACT_MATCH_PART_NONE)
+ first_matched_part = this_part_match;
+ }
+ }
+ }
+
+ match_type = EAB_CONTACT_MATCH_NONE;
+
+ if (this_part_match != EAB_CONTACT_MATCH_PART_NONE) {
+
+ if (match_count > 0)
+ match_type = EAB_CONTACT_MATCH_VAGUE;
+
+ if (fragment_count == match_count) {
+
+ match_type = EAB_CONTACT_MATCH_EXACT;
+
+ } else if (fragment_count == match_count + 1) {
+
+ match_type = EAB_CONTACT_MATCH_PARTIAL;
+
+ }
+ }
+
+ if (matched_parts_out)
+ *matched_parts_out = matched_parts;
+ if (first_matched_part_out)
+ *first_matched_part_out = first_matched_part;
+ if (matched_character_count_out)
+ *matched_character_count_out = matched_character_count;
+
+ g_strfreev (namev);
+ g_strfreev (givenv);
+ g_strfreev (addv);
+ g_strfreev (familyv);
+
+ return match_type;
+}
+
+EABContactMatchType
+eab_contact_compare_file_as (EContact *contact1,
+ EContact *contact2)
+{
+ EABContactMatchType match_type;
+ gchar *a, *b;
+
+ g_return_val_if_fail (E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+
+ a = e_contact_get (contact1, E_CONTACT_FILE_AS);
+ b = e_contact_get (contact2, E_CONTACT_FILE_AS);
+
+ if (a == NULL || b == NULL) {
+ g_free (a);
+ g_free (b);
+ return EAB_CONTACT_MATCH_NOT_APPLICABLE;
+ }
+
+ if (!strcmp (a, b))
+ match_type = EAB_CONTACT_MATCH_EXACT;
+ else if (g_utf8_validate (a, -1, NULL) && g_utf8_validate (b, -1, NULL) &&
+ !g_utf8_collate (a, b))
+ match_type = EAB_CONTACT_MATCH_PARTIAL;
+ else
+ match_type = EAB_CONTACT_MATCH_NONE;
+
+ g_free (a);
+ g_free (b);
+ return match_type;
+}
+
+EABContactMatchType
+eab_contact_compare_name (EContact *contact1,
+ EContact *contact2)
+{
+ EContactName *a, *b;
+ gint matches = 0, possible = 0;
+ gboolean family_match = FALSE;
+
+ g_return_val_if_fail (E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+
+ a = e_contact_get (contact1, E_CONTACT_NAME);
+ b = e_contact_get (contact2, E_CONTACT_NAME);
+
+ if (a == NULL || b == NULL) {
+ g_free (a);
+ g_free (b);
+ return EAB_CONTACT_MATCH_NOT_APPLICABLE;
+ }
+
+ if (a->given && b->given && *a->given && *b->given) {
+ ++possible;
+ if (name_fragment_match_with_synonyms (a->given, b->given, FALSE /* both inputs are complete */)) {
+ ++matches;
+ }
+ }
+
+ if (a->additional && b->additional && *a->additional && *b->additional) {
+ ++possible;
+ if (name_fragment_match_with_synonyms (a->additional, b->additional, FALSE /* both inputs are complete */)) {
+ ++matches;
+ }
+ }
+
+ if (a->family && b->family && *a->family && *b->family) {
+ ++possible;
+ /* We don't allow "loose matching" (i.e. John vs. Jon) on family names */
+ if (!e_utf8_casefold_collate (a->family, b->family)) {
+ ++matches;
+ family_match = TRUE;
+ }
+ }
+
+ e_contact_name_free (a);
+ e_contact_name_free (b);
+
+ /* Now look at the # of matches and try to intelligently map
+ * an EAB_CONTACT_MATCH_* type to it. Special consideration is given
+ * to family-name matches. */
+
+ if (possible == 0)
+ return EAB_CONTACT_MATCH_NOT_APPLICABLE;
+
+ if (possible == 1)
+ return family_match ? EAB_CONTACT_MATCH_VAGUE : EAB_CONTACT_MATCH_NONE;
+
+ if (possible == matches)
+ return family_match ? EAB_CONTACT_MATCH_EXACT : EAB_CONTACT_MATCH_PARTIAL;
+
+ if (possible == matches + 1)
+ return family_match ? EAB_CONTACT_MATCH_VAGUE : EAB_CONTACT_MATCH_NONE;
+
+ return EAB_CONTACT_MATCH_NONE;
+}
+
+/*** Nickname Comparisons ***/
+
+EABContactMatchType
+eab_contact_compare_nickname (EContact *contact1,
+ EContact *contact2)
+{
+ g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+
+ return EAB_CONTACT_MATCH_NOT_APPLICABLE;
+}
+
+/*** E-mail Comparisons ***/
+
+static gboolean
+match_email_username (const gchar *addr1,
+ const gchar *addr2)
+{
+ gint c1, c2;
+ if (addr1 == NULL || addr2 == NULL)
+ return FALSE;
+
+ while (*addr1 && *addr2 && *addr1 != '@' && *addr2 != '@') {
+ c1 = isupper (*addr1) ? tolower (*addr1) : *addr1;
+ c2 = isupper (*addr2) ? tolower (*addr2) : *addr2;
+ if (c1 != c2)
+ return FALSE;
+ ++addr1;
+ ++addr2;
+ }
+
+ return *addr1 == *addr2;
+}
+
+static gboolean
+match_email_hostname (const gchar *addr1,
+ const gchar *addr2)
+{
+ gint c1, c2;
+ gboolean seen_at1, seen_at2;
+ if (addr1 == NULL || addr2 == NULL)
+ return FALSE;
+
+ /* Walk to the end of each string. */
+ seen_at1 = FALSE;
+ if (*addr1) {
+ while (*addr1) {
+ if (*addr1 == '@')
+ seen_at1 = TRUE;
+ ++addr1;
+ }
+ --addr1;
+ }
+
+ seen_at2 = FALSE;
+ if (*addr2) {
+ while (*addr2) {
+ if (*addr2 == '@')
+ seen_at2 = TRUE;
+ ++addr2;
+ }
+ --addr2;
+ }
+
+ if (!seen_at1 && !seen_at2)
+ return TRUE;
+ if (!seen_at1 || !seen_at2)
+ return FALSE;
+
+ while (*addr1 != '@' && *addr2 != '@') {
+ c1 = isupper (*addr1) ? tolower (*addr1) : *addr1;
+ c2 = isupper (*addr2) ? tolower (*addr2) : *addr2;
+ if (c1 != c2)
+ return FALSE;
+ --addr1;
+ --addr2;
+ }
+ if ((*addr1 == '@' && *addr2 != '@') || (*addr2 == '@' && *addr1 != '@'))
+ return FALSE;
+
+ return TRUE;
+}
+
+static EABContactMatchType
+compare_email_addresses (const gchar *addr1,
+ const gchar *addr2)
+{
+ if (addr1 == NULL || *addr1 == 0 ||
+ addr2 == NULL || *addr2 == 0)
+ return EAB_CONTACT_MATCH_NOT_APPLICABLE;
+
+ if (match_email_username (addr1, addr2))
+ return match_email_hostname (addr1, addr2) ? EAB_CONTACT_MATCH_EXACT : EAB_CONTACT_MATCH_VAGUE;
+
+ return EAB_CONTACT_MATCH_NONE;
+}
+
+EABContactMatchType
+eab_contact_compare_email (EContact *contact1,
+ EContact *contact2)
+{
+ EABContactMatchType match = EAB_CONTACT_MATCH_NOT_APPLICABLE;
+ GList *contact1_email, *contact2_email;
+ GList *i1, *i2;
+
+ g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+
+ contact1_email = e_contact_get (contact1, E_CONTACT_EMAIL);
+ contact2_email = e_contact_get (contact2, E_CONTACT_EMAIL);
+
+ if (contact1_email == NULL || contact2_email == NULL) {
+ g_list_foreach (contact1_email, (GFunc) g_free, NULL);
+ g_list_free (contact1_email);
+
+ g_list_foreach (contact2_email, (GFunc) g_free, NULL);
+ g_list_free (contact2_email);
+ return EAB_CONTACT_MATCH_NOT_APPLICABLE;
+ }
+
+ i1 = contact1_email;
+
+ /* Do pairwise-comparisons on all of the e-mail addresses. If
+ * we find an exact match, there is no reason to keep
+ * checking. */
+ while (i1 && match != EAB_CONTACT_MATCH_EXACT) {
+ gchar *addr1 = (gchar *) i1->data;
+
+ i2 = contact2_email;
+ while (i2 && match != EAB_CONTACT_MATCH_EXACT) {
+ gchar *addr2 = (gchar *) i2->data;
+
+ match = combine_comparisons (match, compare_email_addresses (addr1, addr2));
+
+ i2 = i2->next;
+ }
+
+ i1 = i1->next;
+ }
+
+ g_list_foreach (contact1_email, (GFunc) g_free, NULL);
+ g_list_free (contact1_email);
+
+ g_list_foreach (contact2_email, (GFunc) g_free, NULL);
+ g_list_free (contact2_email);
+
+ return match;
+}
+
+EABContactMatchType
+eab_contact_compare_address (EContact *contact1,
+ EContact *contact2)
+{
+ g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+
+ /* Unimplemented */
+
+ return EAB_CONTACT_MATCH_NOT_APPLICABLE;
+}
+
+EABContactMatchType
+eab_contact_compare_telephone (EContact *contact1,
+ EContact *contact2)
+{
+ g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+
+ /* Unimplemented */
+
+ return EAB_CONTACT_MATCH_NOT_APPLICABLE;
+}
+
+EABContactMatchType
+eab_contact_compare (EContact *contact1,
+ EContact *contact2)
+{
+ EABContactMatchType result;
+
+ g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
+
+ result = EAB_CONTACT_MATCH_NONE;
+ if (!e_contact_get (contact1, E_CONTACT_IS_LIST)) {
+ result = combine_comparisons (result, eab_contact_compare_name (contact1, contact2));
+ result = combine_comparisons (result, eab_contact_compare_nickname (contact1, contact2));
+ if (!e_contact_get (contact2, E_CONTACT_IS_LIST))
+ result = combine_comparisons (result, eab_contact_compare_email (contact1, contact2));
+ result = combine_comparisons (result, eab_contact_compare_address (contact1, contact2));
+ result = combine_comparisons (result, eab_contact_compare_telephone (contact1, contact2));
+ }
+ result = combine_comparisons (result, eab_contact_compare_file_as (contact1, contact2));
+
+ return result;
+}
+
+typedef struct _MatchSearchInfo MatchSearchInfo;
+struct _MatchSearchInfo {
+ EContact *contact;
+ GList *avoid;
+ EABContactMatchQueryCallback cb;
+ gpointer closure;
+};
+
+static void
+match_search_info_free (MatchSearchInfo *info)
+{
+ if (info) {
+ g_object_unref (info->contact);
+
+ /* This should already have been deallocated, but just in case... */
+ if (info->avoid) {
+ g_list_foreach (info->avoid, (GFunc) g_object_unref, NULL);
+ g_list_free (info->avoid);
+ info->avoid = NULL;
+ }
+
+ g_free (info);
+ }
+}
+
+static void
+query_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ MatchSearchInfo *info = (MatchSearchInfo *) user_data;
+ EABContactMatchType best_match = EAB_CONTACT_MATCH_NONE;
+ EContact *best_contact = NULL;
+ EBookClient *book_client = E_BOOK_CLIENT (source_object);
+ GSList *remaining_contacts = NULL;
+ GSList *contacts = NULL;
+ GError *error = NULL;
+ const GSList *ii;
+
+ if (result != NULL)
+ e_book_client_get_contacts_finish (
+ book_client, result, &contacts, &error);
+
+ if (error != NULL) {
+ g_warning (
+ "%s: Failed to get contacts: %s\n",
+ G_STRFUNC, error->message);
+ g_error_free (error);
+
+ info->cb (
+ info->contact, NULL,
+ EAB_CONTACT_MATCH_NONE,
+ info->closure);
+
+ match_search_info_free (info);
+ g_object_unref (book_client);
+ return;
+ }
+
+ /* remove the contacts we're to avoid from the list, if they're present */
+ for (ii = contacts; ii != NULL; ii = g_slist_next (ii)) {
+ EContact *this_contact = E_CONTACT (ii->data);
+ const gchar *this_uid;
+ GList *iterator;
+ gboolean avoid = FALSE;
+
+ this_uid = e_contact_get_const (this_contact, E_CONTACT_UID);
+ if (!this_uid)
+ continue;
+
+ for (iterator = info->avoid; iterator; iterator = iterator->next) {
+ const gchar *avoid_uid;
+
+ avoid_uid = e_contact_get_const (iterator->data, E_CONTACT_UID);
+ if (!avoid_uid)
+ continue;
+
+ if (!strcmp (avoid_uid, this_uid)) {
+ avoid = TRUE;
+ break;
+ }
+ }
+ if (!avoid)
+ remaining_contacts = g_slist_prepend (remaining_contacts, g_object_ref (this_contact));
+ }
+
+ remaining_contacts = g_slist_reverse (remaining_contacts);
+
+ for (ii = remaining_contacts; ii != NULL; ii = g_slist_next (ii)) {
+ EContact *this_contact = E_CONTACT (ii->data);
+ EABContactMatchType this_match = eab_contact_compare (info->contact, this_contact);
+ if ((gint) this_match > (gint) best_match) {
+ best_match = this_match;
+ best_contact = this_contact;
+ }
+ }
+
+ if (best_contact)
+ best_contact = g_object_ref (best_contact);
+
+ g_slist_free_full (contacts, (GDestroyNotify) g_object_unref);
+ g_slist_free_full (remaining_contacts, (GDestroyNotify) g_object_unref);
+
+ info->cb (info->contact, best_contact, best_match, info->closure);
+ match_search_info_free (info);
+ g_object_unref (book_client);
+ if (best_contact)
+ g_object_unref (best_contact);
+}
+
+#define MAX_QUERY_PARTS 10
+static void
+use_common_book_client (EBookClient *book_client,
+ MatchSearchInfo *info)
+{
+ EContact *contact = info->contact;
+ EContactName *contact_name;
+ GList *contact_email;
+ gchar *query_parts[MAX_QUERY_PARTS + 1];
+ gint p = 0;
+ gchar *contact_file_as, *qj;
+ EBookQuery *query = NULL;
+ gint i;
+
+ if (book_client == NULL) {
+ info->cb (info->contact, NULL, EAB_CONTACT_MATCH_NONE, info->closure);
+ match_search_info_free (info);
+ return;
+ }
+
+ contact_file_as = e_contact_get (contact, E_CONTACT_FILE_AS);
+ if (contact_file_as) {
+ query_parts[p++] = g_strdup_printf ("(contains \"file_as\" \"%s\")", contact_file_as);
+ g_free (contact_file_as);
+ }
+
+ if (!e_contact_get (contact, E_CONTACT_IS_LIST)) {
+ contact_name = e_contact_get (contact, E_CONTACT_NAME);
+ if (contact_name) {
+ if (contact_name->given && *contact_name->given)
+ query_parts[p++] = g_strdup_printf ("(contains \"full_name\" \"%s\")", contact_name->given);
+
+ if (contact_name->additional && *contact_name->additional)
+ query_parts[p++] = g_strdup_printf ("(contains \"full_name\" \"%s\")", contact_name->additional);
+
+ if (contact_name->family && *contact_name->family)
+ query_parts[p++] = g_strdup_printf ("(contains \"full_name\" \"%s\")", contact_name->family);
+
+ e_contact_name_free (contact_name);
+ }
+
+ contact_email = e_contact_get (contact, E_CONTACT_EMAIL);
+ if (contact_email) {
+ GList *iter;
+ for (iter = contact_email; iter && p < MAX_QUERY_PARTS; iter = iter->next) {
+ gchar *addr = g_strdup (iter->data);
+ if (addr && *addr) {
+ gchar *s = addr;
+ while (*s) {
+ if (*s == '@') {
+ *s = '\0';
+ break;
+ }
+ ++s;
+ }
+ query_parts[p++] = g_strdup_printf ("(beginswith \"email\" \"%s\")", addr);
+ g_free (addr);
+ }
+ }
+ }
+ g_list_foreach (contact_email, (GFunc) g_free, NULL);
+ g_list_free (contact_email);
+ }
+
+ /* Build up our full query from the parts. */
+ query_parts[p] = NULL;
+ qj = g_strjoinv (" ", query_parts);
+ for (i = 0; query_parts[i] != NULL; i++)
+ g_free (query_parts[i]);
+ if (p > 1) {
+ gchar *s;
+ s = g_strdup_printf ("(or %s)", qj);
+ query = e_book_query_from_string (s);
+ g_free (s);
+ }
+ else if (p == 1) {
+ query = e_book_query_from_string (qj);
+ }
+ else {
+ query = NULL;
+ }
+
+ if (query) {
+ gchar *query_str = e_book_query_to_string (query);
+
+ e_book_client_get_contacts (book_client, query_str, NULL, query_cb, info);
+
+ g_free (query_str);
+ } else
+ query_cb (G_OBJECT (book_client), NULL, info);
+
+ g_free (qj);
+ if (query)
+ e_book_query_unref (query);
+}
+
+static void
+book_client_connect_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ MatchSearchInfo *info = user_data;
+ EClient *client;
+
+ client = e_book_client_connect_finish (result, NULL);
+
+ /* Client may be NULL; don't use a type cast macro. */
+ use_common_book_client ((EBookClient *) client, info);
+}
+
+void
+eab_contact_locate_match (ESourceRegistry *registry,
+ EContact *contact,
+ EABContactMatchQueryCallback cb,
+ gpointer closure)
+{
+ eab_contact_locate_match_full (
+ registry, NULL, contact, NULL, cb, closure);
+}
+
+/**
+ * e_contact_locate_match_full:
+ * @registry: an #ESourceRegistry
+ * @book: The book to look in. If this is NULL, use the default
+ * addressbook.
+ * @contact: The contact to compare to.
+ * @avoid: A list of contacts to not match. These will not show up in the search.
+ * @cb: The function to call.
+ * @closure: The closure to add to the call.
+ *
+ * Look for the best match and return it using the EABContactMatchQueryCallback.
+ **/
+void
+eab_contact_locate_match_full (ESourceRegistry *registry,
+ EBookClient *book_client,
+ EContact *contact,
+ GList *avoid,
+ EABContactMatchQueryCallback cb,
+ gpointer closure)
+{
+ MatchSearchInfo *info;
+ ESource *source;
+
+ g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+ g_return_if_fail (E_IS_CONTACT (contact));
+ g_return_if_fail (cb != NULL);
+
+ info = g_new0 (MatchSearchInfo, 1);
+ info->contact = g_object_ref (contact);
+ info->cb = cb;
+ info->closure = closure;
+ info->avoid = g_list_copy (avoid);
+ g_list_foreach (info->avoid, (GFunc) g_object_ref, NULL);
+
+ if (book_client) {
+ use_common_book_client (g_object_ref (book_client), info);
+ return;
+ }
+
+ source = e_source_registry_ref_default_address_book (registry);
+
+ e_book_client_connect (source, NULL, book_client_connect_cb, info);
+
+ g_object_unref (source);
+}
+
diff --git a/addressbook/gui/widgets/eab-contact-compare.h b/addressbook/gui/widgets/eab-contact-compare.h
new file mode 100644
index 0000000000..f6b05705cf
--- /dev/null
+++ b/addressbook/gui/widgets/eab-contact-compare.h
@@ -0,0 +1,101 @@
+/*
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Jon Trowbridge <trow@ximian.com>
+ * Chris Toshok <toshok@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+#ifndef __EAB_CONTACT_COMPARE_H__
+#define __EAB_CONTACT_COMPARE_H__
+
+#include <libebook/libebook.h>
+
+typedef enum {
+ EAB_CONTACT_MATCH_NOT_APPLICABLE = 0,
+ EAB_CONTACT_MATCH_NONE = 1,
+ EAB_CONTACT_MATCH_VAGUE = 2,
+ EAB_CONTACT_MATCH_PARTIAL = 3,
+ EAB_CONTACT_MATCH_EXACT = 4
+} EABContactMatchType;
+
+typedef enum {
+ EAB_CONTACT_MATCH_PART_NOT_APPLICABLE = -1,
+ EAB_CONTACT_MATCH_PART_NONE = 0,
+ EAB_CONTACT_MATCH_PART_GIVEN_NAME = 1 << 0,
+ EAB_CONTACT_MATCH_PART_ADDITIONAL_NAME = 1 << 2,
+ EAB_CONTACT_MATCH_PART_FAMILY_NAME = 1 << 3
+} EABContactMatchPart;
+
+typedef void (*EABContactMatchQueryCallback) (EContact *contact,
+ EContact *match,
+ EABContactMatchType type,
+ gpointer closure);
+
+EABContactMatchType
+ eab_contact_compare_name_to_string
+ (EContact *contact,
+ const gchar *str);
+
+EABContactMatchType
+ eab_contact_compare_name_to_string_full
+ (EContact *contact,
+ const gchar *str,
+ gboolean allow_partial_matches,
+ gint *matched_parts,
+ EABContactMatchPart *first_matched_part,
+ gint *matched_character_count);
+
+EABContactMatchType
+ eab_contact_compare_file_as (EContact *contact1,
+ EContact *contact2);
+EABContactMatchType
+ eab_contact_compare_name (EContact *contact1,
+ EContact *contact2);
+EABContactMatchType
+ eab_contact_compare_nickname (EContact *contact1,
+ EContact *contact2);
+EABContactMatchType
+ eab_contact_compare_email (EContact *contact1,
+ EContact *contact2);
+EABContactMatchType
+ eab_contact_compare_address (EContact *contact1,
+ EContact *contact2);
+EABContactMatchType
+ eab_contact_compare_telephone (EContact *contact1,
+ EContact *contact2);
+
+EABContactMatchType
+ eab_contact_compare (EContact *contact1,
+ EContact *contact2);
+
+void eab_contact_locate_match (ESourceRegistry *registry,
+ EContact *contact,
+ EABContactMatchQueryCallback cb,
+ gpointer closure);
+void eab_contact_locate_match_full (ESourceRegistry *registry,
+ EBookClient *book_client,
+ EContact *contact,
+ GList *avoid,
+ EABContactMatchQueryCallback cb,
+ gpointer closure);
+
+#endif /* __E_CONTACT_COMPARE_H__ */
+
diff --git a/addressbook/gui/widgets/eab-contact-duplicate-detected.ui b/addressbook/gui/widgets/eab-contact-duplicate-detected.ui
new file mode 100644
index 0000000000..124b9c2787
--- /dev/null
+++ b/addressbook/gui/widgets/eab-contact-duplicate-detected.ui
@@ -0,0 +1,219 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkDialog" id="dialog-duplicate-contact">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Duplicate Contact Detected</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="height-request">400</property>
+ <property name="width-request">500</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="button4">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button3">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-add</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button5">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Merge</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkViewport" id="viewport1">
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="n_rows">5</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="EABContactDisplay" type-func="eab_contact_display_get_type" id="custom-old-contact">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">The name or email address of this contact already exists
+in this folder. Would you like to add it anyway?</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Original Contact:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">New Contact:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="EABContactDisplay" type-func="eab_contact_display_get_type" id="custom-new-contact">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0</property>
+ <property name="xscale">1</property>
+ <property name="yscale">0</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">0</property>
+ <property name="right_padding">0</property>
+ <child>
+ <object class="GtkImage" id="custom2">
+ <property name="visible">True</property>
+ <property name="icon-name">avatar-default</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="1">button4</action-widget>
+ <action-widget response="0">button3</action-widget>
+ <action-widget response="2">button5</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/addressbook/gui/widgets/eab-contact-merging.c b/addressbook/gui/widgets/eab-contact-merging.c
new file mode 100644
index 0000000000..56fef2bc40
--- /dev/null
+++ b/addressbook/gui/widgets/eab-contact-merging.c
@@ -0,0 +1,797 @@
+/*
+ * Code for checking for duplicates when doing EContact work.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Christopher James Lahey <clahey@ximian.com>
+ * Chris Toshok <toshok@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "eab-contact-merging.h"
+#include "eab-contact-compare.h"
+#include <gtk/gtk.h>
+#include <string.h>
+#include "addressbook/gui/widgets/eab-contact-display.h"
+#include "e-util/e-util.h"
+#include "e-util/e-util-private.h"
+#include <glib/gi18n.h>
+
+#include <camel/camel.h>
+
+typedef struct dropdown_data dropdown_data;
+typedef enum {
+ E_CONTACT_MERGING_ADD,
+ E_CONTACT_MERGING_COMMIT,
+ E_CONTACT_MERGING_FIND
+} EContactMergingOpType;
+
+typedef struct {
+ EContactMergingOpType op;
+ ESourceRegistry *registry;
+ EBookClient *book_client;
+ /*contact is the new contact which the user has tried to add to the addressbook*/
+ EContact *contact;
+ /*match is the duplicate contact already existing in the addressbook*/
+ EContact *match;
+ GList *avoid;
+ EABMergingAsyncCallback cb;
+ EABMergingIdAsyncCallback id_cb;
+ EABMergingContactAsyncCallback c_cb;
+ gpointer closure;
+} EContactMergingLookup;
+
+struct dropdown_data {
+ EContact *match;
+ EContactField field;
+
+ /* for E_CONTACT_EMAIL field */
+ GList *email_attr_list_item;
+ EVCardAttribute *email_attr;
+};
+static void match_query_callback (EContact *contact, EContact *match, EABContactMatchType type, gpointer closure);
+
+#define SIMULTANEOUS_MERGING_REQUESTS 20
+
+static GList *merging_queue = NULL;
+static gint running_merge_requests = 0;
+
+static void
+add_lookup (EContactMergingLookup *lookup)
+{
+ if (running_merge_requests < SIMULTANEOUS_MERGING_REQUESTS) {
+ running_merge_requests++;
+ eab_contact_locate_match_full (
+ lookup->registry, lookup->book_client,
+ lookup->contact, lookup->avoid,
+ match_query_callback, lookup);
+ }
+ else {
+ merging_queue = g_list_append (merging_queue, lookup);
+ }
+}
+
+static void
+finished_lookup (void)
+{
+ running_merge_requests--;
+
+ while (running_merge_requests < SIMULTANEOUS_MERGING_REQUESTS) {
+ EContactMergingLookup *lookup;
+
+ if (!merging_queue)
+ break;
+
+ lookup = merging_queue->data;
+
+ merging_queue = g_list_remove_link (merging_queue, merging_queue);
+
+ running_merge_requests++;
+ eab_contact_locate_match_full (
+ lookup->registry, lookup->book_client,
+ lookup->contact, lookup->avoid,
+ match_query_callback, lookup);
+ }
+}
+
+static void
+free_lookup (EContactMergingLookup *lookup)
+{
+ g_object_unref (lookup->registry);
+ g_object_unref (lookup->book_client);
+ g_object_unref (lookup->contact);
+ g_list_free (lookup->avoid);
+ if (lookup->match)
+ g_object_unref (lookup->match);
+ g_free (lookup);
+}
+
+static void
+final_id_cb (EBookClient *book_client,
+ const GError *error,
+ const gchar *id,
+ gpointer closure)
+{
+ EContactMergingLookup *lookup = closure;
+
+ if (lookup->id_cb)
+ lookup->id_cb (
+ lookup->book_client,
+ error, id, lookup->closure);
+
+ free_lookup (lookup);
+
+ finished_lookup ();
+}
+
+static void
+final_cb_as_id (EBookClient *book_client,
+ const GError *error,
+ gpointer closure)
+{
+ EContactMergingLookup *lookup = closure;
+
+ if (lookup->id_cb)
+ lookup->id_cb (
+ lookup->book_client,
+ error, lookup->contact ?
+ e_contact_get_const (
+ lookup->contact, E_CONTACT_UID) : NULL,
+ lookup->closure);
+
+ free_lookup (lookup);
+
+ finished_lookup ();
+}
+
+static void
+final_cb (EBookClient *book_client,
+ const GError *error,
+ gpointer closure)
+{
+ EContactMergingLookup *lookup = closure;
+
+ if (lookup->cb)
+ lookup->cb (lookup->book_client, error, lookup->closure);
+
+ free_lookup (lookup);
+
+ finished_lookup ();
+}
+
+static void
+modify_contact_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EBookClient *book_client = E_BOOK_CLIENT (source_object);
+ EContactMergingLookup *lookup = user_data;
+ GError *error = NULL;
+
+ g_return_if_fail (book_client != NULL);
+ g_return_if_fail (lookup != NULL);
+
+ e_book_client_modify_contact_finish (book_client, result, &error);
+
+ if (lookup->op == E_CONTACT_MERGING_ADD)
+ final_cb_as_id (book_client, error, lookup);
+ else
+ final_cb (book_client, error, lookup);
+
+ if (error != NULL)
+ g_error_free (error);
+}
+
+static void
+add_contact_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EBookClient *book_client = E_BOOK_CLIENT (source_object);
+ EContactMergingLookup *lookup = user_data;
+ gchar *uid = NULL;
+ GError *error = NULL;
+
+ g_return_if_fail (book_client != NULL);
+ g_return_if_fail (lookup != NULL);
+
+ e_book_client_add_contact_finish (book_client, result, &uid, &error);
+
+ final_id_cb (book_client, error, uid, lookup);
+
+ if (error != NULL)
+ g_error_free (error);
+}
+
+static void
+doit (EContactMergingLookup *lookup,
+ gboolean force_modify)
+{
+ if (lookup->op == E_CONTACT_MERGING_ADD) {
+ if (force_modify)
+ e_book_client_modify_contact (lookup->book_client, lookup->contact, NULL, modify_contact_ready_cb, lookup);
+ else
+ e_book_client_add_contact (lookup->book_client, lookup->contact, NULL, add_contact_ready_cb, lookup);
+ } else if (lookup->op == E_CONTACT_MERGING_COMMIT)
+ e_book_client_modify_contact (lookup->book_client, lookup->contact, NULL, modify_contact_ready_cb, lookup);
+}
+
+static void
+cancelit (EContactMergingLookup *lookup)
+{
+ GError *error;
+
+ error = g_error_new_literal (
+ G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Cancelled"));
+
+ if (lookup->op == E_CONTACT_MERGING_ADD) {
+ final_id_cb (lookup->book_client, error, NULL, lookup);
+ } else if (lookup->op == E_CONTACT_MERGING_COMMIT) {
+ final_cb (lookup->book_client, error, lookup);
+ }
+
+ g_error_free (error);
+}
+
+static void
+dialog_map (GtkWidget *window,
+ GdkEvent *event,
+ GtkWidget *table)
+{
+ GtkAllocation allocation;
+ gint h, w;
+
+ gtk_widget_get_allocation (table, &allocation);
+
+ /* Spacing around the table */
+ w = allocation.width + 30;
+ /* buttons and outer spacing */
+ h = allocation.height + 60;
+ if (w > 400)
+ w = 400;
+ if (h > 450)
+ h = 450;
+
+ gtk_widget_set_size_request (window, w, h);
+}
+
+static void
+dropdown_changed (GtkWidget *dropdown,
+ dropdown_data *data)
+{
+ gchar *str = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dropdown));
+
+ if (str && *str)
+ e_contact_set (data->match, data->field, str);
+ else
+ e_contact_set (data->match, data->field, NULL);
+
+ g_free (str);
+}
+
+static void
+email_dropdown_changed (GtkWidget *dropdown,
+ dropdown_data *data)
+{
+ gchar *str = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dropdown));
+
+ if (str && *str)
+ data->email_attr_list_item->data = data->email_attr;
+ else
+ data->email_attr_list_item->data = NULL;
+
+ g_free (str);
+}
+
+static void
+remove_contact_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EBookClient *book_client = E_BOOK_CLIENT (source_object);
+ EContactMergingLookup *lookup = user_data;
+ GError *error = NULL;
+
+ g_return_if_fail (book_client != NULL);
+ g_return_if_fail (lookup != NULL);
+
+ e_book_client_remove_contact_finish (book_client, result, &error);
+
+ if (error != NULL) {
+ g_warning (
+ "%s: Failed to remove contact: %s",
+ G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+
+ e_book_client_add_contact (
+ book_client, lookup->contact, NULL,
+ add_contact_ready_cb, lookup);
+}
+
+static gint
+mergeit (EContactMergingLookup *lookup)
+{
+ GtkWidget *scrolled_window, *label, *hbox, *dropdown;
+ GtkWidget *content_area;
+ GtkWidget *dialog;
+ GtkTable *table;
+ EContactField field;
+ gchar *string = NULL, *string1 = NULL;
+ GList *match_email_attr_list, *contact_email_attr_list, *miter, *citer, *use_email_attr_list;
+ GHashTable *match_emails; /* emails in the 'match' contact */
+ gint row = -1;
+ gint value = 0, result;
+
+ dialog = gtk_dialog_new ();
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Merge Contact"));
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
+
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (
+ GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+ table = (GtkTable *) gtk_table_new (20, 2, FALSE);
+ gtk_container_set_border_width ((GtkContainer *) table, 12);
+ gtk_table_set_row_spacings (table, 6);
+ gtk_table_set_col_spacings (table, 2);
+
+ gtk_dialog_add_buttons (
+ GTK_DIALOG (dialog),
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ _("_Merge"), GTK_RESPONSE_OK,
+ NULL);
+
+ match_emails = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, NULL);
+ match_email_attr_list = e_contact_get_attributes (lookup->match, E_CONTACT_EMAIL);
+ contact_email_attr_list = e_contact_get_attributes (lookup->contact, E_CONTACT_EMAIL);
+ use_email_attr_list = NULL;
+
+ for (miter = match_email_attr_list; miter; miter = g_list_next (miter)) {
+ EVCardAttribute *attr = miter->data;
+ gchar *email;
+
+ email = e_vcard_attribute_get_value (attr);
+ if (email && *email) {
+ g_hash_table_insert (match_emails, email, attr);
+ use_email_attr_list = g_list_prepend (use_email_attr_list, attr);
+ } else {
+ g_free (email);
+ }
+ }
+
+ use_email_attr_list = g_list_reverse (use_email_attr_list);
+
+ /*we match all the string fields of the already existing contact and the new contact.*/
+ for (field = E_CONTACT_FULL_NAME; field != (E_CONTACT_LAST_SIMPLE_STRING -1); field++) {
+ dropdown_data *data = NULL;
+ string = (gchar *) e_contact_get_const (lookup->contact, field);
+ string1 = (gchar *) e_contact_get_const (lookup->match, field);
+
+ /*the field must exist in the new as well as the duplicate contact*/
+ if (string && *string) {
+ if (field == E_CONTACT_EMAIL_1) {
+ for (citer = contact_email_attr_list; citer; citer = g_list_next (citer)) {
+ EVCardAttribute *attr = citer->data;
+ gchar *email;
+
+ email = e_vcard_attribute_get_value (attr);
+ if (email && *email) {
+ if (!g_hash_table_lookup (match_emails, email)) {
+ dropdown_data *data;
+
+ /* the email is not set in both contacts */
+ use_email_attr_list = g_list_append (use_email_attr_list, attr);
+
+ row++;
+ label = gtk_label_new (_("Email"));
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget *) label, FALSE, FALSE, 0);
+ gtk_table_attach_defaults (table, (GtkWidget *) hbox, 0, 1, row, row + 1);
+
+ dropdown = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), email);
+
+ data = g_new0 (dropdown_data, 1);
+
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), "");
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 0);
+ data->field = E_CONTACT_EMAIL;
+ data->match = lookup->match;
+ data->email_attr_list_item = g_list_last (use_email_attr_list);
+ data->email_attr = attr;
+
+ g_signal_connect (
+ dropdown, "changed",
+ G_CALLBACK (email_dropdown_changed), data);
+ g_object_set_data_full (G_OBJECT (dropdown), "eab-contact-merging::dropdown-data", data, g_free);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget *) dropdown, FALSE, FALSE, 0);
+ gtk_table_attach_defaults (table, (GtkWidget *) hbox, 1, 2, row, row + 1);
+ gtk_widget_show ((GtkWidget *) dropdown);
+ }
+ }
+ g_free (email);
+ }
+ continue;
+ }
+
+ if (field == E_CONTACT_EMAIL_2 || field == E_CONTACT_EMAIL_3 || field == E_CONTACT_EMAIL_4) {
+ /* emails are compared above */
+ continue;
+ }
+
+ if (((field == E_CONTACT_FULL_NAME) && (!g_ascii_strcasecmp (string, string1)))) {
+ row++;
+ label = gtk_label_new (e_contact_pretty_name (field));
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget *) label, FALSE, FALSE, 0);
+ gtk_table_attach_defaults (table, (GtkWidget *) hbox, 0, 1, row, row + 1);
+
+ label = gtk_label_new (string);
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget *) label, FALSE, FALSE, 0);
+ gtk_table_attach_defaults (table, (GtkWidget *) hbox, 1, 2, row, row + 1);
+ continue;
+ }
+
+ /*for all string fields except name and email*/
+ if (!(string1 && *string1) || (g_ascii_strcasecmp (string, string1))) {
+ row++;
+ label = gtk_label_new (e_contact_pretty_name (field));
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget *) label, FALSE, FALSE, 0);
+ gtk_table_attach_defaults (table, (GtkWidget *) hbox, 0, 1, row, row + 1);
+ data = g_new0 (dropdown_data, 1);
+ dropdown = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), string);
+ e_contact_set (lookup->match, field, string);
+
+ if (string1 && *string1)
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), string1);
+ else
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), "");
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 0);
+ data->field = field;
+ data->match = lookup->match;
+
+ if (field == E_CONTACT_NICKNAME || field == E_CONTACT_GIVEN_NAME)
+ gtk_widget_set_sensitive ((GtkWidget *) dropdown, FALSE);
+
+ g_signal_connect (
+ dropdown, "changed",
+ G_CALLBACK (dropdown_changed), data);
+ g_object_set_data_full (G_OBJECT (dropdown), "eab-contact-merging::dropdown-data", data, g_free);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget *) dropdown, FALSE, FALSE, 0);
+ gtk_table_attach_defaults (table, (GtkWidget *) hbox, 1, 2, row, row + 1);
+ gtk_widget_show_all ((GtkWidget *) dropdown);
+ }
+ }
+ }
+
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 420, 300);
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window), GTK_WIDGET (table));
+ gtk_box_pack_start (GTK_BOX (content_area), GTK_WIDGET (scrolled_window), TRUE, TRUE, 0);
+ gtk_widget_show (scrolled_window);
+ g_signal_connect (
+ dialog, "map-event",
+ G_CALLBACK (dialog_map), table);
+ gtk_widget_show_all ((GtkWidget *) table);
+ result = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ switch (result) {
+ case GTK_RESPONSE_OK:
+ citer = NULL;
+ for (miter = use_email_attr_list; miter; miter = g_list_next (miter)) {
+ if (miter->data)
+ citer = g_list_prepend (citer, miter->data);
+ }
+ citer = g_list_reverse (citer);
+ e_contact_set_attributes (lookup->match, E_CONTACT_EMAIL, citer);
+ g_list_free (citer);
+
+ g_object_unref (lookup->contact);
+ lookup->contact = g_object_ref (lookup->match);
+ e_book_client_remove_contact (
+ lookup->book_client,
+ lookup->match, NULL,
+ remove_contact_ready_cb, lookup);
+ value = 1;
+ break;
+ case GTK_RESPONSE_CANCEL:
+ default:
+ value = 0;
+ break;
+ }
+ gtk_widget_destroy (dialog);
+ g_list_free_full (match_email_attr_list, (GDestroyNotify) e_vcard_attribute_free);
+ g_list_free_full (contact_email_attr_list, (GDestroyNotify) e_vcard_attribute_free);
+ g_list_free (use_email_attr_list);
+ g_hash_table_destroy (match_emails);
+
+ return value;
+}
+
+static gboolean
+check_if_same (EContact *contact,
+ EContact *match)
+{
+ EContactField field;
+ GList *email_attr_list;
+ gint num_of_email;
+ gchar *str = NULL, *string = NULL, *string1 = NULL;
+ gboolean res = TRUE;
+
+ email_attr_list = e_contact_get_attributes (match, E_CONTACT_EMAIL);
+ num_of_email = g_list_length (email_attr_list);
+
+ for (field = E_CONTACT_FULL_NAME; res && field != (E_CONTACT_LAST_SIMPLE_STRING -1); field++) {
+
+ if ((field == E_CONTACT_EMAIL_1 || field == E_CONTACT_EMAIL_2
+ || field == E_CONTACT_EMAIL_3 || field == E_CONTACT_EMAIL_4) && (num_of_email < 4)) {
+ str = (gchar *) e_contact_get_const (contact, field);
+ switch (num_of_email)
+ {
+ case 0:
+ res = FALSE;
+ break;
+ case 1:
+ if ((str && *str) && (g_ascii_strcasecmp (e_contact_get_const (match, E_CONTACT_EMAIL_1),str)))
+ res = FALSE;
+ break;
+ case 2:
+ if ((str && *str) && (g_ascii_strcasecmp (str,e_contact_get_const (match, E_CONTACT_EMAIL_1))) &&
+ (g_ascii_strcasecmp (e_contact_get_const (match, E_CONTACT_EMAIL_2),str)))
+ res = FALSE;
+ break;
+ case 3:
+ if ((str && *str) && (g_ascii_strcasecmp (e_contact_get_const (match, E_CONTACT_EMAIL_1),str)) &&
+ (g_ascii_strcasecmp (e_contact_get_const (match, E_CONTACT_EMAIL_2),str)) &&
+ (g_ascii_strcasecmp (e_contact_get_const (match, E_CONTACT_EMAIL_3),str)))
+ res = FALSE;
+ break;
+ }
+ }
+ else {
+ string = (gchar *) e_contact_get_const (contact, field);
+ string1 = (gchar *) e_contact_get_const (match, field);
+ if ((string && *string) && (string1 && *string1) && (g_ascii_strcasecmp (string1, string))) {
+ res = FALSE;
+ break;
+ /*if the field entry exist in either of the contacts,we'll have to give the choice and thus merge button should be sensitive*/
+ } else if ((string && *string) && !(string1 && *string1)) {
+ res = FALSE;
+ break;
+ }
+ }
+ }
+
+ g_list_free_full (email_attr_list, (GDestroyNotify) e_vcard_attribute_free);
+
+ return res;
+}
+
+static void
+response (GtkWidget *dialog,
+ gint response,
+ EContactMergingLookup *lookup)
+{
+ static gint merge_response;
+
+ switch (response) {
+ case 0:
+ doit (lookup, FALSE);
+ break;
+ case 1:
+ cancelit (lookup);
+ break;
+ case 2:
+ merge_response = mergeit (lookup);
+ if (merge_response)
+ break;
+ return;
+ case GTK_RESPONSE_DELETE_EVENT:
+ cancelit (lookup);
+ break;
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static void
+match_query_callback (EContact *contact,
+ EContact *match,
+ EABContactMatchType type,
+ gpointer closure)
+{
+ EContactMergingLookup *lookup = closure;
+ gint flag;
+ gboolean same_uids;
+
+ if (lookup->op == E_CONTACT_MERGING_FIND) {
+ if (lookup->c_cb)
+ lookup->c_cb (
+ lookup->book_client, NULL,
+ (gint) type <= (gint)
+ EAB_CONTACT_MATCH_VAGUE ? NULL : match,
+ lookup->closure);
+
+ free_lookup (lookup);
+ finished_lookup ();
+ return;
+ }
+
+ /* if had same UID, then we are editing old contact, thus force commit change to it */
+ same_uids = contact && match
+ && e_contact_get_const (contact, E_CONTACT_UID)
+ && e_contact_get_const (match, E_CONTACT_UID)
+ && g_str_equal (e_contact_get_const (contact, E_CONTACT_UID), e_contact_get_const (match, E_CONTACT_UID));
+
+ if ((gint) type <= (gint) EAB_CONTACT_MATCH_VAGUE || same_uids) {
+ doit (lookup, same_uids);
+ } else {
+ GtkBuilder *builder;
+ GtkWidget *container;
+ GtkWidget *merge_button;
+ GtkWidget *widget;
+
+ builder = gtk_builder_new ();
+
+ lookup->match = g_object_ref (match);
+ if (lookup->op == E_CONTACT_MERGING_ADD) {
+ /* Compares all the values of contacts and return true, if they match */
+ flag = check_if_same (contact, match);
+ e_load_ui_builder_definition (
+ builder, "eab-contact-duplicate-detected.ui");
+ merge_button = e_builder_get_widget (builder, "button5");
+ /* Merge Button not sensitive when all values are same */
+ if (flag)
+ gtk_widget_set_sensitive (GTK_WIDGET (merge_button), FALSE);
+ } else if (lookup->op == E_CONTACT_MERGING_COMMIT) {
+ e_load_ui_builder_definition (
+ builder, "eab-contact-commit-duplicate-detected.ui");
+ } else {
+ doit (lookup, FALSE);
+ g_object_unref (builder);
+ return;
+ }
+
+ widget = e_builder_get_widget (builder, "custom-old-contact");
+ eab_contact_display_set_mode (
+ EAB_CONTACT_DISPLAY (widget),
+ EAB_CONTACT_DISPLAY_RENDER_COMPACT);
+ eab_contact_display_set_contact (
+ EAB_CONTACT_DISPLAY (widget), match);
+
+ widget = e_builder_get_widget (builder, "custom-new-contact");
+ eab_contact_display_set_mode (
+ EAB_CONTACT_DISPLAY (widget),
+ EAB_CONTACT_DISPLAY_RENDER_COMPACT);
+ eab_contact_display_set_contact (
+ EAB_CONTACT_DISPLAY (widget), contact);
+
+ widget = e_builder_get_widget (builder, "dialog-duplicate-contact");
+
+ gtk_widget_ensure_style (widget);
+
+ container = gtk_dialog_get_action_area (GTK_DIALOG (widget));
+ gtk_container_set_border_width (GTK_CONTAINER (container), 12);
+
+ container = gtk_dialog_get_content_area (GTK_DIALOG (widget));
+ gtk_container_set_border_width (GTK_CONTAINER (container), 0);
+
+ g_signal_connect (
+ widget, "response",
+ G_CALLBACK (response), lookup);
+
+ gtk_widget_show_all (widget);
+
+ g_object_unref (builder);
+ }
+}
+
+gboolean
+eab_merging_book_add_contact (ESourceRegistry *registry,
+ EBookClient *book_client,
+ EContact *contact,
+ EABMergingIdAsyncCallback cb,
+ gpointer closure)
+{
+ EContactMergingLookup *lookup;
+
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
+
+ lookup = g_new (EContactMergingLookup, 1);
+
+ lookup->op = E_CONTACT_MERGING_ADD;
+ lookup->registry = g_object_ref (registry);
+ lookup->book_client = g_object_ref (book_client);
+ lookup->contact = g_object_ref (contact);
+ lookup->id_cb = cb;
+ lookup->closure = closure;
+ lookup->avoid = NULL;
+ lookup->match = NULL;
+
+ add_lookup (lookup);
+
+ return TRUE;
+}
+
+gboolean
+eab_merging_book_modify_contact (ESourceRegistry *registry,
+ EBookClient *book_client,
+ EContact *contact,
+ EABMergingAsyncCallback cb,
+ gpointer closure)
+{
+ EContactMergingLookup *lookup;
+
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
+
+ lookup = g_new (EContactMergingLookup, 1);
+
+ lookup->op = E_CONTACT_MERGING_COMMIT;
+ lookup->registry = g_object_ref (registry);
+ lookup->book_client = g_object_ref (book_client);
+ lookup->contact = g_object_ref (contact);
+ lookup->cb = cb;
+ lookup->closure = closure;
+ lookup->avoid = g_list_append (NULL, contact);
+ lookup->match = NULL;
+
+ add_lookup (lookup);
+
+ return TRUE;
+}
+
+gboolean
+eab_merging_book_find_contact (ESourceRegistry *registry,
+ EBookClient *book_client,
+ EContact *contact,
+ EABMergingContactAsyncCallback cb,
+ gpointer closure)
+{
+ EContactMergingLookup *lookup;
+
+ lookup = g_new (EContactMergingLookup, 1);
+
+ lookup->op = E_CONTACT_MERGING_FIND;
+ lookup->registry = g_object_ref (registry);
+ lookup->book_client = g_object_ref (book_client);
+ lookup->contact = g_object_ref (contact);
+ lookup->c_cb = cb;
+ lookup->closure = closure;
+ lookup->avoid = g_list_append (NULL, contact);
+ lookup->match = NULL;
+
+ add_lookup (lookup);
+
+ return TRUE;
+}
diff --git a/addressbook/gui/widgets/eab-contact-merging.h b/addressbook/gui/widgets/eab-contact-merging.h
new file mode 100644
index 0000000000..fbdc9b0889
--- /dev/null
+++ b/addressbook/gui/widgets/eab-contact-merging.h
@@ -0,0 +1,66 @@
+/*
+ * The Evolution addressbook client object.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Christopher James Lahey <clahey@ximian.com>
+ * Chris Toshok <toshok@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef __E_CONTACT_MERGING_H__
+#define __E_CONTACT_MERGING_H__
+
+#include <libebook/libebook.h>
+
+G_BEGIN_DECLS
+
+typedef void (*EABMergingAsyncCallback) (EBookClient *book_client,
+ const GError *error,
+ gpointer closure);
+typedef void (*EABMergingIdAsyncCallback) (EBookClient *book_client,
+ const GError *error,
+ const gchar *id,
+ gpointer closure);
+typedef void (*EABMergingContactAsyncCallback)
+ (EBookClient *book_client,
+ const GError *error,
+ EContact *contact,
+ gpointer closure);
+
+gboolean eab_merging_book_add_contact (ESourceRegistry *registry,
+ EBookClient *book_client,
+ EContact *contact,
+ EABMergingIdAsyncCallback cb,
+ gpointer closure);
+
+gboolean eab_merging_book_modify_contact (ESourceRegistry *registry,
+ EBookClient *book_client,
+ EContact *contact,
+ EABMergingAsyncCallback cb,
+ gpointer closure);
+
+gboolean eab_merging_book_find_contact (ESourceRegistry *registry,
+ EBookClient *book_client,
+ EContact *contact,
+ EABMergingContactAsyncCallback cb,
+ gpointer closure);
+
+G_END_DECLS
+
+#endif /* __EAB_CONTACT_MERGING_H__ */