Blob Blame History Raw
/*
 * 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, see <http://www.gnu.org/licenses/>.
 *
 * Author: Stef Walter <stefw@collabora.co.uk>
 */

#include "config.h"

#include "gcr/gcr-internal.h"

#include "gcr-collection-model.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 one or more certificates from a list.
 *
 * The #GcrListSelector can be used to select one or more certificates or keys.
 * Live search is available for quick filtering.
 */

/**
 * GcrListSelector:
 *
 * 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);

	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self), FALSE);

	self->pv->model = gcr_collection_model_new (self->pv->collection,
	                                            GCR_COLLECTION_MODEL_LIST,
	                                            "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));
}

/* -----------------------------------------------------------------------------
 * PUBLIC
 */

/**
 * gcr_list_selector_new:
 * @collection: The collection that contains the objects to display
 *
 * Create a new #GcrListSelector.
 *
 * Returns: (transfer full): 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: (transfer none): 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: (transfer container) (element-type GObject.Object): 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: (element-type GObject.Object): 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);
	}
}