Using Maemo Address Book APIs

Introduction

This document has been reviewed for maemo 4.0.

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, allowing the creation of programs that integrate fully into the address book and communication frameworks of the maemo platform.

This document will look at the steps required in creating a simple application showing all of the user's contacts, allowing some interaction with them, and being able to dump the raw VCard data to the terminal. This document shows how to open and manipulate an address book, how to create and populate the models and views required to display the contacts, and how to use some of the provided dialogs.

Using 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 with statement

 #include <libebook/e-book.h>

and all the data for the libosso-abook functions is included with statement

 #include <libosso-abook/osso-abook.h>

Notes on how to compile a program using libosso-abook can be found at the end of this how-to, including a description on how to add a check for libosso-abook to the configuration file.

Application Initialization

The start of the program is similar to other maemo applications. The first step that is needed is the initialization of libosso to notify that the application has started.

osso_context_t *osso_context;

/* Initialize 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 necessary for supplying the help dialogs and for integrating certain features into the whole of the maemo platform. osso_abook_init also initializes GTK+, gnome-vfs and Galago, so the application does not have to initialize them 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, and uses them to initialize GTK+. In certain cases, such as plug-ins and libraries, however, it is not possible to pass these arguments. For such cases, libosso-abook also provides osso_abook_init_with_name, where the name parameter is used to name the Galago connection. The name should not contain any spaces or special characters:

gboolean osso_abook_init_with_name (const char *name, osso_context_t *osso_context);

When the example application can pass the command line arguments, the first version can be used.

/* 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 main loop quits, all the resources are tidied up, libosso is deinitialized and the program exits normally.

/* Make our UI */
app = app_create ();

/* Run */
gtk_main ();

/* Shutdown */
app_destroy (app);

osso_deinitialize (osso_context);
return 0;

Accessing Evolution Data Server (EDS)

Loading Contacts from EDS

In EDS, contacts are stored in a database called a book. There are three different ways to retrieve contacts: as a single contact, as a static list of contacts meeting unchanging criteria, or as a live view on the book that changes as contacts are added or removed. The last method is the most powerful, and it is also the way that most applications will use.

Opening 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 that create a book. The call to create a book depends on which kind of a 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 calls create different kinds of books. The first two calls can create any kind of a book, the latter two can only create pre-defined books. In nearly all circumstances, the call to use is e_book_new_system_addressbook.

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 immediately, but the book will not be ready for use until the open_response callback is called.

The following 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 a 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 set to FALSE and a non-existing book is opened, the database structure will be created on the disk with no contacts in it. On a new system, there will not be any address books, so TRUE should never be passed, as then the call would fail.

As mentioned above, when using the asynchronous version, control will return to the application immediately. 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);
	

When the open_response callback and the status do not report any errors, 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 address book
   when in the main loop */
g_idle_add (open_addressbook, app);

This means that once the main loop 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 address book: %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 address book, and creates it, if it does not exist already. Error checking is performed by passing a GError into a function, and if the function returns NULL or FALSE (depending on the return type of the function), an error has occurred and will be reported in the GError.

Retrieving Contacts

Once the book has been 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.

Even though the book view is the way used by most applications to get the contacts, it is possible to get single contacts and perform synchronous queries. For getting single contacts, the contact id is required. This means that all the contacts must have been retrieved before using one of the other two methods.

This operation can be performed 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

Queries are used to define the required contacts both when retrieving multiple contacts and when the book view is used. These queries are built from various basic queries, 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 the contacts in the book. The other three functions check for specific fields. Functions e_book_query_field_exists and e_book_query_vcard_field_exists both check for the existence of a specific field. The difference between them is the method of checking: e_book_query_field_exists uses the EContactField enumeration that 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 should search 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 basic queries can be combined in any desired 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 three functions takes in a number of queries, combines them and returns a new query that represents the correct operation applied to all the original queries.

The following code example will combine three queries, so that the contacts returned are contacts who have 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 the ownership of the queries that have been passed in, meaning that the application only needs to unref the result.

