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