/* * gnome-keyring * * Copyright (C) 2010 Stefan Walter * Copyright (C) 2011 Collabora Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * * Author: Stef Walter */ #include "config.h" #include "gcr-collection-model.h" #include "ui/gcr-enum-types.h" #include #include #include /** * SECTION:gcr-collection-model * @title: GcrCollectionModel * @short_description: A GtkTreeModel that represents a collection * * This is an implementation of #GtkTreeModel which represents the objects in * the a #GcrCollection. As objects are added or removed from the collection, * rows are added and removed from this model. * * The row values come from the properties of the objects in the collection. Use * gcr_collection_model_new() to create a new collection model. To have more * control over the values use a set of #GcrColumn structures to define the * columns. This can be done with gcr_collection_model_new_full() or * gcr_collection_model_set_columns(). * * Each row can have a selected state, which is represented by a boolean column. * The selected state can be toggled with gcr_collection_model_toggle_selected() * or set with gcr_collection_model_set_selected_objects() and retrieved with * gcr_collection_model_get_selected_objects(). * * To determine which object a row represents and vice versa, use the * gcr_collection_model_iter_for_object() or gcr_collection_model_object_for_iter() * functions. */ /** * GcrCollectionModel: * * A #GtkTreeModel which contains a row for each object in a #GcrCollection. */ /** * GcrCollectionModelClass: * @parent_class: The parent class * * The class for #GcrCollectionModel. */ /** * GcrCollectionModelMode: * @GCR_COLLECTION_MODEL_LIST: only objects in the top collection, no child objects * @GCR_COLLECTION_MODEL_TREE: show objects in the collection, and child objects in a tree form * * If set GcrCollectionModel is created with a mode of %GCR_COLLECTION_MODEL_TREE, * then any included objects that are themselves a #GcrCollection, will have all child * objects include as child rows in a tree form. */ #define COLLECTION_MODEL_STAMP 0xAABBCCDD enum { PROP_0, PROP_COLLECTION, PROP_COLUMNS, PROP_MODE }; typedef struct { GObject *object; GSequenceIter *parent; GSequence *children; } GcrCollectionRow; typedef struct { GtkTreeIterCompareFunc sort_func; gpointer user_data; GDestroyNotify destroy_func; } GcrCollectionSortClosure; typedef struct _GcrCollectionColumn { gchar *property; GType *type; GtkTreeIterCompareFunc sort_func; gpointer sort_data; GDestroyNotify sort_destroy; } GcrCollectionColumn; struct _GcrCollectionModelPrivate { GcrCollectionModelMode mode; GcrCollection *collection; GHashTable *selected; GSequence *root_sequence; GHashTable *object_to_seq; const GcrColumn *columns; guint n_columns; /* Sort information */ gint sort_column_id; GtkSortType sort_order_type; GcrCollectionSortClosure *column_sort_closures; GcrCollectionSortClosure default_sort_closure; /* Sequence ordering information */ GCompareDataFunc order_current; gpointer order_argument; }; /* Forward declarations */ static void gcr_collection_model_tree_model_init (GtkTreeModelIface *iface); static void gcr_collection_model_tree_sortable_init (GtkTreeSortableIface *iface); G_DEFINE_TYPE_EXTENDED (GcrCollectionModel, gcr_collection_model, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, gcr_collection_model_tree_model_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE, gcr_collection_model_tree_sortable_init) ); typedef gint (*CompareValueFunc) (const GValue *va, const GValue *vb); static gint compare_int_value (const GValue *va, const GValue *vb) { gint a = g_value_get_int (va); gint b = g_value_get_int (vb); if (a > b) return 1; else if (a < b) return -1; return 0; } static gint compare_uint_value (const GValue *va, const GValue *vb) { guint a = g_value_get_uint (va); guint b = g_value_get_uint (vb); if (a > b) return 1; else if (a < b) return -1; return 0; } static gint compare_long_value (const GValue *va, const GValue *vb) { glong a = g_value_get_long (va); glong b = g_value_get_long (vb); if (a > b) return 1; else if (a < b) return -1; return 0; } static gint compare_ulong_value (const GValue *va, const GValue *vb) { gulong a = g_value_get_ulong (va); gulong b = g_value_get_ulong (vb); if (a > b) return 1; else if (a < b) return -1; return 0; } static gint compare_string_value (const GValue *va, const GValue *vb) { const gchar *a = g_value_get_string (va); const gchar *b = g_value_get_string (vb); gchar *case_a; gchar *case_b; gboolean ret; if (a == b) return 0; else if (!a) return -1; else if (!b) return 1; case_a = g_utf8_casefold (a, -1); case_b = g_utf8_casefold (b, -1); ret = g_utf8_collate (case_a, case_b); g_free (case_a); g_free (case_b); return ret; } static gint compare_date_value (const GValue *va, const GValue *vb) { GDate *a = g_value_get_boxed (va); GDate *b = g_value_get_boxed (vb); if (a == b) return 0; else if (!a) return -1; else if (!b) return 1; else return g_date_compare (a, b); } static CompareValueFunc lookup_compare_func (GType type) { switch (type) { case G_TYPE_INT: return compare_int_value; case G_TYPE_UINT: return compare_uint_value; case G_TYPE_LONG: return compare_long_value; case G_TYPE_ULONG: return compare_ulong_value; case G_TYPE_STRING: return compare_string_value; } if (type == G_TYPE_DATE) return compare_date_value; return NULL; } static gint order_sequence_by_closure (gconstpointer a, gconstpointer b, gpointer user_data) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (user_data); GcrCollectionSortClosure *closure = self->pv->order_argument; const GcrCollectionRow *row_a = a; const GcrCollectionRow *row_b = b; GtkTreeIter iter_a; GtkTreeIter iter_b; g_assert (closure); g_assert (closure->sort_func); if (!gcr_collection_model_iter_for_object (self, row_a->object, &iter_a)) g_return_val_if_reached (0); if (!gcr_collection_model_iter_for_object (self, row_b->object, &iter_b)) g_return_val_if_reached (0); return (closure->sort_func) (GTK_TREE_MODEL (self), &iter_a, &iter_b, closure->user_data); } static gint order_sequence_by_closure_reverse (gconstpointer a, gconstpointer b, gpointer user_data) { return 0 - order_sequence_by_closure (a, b, user_data); } static gint order_sequence_as_unsorted (gconstpointer a, gconstpointer b, gpointer user_data) { const GcrCollectionRow *row_a = a; const GcrCollectionRow *row_b = b; return GPOINTER_TO_INT (row_a->object) - GPOINTER_TO_INT (row_b->object); } static gint order_sequence_as_unsorted_reverse (gconstpointer a, gconstpointer b, gpointer user_data) { const GcrCollectionRow *row_a = a; const GcrCollectionRow *row_b = b; return GPOINTER_TO_INT (row_b->object) - GPOINTER_TO_INT (row_a->object); } static void lookup_object_property (GObject *object, const gchar *property_name, GValue *value) { if (g_object_class_find_property (G_OBJECT_GET_CLASS (object), property_name)) g_object_get_property (object, property_name, value); /* Other types have sane defaults */ else if (G_VALUE_TYPE (value) == G_TYPE_STRING) g_value_set_string (value, ""); } static gint order_sequence_by_property (gconstpointer a, gconstpointer b, gpointer user_data) { const GcrCollectionRow *row_a = a; const GcrCollectionRow *row_b = b; GcrCollectionModel *self = GCR_COLLECTION_MODEL (user_data); const GcrColumn *column = self->pv->order_argument; GValue value_a = { 0, }; GValue value_b = { 0, }; CompareValueFunc compare; gint ret; g_assert (column); /* Sort according to property values */ column = &self->pv->columns[self->pv->sort_column_id]; g_value_init (&value_a, column->property_type); lookup_object_property (row_a->object, column->property_name, &value_a); g_value_init (&value_b, column->property_type); lookup_object_property (row_b->object, column->property_name, &value_b); compare = lookup_compare_func (column->property_type); g_assert (compare != NULL); ret = (compare) (&value_a, &value_b); g_value_unset (&value_a); g_value_unset (&value_b); return ret; } static gint order_sequence_by_property_reverse (gconstpointer a, gconstpointer b, gpointer user_data) { return 0 - order_sequence_by_property (a, b, user_data); } static GHashTable* selected_hash_table_new (void) { return g_hash_table_new (g_direct_hash, g_direct_equal); } static gboolean sequence_iter_to_tree (GcrCollectionModel *self, GSequenceIter *seq, GtkTreeIter *iter) { GcrCollectionRow *row; g_return_val_if_fail (seq != NULL, FALSE); if (g_sequence_iter_is_end (seq)) return FALSE; row = g_sequence_get (seq); g_return_val_if_fail (row != NULL && G_IS_OBJECT (row->object), FALSE); memset (iter, 0, sizeof (*iter)); iter->stamp = COLLECTION_MODEL_STAMP; iter->user_data = row->object; iter->user_data2 = seq; return TRUE; } static GSequenceIter * sequence_iter_for_tree (GcrCollectionModel *self, GtkTreeIter *iter) { g_return_val_if_fail (iter != NULL, NULL); g_return_val_if_fail (iter->stamp == COLLECTION_MODEL_STAMP, NULL); return iter->user_data2; } static GtkTreePath * sequence_iter_to_path (GcrCollectionModel *self, GSequenceIter *seq) { GcrCollectionRow *row; GtkTreePath *path; path = gtk_tree_path_new (); while (seq) { gtk_tree_path_prepend_index (path, g_sequence_iter_get_position (seq)); row = g_sequence_get (seq); seq = row->parent; } return path; } static GSequence * child_sequence_for_tree (GcrCollectionModel *self, GtkTreeIter *iter) { GcrCollectionRow *row; GSequenceIter *seq; if (iter == NULL) { return self->pv->root_sequence; } else { seq = sequence_iter_for_tree (self, iter); g_return_val_if_fail (seq != NULL, NULL); row = g_sequence_get (seq); return row->children; } } static void on_object_notify (GObject *object, GParamSpec *spec, GcrCollectionModel *self) { GtkTreeIter iter; GtkTreePath *path; gboolean found = FALSE; guint i; g_return_if_fail (spec->name); for (i = 0; i < self->pv->n_columns - 1; ++i) { g_assert (self->pv->columns[i].property_name); if (g_str_equal (self->pv->columns[i].property_name, spec->name)) { found = TRUE; break; } } /* Tell the tree view that this row changed */ if (found) { if (!gcr_collection_model_iter_for_object (self, object, &iter)) g_return_if_reached (); path = gtk_tree_model_get_path (GTK_TREE_MODEL (self), &iter); g_return_if_fail (path); gtk_tree_model_row_changed (GTK_TREE_MODEL (self), path, &iter); gtk_tree_path_free (path); } } static void on_object_gone (gpointer unused, GObject *was_object) { g_warning ("object contained in GcrCollection and included in GcrCollectionModel " "was destroyed before it was removed from the collection"); } static void on_collection_added (GcrCollection *collection, GObject *object, gpointer user_data); static void on_collection_removed (GcrCollection *collection, GObject *object, gpointer user_data); static void add_object_to_sequence (GcrCollectionModel *self, GSequence *sequence, GSequenceIter *parent, GObject *object, gboolean emit); static void remove_object_from_sequence (GcrCollectionModel *self, GSequence *sequence, GSequenceIter *seq, GObject *object, gboolean emit); static void add_children_to_sequence (GcrCollectionModel *self, GSequence *sequence, GSequenceIter *parent, GcrCollection *collection, GList *children, GHashTable *exclude, gboolean emit) { GList *l; for (l = children; l; l = g_list_next (l)) { if (!exclude || g_hash_table_lookup (exclude, l->data) == NULL) add_object_to_sequence (self, sequence, parent, l->data, emit); } /* Now listen in for any changes */ g_signal_connect_after (collection, "added", G_CALLBACK (on_collection_added), self); g_signal_connect_after (collection, "removed", G_CALLBACK (on_collection_removed), self); } static void add_object_to_sequence (GcrCollectionModel *self, GSequence *sequence, GSequenceIter *parent, GObject *object, gboolean emit) { GcrCollectionRow *row; GcrCollection *collection; GSequenceIter *seq; GtkTreeIter iter; GtkTreePath *path; GList *children; g_assert (GCR_IS_COLLECTION_MODEL (self)); g_assert (G_IS_OBJECT (object)); g_assert (self->pv->order_current); if (g_hash_table_lookup (self->pv->object_to_seq, object)) { g_warning ("object was already added to the GcrCollectionModel. Perhaps " "a loop exists in a tree structure?"); return; } row = g_slice_new0 (GcrCollectionRow); row->object = object; row->parent = parent; row->children = NULL; seq = g_sequence_insert_sorted (sequence, row, self->pv->order_current, self); g_hash_table_insert (self->pv->object_to_seq, object, seq); g_object_weak_ref (G_OBJECT (object), (GWeakNotify)on_object_gone, self); g_signal_connect (object, "notify", G_CALLBACK (on_object_notify), self); if (emit) { if (!sequence_iter_to_tree (self, seq, &iter)) g_assert_not_reached (); path = sequence_iter_to_path (self, seq); g_assert (path != NULL); gtk_tree_model_row_inserted (GTK_TREE_MODEL (self), path, &iter); gtk_tree_path_free (path); } if (self->pv->mode == GCR_COLLECTION_MODEL_TREE && GCR_IS_COLLECTION (object)) { row->children = g_sequence_new (NULL); collection = GCR_COLLECTION (object); children = gcr_collection_get_objects (collection); add_children_to_sequence (self, row->children, seq, collection, children, NULL, emit); g_list_free (children); } } static void on_collection_added (GcrCollection *collection, GObject *object, gpointer user_data) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (user_data); GSequence *sequence; GSequenceIter *parent; GcrCollectionRow *row; if (collection == self->pv->collection) { sequence = self->pv->root_sequence; parent = NULL; } else { parent = g_hash_table_lookup (self->pv->object_to_seq, G_OBJECT (collection)); row = g_sequence_get (parent); g_assert (row->children); sequence = row->children; } add_object_to_sequence (self, sequence, parent, object, TRUE); } static void remove_children_from_sequence (GcrCollectionModel *self, GSequence *sequence, GcrCollection *collection, GHashTable *exclude, gboolean emit) { GSequenceIter *seq, *next; GcrCollectionRow *row; g_signal_handlers_disconnect_by_func (collection, on_collection_added, self); g_signal_handlers_disconnect_by_func (collection, on_collection_removed, self); for (seq = g_sequence_get_begin_iter (sequence); !g_sequence_iter_is_end (seq); seq = next) { next = g_sequence_iter_next (seq); row = g_sequence_get (seq); if (!exclude || g_hash_table_lookup (exclude, row->object) == NULL) remove_object_from_sequence (self, sequence, seq, row->object, emit); } } static void remove_object_from_sequence (GcrCollectionModel *self, GSequence *sequence, GSequenceIter *seq, GObject *object, gboolean emit) { GcrCollectionRow *row; GtkTreePath *path = NULL; if (emit) { path = sequence_iter_to_path (self, seq); g_assert (path != NULL); } row = g_sequence_get (seq); g_assert (row->object == object); g_object_weak_unref (object, on_object_gone, self); g_signal_handlers_disconnect_by_func (object, on_object_notify, self); if (row->children) { g_assert (self->pv->mode == GCR_COLLECTION_MODEL_TREE); g_assert (GCR_IS_COLLECTION (object)); remove_children_from_sequence (self, row->children, GCR_COLLECTION (object), NULL, emit); g_assert (g_sequence_get_length (row->children) == 0); g_sequence_free (row->children); row->children = NULL; } if (self->pv->selected) g_hash_table_remove (self->pv->selected, object); if (!g_hash_table_remove (self->pv->object_to_seq, object)) g_assert_not_reached (); g_sequence_remove (seq); g_slice_free (GcrCollectionRow, row); /* Fire signal for this removed row */ if (path != NULL) { gtk_tree_model_row_deleted (GTK_TREE_MODEL (self), path); gtk_tree_path_free (path); } } static void on_collection_removed (GcrCollection *collection, GObject *object, gpointer user_data) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (user_data); GSequenceIter *seq; GSequence *sequence; seq = g_hash_table_lookup (self->pv->object_to_seq, object); g_return_if_fail (seq != NULL); sequence = g_sequence_iter_get_sequence (seq); g_assert (sequence != NULL); remove_object_from_sequence (self, sequence, seq, object, TRUE); } static void free_owned_columns (gpointer data) { GcrColumn *columns; g_assert (data); /* Only the property column is in use */ for (columns = data; columns->property_name; ++columns) g_free ((gchar*)columns->property_name); g_free (data); } static GtkTreeModelFlags gcr_collection_model_real_get_flags (GtkTreeModel *model) { return GTK_TREE_MODEL_ITERS_PERSIST; } static gint gcr_collection_model_real_get_n_columns (GtkTreeModel *model) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (model); return self->pv->n_columns; } static GType gcr_collection_model_real_get_column_type (GtkTreeModel *model, gint column_id) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (model); g_return_val_if_fail (column_id >= 0 && column_id <= self->pv->n_columns, 0); /* The last is the selected column */ if (column_id == self->pv->n_columns) return G_TYPE_BOOLEAN; return self->pv->columns[column_id].column_type; } static gboolean gcr_collection_model_real_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (model); const gint *indices; GSequence *sequence; GSequenceIter *seq; GcrCollectionRow *row; gint count; gint i; sequence = self->pv->root_sequence; seq = NULL; indices = gtk_tree_path_get_indices_with_depth (path, &count); if (count == 0) return FALSE; for (i = 0; i < count; i++) { if (!sequence) return FALSE; seq = g_sequence_get_iter_at_pos (sequence, indices[i]); if (g_sequence_iter_is_end (seq)) return FALSE; row = g_sequence_get (seq); sequence = row->children; } return sequence_iter_to_tree (self, seq, iter); } static GtkTreePath* gcr_collection_model_real_get_path (GtkTreeModel *model, GtkTreeIter *iter) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (model); GSequenceIter *seq; if (iter == NULL) return gtk_tree_path_new (); seq = sequence_iter_for_tree (self, iter); g_return_val_if_fail (seq != NULL, NULL); return sequence_iter_to_path (self, seq); } static void gcr_collection_model_real_get_value (GtkTreeModel *model, GtkTreeIter *iter, gint column_id, GValue *value) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (model); GObject *object; GValue original; const GcrColumn *column; GParamSpec *spec; object = gcr_collection_model_object_for_iter (self, iter); g_return_if_fail (G_IS_OBJECT (object)); g_return_if_fail (column_id >= 0 && column_id < self->pv->n_columns); /* The selected column? Last one */ if (column_id == self->pv->n_columns - 1) { g_value_init (value, G_TYPE_BOOLEAN); g_value_set_boolean (value, gcr_collection_model_is_selected (self, iter)); return; } /* Figure out which property */ column = &self->pv->columns[column_id]; g_assert (column->property_name); g_value_init (value, column->column_type); /* Lookup the property on the object */ spec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), column->property_name); if (spec != NULL) { /* A transformer is specified, or mismatched types */ if (column->transformer || column->column_type != column->property_type) { memset (&original, 0, sizeof (original)); g_value_init (&original, column->property_type); g_object_get_property (object, column->property_name, &original); if (column->transformer) { (column->transformer) (&original, value); } else { g_warning ("%s property of %s class was of type %s instead of type %s" " and cannot be converted due to lack of transformer", column->property_name, G_OBJECT_TYPE_NAME (object), g_type_name (column->property_type), g_type_name (column->column_type)); spec = NULL; } /* Simple, no transformation necessary */ } else { g_object_get_property (object, column->property_name, value); } } if (spec == NULL) { /* All the number types have sane defaults */ if (column->column_type == G_TYPE_STRING) g_value_set_string (value, ""); } } static gboolean gcr_collection_model_real_iter_next (GtkTreeModel *model, GtkTreeIter *iter) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (model); GSequenceIter *seq = sequence_iter_for_tree (self, iter); g_return_val_if_fail (seq != NULL, FALSE); return sequence_iter_to_tree (self, g_sequence_iter_next (seq), iter); } static gboolean gcr_collection_model_real_iter_children (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (model); GSequence *sequence = child_sequence_for_tree (self, parent); return sequence && sequence_iter_to_tree (self, g_sequence_get_begin_iter (sequence), iter); } static gboolean gcr_collection_model_real_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (model); GSequence *sequence = child_sequence_for_tree (self, iter); return sequence && !g_sequence_iter_is_end (g_sequence_get_begin_iter (sequence)); } static gint gcr_collection_model_real_iter_n_children (GtkTreeModel *model, GtkTreeIter *iter) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (model); GSequence *sequence = child_sequence_for_tree (self, iter); return sequence ? g_sequence_get_length (sequence) : 0; } static gboolean gcr_collection_model_real_iter_nth_child (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent, gint n) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (model); GSequence *sequence; GSequenceIter *seq; sequence = child_sequence_for_tree (self, parent); if (sequence == NULL) return FALSE; seq = g_sequence_get_iter_at_pos (sequence, n); return sequence_iter_to_tree (self, seq, iter); } static gboolean gcr_collection_model_real_iter_parent (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *child) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (model); GSequenceIter *seq; GcrCollectionRow *row; seq = sequence_iter_for_tree (self, child); g_return_val_if_fail (seq != NULL, FALSE); row = g_sequence_get (seq); if (row->parent == NULL) return FALSE; return sequence_iter_to_tree (self, row->parent, iter); } static void gcr_collection_model_real_ref_node (GtkTreeModel *model, GtkTreeIter *iter) { /* Nothing to do */ } static void gcr_collection_model_real_unref_node (GtkTreeModel *model, GtkTreeIter *iter) { /* Nothing to do */ } static void gcr_collection_model_tree_model_init (GtkTreeModelIface *iface) { iface->get_flags = gcr_collection_model_real_get_flags; iface->get_n_columns = gcr_collection_model_real_get_n_columns; iface->get_column_type = gcr_collection_model_real_get_column_type; iface->get_iter = gcr_collection_model_real_get_iter; iface->get_path = gcr_collection_model_real_get_path; iface->get_value = gcr_collection_model_real_get_value; iface->iter_next = gcr_collection_model_real_iter_next; iface->iter_children = gcr_collection_model_real_iter_children; iface->iter_has_child = gcr_collection_model_real_iter_has_child; iface->iter_n_children = gcr_collection_model_real_iter_n_children; iface->iter_nth_child = gcr_collection_model_real_iter_nth_child; iface->iter_parent = gcr_collection_model_real_iter_parent; iface->ref_node = gcr_collection_model_real_ref_node; iface->unref_node = gcr_collection_model_real_unref_node; } static void collection_resort_sequence (GcrCollectionModel *self, GSequenceIter *parent, GSequence *sequence) { GPtrArray *previous; GSequenceIter *seq, *next; gint *new_order; GtkTreePath *path; GtkTreeIter iter; GcrCollectionRow *row; gint index; gint i; /* Make note of how things stand, and at same time resort all kids */ previous = g_ptr_array_new (); for (seq = g_sequence_get_begin_iter (sequence); !g_sequence_iter_is_end (seq); seq = next) { next = g_sequence_iter_next (seq); row = g_sequence_get (seq); if (row->children) collection_resort_sequence (self, seq, row->children); g_ptr_array_add (previous, row->object); } if (previous->len == 0) { g_ptr_array_free (previous, TRUE); return; } /* Actually perform the sort */ g_sequence_sort (sequence, self->pv->order_current, self); /* Now go through and map out how things changed */ new_order = g_new0 (gint, previous->len); for (i = 0; i < previous->len; i++) { seq = g_hash_table_lookup (self->pv->object_to_seq, previous->pdata[i]); g_assert (seq != NULL); index = g_sequence_iter_get_position (seq); g_assert (index >= 0 && index < previous->len); new_order[index] = i; } g_ptr_array_free (previous, TRUE); path = sequence_iter_to_path (self, parent); if (parent == NULL) { gtk_tree_model_rows_reordered (GTK_TREE_MODEL (self), path, NULL, new_order); } else { if (!sequence_iter_to_tree (self, parent, &iter)) g_assert_not_reached (); gtk_tree_model_rows_reordered (GTK_TREE_MODEL (self), path, &iter, new_order); } gtk_tree_path_free (path); g_free (new_order); } static gboolean gcr_collection_model_get_sort_column_id (GtkTreeSortable *sortable, gint *sort_column_id, GtkSortType *order) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (sortable); if (order) *order = self->pv->sort_order_type; if (sort_column_id) *sort_column_id = self->pv->sort_column_id; return (self->pv->sort_column_id != GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID && self->pv->sort_column_id != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID); } static void gcr_collection_model_set_sort_column_id (GtkTreeSortable *sortable, gint sort_column_id, GtkSortType order) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (sortable); GCompareDataFunc func; gpointer argument; const GcrColumn *column; gboolean reverse; reverse = (order == GTK_SORT_DESCENDING); if (sort_column_id == GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID) { func = reverse ? order_sequence_as_unsorted_reverse : order_sequence_as_unsorted; argument = NULL; } else if (sort_column_id == GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID) { func = reverse ? order_sequence_by_closure_reverse : order_sequence_by_closure; argument = &self->pv->default_sort_closure; } else if (sort_column_id >= 0 && sort_column_id < self->pv->n_columns) { if (self->pv->column_sort_closures[sort_column_id].sort_func) { func = reverse ? order_sequence_by_closure_reverse : order_sequence_by_closure; argument = &self->pv->column_sort_closures[sort_column_id]; } else { column = &self->pv->columns[sort_column_id]; if (!(column->flags & GCR_COLUMN_SORTABLE)) return; if (!lookup_compare_func (column->property_type)) { g_warning ("no sort implementation defined for type '%s' on column '%s'", g_type_name (column->property_type), column->property_name); return; } func = reverse ? order_sequence_by_property_reverse : order_sequence_by_property; argument = (gpointer)column; } } else { g_warning ("invalid sort_column_id passed to gtk_tree_sortable_set_sort_column_id(): %d", sort_column_id); return; } if (sort_column_id != self->pv->sort_column_id || order != self->pv->sort_order_type) { self->pv->sort_column_id = sort_column_id; self->pv->sort_order_type = order; gtk_tree_sortable_sort_column_changed (sortable); } if (func != self->pv->order_current || argument != self->pv->order_argument) { self->pv->order_current = func; self->pv->order_argument = (gpointer)argument; collection_resort_sequence (self, NULL, self->pv->root_sequence); } } static void clear_sort_closure (GcrCollectionSortClosure *closure) { if (closure->destroy_func) (closure->destroy_func) (closure->user_data); closure->sort_func = NULL; closure->destroy_func = NULL; closure->user_data = NULL; } static void set_sort_closure (GcrCollectionSortClosure *closure, GtkTreeIterCompareFunc func, gpointer data, GDestroyNotify destroy) { clear_sort_closure (closure); closure->sort_func = func; closure->user_data = data; closure->destroy_func = destroy; } static void gcr_collection_model_set_sort_func (GtkTreeSortable *sortable, gint sort_column_id, GtkTreeIterCompareFunc func, gpointer data, GDestroyNotify destroy) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (sortable); g_return_if_fail (sort_column_id >= 0 && sort_column_id < self->pv->n_columns); set_sort_closure (&self->pv->column_sort_closures[sort_column_id], func, data, destroy); /* Resorts if necessary */ if (self->pv->sort_column_id == sort_column_id) { gcr_collection_model_set_sort_column_id (sortable, self->pv->sort_column_id, self->pv->sort_order_type); } } static void gcr_collection_model_set_default_sort_func (GtkTreeSortable *sortable, GtkTreeIterCompareFunc func, gpointer data, GDestroyNotify destroy) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (sortable); set_sort_closure (&self->pv->default_sort_closure, func, data, destroy); /* Resorts if necessary */ if (self->pv->sort_column_id == GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID) { gcr_collection_model_set_sort_column_id (sortable, self->pv->sort_column_id, self->pv->sort_order_type); } } static gboolean gcr_collection_model_has_default_sort_func (GtkTreeSortable *sortable) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (sortable); return (self->pv->default_sort_closure.sort_func != NULL); } static void gcr_collection_model_tree_sortable_init (GtkTreeSortableIface *iface) { iface->get_sort_column_id = gcr_collection_model_get_sort_column_id; iface->set_sort_column_id = gcr_collection_model_set_sort_column_id; iface->set_sort_func = gcr_collection_model_set_sort_func; iface->set_default_sort_func = gcr_collection_model_set_default_sort_func; iface->has_default_sort_func = gcr_collection_model_has_default_sort_func; } static void gcr_collection_model_init (GcrCollectionModel *self) { self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_COLLECTION_MODEL, GcrCollectionModelPrivate); self->pv->root_sequence = g_sequence_new (NULL); self->pv->object_to_seq = g_hash_table_new (g_direct_hash, g_direct_equal); self->pv->sort_column_id = GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID; self->pv->sort_order_type = GTK_SORT_ASCENDING; self->pv->order_current = order_sequence_as_unsorted; } static void gcr_collection_model_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (object); GcrColumn *columns; switch (prop_id) { case PROP_MODE: self->pv->mode = g_value_get_enum (value); break; case PROP_COLLECTION: gcr_collection_model_set_collection (self, g_value_get_object (value)); break; case PROP_COLUMNS: columns = g_value_get_pointer (value); if (columns) gcr_collection_model_set_columns (self, columns); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gcr_collection_model_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (object); switch (prop_id) { case PROP_MODE: g_value_set_enum (value, self->pv->mode); break; case PROP_COLLECTION: g_value_set_object (value, self->pv->collection); break; case PROP_COLUMNS: g_value_set_pointer (value, (gpointer)self->pv->columns); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gcr_collection_model_dispose (GObject *object) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (object); /* Disconnect from all rows */ if (self->pv->collection) { remove_children_from_sequence (self, self->pv->root_sequence, self->pv->collection, NULL, FALSE); g_object_unref (self->pv->collection); self->pv->collection = NULL; } G_OBJECT_CLASS (gcr_collection_model_parent_class)->dispose (object); } static void gcr_collection_model_finalize (GObject *object) { GcrCollectionModel *self = GCR_COLLECTION_MODEL (object); guint i; g_assert (!self->pv->collection); g_assert (g_sequence_get_length (self->pv->root_sequence) == 0); g_sequence_free (self->pv->root_sequence); g_assert (g_hash_table_size (self->pv->object_to_seq) == 0); g_hash_table_destroy (self->pv->object_to_seq); if (self->pv->selected) { g_assert (g_hash_table_size (self->pv->selected) == 0); g_hash_table_destroy (self->pv->selected); self->pv->selected = NULL; } self->pv->columns = NULL; for (i = 0; i < self->pv->n_columns; i++) clear_sort_closure (&self->pv->column_sort_closures[i]); g_free (self->pv->column_sort_closures); clear_sort_closure (&self->pv->default_sort_closure); G_OBJECT_CLASS (gcr_collection_model_parent_class)->finalize (object); } static void gcr_collection_model_class_init (GcrCollectionModelClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gcr_collection_model_parent_class = g_type_class_peek_parent (klass); gobject_class->dispose = gcr_collection_model_dispose; gobject_class->finalize = gcr_collection_model_finalize; gobject_class->set_property = gcr_collection_model_set_property; gobject_class->get_property = gcr_collection_model_get_property; g_object_class_install_property (gobject_class, PROP_MODE, g_param_spec_enum ("mode", "Mode", "Tree or list mode", GCR_TYPE_COLLECTION_MODEL_MODE, GCR_COLLECTION_MODEL_TREE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (gobject_class, PROP_COLLECTION, g_param_spec_object ("collection", "Object Collection", "Collection to get objects from", GCR_TYPE_COLLECTION, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_COLUMNS, g_param_spec_pointer ("columns", "Columns", "Columns for the model", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_type_class_add_private (klass, sizeof (GcrCollectionModelPrivate)); } /** * gcr_collection_model_new: (skip) * @collection: the collection to represent * @mode: whether list or tree mode * @...: the column names and types * * Create a new #GcrCollectionModel. The variable argument list should contain * pairs of property names, and #GType values. The variable argument list should * be terminated with %NULL. * * Returns: (transfer full): a newly allocated model, which should be released * with g_object_unref(). */ GcrCollectionModel* gcr_collection_model_new (GcrCollection *collection, GcrCollectionModelMode mode, ...) { GcrColumn column; GcrCollectionModel *self; const gchar *arg; GArray *array; va_list va; /* With a null terminator */ array = g_array_new (TRUE, TRUE, sizeof (GcrColumn)); va_start (va, mode); while ((arg = va_arg (va, const gchar*)) != NULL) { memset (&column, 0, sizeof (column)); column.property_name = g_strdup (arg); column.property_type = va_arg (va, GType); column.column_type = column.property_type; g_array_append_val (array, column); } va_end (va); self = gcr_collection_model_new_full (collection, mode, (GcrColumn*)array->data); g_object_set_data_full (G_OBJECT (self), "gcr_collection_model_new", g_array_free (array, FALSE), free_owned_columns); return self; } /** * gcr_collection_model_new_full: (skip) * @collection: the collection to represent * @mode: whether list or tree mode * @columns: the columns the model should contain * * Create a new #GcrCollectionModel. * * Returns: (transfer full): a newly allocated model, which should be released * with g_object_unref() */ GcrCollectionModel* gcr_collection_model_new_full (GcrCollection *collection, GcrCollectionModelMode mode, const GcrColumn *columns) { GcrCollectionModel *self = g_object_new (GCR_TYPE_COLLECTION_MODEL, "collection", collection, "mode", mode, NULL); gcr_collection_model_set_columns (self, columns); return self; } /** * gcr_collection_model_set_columns: (skip) * @self: The model * @columns: The columns the model should contain * * Set the columns that the model should contain. @columns is an array of * #GcrColumn structures, with the last one containing %NULL for all values. * * This function can only be called once, and only if the model was not created * without a set of columns. This function cannot be called after the model * has been added to a view. * * The columns are accessed as static data. They should continue to remain * in memory for longer than the GcrCollectionModel object. * * Returns: The number of columns */ guint gcr_collection_model_set_columns (GcrCollectionModel *self, const GcrColumn *columns) { const GcrColumn *col; guint n_columns; g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), 0); g_return_val_if_fail (columns, 0); g_return_val_if_fail (self->pv->n_columns == 0, 0); /* Count the number of columns, extra column for selected */ for (col = columns, n_columns = 1; col->property_name; ++col) ++n_columns; /* We expect the columns to stay around */ self->pv->columns = columns; self->pv->n_columns = n_columns; self->pv->column_sort_closures = g_new0 (GcrCollectionSortClosure, self->pv->n_columns); return n_columns - 1; } /** * gcr_collection_model_get_collection: * @self: a collection model * * Get the collection which this model represents * * Returns: (transfer none): the collection, owned by the model */ GcrCollection * gcr_collection_model_get_collection (GcrCollectionModel *self) { g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), NULL); return self->pv->collection; } /** * gcr_collection_model_set_collection: * @self: a collection model * @collection: (allow-none): the collection or %NULL * * Set the collection which this model represents */ void gcr_collection_model_set_collection (GcrCollectionModel *self, GcrCollection *collection) { GcrCollection *previous; GHashTable *exclude; GList *children = NULL; GList *l; g_return_if_fail (GCR_IS_COLLECTION_MODEL (self)); g_return_if_fail (collection == NULL || GCR_IS_COLLECTION (collection)); if (collection == self->pv->collection) return; if (collection) g_object_ref (collection); previous = self->pv->collection; self->pv->collection = collection; if (collection) children = gcr_collection_get_objects (collection); if (previous) { exclude = g_hash_table_new (g_direct_hash, g_direct_equal); for (l = children; l != NULL; l = g_list_next (l)) g_hash_table_insert (exclude, l->data, l->data); remove_children_from_sequence (self, self->pv->root_sequence, previous, exclude, TRUE); g_hash_table_destroy (exclude); g_object_unref (previous); } if (collection) { add_children_to_sequence (self, self->pv->root_sequence, NULL, collection, children, self->pv->object_to_seq, TRUE); g_list_free (children); } g_object_notify (G_OBJECT (self), "collection"); } /** * gcr_collection_model_object_for_iter: * @self: The model * @iter: The row * * Get the object that is represented by the given row in the model. * * Returns: (transfer none): The object, owned by the model. */ GObject * gcr_collection_model_object_for_iter (GcrCollectionModel *self, const GtkTreeIter *iter) { g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), NULL); g_return_val_if_fail (iter != NULL, NULL); g_return_val_if_fail (iter->stamp == COLLECTION_MODEL_STAMP, NULL); g_return_val_if_fail (G_IS_OBJECT (iter->user_data), NULL); return G_OBJECT (iter->user_data); } /** * gcr_collection_model_iter_for_object: * @self: The model * @object: The object * @iter: The row for the object * * Set @iter to the row for the given object. If the object is not in this * model, then %FALSE will be returned. * * Returns: %TRUE if the object was present. */ gboolean gcr_collection_model_iter_for_object (GcrCollectionModel *self, GObject *object, GtkTreeIter *iter) { GSequenceIter *seq; g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), FALSE); g_return_val_if_fail (G_IS_OBJECT (object), FALSE); g_return_val_if_fail (iter != NULL, FALSE); seq = g_hash_table_lookup (self->pv->object_to_seq, object); if (seq == NULL) return FALSE; return sequence_iter_to_tree (self, seq, iter); } /** * gcr_collection_model_column_for_selected: * @self: The model * * Get the column identifier for the column that contains the values * of the selected state. * * Returns: The column identifier. */ gint gcr_collection_model_column_for_selected (GcrCollectionModel *self) { g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), 0); g_assert (self->pv->n_columns > 0); return self->pv->n_columns - 1; } /** * gcr_collection_model_toggle_selected: * @self: The model * @iter: The row * * Toggle the selected state of a given row. */ void gcr_collection_model_toggle_selected (GcrCollectionModel *self, GtkTreeIter *iter) { GObject *object; g_return_if_fail (GCR_IS_COLLECTION_MODEL (self)); object = gcr_collection_model_object_for_iter (self, iter); g_return_if_fail (G_IS_OBJECT (object)); if (!self->pv->selected) self->pv->selected = selected_hash_table_new (); if (g_hash_table_lookup (self->pv->selected, object)) g_hash_table_remove (self->pv->selected, object); else g_hash_table_insert (self->pv->selected, object, object); } /** * gcr_collection_model_change_selected: * @self: The model * @iter: The row * @selected: Whether the row should be selected or not. * * Set whether a given row is toggled selected or not. */ void gcr_collection_model_change_selected (GcrCollectionModel *self, GtkTreeIter *iter, gboolean selected) { GtkTreePath *path; GObject *object; g_return_if_fail (GCR_IS_COLLECTION_MODEL (self)); object = gcr_collection_model_object_for_iter (self, iter); g_return_if_fail (G_IS_OBJECT (object)); if (!self->pv->selected) self->pv->selected = g_hash_table_new (g_direct_hash, g_direct_equal); if (selected) g_hash_table_insert (self->pv->selected, object, object); else g_hash_table_remove (self->pv->selected, object); /* Tell the view that this row changed */ path = gtk_tree_model_get_path (GTK_TREE_MODEL (self), iter); g_return_if_fail (path); gtk_tree_model_row_changed (GTK_TREE_MODEL (self), path, iter); gtk_tree_path_free (path); } /** * gcr_collection_model_is_selected: * @self: The model * @iter: The row * * Check whether a given row has been toggled as selected. * * Returns: Whether the row has been selected. */ gboolean gcr_collection_model_is_selected (GcrCollectionModel *self, GtkTreeIter *iter) { GObject *object; g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), FALSE); object = gcr_collection_model_object_for_iter (self, iter); g_return_val_if_fail (G_IS_OBJECT (object), FALSE); if (!self->pv->selected) return FALSE; return g_hash_table_lookup (self->pv->selected, object) ? TRUE : FALSE; } /** * gcr_collection_model_get_selected_objects: * @self: the collection model * * Get a list of checked/selected objects. * * Returns: (transfer container) (element-type GObject.Object): a list of selected * objects, which should be freed with g_list_free() */ GList * gcr_collection_model_get_selected_objects (GcrCollectionModel *self) { GHashTableIter iter; GList *result = NULL; gpointer key; g_return_val_if_fail (GCR_IS_COLLECTION_MODEL (self), NULL); if (!self->pv->selected) return NULL; g_hash_table_iter_init (&iter, self->pv->selected); while (g_hash_table_iter_next (&iter, &key, NULL)) result = g_list_prepend (result, key); return result; } /** * gcr_collection_model_set_selected_objects: * @self: the collection model * @selected: (element-type GObject.Object): a list of objects to select * * Set the checked/selected objects. */ void gcr_collection_model_set_selected_objects (GcrCollectionModel *self, GList *selected) { GHashTable *newly_selected; GList *old_selection; GtkTreeIter iter; GList *l; old_selection = gcr_collection_model_get_selected_objects (self); newly_selected = selected_hash_table_new (); /* Select all the objects in selected which aren't already selected */ for (l = selected; l; l = g_list_next (l)) { if (!self->pv->selected || !g_hash_table_lookup (self->pv->selected, l->data)) { if (!gcr_collection_model_iter_for_object (self, l->data, &iter)) g_return_if_reached (); gcr_collection_model_change_selected (self, &iter, TRUE); } /* Note that we've seen this one */ g_hash_table_insert (newly_selected, l->data, l->data); } /* Unselect all the objects which aren't supposed to be selected */ for (l = old_selection; l; l = g_list_next (l)) { if (!g_hash_table_lookup (newly_selected, l->data)) { if (!gcr_collection_model_iter_for_object (self, l->data, &iter)) g_return_if_reached (); gcr_collection_model_change_selected (self, &iter, FALSE); } } g_list_free (old_selection); g_hash_table_destroy (newly_selected); }