/*
* Code for checking for duplicates when doing EContact work.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see <http://www.gnu.org/licenses/>
*
*
* Authors:
* Christopher James Lahey <clahey@ximian.com>
* Chris Toshok <toshok@ximian.com>
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#include <config.h>
#include "eab-contact-merging.h"
#include "eab-contact-compare.h"
#include <gtk/gtk.h>
#include <string.h>
#include "addressbook/gui/widgets/eab-contact-display.h"
#include "e-util/e-util.h"
#include "e-util/e-util-private.h"
#include <glib/gi18n.h>
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;
EBook *book;
/*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;
EBookIdAsyncCallback id_cb;
EBookAsyncCallback cb;
EBookContactAsyncCallback c_cb;
gpointer closure;
} EContactMergingLookup;
struct dropdown_data {
EContact *match;
EContactField field;
};
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->book, 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->book, lookup->contact, lookup->avoid, match_query_callback, lookup);
}
}
static void
free_lookup (EContactMergingLookup *lookup)
{
g_object_unref (lookup->book);
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 (EBook *book, const GError *error, const gchar *id, gpointer closure)
{
EContactMergingLookup *lookup = closure;
if (lookup->id_cb)
lookup->id_cb (lookup->book, error, id, lookup->closure);
free_lookup (lookup);
finished_lookup ();
}
static void
final_cb_as_id (EBook *book, const GError *error, gpointer closure)
{
EContactMergingLookup *lookup = closure;
if (lookup->id_cb)
lookup->id_cb (lookup->book, error, lookup->contact ? e_contact_get_const (lookup->contact, E_CONTACT_UID) : NULL, lookup->closure);
free_lookup (lookup);
finished_lookup ();
}
static void
final_cb (EBook *book, const GError *error, gpointer closure)
{
EContactMergingLookup *lookup = closure;
if (lookup->cb)
lookup->cb (lookup->book, error, lookup->closure);
free_lookup (lookup);
finished_lookup ();
}
static void
doit (EContactMergingLookup *lookup, gboolean force_commit)
{
if (lookup->op == E_CONTACT_MERGING_ADD) {
if (force_commit)
e_book_commit_contact_async (lookup->book, lookup->contact, final_cb_as_id, lookup);
else
e_book_add_contact_async (lookup->book, lookup->contact, final_id_cb, lookup);
} else if (lookup->op == E_CONTACT_MERGING_COMMIT)
e_book_commit_contact_async (lookup->book, lookup->contact, final_cb, lookup);
}
static void
cancelit (EContactMergingLookup *lookup)
{
GError *error = g_error_new (E_BOOK_ERROR, E_BOOK_ERROR_CANCELLED, _("Cancelled"));
if (lookup->op == E_CONTACT_MERGING_ADD) {
final_id_cb (lookup->book, error, NULL, lookup);
} else if (lookup->op == E_CONTACT_MERGING_COMMIT) {
final_cb (lookup->book, 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;
str = gtk_combo_box_get_active_text (GTK_COMBO_BOX (dropdown));
if (g_ascii_strcasecmp(str, ""))
e_contact_set (data->match, data->field, str);
else
e_contact_set (data->match, data->field, NULL);
return;
}
static gint
mergeit (EContactMergingLookup *lookup)
{
GtkWidget *scrolled_window, *label, *hbox, *dropdown;
GtkWidget *content_area;
GtkDialog *dialog;
GtkTable *table;
EContactField field;
gchar *str = NULL, *string = NULL, *string1 = NULL;
gint num_of_email;
GList *email_attr_list;
gint row = -1;
gint value = 0, result;
dialog = (GtkDialog *)(gtk_dialog_new_with_buttons (_("Merge Contact"), NULL, GTK_DIALOG_NO_SEPARATOR, NULL));
gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
content_area = gtk_dialog_get_content_area (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 ((GtkDialog *) dialog,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
_("_Merge"), GTK_RESPONSE_OK,
NULL);
email_attr_list = e_contact_get_attributes (lookup->match, E_CONTACT_EMAIL);
num_of_email = g_list_length (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) {
/*Four email id's present, should be compared with all email id's in duplicate contact */
/*Merge only if number of email id's in existing contact is less than 4 */
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)) {
EContactField use_field = field;
row++;
str = (gchar *)e_contact_get_const (lookup->contact, use_field);
switch (num_of_email)
{
case 0:
use_field = E_CONTACT_EMAIL_1;
break;
case 1:
/*New contact has email that is NOT equal to email in duplicate contact*/
if ((str && *str) && (g_ascii_strcasecmp (e_contact_get_const (lookup->match, E_CONTACT_EMAIL_1),str))) {
use_field = E_CONTACT_EMAIL_2;
break;
}
else/*Either the new contact has no email OR the email already exist in the duplicate contact*/
continue;
case 2:
/*New contact has email and it is equal to neither of the 2 emails in the duplicate contact*/
if ((str && *str) &&
(g_ascii_strcasecmp (str,e_contact_get_const (lookup->match, E_CONTACT_EMAIL_1))) &&
(g_ascii_strcasecmp (e_contact_get_const (lookup->match, E_CONTACT_EMAIL_2),str))) {
use_field = E_CONTACT_EMAIL_3;
break;
}
else
continue;
case 3:
/*New contact has email and it is equal to none of the 3 emails in the duplicate contact*/
if ((str && *str) &&
(g_ascii_strcasecmp (e_contact_get_const (lookup->match, E_CONTACT_EMAIL_1),str)) &&
(g_ascii_strcasecmp (e_contact_get_const (lookup->match, E_CONTACT_EMAIL_2),str)) &&
(g_ascii_strcasecmp (e_contact_get_const (lookup->match, E_CONTACT_EMAIL_3),str)))
use_field = E_CONTACT_EMAIL_4;
else
continue;
}
label = gtk_label_new (_("Email"));
hbox = gtk_hbox_new (FALSE, 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_new_text ();
gtk_combo_box_append_text (GTK_COMBO_BOX (dropdown), string);
data = g_new0 (dropdown_data, 1);
gtk_combo_box_append_text (GTK_COMBO_BOX (dropdown), "");
gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 0);
data->field = use_field;
data->match = lookup->match;
e_contact_set (lookup->match, use_field, string);
g_signal_connect (dropdown, "changed", G_CALLBACK(dropdown_changed), data);
hbox = gtk_hbox_new (FALSE, 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);
continue;
}
if (((field == E_CONTACT_FULL_NAME) && (!g_ascii_strcasecmp (string, string1)))) {
row++;
label = gtk_label_new (e_contact_pretty_name (field));
hbox = gtk_hbox_new (FALSE, 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_hbox_new (FALSE, 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_hbox_new (FALSE, 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_new_text ();
gtk_combo_box_append_text (GTK_COMBO_BOX (dropdown), string);
e_contact_set (lookup->match, field, string);
if (string1 && *string1)
gtk_combo_box_append_text (GTK_COMBO_BOX (dropdown), string1);
else
gtk_combo_box_append_text (GTK_COMBO_BOX (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);
hbox = gtk_hbox_new (FALSE, 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 (dialog);
switch (result)
{
case GTK_RESPONSE_OK:
lookup->contact = lookup->match;
e_book_remove_contact_async (lookup->book, lookup->match, NULL, lookup);
e_book_add_contact_async (lookup->book, lookup->contact, final_id_cb, lookup);
value = 1;
break;
case GTK_RESPONSE_CANCEL:
default:
value = 0;
break;
}
gtk_widget_destroy (GTK_WIDGET (dialog));
g_list_free (email_attr_list);
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;
for (field = E_CONTACT_FULL_NAME; field != (E_CONTACT_LAST_SIMPLE_STRING -1); field++) {
email_attr_list = e_contact_get_attributes (match, E_CONTACT_EMAIL);
num_of_email = g_list_length (email_attr_list);
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:
return FALSE;
case 1:
if ((str && *str) && (g_ascii_strcasecmp (e_contact_get_const (match, E_CONTACT_EMAIL_1),str)))
return FALSE;
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)))
return FALSE;
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)))
return FALSE;
}
}
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)))
return FALSE;
/*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))
return FALSE;
}
}
g_list_free (email_attr_list);
return TRUE;
}
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, 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;
/* XXX I think we're leaking the GtkBuilder. */
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);
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);
}
}
gboolean
eab_merging_book_add_contact (EBook *book,
EContact *contact,
EBookIdAsyncCallback cb,
gpointer closure)
{
EContactMergingLookup *lookup;
lookup = g_new (EContactMergingLookup, 1);
lookup->op = E_CONTACT_MERGING_ADD;
lookup->book = g_object_ref (book);
lookup->contact = g_object_ref (contact);
lookup->id_cb = cb;
lookup->closure = closure;
lookup->avoid = NULL;
lookup->match = NULL;
add_lookup (lookup);
return TRUE;
}
gboolean
eab_merging_book_commit_contact (EBook *book,
EContact *contact,
EBookAsyncCallback cb,
gpointer closure)
{
EContactMergingLookup *lookup;
lookup = g_new (EContactMergingLookup, 1);
lookup->op = E_CONTACT_MERGING_COMMIT;
lookup->book = g_object_ref (book);
lookup->contact = g_object_ref (contact);
lookup->cb = cb;
lookup->closure = closure;
lookup->avoid = g_list_append (NULL, contact);
lookup->match = NULL;
add_lookup (lookup);
return TRUE;
}
gboolean
eab_merging_book_find_contact (EBook *book,
EContact *contact,
EBookContactAsyncCallback cb,
gpointer closure)
{
EContactMergingLookup *lookup;
lookup = g_new (EContactMergingLookup, 1);
lookup->op = E_CONTACT_MERGING_FIND;
lookup->book = g_object_ref (book);
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;
}