Getting Static List of Contacts.

When using this query to get a list of contacts, the contacts in the list will never change. As with most functions that operate 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 and the contacts have been returned, they should be unreffed with g_object_unref and call g_list_free on the list to free all the memory used.

Getting Dynamic Set of Contacts

Even though under certain circumstances, getting single contacts and static lists of contacts is useful, most applications need to use the book view method to get contacts. A book view is dynamic representation of a query performed on a book.

The book view is an object that has signals to indicate when contacts have been modified, removed or added. If the application uses the libosso-abook widgets, this will be handled automatically, and any changes in the book view will be correctly updated in the widgets. However, if the application is performing actions with book views that are not covered by the supplied widgets, then it is necessary to listen for these signals, and deal with them accordingly.

With a query created, the application can request a book view from the open book. Again, as with opening a book, there are two methods for getting a book view from a book: synchronous and asynchronous.

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 needed for this example. The only requirement is the query "-1" passed in for the max_results, meaning that all the results will be returned, and NULL can be passed in for requested_fields. These two parameters are not as much explicit controls as they are suggestions. The default back-end will ignore them and just return all the results, with all the fields available. In the LDAP back-end, there is support for them.

The book view can be used once it has been returned, either from the e_book_get_book_view call, or from the callback by 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);

The status parameter returns information on whether the call to e_book_async_get_book_view was successful, and the new book view is in the book_view parameter.

Finally, the book view needs to be told to start sending signals to the application when contacts are modified. The function for this is e_book_view_start. There is also a similar function for stopping the book view: 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 book view before calling e_book_view_start, in case the reporting of some contacts is missed.

Creating User Interface

The tutorial application simply displays all the contacts in the system address book as a list. There are two parts in creating this list: the list widget, which is derived from the GtkTreeView widget, and the model that drives the aforementioned widget and stores all the data.

Creating Contact Model

Libosso-abook provides a model derived from the GtkTreeModel object to store the contact data, and handles all that is required to deal with contacts being added and changed. The model is called OssoABookContactModel, and it 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 book view if the application has one. In the get_book_view_cb discussed earlier, the line

osso_abook_tree_model_set_book_view (OSSO_ABOOK_TREE_MODEL (app->contact_model), book_view);

will perform everything that is required.

Creating Contact View

To create a contact view, the application just uses the function osso_abook_contact_view_new_basic (). This function takes the contact model that will be used 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 be treated just 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 unreffing it would not be necessary 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 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, to start the composer to write an e-mail to a contact, and to edit, delete or organize contacts. It is a very powerful dialog, but one that is very simple to use in other applications.

In order to use the contact starter dialog, there has to be 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 in the contact that may happen while the dialog is open. N.B. Multiple applications are able to access the address book simultaneously, 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 address book need to handle the changes 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 book view 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 be treated just as a normal dialog.

Connecting ContactStarter Dialog to View

It is necessary to have a way to make the contact starter appear for a contact. Even though applications can perform in any chosen way, 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. Its signature is

void (* contact_activated) (OssoABookContactView *view, EContact *contact);

It returns the contact that received the double-click, making 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 to integrate fully into the maemo platform.

Accessing 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 performed to an EVCard, can be performed 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. There was an example above showing how to get a single contact that was selected from the contact view, but this requires a list. The OssoABookContactView contains a function that performs 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 mentioned above, the EContact object is also an EVCard object, meaning that the function e_vcard_to_string will return the raw VCard data. This function needs to know the format of the raw vcard data, either version 2.1 or version 3.0 of the VCard specification. The enums 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 Contact View

It is useful to know when the selection on the contact view has changed: the "Dump VCard" button in the tutorial is not of much use when there is no contact selected, so the proper way is 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, and that can be used to decide whether various controls are to be made 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.

Using Autoconf

Applications using autoconf can add the lines

libosso
osso-addressbook-1.0

to the PKG_CHECK_MODULES macro to enable the libosso-abook options.



Improve this page