1 /*
2  * gnome-keyring
3  *
4  * Copyright (C) 2010 Stefan Walter
5  * Copyright (C) 2011 Collabora Ltd.
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-collection-model.h"
26 
27 #include "ui/gcr-enum-types.h"
28 
29 #include <gtk/gtk.h>
30 
31 #include <string.h>
32 #include <unistd.h>
33 
34 /**
35  * SECTION:gcr-collection-model
36  * @title: GcrCollectionModel
37  * @short_description: A GtkTreeModel that represents a collection
38  *
39  * This is an implementation of #GtkTreeModel which represents the objects in
40  * the a #GcrCollection. As objects are added or removed from the collection,
41  * rows are added and removed from this model.
42  *
43  * The row values come from the properties of the objects in the collection. Use
44  * gcr_collection_model_new() to create a new collection model. To have more
45  * control over the values use a set of #GcrColumn structures to define the
46  * columns. This can be done with gcr_collection_model_new_full() or
47  * gcr_collection_model_set_columns().
48  *
49  * Each row can have a selected state, which is represented by a boolean column.
50  * The selected state can be toggled with gcr_collection_model_toggle_selected()
51  * or set with gcr_collection_model_set_selected_objects() and retrieved with
52  * gcr_collection_model_get_selected_objects().
53  *
54  * To determine which object a row represents and vice versa, use the
55  * gcr_collection_model_iter_for_object() or gcr_collection_model_object_for_iter()
56  * functions.
57  */
58 
59 /**
60  * GcrCollectionModel:
61  *
62  * A #GtkTreeModel which contains a row for each object in a #GcrCollection.
63  */
64 
65 /**
66  * GcrCollectionModelClass:
67  * @parent_class: The parent class
68  *
69  * The class for #GcrCollectionModel.
70  */
71 
72 /**
73  * GcrCollectionModelMode:
74  * @GCR_COLLECTION_MODEL_LIST: only objects in the top collection, no child objects
75  * @GCR_COLLECTION_MODEL_TREE: show objects in the collection, and child objects in a tree form
76  *
77  * If set GcrCollectionModel is created with a mode of %GCR_COLLECTION_MODEL_TREE,
78  * then any included objects that are themselves a #GcrCollection, will have all child
79  * objects include as child rows in a tree form.
80  */
81 
82 #define COLLECTION_MODEL_STAMP 0xAABBCCDD
83 
84 enum {
85 	PROP_0,
86 	PROP_COLLECTION,
87 	PROP_COLUMNS,
88 	PROP_MODE
89 };
90 
91 typedef struct {
92 	GObject *object;
93 	GSequenceIter *parent;
94 	GSequence *children;
95 } GcrCollectionRow;
96 
97 typedef struct {
98 	GtkTreeIterCompareFunc sort_func;
99 	gpointer user_data;
100 	GDestroyNotify destroy_func;
101 } GcrCollectionSortClosure;
102 
103 typedef struct _GcrCollectionColumn {
104 	gchar *property;
105 	GType *type;
106 	GtkTreeIterCompareFunc sort_func;
107 	gpointer sort_data;
108 	GDestroyNotify sort_destroy;
109 } GcrCollectionColumn;
110 
111 struct _GcrCollectionModelPrivate {
112 	GcrCollectionModelMode mode;
113 	GcrCollection *collection;
114 	GHashTable *selected;
115 	GSequence *root_sequence;
116 	GHashTable *object_to_seq;
117 
118 	const GcrColumn *columns;
119 	guint n_columns;
120 
121 	/* Sort information */
122 	gint sort_column_id;
123 	GtkSortType sort_order_type;
124 	GcrCollectionSortClosure *column_sort_closures;
125 	GcrCollectionSortClosure default_sort_closure;
126 
127 	/* Sequence ordering information */
128 	GCompareDataFunc order_current;
129 	gpointer order_argument;
130 };
131 
132 /* Forward declarations */
133 static void gcr_collection_model_tree_model_init (GtkTreeModelIface *iface);
134 static void gcr_collection_model_tree_sortable_init (GtkTreeSortableIface *iface);
135 
136 G_DEFINE_TYPE_EXTENDED (GcrCollectionModel, gcr_collection_model, G_TYPE_OBJECT, 0,
137                         G_ADD_PRIVATE (GcrCollectionModel);
138                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, gcr_collection_model_tree_model_init)
139                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE, gcr_collection_model_tree_sortable_init)
140 );
141 
142 typedef gint (*CompareValueFunc) (const GValue *va,
143                                   const GValue *vb);
144 
145 static gint
compare_int_value(const GValue * va,const GValue * vb)146 compare_int_value (const GValue *va,
147                    const GValue *vb)
148 {
149 	gint a = g_value_get_int (va);
150 	gint b = g_value_get_int (vb);
151 	if (a > b) return 1;
152 	else if (a < b) return -1;
153 	return 0;
154 }
155 
156 static gint
compare_uint_value(const GValue * va,const GValue * vb)157 compare_uint_value (const GValue *va,
158                     const GValue *vb)
159 {
160 	guint a = g_value_get_uint (va);
161 	guint b = g_value_get_uint (vb);
162 	if (a > b) return 1;
163 	else if (a < b) return -1;
164 	return 0;
165 }
166 
167 static gint
compare_long_value(const GValue * va,const GValue * vb)168 compare_long_value (const GValue *va,
169                     const GValue *vb)
170 {
171 	glong a = g_value_get_long (va);
172 	glong b = g_value_get_long (vb);
173 	if (a > b) return 1;
174 	else if (a < b) return -1;
175 	return 0;
176 }
177 
178 static gint
compare_ulong_value(const GValue * va,const GValue * vb)179 compare_ulong_value (const GValue *va,
180                      const GValue *vb)
181 {
182 	gulong a = g_value_get_ulong (va);
183 	gulong b = g_value_get_ulong (vb);
184 	if (a > b) return 1;
185 	else if (a < b) return -1;
186 	return 0;
187 }
188 
189 static gint
compare_string_value(const GValue * va,const GValue * vb)190 compare_string_value (const GValue *va,
191                       const GValue *vb)
192 {
193 	const gchar *a = g_value_get_string (va);
194 	const gchar *b = g_value_get_string (vb);
195 	gchar *case_a;
196 	gchar *case_b;
197 	gboolean ret;
198 
199 	if (a == b)
200 		return 0;
201 	else if (!a)
202 		return -1;
203 	else if (!b)
204 		return 1;
205 
206 	case_a = g_utf8_casefold (a, -1);
207 	case_b = g_utf8_casefold (b, -1);
208 	ret = g_utf8_collate (case_a, case_b);
209 	g_free (case_a);
210 	g_free (case_b);
211 
212 	return ret;
213 }
214 
215 static gint
compare_date_value(const GValue * va,const GValue * vb)216 compare_date_value (const GValue *va,
217                     const GValue *vb)
218 {
219 	GDate *a = g_value_get_boxed (va);
220 	GDate *b = g_value_get_boxed (vb);
221 
222 	if (a == b)
223 		return 0;
224 	else if (!a)
225 		return -1;
226 	else if (!b)
227 		return 1;
228 	else
229 		return g_date_compare (a, b);
230 }
231 
232 static CompareValueFunc
lookup_compare_func(GType type)233 lookup_compare_func (GType type)
234 {
235 	switch (type) {
236 	case G_TYPE_INT:
237 		return compare_int_value;
238 	case G_TYPE_UINT:
239 		return compare_uint_value;
240 	case G_TYPE_LONG:
241 		return compare_long_value;
242 	case G_TYPE_ULONG:
243 		return compare_ulong_value;
244 	case G_TYPE_STRING:
245 		return compare_string_value;
246 	}
247 
248 	if (type == G_TYPE_DATE)
249 		return compare_date_value;
250 
251 	return NULL;
252 }
253 
254 static gint
order_sequence_by_closure(gconstpointer a,gconstpointer b,gpointer user_data)255 order_sequence_by_closure (gconstpointer a,
256                            gconstpointer b,
257                            gpointer user_data)
258 {
259 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (user_data);
260 	GcrCollectionSortClosure *closure = self->pv->order_argument;
261 	const GcrCollectionRow *row_a = a;
262 	const GcrCollectionRow *row_b = b;
263 	GtkTreeIter iter_a;
264 	GtkTreeIter iter_b;
265 
266 	g_assert (closure);
267 	g_assert (closure->sort_func);
268 
269 	if (!gcr_collection_model_iter_for_object (self, row_a->object, &iter_a))
270 		g_return_val_if_reached (0);
271 	if (!gcr_collection_model_iter_for_object (self, row_b->object, &iter_b))
272 		g_return_val_if_reached (0);
273 
274 	return (closure->sort_func) (GTK_TREE_MODEL (self),
275 	                             &iter_a, &iter_b, closure->user_data);
276 }
277 
278 static gint
order_sequence_by_closure_reverse(gconstpointer a,gconstpointer b,gpointer user_data)279 order_sequence_by_closure_reverse (gconstpointer a,
280                                    gconstpointer b,
281                                    gpointer user_data)
282 {
283 	return 0 - order_sequence_by_closure (a, b, user_data);
284 }
285 
286 static gint
order_sequence_as_unsorted(gconstpointer a,gconstpointer b,gpointer user_data)287 order_sequence_as_unsorted (gconstpointer a,
288                             gconstpointer b,
289                             gpointer user_data)
290 {
291 	const GcrCollectionRow *row_a = a;
292 	const GcrCollectionRow *row_b = b;
293 	return GPOINTER_TO_INT (row_a->object) - GPOINTER_TO_INT (row_b->object);
294 }
295 
296 static gint
order_sequence_as_unsorted_reverse(gconstpointer a,gconstpointer b,gpointer user_data)297 order_sequence_as_unsorted_reverse (gconstpointer a,
298                                     gconstpointer b,
299                                     gpointer user_data)
300 {
301 	const GcrCollectionRow *row_a = a;
302 	const GcrCollectionRow *row_b = b;
303 	return GPOINTER_TO_INT (row_b->object) - GPOINTER_TO_INT (row_a->object);
304 }
305 
306 static void
lookup_object_property(GObject * object,const gchar * property_name,GValue * value)307 lookup_object_property (GObject *object,
308                         const gchar *property_name,
309                         GValue *value)
310 {
311 	if (g_object_class_find_property (G_OBJECT_GET_CLASS (object), property_name))
312 		g_object_get_property (object, property_name, value);
313 
314 	/* Other types have sane defaults */
315 	else if (G_VALUE_TYPE (value) == G_TYPE_STRING)
316 		g_value_set_string (value, "");
317 }
318 
319 static gint
order_sequence_by_property(gconstpointer a,gconstpointer b,gpointer user_data)320 order_sequence_by_property (gconstpointer a,
321                             gconstpointer b,
322                             gpointer user_data)
323 {
324 	const GcrCollectionRow *row_a = a;
325 	const GcrCollectionRow *row_b = b;
326 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (user_data);
327 	const GcrColumn *column = self->pv->order_argument;
328 	GValue value_a = { 0, };
329 	GValue value_b = { 0, };
330 	CompareValueFunc compare;
331 	gint ret;
332 
333 	g_assert (column);
334 
335 	/* Sort according to property values */
336 	column = &self->pv->columns[self->pv->sort_column_id];
337 	g_value_init (&value_a, column->property_type);
338 	lookup_object_property (row_a->object, column->property_name, &value_a);
339 	g_value_init (&value_b, column->property_type);
340 	lookup_object_property (row_b->object, column->property_name, &value_b);
341 
342 	compare = lookup_compare_func (column->property_type);
343 	g_assert (compare != NULL);
344 
345 	ret = (compare) (&value_a, &value_b);
346 
347 	g_value_unset (&value_a);
348 	g_value_unset (&value_b);
349 
350 	return ret;
351 }
352 
353 static gint
order_sequence_by_property_reverse(gconstpointer a,gconstpointer b,gpointer user_data)354 order_sequence_by_property_reverse (gconstpointer a,
355                                     gconstpointer b,
356                                     gpointer user_data)
357 {
358 	return 0 - order_sequence_by_property (a, b, user_data);
359 }
360 
361 static GHashTable*
selected_hash_table_new(void)362 selected_hash_table_new (void)
363 {
364 	return g_hash_table_new (g_direct_hash, g_direct_equal);
365 }
366 
367 static gboolean
sequence_iter_to_tree(GcrCollectionModel * self,GSequenceIter * seq,GtkTreeIter * iter)368 sequence_iter_to_tree (GcrCollectionModel *self,
369                        GSequenceIter *seq,
370                        GtkTreeIter *iter)
371 {
372 	GcrCollectionRow *row;
373 
374 	g_return_val_if_fail (seq != NULL, FALSE);
375 
376 	if (g_sequence_iter_is_end (seq))
377 		return FALSE;
378 
379 	row = g_sequence_get (seq);
380 	g_return_val_if_fail (row != NULL && G_IS_OBJECT (row->object), FALSE);
381 
382 	memset (iter, 0, sizeof (*iter));
383 	iter->stamp = COLLECTION_MODEL_STAMP;
384 	iter->user_data = row->object;
385 	iter->user_data2 = seq;
386 	return TRUE;
387 }
388 
389 static GSequenceIter *
sequence_iter_for_tree(GcrCollectionModel * self,GtkTreeIter * iter)390 sequence_iter_for_tree (GcrCollectionModel *self,
391                         GtkTreeIter *iter)
392 {
393 	g_return_val_if_fail (iter != NULL, NULL);
394 	g_return_val_if_fail (iter->stamp == COLLECTION_MODEL_STAMP, NULL);
395 	return iter->user_data2;
396 }
397 
398 static GtkTreePath *
sequence_iter_to_path(GcrCollectionModel * self,GSequenceIter * seq)399 sequence_iter_to_path (GcrCollectionModel *self,
400                        GSequenceIter *seq)
401 {
402 	GcrCollectionRow *row;
403 	GtkTreePath *path;
404 
405 	path = gtk_tree_path_new ();
406 	while (seq) {
407 		gtk_tree_path_prepend_index (path, g_sequence_iter_get_position (seq));
408 		row = g_sequence_get (seq);
409 		seq = row->parent;
410 	}
411 	return path;
412 }
413 
414 static GSequence *
child_sequence_for_tree(GcrCollectionModel * self,GtkTreeIter * iter)415 child_sequence_for_tree (GcrCollectionModel *self,
416                          GtkTreeIter *iter)
417 {
418 	GcrCollectionRow *row;
419 	GSequenceIter *seq;
420 
421 	if (iter == NULL) {
422 		return self->pv->root_sequence;
423 	} else {
424 		seq = sequence_iter_for_tree (self, iter);
425 		g_return_val_if_fail (seq != NULL, NULL);
426 		row = g_sequence_get (seq);
427 		return row->children;
428 	}
429 }
430 
431 static void
on_object_notify(GObject * object,GParamSpec * spec,GcrCollectionModel * self)432 on_object_notify (GObject *object, GParamSpec *spec, GcrCollectionModel *self)
433 {
434 	GtkTreeIter iter;
435 	GtkTreePath *path;
436 	gboolean found = FALSE;
437 	guint i;
438 
439 	g_return_if_fail (spec->name);
440 
441 	for (i = 0; i < self->pv->n_columns - 1; ++i) {
442 		g_assert (self->pv->columns[i].property_name);
443 		if (g_str_equal (self->pv->columns[i].property_name, spec->name)) {
444 			found = TRUE;
445 			break;
446 		}
447 	}
448 
449 	/* Tell the tree view that this row changed */
450 	if (found) {
451 		if (!gcr_collection_model_iter_for_object (self, object, &iter))
452 			g_return_if_reached ();
453 		path = gtk_tree_model_get_path (GTK_TREE_MODEL (self), &iter);
454 		g_return_if_fail (path);
455 		gtk_tree_model_row_changed (GTK_TREE_MODEL (self), path, &iter);
456 		gtk_tree_path_free (path);
457 	}
458 }
459 
460 static void
on_object_gone(gpointer unused,GObject * was_object)461 on_object_gone (gpointer unused, GObject *was_object)
462 {
463 	g_warning ("object contained in GcrCollection and included in GcrCollectionModel "
464 	           "was destroyed before it was removed from the collection");
465 }
466 
467 static void      on_collection_added              (GcrCollection *collection,
468                                                    GObject *object,
469                                                    gpointer user_data);
470 
471 static void      on_collection_removed            (GcrCollection *collection,
472                                                    GObject *object,
473                                                    gpointer user_data);
474 
475 static void      add_object_to_sequence           (GcrCollectionModel *self,
476                                                    GSequence *sequence,
477                                                    GSequenceIter *parent,
478                                                    GObject *object,
479                                                    gboolean emit);
480 
481 static void      remove_object_from_sequence      (GcrCollectionModel *self,
482                                                    GSequence *sequence,
483                                                    GSequenceIter *seq,
484                                                    GObject *object,
485                                                    gboolean emit);
486 
487 static void
add_children_to_sequence(GcrCollectionModel * self,GSequence * sequence,GSequenceIter * parent,GcrCollection * collection,GList * children,GHashTable * exclude,gboolean emit)488 add_children_to_sequence (GcrCollectionModel *self,
489                           GSequence *sequence,
490                           GSequenceIter *parent,
491                           GcrCollection *collection,
492                           GList *children,
493                           GHashTable *exclude,
494                           gboolean emit)
495 {
496 	GList *l;
497 
498 	for (l = children; l; l = g_list_next (l)) {
499 		if (!exclude || g_hash_table_lookup (exclude, l->data) == NULL)
500 			add_object_to_sequence (self, sequence, parent, l->data, emit);
501 	}
502 
503 	/* Now listen in for any changes */
504 	g_signal_connect_after (collection, "added", G_CALLBACK (on_collection_added), self);
505 	g_signal_connect_after (collection, "removed", G_CALLBACK (on_collection_removed), self);
506 }
507 
508 static void
add_object_to_sequence(GcrCollectionModel * self,GSequence * sequence,GSequenceIter * parent,GObject * object,gboolean emit)509 add_object_to_sequence (GcrCollectionModel *self,
510                         GSequence *sequence,
511                         GSequenceIter *parent,
512                         GObject *object,
513                         gboolean emit)
514 {
515 	GcrCollectionRow *row;
516 	GcrCollection *collection;
517 	GSequenceIter *seq;
518 	GtkTreeIter iter;
519 	GtkTreePath *path;
520 	GList *children;
521 
522 	g_assert (GCR_IS_COLLECTION_MODEL (self));
523 	g_assert (G_IS_OBJECT (object));
524 	g_assert (self->pv->order_current);
525 
526 	if (g_hash_table_lookup (self->pv->object_to_seq, object)) {
527 		g_warning ("object was already added to the GcrCollectionModel. Perhaps "
528 		           "a loop exists in a tree structure?");
529 		return;
530 	}
531 
532 	row = g_slice_new0 (GcrCollectionRow);
533 	row->object = object;
534 	row->parent = parent;
535 	row->children = NULL;
536 
537 	seq = g_sequence_insert_sorted (sequence, row, self->pv->order_current, self);
538 	g_hash_table_insert (self->pv->object_to_seq, object, seq);
539 	g_object_weak_ref (G_OBJECT (object), (GWeakNotify)on_object_gone, self);
540 	g_signal_connect (object, "notify", G_CALLBACK (on_object_notify), self);
541 
542 	if (emit) {
543 		if (!sequence_iter_to_tree (self, seq, &iter))
544 			g_assert_not_reached ();
545 		path = sequence_iter_to_path (self, seq);
546 		g_assert (path != NULL);
547 		gtk_tree_model_row_inserted (GTK_TREE_MODEL (self), path, &iter);
548 		gtk_tree_path_free (path);
549 	}
550 
551 	if (self->pv->mode == GCR_COLLECTION_MODEL_TREE &&
552 	    GCR_IS_COLLECTION (object)) {
553 		row->children = g_sequence_new (NULL);
554 		collection = GCR_COLLECTION (object);
555 		children = gcr_collection_get_objects (collection);
556 		add_children_to_sequence (self, row->children, seq,
557 		                          collection, children, NULL, emit);
558 		g_list_free (children);
559 	}
560 }
561 
562 static void
on_collection_added(GcrCollection * collection,GObject * object,gpointer user_data)563 on_collection_added (GcrCollection *collection,
564                      GObject *object,
565                      gpointer user_data)
566 {
567 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (user_data);
568 	GSequence *sequence;
569 	GSequenceIter *parent;
570 	GcrCollectionRow *row;
571 
572 	if (collection == self->pv->collection) {
573 		sequence = self->pv->root_sequence;
574 		parent = NULL;
575 	} else {
576 		parent = g_hash_table_lookup (self->pv->object_to_seq, G_OBJECT (collection));
577 		row = g_sequence_get (parent);
578 		g_assert (row->children);
579 		sequence = row->children;
580 	}
581 
582 	add_object_to_sequence (self, sequence, parent, object, TRUE);
583 }
584 
585 static void
remove_children_from_sequence(GcrCollectionModel * self,GSequence * sequence,GcrCollection * collection,GHashTable * exclude,gboolean emit)586 remove_children_from_sequence (GcrCollectionModel *self,
587                                GSequence *sequence,
588                                GcrCollection *collection,
589                                GHashTable *exclude,
590                                gboolean emit)
591 {
592 	GSequenceIter *seq, *next;
593 	GcrCollectionRow *row;
594 
595 	g_signal_handlers_disconnect_by_func (collection, on_collection_added, self);
596 	g_signal_handlers_disconnect_by_func (collection, on_collection_removed, self);
597 
598 	for (seq = g_sequence_get_begin_iter (sequence);
599 	     !g_sequence_iter_is_end (seq); seq = next) {
600 		next = g_sequence_iter_next (seq);
601 		row = g_sequence_get (seq);
602 		if (!exclude || g_hash_table_lookup (exclude, row->object) == NULL)
603 			remove_object_from_sequence (self, sequence, seq, row->object, emit);
604 	}
605 }
606 
607 static void
remove_object_from_sequence(GcrCollectionModel * self,GSequence * sequence,GSequenceIter * seq,GObject * object,gboolean emit)608 remove_object_from_sequence (GcrCollectionModel *self,
609                              GSequence *sequence,
610                              GSequenceIter *seq,
611                              GObject *object,
612                              gboolean emit)
613 {
614 	GcrCollectionRow *row;
615 	GtkTreePath *path = NULL;
616 
617 	if (emit) {
618 		path = sequence_iter_to_path (self, seq);
619 		g_assert (path != NULL);
620 	}
621 
622 	row = g_sequence_get (seq);
623 	g_assert (row->object == object);
624 
625 	g_object_weak_unref (object, on_object_gone, self);
626 	g_signal_handlers_disconnect_by_func (object, on_object_notify, self);
627 
628 	if (row->children) {
629 		g_assert (self->pv->mode == GCR_COLLECTION_MODEL_TREE);
630 		g_assert (GCR_IS_COLLECTION (object));
631 		remove_children_from_sequence (self, row->children,
632 		                               GCR_COLLECTION (object), NULL, emit);
633 		g_assert (g_sequence_get_length (row->children) == 0);
634 		g_sequence_free (row->children);
635 		row->children = NULL;
636 	}
637 
638 	if (self->pv->selected)
639 		g_hash_table_remove (self->pv->selected, object);
640 	if (!g_hash_table_remove (self->pv->object_to_seq, object))
641 		g_assert_not_reached ();
642 
643 	g_sequence_remove (seq);
644 	g_slice_free (GcrCollectionRow, row);
645 
646 	/* Fire signal for this removed row */
647 	if (path != NULL) {
648 		gtk_tree_model_row_deleted (GTK_TREE_MODEL (self), path);
649 		gtk_tree_path_free (path);
650 	}
651 
652 }
653 
654 static void
on_collection_removed(GcrCollection * collection,GObject * object,gpointer user_data)655 on_collection_removed (GcrCollection *collection,
656                        GObject *object,
657                        gpointer user_data)
658 {
659 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (user_data);
660 	GSequenceIter *seq;
661 	GSequence *sequence;
662 
663 	seq = g_hash_table_lookup (self->pv->object_to_seq, object);
664 	g_return_if_fail (seq != NULL);
665 
666 	sequence = g_sequence_iter_get_sequence (seq);
667 	g_assert (sequence != NULL);
668 
669 	remove_object_from_sequence (self, sequence, seq, object, TRUE);
670 }
671 
672 static void
free_owned_columns(gpointer data)673 free_owned_columns (gpointer data)
674 {
675 	GcrColumn *columns;
676 	g_assert (data);
677 
678 	/* Only the property column is in use */
679 	for (columns = data; columns->property_name; ++columns)
680 		g_free ((gchar*)columns->property_name);
681 	g_free (data);
682 }
683 
684 static GtkTreeModelFlags
gcr_collection_model_real_get_flags(GtkTreeModel * model)685 gcr_collection_model_real_get_flags (GtkTreeModel *model)
686 {
687 	return GTK_TREE_MODEL_ITERS_PERSIST;
688 }
689 
690 static gint
gcr_collection_model_real_get_n_columns(GtkTreeModel * model)691 gcr_collection_model_real_get_n_columns (GtkTreeModel *model)
692 {
693 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
694 	return self->pv->n_columns;
695 }
696 
697 static GType
gcr_collection_model_real_get_column_type(GtkTreeModel * model,gint column_id)698 gcr_collection_model_real_get_column_type (GtkTreeModel *model,
699                                            gint column_id)
700 {
701 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
702 	g_return_val_if_fail (column_id >= 0 && column_id <= self->pv->n_columns, 0);
703 
704 	/* The last is the selected column */
705 	if (column_id == self->pv->n_columns)
706 		return G_TYPE_BOOLEAN;
707 
708 	return self->pv->columns[column_id].column_type;
709 }
710 
711 static gboolean
gcr_collection_model_real_get_iter(GtkTreeModel * model,GtkTreeIter * iter,GtkTreePath * path)712 gcr_collection_model_real_get_iter (GtkTreeModel *model,
713                                     GtkTreeIter *iter,
714                                     GtkTreePath *path)
715 {
716 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
717 	const gint *indices;
718 	GSequence *sequence;
719 	GSequenceIter *seq;
720 	GcrCollectionRow *row;
721 	gint count;
722 	gint i;
723 
724 	sequence = self->pv->root_sequence;
725 	seq = NULL;
726 
727 	indices = gtk_tree_path_get_indices_with_depth (path, &count);
728 	if (count == 0)
729 		return FALSE;
730 
731 	for (i = 0; i < count; i++) {
732 		if (!sequence)
733 			return FALSE;
734 		seq = g_sequence_get_iter_at_pos (sequence, indices[i]);
735 		if (g_sequence_iter_is_end (seq))
736 			return FALSE;
737 		row = g_sequence_get (seq);
738 		sequence = row->children;
739 	}
740 
741 	return sequence_iter_to_tree (self, seq, iter);
742 }
743 
744 static GtkTreePath*
gcr_collection_model_real_get_path(GtkTreeModel * model,GtkTreeIter * iter)745 gcr_collection_model_real_get_path (GtkTreeModel *model,
746                                     GtkTreeIter *iter)
747 {
748 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
749 	GSequenceIter *seq;
750 
751 	if (iter == NULL)
752 		return gtk_tree_path_new ();
753 
754 	seq = sequence_iter_for_tree (self, iter);
755 	g_return_val_if_fail (seq != NULL, NULL);
756 	return sequence_iter_to_path (self, seq);
757 }
758 
759 static void
gcr_collection_model_real_get_value(GtkTreeModel * model,GtkTreeIter * iter,gint column_id,GValue * value)760 gcr_collection_model_real_get_value (GtkTreeModel *model,
761                                      GtkTreeIter *iter,
762                                      gint column_id,
763                                      GValue *value)
764 {
765 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
766 	GObject *object;
767 	GValue original;
768 	const GcrColumn *column;
769 	GParamSpec *spec;
770 
771 	object = gcr_collection_model_object_for_iter (self, iter);
772 	g_return_if_fail (G_IS_OBJECT (object));
773 	g_return_if_fail (column_id >= 0 && column_id < self->pv->n_columns);
774 
775 	/* The selected column? Last one */
776 	if (column_id == self->pv->n_columns - 1) {
777 		g_value_init (value, G_TYPE_BOOLEAN);
778 		g_value_set_boolean (value, gcr_collection_model_is_selected (self, iter));
779 		return;
780 	}
781 
782 	/* Figure out which property */
783 	column = &self->pv->columns[column_id];
784 	g_assert (column->property_name);
785 	g_value_init (value, column->column_type);
786 
787 	/* Lookup the property on the object */
788 	spec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), column->property_name);
789 	if (spec != NULL) {
790 		/* A transformer is specified, or mismatched types */
791 		if (column->transformer || column->column_type != column->property_type) {
792 			memset (&original, 0, sizeof (original));
793 			g_value_init (&original, column->property_type);
794 			g_object_get_property (object, column->property_name, &original);
795 
796 			if (column->transformer) {
797 				(column->transformer) (&original, value);
798 			} else {
799 				g_warning ("%s property of %s class was of type %s instead of type %s"
800 				           " and cannot be converted due to lack of transformer",
801 				           column->property_name, G_OBJECT_TYPE_NAME (object),
802 				           g_type_name (column->property_type),
803 				           g_type_name (column->column_type));
804 				spec = NULL;
805 			}
806 
807 		/* Simple, no transformation necessary */
808 		} else {
809 			g_object_get_property (object, column->property_name, value);
810 		}
811 	}
812 
813 	if (spec == NULL) {
814 
815 		/* All the number types have sane defaults */
816 		if (column->column_type == G_TYPE_STRING)
817 			g_value_set_string (value, "");
818 	}
819 }
820 
821 static gboolean
gcr_collection_model_real_iter_next(GtkTreeModel * model,GtkTreeIter * iter)822 gcr_collection_model_real_iter_next (GtkTreeModel *model,
823                                      GtkTreeIter *iter)
824 {
825 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
826 	GSequenceIter *seq = sequence_iter_for_tree (self, iter);
827 	g_return_val_if_fail (seq != NULL, FALSE);
828 	return sequence_iter_to_tree (self, g_sequence_iter_next (seq), iter);
829 }
830 
831 static gboolean
gcr_collection_model_real_iter_children(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * parent)832 gcr_collection_model_real_iter_children (GtkTreeModel *model,
833                                          GtkTreeIter *iter,
834                                          GtkTreeIter *parent)
835 {
836 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
837 	GSequence *sequence = child_sequence_for_tree (self, parent);
838 	return sequence && sequence_iter_to_tree (self, g_sequence_get_begin_iter (sequence), iter);
839 }
840 
841 static gboolean
gcr_collection_model_real_iter_has_child(GtkTreeModel * model,GtkTreeIter * iter)842 gcr_collection_model_real_iter_has_child (GtkTreeModel *model,
843                                           GtkTreeIter *iter)
844 {
845 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
846 	GSequence *sequence = child_sequence_for_tree (self, iter);
847 	return sequence && !g_sequence_iter_is_end (g_sequence_get_begin_iter (sequence));
848 }
849 
850 static gint
gcr_collection_model_real_iter_n_children(GtkTreeModel * model,GtkTreeIter * iter)851 gcr_collection_model_real_iter_n_children (GtkTreeModel *model,
852                                            GtkTreeIter *iter)
853 {
854 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
855 	GSequence *sequence = child_sequence_for_tree (self, iter);
856 	return sequence ? g_sequence_get_length (sequence) : 0;
857 }
858 
859 static gboolean
gcr_collection_model_real_iter_nth_child(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * parent,gint n)860 gcr_collection_model_real_iter_nth_child (GtkTreeModel *model,
861                                           GtkTreeIter *iter,
862                                           GtkTreeIter *parent,
863                                           gint n)
864 {
865 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
866 	GSequence *sequence;
867 	GSequenceIter *seq;
868 
869 	sequence = child_sequence_for_tree (self, parent);
870 	if (sequence == NULL)
871 		return FALSE;
872 	seq = g_sequence_get_iter_at_pos (sequence, n);
873 	return sequence_iter_to_tree (self, seq, iter);
874 }
875 
876 static gboolean
gcr_collection_model_real_iter_parent(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * child)877 gcr_collection_model_real_iter_parent (GtkTreeModel *model,
878                                        GtkTreeIter *iter,
879                                        GtkTreeIter *child)
880 {
881 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (model);
882 	GSequenceIter *seq;
883 	GcrCollectionRow *row;
884 
885 	seq = sequence_iter_for_tree (self, child);
886 	g_return_val_if_fail (seq != NULL, FALSE);
887 	row = g_sequence_get (seq);
888 	if (row->parent == NULL)
889 		return FALSE;
890 	return sequence_iter_to_tree (self, row->parent, iter);
891 }
892 
893 static void
gcr_collection_model_real_ref_node(GtkTreeModel * model,GtkTreeIter * iter)894 gcr_collection_model_real_ref_node (GtkTreeModel *model,
895                                     GtkTreeIter *iter)
896 {
897 	/* Nothing to do */
898 }
899 
900 static void
gcr_collection_model_real_unref_node(GtkTreeModel * model,GtkTreeIter * iter)901 gcr_collection_model_real_unref_node (GtkTreeModel *model,
902                                       GtkTreeIter *iter)
903 {
904 	/* Nothing to do */
905 }
906 
907 static void
gcr_collection_model_tree_model_init(GtkTreeModelIface * iface)908 gcr_collection_model_tree_model_init (GtkTreeModelIface *iface)
909 {
910 	iface->get_flags = gcr_collection_model_real_get_flags;
911 	iface->get_n_columns = gcr_collection_model_real_get_n_columns;
912 	iface->get_column_type = gcr_collection_model_real_get_column_type;
913 	iface->get_iter = gcr_collection_model_real_get_iter;
914 	iface->get_path = gcr_collection_model_real_get_path;
915 	iface->get_value = gcr_collection_model_real_get_value;
916 	iface->iter_next = gcr_collection_model_real_iter_next;
917 	iface->iter_children = gcr_collection_model_real_iter_children;
918 	iface->iter_has_child = gcr_collection_model_real_iter_has_child;
919 	iface->iter_n_children = gcr_collection_model_real_iter_n_children;
920 	iface->iter_nth_child = gcr_collection_model_real_iter_nth_child;
921 	iface->iter_parent = gcr_collection_model_real_iter_parent;
922 	iface->ref_node = gcr_collection_model_real_ref_node;
923 	iface->unref_node = gcr_collection_model_real_unref_node;
924 }
925 
926 static void
collection_resort_sequence(GcrCollectionModel * self,GSequenceIter * parent,GSequence * sequence)927 collection_resort_sequence (GcrCollectionModel *self,
928                             GSequenceIter *parent,
929                             GSequence *sequence)
930 {
931 	GPtrArray *previous;
932 	GSequenceIter *seq, *next;
933 	gint *new_order;
934 	GtkTreePath *path;
935 	GtkTreeIter iter;
936 	GcrCollectionRow *row;
937 	gint index;
938 	gint i;
939 
940 	/* Make note of how things stand, and at same time resort all kids */
941 	previous = g_ptr_array_new ();
942 	for (seq = g_sequence_get_begin_iter (sequence);
943 	     !g_sequence_iter_is_end (seq); seq = next) {
944 		next = g_sequence_iter_next (seq);
945 		row = g_sequence_get (seq);
946 		if (row->children)
947 			collection_resort_sequence (self, seq, row->children);
948 		g_ptr_array_add (previous, row->object);
949 	}
950 
951 	if (previous->len == 0) {
952 		g_ptr_array_free (previous, TRUE);
953 		return;
954 	}
955 
956 	/* Actually perform the sort */
957 	g_sequence_sort (sequence, self->pv->order_current, self);
958 
959 	/* Now go through and map out how things changed */
960 	new_order = g_new0 (gint, previous->len);
961 	for (i = 0; i < previous->len; i++) {
962 		seq = g_hash_table_lookup (self->pv->object_to_seq, previous->pdata[i]);
963 		g_assert (seq != NULL);
964 		index = g_sequence_iter_get_position (seq);
965 		g_assert (index >= 0 && index < previous->len);
966 		new_order[index] = i;
967 	}
968 
969 	g_ptr_array_free (previous, TRUE);
970 
971 	path = sequence_iter_to_path (self, parent);
972 	if (parent == NULL) {
973 		gtk_tree_model_rows_reordered (GTK_TREE_MODEL (self), path, NULL, new_order);
974 	} else {
975 		if (!sequence_iter_to_tree (self, parent, &iter))
976 			g_assert_not_reached ();
977 		gtk_tree_model_rows_reordered (GTK_TREE_MODEL (self), path, &iter, new_order);
978 	}
979 	gtk_tree_path_free (path);
980 	g_free (new_order);
981 }
982 
983 static gboolean
gcr_collection_model_get_sort_column_id(GtkTreeSortable * sortable,gint * sort_column_id,GtkSortType * order)984 gcr_collection_model_get_sort_column_id (GtkTreeSortable *sortable,
985                                          gint *sort_column_id,
986                                          GtkSortType *order)
987 {
988 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (sortable);
989 
990 	if (order)
991 		*order = self->pv->sort_order_type;
992 	if (sort_column_id)
993 		*sort_column_id = self->pv->sort_column_id;
994 	return (self->pv->sort_column_id != GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID &&
995 		self->pv->sort_column_id != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID);
996 }
997 
998 static void
gcr_collection_model_set_sort_column_id(GtkTreeSortable * sortable,gint sort_column_id,GtkSortType order)999 gcr_collection_model_set_sort_column_id (GtkTreeSortable *sortable,
1000                                          gint sort_column_id,
1001                                          GtkSortType order)
1002 {
1003 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (sortable);
1004 	GCompareDataFunc func;
1005 	gpointer argument;
1006 	const GcrColumn *column;
1007 	gboolean reverse;
1008 
1009 	reverse = (order == GTK_SORT_DESCENDING);
1010 
1011 	if (sort_column_id == GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID) {
1012 		func = reverse ? order_sequence_as_unsorted_reverse : order_sequence_as_unsorted;
1013 		argument = NULL;
1014 
1015 	} else if (sort_column_id == GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID) {
1016 		func = reverse ? order_sequence_by_closure_reverse : order_sequence_by_closure;
1017 		argument = &self->pv->default_sort_closure;
1018 
1019 	} else if (sort_column_id >= 0 && sort_column_id < self->pv->n_columns) {
1020 		if (self->pv->column_sort_closures[sort_column_id].sort_func) {
1021 			func = reverse ? order_sequence_by_closure_reverse : order_sequence_by_closure;
1022 			argument = &self->pv->column_sort_closures[sort_column_id];
1023 		} else {
1024 			column = &self->pv->columns[sort_column_id];
1025 			if (!(column->flags & GCR_COLUMN_SORTABLE))
1026 				return;
1027 			if (!lookup_compare_func (column->property_type)) {
1028 				g_warning ("no sort implementation defined for type '%s' on column '%s'",
1029 				           g_type_name (column->property_type), column->property_name);
1030 				return;
1031 			}
1032 
1033 			func = reverse ? order_sequence_by_property_reverse : order_sequence_by_property;
1034 			argument = (gpointer)column;
1035 		}
1036 	} else {
1037 		g_warning ("invalid sort_column_id passed to gtk_tree_sortable_set_sort_column_id(): %d",
1038 		           sort_column_id);
1039 		return;
1040 	}
1041 
1042 	if (sort_column_id != self->pv->sort_column_id ||
1043 	    order != self->pv->sort_order_type) {
1044 		self->pv->sort_column_id = sort_column_id;
1045 		self->pv->sort_order_type = order;
1046 		gtk_tree_sortable_sort_column_changed (sortable);
1047 	}
1048 
1049 	if (func != self->pv->order_current ||
1050 	    argument != self->pv->order_argument) {
1051 		self->pv->order_current = func;
1052 		self->pv->order_argument = (gpointer)argument;
1053 		collection_resort_sequence (self, NULL, self->pv->root_sequence);
1054 	}
1055 }
1056 
1057 static void
clear_sort_closure(GcrCollectionSortClosure * closure)1058 clear_sort_closure (GcrCollectionSortClosure *closure)
1059 {
1060 	if (closure->destroy_func)
1061 		(closure->destroy_func) (closure->user_data);
1062 	closure->sort_func = NULL;
1063 	closure->destroy_func = NULL;
1064 	closure->user_data = NULL;
1065 }
1066 
1067 static void
set_sort_closure(GcrCollectionSortClosure * closure,GtkTreeIterCompareFunc func,gpointer data,GDestroyNotify destroy)1068 set_sort_closure (GcrCollectionSortClosure *closure,
1069                   GtkTreeIterCompareFunc func,
1070                   gpointer data,
1071                   GDestroyNotify destroy)
1072 {
1073 	clear_sort_closure (closure);
1074 	closure->sort_func = func;
1075 	closure->user_data = data;
1076 	closure->destroy_func = destroy;
1077 }
1078 
1079 static void
gcr_collection_model_set_sort_func(GtkTreeSortable * sortable,gint sort_column_id,GtkTreeIterCompareFunc func,gpointer data,GDestroyNotify destroy)1080 gcr_collection_model_set_sort_func (GtkTreeSortable *sortable,
1081                                     gint sort_column_id,
1082                                     GtkTreeIterCompareFunc func,
1083                                     gpointer data,
1084                                     GDestroyNotify destroy)
1085 {
1086 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (sortable);
1087 
1088 	g_return_if_fail (sort_column_id >= 0 && sort_column_id < self->pv->n_columns);
1089 
1090 	set_sort_closure (&self->pv->column_sort_closures[sort_column_id],
1091 	                  func, data, destroy);
1092 
1093 	/* Resorts if necessary */
1094 	if (self->pv->sort_column_id == sort_column_id) {
1095 		gcr_collection_model_set_sort_column_id (sortable,
1096 		                                         self->pv->sort_column_id,
1097 		                                         self->pv->sort_order_type);
1098 	}
1099 }
1100 
1101 static void
gcr_collection_model_set_default_sort_func(GtkTreeSortable * sortable,GtkTreeIterCompareFunc func,gpointer data,GDestroyNotify destroy)1102 gcr_collection_model_set_default_sort_func (GtkTreeSortable *sortable,
1103                                             GtkTreeIterCompareFunc func,
1104                                             gpointer data, GDestroyNotify destroy)
1105 {
1106 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (sortable);
1107 
1108 	set_sort_closure (&self->pv->default_sort_closure,
1109 	                  func, data, destroy);
1110 
1111 	/* Resorts if necessary */
1112 	if (self->pv->sort_column_id == GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID) {
1113 		gcr_collection_model_set_sort_column_id (sortable,
1114 		                                         self->pv->sort_column_id,
1115 		                                         self->pv->sort_order_type);
1116 	}
1117 }
1118 
1119 static gboolean
gcr_collection_model_has_default_sort_func(GtkTreeSortable * sortable)1120 gcr_collection_model_has_default_sort_func (GtkTreeSortable *sortable)
1121 {
1122 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (sortable);
1123 
1124 	return (self->pv->default_sort_closure.sort_func != NULL);
1125 }
1126 
1127 static void
gcr_collection_model_tree_sortable_init(GtkTreeSortableIface * iface)1128 gcr_collection_model_tree_sortable_init (GtkTreeSortableIface *iface)
1129 {
1130 	iface->get_sort_column_id = gcr_collection_model_get_sort_column_id;
1131 	iface->set_sort_column_id = gcr_collection_model_set_sort_column_id;
1132 	iface->set_sort_func = gcr_collection_model_set_sort_func;
1133 	iface->set_default_sort_func = gcr_collection_model_set_default_sort_func;
1134 	iface->has_default_sort_func = gcr_collection_model_has_default_sort_func;
1135 }
1136 
1137 static void
gcr_collection_model_init(GcrCollectionModel * self)1138 gcr_collection_model_init (GcrCollectionModel *self)
1139 {
1140 	self->pv = gcr_collection_model_get_instance_private (self);
1141 
1142 	self->pv->root_sequence = g_sequence_new (NULL);
1143 	self->pv->object_to_seq = g_hash_table_new (g_direct_hash, g_direct_equal);
1144 	self->pv->sort_column_id = GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID;
1145 	self->pv->sort_order_type = GTK_SORT_ASCENDING;
1146 	self->pv->order_current = order_sequence_as_unsorted;
1147 }
1148 
1149 static void
gcr_collection_model_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1150 gcr_collection_model_set_property (GObject *object, guint prop_id,
1151                                    const GValue *value, GParamSpec *pspec)
1152 {
1153 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (object);
1154 	GcrColumn *columns;
1155 
1156 	switch (prop_id) {
1157 	case PROP_MODE:
1158 		self->pv->mode = g_value_get_enum (value);
1159 		break;
1160 	case PROP_COLLECTION:
1161 		gcr_collection_model_set_collection (self, g_value_get_object (value));
1162 		break;
1163 	case PROP_COLUMNS:
1164 		columns = g_value_get_pointer (value);
1165 		if (columns)
1166 			gcr_collection_model_set_columns (self, columns);
1167 		break;
1168 	default:
1169 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1170 		break;
1171 	}
1172 }
1173 
1174 static void
gcr_collection_model_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1175 gcr_collection_model_get_property (GObject *object, guint prop_id,
1176                                    GValue *value, GParamSpec *pspec)
1177 {
1178 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (object);
1179 
1180 	switch (prop_id) {
1181 	case PROP_MODE:
1182 		g_value_set_enum (value, self->pv->mode);
1183 		break;
1184 	case PROP_COLLECTION:
1185 		g_value_set_object (value, self->pv->collection);
1186 		break;
1187 	case PROP_COLUMNS:
1188 		g_value_set_pointer (value, (gpointer)self->pv->columns);
1189 		break;
1190 	default:
1191 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1192 		break;
1193 	}
1194 }
1195 
1196 static void
gcr_collection_model_dispose(GObject * object)1197 gcr_collection_model_dispose (GObject *object)
1198 {
1199 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (object);
1200 
1201 	/* Disconnect from all rows */
1202 	if (self->pv->collection) {
1203 		remove_children_from_sequence (self, self->pv->root_sequence,
1204 		                               self->pv->collection, NULL, FALSE);
1205 		g_object_unref (self->pv->collection);
1206 		self->pv->collection = NULL;
1207 	}
1208 
1209 	G_OBJECT_CLASS (gcr_collection_model_parent_class)->dispose (object);
1210 }
1211 
1212 static void
gcr_collection_model_finalize(GObject * object)1213 gcr_collection_model_finalize (GObject *object)
1214 {
1215 	GcrCollectionModel *self = GCR_COLLECTION_MODEL (object);
1216 	guint i;
1217 
1218 	g_assert (!self->pv->collection);
1219 
1220 	g_assert (g_sequence_get_length (self->pv->root_sequence) == 0);
1221 	g_sequence_free (self->pv->root_sequence);
1222 	g_assert (g_hash_table_size (self->pv->object_to_seq) == 0);
1223 	g_hash_table_destroy (self->pv->object_to_seq);
1224 
1225 	if (self->pv->selected) {
1226 		g_assert (g_hash_table_size (self->pv->selected) == 0);
1227 		g_hash_table_destroy (self->pv->selected);
1228 		self->pv->selected = NULL;
1229 	}
1230 
1231 	self->pv->columns = NULL;
1232 	for (i = 0; i < self->pv->n_columns; i++)
1233 		clear_sort_closure (&self->pv->column_sort_closures[i]);
1234 	g_free (self->pv->column_sort_closures);
1235 	clear_sort_closure (&self->pv->default_sort_closure);
1236 
1237 	G_OBJECT_CLASS (gcr_collection_model_parent_class)->finalize (object);
1238 }
1239 
1240 static void
gcr_collection_model_class_init(GcrCollectionModelClass * klass)1241 gcr_collection_model_class_init (GcrCollectionModelClass *klass)
1242 {
1243 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1244 	gcr_collection_model_parent_class = g_type_class_peek_parent (klass);
1245 
1246 	gobject_class->dispose = gcr_collection_model_dispose;
1247 	gobject_class->finalize = gcr_collection_model_finalize;
1248 	gobject_class->set_property = gcr_collection_model_set_property;
1249 	gobject_class->get_property = gcr_collection_model_get_property;
1250 
1251 	g_object_class_install_property (gobject_class, PROP_MODE,
1252 	              g_param_spec_enum ("mode", "Mode", "Tree or list mode",
1253 	                                 GCR_TYPE_COLLECTION_MODEL_MODE, GCR_COLLECTION_MODEL_TREE,
1254 	                                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1255 
1256 	g_object_class_install_property (gobject_class, PROP_COLLECTION,
1257 	            g_param_spec_object ("collection", "Object Collection", "Collection to get objects from",
1258 	                                 GCR_TYPE_COLLECTION,
1259 	                                 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1260 
1261 	g_object_class_install_property (gobject_class, PROP_COLUMNS,
1262 		g_param_spec_pointer ("columns", "Columns", "Columns for the model",
1263 		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1264 }
1265 
1266 /**
1267  * gcr_collection_model_new: (skip)
1268  * @collection: the collection to represent
1269  * @mode: whether list or tree mode
1270  * @...: the column names and types
1271  *
1272  * Create a new #GcrCollectionModel. The variable argument list should contain
1273  * pairs of property names, and #GType values. The variable argument list should
1274  * be terminated with %NULL.
1275  *
1276  * Returns: (transfer full): a newly allocated model, which should be released
1277  *          with g_object_unref().
1278  */
1279 GcrCollectionModel*
gcr_collection_model_new(GcrCollection * collection,GcrCollectionModelMode mode,...)1280 gcr_collection_model_new (GcrCollection *collection,
1281                           GcrCollectionModelMode mode,
1282                           ...)
1283 {
1284 	GcrColumn column;
1285 	GcrCollectionModel *self;
1286 	const gchar *arg;
1287 	GArray *array;
1288 	va_list va;
1289 
1290 	/* With a null terminator */
1291 	array = g_array_new (TRUE, TRUE, sizeof (GcrColumn));
1292 
1293 	va_start (va, mode);
1294 	while ((arg = va_arg (va, const gchar*)) != NULL) {
1295 		memset (&column, 0, sizeof (column));
1296 		column.property_name = g_strdup (arg);
1297 		column.property_type = va_arg (va, GType);
1298 		column.column_type = column.property_type;
1299 		g_array_append_val (array, column);
1300 	}
1301 	va_end (va);
1302 
1303 	self = gcr_collection_model_new_full (collection, mode, (GcrColumn*)array->data);
1304 	g_object_set_data_full (G_OBJECT (self), "gcr_collection_model_new",
1305 	                        g_array_free (array, FALSE), free_owned_columns);
1306 	return self;
1307 }
1308 
1309 /**
1310  * gcr_collection_model_new_full: (skip)
1311  * @collection: the collection to represent
1312  * @mode: whether list or tree mode
1313  * @columns: the columns the model should contain
1314  *
1315  * Create a new #GcrCollectionModel.
1316  *
1317  * Returns: (transfer full): a newly allocated model, which should be released
1318  *          with g_object_unref()
1319  */
1320 GcrCollectionModel*
gcr_collection_model_new_full(GcrCollection * collection,GcrCollectionModelMode mode,const GcrColumn * columns)1321 gcr_collection_model_new_full (GcrCollection *collection,
1322                                GcrCollectionModelMode mode,
1323                                const GcrColumn *columns)
1324 {
1325 	GcrCollectionModel *self = g_object_new (GCR_TYPE_COLLECTION_MODEL,
1326 	                                         "collection", collection,
1327 	                                         "mode", mode,
1328 	                                         NULL);
1329 	gcr_collection_model_set_columns (self, columns);
1330 	return self;
1331 }
1332 
1333 /**
1334  * gcr_collection_model_set_columns: (skip)
1335  * @self: The model
1336  * @columns: The columns the model should contain
1337  *
1338  * Set the columns that the model should contain. @columns is an array of
1339  * #GcrColumn structures, with the last one containing %NULL for all values.
1340  *
1341  * This function can only be called once, and only if the model was not created
1342  * without a set of columns. This function cannot be called after the model
1343  * has been added to a view.
1344  *
1345  * The columns are accessed as static data. They should continue to remain
1346  * in memory for longer than the GcrCollectionModel object.
1347  *
1348  * Returns: The number of columns
1349  */
1350 guint
gcr_collection_model_set_columns(GcrCollectionModel * self,const GcrColumn * columns)1351 gcr_collection_model_set_columns (GcrCollectionModel *self,
1352                                   const GcrColumn *columns)
1353 {
1354 	const GcrColumn *col;
1355 	guint n_columns;
1356 
1357 	g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), 0);
1358 	g_return_val_if_fail (columns, 0);
1359 	g_return_val_if_fail (self->pv->n_columns == 0, 0);
1360 
1361 	/* Count the number of columns, extra column for selected */
1362 	for (col = columns, n_columns = 1; col->property_name; ++col)
1363 		++n_columns;
1364 
1365 	/* We expect the columns to stay around */
1366 	self->pv->columns = columns;
1367 	self->pv->n_columns = n_columns;
1368 	self->pv->column_sort_closures = g_new0 (GcrCollectionSortClosure, self->pv->n_columns);
1369 
1370 	return n_columns - 1;
1371 }
1372 
1373 /**
1374  * gcr_collection_model_get_collection:
1375  * @self: a collection model
1376  *
1377  * Get the collection which this model represents
1378  *
1379  * Returns: (transfer none): the collection, owned by the model
1380  */
1381 GcrCollection *
gcr_collection_model_get_collection(GcrCollectionModel * self)1382 gcr_collection_model_get_collection (GcrCollectionModel *self)
1383 {
1384 	g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), NULL);
1385 	return self->pv->collection;
1386 }
1387 
1388 /**
1389  * gcr_collection_model_set_collection:
1390  * @self: a collection model
1391  * @collection: (nullable): the collection or %NULL
1392  *
1393  * Set the collection which this model represents
1394  */
1395 void
gcr_collection_model_set_collection(GcrCollectionModel * self,GcrCollection * collection)1396 gcr_collection_model_set_collection (GcrCollectionModel *self,
1397                                      GcrCollection *collection)
1398 {
1399 	GcrCollection *previous;
1400 	GHashTable *exclude;
1401 	GList *children = NULL;
1402 	GList *l;
1403 
1404 	g_return_if_fail (GCR_IS_COLLECTION_MODEL (self));
1405 	g_return_if_fail (collection == NULL || GCR_IS_COLLECTION (collection));
1406 
1407 	if (collection == self->pv->collection)
1408 		return;
1409 
1410 	if (collection)
1411 		g_object_ref (collection);
1412 	previous = self->pv->collection;
1413 	self->pv->collection = collection;
1414 
1415 	if (collection)
1416 		children = gcr_collection_get_objects (collection);
1417 
1418 	if (previous) {
1419 		exclude = g_hash_table_new (g_direct_hash, g_direct_equal);
1420 		for (l = children; l != NULL; l = g_list_next (l))
1421 			g_hash_table_insert (exclude, l->data, l->data);
1422 
1423 		remove_children_from_sequence (self, self->pv->root_sequence,
1424 		                               previous, exclude, TRUE);
1425 
1426 		g_hash_table_destroy (exclude);
1427 		g_object_unref (previous);
1428 	}
1429 
1430 	if (collection) {
1431 		add_children_to_sequence (self, self->pv->root_sequence,
1432 		                          NULL, collection, children,
1433 		                          self->pv->object_to_seq, TRUE);
1434 		g_list_free (children);
1435 	}
1436 
1437 	g_object_notify (G_OBJECT (self), "collection");
1438 }
1439 
1440 /**
1441  * gcr_collection_model_object_for_iter:
1442  * @self: The model
1443  * @iter: The row
1444  *
1445  * Get the object that is represented by the given row in the model.
1446  *
1447  * Returns: (transfer none): The object, owned by the model.
1448  */
1449 GObject *
gcr_collection_model_object_for_iter(GcrCollectionModel * self,const GtkTreeIter * iter)1450 gcr_collection_model_object_for_iter (GcrCollectionModel *self, const GtkTreeIter *iter)
1451 {
1452 	g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), NULL);
1453 	g_return_val_if_fail (iter != NULL, NULL);
1454 	g_return_val_if_fail (iter->stamp == COLLECTION_MODEL_STAMP, NULL);
1455 	g_return_val_if_fail (G_IS_OBJECT (iter->user_data), NULL);
1456 
1457 	return G_OBJECT (iter->user_data);
1458 }
1459 
1460 /**
1461  * gcr_collection_model_iter_for_object:
1462  * @self: The model
1463  * @object: The object
1464  * @iter: The row for the object
1465  *
1466  * Set @iter to the row for the given object. If the object is not in this
1467  * model, then %FALSE will be returned.
1468  *
1469  * Returns: %TRUE if the object was present.
1470  */
1471 gboolean
gcr_collection_model_iter_for_object(GcrCollectionModel * self,GObject * object,GtkTreeIter * iter)1472 gcr_collection_model_iter_for_object (GcrCollectionModel *self, GObject *object,
1473                                       GtkTreeIter *iter)
1474 {
1475 	GSequenceIter *seq;
1476 
1477 	g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), FALSE);
1478 	g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
1479 	g_return_val_if_fail (iter != NULL, FALSE);
1480 
1481 	seq = g_hash_table_lookup (self->pv->object_to_seq, object);
1482 	if (seq == NULL)
1483 		return FALSE;
1484 
1485 	return sequence_iter_to_tree (self, seq, iter);
1486 }
1487 
1488 /**
1489  * gcr_collection_model_column_for_selected:
1490  * @self: The model
1491  *
1492  * Get the column identifier for the column that contains the values
1493  * of the selected state.
1494  *
1495  * Returns: The column identifier.
1496  */
1497 gint
gcr_collection_model_column_for_selected(GcrCollectionModel * self)1498 gcr_collection_model_column_for_selected (GcrCollectionModel *self)
1499 {
1500 	g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), 0);
1501 	g_assert (self->pv->n_columns > 0);
1502 	return self->pv->n_columns - 1;
1503 }
1504 
1505 /**
1506  * gcr_collection_model_toggle_selected:
1507  * @self: The model
1508  * @iter: The row
1509  *
1510  * Toggle the selected state of a given row.
1511  */
1512 void
gcr_collection_model_toggle_selected(GcrCollectionModel * self,GtkTreeIter * iter)1513 gcr_collection_model_toggle_selected (GcrCollectionModel *self, GtkTreeIter *iter)
1514 {
1515 	GObject *object;
1516 
1517 	g_return_if_fail (GCR_IS_COLLECTION_MODEL (self));
1518 
1519 	object = gcr_collection_model_object_for_iter (self, iter);
1520 	g_return_if_fail (G_IS_OBJECT (object));
1521 
1522 	if (!self->pv->selected)
1523 		self->pv->selected = selected_hash_table_new ();
1524 
1525 	if (g_hash_table_lookup (self->pv->selected, object))
1526 		g_hash_table_remove (self->pv->selected, object);
1527 	else
1528 		g_hash_table_insert (self->pv->selected, object, object);
1529 }
1530 
1531 /**
1532  * gcr_collection_model_change_selected:
1533  * @self: The model
1534  * @iter: The row
1535  * @selected: Whether the row should be selected or not.
1536  *
1537  * Set whether a given row is toggled selected or not.
1538  */
1539 void
gcr_collection_model_change_selected(GcrCollectionModel * self,GtkTreeIter * iter,gboolean selected)1540 gcr_collection_model_change_selected (GcrCollectionModel *self, GtkTreeIter *iter, gboolean selected)
1541 {
1542 	GtkTreePath *path;
1543 	GObject *object;
1544 
1545 	g_return_if_fail (GCR_IS_COLLECTION_MODEL (self));
1546 
1547 	object = gcr_collection_model_object_for_iter (self, iter);
1548 	g_return_if_fail (G_IS_OBJECT (object));
1549 
1550 	if (!self->pv->selected)
1551 		self->pv->selected = g_hash_table_new (g_direct_hash, g_direct_equal);
1552 
1553 	if (selected)
1554 		g_hash_table_insert (self->pv->selected, object, object);
1555 	else
1556 		g_hash_table_remove (self->pv->selected, object);
1557 
1558 	/* Tell the view that this row changed */
1559 	path = gtk_tree_model_get_path (GTK_TREE_MODEL (self), iter);
1560 	g_return_if_fail (path);
1561 	gtk_tree_model_row_changed (GTK_TREE_MODEL (self), path, iter);
1562 	gtk_tree_path_free (path);
1563 }
1564 
1565 /**
1566  * gcr_collection_model_is_selected:
1567  * @self: The model
1568  * @iter: The row
1569  *
1570  * Check whether a given row has been toggled as selected.
1571  *
1572  * Returns: Whether the row has been selected.
1573  */
1574 gboolean
gcr_collection_model_is_selected(GcrCollectionModel * self,GtkTreeIter * iter)1575 gcr_collection_model_is_selected (GcrCollectionModel *self, GtkTreeIter *iter)
1576 {
1577 	GObject *object;
1578 
1579 	g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), FALSE);
1580 
1581 	object = gcr_collection_model_object_for_iter (self, iter);
1582 	g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
1583 
1584 	if (!self->pv->selected)
1585 		return FALSE;
1586 
1587 	return g_hash_table_lookup (self->pv->selected, object) ? TRUE : FALSE;
1588 }
1589 
1590 /**
1591  * gcr_collection_model_get_selected_objects:
1592  * @self: the collection model
1593  *
1594  * Get a list of checked/selected objects.
1595  *
1596  * Returns: (transfer container) (element-type GObject.Object): a list of selected
1597  *          objects, which should be freed with g_list_free()
1598  */
1599 GList *
gcr_collection_model_get_selected_objects(GcrCollectionModel * self)1600 gcr_collection_model_get_selected_objects (GcrCollectionModel *self)
1601 {
1602 	GHashTableIter iter;
1603 	GList *result = NULL;
1604 	gpointer key;
1605 
1606 	g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), NULL);
1607 
1608 	if (!self->pv->selected)
1609 		return NULL;
1610 
1611 	g_hash_table_iter_init (&iter, self->pv->selected);
1612 	while (g_hash_table_iter_next (&iter, &key, NULL))
1613 		result = g_list_prepend (result, key);
1614 	return result;
1615 }
1616 
1617 /**
1618  * gcr_collection_model_set_selected_objects:
1619  * @self: the collection model
1620  * @selected: (element-type GObject.Object): a list of objects to select
1621  *
1622  * Set the checked/selected objects.
1623  */
1624 void
gcr_collection_model_set_selected_objects(GcrCollectionModel * self,GList * selected)1625 gcr_collection_model_set_selected_objects (GcrCollectionModel *self,
1626                                            GList *selected)
1627 {
1628 	GHashTable *newly_selected;
1629 	GList *old_selection;
1630 	GtkTreeIter iter;
1631 	GList *l;
1632 
1633 	old_selection = gcr_collection_model_get_selected_objects (self);
1634 	newly_selected = selected_hash_table_new ();
1635 
1636 	/* Select all the objects in selected which aren't already selected */
1637 	for (l = selected; l; l = g_list_next (l)) {
1638 		if (!self->pv->selected || !g_hash_table_lookup (self->pv->selected, l->data)) {
1639 			if (!gcr_collection_model_iter_for_object (self, l->data, &iter))
1640 				g_return_if_reached ();
1641 			gcr_collection_model_change_selected (self, &iter, TRUE);
1642 		}
1643 
1644 		/* Note that we've seen this one */
1645 		g_hash_table_insert (newly_selected, l->data, l->data);
1646 	}
1647 
1648 	/* Unselect all the objects which aren't supposed to be selected */
1649 	for (l = old_selection; l; l = g_list_next (l)) {
1650 		if (!g_hash_table_lookup (newly_selected, l->data)) {
1651 			if (!gcr_collection_model_iter_for_object (self, l->data, &iter))
1652 				g_return_if_reached ();
1653 			gcr_collection_model_change_selected (self, &iter, FALSE);
1654 		}
1655 	}
1656 
1657 	g_list_free (old_selection);
1658 	g_hash_table_destroy (newly_selected);
1659 }
1660