[gnome-keyring: 2/5] gcr: Add the list selector and live search.



commit cfb39360f9f6316f01ce963e5c66ae1f9c8cb111
Author: Stef Walter <stefw collabora co uk>
Date:   Tue Jul 12 16:47:05 2011 +0200

    gcr: Add the list selector and live search.
    
     * Use live search from Empathy.
     * Selector has [x] boxes next to each object.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=654484

 gcr/Makefile.am                 |    3 +
 gcr/gcr-list-selector-private.h |   40 +++
 gcr/gcr-list-selector.c         |  456 +++++++++++++++++++++++++++
 gcr/gcr-list-selector.h         |   73 +++++
 gcr/gcr-live-search.c           |  651 +++++++++++++++++++++++++++++++++++++++
 gcr/gcr-live-search.h           |   85 +++++
 gcr/gcr-marshal.list            |    1 +
 gcr/gcr.h                       |    1 +
 gcr/tests/frob-gnupg-selector.c |   18 +-
 9 files changed, 1324 insertions(+), 4 deletions(-)
---
diff --git a/gcr/Makefile.am b/gcr/Makefile.am
index fc67f39..45489be 100644
--- a/gcr/Makefile.am
+++ b/gcr/Makefile.am
@@ -41,6 +41,7 @@ inc_HEADERS = \
 	gcr-key-widget.h \
 	gcr-importer.h \
 	gcr-library.h \
+	gcr-list-selector.h \
 	gcr-parser.h \
 	gcr-pkcs11-certificate.h \
 	gcr-renderer.h \
@@ -99,6 +100,8 @@ libgcr_ GCR_MAJOR@_la_SOURCES = \
 	gcr-key-renderer.c gcr-key-renderer.h \
 	gcr-key-widget.c gcr-key-widget.h \
 	gcr-library.c gcr-library.h \
+	gcr-list-selector.c gcr-list-selector.h \
+	gcr-live-search.c gcr-live-search.h \
 	gcr-memory-icon.c gcr-memory-icon.h \
 	gcr-parser.c gcr-parser.h \
 	gcr-pkcs11-certificate.c gcr-pkcs11-certificate.h \
