1 /*
2 * Code for checking for duplicates when doing EContact work.
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 *
16 *
17 * Authors:
18 * Christopher James Lahey <clahey@ximian.com>
19 * Chris Toshok <toshok@ximian.com>
20 *
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 *
23 */
24
25 #include "evolution-config.h"
26
27 #include "eab-contact-merging.h"
28 #include "eab-contact-compare.h"
29 #include <gtk/gtk.h>
30 #include <string.h>
31 #include "addressbook/gui/widgets/eab-contact-display.h"
32 #include "addressbook/util/eab-book-util.h"
33 #include "e-util/e-util.h"
34 #include "e-util/e-util-private.h"
35 #include <glib/gi18n.h>
36
37 #include <camel/camel.h>
38
39 /* should be kept in synch with e-contact-editor */
40 static EContactField
41 im_fetch_set[] =
42 {
43 E_CONTACT_IM_AIM,
44 E_CONTACT_IM_JABBER,
45 E_CONTACT_IM_YAHOO,
46 E_CONTACT_IM_GADUGADU,
47 E_CONTACT_IM_MSN,
48 E_CONTACT_IM_ICQ,
49 E_CONTACT_IM_GROUPWISE,
50 E_CONTACT_IM_SKYPE,
51 E_CONTACT_IM_TWITTER,
52 E_CONTACT_IM_GOOGLE_TALK,
53 E_CONTACT_IM_MATRIX
54 };
55
56 typedef enum {
57 E_CONTACT_MERGING_ADD,
58 E_CONTACT_MERGING_COMMIT,
59 E_CONTACT_MERGING_FIND
60 } EContactMergingOpType;
61
62 typedef struct _MergeDialogData {
63 GtkWidget *dialog;
64 GList *use_email_attr_list, *contact_email_attr_list, *match_email_attr_list;
65 GList *use_tel_attr_list, *contact_tel_attr_list, *match_tel_attr_list;
66 GList *use_im_attr_list, *contact_im_attr_list, *match_im_attr_list;
67 GList *use_sip_attr_list, *contact_sip_attr_list, *match_sip_attr_list;
68 gint row;
69 } MergeDialogData;
70
71 typedef struct {
72 EContactMergingOpType op;
73 ESourceRegistry *registry;
74 EBookClient *book_client;
75 /*contact is the new contact which the user has tried to add to the addressbook*/
76 EContact *contact;
77 /*match is the duplicate contact already existing in the addressbook*/
78 EContact *match;
79 GList *avoid;
80 EABMergingAsyncCallback cb;
81 EABMergingIdAsyncCallback id_cb;
82 EABMergingContactAsyncCallback c_cb;
83 gpointer closure;
84
85 MergeDialogData *merge_dialog_data;
86 } EContactMergingLookup;
87
88 typedef struct _dropdown_data {
89 EContact *match;
90 EContactField field;
91
92 /* for E_CONTACT_EMAIL field */
93 GList *email_attr_list_item;
94 EVCardAttribute *email_attr;
95 } dropdown_data;
96
97 static void match_query_callback (EContact *contact, EContact *match, EABContactMatchType type, gpointer closure);
98
99 #define SIMULTANEOUS_MERGING_REQUESTS 20
100 #define EVOLUTION_UI_SLOT_PARAM "X-EVOLUTION-UI-SLOT"
101
102 static GList *merging_queue = NULL;
103 static gint running_merge_requests = 0;
104
105 static void
merge_dialog_data_free(MergeDialogData * mdd)106 merge_dialog_data_free (MergeDialogData *mdd)
107 {
108 if (!mdd)
109 return;
110
111 gtk_widget_destroy (mdd->dialog);
112
113 g_list_free_full (mdd->match_email_attr_list, (GDestroyNotify) e_vcard_attribute_free);
114 g_list_free_full (mdd->contact_email_attr_list, (GDestroyNotify) e_vcard_attribute_free);
115 g_list_free (mdd->use_email_attr_list);
116
117 g_list_free_full (mdd->match_tel_attr_list, (GDestroyNotify) e_vcard_attribute_free);
118 g_list_free_full (mdd->contact_tel_attr_list, (GDestroyNotify) e_vcard_attribute_free);
119 g_list_free (mdd->use_tel_attr_list);
120
121 g_list_free_full (mdd->match_im_attr_list, (GDestroyNotify) e_vcard_attribute_free);
122 g_list_free_full (mdd->contact_im_attr_list, (GDestroyNotify) e_vcard_attribute_free);
123 g_list_free (mdd->use_im_attr_list);
124
125 g_list_free_full (mdd->match_sip_attr_list, (GDestroyNotify) e_vcard_attribute_free);
126 g_list_free_full (mdd->contact_sip_attr_list, (GDestroyNotify) e_vcard_attribute_free);
127 g_list_free (mdd->use_sip_attr_list);
128
129 g_slice_free (MergeDialogData, mdd);
130 }
131
132 static void
add_lookup(EContactMergingLookup * lookup)133 add_lookup (EContactMergingLookup *lookup)
134 {
135 if (running_merge_requests < SIMULTANEOUS_MERGING_REQUESTS) {
136 running_merge_requests++;
137 eab_contact_locate_match_full (
138 lookup->registry, lookup->book_client,
139 lookup->contact, lookup->avoid,
140 match_query_callback, lookup);
141 }
142 else {
143 merging_queue = g_list_append (merging_queue, lookup);
144 }
145 }
146
147 static void
finished_lookup(void)148 finished_lookup (void)
149 {
150 running_merge_requests--;
151
152 while (running_merge_requests < SIMULTANEOUS_MERGING_REQUESTS) {
153 EContactMergingLookup *lookup;
154
155 if (!merging_queue)
156 break;
157
158 lookup = merging_queue->data;
159
160 merging_queue = g_list_remove_link (merging_queue, merging_queue);
161
162 running_merge_requests++;
163 eab_contact_locate_match_full (
164 lookup->registry, lookup->book_client,
165 lookup->contact, lookup->avoid,
166 match_query_callback, lookup);
167 }
168 }
169
170 static EContactMergingLookup *
new_lookup(void)171 new_lookup (void)
172 {
173 return g_slice_new0 (EContactMergingLookup);
174 }
175
176 static void
free_lookup(EContactMergingLookup * lookup)177 free_lookup (EContactMergingLookup *lookup)
178 {
179 merge_dialog_data_free (lookup->merge_dialog_data);
180 g_object_unref (lookup->registry);
181 g_object_unref (lookup->book_client);
182 g_object_unref (lookup->contact);
183 g_list_free (lookup->avoid);
184 if (lookup->match)
185 g_object_unref (lookup->match);
186 g_slice_free (EContactMergingLookup, lookup);
187 }
188
189 static void
final_id_cb(EBookClient * book_client,const GError * error,const gchar * id,gpointer closure)190 final_id_cb (EBookClient *book_client,
191 const GError *error,
192 const gchar *id,
193 gpointer closure)
194 {
195 EContactMergingLookup *lookup = closure;
196
197 if (lookup->id_cb)
198 lookup->id_cb (
199 lookup->book_client,
200 error, id, lookup->closure);
201
202 free_lookup (lookup);
203
204 finished_lookup ();
205 }
206
207 static void
final_cb_as_id(EBookClient * book_client,const GError * error,gpointer closure)208 final_cb_as_id (EBookClient *book_client,
209 const GError *error,
210 gpointer closure)
211 {
212 EContactMergingLookup *lookup = closure;
213
214 if (lookup->id_cb)
215 lookup->id_cb (
216 lookup->book_client,
217 error, lookup->contact ?
218 e_contact_get_const (
219 lookup->contact, E_CONTACT_UID) : NULL,
220 lookup->closure);
221
222 free_lookup (lookup);
223
224 finished_lookup ();
225 }
226
227 static void
final_cb(EBookClient * book_client,const GError * error,gpointer closure)228 final_cb (EBookClient *book_client,
229 const GError *error,
230 gpointer closure)
231 {
232 EContactMergingLookup *lookup = closure;
233
234 if (lookup->cb)
235 lookup->cb (lookup->book_client, error, lookup->closure);
236
237 free_lookup (lookup);
238
239 finished_lookup ();
240 }
241
242 static void
modify_contact_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)243 modify_contact_ready_cb (GObject *source_object,
244 GAsyncResult *result,
245 gpointer user_data)
246 {
247 EBookClient *book_client = E_BOOK_CLIENT (source_object);
248 EContactMergingLookup *lookup = user_data;
249 GError *error = NULL;
250
251 g_return_if_fail (book_client != NULL);
252 g_return_if_fail (lookup != NULL);
253
254 e_book_client_modify_contact_finish (book_client, result, &error);
255
256 if (lookup->op == E_CONTACT_MERGING_ADD)
257 final_cb_as_id (book_client, error, lookup);
258 else
259 final_cb (book_client, error, lookup);
260
261 if (error != NULL)
262 g_error_free (error);
263 }
264
265 static void
add_contact_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)266 add_contact_ready_cb (GObject *source_object,
267 GAsyncResult *result,
268 gpointer user_data)
269 {
270 EBookClient *book_client = E_BOOK_CLIENT (source_object);
271 EContactMergingLookup *lookup = user_data;
272 gchar *uid = NULL;
273 GError *error = NULL;
274
275 g_return_if_fail (book_client != NULL);
276 g_return_if_fail (lookup != NULL);
277
278 e_book_client_add_contact_finish (book_client, result, &uid, &error);
279
280 final_id_cb (book_client, error, uid, lookup);
281
282 if (error != NULL)
283 g_error_free (error);
284 g_free (uid);
285 }
286
287 static void
doit(EContactMergingLookup * lookup,gboolean force_modify)288 doit (EContactMergingLookup *lookup,
289 gboolean force_modify)
290 {
291 if (lookup->op == E_CONTACT_MERGING_ADD) {
292 if (force_modify)
293 e_book_client_modify_contact (lookup->book_client, lookup->contact, E_BOOK_OPERATION_FLAG_NONE, NULL, modify_contact_ready_cb, lookup);
294 else
295 e_book_client_add_contact (lookup->book_client, lookup->contact, E_BOOK_OPERATION_FLAG_NONE, NULL, add_contact_ready_cb, lookup);
296 } else if (lookup->op == E_CONTACT_MERGING_COMMIT)
297 e_book_client_modify_contact (lookup->book_client, lookup->contact, E_BOOK_OPERATION_FLAG_NONE, NULL, modify_contact_ready_cb, lookup);
298 }
299
300 static void
cancelit(EContactMergingLookup * lookup)301 cancelit (EContactMergingLookup *lookup)
302 {
303 GError *error;
304
305 error = g_error_new_literal (
306 G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Cancelled"));
307
308 if (lookup->op == E_CONTACT_MERGING_ADD) {
309 final_id_cb (lookup->book_client, error, NULL, lookup);
310 } else if (lookup->op == E_CONTACT_MERGING_COMMIT) {
311 final_cb (lookup->book_client, error, lookup);
312 }
313
314 g_error_free (error);
315 }
316
317 static void
dialog_map(GtkWidget * window,GdkEvent * event,GtkWidget * grid)318 dialog_map (GtkWidget *window,
319 GdkEvent *event,
320 GtkWidget *grid)
321 {
322 GtkAllocation allocation;
323 gint h, w;
324
325 gtk_widget_get_allocation (grid, &allocation);
326
327 /* Spacing around the grid */
328 w = allocation.width + 30;
329 /* buttons and outer spacing */
330 h = allocation.height + 60;
331 if (w > 400)
332 w = 400;
333 if (h > 450)
334 h = 450;
335
336 gtk_widget_set_size_request (window, w, h);
337 }
338
339 static void
dropdown_changed(GtkWidget * dropdown,dropdown_data * data)340 dropdown_changed (GtkWidget *dropdown,
341 dropdown_data *data)
342 {
343 gchar *str = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dropdown));
344
345 if (str && *str)
346 e_contact_set (data->match, data->field, str);
347 else
348 e_contact_set (data->match, data->field, NULL);
349
350 g_free (str);
351 }
352
353 static void
attr_dropdown_changed(GtkWidget * dropdown,dropdown_data * data)354 attr_dropdown_changed (GtkWidget *dropdown,
355 dropdown_data *data)
356 {
357 gchar *str = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dropdown));
358
359 if (str && *str)
360 data->email_attr_list_item->data = data->email_attr;
361 else
362 data->email_attr_list_item->data = NULL;
363
364 g_free (str);
365 }
366
367 static void
remove_contact_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)368 remove_contact_ready_cb (GObject *source_object,
369 GAsyncResult *result,
370 gpointer user_data)
371 {
372 EBookClient *book_client = E_BOOK_CLIENT (source_object);
373 EContactMergingLookup *lookup = user_data;
374 GError *error = NULL;
375
376 g_return_if_fail (book_client != NULL);
377 g_return_if_fail (lookup != NULL);
378
379 e_book_client_remove_contact_finish (book_client, result, &error);
380
381 if (error != NULL) {
382 g_warning (
383 "%s: Failed to remove contact: %s",
384 G_STRFUNC, error->message);
385 g_error_free (error);
386 }
387
388 e_book_client_add_contact (
389 book_client, lookup->contact, E_BOOK_OPERATION_FLAG_NONE, NULL,
390 add_contact_ready_cb, lookup);
391 }
392
393 static void
create_dropdowns_for_multival_attr(GList * match_attr_list,GList * contact_attr_list,GList ** use_attr_list,gint * row,GtkGrid * grid,const gchar * (* label_str)(EVCardAttribute *))394 create_dropdowns_for_multival_attr (GList *match_attr_list,
395 GList *contact_attr_list,
396 GList **use_attr_list,
397 gint *row,
398 GtkGrid *grid,
399 const gchar * (*label_str) (EVCardAttribute*))
400 {
401 GtkWidget *label, *dropdown;
402 GList *miter, *citer;
403 GHashTable *match_attrs; /* attr in the 'match' contact from address book */
404
405 match_attrs = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, NULL);
406
407 for (miter = match_attr_list; miter; miter = g_list_next (miter)) {
408 EVCardAttribute *attr = miter->data;
409 gchar *value;
410
411 value = e_vcard_attribute_get_value (attr);
412 if (value && *value) {
413 g_hash_table_insert (match_attrs, value, attr);
414 *use_attr_list = g_list_prepend (*use_attr_list, attr);
415 } else {
416 g_free (value);
417 }
418 }
419
420 *use_attr_list = g_list_reverse (*use_attr_list);
421
422 for (citer = contact_attr_list; citer; citer = g_list_next (citer)) {
423 EVCardAttribute *attr = citer->data;
424 gchar *value;
425
426 value = e_vcard_attribute_get_value (attr);
427 if (value && *value) {
428 if (!g_hash_table_lookup (match_attrs, value)) {
429 dropdown_data *data;
430
431 /* the attr is not set in both contacts */
432 *use_attr_list = g_list_append (*use_attr_list, attr);
433
434 /* remove to avoid collisions with match UI_SLOTs */
435 e_vcard_attribute_remove_param (attr, EVOLUTION_UI_SLOT_PARAM);
436
437 (*row)++;
438 label = gtk_label_new (label_str (attr));
439 gtk_grid_attach (grid, label, 0, *row, 1, 1);
440
441 dropdown = gtk_combo_box_text_new ();
442 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), value);
443
444 data = g_new0 (dropdown_data, 1);
445
446 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), "");
447
448 gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 0);
449
450 data->email_attr_list_item = g_list_last (*use_attr_list);
451 data->email_attr = attr;
452
453 g_signal_connect (
454 dropdown, "changed",
455 G_CALLBACK (attr_dropdown_changed), data);
456 g_object_set_data_full (G_OBJECT (dropdown), "eab-contact-merging::dropdown-data", data, g_free);
457
458 gtk_grid_attach (grid, dropdown, 1, *row, 1, 1);
459 }
460 }
461 g_free (value);
462 }
463 g_hash_table_destroy (match_attrs);
464 }
465
466 static void
set_attributes(EContact * contact,EContactField field,GList * use_attr_list)467 set_attributes(EContact *contact, EContactField field, GList *use_attr_list)
468 {
469 GList *miter, *citer;
470
471 citer = NULL;
472 for (miter = use_attr_list; miter; miter = g_list_next (miter)) {
473 if (miter->data)
474 citer = g_list_prepend (citer, miter->data);
475 }
476 citer = g_list_reverse (citer);
477 e_contact_set_attributes (contact, field, citer);
478 g_list_free (citer);
479 }
480
481 static MergeDialogData *
merge_dialog_data_create(EContactMergingLookup * lookup,GtkWidget * parent)482 merge_dialog_data_create (EContactMergingLookup *lookup,
483 GtkWidget *parent)
484 {
485 GtkWidget *scrolled_window, *label, *dropdown;
486 GtkWidget *content_area;
487 GtkGrid *grid;
488 EContactField field;
489 gchar *string = NULL, *string1 = NULL;
490 MergeDialogData *mdd;
491
492 mdd = g_slice_new0 (MergeDialogData);
493 mdd->row = -1;
494
495 mdd->dialog = gtk_dialog_new ();
496 gtk_window_set_title (GTK_WINDOW (mdd->dialog), _("Merge Contact"));
497 gtk_container_set_border_width (GTK_CONTAINER (mdd->dialog), 5);
498 if (GTK_IS_WINDOW (parent))
499 gtk_window_set_transient_for (GTK_WINDOW (mdd->dialog), GTK_WINDOW (parent));
500
501 content_area = gtk_dialog_get_content_area (GTK_DIALOG (mdd->dialog));
502
503 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
504 gtk_scrolled_window_set_policy (
505 GTK_SCROLLED_WINDOW (scrolled_window),
506 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
507
508 grid = GTK_GRID (gtk_grid_new ());
509 g_object_set (G_OBJECT (grid),
510 "border-width", 12,
511 "row-spacing", 6,
512 "column-spacing", 2,
513 NULL);
514
515 gtk_dialog_add_buttons (
516 GTK_DIALOG (mdd->dialog),
517 _("_Cancel"), GTK_RESPONSE_CANCEL,
518 _("_Merge"), GTK_RESPONSE_OK,
519 NULL);
520
521 /*we match all the string fields of the already existing contact and the new contact.*/
522 for (field = E_CONTACT_FULL_NAME; field != (E_CONTACT_LAST_SIMPLE_STRING -1); field++) {
523 dropdown_data *data = NULL;
524 string = (gchar *) e_contact_get_const (lookup->contact, field);
525 string1 = (gchar *) e_contact_get_const (lookup->match, field);
526
527 /*the field must exist in the new as well as the duplicate contact*/
528 if (string && *string) {
529 if ((field >= E_CONTACT_FIRST_EMAIL_ID && field <= E_CONTACT_LAST_EMAIL_ID) ||
530 (field >= E_CONTACT_FIRST_PHONE_ID && field <= E_CONTACT_LAST_PHONE_ID) ||
531 (field >= E_CONTACT_IM_AIM_HOME_1 && field <= E_CONTACT_IM_ICQ_WORK_3) ||
532 (field >= E_CONTACT_IM_GADUGADU_HOME_1 && field <= E_CONTACT_IM_GADUGADU_WORK_3) ||
533 (field >= E_CONTACT_IM_SKYPE_HOME_1 && field <= E_CONTACT_IM_SKYPE_WORK_3) ||
534 (field >= E_CONTACT_IM_GOOGLE_TALK_HOME_1 && field <= E_CONTACT_IM_GOOGLE_TALK_WORK_3) ||
535 (field >= E_CONTACT_IM_MATRIX_HOME_1 && field <= E_CONTACT_IM_MATRIX_WORK_3)) {
536 /* ignore multival attributes, they are compared after this for-loop */
537 continue;
538 }
539
540 if (!(string1 && *string1) || (g_ascii_strcasecmp (string, string1))) {
541 mdd->row++;
542 label = gtk_label_new (e_contact_pretty_name (field));
543 gtk_grid_attach (grid, label, 0, mdd->row, 1, 1);
544 data = g_new0 (dropdown_data, 1);
545 dropdown = gtk_combo_box_text_new ();
546 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), string);
547
548 if (string1 && *string1)
549 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), string1);
550 else
551 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), "");
552
553 data->field = field;
554 data->match = lookup->match;
555
556 g_signal_connect (
557 dropdown, "changed",
558 G_CALLBACK (dropdown_changed), data);
559 g_object_set_data_full (G_OBJECT (dropdown), "eab-contact-merging::dropdown-data", data, g_free);
560
561 /* Only prefer the original value when it's filled */
562 if (string1 && *string1 && (
563 field == E_CONTACT_NICKNAME ||
564 field == E_CONTACT_GIVEN_NAME ||
565 field == E_CONTACT_FAMILY_NAME ||
566 field == E_CONTACT_FULL_NAME))
567 gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 1);
568 else
569 gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 0);
570
571 gtk_grid_attach (grid, dropdown, 1, mdd->row, 1, 1);
572 }
573 }
574 }
575
576 mdd->match_email_attr_list = e_contact_get_attributes (lookup->match, E_CONTACT_EMAIL);
577 mdd->contact_email_attr_list = e_contact_get_attributes (lookup->contact, E_CONTACT_EMAIL);
578 mdd->use_email_attr_list = NULL;
579 create_dropdowns_for_multival_attr (mdd->match_email_attr_list, mdd->contact_email_attr_list,
580 &(mdd->use_email_attr_list), &(mdd->row), grid, eab_get_email_label_text);
581
582 mdd->match_tel_attr_list = e_contact_get_attributes (lookup->match, E_CONTACT_TEL);
583 mdd->contact_tel_attr_list = e_contact_get_attributes (lookup->contact, E_CONTACT_TEL);
584 mdd->use_tel_attr_list = NULL;
585 create_dropdowns_for_multival_attr (mdd->match_tel_attr_list, mdd->contact_tel_attr_list,
586 &(mdd->use_tel_attr_list), &(mdd->row), grid, eab_get_phone_label_text);
587
588 mdd->match_sip_attr_list = e_contact_get_attributes (lookup->match, E_CONTACT_SIP);
589 mdd->contact_sip_attr_list = e_contact_get_attributes (lookup->contact, E_CONTACT_SIP);
590 mdd->use_sip_attr_list = NULL;
591 create_dropdowns_for_multival_attr (mdd->match_sip_attr_list, mdd->contact_sip_attr_list,
592 &(mdd->use_sip_attr_list), &(mdd->row), grid, eab_get_sip_label_text);
593
594 mdd->match_im_attr_list = e_contact_get_attributes_set (lookup->match, im_fetch_set, G_N_ELEMENTS (im_fetch_set));
595 mdd->contact_im_attr_list = e_contact_get_attributes_set (lookup->contact, im_fetch_set, G_N_ELEMENTS (im_fetch_set));
596 mdd->use_im_attr_list = NULL;
597 create_dropdowns_for_multival_attr (mdd->match_im_attr_list, mdd->contact_im_attr_list,
598 &(mdd->use_im_attr_list), &(mdd->row), grid, eab_get_im_label_text);
599
600 gtk_window_set_default_size (GTK_WINDOW (mdd->dialog), 420, 300);
601 gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (grid));
602 gtk_box_pack_start (GTK_BOX (content_area), GTK_WIDGET (scrolled_window), TRUE, TRUE, 0);
603 gtk_widget_show (scrolled_window);
604 g_signal_connect (
605 mdd->dialog, "map-event",
606 G_CALLBACK (dialog_map), grid);
607 gtk_widget_show_all (GTK_WIDGET (grid));
608
609 return mdd;
610 }
611
612 static gint
mergeit(EContactMergingLookup * lookup,GtkWidget * parent)613 mergeit (EContactMergingLookup *lookup,
614 GtkWidget *parent)
615 {
616 GList *ll;
617 gint value = 0, result, ii;
618
619 if (!lookup->merge_dialog_data)
620 lookup->merge_dialog_data = merge_dialog_data_create (lookup, parent);
621
622 if (lookup->merge_dialog_data->row == -1)
623 result = GTK_RESPONSE_OK;
624 else
625 result = gtk_dialog_run (GTK_DIALOG (lookup->merge_dialog_data->dialog));
626
627 switch (result) {
628 case GTK_RESPONSE_OK:
629 set_attributes (lookup->match, E_CONTACT_EMAIL, lookup->merge_dialog_data->use_email_attr_list);
630 set_attributes (lookup->match, E_CONTACT_TEL, lookup->merge_dialog_data->use_tel_attr_list);
631 set_attributes (lookup->match, E_CONTACT_SIP, lookup->merge_dialog_data->use_sip_attr_list);
632
633 for (ii = 0; ii < G_N_ELEMENTS (im_fetch_set); ii++) {
634 e_contact_set_attributes (lookup->match, im_fetch_set[ii], NULL);
635 }
636
637 for (ll = lookup->merge_dialog_data->use_im_attr_list; ll; ll = ll->next) {
638 EVCard *vcard;
639 vcard = E_VCARD (lookup->match);
640 e_vcard_append_attribute (vcard, e_vcard_attribute_copy ((EVCardAttribute *) ll->data));
641 }
642
643 g_object_unref (lookup->contact);
644 lookup->contact = g_object_ref (lookup->match);
645 e_book_client_remove_contact (
646 lookup->book_client,
647 lookup->match, E_BOOK_OPERATION_FLAG_NONE, NULL,
648 remove_contact_ready_cb, lookup);
649 value = 1;
650 break;
651 case GTK_RESPONSE_CANCEL:
652 default:
653 value = 0;
654 break;
655 }
656 gtk_widget_hide (lookup->merge_dialog_data->dialog);
657
658 return value;
659 }
660
661 static gboolean
check_if_same(EContact * contact,EContact * match)662 check_if_same (EContact *contact,
663 EContact *match)
664 {
665 EContactField field;
666 gchar *string = NULL, *string1 = NULL;
667 gboolean res = TRUE;
668
669
670 for (field = E_CONTACT_FULL_NAME; res && field != (E_CONTACT_LAST_SIMPLE_STRING -1); field++) {
671
672 if (field == E_CONTACT_EMAIL_1) {
673 GList *email_attr_list1, *email_attr_list2, *iter1, *iter2;
674 gint num_of_email1, num_of_email2;
675
676 email_attr_list1 = e_contact_get_attributes (contact, E_CONTACT_EMAIL);
677 num_of_email1 = g_list_length (email_attr_list1);
678
679 email_attr_list2 = e_contact_get_attributes (match, E_CONTACT_EMAIL);
680 num_of_email2 = g_list_length (email_attr_list2);
681
682 if (num_of_email1 != num_of_email2) {
683 res = FALSE;
684 } else { /* Do pairwise-comparisons on all of the e-mail addresses. */
685 iter1 = email_attr_list1;
686 while (iter1) {
687 gboolean matches = FALSE;
688 EVCardAttribute *attr;
689 gchar *email_address1;
690
691 attr = iter1->data;
692 email_address1 = e_vcard_attribute_get_value (attr);
693
694 iter2 = email_attr_list2;
695 while ( iter2 && matches == FALSE) {
696 gchar *email_address2;
697
698 attr = iter2->data;
699 email_address2 = e_vcard_attribute_get_value (attr);
700
701 if (g_ascii_strcasecmp (email_address1, email_address2) == 0) {
702 matches = TRUE;
703 }
704
705 g_free (email_address2);
706 iter2 = g_list_next (iter2);
707 }
708
709 g_free (email_address1);
710 iter1 = g_list_next (iter1);
711
712 if (matches == FALSE) {
713 res = FALSE;
714 break;
715 }
716 }
717 }
718
719 g_list_free_full (email_attr_list1, (GDestroyNotify) e_vcard_attribute_free);
720 g_list_free_full (email_attr_list2, (GDestroyNotify) e_vcard_attribute_free);
721 } else if (field > E_CONTACT_FIRST_EMAIL_ID && field <= E_CONTACT_LAST_EMAIL_ID) {
722 /* nothing to do, all emails are checked above */
723 }
724 else {
725 string = (gchar *) e_contact_get_const (contact, field);
726 string1 = (gchar *) e_contact_get_const (match, field);
727 if ((string && *string) && (string1 && *string1) && (g_ascii_strcasecmp (string1, string))) {
728 res = FALSE;
729 break;
730 /*if the field entry exist in either of the contacts,we'll have to give the choice and thus merge button should be sensitive*/
731 } else if ((string && *string) && !(string1 && *string1)) {
732 res = FALSE;
733 break;
734 }
735 }
736 }
737
738 return res;
739 }
740
741 static GtkWidget *
create_duplicate_contact_detected_dialog(EContact * old_contact,EContact * new_contact,gboolean disable_merge,gboolean is_for_commit)742 create_duplicate_contact_detected_dialog (EContact *old_contact,
743 EContact *new_contact,
744 gboolean disable_merge,
745 gboolean is_for_commit)
746 {
747 GtkWidget *widget, *scrolled;
748 GtkContainer *container;
749 GtkDialog *dialog;
750 const gchar *text;
751
752 widget = gtk_dialog_new ();
753 dialog = GTK_DIALOG (widget);
754
755 g_object_set (
756 G_OBJECT (dialog),
757 "title", _("Duplicate Contact Detected"),
758 "default-width", 500,
759 "default-height", 400,
760 NULL);
761
762 gtk_dialog_add_action_widget (dialog, e_dialog_button_new_with_icon ("process-stop", _("_Cancel")), GTK_RESPONSE_CANCEL);
763
764 if (is_for_commit) {
765 gtk_dialog_add_action_widget (dialog, e_dialog_button_new_with_icon ("document-save", _("_Save")), GTK_RESPONSE_OK);
766 } else {
767 gtk_dialog_add_action_widget (dialog, e_dialog_button_new_with_icon ("list-add", _("_Add")), GTK_RESPONSE_OK);
768 gtk_dialog_add_action_widget (dialog, e_dialog_button_new_with_icon (NULL, _("_Merge")), GTK_RESPONSE_APPLY);
769 }
770
771 if (disable_merge)
772 gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_APPLY, FALSE);
773
774 container = GTK_CONTAINER (gtk_dialog_get_content_area (dialog));
775
776 widget = gtk_grid_new ();
777 g_object_set (
778 G_OBJECT (widget),
779 "orientation", GTK_ORIENTATION_HORIZONTAL,
780 "hexpand", TRUE,
781 "halign", GTK_ALIGN_FILL,
782 "vexpand", TRUE,
783 "valign", GTK_ALIGN_FILL,
784 "margin", 12,
785 NULL);
786
787 gtk_container_add (container, widget);
788 container = GTK_CONTAINER (widget);
789
790 widget = gtk_image_new_from_icon_name ("avatar-default", GTK_ICON_SIZE_BUTTON);
791 g_object_set (
792 G_OBJECT (widget),
793 "hexpand", FALSE,
794 "halign", GTK_ALIGN_START,
795 "vexpand", FALSE,
796 "valign", GTK_ALIGN_START,
797 "margin-right", 12,
798 NULL);
799 gtk_container_add (container, widget);
800
801 widget = gtk_grid_new ();
802 g_object_set (
803 G_OBJECT (widget),
804 "orientation", GTK_ORIENTATION_VERTICAL,
805 "hexpand", TRUE,
806 "halign", GTK_ALIGN_FILL,
807 "vexpand", TRUE,
808 "valign", GTK_ALIGN_FILL,
809 NULL);
810
811 gtk_container_add (container, widget);
812 container = GTK_CONTAINER (widget);
813
814 if (is_for_commit)
815 text = _("The name or email address of this contact already exists\n"
816 "in this folder. Would you like to save the changes anyway?");
817 else
818 text = _("The name or email address of this contact already exists\n"
819 "in this folder. Would you like to add it anyway?");
820
821 widget = gtk_label_new (text);
822 g_object_set (
823 G_OBJECT (widget),
824 "hexpand", FALSE,
825 "halign", GTK_ALIGN_START,
826 "vexpand", FALSE,
827 "valign", GTK_ALIGN_START,
828 "margin-bottom", 6,
829 NULL);
830 gtk_container_add (container, widget);
831
832 if (is_for_commit)
833 text = _("Changed Contact:");
834 else
835 text = _("New Contact:");
836
837 widget = gtk_label_new (text);
838 g_object_set (
839 G_OBJECT (widget),
840 "hexpand", FALSE,
841 "halign", GTK_ALIGN_START,
842 "vexpand", FALSE,
843 "valign", GTK_ALIGN_START,
844 "margin-bottom", 6,
845 NULL);
846 gtk_container_add (container, widget);
847
848 scrolled = gtk_scrolled_window_new (NULL, NULL);
849 g_object_set (
850 G_OBJECT (scrolled),
851 "hexpand", TRUE,
852 "halign", GTK_ALIGN_FILL,
853 "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
854 "vexpand", TRUE,
855 "valign", GTK_ALIGN_FILL,
856 "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
857 "margin-bottom", 6,
858 NULL);
859 gtk_container_add (container, scrolled);
860
861 widget = eab_contact_display_new ();
862 g_object_set (
863 G_OBJECT (widget),
864 "hexpand", TRUE,
865 "halign", GTK_ALIGN_FILL,
866 "vexpand", TRUE,
867 "valign", GTK_ALIGN_FILL,
868 "contact", new_contact,
869 "mode", EAB_CONTACT_DISPLAY_RENDER_COMPACT,
870 NULL);
871 gtk_container_add (GTK_CONTAINER (scrolled), widget);
872
873 if (is_for_commit)
874 text = _("Conflicting Contact:");
875 else
876 text = _("Old Contact:");
877
878 widget = gtk_label_new (text);
879 g_object_set (
880 G_OBJECT (widget),
881 "hexpand", FALSE,
882 "halign", GTK_ALIGN_START,
883 "vexpand", FALSE,
884 "valign", GTK_ALIGN_START,
885 "margin-bottom", 6,
886 NULL);
887 gtk_container_add (container, widget);
888
889 scrolled = gtk_scrolled_window_new (NULL, NULL);
890 g_object_set (
891 G_OBJECT (scrolled),
892 "hexpand", TRUE,
893 "halign", GTK_ALIGN_FILL,
894 "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
895 "vexpand", TRUE,
896 "valign", GTK_ALIGN_FILL,
897 "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
898 NULL);
899 gtk_container_add (container, scrolled);
900
901 widget = eab_contact_display_new ();
902 g_object_set (
903 G_OBJECT (widget),
904 "hexpand", TRUE,
905 "halign", GTK_ALIGN_FILL,
906 "vexpand", TRUE,
907 "valign", GTK_ALIGN_FILL,
908 "contact", old_contact,
909 "mode", EAB_CONTACT_DISPLAY_RENDER_COMPACT,
910 NULL);
911 gtk_container_add (GTK_CONTAINER (scrolled), widget);
912
913 gtk_widget_show_all (gtk_dialog_get_content_area (dialog));
914
915 return GTK_WIDGET (dialog);
916 }
917
918 static void
response(GtkWidget * dialog,gint response,EContactMergingLookup * lookup)919 response (GtkWidget *dialog,
920 gint response,
921 EContactMergingLookup *lookup)
922 {
923 switch (response) {
924 case GTK_RESPONSE_OK:
925 doit (lookup, FALSE);
926 break;
927 case GTK_RESPONSE_CANCEL:
928 cancelit (lookup);
929 break;
930 case GTK_RESPONSE_APPLY:
931 if (mergeit (lookup, dialog))
932 break;
933 return;
934 case GTK_RESPONSE_DELETE_EVENT:
935 cancelit (lookup);
936 break;
937 default:
938 g_warn_if_reached ();
939 break;
940 }
941
942 gtk_widget_destroy (dialog);
943 }
944
945 static void
match_query_callback(EContact * contact,EContact * match,EABContactMatchType type,gpointer closure)946 match_query_callback (EContact *contact,
947 EContact *match,
948 EABContactMatchType type,
949 gpointer closure)
950 {
951 EContactMergingLookup *lookup = closure;
952 gboolean flag;
953 gboolean same_uids;
954
955 if (lookup->op == E_CONTACT_MERGING_FIND) {
956 if (lookup->c_cb)
957 lookup->c_cb (
958 lookup->book_client, NULL,
959 (gint) type <= (gint)
960 EAB_CONTACT_MATCH_VAGUE ? NULL : match,
961 lookup->closure);
962
963 free_lookup (lookup);
964 finished_lookup ();
965 return;
966 }
967
968 /* if had same UID, then we are editing old contact, thus force commit change to it */
969 same_uids = contact && match
970 && e_contact_get_const (contact, E_CONTACT_UID)
971 && e_contact_get_const (match, E_CONTACT_UID)
972 && g_str_equal (e_contact_get_const (contact, E_CONTACT_UID), e_contact_get_const (match, E_CONTACT_UID));
973
974 if ((gint) type <= (gint) EAB_CONTACT_MATCH_VAGUE || same_uids) {
975 doit (lookup, same_uids);
976 } else {
977 GtkWidget *dialog;
978
979 lookup->match = g_object_ref (match);
980 if (lookup->op == E_CONTACT_MERGING_ADD) {
981 /* Compares all the values of contacts and return true, if they match */
982 flag = check_if_same (contact, match);
983 dialog = create_duplicate_contact_detected_dialog (match, contact, flag, FALSE);
984 } else if (lookup->op == E_CONTACT_MERGING_COMMIT) {
985 dialog = create_duplicate_contact_detected_dialog (match, contact, FALSE, TRUE);
986 } else {
987 doit (lookup, FALSE);
988 return;
989 }
990
991 g_signal_connect (
992 dialog, "response",
993 G_CALLBACK (response), lookup);
994
995 gtk_widget_show_all (dialog);
996 }
997 }
998
999 gboolean
eab_merging_book_add_contact(ESourceRegistry * registry,EBookClient * book_client,EContact * contact,EABMergingIdAsyncCallback cb,gpointer closure)1000 eab_merging_book_add_contact (ESourceRegistry *registry,
1001 EBookClient *book_client,
1002 EContact *contact,
1003 EABMergingIdAsyncCallback cb,
1004 gpointer closure)
1005 {
1006 EContactMergingLookup *lookup;
1007
1008 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
1009
1010 lookup = new_lookup ();
1011
1012 lookup->op = E_CONTACT_MERGING_ADD;
1013 lookup->registry = g_object_ref (registry);
1014 lookup->book_client = g_object_ref (book_client);
1015 lookup->contact = g_object_ref (contact);
1016 lookup->id_cb = cb;
1017 lookup->closure = closure;
1018 lookup->avoid = NULL;
1019 lookup->match = NULL;
1020
1021 add_lookup (lookup);
1022
1023 return TRUE;
1024 }
1025
1026 gboolean
eab_merging_book_modify_contact(ESourceRegistry * registry,EBookClient * book_client,EContact * contact,EABMergingAsyncCallback cb,gpointer closure)1027 eab_merging_book_modify_contact (ESourceRegistry *registry,
1028 EBookClient *book_client,
1029 EContact *contact,
1030 EABMergingAsyncCallback cb,
1031 gpointer closure)
1032 {
1033 EContactMergingLookup *lookup;
1034
1035 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
1036
1037 lookup = new_lookup ();
1038
1039 lookup->op = E_CONTACT_MERGING_COMMIT;
1040 lookup->registry = g_object_ref (registry);
1041 lookup->book_client = g_object_ref (book_client);
1042 lookup->contact = g_object_ref (contact);
1043 lookup->cb = cb;
1044 lookup->closure = closure;
1045 lookup->avoid = g_list_append (NULL, contact);
1046 lookup->match = NULL;
1047
1048 add_lookup (lookup);
1049
1050 return TRUE;
1051 }
1052
1053 gboolean
eab_merging_book_find_contact(ESourceRegistry * registry,EBookClient * book_client,EContact * contact,EABMergingContactAsyncCallback cb,gpointer closure)1054 eab_merging_book_find_contact (ESourceRegistry *registry,
1055 EBookClient *book_client,
1056 EContact *contact,
1057 EABMergingContactAsyncCallback cb,
1058 gpointer closure)
1059 {
1060 EContactMergingLookup *lookup;
1061
1062 lookup = new_lookup ();
1063
1064 lookup->op = E_CONTACT_MERGING_FIND;
1065 lookup->registry = g_object_ref (registry);
1066 lookup->book_client = g_object_ref (book_client);
1067 lookup->contact = g_object_ref (contact);
1068 lookup->c_cb = cb;
1069 lookup->closure = closure;
1070 lookup->avoid = g_list_append (NULL, contact);
1071 lookup->match = NULL;
1072
1073 add_lookup (lookup);
1074
1075 return TRUE;
1076 }
1077