[evolution-data-server/openismus-work-3-8: 91/116] Adding cursor example program



commit 8e40a9666a567ddb6706ba2c4d78319b1700309a
Author: Tristan Van Berkom <tristanvb openismus com>
Date:   Tue Jul 23 14:25:39 2013 +0900

    Adding cursor example program
    
    The example program is a contact browser showing off
    some of the cursor features such as localized alphabetic indexes.

 addressbook/libebook/e-book-client-cursor.c        |   11 +-
 configure.ac                                       |    3 +-
 po/POTFILES.skip                                   |    3 +
 tests/Makefile.am                                  |    4 +
 tests/cursor-example/Makefile.am                   |   56 ++
 tests/cursor-example/cursor-data.c                 |  310 +++++++++
 tests/cursor-example/cursor-data.h                 |   29 +
 tests/cursor-example/cursor-example.c              |  684 ++++++++++++++++++++
 tests/cursor-example/cursor-example.gresources.xml |    8 +
 tests/cursor-example/cursor-example.h              |   57 ++
 tests/cursor-example/cursor-example.ui             |  658 +++++++++++++++++++
 tests/cursor-example/cursor-navigator.c            |  261 ++++++++
 tests/cursor-example/cursor-navigator.h            |   63 ++
 tests/cursor-example/cursor-search.c               |  262 ++++++++
 tests/cursor-example/cursor-search.h               |   58 ++
 tests/cursor-example/cursor-search.ui              |   48 ++
 tests/cursor-example/cursor-slot.c                 |  152 +++++
 tests/cursor-example/cursor-slot.h                 |   60 ++
 tests/cursor-example/cursor-slot.ui                |  108 +++
 tests/cursor-example/main.c                        |   43 ++
 20 files changed, 2876 insertions(+), 2 deletions(-)
---
diff --git a/addressbook/libebook/e-book-client-cursor.c b/addressbook/libebook/e-book-client-cursor.c
index 3703f68..2d12b5d 100644
--- a/addressbook/libebook/e-book-client-cursor.c
+++ b/addressbook/libebook/e-book-client-cursor.c
@@ -1685,7 +1685,10 @@ move_by_sync_internal (EBookClientCursor   *cursor,
                        tmp = g_slist_prepend (tmp, contact);
                }
 
-               *out_contacts = g_slist_reverse (tmp);
+               if (out_contacts)
+                       *out_contacts = g_slist_reverse (tmp);
+               else
+                       g_slist_free_full (tmp, (GDestroyNotify)g_object_unref);
 
                g_strfreev (vcards);
        }
