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