No issues found
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2 of the License, or (at your option) version 3.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with the program; if not, see <http://www.gnu.org/licenses/>
14 *
15 *
16 * Authors:
17 * Jon Trowbridge <trow@ximian.com>
18 *
19 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20 *
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <ctype.h>
28 #include <string.h>
29 #include <gtk/gtk.h>
30 #include <glib/gi18n.h>
31
32 #include <libedataserverui/libedataserverui.h>
33
34 #include <addressbook/util/eab-book-util.h>
35 #include "e-contact-editor.h"
36 #include "e-contact-quick-add.h"
37 #include "eab-contact-merging.h"
38 #include "libevolution-utils/e-alert-dialog.h"
39
40 typedef struct _QuickAdd QuickAdd;
41 struct _QuickAdd {
42 gchar *name;
43 gchar *email;
44 gchar *vcard;
45 EContact *contact;
46 GCancellable *cancellable;
47 ESourceRegistry *registry;
48 ESource *source;
49
50 EContactQuickAddCallback cb;
51 gpointer closure;
52
53 GtkWidget *dialog;
54 GtkWidget *name_entry;
55 GtkWidget *email_entry;
56 GtkWidget *combo_box;
57
58 gint refs;
59
60 };
61
62 static QuickAdd *
63 quick_add_new (ESourceRegistry *registry)
64 {
65 QuickAdd *qa = g_new0 (QuickAdd, 1);
66 qa->contact = e_contact_new ();
67 qa->registry = g_object_ref (registry);
68 qa->refs = 1;
69 return qa;
70 }
71
72 static void
73 quick_add_unref (QuickAdd *qa)
74 {
75 if (qa) {
76 --qa->refs;
77 if (qa->refs == 0) {
78 if (qa->cancellable != NULL) {
79 g_cancellable_cancel (qa->cancellable);
80 g_object_unref (qa->cancellable);
81 }
82 g_free (qa->name);
83 g_free (qa->email);
84 g_free (qa->vcard);
85 g_object_unref (qa->contact);
86 g_object_unref (qa->registry);
87 g_free (qa);
88 }
89 }
90 }
91
92 static void
93 quick_add_set_name (QuickAdd *qa,
94 const gchar *name)
95 {
96 if (name == qa->name)
97 return;
98
99 g_free (qa->name);
100 qa->name = g_strdup (name);
101 }
102
103 static void
104 quick_add_set_email (QuickAdd *qa,
105 const gchar *email)
106 {
107 if (email == qa->email)
108 return;
109
110 g_free (qa->email);
111 qa->email = g_strdup (email);
112 }
113
114 static void
115 quick_add_set_vcard (QuickAdd *qa,
116 const gchar *vcard)
117 {
118 if (vcard == qa->vcard)
119 return;
120
121 g_free (qa->vcard);
122 qa->vcard = g_strdup (vcard);
123 }
124
125 static void
126 merge_cb (GObject *source_object,
127 GAsyncResult *result,
128 gpointer user_data)
129 {
130 ESource *source = E_SOURCE (source_object);
131 QuickAdd *qa = user_data;
132 EClient *client = NULL;
133 GError *error = NULL;
134
135 e_client_utils_open_new_finish (source, result, &client, &error);
136
137 /* Ignore cancellations. */
138 if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
139 g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
140 g_warn_if_fail (client == NULL);
141 g_error_free (error);
142 return;
143 }
144
145 if (error != NULL) {
146 g_warn_if_fail (client == NULL);
147 if (qa->cb)
148 qa->cb (NULL, qa->closure);
149 g_error_free (error);
150 quick_add_unref (qa);
151 return;
152 }
153
154 g_return_if_fail (E_IS_CLIENT (client));
155
156 if (!e_client_is_readonly (client))
157 eab_merging_book_add_contact (
158 qa->registry, E_BOOK_CLIENT (client),
159 qa->contact, NULL, NULL);
160 else
161 e_alert_run_dialog_for_args (
162 e_shell_get_active_window (NULL),
163 "addressbook:error-read-only",
164 e_source_get_display_name (source),
165 NULL);
166
167 if (qa->cb)
168 qa->cb (qa->contact, qa->closure);
169
170 g_object_unref (client);
171
172 quick_add_unref (qa);
173 }
174
175 static void
176 quick_add_merge_contact (QuickAdd *qa)
177 {
178 if (qa->cancellable != NULL) {
179 g_cancellable_cancel (qa->cancellable);
180 g_object_unref (qa->cancellable);
181 }
182
183 qa->cancellable = g_cancellable_new ();
184
185 e_client_utils_open_new (
186 qa->source, E_CLIENT_SOURCE_TYPE_CONTACTS,
187 FALSE, qa->cancellable, merge_cb, qa);
188 }
189
190 /* Raise a contact editor with all fields editable,
191 * and hook up all signals accordingly. */
192
193 static void
194 contact_added_cb (EContactEditor *ce,
195 const GError *error,
196 EContact *contact,
197 gpointer closure)
198 {
199 QuickAdd *qa;
200
201 qa = g_object_get_data (G_OBJECT (ce), "quick_add");
202
203 if (qa) {
204 if (qa->cb)
205 qa->cb (qa->contact, qa->closure);
206
207 /* We don't need to unref qa because we set_data_full below */
208 g_object_set_data (G_OBJECT (ce), "quick_add", NULL);
209 }
210 }
211
212 static void
213 editor_closed_cb (GtkWidget *w,
214 gpointer closure)
215 {
216 QuickAdd *qa;
217
218 qa = g_object_get_data (G_OBJECT (w), "quick_add");
219
220 if (qa)
221 /* We don't need to unref qa because we set_data_full below */
222 g_object_set_data (G_OBJECT (w), "quick_add", NULL);
223 }
224
225 static void
226 ce_have_contact (EBookClient *book_client,
227 const GError *error,
228 EContact *contact,
229 gpointer closure)
230 {
231 QuickAdd *qa = (QuickAdd *) closure;
232
233 if (error) {
234 if (book_client)
235 g_object_unref (book_client);
236 g_warning (
237 "Failed to find contact, status %d (%s).",
238 error->code, error->message);
239 quick_add_unref (qa);
240 } else {
241 EShell *shell;
242 EABEditor *contact_editor;
243
244 if (contact) {
245 /* use found contact */
246 if (qa->contact)
247 g_object_unref (qa->contact);
248 qa->contact = g_object_ref (contact);
249 }
250
251 shell = e_shell_get_default ();
252 contact_editor = e_contact_editor_new (
253 shell, book_client, qa->contact, TRUE, TRUE /* XXX */);
254
255 /* Mark it as changed so the Save buttons are
256 * enabled when we bring up the dialog. */
257 g_object_set (
258 contact_editor, "changed", contact != NULL, NULL);
259
260 /* We pass this via object data, so that we don't get a
261 * dangling pointer referenced if both the "contact_added"
262 * and "editor_closed" get emitted. (Which, based on a
263 * backtrace in bugzilla, I think can happen and cause a
264 * crash. */
265 g_object_set_data_full (
266 G_OBJECT (contact_editor), "quick_add", qa,
267 (GDestroyNotify) quick_add_unref);
268
269 g_signal_connect (
270 contact_editor, "contact_added",
271 G_CALLBACK (contact_added_cb), NULL);
272 g_signal_connect (
273 contact_editor, "editor_closed",
274 G_CALLBACK (editor_closed_cb), NULL);
275
276 g_object_unref (book_client);
277 }
278 }
279
280 static void
281 ce_have_book (GObject *source_object,
282 GAsyncResult *result,
283 gpointer user_data)
284 {
285 ESource *source = E_SOURCE (source_object);
286 QuickAdd *qa = user_data;
287 EClient *client = NULL;
288 GError *error = NULL;
289
290 e_client_utils_open_new_finish (source, result, &client, &error);
291
292 /* Ignore cancellations. */
293 if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
294 g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
295 g_warn_if_fail (client == NULL);
296 g_error_free (error);
297 return;
298 }
299
300 if (error != NULL) {
301 g_warn_if_fail (client == NULL);
302 g_warning (
303 "Couldn't open local address book (%s).",
304 error->message);
305 quick_add_unref (qa);
306 g_error_free (error);
307 return;
308 }
309
310 g_return_if_fail (E_IS_CLIENT (client));
311
312 eab_merging_book_find_contact (
313 qa->registry, E_BOOK_CLIENT (client),
314 qa->contact, ce_have_contact, qa);
315 }
316
317 static void
318 edit_contact (QuickAdd *qa)
319 {
320 if (qa->cancellable != NULL) {
321 g_cancellable_cancel (qa->cancellable);
322 g_object_unref (qa->cancellable);
323 }
324
325 qa->cancellable = g_cancellable_new ();
326
327 e_client_utils_open_new (
328 qa->source, E_CLIENT_SOURCE_TYPE_CONTACTS,
329 FALSE, qa->cancellable, ce_have_book, qa);
330 }
331
332 #define QUICK_ADD_RESPONSE_EDIT_FULL 2
333
334 static void
335 clicked_cb (GtkWidget *w,
336 gint button,
337 gpointer closure)
338 {
339 QuickAdd *qa = (QuickAdd *) closure;
340
341 /* Get data out of entries. */
342 if (!qa->vcard && (button == GTK_RESPONSE_OK ||
343 button == QUICK_ADD_RESPONSE_EDIT_FULL)) {
344 gchar *name = NULL;
345 gchar *email = NULL;
346
347 if (qa->name_entry)
348 name = gtk_editable_get_chars (
349 GTK_EDITABLE (qa->name_entry), 0, -1);
350
351 if (qa->email_entry)
352 email = gtk_editable_get_chars (
353 GTK_EDITABLE (qa->email_entry), 0, -1);
354
355 e_contact_set (
356 qa->contact, E_CONTACT_FULL_NAME,
357 (name != NULL) ? name : "");
358
359 e_contact_set (
360 qa->contact, E_CONTACT_EMAIL_1,
361 (email != NULL) ? email : "");
362
363 g_free (name);
364 g_free (email);
365 }
366
367 gtk_widget_destroy (w);
368
369 if (button == GTK_RESPONSE_OK) {
370
371 /* OK */
372 quick_add_merge_contact (qa);
373
374 } else if (button == QUICK_ADD_RESPONSE_EDIT_FULL) {
375
376 /* EDIT FULL */
377 edit_contact (qa);
378
379 } else {
380 /* CANCEL */
381 quick_add_unref (qa);
382 }
383
384 }
385
386 static void
387 sanitize_widgets (QuickAdd *qa)
388 {
389 GtkComboBox *combo_box;
390 const gchar *active_id;
391 gboolean enabled = TRUE;
392
393 g_return_if_fail (qa != NULL);
394 g_return_if_fail (qa->dialog != NULL);
395
396 combo_box = GTK_COMBO_BOX (qa->combo_box);
397 active_id = gtk_combo_box_get_active_id (combo_box);
398 enabled = (active_id != NULL);
399
400 gtk_dialog_set_response_sensitive (
401 GTK_DIALOG (qa->dialog),
402 QUICK_ADD_RESPONSE_EDIT_FULL, enabled);
403 gtk_dialog_set_response_sensitive (
404 GTK_DIALOG (qa->dialog), GTK_RESPONSE_OK, enabled);
405 }
406
407 static void
408 source_changed (ESourceComboBox *source_combo_box,
409 QuickAdd *qa)
410 {
411 ESource *source;
412
413 source = e_source_combo_box_ref_active (source_combo_box);
414
415 if (source != NULL) {
416 if (qa->source != NULL)
417 g_object_unref (qa->source);
418 qa->source = source; /* takes reference */
419 }
420
421 sanitize_widgets (qa);
422 }
423
424 static GtkWidget *
425 build_quick_add_dialog (QuickAdd *qa)
426 {
427 GtkWidget *container;
428 GtkWidget *dialog;
429 GtkWidget *label;
430 GtkTable *table;
431 ESource *source;
432 const gchar *extension_name;
433 const gint xpad = 0, ypad = 0;
434
435 g_return_val_if_fail (qa != NULL, NULL);
436
437 dialog = gtk_dialog_new_with_buttons (
438 _("Contact Quick-Add"),
439 e_shell_get_active_window (NULL),
440 0,
441 _("_Edit Full"), QUICK_ADD_RESPONSE_EDIT_FULL,
442 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
443 GTK_STOCK_OK, GTK_RESPONSE_OK,
444 NULL);
445
446 gtk_widget_ensure_style (dialog);
447
448 container = gtk_dialog_get_action_area (GTK_DIALOG (dialog));
449 gtk_container_set_border_width (GTK_CONTAINER (container), 12);
450 container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
451 gtk_container_set_border_width (GTK_CONTAINER (container), 0);
452
453 g_signal_connect (
454 dialog, "response",
455 G_CALLBACK (clicked_cb), qa);
456
457 qa->dialog = dialog;
458
459 qa->name_entry = gtk_entry_new ();
460 if (qa->name)
461 gtk_entry_set_text (GTK_ENTRY (qa->name_entry), qa->name);
462
463 qa->email_entry = gtk_entry_new ();
464 if (qa->email)
465 gtk_entry_set_text (GTK_ENTRY (qa->email_entry), qa->email);
466
467 if (qa->vcard) {
468 /* when adding vCard, then do not allow change name or email */
469 gtk_widget_set_sensitive (qa->name_entry, FALSE);
470 gtk_widget_set_sensitive (qa->email_entry, FALSE);
471 }
472
473 extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
474 source = e_source_registry_ref_default_address_book (qa->registry);
475
476 qa->combo_box = e_source_combo_box_new (qa->registry, extension_name);
477 e_source_combo_box_set_active (
478 E_SOURCE_COMBO_BOX (qa->combo_box), source);
479
480 g_object_unref (source);
481
482 source_changed (E_SOURCE_COMBO_BOX (qa->combo_box), qa);
483 g_signal_connect (
484 qa->combo_box, "changed",
485 G_CALLBACK (source_changed), qa);
486
487 table = GTK_TABLE (gtk_table_new (3, 2, FALSE));
488 gtk_table_set_row_spacings (table, 6);
489 gtk_table_set_col_spacings (table, 12);
490
491 label = gtk_label_new_with_mnemonic (_("_Full name"));
492 gtk_label_set_mnemonic_widget ((GtkLabel *) label, qa->name_entry);
493 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
494
495 gtk_table_attach (
496 table, label,
497 0, 1, 0, 1,
498 GTK_FILL, 0, xpad, ypad);
499 gtk_table_attach (
500 table, qa->name_entry,
501 1, 2, 0, 1,
502 GTK_EXPAND | GTK_FILL, 0, xpad, ypad);
503
504 label = gtk_label_new_with_mnemonic (_("E_mail"));
505 gtk_label_set_mnemonic_widget ((GtkLabel *) label, qa->email_entry);
506 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
507
508 gtk_table_attach (
509 table, label,
510 0, 1, 1, 2,
511 GTK_FILL, 0, xpad, ypad);
512 gtk_table_attach (
513 table, qa->email_entry,
514 1, 2, 1, 2,
515 GTK_EXPAND | GTK_FILL, 0, xpad, ypad);
516
517 label = gtk_label_new_with_mnemonic (_("_Select Address Book"));
518 gtk_label_set_mnemonic_widget ((GtkLabel *) label, qa->combo_box);
519 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
520
521 gtk_table_attach (
522 table, label,
523 0, 1, 2, 3,
524 GTK_FILL, 0, xpad, ypad);
525 gtk_table_attach (
526 table, qa->combo_box,
527 1, 2, 2, 3,
528 GTK_EXPAND | GTK_FILL, 0, xpad, ypad);
529
530 gtk_container_set_border_width (GTK_CONTAINER (table), 12);
531 container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
532 gtk_box_pack_start (
533 GTK_BOX (container), GTK_WIDGET (table), FALSE, FALSE, 0);
534 gtk_widget_show_all (GTK_WIDGET (table));
535
536 return dialog;
537 }
538
539 void
540 e_contact_quick_add (ESourceRegistry *registry,
541 const gchar *in_name,
542 const gchar *email,
543 EContactQuickAddCallback cb,
544 gpointer closure)
545 {
546 QuickAdd *qa;
547 GtkWidget *dialog;
548 gchar *name = NULL;
549 gint len;
550
551 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
552
553 /* We need to have *something* to work with. */
554 if (in_name == NULL && email == NULL) {
555 if (cb)
556 cb (NULL, closure);
557 return;
558 }
559
560 if (in_name) {
561 name = g_strdup (in_name);
562
563 /* Remove extra whitespace and the quotes some mailers put around names. */
564 g_strstrip (name);
565 len = strlen (name);
566 if ((name[0] == '\'' && name[len - 1] == '\'') ||
567 (name[0] == '"' && name[len - 1] == '"')) {
568 name[0] = ' ';
569 name[len - 1] = ' ';
570 }
571 g_strstrip (name);
572 }
573
574 qa = quick_add_new (registry);
575 qa->cb = cb;
576 qa->closure = closure;
577 if (name)
578 quick_add_set_name (qa, name);
579 if (email)
580 quick_add_set_email (qa, email);
581
582 dialog = build_quick_add_dialog (qa);
583 gtk_widget_show_all (dialog);
584
585 g_free (name);
586 }
587
588 void
589 e_contact_quick_add_free_form (ESourceRegistry *registry,
590 const gchar *text,
591 EContactQuickAddCallback cb,
592 gpointer closure)
593 {
594 gchar *name = NULL, *email = NULL;
595 const gchar *last_at, *s;
596 gboolean in_quote;
597
598 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
599
600 if (text == NULL) {
601 e_contact_quick_add (registry, NULL, NULL, cb, closure);
602 return;
603 }
604
605 /* Look for things that look like e-mail addresses embedded in text */
606 in_quote = FALSE;
607 last_at = NULL;
608 for (s = text; *s; ++s) {
609 if (*s == '@' && !in_quote)
610 last_at = s;
611 else if (*s == '"')
612 in_quote = !in_quote;
613 }
614
615 if (last_at == NULL) {
616 /* No at sign, so we treat it all as the name */
617 name = g_strdup (text);
618 } else {
619 gboolean bad_char = FALSE;
620
621 /* walk backwards to whitespace or a < or a quote... */
622 while (last_at >= text && !bad_char
623 && !(isspace ((gint) *last_at) ||
624 *last_at == '<' ||
625 *last_at == '"')) {
626 /* Check for some stuff that can't appear in a legal e-mail address. */
627 if (*last_at == '['
628 || *last_at == ']'
629 || *last_at == '('
630 || *last_at == ')')
631 bad_char = TRUE;
632 --last_at;
633 }
634 if (last_at < text)
635 last_at = text;
636
637 /* ...and then split the text there */
638 if (!bad_char) {
639 if (text < last_at)
640 name = g_strndup (text, last_at - text);
641 email = g_strdup (last_at);
642 }
643 }
644
645 /* If all else has failed, make it the name. */
646 if (name == NULL && email == NULL)
647 name = g_strdup (text);
648
649 /* Clean up email, remove bracketing <>s */
650 if (email && *email) {
651 gboolean changed = FALSE;
652 g_strstrip (email);
653 if (*email == '<') {
654 *email = ' ';
655 changed = TRUE;
656 }
657 if (email[strlen (email) - 1] == '>') {
658 email[strlen (email) - 1] = ' ';
659 changed = TRUE;
660 }
661 if (changed)
662 g_strstrip (email);
663 }
664
665 e_contact_quick_add (registry, name, email, cb, closure);
666
667 g_free (name);
668 g_free (email);
669 }
670
671 void
672 e_contact_quick_add_email (ESourceRegistry *registry,
673 const gchar *email,
674 EContactQuickAddCallback cb,
675 gpointer closure)
676 {
677 gchar *name = NULL;
678 gchar *addr = NULL;
679 gchar *lt, *gt;
680
681 /* Handle something of the form "Foo <foo@bar.com>". This is more
682 * more forgiving than the free-form parser, allowing for unquoted
683 * whitespace since we know the whole string is an email address. */
684
685 lt = (email != NULL) ? strchr (email, '<') : NULL;
686 gt = (lt != NULL) ? strchr (email, '>') : NULL;
687
688 if (lt != NULL && gt != NULL && (gt - lt) > 0) {
689 name = g_strndup (email, lt - email);
690 addr = g_strndup (lt + 1, gt - lt - 1);
691 } else {
692 addr = g_strdup (email);
693 }
694
695 e_contact_quick_add (registry, name, addr, cb, closure);
696
697 g_free (name);
698 g_free (addr);
699 }
700
701 void
702 e_contact_quick_add_vcard (ESourceRegistry *registry,
703 const gchar *vcard,
704 EContactQuickAddCallback cb,
705 gpointer closure)
706 {
707 QuickAdd *qa;
708 GtkWidget *dialog;
709 EContact *contact;
710
711 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
712
713 /* We need to have *something* to work with. */
714 if (vcard == NULL) {
715 if (cb)
716 cb (NULL, closure);
717 return;
718 }
719
720 qa = quick_add_new (registry);
721 qa->cb = cb;
722 qa->closure = closure;
723 quick_add_set_vcard (qa, vcard);
724
725 contact = e_contact_new_from_vcard (qa->vcard);
726
727 if (contact) {
728 GList *emails;
729 gchar *name;
730 EContactName *contact_name;
731
732 g_object_unref (qa->contact);
733 qa->contact = contact;
734
735 contact_name = e_contact_get (qa->contact, E_CONTACT_NAME);
736 name = e_contact_name_to_string (contact_name);
737 quick_add_set_name (qa, name);
738 g_free (name);
739 e_contact_name_free (contact_name);
740
741 emails = e_contact_get (qa->contact, E_CONTACT_EMAIL);
742 if (emails) {
743 quick_add_set_email (qa, emails->data);
744
745 g_list_foreach (emails, (GFunc) g_free, NULL);
746 g_list_free (emails);
747 }
748 } else {
749 if (cb)
750 cb (NULL, closure);
751
752 quick_add_unref (qa);
753 g_warning ("Contact's vCard parsing failed!");
754 return;
755 }
756
757 dialog = build_quick_add_dialog (qa);
758 gtk_widget_show_all (dialog);
759 }