@@ -2292,6 +2295,12 @@ e_book_client_cursor_move_by_sync (EBookClientCursor   *cursor,
  *
  * See: e_book_client_cursor_set_alphabetic_index_sync().
  *
+ * If this method is called from the same thread context in which
+ * the cursor was created, then the updates to the #EBookClientCursor:position
+ * property are guaranteed to be delivered synchonously upon successful completion
+ * of moving the cursor. Otherwise, notifications will be delivered asynchronously
+ * in the cursor's original thread context.
+ *
  * This asynchronous call is completed with a call to
  * e_book_client_cursor_set_alphabetic_index_finish() from the specified @callback.
  *
diff --git a/configure.ac b/configure.ac
index 704bf9e..dc07bbe 100644
--- a/configure.ac
+++ b/configure.ac
@@ -39,7 +39,7 @@ m4_define([glib_minimum_version], [2.34])
 m4_define([glib_encoded_version], [GLIB_VERSION_2_34])
 
 dnl Keep these two definitions in agreement.
-m4_define([gdk_minimum_version], [3.2])
+m4_define([gdk_minimum_version], [3.9])
 m4_define([gdk_encoded_version], [GDK_VERSION_3_2])
 
 dnl Keep these two definitions in agreement.
@@ -1780,6 +1780,7 @@ tests/test-server-utils/services/Makefile
 tests/test-server-utils/services/org.gnome.evolution.dataserver.AddressBook.service
 tests/test-server-utils/services/org.gnome.evolution.dataserver.Calendar.service
 tests/test-server-utils/services/org.gnome.evolution.dataserver.Sources.service
+tests/cursor-example/Makefile
 docs/Makefile
 docs/reference/Makefile
 docs/reference/addressbook/Makefile
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 541f86e..7ef7172 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -8,3 +8,6 @@ camel/providers/nntp/test-newsrc.c
 modules/ubuntu-online-accounts/google-contacts.service.in
 modules/ubuntu-online-accounts/google-gmail.service.in
 servers/exchange/lib/e2k-user-dialog.c
+tests/cursor-example/cursor-example.ui
+tests/cursor-example/cursor-search.ui
+tests/cursor-example/cursor-slot.ui
diff --git a/tests/Makefile.am b/tests/Makefile.am
index a370561..7287065 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,5 +1,9 @@
 SUBDIRS = test-server-utils libedataserver libebook-contacts libedata-book libebook libecal libedata-cal
 
+if HAVE_GTK
+SUBDIRS += cursor-example
+endif
+
 @GNOME_CODE_COVERAGE_RULES@
 
 -include $(top_srcdir)/git.mk
diff --git a/tests/cursor-example/Makefile.am b/tests/cursor-example/Makefile.am
new file mode 100644
index 0000000..de7252e
--- /dev/null
+++ b/tests/cursor-example/Makefile.am
@@ -0,0 +1,56 @@
+BUILT_SOURCES = cursor-example-resources.h cursor-example-resources.c
+
+noinst_PROGRAMS = cursor-example
+cursor_example_SOURCES =       \
+       $(BUILT_SOURCES)        \
+       cursor-data.c           \
+       cursor-data.h           \
+       cursor-example.c        \
+       cursor-example.h        \
+       cursor-navigator.c      \
+       cursor-navigator.h      \
+       cursor-search.c         \
+       cursor-search.h         \
+       cursor-slot.c           \
+       cursor-slot.h           \
+       main.c
+
+cursor_example_CPPFLAGS =                                      \
+       $(AM_CPPFLAGS)                                          \
+       -DG_LOG_DOMAIN=\"cursor-example\"                       \
+       -I$(top_srcdir)                                         \
+       -I$(top_builddir)                                       \
+       -I$(top_srcdir)/addressbook                             \
+       -I$(top_builddir)/addressbook                           \
+       -I$(top_srcdir)/calendar                                \
+       -I$(top_builddir)/calendar                              \
+       -DSRCDIR=\""$(abs_srcdir)"\"                            \
+       $(EVOLUTION_ADDRESSBOOK_CFLAGS)                         \
+       $(GTK_CFLAGS) $(CAMEL_CFLAGS)                           \
+       $(CODE_COVERAGE_CFLAGS)                                 \
+       $(NULL)
+cursor_example_CFLAGS   = 
+cursor_example_LDFLAGS  = \
+       $(top_builddir)/addressbook/libebook/libebook-1.2.la    \
+       $(top_builddir)/tests/test-server-utils/libetestserverutils.la  \
+       $(top_builddir)/private/libedbus-private.la             \
+       $(EVOLUTION_ADDRESSBOOK_LIBS)                           \
+       $(GTK_LIBS) $(CAMEL_LIBS)                               \
+       $(NULL)
+cursor_example_LDADD =
+
+UI_FILES =                     \
+       cursor-example.ui       \
+       cursor-search.ui        \
+       cursor-slot.ui
+
+# Generate resources
+cursor-example-resources.h: cursor-example.gresources.xml $(UI_FILES)
+       $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) $(srcdir)/cursor-example.gresources.xml \
+               --target=$@ --sourcedir=$(srcdir) --c-name _cursor_example --generate-header
+cursor-example-resources.c: cursor-example.gresources.xml $(UI_FILES)
+       $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) $(srcdir)/cursor-example.gresources.xml \
+               --target=$@ --sourcedir=$(srcdir) --c-name _cursor_example --generate-source
+
+EXTRA_DIST = $(UI_FILES) cursor-example.gresources.xml
+CLEANFILES = $(BUILT_SOURCES)
diff --git a/tests/cursor-example/cursor-data.c b/tests/cursor-example/cursor-data.c
new file mode 100644
index 0000000..00e9961
--- /dev/null
+++ b/tests/cursor-example/cursor-data.c
@@ -0,0 +1,310 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+#include "cursor-data.h"
+
+#define CURSOR_DATA_SOURCE_ID "cursor-example-book"
+
+static void                  load_contacts (EBookClient          *client,
+                                           const gchar          *vcard_directory);
+static EBookClientCursor    *get_cursor    (EBookClient          *book_client);
+
+/* Just an example, we need to spin the main loop
+ * a bit to wait for the ESource to be created,
+ *
+ * So lets just use some global variables here
+ */
+static EBookClient *address_book = NULL;
+static ESource     *address_book_source = NULL;
+
+static void
+cursor_data_source_added (ESourceRegistry *registry,
+                         ESource *source,
+                         gpointer data)
+{
+       GError    *error = NULL;
+       GMainLoop *loop = (GMainLoop *)data;
+
+       if (g_strcmp0 (e_source_get_uid (source), CURSOR_DATA_SOURCE_ID) != 0)
+               return;
+
+       /* Open the address book */
+       address_book = (EBookClient *)e_book_client_connect_sync (source, NULL, &error);
+       if (!address_book)
+               g_error ("Unable to create the test book: %s", error->message);
+
+       address_book_source = g_object_ref (source);
+
+       if (loop)
+               g_main_loop_quit (loop);
+}
+
+static gboolean
+cursor_data_source_timeout (gpointer user_data)
+{
+       g_error ("Timed out while waiting for ESource creation from the registry");
+
+       return FALSE;
+}
+
+static void
+setup_custom_book (ESource *scratch)
+{
+       ESourceBackendSummarySetup *setup;
+
+       g_type_ensure (E_TYPE_SOURCE_BACKEND_SUMMARY_SETUP);
+       setup = e_source_get_extension (scratch, E_SOURCE_EXTENSION_BACKEND_SUMMARY_SETUP);
+       e_source_backend_summary_setup_set_summary_fields (
+               setup,
+               E_CONTACT_FULL_NAME,
+               E_CONTACT_FAMILY_NAME,
+               E_CONTACT_GIVEN_NAME,
+               E_CONTACT_NICKNAME,
+               E_CONTACT_TEL,
+               E_CONTACT_EMAIL,
+               0);
+       e_source_backend_summary_setup_set_indexed_fields (
+               setup,
+               E_CONTACT_FULL_NAME, E_BOOK_INDEX_PREFIX,
+               E_CONTACT_FAMILY_NAME, E_BOOK_INDEX_PREFIX,
+               E_CONTACT_GIVEN_NAME, E_BOOK_INDEX_PREFIX,
+               E_CONTACT_NICKNAME, E_BOOK_INDEX_PREFIX,
+               E_CONTACT_TEL, E_BOOK_INDEX_PREFIX,
+               E_CONTACT_TEL, E_BOOK_INDEX_PHONE,
+               E_CONTACT_EMAIL, E_BOOK_INDEX_PREFIX,
+               0);
+}
+
+/* This ensures that all of the test contacts are
+ * installed in the CURSOR_DATA_SOURCE_ID test book.
+ *
+ * Then it opens an EBookClientCursor.
+ *
+ * The cursor has no filter on the results and is
+ * ordered by family name (ascending) and then given name (ascending).
+ */
+EBookClient *
+cursor_load_data (const gchar        *vcard_path,
+                 EBookClientCursor **ret_cursor)
+{
+       ESourceRegistry *registry;
+       ESource *scratch;
+       ESourceBackend *backend = NULL;
+       ESourceBackendSummarySetup *setup = NULL;
+       EBookClientCursor *cursor = NULL;
+       GMainLoop *loop;
+       GError  *error = NULL;
+       GSList *contacts = NULL;
+       EBookClient *ret_book;
+
+       g_return_val_if_fail (vcard_path != NULL, NULL);
+       g_return_val_if_fail (ret_cursor != NULL, NULL);
+
+       loop = g_main_loop_new (NULL, FALSE);
+
+       registry = e_source_registry_new_sync (NULL, &error);
+       if (!registry)
+               g_error ("Unable to create the registry: %s", error->message);
+
+       /* Listen to the registry for our added source */
+       g_signal_connect (registry, "source-added",
+                         G_CALLBACK (cursor_data_source_added), loop);
+
+       /* Now create a scratch source for our addressbook */
+       scratch = e_source_new_with_uid (CURSOR_DATA_SOURCE_ID, NULL, &error);
+
+       /* Ensure the new ESource will be a local addressbook source */
+       backend = e_source_get_extension (scratch, E_SOURCE_EXTENSION_ADDRESS_BOOK);
+       e_source_backend_set_backend_name (backend, "local");
+
+       /* Setup custom summary fields, so that we can use those fields with the cursor */
+       setup_custom_book (scratch);
+
+       /* Commit the source to the registry */
+       if (!e_source_registry_commit_source_sync (registry, scratch, NULL, &error)) {
+
+               /* It's possible the source already exists if we already ran the example with this data 
server */
+               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
+                       /* If so... then just call our callback early */
+                       ESource *source = e_source_registry_ref_source (registry, CURSOR_DATA_SOURCE_ID);
+
+                       g_clear_error (&error);
+                       g_assert (E_IS_SOURCE (source));
+
+                       /* Run the callback which creates the addressbook client connection */
+                       cursor_data_source_added (registry, source, NULL);
+                       g_object_unref (source);
+               } else
+                       g_error ("Unable to add new addressbook source to the registry: %s", error->message);
+       }
+
+       g_object_unref (scratch);
+
+       if (address_book == NULL) {
+               g_timeout_add (20 * 1000, cursor_data_source_timeout, NULL);
+               g_main_loop_run (loop);
+
+               /* By now we aborted or we have an addressbook created */
+               g_assert (address_book != NULL);
+       }
+
+       /**********************************************************
+        * Ok, done with creating an addressbook, let's add data  *
+        **********************************************************/
+
+       /* First check if there are already some contacts, if so then
+        * avoid adding them again
+        */
+       if (!e_book_client_get_contacts_uids_sync (address_book,
+                                                  "", &contacts, NULL, &error))
+               g_error ("Failed to query addressbook for existing contacts");
+
+       if (contacts != NULL) {
+               /* We already have contacts, no need to add them */
+               g_slist_free_full (contacts, (GDestroyNotify)g_free);
+               contacts = NULL;
+       } else {
+               load_contacts (address_book, vcard_path);
+       }
+
+       /* Addressbook should have contacts now, let's create the cursor */
+       *ret_cursor = get_cursor (address_book);
+
+       /* Cleanup some resources we used to populate the addressbook */
+       g_main_loop_unref (loop);
+       g_object_unref (address_book_source);
+       g_object_unref (registry);
+
+       /* Give the ref through the return value*/
+       ret_book = address_book;
+
+       address_book_source = NULL;
+       address_book = NULL;
+
+       /* Return the addressbook */
+       return ret_book;
+}
+
+static EContact *
+contact_from_file (const gchar *vcard_file)
+{
+       EContact *contact;
+       GError *error;
+       gchar *vcard = NULL;
+
+       if (!g_file_get_contents (vcard_file, &vcard, NULL, &error))
+               g_error ("Failed to load vcard: %s", error->message);
+
+       contact = e_contact_new_from_vcard (vcard);
+       g_free (vcard);
+
+       return contact;
+}
+
+static void
+load_contacts (EBookClient *client,
+              const gchar *vcard_directory)
+{
+       GDir *dir;
+       GError *error = NULL;
+       const gchar *filename;
+       GSList *contacts = NULL;
+
+       dir = g_dir_open (vcard_directory, 0, &error);
+       if (!dir)
+               g_error ("Failed to open vcard directory '%s': %s", vcard_directory, error->message);
+
+       while ((filename = g_dir_read_name (dir)) != NULL) {
+
+               if (g_str_has_suffix (filename, ".vcf")) {
+                       gchar *fullpath = g_build_filename (vcard_directory, filename, NULL);
+                       EContact *contact;
+
+                       g_print ("Loading contact from: %s\n", fullpath);
+
+                       contact = contact_from_file (fullpath);
+                       contacts = g_slist_prepend (contacts, contact);
+
+                       g_free (fullpath);
+               }
+       }
+
+       g_dir_close (dir);
+
+       if (contacts != NULL) {
+
+               if (!e_book_client_add_contacts_sync (client, contacts, NULL, NULL, &error))
+                       g_error ("Failed to add contacts");
+       } else
+               g_error ("No contacts found in vcard directory: %s", vcard_directory);
+}
+
+
+
+static gchar *
+get_addressbook_directory (ESourceRegistry *registry,
+                          ESource         *source)
+{
+       ESource *builtin_source;
+       const gchar *user_data_dir;
+       const gchar *uid;
+       gchar *filename = NULL;
+
+       uid = e_source_get_uid (source);
+       g_return_val_if_fail (uid != NULL, NULL);
+
+       user_data_dir = e_get_user_data_dir ();
+
+       builtin_source = e_source_registry_ref_builtin_address_book (registry);
+
+       /* Special case directory for the builtin addressbook source */
+       if (builtin_source != NULL && e_source_equal (source, builtin_source))
+               uid = "system";
+
+       filename = g_build_filename (user_data_dir, "addressbook", uid, NULL);
+
+       if (builtin_source)
+               g_object_unref (builtin_source);
+
+       return filename;
+}
+
+static EBookClientCursor *
+get_cursor (EBookClient *book_client)
+{
+  EContactField sort_fields[] = { E_CONTACT_FAMILY_NAME, E_CONTACT_GIVEN_NAME };
+  EBookSortType sort_types[] = { E_BOOK_SORT_ASCENDING, E_BOOK_SORT_ASCENDING };
+  EBookClientCursor *cursor = NULL;
+  GError *error = NULL;
+
+  if (!e_book_client_get_cursor_sync (book_client,
+                                     NULL,
+                                     sort_fields,
+                                     sort_types,
+                                     2,
+                                     &cursor,
+                                     NULL,
+                                     &error)) {
+         g_warning ("Unable to create cursor");
+         g_clear_error (&error);
+  }
+
+  return cursor;
+}
diff --git a/tests/cursor-example/cursor-data.h b/tests/cursor-example/cursor-data.h
new file mode 100644
index 0000000..247f775
--- /dev/null
+++ b/tests/cursor-example/cursor-data.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+#ifndef __CURSOR_DATA_H__
+#define __CURSOR_DATA_H__
+
+#include <libebook/libebook.h>
+
+EBookClient *cursor_load_data (const gchar        *vcard_path,
+                              EBookClientCursor **ret_cursor);
+
+#endif /* __CURSOR_DATA_H__ */
diff --git a/tests/cursor-example/cursor-example.c b/tests/cursor-example/cursor-example.c
new file mode 100644
index 0000000..e432ee1
--- /dev/null
+++ b/tests/cursor-example/cursor-example.c
@@ -0,0 +1,684 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+ 
+#include <libebook/libebook.h>
+
+#include "cursor-example.h"
+#include "cursor-navigator.h"
+#include "cursor-search.h"
+#include "cursor-data.h"
+
+#define N_SLOTS         10
+
+#define INITIAL_TIMEOUT 600
+#define TICK_TIMEOUT    100
+
+#define d(x)
+
+typedef enum _TimeoutActivity TimeoutActivity;
+
+/* GObjectClass */
+static void            cursor_example_class_init              (CursorExampleClass *klass);
+static void            cursor_example_init                    (CursorExample      *example);
+static void            cursor_example_dispose                 (GObject            *object);
+
+/* UI Callbacks */
+static gboolean        cursor_example_up_button_press         (CursorExample      *example,
+                                                              GdkEvent           *event,
+                                                              GtkButton          *button);
+static gboolean        cursor_example_up_button_release       (CursorExample      *example,
+                                                              GdkEvent           *event,
+                                                              GtkButton          *button);
+static gboolean        cursor_example_down_button_press       (CursorExample      *example,
+                                                              GdkEvent           *event,
+                                                              GtkButton          *button);
+static gboolean        cursor_example_down_button_release     (CursorExample      *example,
+                                                              GdkEvent           *event,
+                                                              GtkButton          *button);
+static void            cursor_example_navigator_changed       (CursorExample      *example,
+                                                              CursorNavigator    *navigator);
+static void            cursor_example_sexp_changed            (CursorExample      *example,
+                                                              GParamSpec         *pspec,
+                                                              CursorSearch       *search);
+
+/* EDS Callbacks */
+static void            cursor_example_refresh                 (EBookClientCursor  *cursor,
+                                                              CursorExample      *example);
+static void            cursor_example_alphabet_changed        (EBookClientCursor  *cursor,
+                                                              GParamSpec         *pspec,
+                                                              CursorExample      *example);
+static void            cursor_example_status_changed          (EBookClientCursor  *cursor,
+                                                              GParamSpec         *spec,
+                                                              CursorExample      *example);
+
+/* Utilities */
+static void            cursor_example_load_alphabet           (CursorExample      *example);
+static gboolean        cursor_example_move_cursor             (CursorExample      *example,
+                                                              EBookCursorOrigin   origin,
+                                                              gint                count,
+                                                              gboolean            load_page,
+                                                              gboolean           *full_results);
+static void            cursor_example_update_status           (CursorExample      *example);
+static void            cursor_example_update_sensitivity      (CursorExample      *example);
+static void            cursor_example_update_current_index    (CursorExample      *example,
+                                                              EContact           *contact);
+static void            cursor_example_ensure_timeout          (CursorExample      *example,
+                                                              TimeoutActivity     activity);
+static void            cursor_example_cancel_timeout          (CursorExample      *example);
+
+enum _TimeoutActivity {
+       TIMEOUT_NONE = 0,
+       TIMEOUT_UP_INITIAL,
+       TIMEOUT_UP_TICK,
+       TIMEOUT_DOWN_INITIAL,
+       TIMEOUT_DOWN_TICK,
+};
+
+struct _CursorExamplePrivate {
+       /* Screen widgets */
+       GtkWidget *browse_up_button;
+       GtkWidget *browse_down_button;
+       GtkWidget *total_label;
+       GtkWidget *position_label;
+       GtkWidget *alphabet_label;
+       GtkWidget *slots[N_SLOTS];
+       CursorNavigator *navigator;
+
+       /* EDS Resources */
+       EBookClient          *client;
+       EBookClientCursor    *cursor;
+
+       /* Manage the automatic scrolling with button pressed */
+       guint                 timeout_id;
+       TimeoutActivity       activity;
+};
+
+G_DEFINE_TYPE (CursorExample, cursor_example, GTK_TYPE_WINDOW);
+
+/************************************************************************
+ *                          GObjectClass                                *
+ ************************************************************************/
+static void
+cursor_example_class_init (CursorExampleClass *klass)
+{
+       GObjectClass *object_class;
+       GtkWidgetClass *widget_class;
+       gint i;
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->dispose = cursor_example_dispose;
+
+       /* Bind to template */
+       widget_class = GTK_WIDGET_CLASS (klass);
+       gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/evolution/cursor-example/cursor-example.ui");
+
+       gtk_widget_class_bind_child (widget_class, CursorExamplePrivate, navigator);
+       gtk_widget_class_bind_child (widget_class, CursorExamplePrivate, browse_up_button);
+       gtk_widget_class_bind_child (widget_class, CursorExamplePrivate, browse_down_button);
+       gtk_widget_class_bind_child (widget_class, CursorExamplePrivate, alphabet_label);
+       gtk_widget_class_bind_child (widget_class, CursorExamplePrivate, total_label);
+       gtk_widget_class_bind_child (widget_class, CursorExamplePrivate, position_label);
+
+       for (i = 0; i < N_SLOTS; i++) {
+               gchar *name = g_strdup_printf ("contact_slot_%d", i + 1);
+
+               /* XXX Watchout... this GTK+ API will change name soon */
+               gtk_widget_class_automate_child (widget_class, name, FALSE, -1);
+               g_free (name);
+       }
+
+       gtk_widget_class_bind_callback (widget_class, cursor_example_navigator_changed);
+       gtk_widget_class_bind_callback (widget_class, cursor_example_up_button_press);
+       gtk_widget_class_bind_callback (widget_class, cursor_example_up_button_release);
+       gtk_widget_class_bind_callback (widget_class, cursor_example_down_button_press);
+       gtk_widget_class_bind_callback (widget_class, cursor_example_down_button_release);
+       gtk_widget_class_bind_callback (widget_class, cursor_example_sexp_changed);
+
+       g_type_class_add_private (object_class, sizeof (CursorExamplePrivate));
+}
+
+static void
+cursor_example_init (CursorExample *example)
+{
+       CursorExamplePrivate *priv;
+       gint i;
+
+       g_type_ensure (CURSOR_TYPE_NAVIGATOR);
+       g_type_ensure (CURSOR_TYPE_SEARCH);
+
+       example->priv = priv = 
+               G_TYPE_INSTANCE_GET_PRIVATE (example,
+                                CURSOR_TYPE_EXAMPLE,
+                                CursorExamplePrivate);
+
+       gtk_widget_init_template (GTK_WIDGET (example));
+
+       for (i = 0; i < N_SLOTS; i++) {
+
+               /* XXX Watchout... this GTK+ API will change name soon */
+               gchar *name = g_strdup_printf ("contact_slot_%d", i + 1);
+               priv->slots[i] = (GtkWidget *)gtk_widget_get_automated_child (GTK_WIDGET (example),
+                                                                             CURSOR_TYPE_EXAMPLE,
+                                                                             name);
+               g_free (name);
+       }
+}
+
+static void
+cursor_example_dispose (GObject  *object)
+{
+       CursorExample        *example = CURSOR_EXAMPLE (object);
+       CursorExamplePrivate *priv = example->priv;
+
+       cursor_example_cancel_timeout (example);
+
+       if (priv->client) {
+               g_object_unref (priv->client);
+               priv->client = NULL;
+       }
+
+       if (priv->cursor) {
+               g_object_unref (priv->cursor);
+               priv->cursor = NULL;
+       }
+
+       G_OBJECT_CLASS (cursor_example_parent_class)->dispose (object);
+}
+
+/************************************************************************
+ *                           UI Callbacks                               *
+ ************************************************************************/
+static gboolean
+cursor_example_up_button_press (CursorExample *example,
+                               GdkEvent      *event,
+                               GtkButton     *button)
+{
+       d (g_print ("Browse up press\n"));
+
+       /* Move the cursor backwards by 'N_SLOTS + 1' and then refresh the page */
+       if (cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT, 0 - (N_SLOTS + 1), FALSE, 
NULL))
+               cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT, N_SLOTS, TRUE, NULL);
+
+       cursor_example_ensure_timeout (example, TIMEOUT_UP_INITIAL);
+
+       return FALSE;
+}
+
+static gboolean
+cursor_example_up_button_release (CursorExample *example,
+                                 GdkEvent      *event,
+                                 GtkButton     *button)
+{
+       d (g_print ("Browse up release\n"));
+
+       cursor_example_cancel_timeout (example);
+
+       return FALSE;
+}
+
+static gboolean
+cursor_example_down_button_press (CursorExample *example,
+                                 GdkEvent      *event,
+                                 GtkButton     *button)
+{
+       d (g_print ("Browse down press\n"));
+
+       /* Move the cursor backwards by 'N_SLOTS - 1' and then refresh the page */
+       if (cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT, 0 - (N_SLOTS - 1), FALSE, 
NULL))
+               cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT, N_SLOTS, TRUE, NULL);
+
+       cursor_example_ensure_timeout (example, TIMEOUT_DOWN_INITIAL);
+
+       return FALSE;
+}
+
+static gboolean
+cursor_example_down_button_release (CursorExample *example,
+                                   GdkEvent      *event,
+                                   GtkButton     *button)
+{
+       d (g_print ("Browse down released\n"));
+
+       cursor_example_cancel_timeout (example);
+
+       return FALSE;
+}
+
+static void
+cursor_example_navigator_changed (CursorExample      *example,
+                                 CursorNavigator    *navigator)
+{
+       CursorExamplePrivate *priv = example->priv;
+       GError               *error = NULL;
+       gint                  index;
+       gboolean              full_results = FALSE;
+
+       index = cursor_navigator_get_index (priv->navigator);
+
+       d (g_print ("Alphabet index changed to: %d\n", index));
+
+       /* Move to this index */
+       if (!e_book_client_cursor_set_alphabetic_index_sync (priv->cursor, index, NULL, &error)) {
+
+               if (g_error_matches (error,
+                                    E_CLIENT_ERROR,
+                                    E_CLIENT_ERROR_OUT_OF_SYNC)) {
+
+                       /* Just ignore the error.
+                        *
+                        * The addressbook locale has recently changed, very
+                        * soon we will receive an alphabet change notification
+                        * where we will reset the cursor position and reload
+                        * the alphabet.
+                        */
+                       d (g_print ("Cursor was temporarily out of sync while setting the alphabetic 
target\n"));
+               } else
+                       g_warning ("Failed to move the cursor: %s", error->message);
+
+               g_clear_error (&error);
+       }
+
+       /* And load one page full of results starting with this index */
+       if (!cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT,
+                                        N_SLOTS, TRUE, &full_results))
+               return;
+
+       /* If we hit the end of the results (less than a full page) then load the last page of results */
+       if (!full_results) {
+
+               if (cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_RESET,
+                                               0 - (N_SLOTS + 1), FALSE, NULL)) {
+                       cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT,
+                                                   N_SLOTS, TRUE, NULL);
+               }
+       }
+}
+
+static void
+cursor_example_sexp_changed (CursorExample      *example,
+                            GParamSpec         *pspec,
+                            CursorSearch       *search)
+{
+       CursorExamplePrivate *priv = example->priv;
+       gboolean              full_results = FALSE;
+       GError               *error = NULL;
+       const gchar          *sexp;
+
+       sexp = cursor_search_get_sexp (search);
+
+       d (g_print ("Search expression changed to: '%s'\n", sexp));
+
+       /* Set the search expression */
+       if (!e_book_client_cursor_set_sexp_sync (priv->cursor, sexp, NULL, &error)) {
+               g_warning ("Failed to move the cursor: %s", error->message);
+               g_clear_error (&error);
+       }
+
+       /* And re-load one page full of results, load from the previous index */
+       cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_PREVIOUS, N_SLOTS, TRUE, &full_results);
+
+       /* If we hit the end of the results (less than a full page) then load the last page of results */
+       if (!full_results)
+               if (cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_RESET,
+                                               0 - (N_SLOTS + 1), FALSE, NULL)) {
+                       cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT,
+                                                   N_SLOTS, TRUE, NULL);
+       }
+}
+
+/************************************************************************
+ *                           EDS Callbacks                              *
+ ************************************************************************/
+static void
+cursor_example_refresh (EBookClientCursor  *cursor,
+                       CursorExample      *example)
+{
+       d (g_print ("Cursor refreshed\n"));
+
+       /* Repeat our last query, by requesting that we move from the previous origin */
+       cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_PREVIOUS, N_SLOTS, TRUE, NULL);
+
+       cursor_example_update_status (example);
+}
+
+static void
+cursor_example_alphabet_changed (EBookClientCursor *cursor,
+                                GParamSpec        *spec,
+                                CursorExample     *example)
+{
+       d (g_print ("Alphabet Changed\n"));
+
+       cursor_example_load_alphabet (example);
+
+       /* Get the first page of contacts in the addressbook */
+       cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_RESET, N_SLOTS, TRUE, NULL);
+}
+
+static void
+cursor_example_status_changed (EBookClientCursor *cursor,
+                              GParamSpec        *spec,
+                              CursorExample     *example)
+{
+       d (g_print ("Status changed\n"));
+
+       cursor_example_update_status (example);
+}
+
+/************************************************************************
+ *                             Utilities                                *
+ ************************************************************************/
+static void
+cursor_example_load_alphabet (CursorExample *example)
+{
+       CursorExamplePrivate *priv = example->priv;
+       const gchar *const   *alphabet;
+
+       /* Update the alphabet on the navigator */
+       alphabet = e_book_client_cursor_get_alphabet (priv->cursor, NULL, NULL, NULL, NULL);
+       cursor_navigator_set_alphabet (priv->navigator, alphabet);
+
+       /* Reset navigator to the beginning */
+       g_signal_handlers_block_by_func (priv->navigator, cursor_example_navigator_changed, example);
+       cursor_navigator_set_index (priv->navigator, 0);
+       g_signal_handlers_unblock_by_func (priv->navigator, cursor_example_navigator_changed, example);
+}
+
+static void
+cursor_example_update_view (CursorExample     *example,
+                           GSList            *results)
+{
+       CursorExamplePrivate *priv = example->priv;
+       EContact             *contact;
+       gint                  i;
+
+       /* Fill the page with results for the current cursor position
+        */
+       for (i = 0; i < N_SLOTS; i++) {
+               contact = g_slist_nth_data (results, i);
+
+               /* For the first contact, give some visual feedback about where we
+                * are in the list, which alphabet character we're browsing right now.
+                */
+               if (i == 0 && contact)
+                       cursor_example_update_current_index (example, contact);
+
+               cursor_slot_set_from_contact (priv->slots[i], contact);
+       }
+}
+
+/* Returns whether a full page was loaded or if we reached 
+ * the end of the results
+ */
+static gboolean
+cursor_example_move_cursor (CursorExample     *example,
+                           EBookCursorOrigin  origin,
+                           gint               count,
+                           gboolean           load_page,
+                           gboolean          *full_results)
+{
+       CursorExamplePrivate *priv = example->priv;
+       GError               *error = NULL;
+       GSList               *results = NULL, **results_arg = NULL;
+       gboolean              success;
+
+       /* We don't ask EDS for results if we're not going to load the view */
+       g_assert (load_page == TRUE || full_results == NULL);
+
+       /* Only fetch results if we will actually display them */
+       if (load_page)
+               results_arg = &results;
+
+       success = e_book_client_cursor_move_by_sync (priv->cursor,
+                                                    origin,
+                                                    count,
+                                                    results_arg,
+                                                    NULL, &error);
+
+       if (!success) {
+
+               if (g_error_matches (error,
+                                    E_CLIENT_ERROR,
+                                    E_CLIENT_ERROR_OUT_OF_SYNC)) {
+
+                       /* Just ignore this error.
+                        *
+                        * The addressbook has very recently been modified,
+                        * very soon we will receive a "refresh" signal and
+                        * automatically reload the current page position.
+                        */
+                       d (g_print ("Cursor was temporarily out of sync while moving\n"));
+
+               } else
+                       g_warning ("Failed to move the cursor: %s", error->message);
+
+               g_clear_error (&error);
+
+       } else if (load_page) {
+
+               /* Display the results */
+               cursor_example_update_view (example, results);
+       }
+
+       if (full_results)
+               *full_results = g_slist_length (results) == ABS (count);
+
+       g_slist_free_full (results, (GDestroyNotify)g_object_unref);
+
+       return success;
+}
+
+static void
+cursor_example_update_status (CursorExample *example)
+{
+       CursorExamplePrivate *priv = example->priv;
+       GError               *error = NULL;
+       gint                  total, position;
+       gchar                *txt;
+       gboolean              up_sensitive;
+       gboolean              down_sensitive;
+
+       total = e_book_client_cursor_get_total (priv->cursor);
+       position = e_book_client_cursor_get_position (priv->cursor);
+
+       /* Update total indicator label */
+       txt = g_strdup_printf ("%d", total);
+       gtk_label_set_text (GTK_LABEL (priv->total_label), txt);
+       g_free (txt);
+
+       /* Update position indicator label */
+       txt = g_strdup_printf ("%d", position);
+       gtk_label_set_text (GTK_LABEL (priv->position_label), txt);
+       g_free (txt);
+
+       /* Update sensitivity of buttons */
+       if (total <= N_SLOTS) {
+               /* If the amount of contacts is less than the amount of visual slots,
+                * then we cannot browse up and down
+                */
+               up_sensitive = FALSE;
+               down_sensitive = FALSE;
+       } else {
+               /* The cursor is always pointing to the last contact
+                * visible in the view, so if the cursor is further
+                * than N_SLOTS we can rewind.
+                */
+               up_sensitive = (position > N_SLOTS);
+
+               /* So long as we have not reached the last contact, we can move
+                * the cursor down through the list */
+               down_sensitive = (position < total);
+       }
+
+       gtk_widget_set_sensitive (priv->browse_up_button, up_sensitive);
+       gtk_widget_set_sensitive (priv->browse_down_button, down_sensitive);
+}
+
+/* This is called when refreshing the window contents with
+ * the first contact shown in the window.
+ */
+static void
+cursor_example_update_current_index (CursorExample *example,
+                                    EContact      *contact)
+{
+       CursorExamplePrivate *priv = example->priv;
+       const gchar *const   *labels;
+       gint                  index;
+
+       /* Fetch the alphabetic index for this contact */
+       index = e_book_client_cursor_get_contact_alpabetic_index (priv->cursor, contact);
+
+       /* Refresh the current alphabet index indicator.
+        *
+        * The index returned by e_book_client_cursor_get_contact_alpabetic_index() is
+        * a valid position into the array returned by e_book_client_cursor_get_alphabet().
+        */
+       labels = e_book_client_cursor_get_alphabet (priv->cursor, NULL, NULL, NULL, NULL);
+       gtk_label_set_text (GTK_LABEL (priv->alphabet_label), labels[index]);
+
+       /* Update the current scroll position (and avoid reacting to the value change)
+        */
+       if (contact) {
+               g_signal_handlers_block_by_func (priv->navigator, cursor_example_navigator_changed, example);
+               cursor_navigator_set_index (priv->navigator, index);
+               g_signal_handlers_unblock_by_func (priv->navigator, cursor_example_navigator_changed, 
example);
+       }
+}
+
+static gboolean
+cursor_example_timeout (CursorExample *example)
+{
+       CursorExamplePrivate *priv = example->priv;
+       gboolean reschedule = FALSE;
+
+       switch (priv->activity) {
+       case TIMEOUT_NONE:
+               break;
+
+       case TIMEOUT_UP_INITIAL:
+               cursor_example_ensure_timeout (example, TIMEOUT_UP_TICK);
+               break;
+       case TIMEOUT_DOWN_INITIAL:
+               cursor_example_ensure_timeout (example, TIMEOUT_DOWN_TICK);
+               break;
+
+       case TIMEOUT_UP_TICK:
+
+               /* Move the cursor backwards by 'N_SLOTS + 1' and then refresh the page */
+               if (gtk_widget_get_sensitive (priv->browse_up_button) &&
+                   cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT, 0 - (N_SLOTS + 1), 
FALSE, NULL)) {
+
+                       if (gtk_widget_get_sensitive (priv->browse_up_button) &&
+                           cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT, N_SLOTS, TRUE, 
NULL))
+                               reschedule = TRUE;
+               }
+
+               if (!reschedule)
+                       cursor_example_cancel_timeout (example);
+
+               break;
+
+       case TIMEOUT_DOWN_TICK:
+
+               /* Move the cursor backwards by 'N_SLOTS - 1' and then refresh the page */
+               if (gtk_widget_get_sensitive (priv->browse_down_button) &&
+                   cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT, 0 - (N_SLOTS - 1), 
FALSE, NULL)) {
+
+                       if (gtk_widget_get_sensitive (priv->browse_down_button) &&
+                           cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_CURRENT, N_SLOTS, TRUE, 
NULL))
+                               reschedule = TRUE;
+               }
+
+               if (!reschedule)
+                       cursor_example_cancel_timeout (example);
+
+               break;
+       }
+
+       return reschedule;
+}
+
+static void
+cursor_example_ensure_timeout (CursorExample      *example,
+                              TimeoutActivity     activity)
+{
+       CursorExamplePrivate *priv = example->priv;
+       guint                 timeout = 0;
+
+       cursor_example_cancel_timeout (example);
+
+       if (activity == TIMEOUT_UP_INITIAL ||
+           activity == TIMEOUT_DOWN_INITIAL)
+               timeout = INITIAL_TIMEOUT;
+       else
+               timeout = TICK_TIMEOUT;
+
+       priv->activity = activity;
+
+       priv->timeout_id =
+               g_timeout_add (timeout,
+                              (GSourceFunc)cursor_example_timeout,
+                              example);
+}
+
+static void
+cursor_example_cancel_timeout (CursorExample *example)
+{
+       CursorExamplePrivate *priv = example->priv;
+
+       if (priv->timeout_id) {
+               g_source_remove (priv->timeout_id);
+               priv->timeout_id = 0;
+       }
+}
+
+/************************************************************************
+ *                                API                                   *
+ ************************************************************************/
+GtkWidget *
+cursor_example_new (const gchar *vcard_path)
+{
+  CursorExample *example;
+  CursorExamplePrivate *priv;
+
+  example = g_object_new (CURSOR_TYPE_EXAMPLE, NULL);
+  priv    = example->priv;
+
+  priv->client = cursor_load_data (vcard_path, &priv->cursor);
+
+  cursor_example_load_alphabet (example);
+
+  cursor_example_move_cursor (example, E_BOOK_CURSOR_ORIGIN_RESET, N_SLOTS, TRUE, NULL);
+  cursor_example_update_status (example);
+
+  g_signal_connect (priv->cursor, "refresh",
+                   G_CALLBACK (cursor_example_refresh), example);
+  g_signal_connect (priv->cursor, "notify::alphabet",
+                   G_CALLBACK (cursor_example_alphabet_changed), example);
+  g_signal_connect (priv->cursor, "notify::total",
+                   G_CALLBACK (cursor_example_status_changed), example);
+  g_signal_connect (priv->cursor, "notify::position",
+                   G_CALLBACK (cursor_example_status_changed), example);
+
+  g_message ("Cursor example started in locale: %s",
+            e_book_client_get_locale (priv->client));
+
+  return (GtkWidget *)example;
+}
diff --git a/tests/cursor-example/cursor-example.gresources.xml 
b/tests/cursor-example/cursor-example.gresources.xml
new file mode 100644
index 0000000..03c4901
--- /dev/null
+++ b/tests/cursor-example/cursor-example.gresources.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/evolution/cursor-example">
+    <file compressed="true" preprocess="xml-stripblanks">cursor-example.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">cursor-search.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">cursor-slot.ui</file>
+  </gresource>
+</gresources>
diff --git a/tests/cursor-example/cursor-example.h b/tests/cursor-example/cursor-example.h
new file mode 100644
index 0000000..7807c14
--- /dev/null
+++ b/tests/cursor-example/cursor-example.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+ 
+#ifndef __CURSOR_EXAMPLE_H__
+#define __CURSOR_EXAMPLE_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CURSOR_TYPE_EXAMPLE            (cursor_example_get_type())
+#define CURSOR_EXAMPLE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CURSOR_TYPE_EXAMPLE, 
CursorExample))
+#define CURSOR_EXAMPLE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CURSOR_TYPE_EXAMPLE, 
CursorExampleClass))
+#define CURSOR_IS_EXAMPLE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CURSOR_TYPE_EXAMPLE))
+#define CURSOR_IS_EXAMPLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CURSOR_TYPE_EXAMPLE))
+#define CURSOR_EXAMPLE_GET_CLASS(o)    (G_TYPE_INSTANCE_GET_CLASS ((o), CURSOR_EXAMPLE, CursorExampleClass))
+
+typedef struct _CursorExample         CursorExample;
+typedef struct _CursorExamplePrivate  CursorExamplePrivate;
+typedef struct _CursorExampleClass    CursorExampleClass;
+
+struct _CursorExample
+{
+       GtkWindow parent_instance;
+
+       CursorExamplePrivate *priv;
+};
+
+struct _CursorExampleClass
+{
+       GtkWindowClass parent_class;
+};
+
+GType       cursor_example_get_type (void) G_GNUC_CONST;
+GtkWidget  *cursor_example_new      (const gchar   *vcard_path);
+
+G_END_DECLS
+
+#endif /* __CURSOR_EXAMPLE_H__ */
diff --git a/tests/cursor-example/cursor-example.ui b/tests/cursor-example/cursor-example.ui
new file mode 100644
index 0000000..5120f91
--- /dev/null
+++ b/tests/cursor-example/cursor-example.ui
@@ -0,0 +1,658 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.10 -->
+  <!-- interface-requires cursor 0.0 -->
+  <template class="CursorExample" parent="GtkWindow">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Cursor Example Program</property>
+    <property name="default_width">600</property>
+    <property name="default_height">300</property>
+    <child>
+      <object class="GtkBox" id="box1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="border_width">8</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkBox" id="box3">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">2</property>
+            <child>
+              <object class="GtkGrid" id="grid1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="hexpand">False</property>
+                <child>
+                  <object class="GtkBox" id="box4">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="margin_right">6</property>
+                    <property name="hexpand">True</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="CursorSlot" id="contact_slot_1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="CursorSlot" id="contact_slot_2">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="CursorSlot" id="contact_slot_3">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="CursorSlot" id="contact_slot_4">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="CursorSlot" id="contact_slot_5">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">4</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="CursorSlot" id="contact_slot_6">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">5</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="CursorSlot" id="contact_slot_7">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">6</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="CursorSlot" id="contact_slot_8">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">7</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="CursorSlot" id="contact_slot_9">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">8</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="CursorSlot" id="contact_slot_10">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">9</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">1</property>
+                    <property name="width">3</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="CursorNavigator" id="navigator">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hexpand">False</property>
+                    <property name="vexpand">True</property>
+                    <property name="orientation">vertical</property>
+                    <property name="draw_value">False</property>
+                    <property name="has_origin">False</property>
+                    <signal name="index-changed" handler="cursor_example_navigator_changed" swapped="yes"/>
+                  </object>
+                  <packing>
+                    <property name="left_attach">3</property>
+                    <property name="top_attach">1</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="browse_up_button">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="halign">center</property>
+                    <property name="valign">end</property>
+                    <property name="hexpand">False</property>
+                    <property name="relief">none</property>
+                    <signal name="button-press-event" handler="cursor_example_up_button_press" 
swapped="yes"/>
+                    <signal name="button-release-event" handler="cursor_example_up_button_release" 
swapped="yes"/>
+                    <child>
+                      <object class="GtkArrow" id="arrow1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="arrow_type">up</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="left_attach">3</property>
+                    <property name="top_attach">0</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="browse_down_button">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="halign">center</property>
+                    <property name="valign">start</property>
+                    <property name="hexpand">False</property>
+                    <property name="relief">none</property>
+                    <signal name="button-press-event" handler="cursor_example_down_button_press" 
swapped="yes"/>
+                    <signal name="button-release-event" handler="cursor_example_down_button_release" 
swapped="yes"/>
+                    <child>
+                      <object class="GtkArrow" id="arrow2">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="arrow_type">down</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="left_attach">3</property>
+                    <property name="top_attach">2</property>
+                    <property name="width">1</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="box2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkLabel" id="alphabet_label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="hexpand">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">A</property>
+                        <property name="width_chars">3</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                          <attribute name="scale" value="1.5"/>
+                        </attributes>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="CursorSearch" id="search">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="margin_left">6</property>
+                        <property name="margin_right">6</property>
+                        <property name="margin_top">6</property>
+                        <property name="margin_bottom">6</property>
+                        <property name="hexpand">True</property>
+                        <signal name="notify::sexp" handler="cursor_example_sexp_changed" swapped="yes"/>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">0</property>
+                    <property name="width">3</property>
+                    <property name="height">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkFrame" id="frame1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label_xalign">0</property>
+            <property name="shadow_type">in</property>
+            <child>
+              <object class="GtkBox" id="box7">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkLabel" id="label5">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">end</property>
+                    <property name="label" translatable="yes">Total:</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="total_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">0</property>
+                    <property name="width_chars">4</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label6">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">end</property>
+                    <property name="label" translatable="yes">Position:</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="position_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">0</property>
+                    <property name="width_chars">4</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">4</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+            <child type="label_item">
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/tests/cursor-example/cursor-navigator.c b/tests/cursor-example/cursor-navigator.c
new file mode 100644
index 0000000..6ae2d90
--- /dev/null
+++ b/tests/cursor-example/cursor-navigator.c
@@ -0,0 +1,261 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+ 
+#include "cursor-navigator.h"
+
+/* GObjectClass */
+static void            cursor_navigator_class_init      (CursorNavigatorClass *klass);
+static void            cursor_navigator_init            (CursorNavigator      *navigator);
+static void            cursor_navigator_constructed     (GObject              *object);
+static void            cursor_navigator_finalize        (GObject              *object);
+
+/* GtkScaleClass */
+static gchar          *cursor_navigator_format_value    (GtkScale             *scale,
+                                                        gdouble               value);
+
+static void            cursor_navigator_changed         (GtkAdjustment        *adj,
+                                                        GParamSpec           *pspec,
+                                                        CursorNavigator      *navigator);
+
+
+struct _CursorNavigatorPrivate {
+       gchar **alphabet;
+       gint    letters;
+       gint    index;
+};
+
+
+enum {
+       INDEX_CHANGED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+enum {
+       PROP_0,
+       PROP_INDEX,
+};
+
+G_DEFINE_TYPE (CursorNavigator, cursor_navigator, GTK_TYPE_SCALE);
+
+/************************************************************************
+ *                          GObjectClass                                *
+ ************************************************************************/
+static void
+cursor_navigator_class_init (CursorNavigatorClass *klass)
+{
+       GObjectClass       *object_class;
+       GtkScaleClass      *scale_class;
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->constructed = cursor_navigator_constructed;
+       object_class->finalize = cursor_navigator_finalize;
+
+       scale_class = GTK_SCALE_CLASS (klass);
+       scale_class->format_value = cursor_navigator_format_value;
+
+       signals[INDEX_CHANGED] = g_signal_new ("index-changed",
+                                              G_OBJECT_CLASS_TYPE (object_class),
+                                              G_SIGNAL_RUN_LAST,
+                                              0, NULL, NULL, NULL,
+                                              G_TYPE_NONE, 0);
+
+       g_type_class_add_private (object_class, sizeof (CursorNavigatorPrivate));
+}
+
+static void
+cursor_navigator_init (CursorNavigator *navigator)
+{
+       CursorNavigatorPrivate *priv;
+
+       navigator->priv = priv =
+               G_TYPE_INSTANCE_GET_PRIVATE (navigator,
+                                            CURSOR_TYPE_NAVIGATOR,
+                                            CursorNavigatorPrivate);
+
+       priv->letters = -1;
+}
+
+static void
+cursor_navigator_constructed (GObject *object)
+{
+       CursorNavigator        *navigator = CURSOR_NAVIGATOR (object);
+       CursorNavigatorPrivate *priv = navigator->priv;
+       GtkAdjustment          *adj = NULL;
+
+       G_OBJECT_CLASS (cursor_navigator_parent_class)->constructed (object);
+
+       adj = gtk_adjustment_new (0.0F, 0.0F, 1.0F, 1.0F, 1.0F, 0.0F);
+       gtk_range_set_adjustment (GTK_RANGE (navigator), adj);
+
+       g_signal_connect (adj, "notify::value",
+                         G_CALLBACK (cursor_navigator_changed), navigator);
+}
+
+static void
+cursor_navigator_finalize (GObject  *object)
+{
+       CursorNavigator        *navigator = CURSOR_NAVIGATOR (object);
+       CursorNavigatorPrivate *priv = navigator->priv;
+
+       g_strfreev (priv->alphabet);
+
+       G_OBJECT_CLASS (cursor_navigator_parent_class)->finalize (object);
+}
+
+/************************************************************************
+ *                          GtkScaleClass                               *
+ ************************************************************************/
+static gchar *
+cursor_navigator_format_value (GtkScale  *scale,
+                              gdouble    value)
+{
+       CursorNavigator        *navigator = CURSOR_NAVIGATOR (scale);
+       CursorNavigatorPrivate *priv = navigator->priv;
+       gint                    index;
+
+       if (priv->letters < 0)
+               return NULL;
+
+       index = CLAMP ((gint)value, 0, priv->letters - 1);
+
+       /* Return the letter for the gvoidiven value
+        */
+       return g_strdup (priv->alphabet[index]);
+}
+
+static void
+cursor_navigator_changed (GtkAdjustment        *adj,
+                         GParamSpec           *pspec,
+                         CursorNavigator      *navigator)
+{
+       gint index = gtk_adjustment_get_value (adj);
+
+       cursor_navigator_set_index (navigator, index);
+}
+
+/************************************************************************
+ *                                API                                   *
+ ************************************************************************/
+CursorNavigator *
+cursor_navigator_new (void)
+{
+       return g_object_new (CURSOR_TYPE_NAVIGATOR, NULL);
+}
+
+static void
+cursor_navigator_update_parameters (CursorNavigator     *navigator)
+{
+       CursorNavigatorPrivate *priv = navigator->priv;
+       GtkScale               *scale = GTK_SCALE (navigator);
+       GtkAdjustment          *adj;
+       gint                    i;
+
+       gtk_scale_clear_marks (scale);
+       for (i = 0; i < priv->letters; i++) {
+               gchar *letter;
+
+               letter = g_strdup_printf ("<span size=\"x-small\">%s</span>", priv->alphabet[i]);
+
+               gtk_scale_add_mark (scale, i, GTK_POS_LEFT, letter);
+               g_free (letter);
+       }
+
+       adj = gtk_range_get_adjustment (GTK_RANGE (navigator));
+
+       gtk_adjustment_set_upper (adj, priv->letters - 1);
+}
+
+void
+cursor_navigator_set_alphabet (CursorNavigator     *navigator,
+                              const gchar * const *alphabet)
+{
+       CursorNavigatorPrivate *priv;
+
+       g_return_if_fail (CURSOR_IS_NAVIGATOR (navigator));
+       g_return_if_fail (alphabet == NULL ||
+                         g_strv_length ((gchar **)alphabet) > 0);
+
+       priv = navigator->priv;
+
+       g_free (priv->alphabet);
+       if (alphabet) {
+               priv->alphabet = g_strdupv ((gchar **)alphabet);
+               priv->letters = g_strv_length ((gchar **)alphabet);
+       } else {
+               priv->alphabet = NULL;
+               priv->letters = -1;
+       }
+
+       cursor_navigator_update_parameters (navigator);
+       cursor_navigator_set_index (navigator, 0);
+}
+
+const gchar * const *
+cursor_navigator_get_alphabet (CursorNavigator *navigator)
+{
+       CursorNavigatorPrivate *priv;
+
+       g_return_val_if_fail (CURSOR_IS_NAVIGATOR (navigator), NULL);
+
+       priv = navigator->priv;
+
+       return (const gchar * const *)priv->alphabet;
+}
+
+void
+cursor_navigator_set_index (CursorNavigator     *navigator,
+                           gint                 index)
+{
+       CursorNavigatorPrivate *priv;
+       GtkAdjustment          *adj;
+
+       g_return_if_fail (CURSOR_IS_NAVIGATOR (navigator));
+
+       priv = navigator->priv;
+       adj  = gtk_range_get_adjustment (GTK_RANGE (navigator));
+
+       index = CLAMP (index, 0, priv->letters);
+
+       if (priv->index != index) {
+
+               priv->index = index;
+
+               g_signal_emit (navigator, signals[INDEX_CHANGED], 0);
+
+               g_signal_handlers_block_by_func (adj, cursor_navigator_changed, navigator);
+               gtk_adjustment_set_value (adj, priv->index);
+               g_signal_handlers_unblock_by_func (adj, cursor_navigator_changed, navigator);
+       }
+}
+
+gint
+cursor_navigator_get_index (CursorNavigator     *navigator)
+{
+       CursorNavigatorPrivate *priv;
+
+       g_return_val_if_fail (CURSOR_IS_NAVIGATOR (navigator), 0);
+
+       priv = navigator->priv;
+
+       return priv->index;
+}
diff --git a/tests/cursor-example/cursor-navigator.h b/tests/cursor-example/cursor-navigator.h
new file mode 100644
index 0000000..494e111
--- /dev/null
+++ b/tests/cursor-example/cursor-navigator.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+ 
+#ifndef __CURSOR_NAVIGATOR_H__
+#define __CURSOR_NAVIGATOR_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CURSOR_TYPE_NAVIGATOR            (cursor_navigator_get_type())
+#define CURSOR_NAVIGATOR(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CURSOR_TYPE_NAVIGATOR, 
CursorNavigator))
+#define CURSOR_NAVIGATOR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CURSOR_TYPE_NAVIGATOR, 
CursorNavigatorClass))
+#define CURSOR_IS_NAVIGATOR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CURSOR_TYPE_NAVIGATOR))
+#define CURSOR_IS_NAVIGATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CURSOR_TYPE_NAVIGATOR))
+#define CURSOR_NAVIGATOR_GET_CLASS(o)    (G_TYPE_INSTANCE_GET_CLASS ((o), CURSOR_NAVIGATOR, 
CursorNavigatorClass))
+
+typedef struct _CursorNavigator         CursorNavigator;
+typedef struct _CursorNavigatorPrivate  CursorNavigatorPrivate;
+typedef struct _CursorNavigatorClass    CursorNavigatorClass;
+
+struct _CursorNavigator
+{
+       GtkScale parent_instance;
+
+       CursorNavigatorPrivate *priv;
+};
+
+struct _CursorNavigatorClass
+{
+       GtkScaleClass parent_class;
+};
+
+GType                cursor_navigator_get_type        (void) G_GNUC_CONST;
+CursorNavigator     *cursor_navigator_new             (void);
+void                 cursor_navigator_set_alphabet    (CursorNavigator     *navigator,
+                                                      const gchar * const *alphabet);
+const gchar * const *cursor_navigator_get_alphabet    (CursorNavigator     *navigator);
+void                 cursor_navigator_set_index       (CursorNavigator     *navigator,
+                                                      gint                 index);
+gint                 cursor_navigator_get_index       (CursorNavigator     *navigator);
+
+G_END_DECLS
+
+#endif /* __CURSOR_NAVIGATOR_H__ */
diff --git a/tests/cursor-example/cursor-search.c b/tests/cursor-example/cursor-search.c
new file mode 100644
index 0000000..9e8358d
--- /dev/null
+++ b/tests/cursor-example/cursor-search.c
@@ -0,0 +1,262 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+ 
+#include <libebook/libebook.h>
+
+#include "cursor-search.h"
+
+/* GObjectClass */
+static void  cursor_search_class_init     (CursorSearchClass *klass);
+static void  cursor_search_init           (CursorSearch      *search);
+static void  cursor_search_finalize       (GObject           *object);
+static void  cursor_search_get_property   (GObject           *object,
+                                          guint              property_id,
+                                          GValue            *value,
+                                          GParamSpec        *pspec);
+
+/* UI Callbacks */
+static void  cursor_search_option_toggled (CursorSearch         *search,
+                                          GtkWidget            *item);
+static void  cursor_search_entry_changed  (CursorSearch         *search,
+                                          GtkEditable          *entry);
+static void  cursor_search_icon_press     (CursorSearch         *search,
+                                          GtkEntryIconPosition  icon_pos,
+                                          GdkEvent             *event,
+                                          GtkEntry             *entry);
+
+typedef enum {
+       SEARCH_NAME,
+       SEARCH_PHONE,
+       SEARCH_EMAIL
+} SearchType;
+
+struct _CursorSearchPrivate {
+       GtkWidget   *popup;
+       GtkWidget   *name_radio;
+       GtkWidget   *phone_radio;
+       GtkWidget   *email_radio;
+
+       SearchType   type;
+       gchar       *sexp;
+};
+
+enum {
+       PROP_0,
+       PROP_SEXP,
+};
+
+G_DEFINE_TYPE (CursorSearch, cursor_search, GTK_TYPE_SEARCH_ENTRY);
+
+/************************************************************************
+ *                          GObjectClass                                *
+ ************************************************************************/
+static void
+cursor_search_class_init (CursorSearchClass *klass)
+{
+       GObjectClass *object_class;
+       GtkWidgetClass *widget_class;
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = cursor_search_finalize;
+       object_class->get_property = cursor_search_get_property;
+
+
+       g_object_class_install_property (
+               object_class,
+               PROP_SEXP,
+               g_param_spec_string (
+                       "sexp",
+                       "Search Expression",
+                       "The active search expression",
+                       NULL,
+                       G_PARAM_READABLE |
+                       G_PARAM_STATIC_STRINGS));
+
+       /* Bind to template */
+       widget_class = GTK_WIDGET_CLASS (klass);
+       gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/evolution/cursor-example/cursor-search.ui");
+       gtk_widget_class_bind_child (widget_class, CursorSearchPrivate, popup);
+       gtk_widget_class_bind_child (widget_class, CursorSearchPrivate, name_radio);
+       gtk_widget_class_bind_child (widget_class, CursorSearchPrivate, phone_radio);
+       gtk_widget_class_bind_child (widget_class, CursorSearchPrivate, email_radio);
+       gtk_widget_class_bind_callback (widget_class, cursor_search_option_toggled);
+       gtk_widget_class_bind_callback (widget_class, cursor_search_entry_changed);
+       gtk_widget_class_bind_callback (widget_class, cursor_search_icon_press);
+
+       g_type_class_add_private (object_class, sizeof (CursorSearchPrivate));
+}
+
+static void
+cursor_search_init (CursorSearch *search)
+{
+       CursorSearchPrivate *priv;
+
+       search->priv = priv = 
+               G_TYPE_INSTANCE_GET_PRIVATE (search,
+                                CURSOR_TYPE_SEARCH,
+                                CursorSearchPrivate);
+
+       gtk_widget_init_template (GTK_WIDGET (search));
+
+       g_object_set (search,
+                     "primary-icon-activatable", TRUE,
+                     "primary-icon-sensitive", TRUE,
+                     NULL);
+}
+
+static void
+cursor_search_finalize (GObject *object)
+{
+       CursorSearch        *search = CURSOR_SEARCH (object);
+       CursorSearchPrivate *priv = search->priv;
+
+       g_free (priv->sexp);
+
+       G_OBJECT_CLASS (cursor_search_parent_class)->finalize (object);
+}
+
+static void
+cursor_search_get_property (GObject           *object,
+                           guint              property_id,
+                           GValue            *value,
+                           GParamSpec        *pspec)
+{
+       CursorSearch        *search = CURSOR_SEARCH (object);
+       CursorSearchPrivate *priv = search->priv;
+
+       switch (property_id) {
+       case PROP_SEXP:
+               g_value_set_string (value, priv->sexp);
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+
+       }
+}
+
+
+/************************************************************************
+ *                              UI Callbacks                            *
+ ************************************************************************/
+static void
+cursor_search_option_toggled (CursorSearch         *search,
+                             GtkWidget            *item)
+{
+       CursorSearchPrivate *priv = search->priv;
+
+       if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item))) {
+
+               if (item == priv->name_radio)
+                       priv->type = SEARCH_NAME;
+               else if (item == priv->phone_radio)
+                       priv->type = SEARCH_PHONE;
+               else if (item == priv->email_radio)
+                       priv->type = SEARCH_EMAIL;
+       
+               /* Refresh the search */
+               cursor_search_entry_changed (search, NULL);
+       }
+}
+
+static void
+cursor_search_entry_changed (CursorSearch         *search,
+                            GtkEditable          *entry)
+{
+       CursorSearchPrivate *priv = search->priv;
+       EBookQuery  *query = NULL;
+       const gchar *text;
+
+       text = gtk_entry_get_text (GTK_ENTRY (search));
+
+       if (text && text[0]) {
+               switch (priv->type) {
+               case SEARCH_NAME:
+                       query = e_book_query_orv (
+                               e_book_query_field_test (E_CONTACT_FAMILY_NAME,
+                                                        E_BOOK_QUERY_CONTAINS,
+                                                        text),
+                               e_book_query_field_test (E_CONTACT_GIVEN_NAME,
+                                                        E_BOOK_QUERY_CONTAINS,
+                                                        text),
+                               NULL);
+                       break;
+               case SEARCH_PHONE:
+                       query = e_book_query_field_test (E_CONTACT_TEL,
+                                                        E_BOOK_QUERY_CONTAINS,
+                                                        text);
+                       break;
+               case SEARCH_EMAIL:
+                       query = e_book_query_field_test (E_CONTACT_EMAIL,
+                                                        E_BOOK_QUERY_CONTAINS,
+                                                        text);
+                       break;
+               }
+       }
+
+       g_free (priv->sexp);
+
+       if (query) {
+               priv->sexp = e_book_query_to_string (query);
+               e_book_query_unref (query);
+       } else
+               priv->sexp = g_strdup ("");
+
+       g_object_notify (G_OBJECT (search), "sexp");
+}
+
+static void
+cursor_search_icon_press (CursorSearch         *search,
+                         GtkEntryIconPosition  icon_pos,
+                         GdkEvent             *event,
+                         GtkEntry             *entry)
+{
+       CursorSearchPrivate *priv = search->priv;
+       GdkEventButton *button_event = (GdkEventButton *)event;
+
+       if (icon_pos == GTK_ENTRY_ICON_PRIMARY)
+               gtk_menu_popup (GTK_MENU (priv->popup),
+                               NULL, NULL, NULL, NULL,
+                               button_event->button,
+                               button_event->time);
+}
+
+/************************************************************************
+ *                                API                                   *
+ ************************************************************************/
+GtkWidget *
+cursor_search_new (void)
+{
+  return (GtkWidget *)g_object_new (CURSOR_TYPE_SEARCH, NULL);
+}
+
+const gchar *
+cursor_search_get_sexp (CursorSearch *search)
+{
+       CursorSearchPrivate *priv;
+
+       g_return_val_if_fail (CURSOR_IS_SEARCH (search), NULL);
+
+       priv = search->priv;
+
+       return priv->sexp;
+}
diff --git a/tests/cursor-example/cursor-search.h b/tests/cursor-example/cursor-search.h
new file mode 100644
index 0000000..496f16d
--- /dev/null
+++ b/tests/cursor-example/cursor-search.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+ 
+#ifndef __CURSOR_SEARCH_H__
+#define __CURSOR_SEARCH_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CURSOR_TYPE_SEARCH            (cursor_search_get_type())
+#define CURSOR_SEARCH(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CURSOR_TYPE_SEARCH, CursorSearch))
+#define CURSOR_SEARCH_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CURSOR_TYPE_SEARCH, 
CursorSearchClass))
+#define CURSOR_IS_SEARCH(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CURSOR_TYPE_SEARCH))
+#define CURSOR_IS_SEARCH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CURSOR_TYPE_SEARCH))
+#define CURSOR_SEARCH_GET_CLASS(o)    (G_TYPE_INSTANCE_GET_CLASS ((o), CURSOR_SEARCH, CursorSearchClass))
+
+typedef struct _CursorSearch         CursorSearch;
+typedef struct _CursorSearchPrivate  CursorSearchPrivate;
+typedef struct _CursorSearchClass    CursorSearchClass;
+
+struct _CursorSearch
+{
+       GtkSearchEntry parent_instance;
+
+       CursorSearchPrivate *priv;
+};
+
+struct _CursorSearchClass
+{
+       GtkSearchEntryClass parent_class;
+};
+
+GType        cursor_search_get_type  (void) G_GNUC_CONST;
+GtkWidget   *cursor_search_new       (void);
+const gchar *cursor_search_get_sexp  (CursorSearch *search);
+
+G_END_DECLS
+
+#endif /* __CURSOR_SEARCH_H__ */
diff --git a/tests/cursor-example/cursor-search.ui b/tests/cursor-example/cursor-search.ui
new file mode 100644
index 0000000..58abc34
--- /dev/null
+++ b/tests/cursor-example/cursor-search.ui
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.10 -->
+  <template class="CursorSearch" parent="GtkSearchEntry">
+    <property name="visible">True</property>
+    <property name="can_focus">True</property>
+    <property name="primary_icon_name">edit-find-symbolic</property>
+    <signal name="changed" handler="cursor_search_entry_changed" swapped="yes"/>
+    <signal name="icon-press" handler="cursor_search_icon_press" swapped="yes"/>
+  </template>
+  <object class="GtkMenu" id="popup">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkRadioMenuItem" id="name_radio">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">Name</property>
+        <property name="use_underline">True</property>
+        <property name="active">True</property>
+        <property name="draw_as_radio">True</property>
+        <signal name="toggled" handler="cursor_search_option_toggled" swapped="yes"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkRadioMenuItem" id="phone_radio">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">Phonenumber</property>
+        <property name="use_underline">True</property>
+        <property name="draw_as_radio">True</property>
+        <property name="group">name_radio</property>
+        <signal name="toggled" handler="cursor_search_option_toggled" swapped="yes"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkRadioMenuItem" id="email_radio">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">Email</property>
+        <property name="use_underline">True</property>
+        <property name="draw_as_radio">True</property>
+        <property name="group">name_radio</property>
+        <signal name="toggled" handler="cursor_search_option_toggled" swapped="yes"/>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/tests/cursor-example/cursor-slot.c b/tests/cursor-example/cursor-slot.c
new file mode 100644
index 0000000..83d5184
--- /dev/null
+++ b/tests/cursor-example/cursor-slot.c
@@ -0,0 +1,152 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+ 
+#include <libebook/libebook.h>
+
+#include "cursor-slot.h"
+
+/* GObjectClass */
+static void  cursor_slot_class_init  (CursorSlotClass *klass);
+static void  cursor_slot_init        (CursorSlot      *slot);
+
+struct _CursorSlotPrivate {
+       /* Screen widgets */
+       GtkWidget *area;
+       GtkLabel  *name_label;
+       GtkLabel  *emails_label;
+       GtkLabel  *telephones_label;
+};
+
+G_DEFINE_TYPE (CursorSlot, cursor_slot, GTK_TYPE_GRID);
+
+/************************************************************************
+ *                          GObjectClass                                *
+ ************************************************************************/
+static void
+cursor_slot_class_init (CursorSlotClass *klass)
+{
+       GObjectClass *object_class;
+       GtkWidgetClass *widget_class;
+
+       /* Bind to template */
+       widget_class = GTK_WIDGET_CLASS (klass);
+       gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/evolution/cursor-example/cursor-slot.ui");
+       gtk_widget_class_bind_child (widget_class, CursorSlotPrivate, area);
+       gtk_widget_class_bind_child (widget_class, CursorSlotPrivate, name_label);
+       gtk_widget_class_bind_child (widget_class, CursorSlotPrivate, emails_label);
+       gtk_widget_class_bind_child (widget_class, CursorSlotPrivate, telephones_label);
+
+       object_class = G_OBJECT_CLASS (klass);
+       g_type_class_add_private (object_class, sizeof (CursorSlotPrivate));
+}
+
+static void
+cursor_slot_init (CursorSlot *slot)
+{
+       CursorSlotPrivate *priv;
+
+       slot->priv = priv = 
+               G_TYPE_INSTANCE_GET_PRIVATE (slot,
+                                CURSOR_TYPE_SLOT,
+                                CursorSlotPrivate);
+
+       gtk_widget_init_template (GTK_WIDGET (slot));
+}
+
+/************************************************************************
+ *                                API                                   *
+ ************************************************************************/
+GtkWidget *
+cursor_slot_new (EContact   *contact)
+{
+  CursorSlot *slot;
+
+  slot = g_object_new (CURSOR_TYPE_SLOT, NULL);
+
+  cursor_slot_set_from_contact (slot, contact);
+
+  return (GtkWidget *)slot;
+}
+
+static gchar *
+make_string_from_list (EContact      *contact,
+                      EContactField  field)
+{
+       GList *values, *l;
+       GString *string;
+
+       string = g_string_new ("<span size=\"x-small\">");
+       values = e_contact_get (contact, field);
+
+       for (l = values; l; l = l->next) {
+               gchar *value = (gchar *) l->data;
+
+               if (l->prev != NULL)
+                       g_string_append (string, ", ");
+
+               g_string_append (string, value);
+       }
+
+       if (!values)
+               g_string_append (string, " - none - ");
+
+       e_contact_attr_list_free (values);
+       g_string_append (string, "</span>");
+
+       return g_string_free (string, FALSE);
+}
+
+void
+cursor_slot_set_from_contact (CursorSlot *slot,
+                             EContact   *contact)
+{
+       CursorSlotPrivate *priv;
+       const gchar *family_name, *given_name;
+       gchar *str;
+
+       g_return_if_fail (CURSOR_IS_SLOT (slot));
+       g_return_if_fail (contact == NULL || E_IS_CONTACT (contact));
+
+       priv = slot->priv;
+
+       if (!contact) {
+               gtk_widget_hide (priv->area);
+               return;
+       }
+
+       family_name = (const gchar *)e_contact_get_const (contact, E_CONTACT_FAMILY_NAME);
+       given_name = (const gchar *)e_contact_get_const (contact, E_CONTACT_GIVEN_NAME);
+
+       str = g_strdup_printf ("%s, %s", family_name, given_name);
+       gtk_label_set_text (priv->name_label, str);
+       g_free (str);
+
+       str = make_string_from_list (contact, E_CONTACT_EMAIL);
+       gtk_label_set_markup (priv->emails_label, str);
+       g_free (str);
+
+       str = make_string_from_list (contact, E_CONTACT_TEL);
+       gtk_label_set_markup (priv->telephones_label, str);
+       g_free (str);
+
+       gtk_widget_show (priv->area);
+}
+
diff --git a/tests/cursor-example/cursor-slot.h b/tests/cursor-example/cursor-slot.h
new file mode 100644
index 0000000..9f2994b
--- /dev/null
+++ b/tests/cursor-example/cursor-slot.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+ 
+#ifndef __CURSOR_SLOT_H__
+#define __CURSOR_SLOT_H__
+
+#include <gtk/gtk.h>
+#include <libebook/libebook.h>
+
+G_BEGIN_DECLS
+
+#define CURSOR_TYPE_SLOT            (cursor_slot_get_type())
+#define CURSOR_SLOT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CURSOR_TYPE_SLOT, CursorSlot))
+#define CURSOR_SLOT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CURSOR_TYPE_SLOT, CursorSlotClass))
+#define CURSOR_IS_SLOT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CURSOR_TYPE_SLOT))
+#define CURSOR_IS_SLOT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CURSOR_TYPE_SLOT))
+#define CURSOR_SLOT_GET_CLASS(o)    (G_TYPE_INSTANCE_GET_CLASS ((o), CURSOR_SLOT, CursorSlotClass))
+
+typedef struct _CursorSlot         CursorSlot;
+typedef struct _CursorSlotPrivate  CursorSlotPrivate;
+typedef struct _CursorSlotClass    CursorSlotClass;
+
+struct _CursorSlot
+{
+       GtkWindow parent_instance;
+
+       CursorSlotPrivate *priv;
+};
+
+struct _CursorSlotClass
+{
+       GtkWindowClass parent_class;
+};
+
+GType       cursor_slot_get_type         (void) G_GNUC_CONST;
+GtkWidget  *cursor_slot_new              (EContact *contact);
+void        cursor_slot_set_from_contact (CursorSlot *slot,
+                                         EContact   *contact);
+
+G_END_DECLS
+
+#endif /* __CURSOR_SLOT_H__ */
diff --git a/tests/cursor-example/cursor-slot.ui b/tests/cursor-example/cursor-slot.ui
new file mode 100644
index 0000000..bc4c086
--- /dev/null
+++ b/tests/cursor-example/cursor-slot.ui
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.10 -->
+  <template class="CursorSlot" parent="GtkGrid">
+    <property name="can_focus">False</property>
+    <property name="no_show_all">True</property>
+    <property name="hexpand">True</property>
+    <child>
+      <object class="GtkGrid" id="area">
+        <property name="can_focus">False</property>
+        <property name="hexpand">True</property>
+        <property name="vexpand">False</property>
+        <property name="row_spacing">2</property>
+        <property name="column_spacing">2</property>
+        <child>
+          <object class="GtkLabel" id="name_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">start</property>
+            <property name="label" translatable="yes">Family Name, Name</property>
+            <attributes>
+              <attribute name="weight" value="bold"/>
+            </attributes>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+            <property name="width">3</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">end</property>
+            <property name="margin_left">6</property>
+            <property name="hexpand">False</property>
+            <property name="label" translatable="yes">&lt;span 
size="x-small"&gt;Email:&lt;/span&gt;</property>
+            <property name="use_markup">True</property>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">1</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="margin_left">6</property>
+            <property name="hexpand">False</property>
+            <property name="label" translatable="yes">&lt;span 
size="x-small"&gt;Telephone:&lt;/span&gt;</property>
+            <property name="use_markup">True</property>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">2</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="emails_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">&lt;span size="x-small"&gt;email foo 
bar&lt;/span&gt;</property>
+            <property name="use_markup">True</property>
+            <property name="ellipsize">end</property>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">1</property>
+            <property name="width">2</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="telephones_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">&lt;span size="x-small"&gt;1-234-555-6789, 
1-800-DONOUGHTS&lt;/span&gt;</property>
+            <property name="use_markup">True</property>
+            <property name="ellipsize">end</property>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">2</property>
+            <property name="width">2</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">0</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+  </template>
+</interface>
diff --git a/tests/cursor-example/main.c b/tests/cursor-example/main.c
new file mode 100644
index 0000000..87968fa
--- /dev/null
+++ b/tests/cursor-example/main.c
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+
+#include "cursor-example.h"
+
+int
+main (int argc, char *argv[])
+{
+  GtkWidget *example;
+
+  gtk_init (&argc, &argv);
+
+  if (argc < 2)
+    example = cursor_example_new ("./data");
+  else
+    example = cursor_example_new (argv[1]);
+
+  g_signal_connect (example, "destroy",
+                   G_CALLBACK (gtk_main_quit), NULL);
+
+  gtk_widget_show (example);
+  gtk_main ();
+
+  return 0;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]