From 70e09ab9dfa7ead8b8bbd2f7aaffae99675dd0c4 Mon Sep 17 00:00:00 2001 From: "Neil J. Patel" Date: Thu, 15 Nov 2007 10:02:25 +0000 Subject: [PATCH] 2007-11-15 Neil J. Patel * clutter/Makefile.am: * clutter/clutter-model.c: * clutter/clutter-model.h: * clutter/clutter.h: * tests/Makefile.am: * tests/test-model.c: Merged ClutterModel, which closes #443. --- ChangeLog | 10 + clutter/Makefile.am | 2 + clutter/clutter-model.c | 1991 ++++++++++++++++++++++++++++ clutter/clutter-model.h | 302 +++++ clutter/clutter.h | 1 + doc/reference/ChangeLog | 7 + doc/reference/clutter-docs.sgml | 2 + doc/reference/clutter-sections.txt | 67 + doc/reference/clutter.types | 2 + tests/Makefile.am | 3 +- tests/test-model.c | 198 +++ 11 files changed, 2584 insertions(+), 1 deletion(-) create mode 100644 clutter/clutter-model.c create mode 100644 clutter/clutter-model.h create mode 100644 tests/test-model.c diff --git a/ChangeLog b/ChangeLog index 3a6c11c05..806a1a1c5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2007-11-15 Neil J. Patel + + * clutter/Makefile.am: + * clutter/clutter-model.c: + * clutter/clutter-model.h: + * clutter/clutter.h: + * tests/Makefile.am: + * tests/test-model.c: + Merged ClutterModel, which closes #443. + 2007-11-14 Emmanuele Bassi * clutter/clutter-clone-texture.c (set_parent_texture): Hide the diff --git a/clutter/Makefile.am b/clutter/Makefile.am index d84a2e7b0..16ccb393a 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -67,6 +67,7 @@ source_h = \ $(srcdir)/clutter-layout.h \ $(srcdir)/clutter-main.h \ $(srcdir)/clutter-media.h \ + $(srcdir)/clutter-model.h \ $(srcdir)/clutter-rectangle.h \ $(srcdir)/clutter-score.h \ $(srcdir)/clutter-script.h \ @@ -153,6 +154,7 @@ source_c = \ clutter-main.c \ clutter-marshal.c \ clutter-media.c \ + clutter-model.c \ clutter-rectangle.c \ clutter-score.c \ clutter-script.c \ diff --git a/clutter/clutter-model.c b/clutter/clutter-model.c new file mode 100644 index 000000000..abf46c82f --- /dev/null +++ b/clutter/clutter-model.c @@ -0,0 +1,1991 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * Neil Jagdish Patel + * + * Copyright (C) 2006 OpenedHand + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * NB: Inspiration for column storage taken from GtkListStore + */ + +/** + * SECTION:clutter-model + * @short_description: A generic model implementation + * + * #ClutterModel is a generic list model which can be used to implement the + * model-view-controller architectural pattern in Clutter. + * + * The #ClutterModel object is a list model which can accept most GObject + * types as a column type. Internally, it will keep a local copy of the data + * passed in (such as a string or a boxed pointer). + * + * Creating a simple clutter model: + * + * enum { + * COLUMN_INT, + * COLUMN_STRING. + * N_COLUMNS + * }; + * + * { + * ClutterModel *model; + * gint i; + * + * model = clutter_model_new (2, G_TYPE_INT, G_TYPE_STRING); + * for (i = 0; i < 10; i++) + * { + * gchar *string = g_strdup_printf ("String %d", i); + * clutter_model_append (model, + * COLUMN_INT, i, + * COLUMN_STRING, string, + * -1); + * g_free (string); + * } + * + * + * } + * + * + * + * #ClutterModel is available since Clutter 0.6 + */ + + +#include "config.h" + +#include +#include +#include + +#include "clutter-model.h" + +#include "clutter-debug.h" + + +G_DEFINE_TYPE (ClutterModel, clutter_model, G_TYPE_OBJECT); + +enum +{ + ROW_ADDED, + ROW_REMOVED, + ROW_CHANGED, + + SORT_CHANGED, + FILTER_CHANGED, + + LAST_SIGNAL +}; + +static guint model_signals[LAST_SIGNAL] = { 0, }; + +#define CLUTTER_MODEL_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_MODEL, ClutterModelPrivate)) + +struct _ClutterModelPrivate +{ + GSequence *sequence; + + GType *column_types; + gint n_columns; + + ClutterModelFilterFunc filter; + gpointer filter_data; + GDestroyNotify filter_notify; + + ClutterModelSortFunc sort; + guint sort_column; + gpointer sort_data; + GDestroyNotify sort_notify; +}; + +/* Forwards */ +static ClutterModelIter * _model_get_iter_at_row (ClutterModel *model, + guint index_); +static void clutter_model_real_remove (ClutterModel *model, + GSequenceIter *iter); + +/* GObject stuff */ +static void +clutter_model_dispose (GObject *object) +{ + ClutterModel *self = CLUTTER_MODEL(object); + ClutterModelPrivate *priv; + + priv = self->priv; + + G_OBJECT_CLASS (clutter_model_parent_class)->dispose (object); +} + +static void +clutter_model_finalize (GObject *object) +{ + ClutterModelPrivate *priv = CLUTTER_MODEL (object)->priv; + GSequenceIter *iter; + + iter = g_sequence_get_end_iter (priv->sequence); + while (!g_sequence_iter_is_end (iter)) + { + clutter_model_real_remove (CLUTTER_MODEL (object), iter); + iter = g_sequence_iter_next (iter); + } + g_sequence_free (priv->sequence); + + if (priv->sort && priv->sort_data && priv->sort_notify) + priv->sort_notify (priv->sort_data); + + if (priv->filter && priv->filter_data && priv->filter_notify) + priv->filter_notify (priv->filter_data); + + + G_OBJECT_CLASS (clutter_model_parent_class)->finalize (object); +} + +static void +clutter_model_class_init (ClutterModelClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = clutter_model_finalize; + gobject_class->dispose = clutter_model_dispose; + + klass->get_iter_at_row = _model_get_iter_at_row; + + /** + * ClutterModel::row-added: + * @model: the #ClutterModel on which the signal is emitted + * @iter: a #ClutterModelIter pointing to the new row + * + * The ::row-added signal is emitted when a new row has been added + * + * Since 0.6 + */ + model_signals[ROW_ADDED] = + g_signal_new ("row-added", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterModelClass, row_added), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + + /** + * ClutterModel::row-removed: + * @model: the #ClutterModel on which the signal is emitted + * @iter: a #ClutterModelIter pointing to the removed row + * + * The ::row-removed signal is emitted when a row has been removed + * + * Since 0.6 + */ + model_signals[ROW_REMOVED] = + g_signal_new ("row-removed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterModelClass, row_removed), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + + /** + * ClutterModel::row-changed: + * @model: the #ClutterModel on which the signal is emitted + * @iter: a #ClutterModelIter pointing to the changed row + * + * The ::row-removed signal is emitted when a row has been changed + * + * Since 0.6 + */ + model_signals[ROW_CHANGED] = + g_signal_new ("row-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterModelClass, row_changed), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + + /** + * ClutterModel::sort-changed: + * @model: the #ClutterModel on which the signal is emitted + * + * The ::sort-changed signal is emitted after the model has been sorted + * + * Since 0.6 + */ + model_signals[SORT_CHANGED] = + g_signal_new ("sort-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterModelClass, sort_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * ClutterModel::filter-changed: + * @model: the #ClutterModel on which the signal is emitted + * + * The ::filter-changed signal is emitted when a new filter has been applied + * + * Since 0.6 + */ + model_signals[FILTER_CHANGED] = + g_signal_new ("filter-changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterModelClass, filter_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_type_class_add_private (gobject_class, sizeof (ClutterModelPrivate)); +} + +static void +clutter_model_init (ClutterModel *self) +{ + ClutterModelPrivate *priv; + + self->priv = priv = CLUTTER_MODEL_GET_PRIVATE (self); + + priv->sequence = g_sequence_new (NULL); + + priv->column_types = NULL; + priv->n_columns = 0; + + priv->filter = NULL; + priv->filter_data = NULL; + priv->filter_notify = NULL; + priv->sort = NULL; + priv->sort_data = NULL; + priv->sort_column = 0; + priv->sort_notify = NULL; +} + + +static gboolean +clutter_model_check_type (type) +{ + gint i = 0; + static const GType type_list[] = + { + G_TYPE_BOOLEAN, + G_TYPE_CHAR, + G_TYPE_UCHAR, + G_TYPE_INT, + G_TYPE_UINT, + G_TYPE_LONG, + G_TYPE_ULONG, + G_TYPE_INT64, + G_TYPE_UINT64, + G_TYPE_ENUM, + G_TYPE_FLAGS, + G_TYPE_FLOAT, + G_TYPE_DOUBLE, + G_TYPE_STRING, + G_TYPE_POINTER, + G_TYPE_BOXED, + G_TYPE_OBJECT, + G_TYPE_INVALID + }; + + if (! G_TYPE_IS_VALUE_TYPE (type)) + return FALSE; + + + while (type_list[i] != G_TYPE_INVALID) + { + if (g_type_is_a (type, type_list[i])) + return TRUE; + i++; + } + return FALSE; +} + +static gint +_model_sort_func (GValueArray *val_a, + GValueArray *val_b, + ClutterModel *model) +{ + ClutterModelPrivate *priv; + const GValue *a; + const GValue *b; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), 0); + g_return_val_if_fail (val_a, 0); + g_return_val_if_fail (val_b, 0); + priv = model->priv; + + a = g_value_array_get_nth (val_a, priv->sort_column); + b = g_value_array_get_nth (val_b, priv->sort_column); + + return priv->sort (model, a, b, priv->sort_data); +} + +static void +_model_sort (ClutterModel *model) +{ + ClutterModelPrivate *priv; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + priv = model->priv; + + if (!priv->sort) + return; + + g_sequence_sort (priv->sequence, (GCompareDataFunc)_model_sort_func, model); +} + + +static gboolean +_model_filter (ClutterModel *model, ClutterModelIter *iter) +{ + ClutterModelPrivate *priv; + gboolean res = TRUE; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), TRUE); + priv = model->priv; + + if (!priv->filter) + return TRUE; + + res = priv->filter (model, iter, priv->filter_data); + + return res; +} + + +static void +clutter_model_set_n_columns (ClutterModel *model, gint n_columns) +{ + ClutterModelPrivate *priv; + GType *new_columns; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + priv = model->priv; + + new_columns = g_new0 (GType, n_columns); + + priv->column_types = new_columns; + priv->n_columns = n_columns; +} + +static void +clutter_model_set_column_type (ClutterModel *model, + gint column, + GType type) +{ + ClutterModelPrivate *priv; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + priv = model->priv; + + priv->column_types[column] = type; +} + +/** + * clutter_model_new: + * @n_columns: number of columns in the model + * @Varargs: all #GType types for the columns, from first to last + * + * Creates a new model with @n_columns columns with the types passed in. + * + * For example, clutter_model_new (3, G_TYPE_INT, G_TYPE_STRING, + * GDK_TYPE_PIXBUF); will create a new #ClutterModel with three + * columns of type int, string and #GdkPixbuf respectively. + * + * Return value: a new #ClutterModel + * + * Since 0.6 + */ +ClutterModel * +clutter_model_new (guint n_columns, + ...) +{ + ClutterModel *model; + va_list args; + gint i; + + g_return_val_if_fail (n_columns > 0, NULL); + + model = g_object_new (CLUTTER_TYPE_MODEL, NULL); + clutter_model_set_n_columns (model, n_columns); + + va_start (args, n_columns); + + for (i = 0; i < n_columns; i++) + { + GType type = va_arg (args, GType); + + if (!clutter_model_check_type (type)) + { + g_warning ("%s: Invalid type %s\n", G_STRLOC, g_type_name (type)); + g_object_unref (model); + return NULL; + } + clutter_model_set_column_type (model, i, type); + } + + va_end (args); + + return model; +} + +/** + * clutter_model_newv: + * @n_columns: number of columns in the model + * @types: an array of #GType types for the columns, from first to last + * + * Non-vararg creation function. Used primarily by language bindings. + * + * Return value: a new #ClutterModel + * + * Since 0.6 + */ +ClutterModel * +clutter_model_newv (guint n_columns, + GType *types) +{ + ClutterModel *model; + gint i; + + g_return_val_if_fail (n_columns > 0, NULL); + + model = g_object_new (CLUTTER_TYPE_MODEL, NULL); + clutter_model_set_n_columns (model, n_columns); + + for (i = 0; i < n_columns; i++) + { + if (!clutter_model_check_type (types[i])) + { + g_warning ("%s: Invalid type %s\n", G_STRLOC, g_type_name (types[i])); + g_object_unref (model); + return NULL; + } + clutter_model_set_column_type (model, i, types[i]); + } + + return model; +} + +/** + * clutter_model_set_types: + * @model: a #ClutterModel + * @n_columns: number of columns for the model + * @types: an array of #GType types + * + * This function is meant primarily for #GObjects that inherit from + * #ClutterModel, and should only be used when contructing a #ClutterModel. It + * will not work after the initial creation of the #ClutterModel. + * + * Since 0.6 + */ +void +clutter_model_set_types (ClutterModel *model, + guint n_columns, + GType *types) +{ + ClutterModelPrivate *priv; + gint i; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + g_return_if_fail (n_columns > 0); + priv = model->priv; + + g_return_if_fail (priv->n_columns > 0); + + clutter_model_set_n_columns (model, n_columns); + + for (i = 0; i < n_columns; i++) + { + if (!clutter_model_check_type (types[i])) + { + g_warning ("%s: Invalid type %s\n", G_STRLOC, g_type_name (types[i])); + return; + } + clutter_model_set_column_type (model, i, types[i]); + } +} + + +static GValueArray * +clutter_model_new_row (ClutterModel *model) +{ + ClutterModelPrivate *priv; + GValueArray *row; + gint i; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), NULL); + priv = model->priv; + + row = g_value_array_new (priv->n_columns); + + for (i = 0; i < priv->n_columns; i++) + { + GValue value = { 0, }; + + g_value_init (&value, priv->column_types[i]); + g_value_array_append (row, &value); + g_value_unset (&value); + } + + return row; +} + +/** + * clutter_model_append: + * @model: a #ClutterModel + * @Varargs: pairs of column number and value, terminated with -1 + * + * Creates and appends a new row to the #ClutterModel, setting the rows values + * upon creation. For example, to append a new row where column 0 is type + * G_TYPE_INT and column 1 is of type G_TYPE_STRING, you would write + * clutter_model_append (model, 0, 100, 1, "foo", -1);. + * + * Return value: #TRUE if a new row was successfully added. + * + * Since 0.6 + */ +gboolean +clutter_model_append (ClutterModel *model, + ...) +{ + ClutterModelPrivate *priv; + ClutterModelIter *iter; + GSequenceIter *seq_iter; + GValueArray *row; + va_list args; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), FALSE); + priv = model->priv; + + row = clutter_model_new_row (model); + seq_iter = g_sequence_append (priv->sequence, row); + + iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, + "model", model, + "iter", seq_iter, + NULL); + va_start (args, model); + clutter_model_iter_set_valist (iter, args); + va_end (args); + + /*FIXME: Sort the model if necessary */ + g_signal_emit (model, model_signals[ROW_ADDED], 0, iter); + g_object_unref (iter); + return TRUE; +} + + +/** + * clutter_model_prepend: + * @model: a #ClutterModel + * @Varargs: pairs of column number and value, terminated with -1 + * + * Creates and prepends a new row to the #ClutterModel, setting the rows values + * upon creation. For example, to prepend a new row where column 0 is type + * G_TYPE_INT and column 1 is of type G_TYPE_STRING, you would write + * clutter_model_prepend (model, 0, 100, 1, "string", -1);. + * + * Return value: #TRUE if a new row was successfully added. + * + * Since 0.6 + */ +gboolean +clutter_model_prepend (ClutterModel *model, + ...) +{ + ClutterModelPrivate *priv; + ClutterModelIter *iter; + GSequenceIter *seq_iter; + GValueArray *row; + va_list args; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), FALSE); + priv = model->priv; + + row = clutter_model_new_row (model); + seq_iter = g_sequence_prepend (priv->sequence, row); + + iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, + "model", model, + "iter", seq_iter, + NULL); + va_start (args, model); + clutter_model_iter_set_valist (iter, args); + va_end (args); + + /*FIXME: Sort the model if necessary */ + g_signal_emit (model, model_signals[ROW_ADDED], 0, iter); + g_object_unref (iter); + return TRUE; +} + + +/** + * clutter_model_insert: + * @model: a #ClutterModel + * @index_: the position to insert the new row + * @Varargs: pairs of column number and value, terminated with -1 + * + * Inserts a new row to the #ClutterModel at @index_, setting the rows values + * upon creation. For example, to insert a new row at index 100, where column 0 + * is type G_TYPE_INT and column 1 is of type G_TYPE_STRING, you would write + * clutter_model_insert (model, 100, 0, 100, 1, "string", -1); + * . + * + * Return value: #TRUE if a new row was successfully added. + * + * Since 0.6 + */ +gboolean +clutter_model_insert (ClutterModel *model, + guint index_, + ...) +{ + ClutterModelPrivate *priv; + ClutterModelIter *iter; + GValueArray *row; + GSequenceIter *seq_iter; + va_list args; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), FALSE); + priv = model->priv; + + row = clutter_model_new_row (model); + + seq_iter = g_sequence_get_iter_at_pos (priv->sequence, index_); + seq_iter = g_sequence_insert_before (seq_iter, row); + + iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, + "model", model, + "iter", seq_iter, + NULL); + + va_start (args, index_); + clutter_model_iter_set_valist (iter, args); + va_end (args); + + /* FIXME: Sort? */ + + g_signal_emit (model, model_signals[ROW_ADDED], 0, iter); + g_object_unref (iter); + return TRUE; +} + +/** + * clutter_model_insert_value: + * @model: a #ClutterModel + * @index_: position of the row to modify + * @column: column to modify + * @value: new value for the cell + * + * Sets the data in the cell specified by @iter and @column. The type of + * @value must be convertable to the type of the column. + * + * Return value: #TRUE if the @value was successfully inserted. + * + * Since 0.6 + */ +gboolean +clutter_model_insert_value (ClutterModel *model, + guint index_, + guint column, + const GValue *value) +{ + ClutterModelPrivate *priv; + ClutterModelIter *iter; + GSequenceIter *seq_iter; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), FALSE); + priv = model->priv; + + seq_iter = g_sequence_get_iter_at_pos (priv->sequence, index_); + + iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, + "model", model, + "iter", seq_iter, + NULL); + clutter_model_iter_set_value (iter, column, value); + + if (priv->sort_column == column) + _model_sort (model); + + g_signal_emit (model, model_signals[ROW_CHANGED], 0, iter); + g_object_unref (iter); + return TRUE; +} + +static void +clutter_model_real_remove (ClutterModel *model, + GSequenceIter *iter) +{ + ClutterModelPrivate *priv; + GValueArray *value_array; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + priv = model->priv; + + value_array = g_sequence_get (iter); + g_value_array_free (value_array); +} + +/** + * clutter_model_remove: + * @model: a #ClutterModel + * @index_: position of row to remove + * + * Removes a row at @index_ from the model. + * + * Since 0.6 + */ +void +clutter_model_remove (ClutterModel *model, + guint index_) +{ + ClutterModelPrivate *priv; + ClutterModelIter *iter; + GSequenceIter *seq_iter; + gint i = 0; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + priv = model->priv; + + seq_iter = g_sequence_get_begin_iter (priv->sequence); + iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, "model", model, NULL); + + while (!g_sequence_iter_is_end (seq_iter)) + { + g_object_set (iter, "iter", seq_iter, NULL); + if (_model_filter (model, iter)) + { + if (i == index_) + { + g_signal_emit (model, model_signals[ROW_REMOVED], 0, iter); + clutter_model_real_remove (model, seq_iter); + g_sequence_remove (seq_iter); + break; + } + i++; + } + seq_iter = g_sequence_iter_next (seq_iter); + } + g_object_unref (iter); +} + +static ClutterModelIter * +_model_get_iter_at_row (ClutterModel *model, + guint index_) +{ + g_return_val_if_fail (CLUTTER_IS_MODEL (model), NULL); + + return g_object_new (CLUTTER_TYPE_MODEL_ITER, + "model", model, + "row", index_, + NULL); +} + +/** + * clutter_model_get_iter_at_row: + * @model: a #ClutterModel + * @index_: position of the row to retrieve + * + * Retrieves a #ClutterModelIter representing the row at @index_. + * + * Return value: A new #ClutterModelIter, or NULL if @index_ was out-of-bounds + * + * Since 0.6 + **/ +ClutterModelIter * +clutter_model_get_iter_at_row (ClutterModel *model, + guint index_) +{ + ClutterModelClass *klass; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), NULL); + + klass = CLUTTER_MODEL_GET_CLASS (model); + if (klass->get_iter_at_row) + return klass->get_iter_at_row (model, index_); + + return NULL; +} + + +/** + * clutter_model_get_first_iter: + * @model: a #ClutterModel + * + * Retrieves a #ClutterModelIter representing the first row in @model. It calls + * #clutter_model_get_iter_at_row with 0 as the index_ parameter. + * + * Return value: A new #ClutterModelIter + * + * Since 0.6 + **/ +ClutterModelIter * +clutter_model_get_first_iter (ClutterModel *model) +{ + g_return_val_if_fail (CLUTTER_IS_MODEL (model), NULL); + + return clutter_model_get_iter_at_row (model, 0); +} + +/** + * clutter_model_get_last_iter: + * @model: a #ClutterModel + * + * Retrieves a #ClutterModelIter representing the last row in @model. It calls + * #clutter_model_get_iter_at_row with model_length -1 as the index_ parameter. + * + * Return value: A new #ClutterModelIter + * + * Since 0.6 + **/ + ClutterModelIter * +clutter_model_get_last_iter (ClutterModel *model) +{ + guint length; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), NULL); + + length = clutter_model_get_n_rows (model); + + return clutter_model_get_iter_at_row (model, length-1); +} + +/** + * clutter_model_get_n_rows: + * @model: a #ClutterModel + * + * Return value: The length of the @model. If there is a filter set, then the + * length of the filtered @model is returned. + * + * Since 0.6 + */ +guint +clutter_model_get_n_rows (ClutterModel *model) +{ + ClutterModelPrivate *priv; + ClutterModelIter *iter; + GSequenceIter *seq_iter; + guint i = 0; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), 0); + priv = model->priv; + + seq_iter = g_sequence_get_begin_iter (priv->sequence); + iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, "model", model, NULL); + + while (!g_sequence_iter_is_end (seq_iter)) + { + g_object_set (iter, "iter", seq_iter, NULL); + if (_model_filter (model, iter)) + { + i++; + } + seq_iter = g_sequence_iter_next (seq_iter); + } + + g_object_unref (iter); + + return i; +} + + +/** + * clutter_model_set_sorting_column: + * @model: a #ClutterModel + * @column: the column of the @model to sort + * + * Sets the model to sort by @column. + * + * Since 0.6 + */ +void +clutter_model_set_sorting_column (ClutterModel *model, + guint column) +{ + ClutterModelPrivate *priv; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + priv = model->priv; + + if (column < 0 || column > priv->n_columns) + { + g_warning ("%s: Invalid column id value %d\n", G_STRLOC, column); + return; + } + priv->sort_column = column; + + _model_sort (model); + g_signal_emit (model, model_signals[SORT_CHANGED], 0); +} + +/** + * clutter_model_get_sorting_column: + * @model: a #ClutterModelIter + * + * Return value: The column number that the @model sorts. + * + * Since 0.6 + */ +guint +clutter_model_get_sorting_column (ClutterModel *model) +{ + ClutterModelPrivate *priv; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (model), 0); + priv = model->priv; + + return priv->sort_column; +} + +/** + * clutter_model_foreach: + * @model: a #ClutterModel + * @func: a #ClutterModelForeachFunc + * @user_data: user data to pass to @func + * + * Calls @func for each row in the model. + * + * Since 0.6 + */ +void +clutter_model_foreach (ClutterModel *model, + ClutterModelForeachFunc func, + gpointer user_data) +{ + ClutterModelPrivate *priv; + ClutterModelIter *iter; + GSequenceIter *seq_iter; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + priv = model->priv; + + seq_iter = g_sequence_get_begin_iter (priv->sequence); + iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, "model", model, NULL); + + while (!g_sequence_iter_is_end (seq_iter)) + { + g_object_set (iter, "iter", seq_iter, NULL); + if (_model_filter (model, iter)) + { + if (!func (model, iter, user_data)) + break; + } + seq_iter = g_sequence_iter_next (seq_iter); + } + + g_object_unref (iter); +} + +/** + * clutter_model_set_sort: + * @model: a #ClutterModel + * @column: the column to sort on + * @func: a #ClutterModelSortFunc, or #NULL + * @user_data: user data to pass to @func, or #NULL + * @notify: destroy notifier of @user_data, or #NULL + * + * Sorts the @model using @func. + * + * Since 0.6 + */ +void +clutter_model_set_sort (ClutterModel *model, + guint column, + ClutterModelSortFunc func, + gpointer user_data, + GDestroyNotify notify) +{ + ClutterModelPrivate *priv; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + priv = model->priv; + + if (priv->sort_data && priv->sort_notify) + priv->sort_notify (priv->sort_data); + + priv->sort = func; + priv->sort_data = user_data; + priv->sort_notify = notify; + + /* This takes care of calling _model_sort & emitting the signal*/ + clutter_model_set_sorting_column (model, column); +} + +/** + * clutter_model_set_filter: + * @model: a #ClutterModel + * @func: a #ClutterModelFilterFunc, or #NULL + * @user_data: user data to pass to @func, or #NULL + * @notify: destroy notifier of @user_data, or #NULL + * + * Filters the @model using @func. + * + * Since 0.6 + */ +void +clutter_model_set_filter (ClutterModel *model, + ClutterModelFilterFunc func, + gpointer user_data, + GDestroyNotify notify) +{ + ClutterModelPrivate *priv; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + priv = model->priv; + + if (priv->filter_data && priv->filter_notify) + priv->filter_notify (priv->filter_data); + + priv->filter = func; + priv->filter_data = user_data; + priv->filter_notify = notify; + + g_signal_emit (model, model_signals[FILTER_CHANGED], 0); +} + +/* + * ClutterModelIter Object + */ + +G_DEFINE_TYPE (ClutterModelIter, clutter_model_iter, G_TYPE_OBJECT); + +#define CLUTTER_MODEL_ITER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ + CLUTTER_TYPE_MODEL_ITER, ClutterModelIterPrivate)) + +struct _ClutterModelIterPrivate +{ + ClutterModel *model; + GSequenceIter *seq_iter; + + gboolean ignore_sort; +}; + +enum +{ + ITER_PROP_0, + + ITER_PROP_MODEL, + ITER_PROP_ROW, + ITER_PROP_ITER, +}; + +/* Forwards */ +static void _model_iter_get_value (ClutterModelIter *iter, + guint column, + GValue *value); +static void _model_iter_set_value (ClutterModelIter *iter, + guint column, + const GValue *value); +static gboolean _model_iter_is_first (ClutterModelIter *iter); +static gboolean _model_iter_is_last (ClutterModelIter *iter); +static ClutterModelIter * _model_iter_next (ClutterModelIter *iter); +static ClutterModelIter * _model_iter_prev (ClutterModelIter *iter); +static ClutterModel * _model_iter_get_model (ClutterModelIter *iter); +static guint _model_iter_get_row (ClutterModelIter *iter); +static void _model_iter_set_row (ClutterModelIter *iter, + guint row); + +/* GObject stuff */ +static void +clutter_model_iter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterModelIter *iter = CLUTTER_MODEL_ITER (object); + ClutterModelIterPrivate *priv; + + g_return_if_fail (CLUTTER_IS_MODEL_ITER (object)); + priv = iter->priv; + + switch (prop_id) + { + case ITER_PROP_MODEL: + g_value_set_object (value, priv->model); + break; + case ITER_PROP_ROW: + g_value_set_uint (value, clutter_model_iter_get_row (iter)); + break; + case ITER_PROP_ITER: + g_value_set_pointer (value, priv->seq_iter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +clutter_model_iter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterModelIter *iter = CLUTTER_MODEL_ITER (object); + ClutterModelIterPrivate *priv; + + g_return_if_fail (CLUTTER_IS_MODEL_ITER (object)); + priv = iter->priv; + + switch (prop_id) + { + case ITER_PROP_MODEL: + priv->model = g_value_get_object (value); + break; + case ITER_PROP_ROW: + _model_iter_set_row (iter, g_value_get_uint (value)); + break; + case ITER_PROP_ITER: + priv->seq_iter = g_value_get_pointer (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +clutter_model_iter_class_init (ClutterModelIterClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = clutter_model_iter_get_property; + gobject_class->set_property = clutter_model_iter_set_property; + + klass->get_value = _model_iter_get_value; + klass->set_value = _model_iter_set_value; + klass->is_first = _model_iter_is_first; + klass->is_last = _model_iter_is_last; + klass->next = _model_iter_next; + klass->prev = _model_iter_prev; + klass->get_model = _model_iter_get_model; + klass->get_row = _model_iter_get_row; + + /* Properties */ + + /** + * ClutterModelIter:model: + * + * A reference to the #ClutterModel that this iter belongs to. + * + * Since: 0.6 + */ + g_object_class_install_property (gobject_class, + ITER_PROP_MODEL, + g_param_spec_object ("model", + "Model", + "A ClutterModel", + CLUTTER_TYPE_MODEL, + G_PARAM_READWRITE)); + + /** + * ClutterModelIter:row: + * + * A reference to the row number that this iter represents. + * + * Since: 0.6 + */ + g_object_class_install_property (gobject_class, + ITER_PROP_ROW, + g_param_spec_uint ("row", + "Row", + "The row number", + 0, 65535, 0, + G_PARAM_READWRITE)); + + /** + * ClutterModelIter:iter: + * + * An internal iter reference. This property should only be used when + * sub-classing #ClutterModelIter. + * + * Since: 0.6 + */ + g_object_class_install_property (gobject_class, + ITER_PROP_ITER, + g_param_spec_pointer ("iter", + "Iter", + "The internal iter reference", + G_PARAM_READWRITE)); + + g_type_class_add_private (gobject_class, sizeof (ClutterModelIterPrivate)); +} + +static void +clutter_model_iter_init (ClutterModelIter *self) +{ + ClutterModelIterPrivate *priv; + + self->priv = priv = CLUTTER_MODEL_ITER_GET_PRIVATE (self); + + priv->seq_iter = NULL; + priv->model = NULL; + priv->ignore_sort = FALSE; +} + +static void +_model_iter_get_value (ClutterModelIter *iter, + guint column, + GValue *value) +{ + ClutterModelIterPrivate *priv; + GValueArray *value_array; + GValue *iter_value; + GValue real_value = { 0, }; + gboolean converted = FALSE; + + g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter)); + priv = iter->priv; + g_return_if_fail (CLUTTER_IS_MODEL (priv->model)); + g_return_if_fail (priv->seq_iter); + + value_array = g_sequence_get (priv->seq_iter); + iter_value = g_value_array_get_nth (value_array, column); + + if (!g_type_is_a (G_VALUE_TYPE (value), G_VALUE_TYPE (iter_value))) + { + if (!g_value_type_compatible (G_VALUE_TYPE (value), + G_VALUE_TYPE (iter_value)) && + !g_value_type_compatible (G_VALUE_TYPE (iter_value), + G_VALUE_TYPE (value))) + { + g_warning ("%s: Unable to convert from %s to %s\n", + G_STRLOC, + g_type_name (G_VALUE_TYPE (value)), + g_type_name (G_VALUE_TYPE (iter_value))); + return; + } + if (!g_value_transform (value, &real_value)) + { + g_warning ("%s: Unable to make conversion from %s to %s\n", + G_STRLOC, + g_type_name (G_VALUE_TYPE (value)), + g_type_name (G_VALUE_TYPE (iter_value))); + g_value_unset (&real_value); + } + converted = TRUE; + } + + if (converted) + g_value_copy (&real_value, value); + else + g_value_copy (iter_value, value); +} + +static void +_model_iter_set_value (ClutterModelIter *iter, + guint column, + const GValue *value) +{ + ClutterModelPrivate *model_priv; + ClutterModelIterPrivate *priv; + GValueArray *value_array; + GValue *iter_value; + GValue real_value = { 0, }; + gboolean converted = FALSE; + + g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter)); + priv = iter->priv; + g_return_if_fail (CLUTTER_IS_MODEL (priv->model)); + g_return_if_fail (priv->seq_iter); + + model_priv = priv->model->priv; + + value_array = g_sequence_get (priv->seq_iter); + iter_value = g_value_array_get_nth (value_array, column); + + if (!g_type_is_a (G_VALUE_TYPE (value), G_VALUE_TYPE (iter_value))) + { + if (!g_value_type_compatible (G_VALUE_TYPE (value), + G_VALUE_TYPE (iter_value)) && + !g_value_type_compatible (G_VALUE_TYPE (iter_value), + G_VALUE_TYPE (value))) + { + g_warning ("%s: Unable to convert from %s to %s\n", + G_STRLOC, + g_type_name (G_VALUE_TYPE (value)), + g_type_name (G_VALUE_TYPE (iter_value))); + return; + } + if (!g_value_transform (value, &real_value)) + { + g_warning ("%s: Unable to make conversion from %s to %s\n", + G_STRLOC, + g_type_name (G_VALUE_TYPE (value)), + g_type_name (G_VALUE_TYPE (iter_value))); + g_value_unset (&real_value); + } + converted = TRUE; + } + + if (converted) + { + g_value_copy (&real_value, iter_value); + g_value_unset (&real_value); + } + else + g_value_copy (value, iter_value); + + /* Check if we need to sort */ + if (!priv->ignore_sort) + { + if (model_priv->sort_column == column && model_priv->sort) + { + _model_sort (priv->model); + } + g_signal_emit (priv->model, model_signals[ROW_CHANGED], 0, iter); + } +} + +static gboolean +_model_iter_is_first (ClutterModelIter *iter) +{ + ClutterModel *model = NULL; + ClutterModelPrivate *model_priv; + ClutterModelIterPrivate *iter_priv; + GSequenceIter *begin, *end; + ClutterModelIter *temp_iter; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), TRUE); + iter_priv = iter->priv; + model = iter_priv->model; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), TRUE); + model_priv = model->priv; + + begin = g_sequence_get_begin_iter (model_priv->sequence); + end = iter_priv->seq_iter; + + temp_iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, "model", model, NULL); + + while (!g_sequence_iter_is_begin (begin)) + { + temp_iter->priv->seq_iter = begin; + if (_model_filter (model, temp_iter)) + { + end = begin; + break; + } + begin = g_sequence_iter_prev (begin); + } + /* This is because the 'begin_iter' is always *before* the last valid iter. + * Otherwise we'd have endless loops + */ + end = g_sequence_iter_prev (end); + + g_object_unref (temp_iter); + return iter_priv->seq_iter == end; +} + +static gboolean +_model_iter_is_last (ClutterModelIter *iter) +{ + ClutterModel *model = NULL; + ClutterModelPrivate *model_priv; + ClutterModelIterPrivate *iter_priv; + GSequenceIter *begin, *end; + ClutterModelIter *temp_iter; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), TRUE); + iter_priv = iter->priv; + model = iter_priv->model; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), TRUE); + model_priv = model->priv; + + if (g_sequence_iter_is_end (iter_priv->seq_iter)) + return TRUE; + + begin = g_sequence_get_end_iter (model_priv->sequence); + begin = g_sequence_iter_prev (begin); + end = iter_priv->seq_iter; + + temp_iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, + "model", model, + "iter", begin, + NULL); + + while (!g_sequence_iter_is_begin (begin)) + { + temp_iter->priv->seq_iter = begin; + if (_model_filter (model, temp_iter)) + { + end = begin; + break; + } + begin = g_sequence_iter_prev (begin); + } + /* This is because the 'end_iter' is always *after* the last valid iter. + * Otherwise we'd have endless loops + */ + end = g_sequence_iter_next (end); + + g_object_unref (temp_iter); + return iter_priv->seq_iter == end; +} + +static ClutterModelIter * +_model_iter_next (ClutterModelIter *iter) +{ + ClutterModel *model = NULL; + ClutterModelPrivate *model_priv; + ClutterModelIterPrivate *iter_priv; + GSequenceIter *filter_next; + ClutterModelIter *temp_iter; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), iter); + iter_priv = iter->priv; + model = iter_priv->model; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), iter); + model_priv = model->priv; + + filter_next = g_sequence_iter_next (iter_priv->seq_iter); + temp_iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, "model", model, NULL); + + while (!g_sequence_iter_is_end (filter_next)) + { + g_object_set (temp_iter, "iter", filter_next, NULL); + if (_model_filter (model, temp_iter)) + { + break; + } + filter_next = g_sequence_iter_next (filter_next); + } + + g_object_unref (temp_iter); + + /* We do this because the 'end_iter' is always *after* the last valid iter. + * Otherwise loops will go on forever + */ + if (filter_next == iter_priv->seq_iter) + filter_next = g_sequence_iter_next (filter_next); + + g_object_set (iter, "iter", filter_next, NULL); + return iter; +} + +static ClutterModelIter * +_model_iter_prev (ClutterModelIter *iter) +{ + ClutterModel *model = NULL; + ClutterModelPrivate *model_priv; + ClutterModelIterPrivate *iter_priv; + GSequenceIter *filter_prev; + ClutterModelIter *temp_iter; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), iter); + iter_priv = iter->priv; + model = iter_priv->model; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), iter); + model_priv = model->priv; + + filter_prev = g_sequence_iter_prev (iter_priv->seq_iter); + temp_iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, "model", model, NULL); + + while (!g_sequence_iter_is_begin (filter_prev)) + { + g_object_set (temp_iter, "iter", filter_prev, NULL); + if (_model_filter (model, temp_iter)) + { + break; + } + filter_prev = g_sequence_iter_prev (filter_prev); + } + + /* We do this because the 'end_iter' is always *after* the last valid iter. + * Otherwise loops will go on forever + */ + if (filter_prev == iter_priv->seq_iter) + filter_prev = g_sequence_iter_prev (filter_prev); + + g_object_set (iter, "iter", filter_prev, NULL); + + g_object_unref (temp_iter); + + return iter; +} + +static ClutterModel * +_model_iter_get_model (ClutterModelIter *iter) +{ + ClutterModelIterPrivate *priv; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), NULL); + priv = iter->priv; + g_return_val_if_fail (CLUTTER_IS_MODEL (priv->model), NULL); + + return priv->model; +} + +static guint +_model_iter_get_row (ClutterModelIter *iter) +{ + ClutterModel *model = NULL; + ClutterModelPrivate *model_priv; + ClutterModelIterPrivate *iter_priv; + GSequenceIter *filter_next; + ClutterModelIter *temp_iter; + guint row = 0; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), row); + iter_priv = iter->priv; + model = iter_priv->model; + + g_return_val_if_fail (CLUTTER_IS_MODEL (model), row); + model_priv = model->priv; + + filter_next = g_sequence_iter_next (iter_priv->seq_iter); + temp_iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, + "model", model, NULL); + + while (!g_sequence_iter_is_end (filter_next)) + { + g_object_set (temp_iter, "iter", filter_next, NULL); + if (_model_filter (model, temp_iter)) + { + if (iter_priv->seq_iter == temp_iter->priv->seq_iter) + break; + row++; + } + filter_next = g_sequence_iter_next (filter_next); + } + + g_object_unref (temp_iter); + + return row; +} + +static void +_model_iter_set_row (ClutterModelIter *iter, guint row) +{ + ClutterModel *model = NULL; + ClutterModelPrivate *model_priv; + ClutterModelIterPrivate *iter_priv; + GSequenceIter *filter_next; + ClutterModelIter *temp_iter; + guint i = 0; + + g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter)); + iter_priv = iter->priv; + model = iter_priv->model; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + model_priv = model->priv; + + filter_next = g_sequence_get_begin_iter (model_priv->sequence); + temp_iter = g_object_new (CLUTTER_TYPE_MODEL_ITER, + "model", model, NULL); + + while (!g_sequence_iter_is_end (filter_next)) + { + g_object_set (temp_iter, "iter", filter_next, NULL); + if (_model_filter (model, temp_iter)) + { + if (i == row) + { + iter_priv->seq_iter = filter_next; + break; + } + i++; + } + filter_next = g_sequence_iter_next (filter_next); + } + g_object_unref (temp_iter); +} + +/* + * Public functions + */ + +/** + * clutter_model_iter_set_valist: + * @iter: a #ClutterModelIter + * @args: va_list of column/value pairs, terminiated by -1 + * + * See #clutter_model_iter_set; this version takes a va_list for language + * bindings. + * + * Since 0.6 + */ +void +clutter_model_iter_set_valist (ClutterModelIter *iter, + va_list args) +{ + ClutterModel *model; + ClutterModelPrivate *model_priv; + ClutterModelIterPrivate *priv; + guint column = 0; + gboolean sort = FALSE; + + g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter)); + priv = iter->priv; + model = priv->model; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + model_priv = model->priv; + + column = va_arg (args, gint); + + /* Don't want to sort while setting lots of fields, leave that till the end + */ + priv->ignore_sort = TRUE; + while (column != -1) + { + GValue value = { 0, }; + gchar *error = NULL; + + if (column < 0 || column >= model_priv->n_columns) + { + g_warning ("%s: Invalid column number %d added to iter (remember to end you list of columns with a -1)", G_STRLOC, column); + break; + } + g_value_init (&value, model_priv->column_types[column]); + + G_VALUE_COLLECT (&value, args, 0, &error); + if (error) + { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + + /* Leak value as it might not be in a sane state */ + break; + } + + clutter_model_iter_set_value (iter, column, &value); + + g_value_unset (&value); + + if (column == model_priv->sort_column) + sort = TRUE; + + column = va_arg (args, gint); + } + priv->ignore_sort = FALSE; + if (sort) + _model_sort (model); + + g_signal_emit (model, model_signals[ROW_CHANGED], 0, iter); +} + +/** + * clutter_model_iter_get: + * @iter: a #ClutterModelIter + * @Varargs: va_list of column/return location pairs, terminiated by -1 + * + * Gets the value of one or more cells in the row referenced by @iter. The + * variable argument list should contain integer column numbers, each column + * column number followed by a place to store the value being retrieved. The + * list is terminated by a -1. For example, to get a value from column 0 + * with type G_TYPE_STRING, you would write: clutter_model_iter_get + * (iter, 0, &place_string_here, -1);, where place_string_here is + * a gchar* to be filled with the string. If appropriate, the returned values + * have to be freed or unreferenced. + * + * Since 0.6 + */ +void +clutter_model_iter_get (ClutterModelIter *iter, + ...) +{ + va_list args; + + g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter)); + + va_start (args, iter); + clutter_model_iter_get_valist (iter, args); + va_end (args); +} + +/** + * clutter_model_iter_get_value: + * @iter: a #ClutterModelIter + * @column: column number to retrieve the value from + * @value: an empty #GValue to set + * + * Sets an initializes @value to that at @column. When done with @value, + * #g_value_unset() needs to be called to free any allocated memory. + * + * Since 0.6 + */ +void +clutter_model_iter_get_value (ClutterModelIter *iter, + guint column, + GValue *value) +{ + ClutterModelIterClass *klass; + + g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter)); + + klass = CLUTTER_MODEL_ITER_GET_CLASS (iter); + g_return_if_fail (klass->get_value != NULL); + + klass->get_value (iter, column, value); +} + +/** + * clutter_model_iter_get_valist: + * @iter: a #ClutterModelIter + * @args: va_list of column/return location pairs, terminiated by -1 + * + * See #clutter_model_iter_get; this version takes a va_list for language + * bindings. + * + * Since 0.6 + */ +void +clutter_model_iter_get_valist (ClutterModelIter *iter, + va_list args) +{ + ClutterModel *model; + ClutterModelPrivate *model_priv; + ClutterModelIterPrivate *priv; + guint column = 0; + + g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter)); + priv = iter->priv; + model = priv->model; + + g_return_if_fail (CLUTTER_IS_MODEL (model)); + model_priv = model->priv; + + column = va_arg (args, gint); + + while (column != -1) + { + GValue value = { 0, }; + gchar *error = NULL; + + if (column < 0 || column >= model_priv->n_columns) + { + g_warning ("%s: Invalid column number %d added to iter (remember to end you list of columns with a -1)", G_STRLOC, column); + break; + } + + g_value_init (&value, model_priv->column_types[column]); + clutter_model_iter_get_value (iter, column, &value); + + G_VALUE_LCOPY (&value, args, 0, &error); + if (error) + { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + + /* Leak value as it might not be in a sane state */ + break; + } + + g_value_unset (&value); + + column = va_arg (args, gint); + } +} + +/** + * clutter_model_iter_set: + * @iter: a #ClutterModelIter + * @Varargs: va_list of column/return location pairs, terminiated by -1 + * + * Sets the value of one or more cells in the row referenced by @iter. The + * variable argument list should contain integer column numbers, each column + * column number followed by the value to be set. The list is terminated by a + * -1. For example, to set column 0 with type G_TYPE_STRING, you would write: + * clutter_model_iter_set (iter, 0, "foo", -1);. + * + * Since 0.6 + */ +void +clutter_model_iter_set (ClutterModelIter *iter, + ...) +{ + va_list args; + + g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter)); + + va_start (args, iter); + clutter_model_iter_set_valist (iter, args); + va_end (args); +} + + +/** + * clutter_model_iter_set_value: + * @iter: a #ClutterModelIter + * @column: column number to retrieve the value from + * @value: new value for the cell + * + * Sets the data in the cell specified by @iter and @column. The type of @value + * must be convertable to the type of the column. + * + * Since 0.6 + */ +void +clutter_model_iter_set_value (ClutterModelIter *iter, + guint column, + const GValue *value) +{ + ClutterModelIterClass *klass; + + g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter)); + + klass = CLUTTER_MODEL_ITER_GET_CLASS (iter); + g_return_if_fail (klass->set_value != NULL); + + klass->set_value (iter, column, value); +} + +/** + * clutter_model_iter_is_first: + * @iter: a #ClutterModelIter + * + * Return value: #TRUE if @iter is the first iter in the filtered model. + * + * Since 0.6 + */ +gboolean +clutter_model_iter_is_first (ClutterModelIter *iter) +{ + ClutterModelIterClass *klass; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), FALSE); + + klass = CLUTTER_MODEL_ITER_GET_CLASS (iter); + g_return_val_if_fail (klass->is_first != NULL, FALSE); + + return klass->is_first (iter); +} + +/** + * clutter_model_iter_is_last: + * @iter: a #ClutterModelIter + * + * Return value: #TRUE if @iter is the last iter in the filtered model. + * + * Since 0.6 + */ +gboolean +clutter_model_iter_is_last (ClutterModelIter *iter) +{ + ClutterModelIterClass *klass; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), FALSE); + + klass = CLUTTER_MODEL_ITER_GET_CLASS (iter); + g_return_val_if_fail (klass->is_last != NULL, FALSE); + + return klass->is_last (iter); +} + +/** + * clutter_model_iter_next: + * @iter: a #ClutterModelIter + * + * Sets the @iter to point at the next position in the model after @iter. + * + * Return value: @iter, pointing at the next position. + * + * Since 0.6 + */ +ClutterModelIter * +clutter_model_iter_next (ClutterModelIter *iter) +{ + ClutterModelIterClass *klass; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), NULL); + + klass = CLUTTER_MODEL_ITER_GET_CLASS (iter); + g_return_val_if_fail (klass->next != NULL, NULL); + + return klass->next (iter); +} + +/** + * clutter_model_iter_prev: + * @iter: a #ClutterModelIter + * + * Sets the @iter to point at the previous position in the model after @iter. + * + * Return value: @iter, pointing at the previous position. + * + * Since 0.6 + */ +ClutterModelIter * +clutter_model_iter_prev (ClutterModelIter *iter) +{ + ClutterModelIterClass *klass; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), NULL); + + klass = CLUTTER_MODEL_ITER_GET_CLASS (iter); + g_return_val_if_fail (klass->prev != NULL, NULL); + + return klass->prev (iter); +} + +/** + * clutter_model_iter_get_model: + * @iter: a #ClutterModelIter + * + * Returns a pointer to the #ClutterModel that this iter is part of. + * + * Return value: a pointer to a #ClutterModel. + * + * Since 0.6 + */ +ClutterModel * +clutter_model_iter_get_model (ClutterModelIter *iter) +{ + ClutterModelIterClass *klass; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), NULL); + + klass = CLUTTER_MODEL_ITER_GET_CLASS (iter); + g_return_val_if_fail (klass->get_model != NULL, NULL); + + return klass->get_model (iter); +} + +/** + * clutter_model_iter_get_row: + * @iter: a #ClutterModelIter + * + * Returns the position of the row that the @iter points to. + * + * Return value: the position of the @iter in the model. + * + * Since 0.6 + */ +guint +clutter_model_iter_get_row (ClutterModelIter *iter) +{ + ClutterModelIterClass *klass; + + g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), -1); + + klass = CLUTTER_MODEL_ITER_GET_CLASS (iter); + g_return_val_if_fail (klass->get_row != NULL, -1); + + return klass->get_row (iter); +} diff --git a/clutter/clutter-model.h b/clutter/clutter-model.h new file mode 100644 index 000000000..90b51d622 --- /dev/null +++ b/clutter/clutter-model.h @@ -0,0 +1,302 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * Neil Jagdish Patel + * Emmanuele Bassi + * + * Copyright (C) 2006 OpenedHand + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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 library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CLUTTER_MODEL_H__ +#define __CLUTTER_MODEL_H__ + +#include +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_MODEL (clutter_model_get_type ()) +#define CLUTTER_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_MODEL, ClutterModel)) +#define CLUTTER_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_MODEL, ClutterModelClass)) +#define CLUTTER_IS_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_MODEL)) +#define CLUTTER_IS_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_MODEL)) +#define CLUTTER_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_MODEL, ClutterModelClass)) + +typedef struct _ClutterModel ClutterModel; +typedef struct _ClutterModelClass ClutterModelClass; +typedef struct _ClutterModelPrivate ClutterModelPrivate; +typedef struct _ClutterModelIter ClutterModelIter; +typedef struct _ClutterModelIterClass ClutterModelIterClass; +typedef struct _ClutterModelIterPrivate ClutterModelIterPrivate; + + +/** + * ClutterModelFilterFunc: + * @model: a #ClutterModel + * @iter: the iterator for the row + * @user_data: data passed to clutter_model_set_filter() + * + * Filters the content of a row in the model. + * + * Return value: If the row should be displayed, return %TRUE + * + * Since: 0.6 + */ +typedef gboolean (*ClutterModelFilterFunc) (ClutterModel *model, + ClutterModelIter *iter, + gpointer user_data); + +/** + * ClutterModelSortFunc: + * @model: a #ClutterModel + * @a: a #GValue representing the contents of the row + * @b: a #GValue representing the contents of the second row + * @user_data: data passed to clutter_model_set_sort() + * + * Compares the content of two rows in the model. + * + * Return value: a positive integer if @a is after @b, a negative integer if + * @a is before @b, or 0 if the rows are the same + * + * Since: 0.6 + */ +typedef gboolean (*ClutterModelSortFunc) (ClutterModel *model, + const GValue *a, + const GValue *b, + gpointer user_data); + +/** + * ClutterModelForeachFunc: + * @model: a #ClutterModel + * @iter: the iterator for the row + * @user_data: data passed to clutter_model_foreach() + * + * Iterates on the content of a row in the model + * + * Return value: %TRUE if the iteration should continue, %FALSE otherwise + * + * Since: 0.6 + */ +typedef gboolean (*ClutterModelForeachFunc) (ClutterModel *model, + ClutterModelIter *iter, + gpointer user_data); + +/** + * ClutterModel: + * + * Base class for list models. The #ClutterModel structure contains + * only private data and should be manipulated using the provided + * API. + * + * Since: 0.6 + */ +struct _ClutterModel +{ + /*< private >*/ + GObject parent_instance; + + ClutterModelPrivate *priv; +}; + +/** + * ClutterModelClass: + * @row_added: signal class handler for ClutterModel::row-added + * @row_removed: signal class handler for ClutterModel::row-removed + * @row_changed: signal class handler for ClutterModel::row-changed + * @sort_changed: signal class handler for ClutterModel::sort-changed + * @filter_changed: signal class handler for ClutterModel::filter-changed + * + * Class for #ClutterModel instances. + * + * Since: 0.6 + */ +struct _ClutterModelClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< public >*/ + /* vtable */ + ClutterModelIter *(* get_iter_at_row) (ClutterModel *model, + guint index_); + + /* signals */ + void (* row_added) (ClutterModel *model, + ClutterModelIter *iter); + void (* row_removed) (ClutterModel *model, + ClutterModelIter *iter); + void (* row_changed) (ClutterModel *model, + ClutterModelIter *iter); + + void (* sort_changed) (ClutterModel *model); + void (* filter_changed) (ClutterModel *model); + + /*< private >*/ + /* padding for future */ + void (*_clutter_model_1) (void); + void (*_clutter_model_2) (void); + void (*_clutter_model_3) (void); + void (*_clutter_model_4) (void); +}; + + + +GType clutter_model_get_type (void) G_GNUC_CONST; + +ClutterModel * clutter_model_new (guint n_columns, + ...); +ClutterModel * clutter_model_newv (guint n_columns, + GType *types); +void clutter_model_set_types (ClutterModel *model, + guint n_columns, + GType *types); + +gboolean clutter_model_append (ClutterModel *model, + ...); +gboolean clutter_model_prepend (ClutterModel *model, + ...); +gboolean clutter_model_insert (ClutterModel *model, + guint index_, + ...); +gboolean clutter_model_insert_value (ClutterModel *model, + guint index_, + guint column, + const GValue *value); +void clutter_model_remove (ClutterModel *model, + guint index_); +guint clutter_model_get_n_rows (ClutterModel *model); + +ClutterModelIter *clutter_model_get_first_iter (ClutterModel *model); +ClutterModelIter *clutter_model_get_last_iter (ClutterModel *model); +ClutterModelIter *clutter_model_get_iter_at_row (ClutterModel *model, + guint index_); +void clutter_model_set_sorting_column (ClutterModel *model, + guint column); +guint clutter_model_get_sorting_column (ClutterModel *model); +void clutter_model_foreach (ClutterModel *model, + ClutterModelForeachFunc func, + gpointer user_data); +void clutter_model_set_sort (ClutterModel *model, + guint column, + ClutterModelSortFunc func, + gpointer user_data, + GDestroyNotify notify); +void clutter_model_set_filter (ClutterModel *model, + ClutterModelFilterFunc func, + gpointer user_data, + GDestroyNotify notify); + + +/* + * ClutterModelIter + */ + +#define CLUTTER_TYPE_MODEL_ITER (clutter_model_iter_get_type ()) +#define CLUTTER_MODEL_ITER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_MODEL_ITER, ClutterModelIter)) +#define CLUTTER_MODEL_ITER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_MODEL_ITER, ClutterModelIterClass)) +#define CLUTTER_IS_MODEL_ITER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_MODEL_ITER)) +#define CLUTTER_IS_MODEL_ITER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_MODEL_ITER)) +#define CLUTTER_MODEL_ITER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_MODEL_ITER, ClutterModelIterClass)) + +/** + * ClutterModelIter: + * + * Base class for list models iters. The #ClutterModelIter structure contains + * only private data and should be manipulated using the provided + * API. + * + * Since: 0.6 + */ +struct _ClutterModelIter +{ + /*< private >*/ + GObject parent_instance; + + ClutterModelIterPrivate *priv; +}; + +/** + * ClutterModelIterClass: + * + * Class for #ClutterModelIter instances. + * + * Since: 0.6 + */ +struct _ClutterModelIterClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< public >*/ + + /* vtable not signals */ + void (* get_value) (ClutterModelIter *iter, + guint column, + GValue *value); + void (* set_value) (ClutterModelIter *iter, + guint column, + const GValue *value); + + gboolean (* is_first) (ClutterModelIter *iter); + gboolean (* is_last) (ClutterModelIter *iter); + + ClutterModelIter *(* next) (ClutterModelIter *iter); + ClutterModelIter *(* prev) (ClutterModelIter *iter); + + ClutterModel* (* get_model) (ClutterModelIter *iter); + guint (* get_row) (ClutterModelIter *iter); + + /*< private >*/ + /* padding for future */ + void (*_clutter_model_iter_1) (void); + void (*_clutter_model_iter_2) (void); + void (*_clutter_model_iter_3) (void); + void (*_clutter_model_iter_4) (void); +}; + +GType clutter_model_iter_get_type (void) G_GNUC_CONST; + + +void clutter_model_iter_get (ClutterModelIter *iter, + ...); +void clutter_model_iter_get_valist (ClutterModelIter *iter, + va_list args); +void clutter_model_iter_get_value (ClutterModelIter *iter, + guint column, + GValue *value); +void clutter_model_iter_set (ClutterModelIter *iter, + ...); +void clutter_model_iter_set_valist (ClutterModelIter *iter, + va_list args); +void clutter_model_iter_set_value (ClutterModelIter *iter, + guint column, + const GValue *value); +gboolean clutter_model_iter_is_first (ClutterModelIter *iter); +gboolean clutter_model_iter_is_last (ClutterModelIter *iter); +ClutterModelIter *clutter_model_iter_next (ClutterModelIter *iter); +ClutterModelIter *clutter_model_iter_prev (ClutterModelIter *iter); +ClutterModel * clutter_model_iter_get_model (ClutterModelIter *iter); +guint clutter_model_iter_get_row (ClutterModelIter *iter); + + +G_END_DECLS + +#endif /* __CLUTTER_MODEL_H__ */ diff --git a/clutter/clutter.h b/clutter/clutter.h index 89dafb475..01a239884 100644 --- a/clutter/clutter.h +++ b/clutter/clutter.h @@ -54,6 +54,7 @@ #include "clutter-layout.h" #include "clutter-main.h" #include "clutter-media.h" +#include "clutter-model.h" #include "clutter-stage.h" #include "clutter-texture.h" #include "clutter-timeout-pool.h" diff --git a/doc/reference/ChangeLog b/doc/reference/ChangeLog index 608421f71..25c869bde 100644 --- a/doc/reference/ChangeLog +++ b/doc/reference/ChangeLog @@ -1,3 +1,10 @@ +2007-11-15 Neil J. Patel + + * clutter-docs.sgml: + * clutter-sections.txt: + * clutter.types: + Added support for ClutterModel. + 2007-11-14 Emmanuele Bassi * clutter-sections.txt: Update with the ClutterScriptable changes diff --git a/doc/reference/clutter-docs.sgml b/doc/reference/clutter-docs.sgml index de2a550d2..c9dca0d6a 100644 --- a/doc/reference/clutter-docs.sgml +++ b/doc/reference/clutter-docs.sgml @@ -145,6 +145,8 @@ + + diff --git a/doc/reference/clutter-sections.txt b/doc/reference/clutter-sections.txt index f29aacc22..b61abb939 100644 --- a/doc/reference/clutter-sections.txt +++ b/doc/reference/clutter-sections.txt @@ -1133,3 +1133,70 @@ CLUTTER_SCRIPTABLE_GET_IFACE ClutterScriptable clutter_scriptable_get_type + +
+clutter-model +ClutterModel +ClutterModel +ClutterModelClass +ClutterModelFilterFunc +ClutterModelSortFunc +ClutterModelForeachFunc +clutter_model_new +clutter_model_newv +clutter_model_set_types +clutter_model_append +clutter_model_prepend +clutter_model_insert +clutter_model_insert_value +clutter_model_remove +clutter_model_get_n_rows +clutter_model_get_first_iter +clutter_model_get_last_iter +clutter_model_get_iter_at_row +clutter_model_set_sorting_column +clutter_model_get_sorting_column +clutter_model_foreach +clutter_model_set_sort +clutter_model_set_filter + +CLUTTER_TYPE_MODEL +CLUTTER_MODEL +CLUTTER_IS_MODEL +CLUTTER_MODEL_CLASS +CLUTTER_IS_MODEL_CLASS +CLUTTER_MODEL_GET_CLASS + +ClutterModelPrivate +
+ +
+clutter-model-iter +ClutterModelIter +ClutterModelIter +ClutterModelIterClass +clutter_model_iter_get +clutter_model_iter_get_valist +clutter_model_iter_get_value +clutter_model_iter_set +clutter_model_iter_set_valist +clutter_model_iter_set_value +clutter_model_iter_is_first +clutter_model_iter_is_last +clutter_model_iter_next +clutter_model_iter_prev +clutter_model_iter_get_model +clutter_model_iter_get_row + +CLUTTER_TYPE_MODEL_ITER +CLUTTER_MODEL_ITER +CLUTTER_IS_MODEL_ITER +CLUTTER_MODEL_ITER_CLASS +CLUTER_IS_MODEL_ITER_CLASS +CLUTTER_MODEL_GET_CLASS + +ClutterModelIterPrivate +
+ + + diff --git a/doc/reference/clutter.types b/doc/reference/clutter.types index d0187b471..c9412ca48 100644 --- a/doc/reference/clutter.types +++ b/doc/reference/clutter.types @@ -26,3 +26,5 @@ clutter_hbox_get_type clutter_vbox_get_type clutter_script_get_type clutter_scriptable_get_type +clutter_model_get_type +clutter_model_iter_get_type diff --git a/tests/Makefile.am b/tests/Makefile.am index 6c6aa4ba9..a06df81f7 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,7 +1,7 @@ noinst_PROGRAMS = test-textures test-events test-offscreen test-scale \ test-actors test-behave test-text test-entry test-project \ test-boxes test-perspective test-rotate test-depth \ - test-threads test-timeline test-score test-script + test-threads test-timeline test-score test-script test-model INCLUDES = -I$(top_srcdir)/ LDADD = $(top_builddir)/clutter/libclutter-@CLUTTER_FLAVOUR@-@CLUTTER_MAJORMINOR@.la @@ -25,5 +25,6 @@ test_threads_SOURCES = test-threads.c test_timeline_SOURCES = test-timeline.c test_score_SOURCES = test-score.c test_script_SOURCES = test-script.c +test_model_SOURCES = test-model.c EXTRA_DIST = redhand.png test-script.json diff --git a/tests/test-model.c b/tests/test-model.c new file mode 100644 index 000000000..7238d9719 --- /dev/null +++ b/tests/test-model.c @@ -0,0 +1,198 @@ +#include +#include + +static void +print_iter (ClutterModelIter *iter, const gchar *text) +{ + gint i; + gchar *string; + + clutter_model_iter_get (iter, 0, &i, 1, &string, -1); + g_print ("%s: %d, %s\n", text, i, string); + g_free (string); +} + +static gboolean +foreach_func (ClutterModel *model, ClutterModelIter *iter, gpointer null) +{ + gint i; + gchar *string; + + clutter_model_iter_get (iter, 0, &i, 1, &string, -1); + g_print ("Foreach: %d: %s\n", i, string); + + g_free (string); + return TRUE; +} + +static gboolean +filter_func (ClutterModel *model, ClutterModelIter *iter, gpointer null) +{ + gint i = 0; + + clutter_model_iter_get (iter, 0, &i, -1); + + return !(i % 2); +} + +static gint +sort_func (ClutterModel *model, + const GValue *a, + const GValue *b, + gpointer null) +{ + return -1 * strcmp (g_value_get_string (a), g_value_get_string (b)); +} + +static void +on_row_changed (ClutterModel *model, ClutterModelIter *iter) +{ + print_iter (iter, "Changed"); +} + +static void +filter_model (ClutterModel *model) +{ + ClutterModelIter *iter; + + clutter_model_set_filter (model, filter_func, NULL, NULL); + + iter = clutter_model_get_first_iter (model); + + while (!clutter_model_iter_is_last (iter)) + { + print_iter (iter, "Filtered Forward Iteration"); + iter = clutter_model_iter_next (iter); + } + g_object_unref (iter); + + clutter_model_set_sort (model, 1, sort_func, NULL, NULL); + + g_signal_connect (model, "row-changed", + G_CALLBACK (on_row_changed), NULL); + + iter = clutter_model_get_iter_at_row (model, 0); + clutter_model_iter_set (iter, 1, "Changed string of 0th row, automatically" + " gets sorted", -1); + g_object_unref (iter); + + clutter_model_foreach (model, foreach_func, NULL); + + clutter_model_set_filter (model, NULL, NULL, NULL); + while (clutter_model_get_n_rows (model)) + { + clutter_model_remove (model, 0); + } + + clutter_main_quit (); +} + +static void +iterate (ClutterModel *model) +{ + ClutterModelIter *iter; + + iter = clutter_model_get_first_iter (model); + + while (!clutter_model_iter_is_last (iter)) + { + print_iter (iter, "Forward Iteration"); + iter = clutter_model_iter_next (iter); + } + g_object_unref (iter); + + iter = clutter_model_get_last_iter (model); + do + { + print_iter (iter, "Reverse Iteration"); + iter = clutter_model_iter_prev (iter); + } + while (!clutter_model_iter_is_first (iter)); + + print_iter (iter, "Reverse Iteration"); + g_object_unref (iter); + + filter_model (model); +} + + +static gboolean +populate_model (ClutterModel *model) +{ + gint i; + + for (i = 0; i < 10; i++) + { + gchar *string = g_strdup_printf ("String %d", i); + + clutter_model_append (model, + 0, i, + 1, string, + -1); + g_free (string); + } + + clutter_model_foreach (model, foreach_func, NULL); + iterate (model); + + return FALSE; +} + +static void +on_row_added (ClutterModel *model, ClutterModelIter *iter, gpointer null) +{ + gint i; + gchar *string; + + clutter_model_iter_get (iter, 0, &i, 1, &string, -1); + + g_print ("Added: %d, %s\n", i, string); + + g_free (string); +} + +static void +on_row_removed (ClutterModel *model, ClutterModelIter *iter, gpointer null) +{ + print_iter (iter, "Removed"); +} + +static void +on_sort_changed (ClutterModel *model) +{ + g_print ("\nSort Changed\n\n"); + clutter_model_foreach (model, foreach_func, NULL); +} + +static void +on_filter_changed (ClutterModel *model) +{ + g_print ("\nFilter Changed\n\n"); +} + +int +main (int argc, char *argv[]) +{ + ClutterModel *model; + + clutter_init (&argc, &argv); + + model = clutter_model_new (2, G_TYPE_INT, G_TYPE_STRING); + + g_timeout_add (1000, (GSourceFunc)populate_model, model); + + g_signal_connect (model, "row-added", + G_CALLBACK (on_row_added), NULL); + g_signal_connect (model, "row-removed", + G_CALLBACK (on_row_removed), NULL); + g_signal_connect (model, "sort-changed", + G_CALLBACK (on_sort_changed), NULL); + g_signal_connect (model, "filter-changed", + G_CALLBACK (on_filter_changed), NULL); + + clutter_main(); + + g_object_unref (model); + + return 0; +}