1256dd65b79cce86a8b1d2d81593cf99e2a00d1d
[modest] / src / hildon2 / modest-address-book.c
1 /* Copyright (c) 2007, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 /* modest-address-book.c */
31
32 #include <config.h>
33 #include <glib/gi18n.h>
34 #include <modest-address-book.h>
35 #include <modest-text-utils.h>
36 #include <libebook/e-book.h>
37 #include <libebook/e-book-view.h>
38 #include <libebook/e-vcard.h>
39 #include "modest-hildon-includes.h"
40 #include <libosso-abook/osso-abook.h>
41 #include "modest-platform.h"
42 #include "modest-runtime.h"
43 #include "widgets/modest-window-mgr.h"
44 #include "widgets/modest-ui-constants.h"
45 #include <string.h>
46 #include <gtk/gtksizegroup.h>
47 #include <gtk/gtkbox.h>
48 #include <gtk/gtklabel.h>
49 #include <gtk/gtkcellrenderertext.h>
50 #include <gtk/gtktreeselection.h>
51 #include <gtk/gtkentry.h>
52 #include <modest-maemo-utils.h>
53
54 static OssoABookContactModel *contact_model =  NULL;
55 static EBook *book = NULL;
56 static EBookView * book_view = NULL;
57
58 static GSList *get_recipients_for_given_contact (EContact * contact, gboolean *canceled);
59 static gchar *get_email_addr_from_user(const gchar * given_name, gboolean *canceled);
60 static gchar *ui_get_formatted_email_id(gchar * current_given_name,
61                                         gchar * current_sur_name, gchar * current_email_id);
62 static gchar *run_add_email_addr_to_contact_dlg(const gchar * contact_name, gboolean *canceled);
63 static GSList *select_email_addrs_for_contact(GList * email_addr_list);
64 static gboolean resolve_address (const gchar *address, GSList **resolved_addresses, GSList **contact_id, gboolean *canceled);
65 static gchar *unquote_string (const gchar *str);
66
67 static gboolean
68 open_addressbook ()
69 {
70         OssoABookRoster *roster;
71         GError *error = NULL;
72         time_t init,end;
73
74         if (book && book_view)
75                 return TRUE;
76
77         roster = osso_abook_aggregator_get_default (&error);
78         if (error)
79                 goto error;
80
81         /* Wait until it's ready */
82         init = time (NULL);
83         osso_abook_waitable_run ((OssoABookWaitable *) roster,
84                                  g_main_context_default (),
85                                  &error);
86         end = time (NULL);
87         g_debug ("Opening addressbook lasted %ld seconds", (gint) end-init);
88
89         if (error)
90                 goto error;
91
92         if (!osso_abook_waitable_is_ready ((OssoABookWaitable *) roster,
93                                            &error))
94                 goto error;
95
96         book = osso_abook_roster_get_book (roster);
97         book_view = osso_abook_roster_get_book_view (roster);
98
99         return TRUE;
100  error:
101         g_warning ("error opening addressbook %s", error->message);
102         g_error_free (error);
103         return FALSE;
104 }
105
106 void
107 modest_address_book_add_address (const gchar *address,
108                                  GtkWindow *parent)
109 {
110         GtkWidget *dialog = NULL;
111         gchar *email_address;
112         EVCardAttribute *attribute;
113
114         if (!open_addressbook ()) {
115                 return;
116         }
117
118         email_address = modest_text_utils_get_email_address (address);
119
120         attribute = e_vcard_attribute_new (NULL, EVC_EMAIL);
121         e_vcard_attribute_add_value (attribute, email_address);
122         dialog = osso_abook_temporary_contact_dialog_new (parent, book, attribute, NULL);
123
124         gtk_dialog_run (GTK_DIALOG (dialog));
125
126         gtk_widget_destroy (dialog);
127
128         e_vcard_attribute_free (attribute);
129         g_free (email_address);
130
131 }
132
133 void
134 modest_address_book_select_addresses (ModestRecptEditor *recpt_editor,
135                                       GtkWindow *parent_window)
136 {
137         GtkWidget *contact_chooser = NULL;
138         GList *contacts_list = NULL;
139         GSList *email_addrs_per_contact = NULL;
140         gchar *econtact_id;
141         gboolean focus_recpt_editor = FALSE;
142
143         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
144
145         /* TODO: figure out how to make the contact chooser modal */
146         contact_chooser = osso_abook_contact_chooser_new_with_capabilities (parent_window,
147                                                                             _AB("addr_ti_dia_select_contacts"),
148                                                                             OSSO_ABOOK_CAPS_EMAIL, 
149                                                                             OSSO_ABOOK_CONTACT_ORDER_NAME);
150         /* Enable multiselection */
151         osso_abook_contact_chooser_set_maximum_selection (OSSO_ABOOK_CONTACT_CHOOSER (contact_chooser),
152                                                           G_MAXUINT);
153
154         if (gtk_dialog_run (GTK_DIALOG (contact_chooser)) == GTK_RESPONSE_OK)
155                 contacts_list = osso_abook_contact_chooser_get_selection (OSSO_ABOOK_CONTACT_CHOOSER (contact_chooser));
156         gtk_widget_destroy (contact_chooser);
157
158         if (contacts_list) {
159                 GList *node;
160
161                 for (node = contacts_list; node != NULL; node = g_list_next (node)) {
162                         EContact *contact = (EContact *) node->data;
163                         gboolean canceled;
164
165                         email_addrs_per_contact = get_recipients_for_given_contact (contact, &canceled);
166                         if (email_addrs_per_contact) {
167                                 econtact_id = (gchar *) e_contact_get_const (contact, E_CONTACT_UID);
168                                 modest_recpt_editor_add_resolved_recipient (MODEST_RECPT_EDITOR (recpt_editor), 
169                                                                             email_addrs_per_contact, econtact_id);
170                                 g_slist_foreach (email_addrs_per_contact, (GFunc) g_free, NULL);
171                                 g_slist_free (email_addrs_per_contact);
172                                 email_addrs_per_contact = NULL;
173                                 focus_recpt_editor = TRUE;
174                         }
175                 }
176                 g_list_free (contacts_list);
177         }
178
179         if (focus_recpt_editor)
180                 modest_recpt_editor_grab_focus (MODEST_RECPT_EDITOR (recpt_editor));
181
182 }
183
184 /**
185  * This function returns the resolved recipients for a given EContact.
186  * If no e-mail address is defined, it launches 'Add e-mail address to contact'
187  * dialog to obtain one. If multiple e-mail addresses are found, it launches
188  * 'Select e-mail address' dialog to allow user to select one or more e-mail
189  * addresses for that contact.
190  *
191  * @param  Contact of type #EContact
192  * @return List of resolved recipient strings, to be freed by calling function.
193  */
194 static GSList *
195 get_recipients_for_given_contact (EContact * contact,
196                                   gboolean *canceled)
197 {
198         gchar *givenname = NULL;
199         gchar *familyname = NULL;
200         gchar *nickname = NULL;
201         gchar *emailid = NULL;
202         const gchar *display_name = NULL;
203         GList *list = NULL;
204         gchar *formatted_string = NULL;
205         gboolean email_not_present = FALSE;
206         GSList *formattedlist = NULL, *selected_email_addr_list = NULL, *node = NULL;
207
208         if (!contact) {
209                 return NULL;
210         }
211
212         givenname = (gchar *) e_contact_get_const(contact, E_CONTACT_GIVEN_NAME);
213         familyname = (gchar *) e_contact_get_const(contact, E_CONTACT_FAMILY_NAME);
214         nickname = (gchar *) e_contact_get_const(contact, E_CONTACT_NICKNAME);
215         if (!nickname)
216                 nickname = "";
217         list = (GList *) e_contact_get(contact, E_CONTACT_EMAIL);
218
219         if (!list) {
220                 email_not_present = TRUE;
221         }
222
223         if (list && g_list_length(list) == 1) {
224                 if (list->data == NULL || g_utf8_strlen(list->data, -1) == 0) {
225                         email_not_present = TRUE;
226                 } else {
227                         emailid = g_strstrip(g_strdup(list->data));
228                         if (g_utf8_strlen(emailid, -1) == 0) {
229                                 g_free(emailid);
230                                 email_not_present = TRUE;
231                         }
232                 }
233         }
234
235         /*Launch the 'Add e-mail addr to contact' dialog if required */
236         if (email_not_present) {
237                 OssoABookContact *abook_contact;
238
239                 abook_contact = osso_abook_contact_new_from_template (contact);
240                 display_name = osso_abook_contact_get_display_name(abook_contact);
241
242                 emailid = get_email_addr_from_user(display_name, canceled);
243                 if (emailid) {
244                         e_contact_set(E_CONTACT (abook_contact), E_CONTACT_EMAIL_1, emailid);
245                         osso_abook_contact_commit (abook_contact, FALSE, NULL, NULL);
246                 }
247                 g_object_unref (abook_contact);
248         }
249
250         if (emailid) {
251                 if (givenname || familyname)
252                         formatted_string =
253                             ui_get_formatted_email_id(givenname, familyname, emailid);
254                 else
255                         formatted_string = g_strdup(emailid);
256                 formattedlist = g_slist_append(formattedlist, formatted_string);
257                 g_free(emailid);
258         }
259
260         /*Launch the 'Select e-mail address' dialog if required */
261         if (g_list_length(list) > 1) {
262                 selected_email_addr_list = select_email_addrs_for_contact(list);
263                 for (node = selected_email_addr_list; node != NULL; node = node->next) {
264                         if (givenname || familyname)
265                                 formatted_string =
266                                     ui_get_formatted_email_id(givenname, familyname, node->data);
267                         else
268                                 formatted_string = g_strdup(node->data);
269                         formattedlist = g_slist_append(formattedlist, formatted_string);
270                 }
271                 if (selected_email_addr_list) {
272                         g_slist_foreach(selected_email_addr_list, (GFunc) g_free, NULL);
273                         g_slist_free(selected_email_addr_list);
274                 }
275         }
276
277         if (list) {
278                 g_list_foreach(list, (GFunc) g_free, NULL);
279                 g_list_free(list);
280         }
281
282         return formattedlist;
283 }
284
285 /**
286  * This is a helper function used to launch 'Add e-mail address to contact' dialog
287  * after showing appropriate notification, when there is no e-mail address defined
288  * for a selected contact.
289  *
290  * @param  given_name  Given name of the contact
291  * @param  family_name  Family name of the contact
292  * @return E-mail address string entered by user, to be freed by calling function.
293  */
294 static gchar *
295 get_email_addr_from_user(const gchar * given_name, gboolean *canceled)
296 {
297         gchar *notification = NULL;
298         gchar *email_addr = NULL;
299         GtkWidget *note;
300         gboolean note_response;
301
302
303         notification = g_strdup_printf(_("mcen_nc_email_address_not_defined"), given_name);
304
305         note = hildon_note_new_confirmation (NULL, notification);
306         note_response = gtk_dialog_run (GTK_DIALOG(note));
307         gtk_widget_destroy (note);
308         g_free(notification);
309
310         if (note_response == GTK_RESPONSE_OK) {
311                 email_addr = run_add_email_addr_to_contact_dlg (given_name, canceled);
312         }
313
314         return email_addr;
315 }
316
317 /**
318 This function is used to get the formated email id with given name and sur name
319 in the format "GIVENNAME SURNAME <EMAIL ADDRESS>".
320 @param current_given_name    to hold the given name
321 @param current_sur_name      to hold the sur name
322 @param current_email_id      to hold the email id. 
323 @return gchar* string to be freed by calling function
324 */
325 static gchar *
326 ui_get_formatted_email_id(gchar * current_given_name,
327                           gchar * current_sur_name, gchar * current_email_id)
328 {
329         GString *email_id_str = NULL;
330
331         email_id_str = g_string_new(NULL);
332
333         if ((current_given_name != NULL) && ((strlen(current_given_name) != 0))
334             && (current_sur_name != NULL) && ((strlen(current_sur_name) != 0))) {
335                 g_string_append_printf(email_id_str, "%s %s", current_given_name, current_sur_name);
336         } else if ((current_given_name != NULL) && (strlen(current_given_name) != 0)) {
337                 g_string_append_printf(email_id_str, "%s", current_given_name);
338         } else if ((current_sur_name != NULL) && (strlen(current_sur_name) != 0)) {
339                 g_string_append_printf(email_id_str, "%s", current_sur_name);
340         }
341         if (g_utf8_strchr (email_id_str->str, -1, ' ')) {
342                 g_string_prepend_c (email_id_str, '\"');
343                 g_string_append_c (email_id_str, '\"');
344         }
345         g_string_append_printf (email_id_str, " %c%s%c", '<', current_email_id, '>');
346         return g_string_free (email_id_str, FALSE);
347 }
348
349 /**
350  * This is a helper function used to create & run 'Add e-mail address to contact' dialog.
351  * It allows user to enter an e-mail address, and shows appropriate infonote if the
352  * entered string is not a valid e-mail address.
353  *
354  * It must return TRUE in canceled if the dialog was canceled by the user
355  *
356  * @param  contact_name  Full name of the contact
357  * @return E-mail address string entered by user, to be freed by calling function.
358  */
359 static gchar *
360 run_add_email_addr_to_contact_dlg(const gchar * contact_name,
361                                   gboolean *canceled)
362 {
363         GtkWidget *add_email_addr_to_contact_dlg = NULL;
364         GtkSizeGroup *size_group = NULL;
365         GtkWidget *cptn_cntrl = NULL;
366         GtkWidget *name_label = NULL;
367         GtkWidget *email_entry = NULL;
368         gint result = -1;
369         gchar *new_email_addr = NULL;
370         gboolean run_dialog = TRUE;
371
372         g_return_val_if_fail (canceled, NULL);
373
374         *canceled = FALSE;
375
376         add_email_addr_to_contact_dlg =
377             gtk_dialog_new_with_buttons(_("mcen_ti_add_email_title"), NULL,
378                                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
379                                         _HL("wdgt_bd_save"), GTK_RESPONSE_ACCEPT, NULL);
380         gtk_dialog_set_has_separator(GTK_DIALOG(add_email_addr_to_contact_dlg), FALSE);
381 #ifdef MODEST_TOOLKIT_HILDON2
382         gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (add_email_addr_to_contact_dlg)->vbox), 
383                                         HILDON_MARGIN_DOUBLE);
384 #endif
385         /*Set app_name & state_save related tags to the window */
386
387         size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
388         name_label = gtk_label_new(contact_name);
389         gtk_misc_set_alignment(GTK_MISC(name_label), 0.0, 0.5);
390         cptn_cntrl =
391                 modest_maemo_utils_create_captioned (size_group, NULL,
392                                                      _("mcen_ia_add_email_name"), FALSE,
393                                                      name_label);
394         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(add_email_addr_to_contact_dlg)->vbox), cptn_cntrl,
395                            FALSE, FALSE, 0);
396
397         email_entry = hildon_entry_new (HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH);
398         cptn_cntrl = modest_maemo_utils_create_captioned (size_group, NULL, 
399                                                           _("mcen_fi_add_email_name"), FALSE,
400                                                           email_entry);
401         hildon_gtk_entry_set_input_mode(GTK_ENTRY(email_entry), HILDON_GTK_INPUT_MODE_FULL);
402         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(add_email_addr_to_contact_dlg)->vbox), cptn_cntrl,
403                            TRUE, TRUE, 0);
404
405         gtk_widget_show_all(add_email_addr_to_contact_dlg);
406
407         while (run_dialog) {
408                 run_dialog = FALSE;
409                 gtk_widget_grab_focus(email_entry);
410                 result = gtk_dialog_run(GTK_DIALOG(add_email_addr_to_contact_dlg));
411
412                 if (result == GTK_RESPONSE_ACCEPT) {
413                         const gchar *invalid_char_offset = NULL;
414                         new_email_addr = g_strdup(hildon_entry_get_text(HILDON_ENTRY(email_entry)));
415                         new_email_addr = g_strstrip(new_email_addr);
416                         if (!modest_text_utils_validate_email_address (new_email_addr, &invalid_char_offset)) {
417                                 gtk_widget_grab_focus(email_entry);
418                                 if ((invalid_char_offset != NULL)&&(*invalid_char_offset != '\0')) {
419                                         gchar *char_in_string = g_strdup_printf ("%c", *invalid_char_offset);
420                                         gchar *message = g_strdup_printf(
421                                                 _CS("ckdg_ib_illegal_characters_entered"), 
422                                                 char_in_string);
423                                         g_free (char_in_string);
424                                         hildon_banner_show_information (
425                                                 add_email_addr_to_contact_dlg, NULL, message );
426                                         g_free (message);
427                                 } else {
428                                         hildon_banner_show_information (add_email_addr_to_contact_dlg, NULL, _("mcen_ib_invalid_email"));
429                                         run_dialog = TRUE;
430                                 }
431                                 gtk_editable_select_region((GtkEditable *) email_entry, 0, -1);
432                                 g_free(new_email_addr);
433                                 new_email_addr = NULL;
434                         }
435                 } else {
436                         *canceled = TRUE;
437                 }
438         }
439
440         gtk_widget_destroy(add_email_addr_to_contact_dlg);
441
442         return new_email_addr;
443 }
444
445 /**
446  * This is helper function to create & run 'Select e-mail address' dialog, used when
447  * multiple e-mail addresses are found for a selected contact. It allows user to select
448  * one or more e-mail addresses for that contact.
449  *
450  * @param  email_addr_list  List of e-mail addresses for that contact
451  * @return List of user selected e-mail addresses, to be freed by calling function.
452  */
453 static GSList *
454 select_email_addrs_for_contact(GList * email_addr_list)
455 {
456         GtkWidget *select_email_addr_dlg = NULL;
457         GSList *selected_email_addr_list = NULL;
458         GList *node;
459         GtkWidget *selector;
460         gint result = -1;
461
462         if (!email_addr_list)
463                 return NULL;
464
465         select_email_addr_dlg = hildon_picker_dialog_new (NULL);
466         gtk_window_set_title (GTK_WINDOW (select_email_addr_dlg), _("mcen_ti_select_email_title"));
467
468         selector = hildon_touch_selector_new_text ();
469         for (node = email_addr_list; node != NULL && node->data != NULL; node = node->next) {
470                 gchar *email_addr;
471                 email_addr = g_strstrip(g_strdup(node->data));
472                 hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), email_addr);
473                 g_free(email_addr);
474         }
475
476         hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (select_email_addr_dlg),
477                                            HILDON_TOUCH_SELECTOR (selector));
478         gtk_window_set_default_size (GTK_WINDOW (select_email_addr_dlg), MODEST_DIALOG_WINDOW_MAX_HEIGHT, -1);
479
480         gtk_widget_show_all(select_email_addr_dlg);
481         result = gtk_dialog_run(GTK_DIALOG(select_email_addr_dlg));
482
483         if (result == GTK_RESPONSE_OK) {
484                 gchar *current_text;
485
486                 current_text = hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector));
487                 selected_email_addr_list = g_slist_append (selected_email_addr_list, current_text);
488         }
489
490         gtk_widget_destroy(select_email_addr_dlg);
491         return selected_email_addr_list;
492 }
493
494 /* Assumes that the second argument (the user provided one) is a pure
495    email address without name */
496 static gint
497 compare_addresses (const gchar *address1,
498                    const gchar *mail2)
499 {
500         gint retval;
501         gchar *mail1;
502
503         mail1 = modest_text_utils_get_email_address (address1);
504         retval = g_strcmp0 (mail1, mail2);
505         g_free (mail1);
506
507         return retval;
508 }
509
510 static EContact *
511 get_contact_for_address (GList *contacts,
512                          const gchar *address)
513 {
514         EContact *retval = NULL, *contact;
515         GList *iter;
516         gchar *email;
517
518         email = modest_text_utils_get_email_address (address);
519         iter = contacts;
520         while (iter && !retval) {
521                 GList *emails = NULL;
522
523                 contact = E_CONTACT (iter->data);
524                 emails = e_contact_get (contact, E_CONTACT_EMAIL);
525                 if (emails) {
526                         /* Look for the email address */
527                         if (g_list_find_custom (emails, email, (GCompareFunc) compare_addresses))
528                                 retval = contact;
529
530                         /* Free the list */
531                         g_list_foreach (emails, (GFunc) g_free, NULL);
532                         g_list_free (emails);
533                 }
534                 iter = g_list_next (iter);
535         }
536         g_free (email);
537
538         return retval;
539 }
540
541 static void
542 async_get_contacts_cb (EBook *book,
543                        EBookStatus status,
544                        GList *contacts,
545                        gpointer closure)
546 {
547         GSList *addresses, *iter;
548         GList *to_commit_contacts, *to_add_contacts;
549         EContact *self_contact;
550
551         addresses = (GSList *) closure;
552
553         /* Check errors */
554         if (status != E_BOOK_ERROR_OK)
555                 goto frees;
556
557         self_contact = (EContact *) osso_abook_self_contact_get_default ();
558         if (self_contact) {
559                 contacts = g_list_prepend (contacts, self_contact);
560         }
561
562         iter = addresses;
563         to_commit_contacts = NULL;
564         to_add_contacts = NULL;
565         while (iter) {
566                 EContact *contact;
567                 const gchar *address;
568
569                 /* Look for a contact with such address. We perform
570                    this kind of search because we assume that users
571                    don't usually send emails to tons of addresses */
572                 address = (const gchar *) iter->data;
573                 contact = get_contact_for_address (contacts, address);
574
575                 /* Add new or commit existing contact */
576                 if (contact) {
577                         to_commit_contacts = g_list_prepend (to_commit_contacts, contact);
578                         g_debug ("----Preparing to commit contact %s", address);
579                 } else {
580                         gchar *email_address, *display_address;
581
582                         /* Create new contact and add it to the list */
583                         contact = e_contact_new ();
584                         email_address = modest_text_utils_get_email_address (address);
585                         e_contact_set (contact, E_CONTACT_EMAIL_1, email_address);
586                         g_free (email_address);
587
588                         display_address = g_strdup (address);
589                         if (display_address) {
590                                 modest_text_utils_get_display_address (display_address);
591                                 if ((display_address[0] != '\0') && (strlen (display_address) != strlen (address)))
592                                         e_contact_set (contact, E_CONTACT_FULL_NAME, (const gpointer)display_address);
593                                 g_free (display_address);
594                         }
595
596                         to_add_contacts = g_list_prepend (to_add_contacts, contact);
597                         g_debug ("----Preparing to add contact %s", address);
598                 }
599
600                 iter = g_slist_next (iter);
601         }
602
603         /* Asynchronously add contacts */
604         if (to_add_contacts)
605                 e_book_async_add_contacts (book, to_add_contacts, NULL, NULL);
606
607         /* Asynchronously commit contacts */
608         if (to_commit_contacts)
609                 e_book_async_commit_contacts (book, to_commit_contacts, NULL, NULL);
610
611         /* Free lists */
612         g_list_free (to_add_contacts);
613         g_list_free (to_commit_contacts);
614
615  frees:
616         if (addresses) {
617                 g_slist_foreach (addresses, (GFunc) g_free, NULL);
618                 g_slist_free (addresses);
619         }
620         if (contacts)
621                 g_list_free (contacts);
622 }
623
624 typedef struct _CheckNamesInfo {
625         GtkWidget *banner;
626         guint show_banner_timeout;
627         guint hide_banner_timeout;
628         gboolean hide;
629         gboolean free_info;
630 } CheckNamesInfo;
631
632 static void
633 hide_check_names_banner (CheckNamesInfo *info)
634 {
635         if (info->show_banner_timeout > 0) {
636                 g_source_remove (info->show_banner_timeout);
637                 info->show_banner_timeout = 0;
638         }
639         if (info->hide_banner_timeout > 0) {
640                 info->hide = TRUE;
641                 return;
642         }
643
644         if (info->banner) {
645                 gtk_widget_destroy (info->banner);
646                 info->banner = NULL;
647                 info->hide = FALSE;
648         }
649
650         if (info->free_info) {
651                 g_slice_free (CheckNamesInfo, info);
652         }
653 }
654
655 static gboolean hide_banner_timeout_handler (CheckNamesInfo *info)
656 {
657         info->hide_banner_timeout = 0;
658         if (info->hide) {
659                 gtk_widget_destroy (info->banner);
660                 info->banner = NULL;
661         }
662         if (info->free_info) {
663                 g_slice_free (CheckNamesInfo, info);
664         }
665         return FALSE;
666 }
667
668 static gboolean show_banner_timeout_handler (CheckNamesInfo *info)
669 {
670         info->show_banner_timeout = 0;
671         info->banner = hildon_banner_show_animation (NULL, NULL, _("mail_ib_checking_names"));
672         info->hide_banner_timeout = g_timeout_add (1000, (GSourceFunc) hide_banner_timeout_handler, (gpointer) info);
673         return FALSE;
674 }
675
676 static void show_check_names_banner (CheckNamesInfo *info)
677 {
678         if (info->hide_banner_timeout > 0) {
679                 g_source_remove (info->hide_banner_timeout);
680                 info->hide_banner_timeout = 0;
681         }
682
683         info->hide = FALSE;
684         if (info->show_banner_timeout > 0)
685                 return;
686
687         if (info->banner == NULL) {
688                 info->show_banner_timeout = g_timeout_add (500, (GSourceFunc) show_banner_timeout_handler, (gpointer) info);
689         }
690 }
691
692 static void clean_check_names_banner (CheckNamesInfo *info)
693 {
694         if (info->hide_banner_timeout) {
695                 info->free_info = TRUE;
696         } else {
697                 if (info->show_banner_timeout) {
698                         g_source_remove (info->show_banner_timeout);
699                 }
700                 if (info->banner)
701                         gtk_widget_destroy (info->banner);
702                 g_slice_free (CheckNamesInfo, info);
703         }
704 }
705
706 void free_resolved_addresses_list (gpointer data,
707                                    gpointer ignored)
708 {
709         GSList *list = (GSList *)data;
710         g_slist_foreach (list, (GFunc) g_free, NULL);
711         g_slist_free (list);
712 }
713
714 gboolean
715 modest_address_book_check_names (ModestRecptEditor *recpt_editor,
716                                  GSList **address_list)
717 {
718         const gchar *recipients = NULL;
719         GSList *start_indexes = NULL, *end_indexes = NULL;
720         GSList *current_start, *current_end;
721         gboolean result = TRUE;
722         GtkTextBuffer *buffer;
723         gint offset_delta = 0;
724         gint last_length;
725         GtkTextIter start_iter, end_iter;
726
727         g_return_val_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor), FALSE);
728
729         recipients = modest_recpt_editor_get_recipients (recpt_editor);
730         last_length = g_utf8_strlen (recipients, -1);
731         modest_text_utils_get_addresses_indexes (recipients, &start_indexes, &end_indexes);
732
733         if (start_indexes == NULL) {
734                 if (last_length != 0) {
735                         hildon_banner_show_information (NULL, NULL, _("mcen_nc_no_matching_contacts"));
736                         return FALSE;
737                 } else {
738                         return TRUE;
739                 }
740         }
741
742         current_start = start_indexes;
743         current_end = end_indexes;
744         buffer = modest_recpt_editor_get_buffer (recpt_editor);
745
746         while (current_start != NULL) {
747                 gchar *address;
748                 gchar *start_ptr, *end_ptr;
749                 gint start_pos, end_pos;
750                 const gchar *invalid_char_position = NULL;
751                 gboolean store_address = FALSE;
752
753                 start_pos = (*((gint*) current_start->data)) + offset_delta;
754                 end_pos = (*((gint*) current_end->data)) + offset_delta;
755
756                 start_ptr = g_utf8_offset_to_pointer (recipients, start_pos);
757                 end_ptr = g_utf8_offset_to_pointer (recipients, end_pos);
758
759                 address = g_strstrip (g_strndup (start_ptr, end_ptr - start_ptr));
760                 gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start_pos);
761                 gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end_pos);
762                 gtk_text_buffer_select_range (buffer, &start_iter, &end_iter);
763
764                 if (!modest_text_utils_validate_recipient (address, &invalid_char_position)) {
765                         if ((invalid_char_position != NULL) && (*invalid_char_position != '\0')) {
766                                 gchar *char_in_string = g_strdup_printf("%c", *invalid_char_position);
767                                 gchar *message = 
768                                         g_strdup_printf(_CS("ckdg_ib_illegal_characters_entered"), 
769                                                         char_in_string);
770                                 g_free (char_in_string);
771                                 hildon_banner_show_information (NULL, NULL, message );
772                                 g_free (message);
773                                 result = FALSE;
774                         } else if (strstr (address, "@") == NULL) {
775                                 /* here goes searching in addressbook */
776                                 gboolean canceled;
777                                 GSList *contact_ids = NULL;
778                                 GSList *resolved_addresses = NULL;
779
780                                 result = resolve_address (address, &resolved_addresses, &contact_ids, &canceled);
781
782                                 if (result) {
783                                         gint new_length;
784
785                                         modest_recpt_editor_replace_with_resolved_recipients (recpt_editor,
786                                                                                               &start_iter, &end_iter,
787                                                                                               resolved_addresses,
788                                                                                               contact_ids);
789                                         g_slist_foreach (contact_ids, (GFunc) g_free, NULL);
790                                         g_slist_foreach (resolved_addresses, free_resolved_addresses_list, NULL);
791                                         g_slist_free (contact_ids);
792                                         g_slist_free (resolved_addresses);
793
794                                         /* update offset delta */
795                                         recipients = modest_recpt_editor_get_recipients (recpt_editor);
796                                         new_length = g_utf8_strlen (recipients, -1);
797                                         offset_delta = offset_delta + new_length - last_length;
798                                         last_length = new_length;
799                                 } else {
800                                         if (canceled) {
801                                                 /* We have to remove the recipient if not resolved */
802                                                 modest_recpt_editor_replace_with_resolved_recipient (recpt_editor,
803                                                                                                      &start_iter, 
804                                                                                                      &end_iter,
805                                                                                                      NULL,
806                                                                                                      NULL);
807                                         } else {
808                                                 /* There is no contact with that name so it's not
809                                                    valid. Don't show any error because it'll be done
810                                                    later */
811                                                 result = FALSE;
812                                         }
813                                 }
814                         } else {
815                                 /* this address is not valid, select it and return control to user showing banner */
816                                 hildon_banner_show_information (NULL, NULL, _("mcen_ib_invalid_email"));
817                                 result = FALSE;
818                         }
819                 } else {
820                         GSList *tags, *node;
821                         gboolean has_recipient = FALSE;
822
823                         tags = gtk_text_iter_get_tags (&start_iter);
824                         for (node = tags; node != NULL; node = g_slist_next (node)) {
825                                 GtkTextTag *tag = GTK_TEXT_TAG (node->data);
826                                 if (g_object_get_data (G_OBJECT (tag), "recipient-tag-id") != NULL) {
827                                         has_recipient = TRUE;
828                                         break;
829                                 }
830                         }
831                         g_slist_free (tags);
832                         if (!has_recipient) {
833                                 GSList * addr_list = NULL;
834
835                                 addr_list = g_slist_prepend (addr_list, address);
836                                 modest_recpt_editor_replace_with_resolved_recipient (recpt_editor,
837                                                                                      &start_iter, &end_iter,
838                                                                                      addr_list,
839                                                                                      "");
840                                 g_slist_free (addr_list);
841                                 store_address = TRUE;
842                         }
843                 }
844
845                 /* so, it seems a valid address */
846                 /* note: adding it the to the addressbook if it did not exist yet,
847                  * and adding it to the recent_list */
848                 if (result && address_list && store_address)
849                         *address_list = g_slist_prepend (*address_list, address);
850                 else
851                         g_free (address);
852
853                 if (result == FALSE)
854                         break;
855
856                 current_start = g_slist_next (current_start);
857                 current_end = g_slist_next (current_end);
858         }
859
860         /* Remove dup's */
861         if (address_list && *address_list)
862                 *address_list = modest_text_utils_remove_duplicate_addresses_list (*address_list);
863
864         if (current_start == NULL) {
865                 gtk_text_buffer_get_end_iter (buffer, &end_iter);
866                 gtk_text_buffer_place_cursor (buffer, &end_iter);
867         }
868
869         g_slist_foreach (start_indexes, (GFunc) g_free, NULL);
870         g_slist_foreach (end_indexes, (GFunc) g_free, NULL);
871         g_slist_free (start_indexes);
872         g_slist_free (end_indexes);
873
874         return result;
875
876 }
877
878 typedef struct _GetContactsInfo {
879         GMainLoop *mainloop;
880         GList *result;
881 } GetContactsInfo;
882
883 static void 
884 get_contacts_for_name_cb (EBook *book, 
885                           EBookStatus status, 
886                           GList *list, 
887                           gpointer userdata)
888 {
889         GetContactsInfo *info = (GetContactsInfo *) userdata;
890
891         if (status == E_BOOK_ERROR_OK)
892                 info->result = list;
893
894         g_main_loop_quit (info->mainloop);
895 }
896
897 static GList *
898 get_contacts_for_name (const gchar *name)
899 {
900         EBookQuery *full_name_book_query = NULL;
901         GList *result;
902         gchar *unquoted;
903         GetContactsInfo *info;
904
905         if (name == NULL)
906                 return NULL;
907
908         unquoted = unquote_string (name);
909         full_name_book_query = e_book_query_field_test (E_CONTACT_FULL_NAME, E_BOOK_QUERY_CONTAINS, unquoted);
910         g_free (unquoted);
911
912         /* TODO: Make it launch a mainloop */
913         info = g_slice_new (GetContactsInfo);
914         info->mainloop = g_main_loop_new (NULL, FALSE);
915         info->result = NULL;
916         if (e_book_async_get_contacts (book, full_name_book_query, get_contacts_for_name_cb, info) == 0) {
917                 GDK_THREADS_LEAVE ();
918                 g_main_loop_run (info->mainloop);
919                 GDK_THREADS_ENTER ();
920         } 
921         result = info->result;
922         e_book_query_unref (full_name_book_query);
923         g_main_loop_unref (info->mainloop);
924         g_slice_free (GetContactsInfo, info);
925
926         return result;
927 }
928
929 #ifdef HAVE_OSSO_ABOOK_CONTACT_CHOOSER_SET_VISIBLE_FUNC
930 static gboolean
931 filter_by_name (OssoABookContactChooser *chooser,
932                 OssoABookContact        *contact,
933                 gpointer                 user_data)
934 {
935         const gchar *contact_name;
936         const gchar *name = (const gchar *) user_data;
937
938         contact_name = osso_abook_contact_get_name (contact);
939         /* contact_name includes both name and surname */
940         if (contact_name && name && strstr (contact_name, name))
941                 return TRUE;
942         else
943                 return FALSE;
944 }
945 #endif
946
947 static GList *
948 select_contacts_for_name_dialog (const gchar *name)
949 {
950 #ifdef HAVE_OSSO_ABOOK_CONTACT_CHOOSER_SET_VISIBLE_FUNC
951         GtkWidget *contact_view;
952         OssoABookContactChooser *contact_dialog;
953 #else
954         EBookQuery *full_name_book_query = NULL;
955         EBookView *book_view = NULL;
956 #endif
957
958         GList *result = NULL;
959         gchar *unquoted;
960
961         unquoted = unquote_string (name);
962
963 #ifdef HAVE_OSSO_ABOOK_CONTACT_CHOOSER_SET_VISIBLE_FUNC
964         contact_dialog = (OssoABookContactChooser *)
965                 osso_abook_contact_chooser_new_with_capabilities (NULL,
966                                                                   _AB("addr_ti_dia_select_contacts"),
967                                                                   OSSO_ABOOK_CAPS_EMAIL,
968                                                                   OSSO_ABOOK_CONTACT_ORDER_NAME);
969
970         /* Enable multiselection */
971         osso_abook_contact_chooser_set_maximum_selection (contact_dialog, G_MAXUINT);
972
973         /* Set up the filtering */
974         contact_view = osso_abook_contact_chooser_get_contact_view (contact_dialog);
975         osso_abook_contact_chooser_set_model (contact_dialog, contact_model);
976         osso_abook_contact_chooser_set_visible_func (contact_dialog, filter_by_name, unquoted, NULL);
977
978         if (gtk_dialog_run (GTK_DIALOG (contact_dialog)) == GTK_RESPONSE_OK)
979                 result = osso_abook_contact_chooser_get_selection (contact_dialog);
980
981         gtk_widget_destroy ((GtkWidget *) contact_dialog);
982 #else
983         full_name_book_query = e_book_query_field_test (E_CONTACT_FULL_NAME, E_BOOK_QUERY_CONTAINS, unquoted);
984         e_book_get_book_view (book, full_name_book_query, NULL, -1, &book_view, NULL);
985         e_book_query_unref (full_name_book_query);
986
987         if (book_view) {
988                 GtkWidget *contact_dialog = NULL;
989                 osso_abook_list_store_set_book_view (OSSO_ABOOK_LIST_STORE (contact_model), book_view);
990                 e_book_view_start (book_view);
991
992                 /* TODO: figure out how to make the contact chooser modal */
993                 contact_dialog = osso_abook_contact_chooser_new_with_capabilities (NULL,
994                                                                                    _AB("addr_ti_dia_select_contacts"),
995                                                                                    OSSO_ABOOK_CAPS_EMAIL,
996                                                                                    OSSO_ABOOK_CONTACT_ORDER_NAME);
997                 /* Enable multiselection */
998                 osso_abook_contact_chooser_set_maximum_selection (OSSO_ABOOK_CONTACT_CHOOSER (contact_dialog),
999                                                                   G_MAXUINT);
1000                 osso_abook_contact_chooser_set_model (OSSO_ABOOK_CONTACT_CHOOSER (contact_dialog),
1001                                                       contact_model);
1002
1003                 if (gtk_dialog_run (GTK_DIALOG (contact_dialog)) == GTK_RESPONSE_OK)
1004                         result = osso_abook_contact_chooser_get_selection (OSSO_ABOOK_CONTACT_CHOOSER (contact_dialog));
1005                 e_book_view_stop (book_view);
1006                 g_object_unref (book_view);
1007                 gtk_widget_destroy (contact_dialog);
1008         }
1009 #endif
1010
1011         g_free (unquoted);
1012
1013         return result;
1014 }
1015
1016 static gboolean
1017 resolve_address (const gchar *address, 
1018                  GSList **resolved_addresses, 
1019                  GSList **contact_ids,
1020                  gboolean *canceled)
1021 {
1022         GList *resolved_contacts;
1023         CheckNamesInfo *info;;
1024
1025         g_return_val_if_fail (canceled, FALSE);
1026
1027         *resolved_addresses = NULL;
1028         *contact_ids = NULL;
1029         *canceled = FALSE;
1030         info = g_slice_new0 (CheckNamesInfo);
1031         show_check_names_banner (info);
1032
1033         contact_model = osso_abook_contact_model_get_default ();
1034         if (!open_addressbook ()) {
1035                 hide_check_names_banner (info);
1036                 if (contact_model) {
1037                         g_object_unref (contact_model);
1038                         contact_model = NULL;
1039                 }
1040                 clean_check_names_banner (info);
1041                 return FALSE;
1042         }
1043
1044         resolved_contacts = get_contacts_for_name (address);
1045         hide_check_names_banner (info);
1046
1047         if (resolved_contacts == NULL) {
1048                 /* no matching contacts for the search string */
1049                 modest_platform_run_information_dialog (NULL, _("mcen_nc_no_matching_contacts"), FALSE);
1050                 clean_check_names_banner (info);
1051                 return FALSE;
1052         }
1053
1054         if (g_list_length (resolved_contacts) > 1) {
1055                 /* show a dialog to select the contact from the resolved ones */
1056                 g_list_free (resolved_contacts);
1057
1058                 resolved_contacts = select_contacts_for_name_dialog (address);
1059         }
1060
1061         /* get the resolved contacts (can be no contact) */
1062         if (resolved_contacts) {
1063                 GList *node;
1064                 gboolean found = FALSE;
1065
1066                 for (node = resolved_contacts; node != NULL; node = g_list_next (node)) {
1067                         EContact *contact = (EContact *) node->data;
1068                         GSList *resolved;
1069                         gchar *contact_id;
1070
1071                         resolved = get_recipients_for_given_contact (contact, canceled);
1072                         if (resolved) {
1073                                 contact_id = g_strdup (e_contact_get_const (contact, E_CONTACT_UID));
1074                                 *contact_ids = g_slist_append (*contact_ids, contact_id);
1075                                 found = TRUE;
1076                                 *resolved_addresses = g_slist_append (*resolved_addresses, resolved);
1077                         }
1078                 }
1079
1080                 g_list_free (resolved_contacts);
1081                 clean_check_names_banner (info);
1082
1083                 return found;
1084         } else {
1085                 /* cancelled dialog to select more than one contact or
1086                  * selected no contact */
1087                 clean_check_names_banner (info);
1088                 return FALSE;
1089         }
1090
1091 }
1092
1093 static gchar *
1094 unquote_string (const gchar *str)
1095 {
1096         GString *buffer;
1097         gchar *p;
1098
1099         if (str == NULL)
1100                 return NULL;
1101
1102         buffer = g_string_new_len (NULL, strlen (str));
1103         for (p = (gchar *) str; *p != '\0'; p = g_utf8_next_char (p)) {
1104                 if (*p == '"') {
1105                         p = g_utf8_next_char (p);
1106                         while ((*p != '\0')&&(*p != '"')) {
1107                                 if (*p == '\\') {
1108                                         g_string_append_unichar (buffer, g_utf8_get_char (p));
1109                                         p = g_utf8_next_char (p);
1110
1111                                 }
1112                                 g_string_append_unichar (buffer, g_utf8_get_char (p));
1113                                 p = g_utf8_next_char (p);
1114                         }
1115                 } else {
1116                         g_string_append_unichar (buffer, g_utf8_get_char (p));
1117                 }
1118         }
1119
1120         return g_string_free (buffer, FALSE);
1121
1122 }
1123
1124 gboolean
1125 modest_address_book_has_address (const gchar *address)
1126 {
1127         GList *contacts = NULL;
1128         GError *err = NULL;
1129         gchar *email;
1130         gboolean result;
1131         OssoABookAggregator *roster;
1132
1133         g_return_val_if_fail (address, FALSE);
1134
1135         if (!book) {
1136                 if (!open_addressbook ()) {
1137                         g_return_val_if_reached (FALSE);
1138                 }
1139         }
1140         g_return_val_if_fail (book, FALSE);
1141
1142         email = modest_text_utils_get_email_address (address);
1143
1144         roster = (OssoABookAggregator *) osso_abook_aggregator_get_default (NULL);
1145         contacts = osso_abook_aggregator_find_contacts_for_email_address (roster, email);
1146         if (!contacts) {
1147                 if (err)
1148                         g_error_free (err);
1149                 g_free (email);
1150                 return FALSE;
1151         }
1152
1153         if (contacts) {
1154                 g_list_free (contacts);
1155                 result = TRUE;
1156         }
1157
1158         g_free (email);
1159
1160         return result;
1161 }
1162
1163 const gchar *
1164 modest_address_book_get_my_name ()
1165 {
1166         OssoABookSelfContact *self_contact = osso_abook_self_contact_get_default ();
1167
1168         /* We are not using osso_abook_contact_get_display_name
1169            because that method fallbacks to another fields if the name
1170            is not defined */
1171         if (self_contact)
1172                 return e_contact_get ((EContact *) self_contact, E_CONTACT_FULL_NAME);
1173         else
1174                 return NULL;
1175 }
1176
1177 void
1178 modest_address_book_init (void)
1179 {
1180         open_addressbook ();
1181 }
1182
1183 void
1184 modest_address_book_add_address_list (GSList *address_list)
1185 {
1186         EBookQuery **queries, *composite_query;
1187         gint num_add, i;
1188
1189         g_return_if_fail (address_list);
1190
1191         if (!book)
1192                 if (!open_addressbook ())
1193                         g_return_if_reached ();
1194
1195         /* Create the list of queries */
1196         num_add = g_slist_length (address_list);
1197         queries = g_malloc0 (sizeof (EBookQuery *) * num_add);
1198         for (i = 0; i < num_add; i++) {
1199                 gchar *email;
1200
1201                 email = modest_text_utils_get_email_address (g_slist_nth_data (address_list, i));
1202                 queries[i] = e_book_query_field_test (E_CONTACT_EMAIL, E_BOOK_QUERY_IS, email);
1203                 g_free (email);
1204         }
1205
1206         /* Create the query */
1207         composite_query = e_book_query_or (num_add, queries, TRUE);
1208
1209         /* Asynchronously retrieve contacts */
1210         e_book_async_get_contacts (book, composite_query, async_get_contacts_cb, address_list);
1211
1212         /* Frees. This will unref the subqueries as well */
1213         e_book_query_unref (composite_query);
1214 }