From a3b2e00c235aecaaaad14881d0444e840089cf7e Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Tue, 24 Sep 2013 09:21:34 +0200 Subject: 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. --- addressbook/gui/Makefile.am | 2 +- addressbook/gui/contact-editor/Makefile.am | 3 +- addressbook/gui/contact-list-editor/Makefile.am | 2 +- addressbook/gui/merging/Makefile.am | 25 - .../eab-contact-commit-duplicate-detected.ui | 185 ----- addressbook/gui/merging/eab-contact-compare.c | 839 --------------------- addressbook/gui/merging/eab-contact-compare.h | 101 --- .../gui/merging/eab-contact-duplicate-detected.ui | 219 ------ addressbook/gui/merging/eab-contact-merging.c | 797 ------------------- addressbook/gui/merging/eab-contact-merging.h | 66 -- addressbook/gui/widgets/Makefile.am | 21 +- .../eab-contact-commit-duplicate-detected.ui | 185 +++++ addressbook/gui/widgets/eab-contact-compare.c | 839 +++++++++++++++++++++ addressbook/gui/widgets/eab-contact-compare.h | 101 +++ .../gui/widgets/eab-contact-duplicate-detected.ui | 219 ++++++ addressbook/gui/widgets/eab-contact-merging.c | 797 +++++++++++++++++++ addressbook/gui/widgets/eab-contact-merging.h | 66 ++ configure.ac | 1 - modules/addressbook/Makefile.am | 1 - modules/vcard-inline/Makefile.am | 1 - modules/vcard-inline/e-mail-part-vcard.c | 2 +- po/POTFILES.in | 6 +- 22 files changed, 2229 insertions(+), 2249 deletions(-) delete mode 100644 addressbook/gui/merging/Makefile.am delete mode 100644 addressbook/gui/merging/eab-contact-commit-duplicate-detected.ui delete mode 100644 addressbook/gui/merging/eab-contact-compare.c delete mode 100644 addressbook/gui/merging/eab-contact-compare.h delete mode 100644 addressbook/gui/merging/eab-contact-duplicate-detected.ui delete mode 100644 addressbook/gui/merging/eab-contact-merging.c delete mode 100644 addressbook/gui/merging/eab-contact-merging.h create mode 100644 addressbook/gui/widgets/eab-contact-commit-duplicate-detected.ui create mode 100644 addressbook/gui/widgets/eab-contact-compare.c create mode 100644 addressbook/gui/widgets/eab-contact-compare.h create mode 100644 addressbook/gui/widgets/eab-contact-duplicate-detected.ui create mode 100644 addressbook/gui/widgets/eab-contact-merging.c create mode 100644 addressbook/gui/widgets/eab-contact-merging.h diff --git a/addressbook/gui/Makefile.am b/addressbook/gui/Makefile.am index 7f08bf425d..fb552f216d 100644 --- a/addressbook/gui/Makefile.am +++ b/addressbook/gui/Makefile.am @@ -1,3 +1,3 @@ -SUBDIRS = merging widgets contact-editor contact-list-editor +SUBDIRS = widgets contact-editor contact-list-editor -include $(top_srcdir)/git.mk diff --git a/addressbook/gui/contact-editor/Makefile.am b/addressbook/gui/contact-editor/Makefile.am index 5307298a0f..4e5d0b7111 100644 --- a/addressbook/gui/contact-editor/Makefile.am +++ b/addressbook/gui/contact-editor/Makefile.am @@ -4,7 +4,7 @@ libecontacteditor_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ -I$(top_srcdir) \ -I$(top_srcdir)/addressbook/ \ - -I$(top_srcdir)/addressbook/gui/merging \ + -I$(top_srcdir)/addressbook/gui/widgets \ -I$(top_builddir)/shell \ -DEVOLUTION_UIDIR=\""$(uidir)"\" \ -DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\" \ @@ -29,7 +29,6 @@ libecontacteditor_la_LIBADD = \ $(top_builddir)/e-util/libevolution-util.la \ $(top_builddir)/addressbook/util/libeabutil.la \ $(top_builddir)/addressbook/gui/widgets/libeabwidgets.la \ - $(top_builddir)/addressbook/gui/merging/libeabbookmerging.la \ $(top_builddir)/addressbook/printing/libecontactprint.la \ $(EVOLUTION_ADDRESSBOOK_LIBS) \ $(EVOLUTION_DATA_SERVER_LIBS) \ diff --git a/addressbook/gui/contact-list-editor/Makefile.am b/addressbook/gui/contact-list-editor/Makefile.am index 6f38f09160..01a04bce82 100644 --- a/addressbook/gui/contact-list-editor/Makefile.am +++ b/addressbook/gui/contact-list-editor/Makefile.am @@ -4,7 +4,7 @@ libecontactlisteditor_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ -I$(top_srcdir) \ -I$(top_srcdir)/addressbook/ \ - -I$(top_srcdir)/addressbook/gui/merging \ + -I$(top_srcdir)/addressbook/gui/widgets \ -I$(top_srcdir)/addressbook/gui/contact-editor \ -I$(top_builddir)/shell \ -DEVOLUTION_UIDIR=\""$(uidir)"\" \ diff --git a/addressbook/gui/merging/Makefile.am b/addressbook/gui/merging/Makefile.am deleted file mode 100644 index 319c1221fc..0000000000 --- a/addressbook/gui/merging/Makefile.am +++ /dev/null @@ -1,25 +0,0 @@ -noinst_LTLIBRARIES = libeabbookmerging.la - -libeabbookmerging_la_CPPFLAGS = \ - $(AM_CPPFLAGS) \ - -DG_LOG_DOMAIN=\"eab-contact-merging\" \ - -DEVOLUTION_UIDIR=\""$(uidir)"\" \ - -I$(top_srcdir) \ - -I$(top_srcdir)/addressbook \ - $(EVOLUTION_DATA_SERVER_CFLAGS) \ - $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) - -libeabbookmerging_la_SOURCES = \ - eab-contact-compare.c \ - eab-contact-compare.h \ - eab-contact-merging.c \ - eab-contact-merging.h - -ui_DATA = \ - eab-contact-duplicate-detected.ui \ - eab-contact-commit-duplicate-detected.ui - -EXTRA_DIST = $(ui_DATA) - --include $(top_srcdir)/git.mk diff --git a/addressbook/gui/merging/eab-contact-commit-duplicate-detected.ui b/addressbook/gui/merging/eab-contact-commit-duplicate-detected.ui deleted file mode 100644 index 04ed728a56..0000000000 --- a/addressbook/gui/merging/eab-contact-commit-duplicate-detected.ui +++ /dev/null @@ -1,185 +0,0 @@ - - - - - 6 - True - Duplicate Contact Detected - GTK_WINDOW_TOPLEVEL - GTK_WIN_POS_CENTER_ON_PARENT - False - True - False - - - True - False - 6 - - - True - GTK_BUTTONBOX_END - - - True - True - True - gtk-cancel - True - GTK_RELIEF_NORMAL - - - - - True - True - True - gtk-save - True - GTK_RELIEF_NORMAL - - - - - 0 - False - True - GTK_PACK_END - - - - - 6 - True - 5 - 2 - False - 6 - 6 - - - True - - - 1 - 2 - 4 - 5 - - - - - True - The name or email of this contact already exists in this folder. Would you like to save the changes anyway? - False - False - GTK_JUSTIFY_LEFT - False - False - 0 - 0.5 - 0 - 0 - - - 1 - 2 - 0 - 1 - fill - - - - - True - Conflicting Contact: - False - False - GTK_JUSTIFY_CENTER - False - False - 0 - 0.5 - 0 - 0 - - - 1 - 2 - 3 - 4 - fill - - - - - True - Changed Contact: - False - False - GTK_JUSTIFY_CENTER - False - False - 0 - 0.5 - 0 - 0 - - - 1 - 2 - 1 - 2 - fill - - - - - - True - - - 1 - 2 - 2 - 3 - - - - - True - 0.5 - 0 - 1 - 0 - - - True - avatar-default - - - - - 0 - 1 - 0 - 5 - fill - fill - - - - - 0 - True - True - - - - - - button3 - button4 - - - diff --git a/addressbook/gui/merging/eab-contact-compare.c b/addressbook/gui/merging/eab-contact-compare.c deleted file mode 100644 index bfca37cbff..0000000000 --- a/addressbook/gui/merging/eab-contact-compare.c +++ /dev/null @@ -1,839 +0,0 @@ -/* - * 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 - * - * - * Authors: - * Jon Trowbridge - * Chris Toshok - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include - -#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/merging/eab-contact-compare.h b/addressbook/gui/merging/eab-contact-compare.h deleted file mode 100644 index f6b05705cf..0000000000 --- a/addressbook/gui/merging/eab-contact-compare.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * - * 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 - * - * - * Authors: - * Jon Trowbridge - * Chris Toshok - * - * 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 - -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/merging/eab-contact-duplicate-detected.ui b/addressbook/gui/merging/eab-contact-duplicate-detected.ui deleted file mode 100644 index 124b9c2787..0000000000 --- a/addressbook/gui/merging/eab-contact-duplicate-detected.ui +++ /dev/null @@ -1,219 +0,0 @@ - - - - - True - Duplicate Contact Detected - GTK_WINDOW_TOPLEVEL - GTK_WIN_POS_NONE - False - True - False - True - False - False - GDK_WINDOW_TYPE_HINT_DIALOG - GDK_GRAVITY_NORTH_WEST - 400 - 500 - - - True - False - 0 - - - True - GTK_BUTTONBOX_END - - - True - True - True - gtk-cancel - True - GTK_RELIEF_NORMAL - True - - - - - True - True - True - gtk-add - True - GTK_RELIEF_NORMAL - True - - - - - True - True - True - _Merge - True - GTK_RELIEF_NORMAL - True - - - - - 0 - False - True - GTK_PACK_END - - - - - True - True - GTK_POLICY_NEVER - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - GTK_CORNER_TOP_LEFT - - - - - 12 - True - 5 - 2 - False - 6 - 12 - - - True - - - 1 - 2 - 4 - 5 - - - - - True - The name or email address of this contact already exists -in this folder. Would you like to add it anyway? - False - False - GTK_JUSTIFY_LEFT - False - False - 0 - 0.5 - 0 - 0 - - - 1 - 2 - 0 - 1 - fill - - - - - True - Original Contact: - False - False - GTK_JUSTIFY_CENTER - False - False - 0 - 0.5 - 0 - 0 - - - 1 - 2 - 3 - 4 - fill - - - - - True - New Contact: - False - False - GTK_JUSTIFY_CENTER - False - False - 0 - 0.5 - 0 - 0 - - - 1 - 2 - 1 - 2 - fill - - - - - - True - - - 1 - 2 - 2 - 3 - - - - - True - 0.5 - 0 - 1 - 0 - 0 - 0 - 0 - 0 - - - True - avatar-default - - - - - 0 - 1 - 0 - 5 - fill - fill - - - - - - - - - - - - button4 - button3 - button5 - - - diff --git a/addressbook/gui/merging/eab-contact-merging.c b/addressbook/gui/merging/eab-contact-merging.c deleted file mode 100644 index 56fef2bc40..0000000000 --- a/addressbook/gui/merging/eab-contact-merging.c +++ /dev/null @@ -1,797 +0,0 @@ -/* - * 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 - * - * - * Authors: - * Christopher James Lahey - * Chris Toshok - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include "eab-contact-merging.h" -#include "eab-contact-compare.h" -#include -#include -#include "addressbook/gui/widgets/eab-contact-display.h" -#include "e-util/e-util.h" -#include "e-util/e-util-private.h" -#include - -#include - -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/merging/eab-contact-merging.h b/addressbook/gui/merging/eab-contact-merging.h deleted file mode 100644 index fbdc9b0889..0000000000 --- a/addressbook/gui/merging/eab-contact-merging.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 - * - * - * Authors: - * Christopher James Lahey - * Chris Toshok - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef __E_CONTACT_MERGING_H__ -#define __E_CONTACT_MERGING_H__ - -#include - -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__ */ 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 @@ + + + + + 6 + True + Duplicate Contact Detected + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_CENTER_ON_PARENT + False + True + False + + + True + False + 6 + + + True + GTK_BUTTONBOX_END + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + + + + + True + True + True + gtk-save + True + GTK_RELIEF_NORMAL + + + + + 0 + False + True + GTK_PACK_END + + + + + 6 + True + 5 + 2 + False + 6 + 6 + + + True + + + 1 + 2 + 4 + 5 + + + + + True + The name or email of this contact already exists in this folder. Would you like to save the changes anyway? + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + + + 1 + 2 + 0 + 1 + fill + + + + + True + Conflicting Contact: + False + False + GTK_JUSTIFY_CENTER + False + False + 0 + 0.5 + 0 + 0 + + + 1 + 2 + 3 + 4 + fill + + + + + True + Changed Contact: + False + False + GTK_JUSTIFY_CENTER + False + False + 0 + 0.5 + 0 + 0 + + + 1 + 2 + 1 + 2 + fill + + + + + + True + + + 1 + 2 + 2 + 3 + + + + + True + 0.5 + 0 + 1 + 0 + + + True + avatar-default + + + + + 0 + 1 + 0 + 5 + fill + fill + + + + + 0 + True + True + + + + + + button3 + button4 + + + 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 + * + * + * Authors: + * Jon Trowbridge + * Chris Toshok + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#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 + * + * + * Authors: + * Jon Trowbridge + * Chris Toshok + * + * 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 + +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 @@ + + + + + True + Duplicate Contact Detected + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + 400 + 500 + + + True + False + 0 + + + True + GTK_BUTTONBOX_END + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + + + + + True + True + True + gtk-add + True + GTK_RELIEF_NORMAL + True + + + + + True + True + True + _Merge + True + GTK_RELIEF_NORMAL + True + + + + + 0 + False + True + GTK_PACK_END + + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + + 12 + True + 5 + 2 + False + 6 + 12 + + + True + + + 1 + 2 + 4 + 5 + + + + + True + The name or email address of this contact already exists +in this folder. Would you like to add it anyway? + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + + + 1 + 2 + 0 + 1 + fill + + + + + True + Original Contact: + False + False + GTK_JUSTIFY_CENTER + False + False + 0 + 0.5 + 0 + 0 + + + 1 + 2 + 3 + 4 + fill + + + + + True + New Contact: + False + False + GTK_JUSTIFY_CENTER + False + False + 0 + 0.5 + 0 + 0 + + + 1 + 2 + 1 + 2 + fill + + + + + + True + + + 1 + 2 + 2 + 3 + + + + + True + 0.5 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + + + True + avatar-default + + + + + 0 + 1 + 0 + 5 + fill + fill + + + + + + + + + + + + button4 + button3 + button5 + + + 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 + * + * + * Authors: + * Christopher James Lahey + * Chris Toshok + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "eab-contact-merging.h" +#include "eab-contact-compare.h" +#include +#include +#include "addressbook/gui/widgets/eab-contact-display.h" +#include "e-util/e-util.h" +#include "e-util/e-util-private.h" +#include + +#include + +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 + * + * + * Authors: + * Christopher James Lahey + * Chris Toshok + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef __E_CONTACT_MERGING_H__ +#define __E_CONTACT_MERGING_H__ + +#include + +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__ */ diff --git a/configure.ac b/configure.ac index 651bbd5519..4509a64660 100644 --- a/configure.ac +++ b/configure.ac @@ -1425,7 +1425,6 @@ addressbook/Makefile addressbook/gui/Makefile addressbook/gui/contact-editor/Makefile addressbook/gui/contact-list-editor/Makefile -addressbook/gui/merging/Makefile addressbook/gui/widgets/Makefile addressbook/importers/Makefile addressbook/printing/Makefile diff --git a/modules/addressbook/Makefile.am b/modules/addressbook/Makefile.am index 8faf8a3967..f30a75204c 100644 --- a/modules/addressbook/Makefile.am +++ b/modules/addressbook/Makefile.am @@ -55,7 +55,6 @@ module_addressbook_la_LIBADD = \ $(top_builddir)/composer/libevolution-mail-composer.la \ $(top_builddir)/addressbook/printing/libecontactprint.la \ $(top_builddir)/shell/libevolution-shell.la \ - $(top_builddir)/addressbook/gui/merging/libeabbookmerging.la \ $(top_builddir)/addressbook/gui/widgets/libeabwidgets.la \ $(top_builddir)/addressbook/util/libeabutil.la \ $(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \ diff --git a/modules/vcard-inline/Makefile.am b/modules/vcard-inline/Makefile.am index 84a21eb96a..cc7627d510 100644 --- a/modules/vcard-inline/Makefile.am +++ b/modules/vcard-inline/Makefile.am @@ -23,7 +23,6 @@ module_vcard_inline_la_LIBADD = \ $(top_builddir)/em-format/libevolution-mail-formatter.la \ $(top_builddir)/addressbook/util/libeabutil.la \ $(top_builddir)/addressbook/gui/widgets/libeabwidgets.la \ - $(top_builddir)/addressbook/gui/merging/libeabbookmerging.la \ $(top_builddir)/addressbook/printing/libecontactprint.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ diff --git a/modules/vcard-inline/e-mail-part-vcard.c b/modules/vcard-inline/e-mail-part-vcard.c index 5986154d55..3f52ec636d 100644 --- a/modules/vcard-inline/e-mail-part-vcard.c +++ b/modules/vcard-inline/e-mail-part-vcard.c @@ -25,7 +25,7 @@ #include #include -#include +#include #define E_MAIL_PART_VCARD_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ diff --git a/po/POTFILES.in b/po/POTFILES.in index 9f18248d31..785fd59d18 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -9,9 +9,9 @@ addressbook/gui/contact-editor/e-contact-quick-add.c [type: gettext/glade]addressbook/gui/contact-editor/fullname.ui [type: gettext/glade]addressbook/gui/contact-list-editor/contact-list-editor.ui addressbook/gui/contact-list-editor/e-contact-list-editor.c -[type: gettext/glade]addressbook/gui/merging/eab-contact-commit-duplicate-detected.ui -[type: gettext/glade]addressbook/gui/merging/eab-contact-duplicate-detected.ui -addressbook/gui/merging/eab-contact-merging.c +[type: gettext/glade]addressbook/gui/widgets/eab-contact-commit-duplicate-detected.ui +[type: gettext/glade]addressbook/gui/widgets/eab-contact-duplicate-detected.ui +addressbook/gui/widgets/eab-contact-merging.c addressbook/gui/widgets/addresstypes.xml addressbook/gui/widgets/e-addressbook-model.c addressbook/gui/widgets/e-addressbook-reflow-adapter.c -- cgit v1.2.3