Using maemo address book APIs
This document is released under the GPL license.
Introduction
This document has been reviewed for maemo 3.x.
This document describes how to use address book APIs provided by the libosso-abook component in the maemo platform. Libosso-abook is a collection of powerful widgets and objects that allow the creation of programs which integrate fully with the addressbook and communication frameworks of the maemo platform.
This document will look at the steps required in creating a simple application that shows all the user's contacts, allows some interactions with them, and can dump the raw VCard data to the terminal. It will show how to open and manipulate an addressbook, how to create and populate the models and views required to display the contacts and how some of the provided dialogs are used.
Using the library
Include files
There are only two include files needed to deal with the functions in this tutorial. All the relevant data for the Evolution Data Server functions is included by
#include <libebook/e-book.h>
and all the data for the libosso-abook functions is included with
#include <libosso-abook/osso-abook.h
Notes on how to compile a program using libosso-abook can be found at the end of this tutorial including how to add a check for libosso-abook to the configure file.
Application Initialization
The start of the program is similar to other maemo applications. The first thing that needs to be done is the initialization of libosso.
osso_context_t *osso_context; /* Initialise osso */ osso_context = osso_initialize ("osso-abook-example", VERSION, TRUE, NULL); if (osso_context == NULL) { g_critical ("Error initializing osso"); return 1; }
When initializing libosso-abook this osso_context variable needs to be passed to osso_abook_init. It is needed to supply the help dialogs and to integrate certain features with the whole of the maemo platform. osso_abook_init also initializes GTK+, gnome-vfs and Galago so the application does not have to do that itself.
This is the prototype for osso_abook_init:
gboolean osso_abook_init (int *argc, char ***argv, osso_context_t *osso_context);
osso_abook_init also takes the command line arguments passed to the application, which it uses to initialize GTK+. In certain cases, such as plugins and libraries, however, it is not possible to pass in these arguments. For cases like this libosso-abook also provides osso_abook_init_with_name where the name parameter is used to name the galago connection, and should have no spaces or special characters:
gboolean osso_abook_init_with_name (const char *name, osso_context_t *osso_context);
As the example application can pass in the command line arguments, then it can use the first version.
/* Init abook */ if (!osso_abook_init (&argc, &argv, osso_context)) { g_critical ("Error initializing libosso-abook"); osso_deinitialize (osso_context); return 1; }
The rest of the main function just creates the application and enters the main loop. After the mainloop quits it tidies up all the resources and deinitializes libosso and exits like a normal program.
/* Make our UI */ app = app_create (); /* Run */ gtk_main (); /* Shutdown */ app_destroy (app); osso_deinitialize (osso_context); return 0;
Accessing the Evolution data server (EDS)
Loading the contacts from EDS
In EDS, contacts are stored in a database on disk called a book and there are three different ways to retrieve them, as a single contact, as a static list of contacts that meet a criteria that never changes, or as a live view on the book that changes as contacts are added or removed. This last method is the most powerful and is the way that most applications will use.
Opening the book
Before any operation can be performed on the contacts, the book containing them needs to be created and then opened. There are a number of different calls which create a book this depending on which book is required.
EBook *e_book_new (ESource *source, GError **error); EBook *e_book_new_from_uri (const char *uri, GError **error); EBook *e_book_new_system_addressbook (GError **error); EBook *e_book_new_default_addressbook (GError **error);
The differences are in what book the calls create. The first two can create any book, the latter two create pre-defined books. In nearly all circumstances e_book_new_system_addressbook is the one required.
Once an EBook object is created, the book must be opened. Books can either be opened synchronously meaning that the application will pause until the book is opened, or asynchronously, meaning that control will return to the application immediatly, but the book will not be ready for use until the open_response callback is called.
These are the two prototypes for opening books:
gboolean e_book_open (EBook *book, gboolean only_if_exists, GError **error); guint e_book_async_open (EBook *book, gboolean only_if_exists, EBookCallback open_response, gpointer closure);
There is a parameter called only_if_exists. This parameter controls whether or not the database is created on disk. If set to TRUE and the application attempts to open a book that does not already exist an error will be returned. If it is set to FALSE and a non-existing book is opened, then the database structure will be created on the disk with no contacts in it. On a new system the addressbook will not exist, so FALSE should never be passed as the call will fail otherwise.
As mentioned above when using the asynchronous version, control will return to the application immediately and when the book is opened the open_response callback will be called and closure will be passed to it. The EBookCallback is defined as:
void (*EBookCallback) (EBook *book, EBookStatus status, gpointer closure);
Once the open_response callback and the status does not report that an error occurred the book is open and ready for use.
The only difficulty in opening a book is that the main loop needs to be running for it to work. This means that if the main loop is not running and the book needs to be opened automatically, then the e_book_new call needs to be made in an idle handler. The example program does this at the end of the app_create function, passing in the AppData structure.
AppData *app; /* Install an idle handler to open the addressbook when we're in the mainloop */ g_idle_add (open_addressbook, app);
This means that once the mainloop is running, open_addressbook will be called. open_addressbook is the function that creates and opens the book using the asynchronous functions discussed above.
static gboolean open_addressbook (gpointer user_data) { AppData *app; GError *error; app = user_data; error = NULL; app->book = e_book_new_system_addressbook (&error); if (!app->book) { g_warning ("Error opening system addressbook: %s", error->message); g_error_free (error); return FALSE; } e_book_async_open (app->book, FALSE, book_open_cb, app); /* Return FALSE so this callback is only run once */ return FALSE; }
This function attempts to open the system addressbook, and creates it if it doesn't already exist. Error checking is done by passing a GError into a function, and if the function returns NULL or FALSE (depending on the function's return type) an error occurred and this will be reported in the GError.
Retrieving the contacts
Once the book is successfully opened, either directly after a call to e_book_open, or in the open_response callback of e_book_async_open the contacts can be manipulated.
Although the book view is the way most applications will get contacts, it is possible to get single contacts and perform synchronous queries. For getting single contacts the contact id is required, which means that contacts need to have been retrieved before using one of the other two methods.
This operation can be done either synchronously or asynchronously, as required, by using e_book_get_contact or e_book_async_get_contact:
gboolean e_book_get_contact (EBook *book, const char *id, EContact **contact, GError **error); guint e_book_async_get_contact (EBook *book, const char *id, EBookContactCallback cb, gpointer closure);
The EBookContactCallback is defined as
void (*EBookContactCallback) (EBook *book, EBookStatus status, EContact *contact, gpointer closure);
Queries
Retrieving multiple contacts and the book view both use queries to define the required contacts. These queries are built from some basic queries which are combined using the boolean operations AND, OR and NOT. The basic queries are created with the following four functions:
EBookQuery *e_book_query_field_exists (EContactField field); EBookQuery *e_book_query_vcard_field_exists (const char *field); EBookQuery *e_book_query_field_test (EContactField field, EBookQueryTest test, const char *value); EBookQuery *e_book_query_any_field_contains (const char *value);
Of these, e_book_query_any_field_contains is the most generic and if passed an empty string ("") acts as the method for getting all contacts in the book. The other three functions check for specific fields. e_book_query_field_exists and e_book_query_vcard_field_exists both check for the existance of a field. The difference between them is the method of checking. e_book_query_field_exists uses the EContactField enumeration which contains many common field types such as E_CONTACT_FULL_NAME, E_CONTACT_HOMEPAGE_URL and E_CONTACT_EMAIL_1 (the full list can be found in the include file libebook/e-contact.h).
The final function, e_book_query_field_test, is the most powerful, and uses the EBookQueryTest to define the type of test to perform on the field. There are four possible tests; IS, CONTAINS, BEGINS_WITH and ENDS_WITH. These are defined in the file libebook/e-book-query.h. The term the query searches for is given in the third parameter. For the call:
query = e_book_query_field_test (E_CONTACT_FULL_NAME, E_BOOK_QUERY_BEGINS_WITH, "ross");
the resulting query will match contacts with the full name field containing "Ross Smith" and "Ross Bloggs" (queries are case insensitive), but will not match "Jonathan Ross" or "Diana Ross".
These base queries can be combined in any way using the following functions that implement the boolean operations:
EBookQuery *e_book_query_and (int nqs, EBookQuery **qs, gboolean unref); EBookQuery *e_book_query_or (int nqs, EBookQuery **qs, gboolean unref); EBookQuery *e_book_query_not (int nqs, EBookQuery **qs, gboolean unref);
Each of these 3 functions takes in a number of queries and combines them and returns a new query that represents the correct operation applied to all the original queries.
This example snippet of code will combine three queries so that the contacts returned are contacts with a blog and whose name begins with either Matthew or Ross.
EBookQuery *name_query[2], *blog_query[2]; EBookQuery *query; name_query[0] = e_book_query_field_test (E_CONTACT_FULL_NAME, E_BOOK_QUERY_BEGINS_WITH, "ross"); name_query[1] = e_book_query_field_test (E_CONTACT_FULL_NAME, E_BOOK_QUERY_BEGINS_WITH, "matthew"); blog_query[0] = e_book_query_field_exists (E_CONTACT_BLOG_URL); /* Combine the name queries with an OR operation to create the second bit of the blog query. */ blog_query[1] = e_book_query_or (2, name_query, TRUE); query = e_book_query_and (2, blog_query, TRUE); /* carry out query on book */ e_book_query_unref (query);
When combining queries, the boolean operation functions are able to take ownership of the queries that have been passed in which means that the application only needs to unref the result.
Getting a static list of contacts.
When using a query to get a list of contacts, the contacts in that list will never change. As with most functions that operation on the book there is a synchronous and an asynchronous way to get a static list of contacts.
gboolean e_book_get_contacts (EBook *book, EBookQuery *query, GList **contacts, GError **error); guint e_book_async_get_contacts (EBook *book, EBookQuery *query, EBookListCallback cb, gpointer closure);
The EBookListCallback is defined as
void (*EBookListCallback) (EBook *book, EBookStatus status, GList *list, gpointer closure);
Once the application has finished with the contacts returned they should unref them with g_object_unref and call g_list_free on the list to free all the memory used.
Getting a dynamic set of contacts
Although getting single contacts and static lists of contacts are useful in certain circumstances, most applications will want to be using the bookview method of getting contacts. A bookview is dynamic (or live) representation of a query performed on a book.
The bookview is an object that has signals for when contacts are changed, removed or added. If the application uses the libosso-abook widgets this will be handled automatically and any changes in the bookview will be correctly updated in the widgets, however, if the application is doing things with bookviews that are not covered by the supplied widgets, then it will have to listen for these signals and deal with them accordingly.
With a query created the application can request a bookview from the open book. Again, as with opening a book, there are two methods for getting a bookview from a book, synchronously and asynchronously:
gboolean e_book_get_book_view (EBook *book, EBookQuery *query, GList *requested_fields, int max_results, EBookView **book_view, GError **error); guint e_book_async_get_book_view (EBook *book, EBookQuery *query, GList *requested_fields, int max_results, EBookBookViewCallback cb, gpointer closure);
These functions are more powerful than are needed for this example, all thats needed is the query, -1 passed in for the max_results means that all the results will be returned and NULL can be passed in for requested_fields. These two parameters are more hints rather than explicit controls. The default backend ignores them and just returns all the results with all the fields available to it. The LDAP backend does have support for them.
The bookview is able to be used once it has been returned, either from the e_book_get_book_view call, or in the callback from e_book_async_get_book_view.
static void book_open_cb (EBook *book, EBookStatus status, gpointer user_data) { AppData *app; EBookQuery *query; app = user_data; if (status != E_BOOK_ERROR_OK) { g_warning ("Error opening book"); return; } query = e_book_query_any_field_contains (""); e_book_async_get_book_view (app->book, query, NULL, -1, get_book_view_cb, app); e_book_query_unref (query); }
EBookBookViewCallback has the following prototype:
void (*EBookBookViewCallback) (EBook *book, EBookStatus status, EBookView *book_view, gpointer closure);
Whether the call to e_book_async_get_book_view was successful or not is returned by the status parameter, and the new bookview is in the book_view parameter.
Finally, the bookview needs to be told to start sending signals to the application when contacts are modified, and the function to do this is e_book_view_start and it has a similar function for stopping the bookview, e_book_view_stop.
void e_book_view_start (EBookView *book_view); void e_book_view_stop (EBookView *book_view);
The application should connect signals to the bookview before calling e_book_view_start in case it misses some contacts being reported.
Creating the user interface
The tutorial application simply displays all the contacts in the system addressbook as a list. There are two parts to creating this list, the list widget, which is derived from the GtkTreeView widget, and the model that drives this widget and stores all the data.
Creating the contact model
Libosso-abook provides a model derived from the GtkTreeModel object to store the contact data and handles all the required machinary to deal with contacts being added and changed. The model is called OssoABookContactModel and is created with osso_abook_contact_model_new() which takes no arguments.
app->contact_model = osso_abook_contact_model_new ();
OssoABookContactModel populates itself from an EBookView. All that is required to populate it is to set the bookview once the application has one. In the get_book_view_cb from earlier, the line
osso_abook_tree_model_set_book_view (OSSO_ABOOK_TREE_MODEL (app->contact_model), book_view);
will do everything that is required.
Creating the contact view
To create the contact view the application just use the function osso_abook_contact_view_new_basic (). This function takes the contact model that it will use to obtain the contacts.
app->contact_view = osso_abook_contact_view_new_basic (app->contact_model); g_object_unref (app->contact_model);
This new widget can just be treated like any other widget and placed in the UI in the usual way. Once the contact view has been created, the model is unreffed as the application does not need to hold a reference to it anymore. This means that when the contact view is destroyed, the model will be as well. If the application needed to keep the model around after the view had been destroyed, then it would not be necessary to unref it here.
There is another more powerful (but also more complicated) way of creating views that can automatically filter contacts based on a filter model. The function osso_abook_contact_view_new ().
GtkWidget *osso_abook_contact_view_new (OssoABookContactModel *model, OssoABookFilterModel *filter_model);
Performing actions on the contacts
In the Osso-Addressbook application, the main way of interacting with contacts is through the "contact starter dialog". This dialog is able to start chat and voip sessions with contacts, start the composer to write an email to a contact, and to edit, delete or organise contacts. It is a very powerful dialog, but one that is very simple to use in other applications.
The things needed to use the contact starter dialog are a contact (obviously), and a book view. The contact is needed to get the contact information and the book view is used to listen to any changes to the contact that may happen while the dialog is open. Always remember that multiple applications are able to access the addressbook at the same time, and while one program has a dialog open, another program may change some details in the contact, or even delete it. The stock dialogs and widgets in libosso-abook handle these cases, but it is important to remember that applications doing anything else with the addressbook need to handle it as well.
To create the contact starter dialog the function osso_abook_contact_starter_new () is used. This function returns a widget derived from GtkDialog and so can be manipulated using all the standard GtkDialog functions.
GtkWidget *osso_abook_contact_starter_new ();
The contact and bookview can be set on the dialog with the following functions:
void osso_abook_contact_starter_set_contact (OssoABookContactStarter *starter, EContact *contact); void osso_abook_contact_starter_set_book_view (OssoABookContactStarter *starter, EBookView *book_view);
After this the ContactStarter can just be treated as a normal dialog.
Connecting the ContactStarter dialog to the view
Some way of making the contact starter appear for a contact is needed. Although applications can do this whatever way they choose, for this tutorial the dialog will appear whenever the user double clicks on a contact in the list. The OssoABookContactView contains a signal that can help here, ::contact_activated. It's signature is
void (* contact_activated) (OssoABookContactView *view, EContact *contact);
It provides the contact that received the double click operation which makes it ideal in this situation.
static void contact_activated_cb (OssoABookContactView *view, EContact *contact, gpointer user_data) { AppData *app; GtkWidget *starter; app = user_data; starter = osso_abook_contact_starter_new (); osso_abook_contact_starter_set_book_view (OSSO_ABOOK_CONTACT_STARTER (starter), app->book_view); osso_abook_contact_starter_set_contact (OSSO_ABOOK_CONTACT_STARTER (starter), contact); gtk_dialog_run (GTK_DIALOG (starter)); gtk_widget_destroy (starter); } . . . g_signal_connect (app->contact_view, "contact-activated", G_CALLBACK (contact_activated_cb), app);
With this simple callback function, the application can present the user with the ability to carry out very powerful functionality and integrate fully into the maemo platform.
Accessing the raw VCard data
An EContact is an object derived from the EVCard object. This means that EContacts can be treated as EVCards and anything that can be done to an EVCard can be done to an EContact. To demonstrate this the application has a button to dump the raw vcard data of any selected contacts.
This button is just a normal button, created with gtk_button_new_with_label and added to the UI in the usual way.
app->dump_button = gtk_button_new_with_label ("Dump VCards"); gtk_box_pack_start (GTK_BOX (box), app->dump_button, FALSE, FALSE, 0); gtk_widget_show (app->dump_button); g_signal_connect (app->dump_button, "clicked", G_CALLBACK (dump_vcards), app);
In the dump_vcards function the application gets a list of selected contacts and iterates through them, printing the vcards. It was shown above how to get a single contact that was selected from the contact view, but this requires a list. The OssoABookContactView contains a function that does exactly that:
GList *osso_abook_contact_view_get_selection (OssoABookContactView *view);
This function returns a list of all the selected EContact objects. Iterating through them is a simple list operation. As was said above, the EContact object is also an EVCard object, which means that the function e_vcard_to_string that will return the raw VCard data. This function wants to know the format of the raw vcard data, either version 2.1 or version 3.0 of the VCard spec. The enum to do this are EVC_FORMAT_VCARD_21 and EVC_FORMAT_VCARD_30 for version 2.1 and 3.0 respectively.
static void dump_vcards (GtkWidget *button, gpointer user_data) { AppData *app; GList *contacts, *c; int count; app = user_data; count = 1; contacts = osso_abook_contact_view_get_selection (OSSO_ABOOK_CONTACT_VIEW (app->contact_view)); for (c = contacts; c; c = c->next) { EContact *contact = E_CONTACT (c->data); EVCard *vcard = E_VCARD (contact); char *v; v = e_vcard_to_string (vcard, EVC_FORMAT_VCARD_30); g_print ("Card %d\n", count); g_print ("%s", v); g_print ("-------\n"); g_free (v); count++; } g_list_free (contacts); }
It is not necessary to free the contacts in the list, but the list itself does need to be freed, or else it will leak.
Listening to selection change on the contact view
It is useful to know when the selection on the contact view has changed, in the tutorial the "Dump VCard" button is not much use when there is no contact selected, so it is always nice to make it insensitive in these cases. Once again OssoABookContactView provides a signal that is useful in these situations.
void (* selection_changed) (OssoABookContactView *view, guint n_selected_rows);
::selection_changed tells the callback the number of selected rows which is used to know whether or not to make some controls sensitive.
static void selection_changed_cb (OssoABookContactView *view, guint n_selected_rows, gpointer user_data) { AppData *app; app = user_data; gtk_widget_set_sensitive (app->dump_button, (n_selected_rows > 0)); }
Compiling programs which use Libosso-abook
Libosso-abook provides a pkgconfig file for getting all the required cflags and library link flags to compile programs with it.
Using autoconf
Applications which use autoconf can add the lines
libosso osso-addressbook-1.0
to the PKG_CHECK_MODULES macro to enable the libosso-abook options.
Example Code
A full C example application source about the Address Book API can be found in the maemo-examples package.Improve this page