1 /*
2  * gnome-keyring
3  *
4  * Copyright (C) 2011 Collabora Ltd.
5  * Copyright (C) 2010 Stefan Walter
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU Lesser General Public License as
9  * published by the Free Software Foundation; either version 2.1 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
19  *
20  * Author: Stef Walter <stefw@collabora.co.uk>
21  */
22 
23 #include "config.h"
24 
25 #include "gcr/gcr-internal.h"
26 
27 #include "gcr-collection-model.h"
28 #include "gcr-list-selector.h"
29 #include "gcr-list-selector-private.h"
30 #include "gcr-live-search.h"
31 
32 #include <glib/gi18n-lib.h>
33 
34 #include <string.h>
35 
36 /**
37  * SECTION:gcr-list-selector
38  * @title: GcrListSelector
39  * @short_description: A selector widget to one or more certificates from a list.
40  *
41  * The #GcrListSelector can be used to select one or more certificates or keys.
42  * Live search is available for quick filtering.
43  */
44 
45 /**
46  * GcrListSelector:
47  *
48  * A list selector widget.
49  */
50 
51 /**
52  * GcrListSelectorClass:
53  *
54  * The class for #GcrListSelector.
55  */
56 
57 enum {
58 	PROP_0,
59 	PROP_COLLECTION
60 };
61 
62 struct _GcrListSelectorPrivate {
63 	GcrCollection *collection;
64 	GcrCollectionModel *model;
65 
66 	GtkTreeModelFilter *filter;
67 	GtkWidget *search_widget;
68 };
69 
70 G_DEFINE_TYPE_WITH_PRIVATE (GcrListSelector, gcr_list_selector, GTK_TYPE_TREE_VIEW);
71 
72 static gboolean
object_is_visible(GcrListSelector * self,GObject * object)73 object_is_visible (GcrListSelector *self, GObject *object)
74 {
75 	gchar *text;
76 	gboolean visible;
77 
78 	if (g_object_class_find_property (G_OBJECT_GET_CLASS (object), "search-text"))
79 		g_object_get (object, "search-text", &text, NULL);
80 	else
81 		g_object_get (object, "label", &text, NULL);
82 
83 	visible = _gcr_live_search_match (GCR_LIVE_SEARCH (self->pv->search_widget), text);
84 	g_free (text);
85 
86 	return visible;
87 }
88 
89 static gboolean
on_tree_filter_visible_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)90 on_tree_filter_visible_func (GtkTreeModel *model, GtkTreeIter *iter,
91                              gpointer user_data)
92 {
93 	GcrListSelector *self = GCR_LIST_SELECTOR (user_data);
94 	GObject *object;
95 
96 	if (self->pv->search_widget == NULL ||
97 	    !gtk_widget_get_visible (self->pv->search_widget))
98 		return TRUE;
99 
100 	object = gcr_collection_model_object_for_iter (self->pv->model, iter);
101 	if (object != NULL)
102 		return object_is_visible (self, object);
103 
104 	return FALSE;
105 }
106 
107 static gboolean
on_tree_view_start_search(GtkTreeView * view,gpointer user_data)108 on_tree_view_start_search (GtkTreeView *view, gpointer user_data)
109 {
110 	GcrListSelector *self = GCR_LIST_SELECTOR (view);
111 
112 	if (self->pv->search_widget == NULL)
113 		return FALSE;
114 
115 	if (gtk_widget_get_visible (self->pv->search_widget))
116 		gtk_widget_grab_focus (self->pv->search_widget);
117 	else
118 		gtk_widget_show (self->pv->search_widget);
119 
120 	return TRUE;
121 }
122 
123 static void
on_search_widget_text_notify(GcrLiveSearch * search,GParamSpec * pspec,gpointer user_data)124 on_search_widget_text_notify (GcrLiveSearch *search, GParamSpec *pspec,
125                               gpointer user_data)
126 {
127 	GcrListSelector *self = GCR_LIST_SELECTOR (user_data);
128 #if 0
129 	GtkTreeViewColumn *focus_column;
130 	GtkTreeModel *model;
131 	GtkTreeIter iter;
132 	GtkTreePath *path;
133 	gboolean set_cursor = FALSE;
134 #endif
135 
136 	gtk_tree_model_filter_refilter (self->pv->filter);
137 
138 #if 0
139 	/* Set cursor on the first object. */
140 
141 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
142 	gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
143 
144 	if (path == NULL) {
145 		path = gtk_tree_path_new_from_string ("0");
146 		set_cursor = TRUE;
147 	}
148 
149 	if (set_cursor) {
150 		/* FIXME: Workaround for GTK bug #621651, we have to make sure
151 		 * the path is valid. */
152 		if (gtk_tree_model_get_iter (model, &iter, path)) {
153 			gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path,
154 				focus_column, FALSE);
155 		}
156 	}
157 
158 	gtk_tree_path_free (path);
159 #endif
160 }
161 
162 static void
on_search_widget_activate(GtkWidget * search,gpointer user_data)163 on_search_widget_activate (GtkWidget *search, gpointer user_data)
164 {
165 	GcrListSelector *self = GCR_LIST_SELECTOR (user_data);
166 	GtkTreePath *path;
167 	GtkTreeViewColumn *focus_column;
168 
169 	gtk_tree_view_get_cursor (GTK_TREE_VIEW (self), &path, &focus_column);
170 	if (path != NULL) {
171 		gtk_tree_view_row_activated (GTK_TREE_VIEW (self), path, focus_column);
172 		gtk_tree_path_free (path);
173 
174 		gtk_widget_hide (search);
175 	}
176 }
177 
178 static gboolean
on_search_widget_key_navigation(GtkWidget * search,GdkEvent * event,gpointer user_data)179 on_search_widget_key_navigation (GtkWidget *search, GdkEvent *event, gpointer user_data)
180 {
181 	GcrListSelector *self = GCR_LIST_SELECTOR (user_data);
182 	GdkEvent *new_event;
183 	gboolean ret = FALSE;
184 
185 	new_event = gdk_event_copy (event);
186 	gtk_widget_grab_focus (GTK_WIDGET (self));
187 	ret = gtk_widget_event (GTK_WIDGET (self), new_event);
188 	gtk_widget_grab_focus (search);
189 
190 	gdk_event_free (new_event);
191 
192 	return ret;
193 }
194 
195 static void
on_check_column_toggled(GtkCellRendererToggle * cell,gchar * path,gpointer user_data)196 on_check_column_toggled (GtkCellRendererToggle *cell, gchar *path, gpointer user_data)
197 {
198 	GcrListSelector *self = GCR_LIST_SELECTOR (user_data);
199 	GtkTreeIter iter, model_iter;
200 
201 	g_assert (path != NULL);
202 
203 	if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (self->pv->filter), &iter, path)) {
204 		gtk_tree_model_filter_convert_iter_to_child_iter (self->pv->filter, &model_iter, &iter);
205 		gcr_collection_model_toggle_selected (self->pv->model, &model_iter);
206 	}
207 }
208 
209 static void
gcr_list_selector_constructed(GObject * object)210 gcr_list_selector_constructed (GObject *object)
211 {
212 	GcrListSelector *self = GCR_LIST_SELECTOR (object);
213 	GtkCellRenderer *cell;
214 	GtkTreeViewColumn *column;
215 	guint column_id;
216 
217 	G_OBJECT_CLASS (gcr_list_selector_parent_class)->constructed (object);
218 
219 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self), FALSE);
220 
221 	self->pv->model = gcr_collection_model_new (self->pv->collection,
222 	                                            GCR_COLLECTION_MODEL_LIST,
223 	                                            "icon", G_TYPE_ICON,
224 	                                            "markup", G_TYPE_STRING,
225 	                                            NULL);
226 
227 	self->pv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
228 	                                          GTK_TREE_MODEL (self->pv->model), NULL));
229 	gtk_tree_model_filter_set_visible_func (self->pv->filter,
230 	                                        on_tree_filter_visible_func, self, NULL);
231 
232 	gtk_tree_view_set_model (GTK_TREE_VIEW (self), GTK_TREE_MODEL (self->pv->filter));
233 
234 	/* The check */
235 
236 	cell = gtk_cell_renderer_toggle_new ();
237 	g_signal_connect (cell, "toggled", G_CALLBACK (on_check_column_toggled), self);
238 
239 	column_id = gcr_collection_model_column_for_selected (self->pv->model);
240 	column = gtk_tree_view_column_new_with_attributes ("", cell, "active", column_id, NULL);
241 	gtk_tree_view_column_set_resizable (column, FALSE);
242 	gtk_tree_view_append_column (GTK_TREE_VIEW (self), column);
243 
244 	column = gtk_tree_view_column_new ();
245 
246 	/* The icon */
247 	cell = gtk_cell_renderer_pixbuf_new ();
248 	g_object_set (cell, "stock-size", GTK_ICON_SIZE_DND, NULL);
249 	gtk_tree_view_column_pack_start (column, cell, FALSE);
250 	gtk_tree_view_column_add_attribute (column, cell, "gicon", 0);
251 
252 	/* The markup */
253 	cell = gtk_cell_renderer_text_new ();
254 	gtk_tree_view_column_pack_start (column, cell, TRUE);
255 	gtk_tree_view_column_add_attribute (column, cell, "markup", 1);
256 
257 	gtk_tree_view_append_column (GTK_TREE_VIEW (self), column);
258 }
259 
260 static void
gcr_list_selector_init(GcrListSelector * self)261 gcr_list_selector_init (GcrListSelector *self)
262 {
263 	self->pv = gcr_list_selector_get_instance_private (self);
264 }
265 
266 static void
gcr_list_selector_dispose(GObject * obj)267 gcr_list_selector_dispose (GObject *obj)
268 {
269 	GcrListSelector *self = GCR_LIST_SELECTOR (obj);
270 
271 	if (self->pv->filter)
272 		g_object_unref (self->pv->filter);
273 	self->pv->filter = NULL;
274 
275 	if (self->pv->model)
276 		g_object_unref (self->pv->model);
277 	self->pv->model = NULL;
278 
279 	if (self->pv->collection)
280 		g_object_unref (self->pv->collection);
281 	self->pv->collection = NULL;
282 
283 	_gcr_list_selector_set_live_search (self, NULL);
284 
285 	G_OBJECT_CLASS (gcr_list_selector_parent_class)->dispose (obj);
286 }
287 
288 static void
gcr_list_selector_finalize(GObject * obj)289 gcr_list_selector_finalize (GObject *obj)
290 {
291 	GcrListSelector *self = GCR_LIST_SELECTOR (obj);
292 
293 	g_assert (!self->pv->collection);
294 	g_assert (!self->pv->model);
295 
296 	G_OBJECT_CLASS (gcr_list_selector_parent_class)->finalize (obj);
297 }
298 
299 static void
gcr_list_selector_set_property(GObject * obj,guint prop_id,const GValue * value,GParamSpec * pspec)300 gcr_list_selector_set_property (GObject *obj, guint prop_id, const GValue *value,
301                                  GParamSpec *pspec)
302 {
303 	GcrListSelector *self = GCR_LIST_SELECTOR (obj);
304 
305 	switch (prop_id) {
306 	case PROP_COLLECTION:
307 		g_return_if_fail (!self->pv->collection);
308 		self->pv->collection = g_value_dup_object (value);
309 		g_return_if_fail (self->pv->collection);
310 		break;
311 	default:
312 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
313 		break;
314 	}
315 }
316 
317 static void
gcr_list_selector_get_property(GObject * obj,guint prop_id,GValue * value,GParamSpec * pspec)318 gcr_list_selector_get_property (GObject *obj, guint prop_id, GValue *value,
319                                  GParamSpec *pspec)
320 {
321 	GcrListSelector *self = GCR_LIST_SELECTOR (obj);
322 
323 	switch (prop_id) {
324 	case PROP_COLLECTION:
325 		g_value_set_object (value, gcr_list_selector_get_collection (self));
326 		break;
327 	default:
328 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
329 		break;
330 	}
331 }
332 
333 static void
gcr_list_selector_class_init(GcrListSelectorClass * klass)334 gcr_list_selector_class_init (GcrListSelectorClass *klass)
335 {
336 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
337 
338 	gobject_class->constructed = gcr_list_selector_constructed;
339 	gobject_class->dispose = gcr_list_selector_dispose;
340 	gobject_class->finalize = gcr_list_selector_finalize;
341 	gobject_class->set_property = gcr_list_selector_set_property;
342 	gobject_class->get_property = gcr_list_selector_get_property;
343 
344 	/**
345 	 * GcrListSelector:collection:
346 	 *
347 	 * The collection which contains the objects to display in the selector.
348 	 */
349 	g_object_class_install_property (gobject_class, PROP_COLLECTION,
350 	           g_param_spec_object ("collection", "Collection", "Collection to select from",
351 	                                GCR_TYPE_COLLECTION,
352 	                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
353 }
354 
355 /* -----------------------------------------------------------------------------
356  * PUBLIC
357  */
358 
359 /**
360  * gcr_list_selector_new:
361  * @collection: The collection that contains the objects to display
362  *
363  * Create a new #GcrListSelector.
364  *
365  * Returns: (transfer full): a newly allocated selector, which should be
366  *          released with g_object_unref()
367  */
368 GcrListSelector *
gcr_list_selector_new(GcrCollection * collection)369 gcr_list_selector_new (GcrCollection *collection)
370 {
371 	return g_object_new (GCR_TYPE_LIST_SELECTOR,
372 	                     "collection", collection,
373 	                     NULL);
374 }
375 
376 /**
377  * gcr_list_selector_get_collection:
378  * @self: The selector
379  *
380  * Get the collection that this selector is displaying objects from.
381  *
382  * Returns: (transfer none): The collection, owned by the selector.
383  */
384 GcrCollection *
gcr_list_selector_get_collection(GcrListSelector * self)385 gcr_list_selector_get_collection (GcrListSelector *self)
386 {
387 	g_return_val_if_fail (GCR_IS_LIST_SELECTOR (self), NULL);
388 	return self->pv->collection;
389 }
390 
391 /**
392  * gcr_list_selector_get_selected:
393  * @self: The selector
394  *
395  * Get a list of selected objects.
396  *
397  * Returns: (transfer container) (element-type GObject.Object): the list of
398  *          selected objects, to be released with g_list_free()
399  */
400 GList*
gcr_list_selector_get_selected(GcrListSelector * self)401 gcr_list_selector_get_selected (GcrListSelector *self)
402 {
403 	g_return_val_if_fail (GCR_IS_LIST_SELECTOR (self), NULL);
404 	return gcr_collection_model_get_selected_objects (self->pv->model);
405 }
406 
407 /**
408  * gcr_list_selector_set_selected:
409  * @self: The selector
410  * @selected: (element-type GObject.Object): the list of objects to select
411  *
412  * Select certain objects in the selector.
413  */
414 void
gcr_list_selector_set_selected(GcrListSelector * self,GList * selected)415 gcr_list_selector_set_selected (GcrListSelector *self, GList *selected)
416 {
417 	g_return_if_fail (GCR_IS_LIST_SELECTOR (self));
418 	gcr_collection_model_set_selected_objects (self->pv->model, selected);
419 }
420 
421 
422 void
_gcr_list_selector_set_live_search(GcrListSelector * self,GcrLiveSearch * search)423 _gcr_list_selector_set_live_search (GcrListSelector *self, GcrLiveSearch *search)
424 {
425 	g_return_if_fail (GCR_IS_LIST_SELECTOR (self));
426 
427 	/* remove old handlers if old search was not null */
428 	if (self->pv->search_widget != NULL) {
429 		g_signal_handlers_disconnect_by_func (self, on_tree_view_start_search, NULL);
430 
431 		g_signal_handlers_disconnect_by_func (self->pv->search_widget,
432 			on_search_widget_text_notify, self);
433 		g_signal_handlers_disconnect_by_func (self->pv->search_widget,
434 			on_search_widget_activate, self);
435 		g_signal_handlers_disconnect_by_func (self->pv->search_widget,
436 			on_search_widget_key_navigation, self);
437 		g_object_unref (self->pv->search_widget);
438 		self->pv->search_widget = NULL;
439 	}
440 
441 	/* connect handlers if new search is not null */
442 	if (search != NULL) {
443 		self->pv->search_widget = GTK_WIDGET (g_object_ref (search));
444 
445 		g_signal_connect (self, "start-interactive-search",
446 		                  G_CALLBACK (on_tree_view_start_search), NULL);
447 
448 		g_signal_connect (self->pv->search_widget, "notify::text",
449 			G_CALLBACK (on_search_widget_text_notify), self);
450 		g_signal_connect (self->pv->search_widget, "activate",
451 			G_CALLBACK (on_search_widget_activate), self);
452 		g_signal_connect (self->pv->search_widget, "key-navigation",
453 			G_CALLBACK (on_search_widget_key_navigation), self);
454 	}
455 }
456