aboutsummaryrefslogtreecommitdiffstats
path: root/addressbook/gui/merging
diff options
context:
space:
mode:
Diffstat (limited to 'addressbook/gui/merging')
-rw-r--r--addressbook/gui/merging/.cvsignore1
-rw-r--r--addressbook/gui/merging/Makefile.am17
-rw-r--r--addressbook/gui/merging/e-card-merging.c198
-rw-r--r--addressbook/gui/merging/e-card-merging.h29
-rw-r--r--addressbook/gui/merging/eab-contact-commit-duplicate-detected.glade (renamed from addressbook/gui/merging/e-card-merging-book-commit-duplicate-detected.glade)12
-rw-r--r--addressbook/gui/merging/eab-contact-compare.c736
-rw-r--r--addressbook/gui/merging/eab-contact-compare.h73
-rw-r--r--addressbook/gui/merging/eab-contact-duplicate-detected.glade (renamed from addressbook/gui/merging/e-card-duplicate-detected.glade)12
-rw-r--r--addressbook/gui/merging/eab-contact-merging.c197
-rw-r--r--addressbook/gui/merging/eab-contact-merging.h30
10 files changed, 1059 insertions, 246 deletions
diff --git a/addressbook/gui/merging/.cvsignore b/addressbook/gui/merging/.cvsignore
index d6c55c7345..b6fa83ca4c 100644
--- a/addressbook/gui/merging/.cvsignore
+++ b/addressbook/gui/merging/.cvsignore
@@ -5,3 +5,4 @@ Makefile
Makefile.in
*.lo
*.la
+*.gladep
diff --git a/addressbook/gui/merging/Makefile.am b/addressbook/gui/merging/Makefile.am
index f30edd5745..4627f30594 100644
--- a/addressbook/gui/merging/Makefile.am
+++ b/addressbook/gui/merging/Makefile.am
@@ -1,7 +1,8 @@
INCLUDES = \
- -DG_LOG_DOMAIN=\"e-card-gui\" \
+ -DG_LOG_DOMAIN=\"eab-contact-merging\" \
-DEVOLUTION_GLADEDIR=\""$(gladedir)"\" \
-I$(top_srcdir) \
+ -I$(top_srcdir)/addressbook \
-I$(top_srcdir)/addressbook/backend \
-I$(top_builddir)/addressbook/backend \
-DG_DISABLE_DEPRECATED \
@@ -11,15 +12,17 @@ INCLUDES = \
$(EVOLUTION_ADDRESSBOOK_CFLAGS)
noinst_LTLIBRARIES = \
- libecardmerging.la
+ libeabbookmerging.la
-libecardmerging_la_SOURCES = \
- e-card-merging.c \
- e-card-merging.h
+libeabbookmerging_la_SOURCES = \
+ eab-contact-compare.c \
+ eab-contact-compare.h \
+ eab-contact-merging.c \
+ eab-contact-merging.h
-glade_DATA = e-card-duplicate-detected.glade \
- e-card-merging-book-commit-duplicate-detected.glade
+glade_DATA = eab-contact-duplicate-detected.glade \
+ eab-contact-commit-duplicate-detected.glade
EXTRA_DIST = \
$(glade_DATA)
diff --git a/addressbook/gui/merging/e-card-merging.c b/addressbook/gui/merging/e-card-merging.c
deleted file mode 100644
index f2322e772c..0000000000
--- a/addressbook/gui/merging/e-card-merging.c
+++ /dev/null
@@ -1,198 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
-/*
- * Code for checking for duplicates when doing ECard work.
- *
- * Author:
- * Christopher James Lahey <clahey@ximian.com>
- *
- * Copyright 2001, Ximian, Inc.
- */
-
-#include <config.h>
-
-#include "e-card-merging.h"
-#include <ebook/e-card-compare.h>
-#include <glade/glade.h>
-#include <gtk/gtksignal.h>
-#include "addressbook/gui/widgets/e-minicard-widget.h"
-
-typedef enum {
- E_CARD_MERGING_ADD,
- E_CARD_MERGING_COMMIT
-} ECardMergingOpType;
-
-typedef struct {
- ECardMergingOpType op;
- EBook *book;
- ECard *card;
- EBookIdCallback id_cb;
- EBookCallback cb;
- gpointer closure;
-} ECardMergingLookup;
-
-static void
-free_lookup (ECardMergingLookup *lookup)
-{
- g_object_unref (lookup->book);
- g_object_unref (lookup->card);
-
- g_free (lookup);
-}
-
-static void
-final_id_cb (EBook *book, EBookStatus status, const char *id, gpointer closure)
-{
- ECardMergingLookup *lookup = closure;
-
- if (lookup->id_cb)
- lookup->id_cb (lookup->book, status, id, lookup->closure);
-
- free_lookup (lookup);
-}
-
-static void
-final_cb (EBook *book, EBookStatus status, gpointer closure)
-{
- ECardMergingLookup *lookup = closure;
-
- if (lookup->cb)
- lookup->cb (lookup->book, status, lookup->closure);
-
- free_lookup (lookup);
-}
-
-static void
-doit (ECardMergingLookup *lookup)
-{
- if (lookup->op == E_CARD_MERGING_ADD)
- e_book_add_card (lookup->book, lookup->card, final_id_cb, lookup);
- else if (lookup->op == E_CARD_MERGING_COMMIT)
- e_book_commit_card (lookup->book, lookup->card, final_cb, lookup);
-}
-
-static void
-cancelit (ECardMergingLookup *lookup)
-{
- if (lookup->op == E_CARD_MERGING_ADD) {
- if (lookup->id_cb)
- final_id_cb (lookup->book, E_BOOK_STATUS_CANCELLED, NULL, lookup);
- } else if (lookup->op == E_CARD_MERGING_COMMIT) {
- if (lookup->cb)
- final_cb (lookup->book, E_BOOK_STATUS_CANCELLED, lookup);
- }
-}
-
-static void
-response (GtkWidget *dialog, int response, ECardMergingLookup *lookup)
-{
- gtk_widget_destroy (dialog);
-
- switch (response) {
- case 0:
- doit (lookup);
- break;
- case 1:
- cancelit (lookup);
- break;
- }
-}
-
-static void
-match_query_callback (ECard *card, ECard *match, ECardMatchType type, gpointer closure)
-{
- ECardMergingLookup *lookup = closure;
-
- if ((gint) type <= (gint) E_CARD_MATCH_VAGUE) {
- doit (lookup);
- } else {
- GladeXML *ui;
-
- GtkWidget *widget;
-
- if (lookup->op == E_CARD_MERGING_ADD)
- ui = glade_xml_new (EVOLUTION_GLADEDIR "/e-card-duplicate-detected.glade", NULL, NULL);
- else if (lookup->op == E_CARD_MERGING_COMMIT)
- ui = glade_xml_new (EVOLUTION_GLADEDIR "/e-card-merging-book-commit-duplicate-detected.glade", NULL, NULL);
- else {
- doit (lookup);
- return;
- }
-
- widget = glade_xml_get_widget (ui, "custom-old-card");
- g_object_set (widget,
- "card", match,
- NULL);
-
- widget = glade_xml_get_widget (ui, "custom-new-card");
- g_object_set (widget,
- "card", card,
- NULL);
-
- widget = glade_xml_get_widget (ui, "dialog-duplicate-contact");
-
- g_signal_connect (widget, "response",
- G_CALLBACK (response), lookup);
-
- gtk_widget_show_all (widget);
- }
-}
-
-gboolean
-e_card_merging_book_add_card (EBook *book,
- ECard *card,
- EBookIdCallback cb,
- gpointer closure)
-{
- ECardMergingLookup *lookup;
-
- lookup = g_new (ECardMergingLookup, 1);
-
- lookup->op = E_CARD_MERGING_ADD;
- lookup->book = g_object_ref (book);
- lookup->card = g_object_ref (card);
- lookup->id_cb = cb;
- lookup->closure = closure;
-
- e_card_locate_match_full (book, card, NULL, match_query_callback, lookup);
-
- return TRUE;
-}
-
-gboolean
-e_card_merging_book_commit_card (EBook *book,
- ECard *card,
- EBookCallback cb,
- gpointer closure)
-{
- ECardMergingLookup *lookup;
- GList *avoid;
-
- lookup = g_new (ECardMergingLookup, 1);
-
- lookup->op = E_CARD_MERGING_COMMIT;
- lookup->book = g_object_ref (book);
- lookup->card = g_object_ref (card);
- lookup->cb = cb;
- lookup->closure = closure;
-
- avoid = g_list_append (NULL, card);
-
- e_card_locate_match_full (book, card, avoid, match_query_callback, lookup);
-
- g_list_free (avoid);
-
- return TRUE;
-}
-
-GtkWidget *
-e_card_merging_create_old_card(gchar *name,
- gchar *string1, gchar *string2,
- gint int1, gint int2);
-
-GtkWidget *
-e_card_merging_create_old_card(gchar *name,
- gchar *string1, gchar *string2,
- gint int1, gint int2)
-{
- return e_minicard_widget_new ();
-}
diff --git a/addressbook/gui/merging/e-card-merging.h b/addressbook/gui/merging/e-card-merging.h
deleted file mode 100644
index 8ea1f9f018..0000000000
--- a/addressbook/gui/merging/e-card-merging.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
-/*
- * The Evolution addressbook client object.
- *
- * Author:
- * Christopher James Lahey <clahey@ximian.com>
- *
- * Copyright 2001, Ximian, Inc.
- */
-
-#ifndef __E_CARD_MERGING_H__
-#define __E_CARD_MERGING_H__
-
-#include <addressbook/backend/ebook/e-book.h>
-
-G_BEGIN_DECLS
-
-gboolean e_card_merging_book_add_card (EBook *book,
- ECard *card,
- EBookIdCallback cb,
- gpointer closure);
-gboolean e_card_merging_book_commit_card (EBook *book,
- ECard *card,
- EBookCallback cb,
- gpointer closure);
-
-G_END_DECLS
-
-#endif /* ! __E_CARD_MERGING_H__ */
diff --git a/addressbook/gui/merging/e-card-merging-book-commit-duplicate-detected.glade b/addressbook/gui/merging/eab-contact-commit-duplicate-detected.glade
index f7cfd259e5..789c465d92 100644
--- a/addressbook/gui/merging/e-card-merging-book-commit-duplicate-detected.glade
+++ b/addressbook/gui/merging/eab-contact-commit-duplicate-detected.glade
@@ -68,12 +68,12 @@
<property name="column_spacing">6</property>
<child>
- <widget class="Custom" id="custom-old-card">
+ <widget class="Custom" id="custom-old-contact">
<property name="visible">True</property>
- <property name="creation_function">e_card_merging_create_old_card</property>
+ <property name="creation_function">_eab_contact_merging_create_contact_display</property>
<property name="int1">0</property>
<property name="int2">0</property>
- <property name="last_modification_time">Fri, 08 Jun 2001 01:33:22 GMT</property>
+ <property name="last_modification_time">Sun, 05 Oct 2003 03:54:20 GMT</property>
</widget>
<packing>
<property name="left_attach">1</property>
@@ -155,12 +155,12 @@ exists in this folder. Would you like to add it anyway?</property>
</child>
<child>
- <widget class="Custom" id="custom-new-card">
+ <widget class="Custom" id="custom-new-contact">
<property name="visible">True</property>
- <property name="creation_function">e_card_merging_create_old_card</property>
+ <property name="creation_function">_eab_contact_merging_create_contact_display</property>
<property name="int1">0</property>
<property name="int2">0</property>
- <property name="last_modification_time">Fri, 08 Jun 2001 01:33:22 GMT</property>
+ <property name="last_modification_time">Sun, 05 Oct 2003 03:53:42 GMT</property>
</widget>
<packing>
<property name="left_attach">1</property>
diff --git a/addressbook/gui/merging/eab-contact-compare.c b/addressbook/gui/merging/eab-contact-compare.c
new file mode 100644
index 0000000000..409b1bce81
--- /dev/null
+++ b/addressbook/gui/merging/eab-contact-compare.c
@@ -0,0 +1,736 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * eab-contact-compare.c
+ *
+ * Copyright (C) 2001, 2002, 2003 Ximian, Inc.
+ *
+ * Authors: Jon Trowbridge <trow@ximian.com>
+ * Chris Toshok <toshok@ximian.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ */
+
+#include <config.h>
+#include <ctype.h>
+#include <string.h>
+#include "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 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 (const gchar *a, const gchar *b, gboolean strict)
+{
+ gint len;
+
+ if (!(a && b && *a && *b))
+ return FALSE;
+
+ /* If we are in 'strict' mode, b must match the beginning of a.
+ So "Robert", "Rob" would match, but "Robert", "Robbie" wouldn't.
+
+ If strict is FALSE, it is sufficient for the strings to share
+ some leading characters. In this case, "Robert" and "Robbie"
+ would match, as would "Dave" and "Dan". */
+
+ if (strict) {
+ len = g_utf8_strlen (b, -1);
+ } else {
+ len = MIN (g_utf8_strlen (a, -1), g_utf8_strlen (b, -1));
+ }
+
+ return !e_utf8_casefold_collate_len (a, b, len);
+}
+
+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 (name_fragment_match (a, b, strict))
+ 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_name (EContact *contact1, EContact *contact2)
+{
+ EContactName *a, *b;
+ gint matches=0, possible=0;
+ gboolean given_match = FALSE, additional_match = FALSE, 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)
+ return EAB_CONTACT_MATCH_NOT_APPLICABLE;
+
+ if (a->given && b->given) {
+ ++possible;
+ if (name_fragment_match_with_synonyms (a->given, b->given, FALSE /* both inputs are complete */)) {
+ ++matches;
+ given_match = TRUE;
+ }
+ }
+
+ if (a->additional && b->additional) {
+ ++possible;
+ if (name_fragment_match_with_synonyms (a->additional, b->additional, FALSE /* both inputs are complete */)) {
+ ++matches;
+ additional_match = TRUE;
+ }
+ }
+
+ if (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;
+ }
+
+ /* This will match bob@foo.ximian.com and bob@ximian.com */
+ return *addr1 == '.' || *addr2 == '.';
+}
+
+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) {
+ char *addr1 = (char *) i1->data;
+
+ i2 = contact2_email;
+ while (i2 && match != EAB_CONTACT_MATCH_EXACT) {
+ char *addr2 = (char *) 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;
+ result = combine_comparisons (result, eab_contact_compare_name (contact1, contact2));
+ result = combine_comparisons (result, eab_contact_compare_nickname (contact1, contact2));
+ 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));
+
+ 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 (EBook *book, EBookStatus status, GList *contacts, gpointer closure)
+{
+ /* XXX we need to free contacts */
+ MatchSearchInfo *info = (MatchSearchInfo *) closure;
+ EABContactMatchType best_match = EAB_CONTACT_MATCH_NONE;
+ EContact *best_contact = NULL;
+ GList *remaining_contacts = NULL;
+ const GList *i;
+
+ if (status != E_BOOK_ERROR_OK) {
+ info->cb (info->contact, NULL, EAB_CONTACT_MATCH_NONE, info->closure);
+ match_search_info_free (info);
+ return;
+ }
+
+ /* remove the contacts we're to avoid from the list, if they're present */
+ for (i = contacts; i != NULL; i = g_list_next (i)) {
+ EContact *this_contact = E_CONTACT (i->data);
+ GList *iterator;
+ gboolean avoid = FALSE;
+ for (iterator = info->avoid; iterator; iterator = iterator->next) {
+ if (!strcmp (e_contact_get_const (iterator->data, E_CONTACT_UID),
+ e_contact_get_const (this_contact, E_CONTACT_UID))) {
+ avoid = TRUE;
+ break;
+ }
+ }
+ if (!avoid)
+ remaining_contacts = g_list_prepend (remaining_contacts, this_contact);
+ }
+
+ remaining_contacts = g_list_reverse (remaining_contacts);
+
+ for (i = remaining_contacts; i != NULL; i = g_list_next (i)) {
+ EContact *this_contact = E_CONTACT (i->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;
+ }
+ }
+
+ g_list_free (remaining_contacts);
+
+ info->cb (info->contact, best_contact, best_match, info->closure);
+ match_search_info_free (info);
+}
+
+#define MAX_QUERY_PARTS 10
+static void
+use_common_book_cb (EBook *book, gpointer closure)
+{
+ MatchSearchInfo *info = (MatchSearchInfo *) closure;
+ EContact *contact = info->contact;
+ EContactName *contact_name;
+ GList *contact_email;
+ gchar *query_parts[MAX_QUERY_PARTS];
+ gint p=0;
+ gchar *query, *qj;
+ int i;
+
+ if (book == NULL) {
+ info->cb (info->contact, NULL, EAB_CONTACT_MATCH_NONE, info->closure);
+ match_search_info_free (info);
+ return;
+ }
+
+ 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 > 0) {
+ query = g_strdup_printf ("(or %s)", qj);
+ g_free (qj);
+ } else {
+ query = qj;
+ }
+
+ if (query && *query)
+ e_book_async_get_contacts (book, query, query_cb, info);
+ else
+ query_cb (book, E_BOOK_ERROR_OK, NULL, info);
+
+ g_free (query);
+}
+
+void
+eab_contact_locate_match (EContact *contact, EABContactMatchQueryCallback cb, gpointer closure)
+{
+ MatchSearchInfo *info;
+
+ g_return_if_fail (contact && E_IS_CONTACT (contact));
+ g_return_if_fail (cb != NULL);
+
+ info = g_new (MatchSearchInfo, 1);
+ info->contact = contact;
+ g_object_ref (contact);
+ info->cb = cb;
+ info->closure = closure;
+ info->avoid = NULL;
+
+ addressbook_load_default_book (use_common_book_cb, info);
+}
+
+/**
+ * e_contact_locate_match_full:
+ * @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 (EBook *book, EContact *contact, GList *avoid, EABContactMatchQueryCallback cb, gpointer closure)
+{
+ MatchSearchInfo *info;
+
+ g_return_if_fail (contact && E_IS_CONTACT (contact));
+ g_return_if_fail (cb != NULL);
+
+ info = g_new (MatchSearchInfo, 1);
+ info->contact = 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)
+ use_common_book_cb (book, info);
+ else
+ addressbook_load_default_book (use_common_book_cb, info);
+}
+
diff --git a/addressbook/gui/merging/eab-contact-compare.h b/addressbook/gui/merging/eab-contact-compare.h
new file mode 100644
index 0000000000..07a9cd7e35
--- /dev/null
+++ b/addressbook/gui/merging/eab-contact-compare.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * eab-contact-compare.h
+ *
+ * Copyright (C) 2001,2002,2003 Ximian, Inc.
+ *
+ * Authors: Jon Trowbridge <trow@ximian.com>
+ * Chris Toshok <toshok@ximian.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ */
+
+#ifndef __EAB_CONTACT_COMPARE_H__
+#define __EAB_CONTACT_COMPARE_H__
+
+#include "ebook/e-book.h"
+#include "ebook/e-contact.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_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 (EContact *contact, EABContactMatchQueryCallback cb, gpointer closure);
+void eab_contact_locate_match_full (EBook *book, EContact *contact, GList *avoid, EABContactMatchQueryCallback cb, gpointer closure);
+
+
+
+#endif /* __E_CONTACT_COMPARE_H__ */
+
diff --git a/addressbook/gui/merging/e-card-duplicate-detected.glade b/addressbook/gui/merging/eab-contact-duplicate-detected.glade
index 5c287cd163..c44547fb92 100644
--- a/addressbook/gui/merging/e-card-duplicate-detected.glade
+++ b/addressbook/gui/merging/eab-contact-duplicate-detected.glade
@@ -68,12 +68,12 @@
<property name="column_spacing">6</property>
<child>
- <widget class="Custom" id="custom-old-card">
+ <widget class="Custom" id="custom-old-contact">
<property name="visible">True</property>
- <property name="creation_function">e_card_merging_create_old_card</property>
+ <property name="creation_function">_eab_contact_merging_create_contact_display</property>
<property name="int1">0</property>
<property name="int2">0</property>
- <property name="last_modification_time">Fri, 08 Jun 2001 01:33:22 GMT</property>
+ <property name="last_modification_time">Sun, 05 Oct 2003 03:55:10 GMT</property>
</widget>
<packing>
<property name="left_attach">1</property>
@@ -155,12 +155,12 @@ in this folder. Would you like to add it anyway?</property>
</child>
<child>
- <widget class="Custom" id="custom-new-card">
+ <widget class="Custom" id="custom-new-contact">
<property name="visible">True</property>
- <property name="creation_function">e_card_merging_create_old_card</property>
+ <property name="creation_function">_eab_contact_merging_create_contact_display</property>
<property name="int1">0</property>
<property name="int2">0</property>
- <property name="last_modification_time">Fri, 08 Jun 2001 01:33:22 GMT</property>
+ <property name="last_modification_time">Sun, 05 Oct 2003 03:54:50 GMT</property>
</widget>
<packing>
<property name="left_attach">1</property>
diff --git a/addressbook/gui/merging/eab-contact-merging.c b/addressbook/gui/merging/eab-contact-merging.c
new file mode 100644
index 0000000000..38a3c188b2
--- /dev/null
+++ b/addressbook/gui/merging/eab-contact-merging.c
@@ -0,0 +1,197 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Code for checking for duplicates when doing EContact work.
+ *
+ * Authors:
+ * Christopher James Lahey <clahey@ximian.com>
+ * Chris Toshok <toshok@ximian.com>
+ *
+ * Copyright (C) 2001, 2002, 2003, Ximian, Inc.
+ */
+
+#include <config.h>
+
+#include "eab-contact-merging.h"
+#include "eab-contact-compare.h"
+#include <glade/glade.h>
+#include <gtk/gtksignal.h>
+#include "addressbook/gui/widgets/eab-contact-display.h"
+
+typedef enum {
+ E_CONTACT_MERGING_ADD,
+ E_CONTACT_MERGING_COMMIT
+} EContactMergingOpType;
+
+typedef struct {
+ EContactMergingOpType op;
+ EBook *book;
+ EContact *contact;
+ EBookIdCallback id_cb;
+ EBookCallback cb;
+ gpointer closure;
+} EContactMergingLookup;
+
+static void
+free_lookup (EContactMergingLookup *lookup)
+{
+ g_object_unref (lookup->book);
+ g_object_unref (lookup->contact);
+
+ g_free (lookup);
+}
+
+static void
+final_id_cb (EBook *book, EBookStatus status, const char *id, gpointer closure)
+{
+ EContactMergingLookup *lookup = closure;
+
+ if (lookup->id_cb)
+ lookup->id_cb (lookup->book, status, id, lookup->closure);
+
+ free_lookup (lookup);
+}
+
+static void
+final_cb (EBook *book, EBookStatus status, gpointer closure)
+{
+ EContactMergingLookup *lookup = closure;
+
+ if (lookup->cb)
+ lookup->cb (lookup->book, status, lookup->closure);
+
+ free_lookup (lookup);
+}
+
+static void
+doit (EContactMergingLookup *lookup)
+{
+ if (lookup->op == E_CONTACT_MERGING_ADD)
+ e_book_async_add_contact (lookup->book, lookup->contact, final_id_cb, lookup);
+ else if (lookup->op == E_CONTACT_MERGING_COMMIT)
+ e_book_async_commit_contact (lookup->book, lookup->contact, final_cb, lookup);
+}
+
+static void
+cancelit (EContactMergingLookup *lookup)
+{
+ if (lookup->op == E_CONTACT_MERGING_ADD) {
+ if (lookup->id_cb)
+ final_id_cb (lookup->book, E_BOOK_ERROR_CANCELLED, NULL, lookup);
+ } else if (lookup->op == E_CONTACT_MERGING_COMMIT) {
+ if (lookup->cb)
+ final_cb (lookup->book, E_BOOK_ERROR_CANCELLED, lookup);
+ }
+}
+
+static void
+response (GtkWidget *dialog, int response, EContactMergingLookup *lookup)
+{
+ gtk_widget_destroy (dialog);
+
+ switch (response) {
+ case 0:
+ doit (lookup);
+ break;
+ case 1:
+ cancelit (lookup);
+ break;
+ }
+}
+
+static void
+match_query_callback (EContact *contact, EContact *match, EABContactMatchType type, gpointer closure)
+{
+ EContactMergingLookup *lookup = closure;
+
+ if ((gint) type <= (gint) EAB_CONTACT_MATCH_VAGUE) {
+ doit (lookup);
+ } else {
+ GladeXML *ui;
+
+ GtkWidget *widget;
+
+ if (lookup->op == E_CONTACT_MERGING_ADD)
+ ui = glade_xml_new (EVOLUTION_GLADEDIR "/eab-contact-duplicate-detected.glade", NULL, NULL);
+ else if (lookup->op == E_CONTACT_MERGING_COMMIT)
+ ui = glade_xml_new (EVOLUTION_GLADEDIR "/eab-contact-merging-commit-duplicate-detected.glade", NULL, NULL);
+ else {
+ doit (lookup);
+ return;
+ }
+
+ widget = glade_xml_get_widget (ui, "custom-old-contact");
+ eab_contact_display_render (EAB_CONTACT_DISPLAY (widget),
+ match, EAB_CONTACT_DISPLAY_RENDER_COMPACT);
+
+ widget = glade_xml_get_widget (ui, "custom-new-contact");
+ eab_contact_display_render (EAB_CONTACT_DISPLAY (widget),
+ contact, EAB_CONTACT_DISPLAY_RENDER_COMPACT);
+
+ widget = glade_xml_get_widget (ui, "dialog-duplicate-contact");
+
+ g_signal_connect (widget, "response",
+ G_CALLBACK (response), lookup);
+
+ gtk_widget_show_all (widget);
+ }
+}
+
+gboolean
+eab_merging_book_add_contact (EBook *book,
+ EContact *contact,
+ EBookIdCallback cb,
+ gpointer closure)
+{
+ EContactMergingLookup *lookup;
+
+ lookup = g_new (EContactMergingLookup, 1);
+
+ lookup->op = E_CONTACT_MERGING_ADD;
+ lookup->book = g_object_ref (book);
+ lookup->contact = g_object_ref (contact);
+ lookup->id_cb = cb;
+ lookup->closure = closure;
+
+ eab_contact_locate_match_full (book, contact, NULL, match_query_callback, lookup);
+
+ return TRUE;
+}
+
+gboolean
+eab_merging_book_commit_contact (EBook *book,
+ EContact *contact,
+ EBookCallback cb,
+ gpointer closure)
+{
+ EContactMergingLookup *lookup;
+ GList *avoid;
+
+ lookup = g_new (EContactMergingLookup, 1);
+
+ lookup->op = E_CONTACT_MERGING_COMMIT;
+ lookup->book = g_object_ref (book);
+ lookup->contact = g_object_ref (contact);
+ lookup->cb = cb;
+ lookup->closure = closure;
+
+ avoid = g_list_append (NULL, contact);
+
+ eab_contact_locate_match_full (book, contact, avoid, match_query_callback, lookup);
+
+ g_list_free (avoid);
+
+ return TRUE;
+}
+
+GtkWidget *
+_eab_contact_merging_create_contact_display(gchar *name,
+ gchar *string1, gchar *string2,
+ gint int1, gint int2);
+
+GtkWidget *
+_eab_contact_merging_create_contact_display(gchar *name,
+ gchar *string1, gchar *string2,
+ gint int1, gint int2)
+{
+ return eab_contact_display_new();
+}
diff --git a/addressbook/gui/merging/eab-contact-merging.h b/addressbook/gui/merging/eab-contact-merging.h
new file mode 100644
index 0000000000..6011263f31
--- /dev/null
+++ b/addressbook/gui/merging/eab-contact-merging.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * The Evolution addressbook client object.
+ *
+ * Authors:
+ * Christopher James Lahey <clahey@ximian.com>
+ * Chris Toshok <toshok@ximian.com>
+ *
+ * Copyright (C) 2001, 2002, 2003 Ximian, Inc.
+ */
+
+#ifndef __E_CONTACT_MERGING_H__
+#define __E_CONTACT_MERGING_H__
+
+#include <addressbook/backend/ebook/e-book-async.h>
+
+G_BEGIN_DECLS
+
+gboolean eab_merging_book_add_contact (EBook *book,
+ EContact *contact,
+ EBookIdCallback cb,
+ gpointer closure);
+gboolean eab_merging_book_commit_contact (EBook *book,
+ EContact *contact,
+ EBookCallback cb,
+ gpointer closure);
+
+G_END_DECLS
+
+#endif /* ! __EAB_CONTACT_MERGING_H__ */