diff --git a/gcr/gcr-list-selector-private.h b/gcr/gcr-list-selector-private.h
new file mode 100644
index 0000000..5f73e0b
--- /dev/null
+++ b/gcr/gcr-list-selector-private.h
@@ -0,0 +1,40 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Lesser 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#ifndef __GCR_LIST_SELECTOR_PRIVATE_H__
+#define __GCR_LIST_SELECTOR_PRIVATE_H__
+
+#include "gcr-types.h"
+#include "gcr-list-selector.h"
+#include "gcr-live-search.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void              _gcr_list_selector_set_live_search           (GcrListSelector *self,
+                                                                GcrLiveSearch *search);
+
+G_END_DECLS
+
+#endif /* __GCR_LIST_SELECTOR_PRIVATE_H__ */
diff --git a/gcr/gcr-list-selector.c b/gcr/gcr-list-selector.c
new file mode 100644
index 0000000..bbc862d
--- /dev/null
+++ b/gcr/gcr-list-selector.c
@@ -0,0 +1,456 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2010 Stefan Walter
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Lesser 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#include "config.h"
+
+#include "gcr-collection-model.h"
+#include "gcr-internal.h"
+#include "gcr-list-selector.h"
+#include "gcr-list-selector-private.h"
+#include "gcr-live-search.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <string.h>
+
+/**
+ * SECTION:gcr-list-selector
+ * @title: GcrListSelector
+ * @short_description: A selector widget to select a single certificate or key.
+ *
+ * The #GcrListSelector can be used to select a certificate or key. It allows
+ * the user to select one object from the selector at a time.
+ */
+
+/**
+ * GcrListSelector:
+ * @parent: Parent object
+ *
+ * A list selector widget.
+ */
+
+/**
+ * GcrListSelectorClass:
+ *
+ * The class for #GcrListSelector.
+ */
+
+enum {
+	PROP_0,
+	PROP_COLLECTION
+};
+
+struct _GcrListSelectorPrivate {
+	GcrCollection *collection;
+	GcrCollectionModel *model;
+
+	GtkTreeModelFilter *filter;
+	GtkWidget *search_widget;
+};
+
+G_DEFINE_TYPE (GcrListSelector, gcr_list_selector, GTK_TYPE_TREE_VIEW);
+
+static gboolean
+object_is_visible (GcrListSelector *self, GObject *object)
+{
+	gchar *text;
+	gboolean visible;
+
+	if (g_object_class_find_property (G_OBJECT_GET_CLASS (object), "search-text"))
+		g_object_get (object, "search-text", &text, NULL);
+	else
+		g_object_get (object, "label", &text, NULL);
+
+	visible = _gcr_live_search_match (GCR_LIVE_SEARCH (self->pv->search_widget), text);
+	g_free (text);
+
+	return visible;
+}
+
+static gboolean
+on_tree_filter_visible_func (GtkTreeModel *model, GtkTreeIter *iter,
+                             gpointer user_data)
+{
+	GcrListSelector *self = GCR_LIST_SELECTOR (user_data);
+	GObject *object;
+
+	if (self->pv->search_widget == NULL ||
+	    !gtk_widget_get_visible (self->pv->search_widget))
+		return TRUE;
+
+	object = gcr_collection_model_object_for_iter (self->pv->model, iter);
+	if (object != NULL)
+		return object_is_visible (self, object);
+
+	return FALSE;
+}
+
+static gboolean
+on_tree_view_start_search (GtkTreeView *view, gpointer user_data)
+{
+	GcrListSelector *self = GCR_LIST_SELECTOR (view);
+
+	if (self->pv->search_widget == NULL)
+		return FALSE;
+
+	if (gtk_widget_get_visible (self->pv->search_widget))
+		gtk_widget_grab_focus (self->pv->search_widget);
+	else
+		gtk_widget_show (self->pv->search_widget);
+
+	return TRUE;
+}
+
+static void
+on_search_widget_text_notify (GcrLiveSearch *search, GParamSpec *pspec,
+                              gpointer user_data)
+{
+	GcrListSelector *self = GCR_LIST_SELECTOR (user_data);
+#if 0
+	GtkTreeViewColumn *focus_column;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	gboolean set_cursor = FALSE;
+#endif
+
+	gtk_tree_model_filter_refilter (self->pv->filter);
+
+#if 0
+	/* Set cursor on the first object. */
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
+	gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
+
+	if (path == NULL) {
+		path = gtk_tree_path_new_from_string ("0");
+		set_cursor = TRUE;
+	}
+
+	if (set_cursor) {
+		/* FIXME: Workaround for GTK bug #621651, we have to make sure
+		 * the path is valid. */
+		if (gtk_tree_model_get_iter (model, &iter, path)) {
+			gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path,
+				focus_column, FALSE);
+		}
+	}
+
+	gtk_tree_path_free (path);
+#endif
+}
+
+static void
+on_search_widget_activate (GtkWidget *search, gpointer user_data)
+{
+	GcrListSelector *self = GCR_LIST_SELECTOR (user_data);
+	GtkTreePath *path;
+	GtkTreeViewColumn *focus_column;
+
+	gtk_tree_view_get_cursor (GTK_TREE_VIEW (self), &path, &focus_column);
+	if (path != NULL) {
+		gtk_tree_view_row_activated (GTK_TREE_VIEW (self), path, focus_column);
+		gtk_tree_path_free (path);
+
+		gtk_widget_hide (search);
+	}
+}
+
+static gboolean
+on_search_widget_key_navigation (GtkWidget *search, GdkEvent *event, gpointer user_data)
+{
+	GcrListSelector *self = GCR_LIST_SELECTOR (user_data);
+	GdkEvent *new_event;
+	gboolean ret = FALSE;
+
+	new_event = gdk_event_copy (event);
+	gtk_widget_grab_focus (GTK_WIDGET (self));
+	ret = gtk_widget_event (GTK_WIDGET (self), new_event);
+	gtk_widget_grab_focus (search);
+
+	gdk_event_free (new_event);
+
+	return ret;
+}
+
+static void
+on_check_column_toggled (GtkCellRendererToggle *cell, gchar *path, gpointer user_data)
+{
+	GcrListSelector *self = GCR_LIST_SELECTOR (user_data);
+	GtkTreeIter iter, model_iter;
+
+	g_assert (path != NULL);
+
+	if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (self->pv->filter), &iter, path)) {
+		gtk_tree_model_filter_convert_iter_to_child_iter (self->pv->filter, &model_iter, &iter);
+		gcr_collection_model_toggle_selected (self->pv->model, &model_iter);
+	}
+}
+
+static void
+gcr_list_selector_constructed (GObject *object)
+{
+	GcrListSelector *self = GCR_LIST_SELECTOR (object);
+	GtkCellRenderer *cell;
+	GtkTreeViewColumn *column;
+	guint column_id;
+
+	G_OBJECT_CLASS (gcr_list_selector_parent_class)->constructed (object);
+
+	self->pv->model = gcr_collection_model_new (self->pv->collection,
+	                                            "icon", G_TYPE_ICON,
+	                                            "markup", G_TYPE_STRING,
+	                                            NULL);
+
+	self->pv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
+	                                          GTK_TREE_MODEL (self->pv->model), NULL));
+	gtk_tree_model_filter_set_visible_func (self->pv->filter,
+	                                        on_tree_filter_visible_func, self, NULL);
+
+	gtk_tree_view_set_model (GTK_TREE_VIEW (self), GTK_TREE_MODEL (self->pv->filter));
+
+	/* The check */
+
+	cell = gtk_cell_renderer_toggle_new ();
+	g_signal_connect (cell, "toggled", G_CALLBACK (on_check_column_toggled), self);
+
+	column_id = gcr_collection_model_column_for_selected (self->pv->model);
+	column = gtk_tree_view_column_new_with_attributes ("", cell, "active", column_id, NULL);
+	gtk_tree_view_column_set_resizable (column, FALSE);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (self), column);
+
+	column = gtk_tree_view_column_new ();
+
+	/* The icon */
+	cell = gtk_cell_renderer_pixbuf_new ();
+	g_object_set (cell, "stock-size", GTK_ICON_SIZE_DND, NULL);
+	gtk_tree_view_column_pack_start (column, cell, FALSE);
+	gtk_tree_view_column_add_attribute (column, cell, "gicon", 0);
+
+	/* The markup */
+	cell = gtk_cell_renderer_text_new ();
+	gtk_tree_view_column_pack_start (column, cell, TRUE);
+	gtk_tree_view_column_add_attribute (column, cell, "markup", 1);
+
+	gtk_tree_view_append_column (GTK_TREE_VIEW (self), column);
+}
+
+static void
+gcr_list_selector_init (GcrListSelector *self)
+{
+	self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_LIST_SELECTOR, GcrListSelectorPrivate);
+}
+
+static void
+gcr_list_selector_dispose (GObject *obj)
+{
+	GcrListSelector *self = GCR_LIST_SELECTOR (obj);
+
+	if (self->pv->filter)
+		g_object_unref (self->pv->filter);
+	self->pv->filter = NULL;
+
+	if (self->pv->model)
+		g_object_unref (self->pv->model);
+	self->pv->model = NULL;
+
+	if (self->pv->collection)
+		g_object_unref (self->pv->collection);
+	self->pv->collection = NULL;
+
+	_gcr_list_selector_set_live_search (self, NULL);
+
+	G_OBJECT_CLASS (gcr_list_selector_parent_class)->dispose (obj);
+}
+
+static void
+gcr_list_selector_finalize (GObject *obj)
+{
+	GcrListSelector *self = GCR_LIST_SELECTOR (obj);
+
+	g_assert (!self->pv->collection);
+	g_assert (!self->pv->model);
+
+	G_OBJECT_CLASS (gcr_list_selector_parent_class)->finalize (obj);
+}
+
+static void
+gcr_list_selector_set_property (GObject *obj, guint prop_id, const GValue *value,
+                                 GParamSpec *pspec)
+{
+	GcrListSelector *self = GCR_LIST_SELECTOR (obj);
+
+	switch (prop_id) {
+	case PROP_COLLECTION:
+		g_return_if_fail (!self->pv->collection);
+		self->pv->collection = g_value_dup_object (value);
+		g_return_if_fail (self->pv->collection);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gcr_list_selector_get_property (GObject *obj, guint prop_id, GValue *value,
+                                 GParamSpec *pspec)
+{
+	GcrListSelector *self = GCR_LIST_SELECTOR (obj);
+
+	switch (prop_id) {
+	case PROP_COLLECTION:
+		g_value_set_object (value, gcr_list_selector_get_collection (self));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gcr_list_selector_class_init (GcrListSelectorClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gobject_class->constructed = gcr_list_selector_constructed;
+	gobject_class->dispose = gcr_list_selector_dispose;
+	gobject_class->finalize = gcr_list_selector_finalize;
+	gobject_class->set_property = gcr_list_selector_set_property;
+	gobject_class->get_property = gcr_list_selector_get_property;
+
+	g_type_class_add_private (gobject_class, sizeof (GcrListSelectorPrivate));
+
+	/**
+	 * GcrListSelector:collection:
+	 *
+	 * The collection which contains the objects to display in the selector.
+	 */
+	g_object_class_install_property (gobject_class, PROP_COLLECTION,
+	           g_param_spec_object ("collection", "Collection", "Collection to select from",
+	                                GCR_TYPE_COLLECTION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	_gcr_initialize ();
+}
+
+/* -----------------------------------------------------------------------------
+ * PUBLIC
+ */
+
+/**
+ * gcr_selector_new:
+ * @collection: The collection that contains the objects to display
+ *
+ * Create a new #GcrTreeSelector.
+ *
+ * Returns: A newly allocated selector, which should be released with
+ *     g_object_unref().
+ */
+GcrListSelector*
+gcr_list_selector_new (GcrCollection *collection)
+{
+	return g_object_new (GCR_TYPE_LIST_SELECTOR,
+	                     "collection", collection,
+	                     NULL);
+}
+
+/**
+ * gcr_list_selector_get_collection:
+ * @self: The selector
+ *
+ * Get the collection that this selector is displaying objects from.
+ *
+ * Returns: The collection, owned by the selector.
+ */
+GcrCollection*
+gcr_list_selector_get_collection (GcrListSelector *self)
+{
+	g_return_val_if_fail (GCR_IS_LIST_SELECTOR (self), NULL);
+	return self->pv->collection;
+}
+
+/**
+ * gcr_list_selector_get_selected:
+ * @self: The selector
+ *
+ * Get a list of selected objects.
+ *
+ * Returns: The list of selected objects, to be released with g_list_free().
+ */
+GList*
+gcr_list_selector_get_selected (GcrListSelector *self)
+{
+	g_return_val_if_fail (GCR_IS_LIST_SELECTOR (self), NULL);
+	return gcr_collection_model_get_selected_objects (self->pv->model);
+}
+
+/**
+ * gcr_list_selector_set_selected:
+ * @self: The selector
+ * @selected: The list of objects to select.
+ *
+ * Select certain objects in the selector.
+ */
+void
+gcr_list_selector_set_selected (GcrListSelector *self, GList *selected)
+{
+	g_return_if_fail (GCR_IS_LIST_SELECTOR (self));
+	gcr_collection_model_set_selected_objects (self->pv->model, selected);
+}
+
+
+void
+_gcr_list_selector_set_live_search (GcrListSelector *self, GcrLiveSearch *search)
+{
+	g_return_if_fail (GCR_IS_LIST_SELECTOR (self));
+
+	/* remove old handlers if old search was not null */
+	if (self->pv->search_widget != NULL) {
+		g_signal_handlers_disconnect_by_func (self, on_tree_view_start_search, NULL);
+
+		g_signal_handlers_disconnect_by_func (self->pv->search_widget,
+			on_search_widget_text_notify, self);
+		g_signal_handlers_disconnect_by_func (self->pv->search_widget,
+			on_search_widget_activate, self);
+		g_signal_handlers_disconnect_by_func (self->pv->search_widget,
+			on_search_widget_key_navigation, self);
+		g_object_unref (self->pv->search_widget);
+		self->pv->search_widget = NULL;
+	}
+
+	/* connect handlers if new search is not null */
+	if (search != NULL) {
+		self->pv->search_widget = g_object_ref (search);
+
+		g_signal_connect (self, "start-interactive-search",
+		                  G_CALLBACK (on_tree_view_start_search), NULL);
+
+		g_signal_connect (self->pv->search_widget, "notify::text",
+			G_CALLBACK (on_search_widget_text_notify), self);
+		g_signal_connect (self->pv->search_widget, "activate",
+			G_CALLBACK (on_search_widget_activate), self);
+		g_signal_connect (self->pv->search_widget, "key-navigation",
+			G_CALLBACK (on_search_widget_key_navigation), self);
+	}
+}
diff --git a/gcr/gcr-list-selector.h b/gcr/gcr-list-selector.h
new file mode 100644
index 0000000..6d6e748
--- /dev/null
+++ b/gcr/gcr-list-selector.h
@@ -0,0 +1,73 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2010 Stefan Walter
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Lesser 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#ifndef __GCR_LIST_SELECTOR_H__
+#define __GCR_LIST_SELECTOR_H__
+
+#include "gcr-types.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GCR_TYPE_LIST_SELECTOR               (gcr_list_selector_get_type ())
+#define GCR_LIST_SELECTOR(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_LIST_SELECTOR, GcrListSelector))
+#define GCR_LIST_SELECTOR_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_LIST_SELECTOR, GcrListSelectorClass))
+#define GCR_IS_LIST_SELECTOR(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_LIST_SELECTOR))
+#define GCR_IS_LIST_SELECTOR_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_LIST_SELECTOR))
+#define GCR_LIST_SELECTOR_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_LIST_SELECTOR, GcrListSelectorClass))
+
+typedef struct _GcrListSelector GcrListSelector;
+typedef struct _GcrListSelectorClass GcrListSelectorClass;
+typedef struct _GcrListSelectorPrivate GcrListSelectorPrivate;
+
+struct _GcrListSelector {
+	GtkTreeView parent;
+
+	/*< private >*/
+	GcrListSelectorPrivate *pv;
+};
+
+struct _GcrListSelectorClass {
+
+	/*< private >*/
+	GtkTreeViewClass parent_class;
+};
+
+GType                    gcr_list_selector_get_type         (void);
+
+GcrListSelector*         gcr_list_selector_new              (GcrCollection *collection);
+
+GcrCollection*           gcr_list_selector_get_collection   (GcrListSelector *self);
+
+const GcrColumn*         gcr_list_selector_get_columns      (GcrListSelector *self);
+
+GList*                   gcr_list_selector_get_selected     (GcrListSelector *self);
+
+void                     gcr_list_selector_set_selected     (GcrListSelector *self,
+                                                             GList *selected);
+
+G_END_DECLS
+
+#endif /* __GCR_LIST_SELECTOR_H__ */
diff --git a/gcr/gcr-live-search.c b/gcr/gcr-live-search.c
new file mode 100644
index 0000000..b0f4414
--- /dev/null
+++ b/gcr/gcr-live-search.c
@@ -0,0 +1,651 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2010 Collabora Ltd.
+ * Copyright (C) 2007-2010 Nokia Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Felix Kaser <felix kaser collabora co uk>
+ *          Xavier Claessens <xavier claessens collabora co uk>
+ *          Claudio Saavedra <csaavedra igalia com>
+ *          Stef Walter <stefw collabora co uk>
+ */
+
+/* Code borrowed from Empathy */
+
+#include "config.h"
+
+#include "gcr-live-search.h"
+#include "gcr-marshal.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <string.h>
+
+G_DEFINE_TYPE (GcrLiveSearch, _gcr_live_search, GTK_TYPE_BOX)
+
+struct _GcrLiveSearchPrivate {
+	GtkWidget *search_entry;
+	GtkWidget *hook_widget;
+	GPtrArray *stripped_words;
+};
+
+enum {
+	PROP_0,
+	PROP_HOOK_WIDGET,
+	PROP_TEXT
+};
+
+enum {
+	ACTIVATE,
+	KEYNAV,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void on_hook_widget_destroy (GtkWidget *object, gpointer user_data);
+
+static gunichar
+stripped_char (gunichar ch)
+{
+	gunichar retval = 0;
+	GUnicodeType utype;
+	gunichar *decomp;
+	gsize dlen;
+
+	utype = g_unichar_type (ch);
+
+	switch (utype) {
+	case G_UNICODE_CONTROL:
+	case G_UNICODE_FORMAT:
+	case G_UNICODE_UNASSIGNED:
+	case G_UNICODE_NON_SPACING_MARK:
+	case G_UNICODE_COMBINING_MARK:
+	case G_UNICODE_ENCLOSING_MARK:
+		/* Ignore those */
+		break;
+	case G_UNICODE_PRIVATE_USE:
+	case G_UNICODE_SURROGATE:
+	case G_UNICODE_LOWERCASE_LETTER:
+	case G_UNICODE_MODIFIER_LETTER:
+	case G_UNICODE_OTHER_LETTER:
+	case G_UNICODE_TITLECASE_LETTER:
+	case G_UNICODE_UPPERCASE_LETTER:
+	case G_UNICODE_DECIMAL_NUMBER:
+	case G_UNICODE_LETTER_NUMBER:
+	case G_UNICODE_OTHER_NUMBER:
+	case G_UNICODE_CONNECT_PUNCTUATION:
+	case G_UNICODE_DASH_PUNCTUATION:
+	case G_UNICODE_CLOSE_PUNCTUATION:
+	case G_UNICODE_FINAL_PUNCTUATION:
+	case G_UNICODE_INITIAL_PUNCTUATION:
+	case G_UNICODE_OTHER_PUNCTUATION:
+	case G_UNICODE_OPEN_PUNCTUATION:
+	case G_UNICODE_CURRENCY_SYMBOL:
+	case G_UNICODE_MODIFIER_SYMBOL:
+	case G_UNICODE_MATH_SYMBOL:
+	case G_UNICODE_OTHER_SYMBOL:
+	case G_UNICODE_LINE_SEPARATOR:
+	case G_UNICODE_PARAGRAPH_SEPARATOR:
+	case G_UNICODE_SPACE_SEPARATOR:
+	default:
+		ch = g_unichar_tolower (ch);
+		decomp = g_unicode_canonical_decomposition (ch, &dlen);
+		if (decomp != NULL) {
+			retval = decomp[0];
+			g_free (decomp);
+		}
+	}
+
+	return retval;
+}
+
+static void
+append_word (GPtrArray **word_array, GString **word)
+{
+	if (*word != NULL) {
+		if (*word_array == NULL)
+			*word_array = g_ptr_array_new_with_free_func (g_free);
+		g_ptr_array_add (*word_array, g_string_free (*word, FALSE));
+		*word = NULL;
+	}
+}
+
+GPtrArray *
+_gcr_live_search_strip_utf8_string (const gchar *string)
+{
+	GPtrArray *word_array = NULL;
+	GString *word = NULL;
+	const gchar *p;
+
+	if (string == NULL || *string == '\0')
+		return NULL;
+
+	for (p = string; *p != '\0'; p = g_utf8_next_char (p)) {
+		gunichar sc;
+
+		/* Make the char lower-case, remove its accentuation marks, and ignore it
+		 * if it is just unicode marks */
+		sc = stripped_char (g_utf8_get_char (p));
+		if (sc == 0)
+			continue;
+
+		/* If it is not alpha-num, it is separator between words */
+		if (!g_unichar_isalnum (sc)) {
+			append_word (&word_array, &word);
+			continue;
+		}
+
+		/* It is alpha-num, append this char to current word, or start new word */
+		if (word == NULL)
+			word = g_string_new (NULL);
+		g_string_append_unichar (word, sc);
+	}
+
+	append_word (&word_array, &word);
+
+	return word_array;
+}
+
+static gboolean
+live_search_match_prefix (const gchar *string, const gchar *prefix)
+{
+	const gchar *p;
+	const gchar *prefix_p;
+	gboolean next_word = FALSE;
+
+	if (prefix == NULL || prefix[0] == 0)
+		return TRUE;
+
+	if (string == NULL || *string == '\0')
+		return FALSE;
+
+	prefix_p = prefix;
+	for (p = string; *p != '\0'; p = g_utf8_next_char (p)) {
+		gunichar sc;
+
+		/* Make the char lower-case, remove its accentuation marks, and ignore it
+		 * if it is just unicode marks */
+		sc = stripped_char (g_utf8_get_char (p));
+		if (sc == 0)
+			continue;
+
+		/* If we want to go to next word, ignore alpha-num chars */
+		if (next_word && g_unichar_isalnum (sc))
+			continue;
+		next_word = FALSE;
+
+		/* Ignore word separators */
+		if (!g_unichar_isalnum (sc))
+			continue;
+
+		/* If this char does not match prefix_p, go to next word and start again
+		 * from the beginning of prefix */
+		if (sc != g_utf8_get_char (prefix_p)) {
+			next_word = TRUE;
+			prefix_p = prefix;
+			continue;
+		}
+
+		/* prefix_p match, verify to next char. If this was the last of prefix,
+		 * it means it completely machted and we are done. */
+		prefix_p = g_utf8_next_char (prefix_p);
+		if (*prefix_p == '\0')
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+gboolean
+_gcr_live_search_match_words (const gchar *string, GPtrArray *words)
+{
+	guint i;
+
+	if (words == NULL)
+		return TRUE;
+
+	for (i = 0; i < words->len; i++)
+		if (!live_search_match_prefix (string, g_ptr_array_index (words, i)))
+			return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+fire_key_navigation_sig (GcrLiveSearch *self, GdkEventKey *event)
+{
+	gboolean ret;
+
+	g_signal_emit (self, signals[KEYNAV], 0, event, &ret);
+	return ret;
+}
+
+static gboolean
+on_search_entry_key_pressed (GtkEntry *entry, GdkEventKey *event, gpointer user_data)
+{
+	GcrLiveSearch *self = GCR_LIVE_SEARCH (user_data);
+
+	/* if esc key pressed, hide the search */
+	if (event->keyval == GDK_KEY_Escape) {
+		gtk_widget_hide (GTK_WIDGET (self));
+		return TRUE;
+	}
+
+	/* emit key navigation signal, so other widgets can respond to it properly */
+	if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_Down
+	    || event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_Page_Down) {
+		return fire_key_navigation_sig (self, event);
+	}
+
+	if (event->keyval == GDK_KEY_Home || event->keyval == GDK_KEY_End ||
+	    event->keyval == GDK_KEY_space) {
+		/* If the live search is visible, the entry should catch the Home/End
+		 * and space events */
+		if (!gtk_widget_get_visible (GTK_WIDGET (self))) {
+			return fire_key_navigation_sig (self, event);
+		}
+	}
+
+	return FALSE;
+}
+
+static void
+on_search_entry_text_changed (GtkEntry *entry, gpointer user_data)
+{
+	GcrLiveSearch *self = GCR_LIVE_SEARCH (user_data);
+	const gchar *text;
+
+	text = gtk_entry_get_text (entry);
+
+	if (text == NULL || *text == '\0')
+		gtk_widget_hide (GTK_WIDGET (self));
+	else
+		gtk_widget_show (GTK_WIDGET (self));
+
+	if (self->pv->stripped_words != NULL)
+		g_ptr_array_unref (self->pv->stripped_words);
+
+	self->pv->stripped_words = _gcr_live_search_strip_utf8_string (text);
+
+	g_object_notify (G_OBJECT (self), "text");
+}
+
+static void
+on_search_entry_close_pressed (GtkEntry *entry, GtkEntryIconPosition icon_pos,
+                               GdkEvent *event, gpointer user_data)
+{
+	GcrLiveSearch *self = GCR_LIVE_SEARCH (user_data);
+	gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static gboolean
+on_hook_widget_key_press_event (GtkWidget *widget, GdkEventKey *event,
+                                gpointer user_data)
+{
+	GcrLiveSearch *self = GCR_LIVE_SEARCH (user_data);
+	GdkEvent *new_event;
+	gboolean ret;
+
+	/* dont forward this event to the entry, else the event is consumed by the
+	 * entry and does not close the window */
+	if (!gtk_widget_get_visible (GTK_WIDGET (self)) &&
+	    event->keyval == GDK_KEY_Escape)
+		return FALSE;
+
+	/* do not show the search if CTRL and/or ALT are pressed with a key
+	 * this is needed, because otherwise the CTRL + F accel would not work,
+	 * because the entry consumes it */
+	if (event->state & (GDK_MOD1_MASK | GDK_CONTROL_MASK) ||
+	    event->keyval == GDK_KEY_Control_L ||
+	    event->keyval == GDK_KEY_Control_R)
+		return FALSE;
+
+	/* dont forward the up/down and Page Up/Down arrow keys to the entry,
+	 * they are needed for navigation in the treeview and are not needed in
+	 * the search entry */
+	if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_Down ||
+	    event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_Page_Down)
+		return FALSE;
+
+	if (event->keyval == GDK_KEY_Home || event->keyval == GDK_KEY_End ||
+	    event->keyval == GDK_KEY_space) {
+		/* Home/End and space keys have to be forwarded to the entry only if
+		 * the live search is visible (to move the cursor inside the entry). */
+		if (!gtk_widget_get_visible (GTK_WIDGET (self)))
+			return FALSE;
+	}
+
+	/* realize the widget if it is not realized yet */
+	gtk_widget_realize (self->pv->search_entry);
+	if (!gtk_widget_has_focus (self->pv->search_entry)) {
+		gtk_widget_grab_focus (self->pv->search_entry);
+		gtk_editable_set_position (GTK_EDITABLE (self->pv->search_entry), -1);
+	}
+
+	/* forward the event to the search entry */
+	new_event = gdk_event_copy ((GdkEvent *) event);
+	ret = gtk_widget_event (self->pv->search_entry, new_event);
+	gdk_event_free (new_event);
+
+	return ret;
+}
+
+static void
+on_search_entry_activate (GtkEntry *entry, GcrLiveSearch *self)
+{
+	g_signal_emit (self, signals[ACTIVATE], 0);
+}
+
+static void
+live_search_release_hook_widget (GcrLiveSearch *self)
+{
+	/* remove old handlers if old source was not null */
+	if (self->pv->hook_widget != NULL) {
+		g_signal_handlers_disconnect_by_func (self->pv->hook_widget,
+		                                      on_hook_widget_key_press_event, self);
+		g_signal_handlers_disconnect_by_func (self->pv->hook_widget,
+		                                      on_hook_widget_destroy, self);
+		g_object_unref (self->pv->hook_widget);
+		self->pv->hook_widget = NULL;
+	}
+}
+
+static void
+on_hook_widget_destroy (GtkWidget *object, gpointer user_data)
+{
+	GcrLiveSearch *self = GCR_LIVE_SEARCH (user_data);
+
+	/* unref the hook widget and hide search */
+	gtk_widget_hide (GTK_WIDGET (self));
+	live_search_release_hook_widget (self);
+}
+
+static void
+live_search_dispose (GObject *obj)
+{
+	GcrLiveSearch *self = GCR_LIVE_SEARCH (obj);
+
+	live_search_release_hook_widget (self);
+
+	G_OBJECT_CLASS (_gcr_live_search_parent_class)->dispose (obj);
+}
+
+static void
+live_search_finalize (GObject *obj)
+{
+	GcrLiveSearch *self = GCR_LIVE_SEARCH (obj);
+
+	if (self->pv->stripped_words != NULL)
+		g_ptr_array_unref (self->pv->stripped_words);
+
+	G_OBJECT_CLASS (_gcr_live_search_parent_class)->finalize (obj);
+}
+
+static void
+live_search_get_property (GObject *object, guint param_id,
+                          GValue *value, GParamSpec *pspec)
+{
+	GcrLiveSearch *self = GCR_LIVE_SEARCH (object);
+
+	switch (param_id) {
+	case PROP_HOOK_WIDGET:
+		g_value_set_object (value, _gcr_live_search_get_hook_widget (self));
+		break;
+	case PROP_TEXT:
+		g_value_set_string (value, _gcr_live_search_get_text (self));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static void
+live_search_set_property (GObject *object, guint param_id,
+                          const GValue *value, GParamSpec *pspec)
+{
+	GcrLiveSearch *self = GCR_LIVE_SEARCH (object);
+
+	switch (param_id) {
+	case PROP_HOOK_WIDGET:
+		_gcr_live_search_set_hook_widget (self, g_value_get_object (value));
+		break;
+	case PROP_TEXT:
+		_gcr_live_search_set_text (self, g_value_get_string (value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	};
+}
+
+static void
+live_search_unmap (GtkWidget *widget)
+{
+	GcrLiveSearch *self = GCR_LIVE_SEARCH (widget);
+
+	GTK_WIDGET_CLASS (_gcr_live_search_parent_class)->unmap (widget);
+
+	/* unmap can happen if a parent gets hidden, in that case we want to hide
+	 * the live search as well, so when it gets mapped again, the live search
+	 * won't be shown. */
+	gtk_widget_hide (widget);
+
+	gtk_entry_set_text (GTK_ENTRY (self->pv->search_entry), "");
+	gtk_widget_grab_focus (self->pv->hook_widget);
+}
+
+static void
+live_search_show (GtkWidget *widget)
+{
+	GcrLiveSearch *self = GCR_LIVE_SEARCH (widget);
+
+	if (!gtk_widget_has_focus (self->pv->search_entry))
+		gtk_widget_grab_focus (self->pv->search_entry);
+
+	GTK_WIDGET_CLASS (_gcr_live_search_parent_class)->show (widget);
+}
+
+static void
+live_search_grab_focus (GtkWidget *widget)
+{
+	GcrLiveSearch *self = GCR_LIVE_SEARCH (widget);
+
+	if (!gtk_widget_has_focus (self->pv->search_entry)) {
+		gtk_widget_grab_focus (self->pv->search_entry);
+		gtk_editable_set_position (GTK_EDITABLE (self->pv->search_entry), -1);
+	}
+}
+
+static void
+_gcr_live_search_class_init (GcrLiveSearchClass *klass)
+{
+	GObjectClass *object_class = (GObjectClass *) klass;
+	GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+	GParamSpec *param_spec;
+
+	object_class->finalize = live_search_finalize;
+	object_class->dispose = live_search_dispose;
+	object_class->get_property = live_search_get_property;
+	object_class->set_property = live_search_set_property;
+
+	widget_class->unmap = live_search_unmap;
+	widget_class->show = live_search_show;
+	widget_class->grab_focus = live_search_grab_focus;
+
+	signals[ACTIVATE] = g_signal_new ("activate",
+	                                  G_TYPE_FROM_CLASS (object_class),
+	                                  G_SIGNAL_RUN_LAST,
+	                                  0,
+	                                  NULL, NULL,
+	                                  g_cclosure_marshal_VOID__VOID,
+	                                  G_TYPE_NONE, 0);
+
+	signals[KEYNAV] = g_signal_new ("key-navigation",
+	                                G_TYPE_FROM_CLASS (object_class),
+	                                G_SIGNAL_RUN_LAST,
+	                                0,
+	                                g_signal_accumulator_true_handled, NULL,
+	                                _gcr_marshal_BOOLEAN__BOXED,
+	                                G_TYPE_BOOLEAN, 1, GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	param_spec = g_param_spec_object ("hook-widget", "Live Search Hook Widget",
+	                                  "The live search catches key-press-events on this widget",
+	                                  GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+	g_object_class_install_property (object_class, PROP_HOOK_WIDGET, param_spec);
+
+	param_spec = g_param_spec_string ("text", "Live Search Text",
+	                                  "The text of the live search entry",
+	                                  "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+	g_object_class_install_property (object_class, PROP_TEXT, param_spec);
+
+	g_type_class_add_private (klass, sizeof (GcrLiveSearchPrivate));
+}
+
+static void
+_gcr_live_search_init (GcrLiveSearch *self)
+{
+	self->pv = G_TYPE_INSTANCE_GET_PRIVATE ((self), GCR_TYPE_LIVE_SEARCH,
+	                                        GcrLiveSearchPrivate);
+
+	gtk_widget_set_no_show_all (GTK_WIDGET (self), TRUE);
+
+	self->pv->search_entry = gtk_entry_new ();
+	gtk_entry_set_icon_from_stock (GTK_ENTRY (self->pv->search_entry),
+	                               GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLOSE);
+	gtk_entry_set_icon_activatable (GTK_ENTRY (self->pv->search_entry),
+	                                GTK_ENTRY_ICON_SECONDARY, TRUE);
+	gtk_entry_set_icon_sensitive (GTK_ENTRY (self->pv->search_entry),
+	                              GTK_ENTRY_ICON_SECONDARY, TRUE);
+	gtk_widget_show (self->pv->search_entry);
+
+	gtk_box_pack_start (GTK_BOX (self), self->pv->search_entry, TRUE, TRUE, 0);
+
+	g_signal_connect (self->pv->search_entry, "icon_release",
+	                  G_CALLBACK (on_search_entry_close_pressed), self);
+	g_signal_connect (self->pv->search_entry, "changed",
+	                  G_CALLBACK (on_search_entry_text_changed), self);
+	g_signal_connect (self->pv->search_entry, "key-press-event",
+	                  G_CALLBACK (on_search_entry_key_pressed), self);
+	g_signal_connect (self->pv->search_entry, "activate",
+	                  G_CALLBACK (on_search_entry_activate), self);
+
+	self->pv->hook_widget = NULL;
+}
+
+GtkWidget *
+_gcr_live_search_new (GtkWidget *hook)
+{
+	g_return_val_if_fail (hook == NULL || GTK_IS_WIDGET (hook), NULL);
+
+	return g_object_new (GCR_TYPE_LIVE_SEARCH,
+	                     "hook-widget", hook,
+	                     NULL);
+}
+
+/* public methods */
+
+GtkWidget *
+_gcr_live_search_get_hook_widget (GcrLiveSearch *self)
+{
+	g_return_val_if_fail (GCR_IS_LIVE_SEARCH (self), NULL);
+
+	return self->pv->hook_widget;
+}
+
+void
+_gcr_live_search_set_hook_widget (GcrLiveSearch *self, GtkWidget *hook)
+{
+	g_return_if_fail (GCR_IS_LIVE_SEARCH (self));
+	g_return_if_fail (hook == NULL || GTK_IS_WIDGET (hook));
+
+	/* release the actual widget */
+	live_search_release_hook_widget (self);
+
+	/* connect handlers if new source is not null */
+	if (hook != NULL) {
+		self->pv->hook_widget = g_object_ref (hook);
+		g_signal_connect (self->pv->hook_widget, "key-press-event",
+		                  G_CALLBACK (on_hook_widget_key_press_event),
+		                  self);
+		g_signal_connect (self->pv->hook_widget, "destroy",
+		                  G_CALLBACK (on_hook_widget_destroy),
+		                  self);
+	}
+}
+
+const gchar *
+_gcr_live_search_get_text (GcrLiveSearch *self)
+{
+	g_return_val_if_fail (GCR_IS_LIVE_SEARCH (self), NULL);
+
+	return gtk_entry_get_text (GTK_ENTRY (self->pv->search_entry));
+}
+
+void
+_gcr_live_search_set_text (GcrLiveSearch *self, const gchar *text)
+{
+	g_return_if_fail (GCR_IS_LIVE_SEARCH (self));
+	g_return_if_fail (text != NULL);
+
+	gtk_entry_set_text (GTK_ENTRY (self->pv->search_entry), text);
+}
+
+/**
+ * _gcr_live_search_match:
+ * @self: a #GcrLiveSearch
+ * @string: a string where to search, must be valid UTF-8.
+ *
+ * Search if one of the words in @string string starts with the current text
+ * of @self.
+ *
+ * Searching for "aba" in "Abasto" will match, searching in "Moraba" will not,
+ * and searching in "A tool (abacus)" will do.
+ *
+ * The match is not case-sensitive, and regardless of the accentuation marks.
+ *
+ * Returns: %TRUE if a match is found, %FALSE otherwise.
+ *
+ **/
+gboolean
+_gcr_live_search_match (GcrLiveSearch *self, const gchar *string)
+{
+	g_return_val_if_fail (GCR_IS_LIVE_SEARCH (self), FALSE);
+
+	return _gcr_live_search_match_words (string, self->pv->stripped_words);
+}
+
+gboolean
+_gcr_live_search_match_string (const gchar *string, const gchar *prefix)
+{
+	GPtrArray *words;
+	gboolean match;
+
+	words = _gcr_live_search_strip_utf8_string (prefix);
+	match = _gcr_live_search_match_words (string, words);
+	if (words != NULL)
+		g_ptr_array_unref (words);
+
+	return match;
+}
+
+GPtrArray *
+_gcr_live_search_get_words (GcrLiveSearch *self)
+{
+	return self->pv->stripped_words;
+}
diff --git a/gcr/gcr-live-search.h b/gcr/gcr-live-search.h
new file mode 100644
index 0000000..7dff29b
--- /dev/null
+++ b/gcr/gcr-live-search.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2010 Collabora Ltd.
+ * Copyright (C) 2007-2010 Nokia Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Felix Kaser <felix kaser collabora co uk>
+ *          Xavier Claessens <xavier claessens collabora co uk>
+ *          Claudio Saavedra <csaavedra igalia com>
+ *          Stef Walter <stefw collabora co uk>
+ */
+
+/* Code borrowed from Empathy */
+
+#ifndef __GCR_LIVE_SEARCH_H__
+#define __GCR_LIVE_SEARCH_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GCR_TYPE_LIVE_SEARCH         (_gcr_live_search_get_type ())
+#define GCR_LIVE_SEARCH(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GCR_TYPE_LIVE_SEARCH, GcrLiveSearch))
+#define GCR_LIVE_SEARCH_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GCR_TYPE_LIVE_SEARCH, GcrLiveSearchClass))
+#define GCR_IS_LIVE_SEARCH(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GCR_TYPE_LIVE_SEARCH))
+#define GCR_IS_LIVE_SEARCH_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GCR_TYPE_LIVE_SEARCH))
+#define GCR_LIVE_SEARCH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GCR_TYPE_LIVE_SEARCH, GcrLiveSearchClass))
+
+typedef struct _GcrLiveSearch      GcrLiveSearch;
+typedef struct _GcrLiveSearchClass GcrLiveSearchClass;
+typedef struct _GcrLiveSearchPrivate GcrLiveSearchPrivate;
+
+struct _GcrLiveSearch {
+	GtkBox parent;
+
+	/* <private> */
+	GcrLiveSearchPrivate *pv;
+};
+
+struct _GcrLiveSearchClass {
+	GtkBoxClass parent_class;
+};
+
+GType             _gcr_live_search_get_type               (void) G_GNUC_CONST;
+
+GtkWidget *       _gcr_live_search_new                    (GtkWidget *hook);
+
+GtkWidget *       _gcr_live_search_get_hook_widget        (GcrLiveSearch *self);
+void              _gcr_live_search_set_hook_widget        (GcrLiveSearch *self,
+                                                           GtkWidget *hook);
+
+const gchar *     _gcr_live_search_get_text               (GcrLiveSearch *self);
+void              _gcr_live_search_set_text               (GcrLiveSearch *self,
+                                                           const gchar *text);
+
+gboolean          _gcr_live_search_match                  (GcrLiveSearch *self,
+                                                           const gchar *string);
+
+GPtrArray *       _gcr_live_search_strip_utf8_string      (const gchar *string);
+
+gboolean          _gcr_live_search_match_words            (const gchar *string,
+                                                           GPtrArray *words);
+
+GPtrArray *       _gcr_live_search_get_words              (GcrLiveSearch *self);
+
+/* Made public for unit tests */
+gboolean          _gcr_live_search_match_string           (const gchar *string,
+                                                           const gchar *prefix);
+
+G_END_DECLS
+
+#endif /* __GCR_LIVE_SEARCH_H__ */
diff --git a/gcr/gcr-marshal.list b/gcr/gcr-marshal.list
index 9a842f2..09f114f 100644
--- a/gcr/gcr-marshal.list
+++ b/gcr/gcr-marshal.list
@@ -1,4 +1,5 @@
 BOOLEAN:INT
+BOOLEAN:BOXED
 VOID:STRING,BOXED
 VOID:BOXED
 VOID:STRING
diff --git a/gcr/gcr.h b/gcr/gcr.h
index 12a90b2..332d8f1 100644
--- a/gcr/gcr.h
+++ b/gcr/gcr.h
@@ -44,6 +44,7 @@
 #include "gcr-key-widget.h"
 #include "gcr-importer.h"
 #include "gcr-library.h"
+#include "gcr-list-selector.h"
 #include "gcr-parser.h"
 #include "gcr-renderer.h"
 #include "gcr-pkcs11-certificate.h"
diff --git a/gcr/tests/frob-gnupg-selector.c b/gcr/tests/frob-gnupg-selector.c
index 477c132..444483e 100644
--- a/gcr/tests/frob-gnupg-selector.c
+++ b/gcr/tests/frob-gnupg-selector.c
@@ -26,6 +26,8 @@
 #include "gcr/gcr.h"
 #include "gcr/gcr-gnupg-collection.h"
 #include "gcr/gcr-gnupg-key.h"
+#include "gcr/gcr-list-selector-private.h"
+#include "gcr/gcr-live-search.h"
 
 #include <gtk/gtk.h>
 
@@ -49,9 +51,11 @@ int
 main (int argc, char *argv[])
 {
 	GcrCollection *collection;
-	GcrTreeSelector *selector;
+	GcrListSelector *selector;
 	GtkWidget *scroll;
 	GtkDialog *dialog;
+	GtkWidget *search;
+	GtkBox *box;
 
 	gtk_init (&argc, &argv);
 
@@ -59,15 +63,21 @@ main (int argc, char *argv[])
 	g_object_ref_sink (dialog);
 
 	collection = _gcr_gnupg_collection_new (NULL);
-	selector = gcr_tree_selector_new (collection, GCR_GNUPG_KEY_COLUMNS);
+	selector = gcr_list_selector_new (collection);
+	search = _gcr_live_search_new (GTK_WIDGET (selector));
+	_gcr_list_selector_set_live_search (selector, GCR_LIVE_SEARCH (search));
 
 	scroll = gtk_scrolled_window_new (NULL, NULL);
 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_ETCHED_IN);
 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
 	gtk_container_add (GTK_CONTAINER (scroll), GTK_WIDGET (selector));
 
-	gtk_widget_show_all (scroll);
-	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (dialog)), GTK_WIDGET (scroll));
+	box = GTK_BOX (gtk_dialog_get_content_area (dialog));
+	gtk_box_pack_start (box, GTK_WIDGET (scroll), TRUE, TRUE, 0);
+	gtk_box_pack_end (box, search, FALSE, TRUE, 0);
+
+	gtk_widget_show (GTK_WIDGET (selector));
+	gtk_widget_show (scroll);
 
 	_gcr_gnupg_collection_load_async (GCR_GNUPG_COLLECTION (collection), NULL,
 	                                  on_collection_loaded, NULL);



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