[evolution-data-server/openismus-work-3-8: 91/118] Adding cursor example program
- From: Tristan Van Berkom <tvb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/openismus-work-3-8: 91/118] Adding cursor example program
- Date: Wed, 2 Oct 2013 21:23:22 +0000 (UTC)
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"><span
size="x-small">Email:</span></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"><span
size="x-small">Telephone:</span></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"><span size="x-small">email foo
bar</span></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"><span size="x-small">1-234-555-6789,
1-800-DONOUGHTS</span></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]