b65fae355db2b844bff34c583e75be0ce83fa624
[modest] / src / maemo / 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 <libosso-abook/osso-abook.h>
39 #include <hildon-widgets/hildon-note.h>
40 #include <hildon-widgets/hildon-caption.h>
41 #include <hildon-widgets/hildon-banner.h>
42 #include <string.h>
43 #include <gtk/gtksizegroup.h>
44 #include <gtk/gtkbox.h>
45 #include <gtk/gtklabel.h>
46 #include <gtk/gtkcellrenderertext.h>
47 #include <gtk/gtktreeselection.h>
48
49 static OssoABookContactModel *contact_model =  NULL;
50 static EBook *book = NULL;
51 static EBookView * book_view = NULL;
52
53 static GSList *get_recipients_for_given_contact(EContact * contact);
54 static void commit_contact(EContact * contact);
55 static gchar *get_email_addr_from_user(const gchar * given_name);
56 static gchar *ui_get_formatted_email_id(gchar * current_given_name,
57                                         gchar * current_sur_name, gchar * current_email_id);
58 static gchar *run_add_email_addr_to_contact_dlg(const gchar * contact_name);
59 static GSList *select_email_addrs_for_contact(GList * email_addr_list);
60
61
62 static void
63 get_book_view_cb (EBook *book, EBookStatus status, EBookView *bookview, gpointer data)
64 {
65         if (status != E_BOOK_ERROR_OK) {
66                 g_object_unref (book);
67                 book = NULL;
68                 return;
69         }
70         book_view = bookview;
71
72         if (contact_model)
73                 osso_abook_tree_model_set_book_view (OSSO_ABOOK_TREE_MODEL (contact_model),
74                                                      book_view);
75
76         e_book_view_start (book_view);
77 }
78
79 static void
80 book_open_cb (EBook *view, EBookStatus status, gpointer data)
81 {
82         EBookQuery *query = NULL;
83
84         if (status != E_BOOK_ERROR_OK) {
85                 g_object_unref (book);
86                 book = NULL;
87                 return;
88         }
89         query = e_book_query_any_field_contains ("");
90         e_book_async_get_book_view (book, query, NULL, -1, get_book_view_cb, NULL);
91         e_book_query_unref (query);
92 }
93
94 static gboolean 
95 open_addressbook ()
96 {
97         book = e_book_new_system_addressbook (NULL);
98         if (!book)
99                 return FALSE;
100
101         e_book_async_open (book, FALSE, book_open_cb, NULL);
102
103         return TRUE; /* FIXME */        
104 }
105
106 void
107 modest_address_book_add_address (const gchar *address)
108 {
109         OssoABookAccount *account = NULL;
110         GtkWidget *dialog = NULL;
111
112         contact_model = osso_abook_contact_model_new ();
113         if (!open_addressbook ()) {
114                 if (contact_model) {
115                         g_object_unref (contact_model);
116                         contact_model = NULL;
117                 }
118                 return;
119         }
120         
121         account = osso_abook_account_get (EVC_EMAIL, NULL, address);
122         dialog = osso_abook_add_to_contacts_dialog_new (contact_model, account);
123         g_object_unref (account);
124         gtk_dialog_run (GTK_DIALOG (dialog));
125
126         if (contact_model) {
127                 g_object_unref (contact_model);
128                 contact_model = NULL;
129         }
130
131         gtk_widget_destroy (dialog);
132
133 }
134
135 void
136 modest_address_book_select_addresses (ModestRecptEditor *recpt_editor)
137 {
138         GtkWidget *contact_view = NULL;
139         GList *contacts_list = NULL;
140         GtkWidget *contact_dialog;
141         GSList *email_addrs_per_contact = NULL;
142         gchar *econtact_id;
143
144         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
145
146         contact_model = osso_abook_contact_model_new ();
147         if (!open_addressbook ()) {
148                 if (contact_model) {
149                         g_object_unref (contact_model);
150                         contact_model = NULL;
151                 }
152                 return;
153         }
154
155         contact_view = osso_abook_contact_selector_new_basic (contact_model);
156         osso_abook_contact_selector_set_minimum_selection (OSSO_ABOOK_CONTACT_SELECTOR (contact_view), 1);
157
158         contact_dialog = osso_abook_select_dialog_new (OSSO_ABOOK_TREE_VIEW (contact_view));
159         osso_abook_select_dialog_set_new_contact (OSSO_ABOOK_SELECT_DIALOG (contact_dialog), TRUE);
160
161         gtk_widget_show (contact_dialog);
162
163         if (gtk_dialog_run (GTK_DIALOG (contact_dialog)) == GTK_RESPONSE_OK) {
164                 contacts_list = 
165                         osso_abook_contact_selector_get_extended_selection (OSSO_ABOOK_CONTACT_SELECTOR
166                                                                              (contact_view));
167         }
168         
169         if (contacts_list) {
170                 GList *node;
171
172                 for (node = contacts_list; node != NULL; node = g_list_next (node)) {
173                         EContact *contact = (EContact *) node->data;
174
175                         email_addrs_per_contact = get_recipients_for_given_contact (contact);
176                         if (email_addrs_per_contact) {
177                                 econtact_id = (gchar *) e_contact_get_const (contact, E_CONTACT_UID);
178                                 modest_recpt_editor_add_resolved_recipient (MODEST_RECPT_EDITOR (recpt_editor), 
179                                                                             email_addrs_per_contact, econtact_id);
180                                 g_slist_foreach (email_addrs_per_contact, (GFunc) g_free, NULL);
181                                 g_slist_free (email_addrs_per_contact);
182                                 email_addrs_per_contact = NULL;
183                         }
184                 }
185                 g_list_free (contacts_list);
186         }
187
188         if (contact_view) {
189                 g_object_unref (contact_view);
190                 contact_view = NULL;
191         }
192
193         if (contact_model) {
194                 g_object_unref (contact_model);
195                 contact_model = NULL;
196         }
197
198         gtk_widget_destroy (contact_dialog);
199
200 }
201
202 /**
203  * This function returns the resolved recipients for a given EContact.
204  * If no e-mail address is defined, it launches 'Add e-mail address to contact'
205  * dialog to obtain one. If multiple e-mail addresses are found, it launches
206  * 'Select e-mail address' dialog to allow user to select one or more e-mail
207  * addresses for that contact.
208  *
209  * @param  Contact of type #EContact
210  * @return List of resolved recipient strings, to be freed by calling function.
211  */
212 static GSList *get_recipients_for_given_contact(EContact * contact)
213 {
214         gchar *givenname = NULL;
215         gchar *familyname = NULL;
216         gchar *nickname = NULL;
217         gchar *emailid = NULL;
218         const gchar *display_name = NULL;
219         GList *list = NULL;
220         gchar *formatted_string = NULL;
221         gboolean email_not_present = FALSE;
222         GSList *formattedlist = NULL, *selected_email_addr_list = NULL, *node = NULL;
223
224         if (!contact) {
225                 return NULL;
226         }
227
228         givenname = (gchar *) e_contact_get_const(contact, E_CONTACT_GIVEN_NAME);
229         familyname = (gchar *) e_contact_get_const(contact, E_CONTACT_FAMILY_NAME);
230         nickname = (gchar *) e_contact_get_const(contact, E_CONTACT_NICKNAME);
231         if (!nickname)
232                 nickname = "";
233         list = (GList *) e_contact_get(contact, E_CONTACT_EMAIL);
234
235         if (!list) {
236                 email_not_present = TRUE;
237         }
238
239         if (list && g_list_length(list) == 1) {
240                 if (list->data == NULL || g_utf8_strlen(list->data, -1) == 0) {
241                         email_not_present = TRUE;
242                 } else {
243                         emailid = g_strstrip(g_strdup(list->data));
244                         if (g_utf8_strlen(emailid, -1) == 0) {
245                                 g_free(emailid);
246                                 email_not_present = TRUE;
247                         }
248                 }
249         }
250
251         /*Launch the 'Add e-mail addr to contact' dialog if required */
252         if (email_not_present) {
253                 display_name = osso_abook_contact_get_display_name(contact);
254                 emailid = get_email_addr_from_user(display_name);
255
256                 if (emailid) {
257                         e_contact_set(contact, E_CONTACT_EMAIL_1, emailid);
258                         commit_contact(contact);
259                 }
260         }
261
262         if (emailid) {
263                 if (givenname || familyname)
264                         formatted_string =
265                             ui_get_formatted_email_id(givenname, familyname, emailid);
266                 else
267                         formatted_string = g_strdup(emailid);
268                 formattedlist = g_slist_append(formattedlist, formatted_string);
269                 g_free(emailid);
270         }
271
272         /*Launch the 'Select e-mail address' dialog if required */
273         if (g_list_length(list) > 1) {
274                 selected_email_addr_list = select_email_addrs_for_contact(list);
275                 for (node = selected_email_addr_list; node != NULL; node = node->next) {
276                         if (givenname || familyname)
277                                 formatted_string =
278                                     ui_get_formatted_email_id(givenname, familyname, node->data);
279                         else
280                                 formatted_string = g_strdup(node->data);
281                         formattedlist = g_slist_append(formattedlist, formatted_string);
282                 }
283                 if (selected_email_addr_list) {
284                         g_slist_foreach(selected_email_addr_list, (GFunc) g_free, NULL);
285                         g_slist_free(selected_email_addr_list);
286                 }
287         }
288
289         if (list) {
290                 g_list_foreach(list, (GFunc) g_free, NULL);
291                 g_list_free(list);
292         }
293
294         return formattedlist;
295 }
296
297 /**
298  * This is a helper function to commit a EContact to Address_Book application.
299  *
300  * @param  contact  Contact of type #EContact
301  * @return void
302  */
303 static void 
304 commit_contact(EContact * contact)
305 {
306         if (!contact || !book)
307                 return;
308
309         osso_abook_contact_commit(contact, FALSE, book);
310 }
311
312 /**
313  * This is a helper function used to launch 'Add e-mail address to contact' dialog
314  * after showing appropriate notification, when there is no e-mail address defined
315  * for a selected contact.
316  *
317  * @param  given_name  Given name of the contact
318  * @param  family_name  Family name of the contact
319  * @return E-mail address string entered by user, to be freed by calling function.
320  */
321 static gchar *
322 get_email_addr_from_user(const gchar * given_name)
323 {
324         gchar *notification = NULL;
325         gchar *email_addr = NULL;
326         GtkWidget *note;
327         gboolean note_response;
328
329
330         notification = g_strdup_printf(_("mcen_nc_email_address_not_defined"), given_name);
331
332         note = hildon_note_new_confirmation (NULL, notification);
333         note_response = gtk_dialog_run (GTK_DIALOG(note));
334         gtk_widget_destroy (note);
335         g_free(notification);
336
337         if (note_response == GTK_RESPONSE_OK) {
338                 email_addr = run_add_email_addr_to_contact_dlg(given_name);
339         }
340
341         return email_addr;
342 }
343
344 /**
345 This function is used to get the formated email id with given name and sur name
346 in the format "GIVENNAME SURNAME <EMAIL ADDRESS>".
347 @param current_given_name    to hold the given name
348 @param current_sur_name      to hold the sur name
349 @param current_email_id      to hold the email id. 
350 @return gchar* string to be freed by calling function
351 */
352 static gchar *
353 ui_get_formatted_email_id(gchar * current_given_name,
354                           gchar * current_sur_name, gchar * current_email_id)
355 {
356         GString *email_id_str = NULL;
357         gchar *email_id = NULL;
358
359         email_id_str = g_string_new(NULL);
360
361         if ((current_given_name != NULL) && ((strlen(current_given_name) != 0))
362             && (current_sur_name != NULL) && ((strlen(current_sur_name) != 0))) {
363                 g_string_printf(email_id_str, "%s %s %c%s%c", current_given_name,
364                                 current_sur_name, '<', current_email_id, '>');
365                 email_id = g_strdup(email_id_str->str);
366         } else if ((current_given_name != NULL) && (strlen(current_given_name) != 0)) {
367                 g_string_printf(email_id_str, "%s %c%s%c", current_given_name, '<',
368                                 current_email_id, '>');
369                 email_id = g_strdup(email_id_str->str);
370         } else if ((current_sur_name != NULL) && (strlen(current_sur_name) != 0)) {
371                 g_string_printf(email_id_str, "%s %c%s%c", current_sur_name, '<',
372                                 current_email_id, '>');
373                 email_id = g_strdup(email_id_str->str);
374         }
375         g_string_free(email_id_str, TRUE);
376         email_id_str = NULL;
377         return (email_id);
378 }
379
380 /**
381  * This is a helper function used to create & run 'Add e-mail address to contact' dialog.
382  * It allows user to enter an e-mail address, and shows appropriate infonote if the
383  * entered string is not a valid e-mail address.
384  *
385  * @param  contact_name  Full name of the contact
386  * @return E-mail address string entered by user, to be freed by calling function.
387  */
388 static gchar *
389 run_add_email_addr_to_contact_dlg(const gchar * contact_name)
390 {
391         GtkWidget *add_email_addr_to_contact_dlg = NULL;
392         GtkSizeGroup *size_group = NULL;
393         GtkWidget *cptn_cntrl = NULL;
394         GtkWidget *name_label = NULL;
395         GtkWidget *email_entry = NULL;
396         gint result = -1;
397         gchar *new_email_addr = NULL;
398         gboolean run_dialog = TRUE;
399
400         add_email_addr_to_contact_dlg =
401             gtk_dialog_new_with_buttons(_("mcen_ti_add_email_title"), NULL,
402                                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
403                                         _("mcen_bd_dialog_ok"), GTK_RESPONSE_ACCEPT,
404                                         _("mcen_bd_dialog_cancel"), GTK_RESPONSE_REJECT, NULL);
405         gtk_dialog_set_has_separator(GTK_DIALOG(add_email_addr_to_contact_dlg), FALSE);
406         /*Set app_name & state_save related tags to the window */
407
408         size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
409         name_label = gtk_label_new(contact_name);
410         gtk_misc_set_alignment(GTK_MISC(name_label), 0, 0);
411         cptn_cntrl =
412             hildon_caption_new(size_group, _("mcen_ia_add_email_name"), name_label, NULL,
413                                HILDON_CAPTION_OPTIONAL);
414         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(add_email_addr_to_contact_dlg)->vbox), cptn_cntrl,
415                            FALSE, FALSE, 0);
416
417         email_entry = gtk_entry_new();
418         cptn_cntrl =
419             hildon_caption_new(size_group, _("mcen_fi_add_email_name"), email_entry, NULL,
420                                HILDON_CAPTION_OPTIONAL);
421         hildon_gtk_entry_set_input_mode(GTK_ENTRY(email_entry), HILDON_GTK_INPUT_MODE_FULL);
422         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(add_email_addr_to_contact_dlg)->vbox), cptn_cntrl,
423                            TRUE, TRUE, 0);
424
425         gtk_widget_show_all(add_email_addr_to_contact_dlg);
426
427         while (run_dialog) {
428                 run_dialog = FALSE;
429                 gtk_widget_grab_focus(email_entry);
430                 result = gtk_dialog_run(GTK_DIALOG(add_email_addr_to_contact_dlg));
431
432                 if (result == GTK_RESPONSE_ACCEPT) {
433                         new_email_addr = g_strdup(gtk_entry_get_text(GTK_ENTRY(email_entry)));
434                         new_email_addr = g_strstrip(new_email_addr);
435                         if (!modest_text_utils_validate_email_address (new_email_addr)) {
436                                 gtk_widget_grab_focus(email_entry);
437                                 gtk_editable_select_region((GtkEditable *) email_entry, 0, -1);
438                                 hildon_banner_show_information (add_email_addr_to_contact_dlg, NULL, _("mcen_ib_invalid_email"));
439                                 run_dialog = TRUE;
440                                 g_free(new_email_addr);
441                                 new_email_addr = NULL;
442                         }
443                 }
444         }
445
446         gtk_widget_destroy(add_email_addr_to_contact_dlg);
447
448         return new_email_addr;
449 }
450
451 /**
452  * This is helper function to create & run 'Select e-mail address' dialog, used when
453  * multiple e-mail addresses are found for a selected contact. It allows user to select
454  * one or more e-mail addresses for that contact.
455  *
456  * @param  email_addr_list  List of e-mail addresses for that contact
457  * @return List of user selected e-mail addresses, to be freed by calling function.
458  */
459 static GSList *
460 select_email_addrs_for_contact(GList * email_addr_list)
461 {
462         GtkWidget *select_email_addr_dlg = NULL;
463         GtkWidget *view = NULL, *scrolledwindow = NULL;
464         GtkTreeSelection *selection = NULL;
465         GtkCellRenderer *renderer = NULL;
466         GtkTreeViewColumn *col = NULL;
467         GtkListStore *list_store = NULL;
468         GtkTreeModel *model = NULL;
469         GtkTreeIter iter;
470         GList *pathslist = NULL, *node = NULL;
471         gint result = -1;
472         gchar *email_addr = NULL;
473         GSList *selected_email_addr_list = NULL;
474
475         if (!email_addr_list)
476                 return NULL;
477
478         select_email_addr_dlg =
479             gtk_dialog_new_with_buttons(_("mcen_ti_select_email_title"),
480                                         NULL,
481                                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
482                                         _("mcen_bd_dialog_ok"), GTK_RESPONSE_ACCEPT,
483                                         _("mcen_bd_dialog_cancel"), GTK_RESPONSE_REJECT, NULL);
484         gtk_dialog_set_has_separator(GTK_DIALOG(select_email_addr_dlg), FALSE);
485
486         scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
487         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(select_email_addr_dlg)->vbox), scrolledwindow, TRUE,
488                            TRUE, 0);
489         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_AUTOMATIC,
490                                        GTK_POLICY_AUTOMATIC);
491
492         view = gtk_tree_view_new();
493         col = gtk_tree_view_column_new();
494         renderer = gtk_cell_renderer_text_new();
495         gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
496         gtk_tree_view_column_pack_start(col, renderer, TRUE);
497         gtk_tree_view_column_add_attribute(col, renderer, "text", 0);
498         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
499         gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
500         gtk_container_add(GTK_CONTAINER(scrolledwindow), view);
501
502         list_store = gtk_list_store_new(1, G_TYPE_STRING);
503         model = GTK_TREE_MODEL(list_store);
504         gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
505
506         for (node = email_addr_list; node != NULL && node->data != NULL; node = node->next) {
507                 email_addr = g_strstrip(g_strdup(node->data));
508                 gtk_list_store_append(list_store, &iter);
509                 gtk_list_store_set(list_store, &iter, 0, email_addr, -1);
510                 g_free(email_addr);
511         }
512
513         gtk_widget_show_all(select_email_addr_dlg);
514         result = gtk_dialog_run(GTK_DIALOG(select_email_addr_dlg));
515
516         if (result == GTK_RESPONSE_ACCEPT) {
517                 pathslist = gtk_tree_selection_get_selected_rows(selection, NULL);
518                 for (node = pathslist; node != NULL; node = node->next) {
519                         if (gtk_tree_model_get_iter(model, &iter, (GtkTreePath *) node->data)) {
520                                 gtk_tree_model_get(model, &iter, 0, &email_addr, -1);
521                                 selected_email_addr_list =
522                                     g_slist_append(selected_email_addr_list, g_strdup(email_addr));
523                                 g_free(email_addr);
524                         }
525                 }
526         }
527
528         gtk_list_store_clear(list_store);
529         gtk_widget_destroy(select_email_addr_dlg);
530         return selected_email_addr_list;
531 }