clutter/clutter/clutter-model.c

2378 lines
63 KiB
C

/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Authored By Matthew Allum <mallum@openedhand.com>
* Neil Jagdish Patel <njp@o-hand.com>
* Emmanuele Bassi <ebassi@openedhand.com>
*
* 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, see <http://www.gnu.org/licenses/>.
*
*
*
* NB: Inspiration for column storage taken from GtkListStore
*/
/**
* SECTION:clutter-model
* @short_description: A generic model implementation
*
* #ClutterModel is a generic list model API which can be used to implement
* the model-view-controller architectural pattern in Clutter.
*
* The #ClutterModel class is a list model which can accept most GObject
* types as a column type.
*
* ## Creating a simple ClutterModel
*
* The example below shows how to create a simple list model.
*
* |[<!-- language="C" -->
* enum
* {
* COLUMN_INT,
* COLUMN_STRING,
*
* N_COLUMNS
* };
*
* {
* ClutterModel *model;
* gint i;
*
* model = clutter_list_model_new (N_COLUMNS,
* // column type, title
* G_TYPE_INT, "my integers",
* G_TYPE_STRING, "my strings");
* 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);
* }
*
*
* }
* ]|
*
* ## Iterating through a ClutterModel
*
* Iterating through the model consists of retrieving a new #ClutterModelIter
* pointing to the starting row, and calling clutter_model_iter_next() or
* clutter_model_iter_prev() to move forward or backwards, repectively.
*
* A valid #ClutterModelIter represents the position between two rows in the
* model. For example, the "first" iterator represents the gap immediately
* before the first row, and the "last" iterator represents the gap immediately
* after the last row. In an empty sequence, the first and last iterators are
* the same.
*
* |[<!-- language="C" -->
* enum
* {
* COLUMN_INT,
* COLUMN_STRING.
*
* N_COLUMNS
* };
*
* {
* ClutterModel *model;
* ClutterModelIter *iter = NULL;
*
* // fill the model
* model = populate_model ();
*
* // get the iterator for the first row in the model
* iter = clutter_model_get_first_iter (model);
* while (!clutter_model_iter_is_last (iter))
* {
* print_row (iter);
*
* iter = clutter_model_iter_next (iter);
* }
*
* // Make sure to unref the iter
* g_object_unref (iter);
* }
* ]|
*
* #ClutterModel is an abstract class. Clutter provides a list model
* implementation called #ClutterListModel which has been optimised
* for insertion and look up in sorted lists.
*
* #ClutterModel is available since Clutter 0.6
*
* ## ClutterModel custom properties for ClutterScript
*
* #ClutterModel defines a custom property "columns" for #ClutterScript
* which allows defining the column names and types. It also defines a custom
* "rows" property which allows filling the #ClutterModel with some
* data.
*
* The definition below will create a #ClutterListModel with three
* columns: the first one with name "Name" and containing strings; the
* second one with name "Score" and containing integers; the third one with
* name "Icon" and containing #ClutterTextures. The model is filled
* with three rows. A row can be defined either with an array that holds
* all columns of a row, or an object that holds "column-name" :
* "column-value" pairs.
*
* |[
* {
* "type" : "ClutterListModel",
* "id" : "teams-model",
* "columns" : [
* [ "Name", "gchararray" ],
* [ "Score", "gint" ],
* [ "Icon", "ClutterTexture" ]
* ],
* "rows" : [
* [ "Team 1", 42, { "type" : "ClutterTexture", "filename" : "team1.png" } ],
* [ "Team 2", 23, "team2-icon-script-id" ],
* { "Name" : "Team 3", "Icon" : "team3-icon-script-id" }
* ]
* }
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <glib-object.h>
#include <gobject/gvaluecollector.h>
#include "clutter-model.h"
#include "clutter-model-private.h"
#include "clutter-marshal.h"
#include "clutter-private.h"
#include "clutter-debug.h"
#include "clutter-scriptable.h"
#include "clutter-script-private.h"
enum
{
PROP_0,
PROP_FILTER_SET
};
enum
{
ROW_ADDED,
ROW_REMOVED,
ROW_CHANGED,
SORT_CHANGED,
FILTER_CHANGED,
LAST_SIGNAL
};
static guint model_signals[LAST_SIGNAL] = { 0, };
struct _ClutterModelPrivate
{
GType *column_types;
gchar **column_names;
/* we use an integer here because we want to be able to use -1 as a
* guard value, to allow calling set_names() and set_types() from
* sub-classes of ClutterModel. see bug:
*
* http://bugzilla.openedhand.com/show_bug.cgi?id=2032
*
* for a reference.
*/
gint n_columns;
ClutterModelFilterFunc filter_func;
gpointer filter_data;
GDestroyNotify filter_notify;
gint sort_column;
ClutterModelSortFunc sort_func;
gpointer sort_data;
GDestroyNotify sort_notify;
};
static void clutter_scriptable_iface_init (ClutterScriptableIface *iface);
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (ClutterModel, clutter_model, G_TYPE_OBJECT,
G_ADD_PRIVATE (ClutterModel)
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,
clutter_scriptable_iface_init))
static GType
clutter_model_real_get_column_type (ClutterModel *model,
guint column)
{
ClutterModelPrivate *priv = model->priv;
if (column >= clutter_model_get_n_columns (model))
return G_TYPE_INVALID;
return priv->column_types[column];
}
static const gchar *
clutter_model_real_get_column_name (ClutterModel *model,
guint column)
{
ClutterModelPrivate *priv = model->priv;
if (column >= clutter_model_get_n_columns (model))
return NULL;
if (priv->column_names && priv->column_names[column])
return priv->column_names[column];
return g_type_name (priv->column_types[column]);
}
static guint
clutter_model_real_get_n_columns (ClutterModel *model)
{
ClutterModelPrivate *priv = model->priv;
if (priv->n_columns < 0)
return 0;
return priv->n_columns;
}
static guint
clutter_model_real_get_n_rows (ClutterModel *model)
{
ClutterModelIter *iter;
guint row_count;
g_return_val_if_fail (CLUTTER_IS_MODEL (model), 0);
iter = clutter_model_get_first_iter (model);
if (iter == NULL)
return 0;
row_count = 0;
while (!clutter_model_iter_is_last (iter))
{
if (clutter_model_filter_iter (model, iter))
row_count += 1;
iter = clutter_model_iter_next (iter);
}
g_object_unref (iter);
return row_count;
}
static void
clutter_model_finalize (GObject *object)
{
ClutterModelPrivate *priv = CLUTTER_MODEL (object)->priv;
gint i;
if (priv->sort_notify)
priv->sort_notify (priv->sort_data);
if (priv->filter_notify)
priv->filter_notify (priv->filter_data);
g_free (priv->column_types);
if (priv->column_names != NULL)
{
/* the column_names vector might have holes in it, so we need
* to use the columns number to clear up everything
*/
for (i = 0; i < priv->n_columns; i++)
g_free (priv->column_names[i]);
g_free (priv->column_names);
}
G_OBJECT_CLASS (clutter_model_parent_class)->finalize (object);
}
static void
clutter_model_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ClutterModelPrivate *priv = CLUTTER_MODEL (gobject)->priv;
switch (prop_id)
{
case PROP_FILTER_SET:
g_value_set_boolean (value, priv->filter_func != NULL);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
clutter_model_class_init (ClutterModelClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
gobject_class->get_property = clutter_model_get_property;
gobject_class->finalize = clutter_model_finalize;
klass->get_column_name = clutter_model_real_get_column_name;
klass->get_column_type = clutter_model_real_get_column_type;
klass->get_n_columns = clutter_model_real_get_n_columns;
klass->get_n_rows = clutter_model_real_get_n_rows;
/**
* ClutterModel:filter-set:
*
* Whether the #ClutterModel has a filter set
*
* This property is set to %TRUE if a filter function has been
* set using clutter_model_set_filter()
*
* Since: 1.0
*/
pspec = g_param_spec_boolean ("filter-set",
"Filter Set",
"Whether the model has a filter",
FALSE,
CLUTTER_PARAM_READABLE);
g_object_class_install_property (gobject_class, PROP_FILTER_SET, pspec);
/**
* 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.
* The data on the row has already been set when the ::row-added signal
* has been emitted.
*
* 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,
_clutter_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
CLUTTER_TYPE_MODEL_ITER);
/**
* 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.
* The data on the row pointed by the passed iterator is still valid
* when the ::row-removed signal has been emitted.
*
* 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,
_clutter_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
CLUTTER_TYPE_MODEL_ITER);
/**
* 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.
* The data on the row has already been updated when the ::row-changed
* signal has been emitted.
*
* 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,
_clutter_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
CLUTTER_TYPE_MODEL_ITER);
/**
* 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,
_clutter_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,
_clutter_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
static void
clutter_model_init (ClutterModel *self)
{
ClutterModelPrivate *priv;
self->priv = priv = clutter_model_get_instance_private (self);
priv->n_columns = -1;
priv->column_types = NULL;
priv->column_names = NULL;
priv->filter_func = NULL;
priv->filter_data = NULL;
priv->filter_notify = NULL;
priv->sort_column = -1;
priv->sort_func = NULL;
priv->sort_data = NULL;
priv->sort_notify = NULL;
}
/* XXX - is this whitelist really necessary? we accept every fundamental
* type.
*/
gboolean
_clutter_model_check_type (GType gtype)
{
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 (gtype))
return FALSE;
while (type_list[i] != G_TYPE_INVALID)
{
if (g_type_is_a (gtype, type_list[i]))
return TRUE;
i++;
}
return FALSE;
}
typedef struct {
gchar *name;
GType type;
} ColumnInfo;
static gboolean
clutter_model_parse_custom_node (ClutterScriptable *scriptable,
ClutterScript *script,
GValue *value,
const gchar *name,
JsonNode *node)
{
if (strcmp (name, "columns") == 0)
{
GSList *columns = NULL;
GList *elements, *l;
if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY)
return FALSE;
elements = json_array_get_elements (json_node_get_array (node));
for (l = elements; l != NULL; l = l->next)
{
JsonNode *child_node = l->data;
JsonArray *array = json_node_get_array (child_node);
ColumnInfo *cinfo;
const gchar *column_name;
const gchar *type_name;
if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY ||
json_array_get_length (array) != 2)
{
g_warning ("A column must be an array of "
"[\"column-name\", \"GType-name\"] pairs");
return FALSE;
}
column_name = json_array_get_string_element (array, 0);
type_name = json_array_get_string_element (array, 1);
cinfo = g_slice_new0 (ColumnInfo);
cinfo->name = g_strdup (column_name);
cinfo->type = clutter_script_get_type_from_name (script, type_name);
columns = g_slist_prepend (columns, cinfo);
}
g_list_free (elements);
g_value_init (value, G_TYPE_POINTER);
g_value_set_pointer (value, g_slist_reverse (columns));
return TRUE;
}
else if (strcmp (name, "rows") == 0)
{
GSList *rows = NULL;
GList *elements, *l;
if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY)
return FALSE;
/*
* at this point we have no information about the column types, so
* we just copy the json elements and resolve them in the
* set_custom_property method
*/
elements = json_array_get_elements (json_node_get_array (node));
for (l = elements; l != NULL; l = l->next)
rows = g_slist_prepend (rows, json_node_copy (l->data));
g_list_free (elements);
g_value_init (value, G_TYPE_POINTER);
g_value_set_pointer (value, g_slist_reverse (rows));
return TRUE;
}
return FALSE;
}
static void
clutter_model_set_custom_property (ClutterScriptable *scriptable,
ClutterScript *script,
const gchar *name,
const GValue *value)
{
if (strcmp (name, "columns") == 0)
{
ClutterModel *model = CLUTTER_MODEL (scriptable);
GSList *columns, *l;
guint n_columns;
gint i;
columns = g_value_get_pointer (value);
n_columns = g_slist_length (columns);
_clutter_model_set_n_columns (model, n_columns, TRUE, TRUE);
for (i = 0, l = columns; l != NULL; l = l->next, i++)
{
ColumnInfo *cinfo = l->data;
_clutter_model_set_column_name (model, i, cinfo->name);
_clutter_model_set_column_type (model, i, cinfo->type);
g_free (cinfo->name);
g_slice_free (ColumnInfo, cinfo);
}
g_slist_free (columns);
}
else if (strcmp (name, "rows") == 0)
{
ClutterModel *model = CLUTTER_MODEL (scriptable);
GSList *rows, *l;
guint n_columns, row = 0;
rows = g_value_get_pointer (value);
n_columns = clutter_model_get_n_columns (model);
for (l = rows; l; l = l->next)
{
JsonNode *node = l->data;
guint *columns = NULL, i, n_values = 0;
GValue *values = NULL;
if (JSON_NODE_TYPE (node) == JSON_NODE_ARRAY)
{
JsonArray *array = json_node_get_array (node);
if (json_array_get_length (array) != n_columns)
{
g_warning ("Row %d contains the wrong count of columns",
g_slist_position (rows, l) + 1);
row += 1;
continue;
}
/* array more requires all columns */
n_values = n_columns;
columns = g_new (guint, n_values);
values = g_new0 (GValue, n_values);
for (i = 0; i < n_values; i++)
{
GType column_type;
const gchar *column_name;
column_type = clutter_model_get_column_type (model, i);
column_name = clutter_model_get_column_name (model, i);
columns[i] = i;
g_value_init (&values[i], column_type);
_clutter_script_parse_node (script, &values[i], column_name,
json_array_get_element (array, i),
NULL);
}
}
else if (JSON_NODE_TYPE (node) == JSON_NODE_OBJECT)
{
JsonObject *object = json_node_get_object (node);
GList *members, *m;
guint column = 0;
/* object mode does not require all columns */
n_values = json_object_get_size (object);
columns = g_new (guint, n_values);
values = g_new0 (GValue, n_values);
members = json_object_get_members (object);
for (m = members; m; m = m->next)
{
const gchar *mname = m->data;
for (i = 0; i < clutter_model_get_n_columns (model); i++)
{
const gchar *cname;
cname = clutter_model_get_column_name (model, i);
if (strcmp (mname, cname) == 0)
{
JsonNode *member;
GType col_type;
const gchar *col_name;
member = json_object_get_member (object, mname);
col_type = clutter_model_get_column_type (model, i);
col_name = clutter_model_get_column_name (model, i);
columns[column] = i;
g_value_init (&values[column], col_type);
_clutter_script_parse_node (script, &values[column],
col_name, member,
NULL);
break;
}
}
column += 1;
}
}
else
{
row += 1;
continue;
}
clutter_model_insertv (model, row, n_values, columns, values);
g_free (values);
g_free (columns);
json_node_free (node);
row += 1;
}
g_slist_free (rows);
}
}
static void
clutter_scriptable_iface_init (ClutterScriptableIface *iface)
{
iface->parse_custom_node = clutter_model_parse_custom_node;
iface->set_custom_property = clutter_model_set_custom_property;
}
/**
* clutter_model_resort:
* @model: a #ClutterModel
*
* Force a resort on the @model. This function should only be
* used by subclasses of #ClutterModel.
*
* Since: 0.6
*/
void
clutter_model_resort (ClutterModel *model)
{
ClutterModelPrivate *priv;
ClutterModelClass *klass;
g_return_if_fail (CLUTTER_IS_MODEL (model));
priv = model->priv;
klass = CLUTTER_MODEL_GET_CLASS (model);
if (klass->resort)
klass->resort (model, priv->sort_func, priv->sort_data);
}
/**
* clutter_model_filter_row:
* @model: a #ClutterModel
* @row: the row to filter
*
* Checks whether @row should be filtered or not using the
* filtering function set on @model.
*
* This function should be used only by subclasses of #ClutterModel.
*
* Return value: %TRUE if the row should be displayed,
* %FALSE otherwise
*
* Since: 0.6
*/
gboolean
clutter_model_filter_row (ClutterModel *model,
guint row)
{
ClutterModelPrivate *priv;
ClutterModelIter *iter;
gboolean res = TRUE;
g_return_val_if_fail (CLUTTER_IS_MODEL (model), TRUE);
priv = model->priv;
if (!priv->filter_func)
return TRUE;
iter = clutter_model_get_iter_at_row (model, row);
if (iter == NULL)
return FALSE;
res = priv->filter_func (model, iter, priv->filter_data);
g_object_unref (iter);
return res;
}
/**
* clutter_model_filter_iter:
* @model: a #ClutterModel
* @iter: the row to filter
*
* Checks whether the row pointer by @iter should be filtered or not using
* the filtering function set on @model.
*
* This function should be used only by subclasses of #ClutterModel.
*
* Return value: %TRUE if the row should be displayed,
* %FALSE otherwise
*
* Since: 0.6
*/
gboolean
clutter_model_filter_iter (ClutterModel *model,
ClutterModelIter *iter)
{
ClutterModelPrivate *priv;
g_return_val_if_fail (CLUTTER_IS_MODEL (model), TRUE);
g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), TRUE);
priv = model->priv;
if (!priv->filter_func)
return TRUE;
return priv->filter_func (model, iter, priv->filter_data);
}
/*< private >
* clutter_model_set_n_columns:
* @model: a #ClutterModel
* @n_columns: number of columns
* @set_types: set the columns type
* @set_names: set the columns name
*
* Sets the number of columns in @model to @n_columns. If @set_types
* or @set_names are %TRUE, initialises the columns type and name
* arrays as well.
*
* This function can only be called once.
*/
void
_clutter_model_set_n_columns (ClutterModel *model,
gint n_columns,
gboolean set_types,
gboolean set_names)
{
ClutterModelPrivate *priv = model->priv;
if (priv->n_columns > 0 && priv->n_columns != n_columns)
return;
priv->n_columns = n_columns;
if (set_types && !priv->column_types)
priv->column_types = g_new0 (GType, n_columns);
if (set_names && !priv->column_names)
priv->column_names = g_new0 (gchar*, n_columns);
}
/*< private >
* _clutter_model_set_column_type:
* @model: a #ClutterModel
* @column: column index
* @gtype: type of the column
*
* Sets the type of @column inside @model
*/
void
_clutter_model_set_column_type (ClutterModel *model,
gint column,
GType gtype)
{
ClutterModelPrivate *priv = model->priv;
priv->column_types[column] = gtype;
}
/*< private >
* _clutter_model_set_column_name:
* @model: a #ClutterModel
* @column: column index
* @name: name of the column, or %NULL
*
* Sets the name of @column inside @model
*/
void
_clutter_model_set_column_name (ClutterModel *model,
gint column,
const gchar *name)
{
ClutterModelPrivate *priv = model->priv;
priv->column_names[column] = g_strdup (name);
}
/**
* clutter_model_set_types:
* @model: a #ClutterModel
* @n_columns: number of columns for the model
* @types: (array length=n_columns): an array of #GType types
*
* Sets the types of the columns inside a #ClutterModel.
*
* 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 || priv->n_columns == n_columns);
g_return_if_fail (priv->column_types == NULL);
_clutter_model_set_n_columns (model, n_columns, TRUE, FALSE);
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]);
}
}
/**
* clutter_model_set_names:
* @model: a #ClutterModel
* @n_columns: the number of column names
* @names: (array length=n_columns): an array of strings
*
* Assigns a name to the columns of a #ClutterModel.
*
* 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_names (ClutterModel *model,
guint n_columns,
const gchar * const names[])
{
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 || priv->n_columns == n_columns);
g_return_if_fail (priv->column_names == NULL);
_clutter_model_set_n_columns (model, n_columns, FALSE, TRUE);
for (i = 0; i < n_columns; i++)
_clutter_model_set_column_name (model, i, names[i]);
}
/**
* clutter_model_get_n_columns:
* @model: a #ClutterModel
*
* Retrieves the number of columns inside @model.
*
* Return value: the number of columns
*
* Since: 0.6
*/
guint
clutter_model_get_n_columns (ClutterModel *model)
{
g_return_val_if_fail (CLUTTER_IS_MODEL (model), 0);
return CLUTTER_MODEL_GET_CLASS (model)->get_n_columns (model);
}
/**
* clutter_model_appendv:
* @model: a #ClutterModel
* @n_columns: the number of columns and values
* @columns: (array length=n_columns): a vector with the columns to set
* @values: (array length=n_columns): a vector with the values
*
* Creates and appends a new row to the #ClutterModel, setting the row
* values for the given @columns upon creation.
*
* Since: 0.6
*/
void
clutter_model_appendv (ClutterModel *model,
guint n_columns,
guint *columns,
GValue *values)
{
ClutterModelPrivate *priv;
ClutterModelIter *iter;
gint i;
gboolean resort = FALSE;
g_return_if_fail (CLUTTER_IS_MODEL (model));
g_return_if_fail (n_columns <= clutter_model_get_n_columns (model));
g_return_if_fail (columns != NULL);
g_return_if_fail (values != NULL);
priv = model->priv;
iter = CLUTTER_MODEL_GET_CLASS (model)->insert_row (model, -1);
g_assert (CLUTTER_IS_MODEL_ITER (iter));
for (i = 0; i < n_columns; i++)
{
if (priv->sort_column == columns[i])
resort = TRUE;
clutter_model_iter_set_value (iter, columns[i], &values[i]);
}
g_signal_emit (model, model_signals[ROW_ADDED], 0, iter);
if (resort)
clutter_model_resort (model);
g_object_unref (iter);
}
/* forward declaration */
static void clutter_model_iter_set_internal_valist (ClutterModelIter *iter,
va_list args);
/**
* clutter_model_append:
* @model: a #ClutterModel
* @...: pairs of column number and value, terminated with -1
*
* Creates and appends a new row to the #ClutterModel, setting the
* row 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:
*
* <informalexample><programlisting>
* ClutterModel *model;
* model = clutter_model_default_new (2,
* G_TYPE_INT, "Score",
* G_TYPE_STRING, "Team");
* clutter_model_append (model, 0, 42, 1, "Team #1", -1);
* </programlisting></informalexample>
*
* Since: 0.6
*/
void
clutter_model_append (ClutterModel *model,
...)
{
ClutterModelIter *iter;
va_list args;
g_return_if_fail (CLUTTER_IS_MODEL (model));
iter = CLUTTER_MODEL_GET_CLASS (model)->insert_row (model, -1);
g_assert (CLUTTER_IS_MODEL_ITER (iter));
/* do not emit the ::row-changed signal */
va_start (args, model);
clutter_model_iter_set_internal_valist (iter, args);
va_end (args);
g_signal_emit (model, model_signals[ROW_ADDED], 0, iter);
g_object_unref (iter);
}
/**
* clutter_model_prependv:
* @model: a #ClutterModel
* @n_columns: the number of columns and values to set
* @columns: (array length=n_columns): a vector containing the columns to set
* @values: (array length=n_columns): a vector containing the values for the cells
*
* Creates and prepends a new row to the #ClutterModel, setting the row
* values for the given @columns upon creation.
*
* Since: 0.6
*/
void
clutter_model_prependv (ClutterModel *model,
guint n_columns,
guint *columns,
GValue *values)
{
ClutterModelPrivate *priv;
ClutterModelIter *iter;
gint i;
gboolean resort = FALSE;
g_return_if_fail (CLUTTER_IS_MODEL (model));
g_return_if_fail (n_columns <= clutter_model_get_n_columns (model));
g_return_if_fail (columns != NULL);
g_return_if_fail (values != NULL);
priv = model->priv;
iter = CLUTTER_MODEL_GET_CLASS (model)->insert_row (model, 0);
g_assert (CLUTTER_IS_MODEL_ITER (iter));
for (i = 0; i < n_columns; i++)
{
if (priv->sort_column == columns[i])
resort = TRUE;
clutter_model_iter_set_value (iter, columns[i], &values[i]);
}
g_signal_emit (model, model_signals[ROW_ADDED], 0, iter);
if (resort)
clutter_model_resort (model);
g_object_unref (iter);
}
/**
* clutter_model_prepend:
* @model: a #ClutterModel
* @...: pairs of column number and value, terminated with -1
*
* Creates and prepends a new row to the #ClutterModel, setting the row
* 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:
*
* <informalexample><programlisting>
* ClutterModel *model;
* model = clutter_model_default_new (2,
* G_TYPE_INT, "Score",
* G_TYPE_STRING, "Team");
* clutter_model_prepend (model, 0, 42, 1, "Team #1", -1);
* </programlisting></informalexample>
*
* Since: 0.6
*/
void
clutter_model_prepend (ClutterModel *model,
...)
{
ClutterModelIter *iter;
va_list args;
g_return_if_fail (CLUTTER_IS_MODEL (model));
iter = CLUTTER_MODEL_GET_CLASS (model)->insert_row (model, 0);
g_assert (CLUTTER_IS_MODEL_ITER (iter));
va_start (args, model);
clutter_model_iter_set_internal_valist (iter, args);
va_end (args);
g_signal_emit (model, model_signals[ROW_ADDED], 0, iter);
g_object_unref (iter);
}
/**
* clutter_model_insert:
* @model: a #ClutterModel
* @row: the position to insert the new row
* @...: pairs of column number and value, terminated with -1
*
* Inserts a new row to the #ClutterModel at @row, setting the row
* 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:
*
* <informalexample><programlisting>
* ClutterModel *model;
* model = clutter_model_default_new (2,
* G_TYPE_INT, "Score",
* G_TYPE_STRING, "Team");
* clutter_model_insert (model, 3, 0, 42, 1, "Team #1", -1);
* </programlisting></informalexample>
*
* Since: 0.6
*/
void
clutter_model_insert (ClutterModel *model,
guint row,
...)
{
ClutterModelIter *iter;
va_list args;
g_return_if_fail (CLUTTER_IS_MODEL (model));
iter = CLUTTER_MODEL_GET_CLASS (model)->insert_row (model, row);
g_assert (CLUTTER_IS_MODEL_ITER (iter));
/* set_valist() will call clutter_model_resort() if one of the
* passed columns matches the model sorting column index
*/
va_start (args, row);
clutter_model_iter_set_internal_valist (iter, args);
va_end (args);
g_signal_emit (model, model_signals[ROW_ADDED], 0, iter);
g_object_unref (iter);
}
/**
* clutter_model_insertv:
* @model: a #ClutterModel
* @row: row index
* @n_columns: the number of columns and values to set
* @columns: (array length=n_columns): a vector containing the columns to set
* @values: (array length=n_columns): a vector containing the values for the cells
*
* Inserts data at @row into the #ClutterModel, setting the row
* values for the given @columns upon creation.
*
* Since: 0.6
*/
void
clutter_model_insertv (ClutterModel *model,
guint row,
guint n_columns,
guint *columns,
GValue *values)
{
ClutterModelPrivate *priv;
ClutterModelIter *iter;
gint i;
gboolean resort = FALSE;
g_return_if_fail (CLUTTER_IS_MODEL (model));
g_return_if_fail (n_columns <= clutter_model_get_n_columns (model));
g_return_if_fail (columns != NULL);
g_return_if_fail (values != NULL);
priv = model->priv;
iter = CLUTTER_MODEL_GET_CLASS (model)->insert_row (model, row);
g_assert (CLUTTER_IS_MODEL_ITER (iter));
for (i = 0; i < n_columns; i++)
{
if (priv->sort_column == columns[i])
resort = TRUE;
clutter_model_iter_set_value (iter, columns[i], &values[i]);
}
g_signal_emit (model, model_signals[ROW_ADDED], 0, iter);
if (resort)
clutter_model_resort (model);
g_object_unref (iter);
}
/**
* clutter_model_insert_value:
* @model: a #ClutterModel
* @row: 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. If the row does
* not exist then it is created.
*
* Since: 0.6
*/
void
clutter_model_insert_value (ClutterModel *model,
guint row,
guint column,
const GValue *value)
{
ClutterModelPrivate *priv;
ClutterModelClass *klass;
ClutterModelIter *iter;
gboolean added = FALSE;
g_return_if_fail (CLUTTER_IS_MODEL (model));
priv = model->priv;
klass = CLUTTER_MODEL_GET_CLASS (model);
iter = klass->get_iter_at_row (model, row);
if (!iter)
{
iter = klass->insert_row (model, row);
added = TRUE;
}
g_assert (CLUTTER_IS_MODEL_ITER (iter));
clutter_model_iter_set_value (iter, column, value);
if (added)
g_signal_emit (model, model_signals[ROW_ADDED], 0, iter);
if (priv->sort_column == column)
clutter_model_resort (model);
g_object_unref (iter);
}
/**
* clutter_model_remove:
* @model: a #ClutterModel
* @row: position of row to remove
*
* Removes the row at the given position from the model.
*
* Since: 0.6
*/
void
clutter_model_remove (ClutterModel *model,
guint row)
{
ClutterModelClass *klass;
g_return_if_fail (CLUTTER_IS_MODEL (model));
klass = CLUTTER_MODEL_GET_CLASS (model);
if (klass->remove_row)
klass->remove_row (model, row);
}
/**
* clutter_model_get_column_name:
* @model: #ClutterModel
* @column: the column number
*
* Retrieves the name of the @column
*
* Return value: the name of the column. The model holds the returned
* string, and it should not be modified or freed
*
* Since: 0.6
*/
const gchar *
clutter_model_get_column_name (ClutterModel *model,
guint column)
{
ClutterModelClass *klass;
g_return_val_if_fail (CLUTTER_IS_MODEL (model), NULL);
if (column >= clutter_model_get_n_columns (model))
{
g_warning ("%s: Invalid column id value %d\n", G_STRLOC, column);
return NULL;
}
klass = CLUTTER_MODEL_GET_CLASS (model);
if (klass->get_column_name)
return klass->get_column_name (model, column);
return NULL;
}
/**
* clutter_model_get_column_type:
* @model: #ClutterModel
* @column: the column number
*
* Retrieves the type of the @column.
*
* Return value: the type of the column.
*
* Since: 0.6
*/
GType
clutter_model_get_column_type (ClutterModel *model,
guint column)
{
ClutterModelClass *klass;
g_return_val_if_fail (CLUTTER_IS_MODEL (model), G_TYPE_INVALID);
if (column >= clutter_model_get_n_columns (model))
{
g_warning ("%s: Invalid column id value %d\n", G_STRLOC, column);
return G_TYPE_INVALID;
}
klass = CLUTTER_MODEL_GET_CLASS (model);
if (klass->get_column_type)
return klass->get_column_type (model, column);
return G_TYPE_INVALID;
}
/**
* clutter_model_get_iter_at_row:
* @model: a #ClutterModel
* @row: position of the row to retrieve
*
* Retrieves a #ClutterModelIter representing the row at the given index.
*
* If a filter function has been set using clutter_model_set_filter()
* then the @model implementation will return the first non filtered
* row.
*
* Return value: (transfer full): A new #ClutterModelIter, or %NULL if @row was
* out of bounds. When done using the iterator object, call g_object_unref()
* to deallocate its resources
*
* Since: 0.6
*/
ClutterModelIter *
clutter_model_get_iter_at_row (ClutterModel *model,
guint row)
{
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, row);
return NULL;
}
/**
* clutter_model_get_first_iter:
* @model: a #ClutterModel
*
* Retrieves a #ClutterModelIter representing the first non-filtered
* row in @model.
*
* Return value: (transfer full): A new #ClutterModelIter.
* Call g_object_unref() when done using it
*
* Since: 0.6
*/
ClutterModelIter *
clutter_model_get_first_iter (ClutterModel *model)
{
ClutterModelIter *retval;
g_return_val_if_fail (CLUTTER_IS_MODEL (model), NULL);
retval = clutter_model_get_iter_at_row (model, 0);
if (retval != NULL)
{
g_assert (clutter_model_filter_iter (model, retval) != FALSE);
g_assert (clutter_model_iter_get_row (retval) == 0);
}
return retval;
}
/**
* clutter_model_get_last_iter:
* @model: a #ClutterModel
*
* Retrieves a #ClutterModelIter representing the last non-filtered
* row in @model.
*
* Return value: (transfer full): A new #ClutterModelIter.
* Call g_object_unref() when done using it
*
* Since: 0.6
*/
ClutterModelIter *
clutter_model_get_last_iter (ClutterModel *model)
{
ClutterModelIter *retval;
guint length;
g_return_val_if_fail (CLUTTER_IS_MODEL (model), NULL);
length = clutter_model_get_n_rows (model);
retval = clutter_model_get_iter_at_row (model, length - 1);
if (retval != NULL)
g_assert (clutter_model_filter_iter (model, retval) != FALSE);
return retval;
}
/**
* clutter_model_get_n_rows:
* @model: a #ClutterModel
*
* Retrieves the number of rows inside @model, eventually taking
* into account any filtering function set using clutter_model_set_filter().
*
* 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)
{
g_return_val_if_fail (CLUTTER_IS_MODEL (model), 0);
return CLUTTER_MODEL_GET_CLASS (model)->get_n_rows (model);
}
/**
* clutter_model_set_sorting_column:
* @model: a #ClutterModel
* @column: the column of the @model to sort, or -1
*
* Sets the model to sort by @column. If @column is a negative value
* the sorting column will be unset.
*
* Since: 0.6
*/
void
clutter_model_set_sorting_column (ClutterModel *model,
gint column)
{
ClutterModelPrivate *priv;
g_return_if_fail (CLUTTER_IS_MODEL (model));
priv = model->priv;
/* The extra comparison for >= 0 is because column gets promoted to
unsigned in the second comparison */
if (column >= 0 && column >= clutter_model_get_n_columns (model))
{
g_warning ("%s: Invalid column id value %d\n", G_STRLOC, column);
return;
}
priv->sort_column = column;
if (priv->sort_column >= 0)
clutter_model_resort (model);
g_signal_emit (model, model_signals[SORT_CHANGED], 0);
}
/**
* clutter_model_get_sorting_column:
* @model: a #ClutterModel
*
* Retrieves the number of column used for sorting the @model.
*
* Return value: a column number, or -1 if the model is not sorted
*
* Since: 0.6
*/
gint
clutter_model_get_sorting_column (ClutterModel *model)
{
g_return_val_if_fail (CLUTTER_IS_MODEL (model), -1);
return model->priv->sort_column;
}
/**
* clutter_model_foreach:
* @model: a #ClutterModel
* @func: (scope call): 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)
{
ClutterModelIter *iter;
g_return_if_fail (CLUTTER_IS_MODEL (model));
iter = clutter_model_get_first_iter (model);
if (!iter)
return;
while (!clutter_model_iter_is_last (iter))
{
if (clutter_model_filter_iter (model, iter))
{
if (!func (model, iter, user_data))
break;
}
iter = clutter_model_iter_next (iter);
}
g_object_unref (iter);
}
/**
* clutter_model_set_sort:
* @model: a #ClutterModel
* @column: the column to sort on
* @func: (allow-none): a #ClutterModelSortFunc, or #NULL
* @user_data: user data to pass to @func, or #NULL
* @notify: destroy notifier of @user_data, or #NULL
*
* Sorts @model using the given sorting function.
*
* Since: 0.6
*/
void
clutter_model_set_sort (ClutterModel *model,
gint column,
ClutterModelSortFunc func,
gpointer user_data,
GDestroyNotify notify)
{
ClutterModelPrivate *priv;
g_return_if_fail (CLUTTER_IS_MODEL (model));
g_return_if_fail ((func != NULL && column >= 0) ||
(func == NULL && column == -1));
priv = model->priv;
if (priv->sort_notify)
priv->sort_notify (priv->sort_data);
priv->sort_func = 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: (allow-none): 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 the given filtering function.
*
* 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_notify)
priv->filter_notify (priv->filter_data);
priv->filter_func = func;
priv->filter_data = user_data;
priv->filter_notify = notify;
g_signal_emit (model, model_signals[FILTER_CHANGED], 0);
g_object_notify (G_OBJECT (model), "filter-set");
}
/**
* clutter_model_get_filter_set:
* @model: a #ClutterModel
*
* Returns whether the @model has a filter in place, set
* using clutter_model_set_filter()
*
* Return value: %TRUE if a filter is set
*
* Since: 1.0
*/
gboolean
clutter_model_get_filter_set (ClutterModel *model)
{
g_return_val_if_fail (CLUTTER_IS_MODEL (model), FALSE);
return model->priv->filter_func != NULL;
}
/*
* ClutterModelIter Object
*/
/**
* SECTION:clutter-model-iter
* @short_description: Iterates through a model
*
* #ClutterModelIter is an object used for iterating through all the rows
* of a #ClutterModel. It allows setting and getting values on the row
* which is currently pointing at.
*
* A #ClutterModelIter represents a position between two elements
* of the sequence. For example, the iterator returned by
* clutter_model_get_first_iter() represents the gap immediately before
* the first row of the #ClutterModel, and the iterator returned by
* clutter_model_get_last_iter() represents the gap immediately after the
* last row.
*
* A #ClutterModelIter can only be created by a #ClutterModel implementation
* and it is valid as long as the model does not change.
*
* #ClutterModelIter is available since Clutter 0.6
*/
struct _ClutterModelIterPrivate
{
ClutterModel *model;
gint row;
};
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (ClutterModelIter, clutter_model_iter, G_TYPE_OBJECT)
enum
{
ITER_PROP_0,
ITER_PROP_MODEL,
ITER_PROP_ROW
};
static ClutterModel *
clutter_model_iter_real_get_model (ClutterModelIter *iter)
{
return iter->priv->model;
}
static guint
clutter_model_iter_real_get_row (ClutterModelIter *iter)
{
return iter->priv->row;
}
/* private function */
void
_clutter_model_iter_set_row (ClutterModelIter *iter,
guint row)
{
iter->priv->row = row;
}
static void
clutter_model_iter_get_value_unimplemented (ClutterModelIter *iter,
guint column,
GValue *value)
{
g_warning ("%s: Iterator of type '%s' does not implement the "
"ClutterModelIter::get_value() virtual function",
G_STRLOC,
g_type_name (G_OBJECT_TYPE (iter)));
}
static void
clutter_model_iter_set_value_unimplemented (ClutterModelIter *iter,
guint column,
const GValue *value)
{
g_warning ("%s: Iterator of type '%s' does not implement the "
"ClutterModelIter::set_value() virtual function",
G_STRLOC,
g_type_name (G_OBJECT_TYPE (iter)));
}
static gboolean
clutter_model_iter_is_first_unimplemented (ClutterModelIter *iter)
{
g_warning ("%s: Iterator of type '%s' does not implement the "
"ClutterModelIter::is_first() virtual function",
G_STRLOC,
g_type_name (G_OBJECT_TYPE (iter)));
return FALSE;
}
static gboolean
clutter_model_iter_is_last_unimplemented (ClutterModelIter *iter)
{
g_warning ("%s: Iterator of type '%s' does not implement the "
"ClutterModelIter::is_last() virtual function",
G_STRLOC,
g_type_name (G_OBJECT_TYPE (iter)));
return FALSE;
}
static ClutterModelIter *
clutter_model_iter_next_unimplemented (ClutterModelIter *iter)
{
g_warning ("%s: Iterator of type '%s' does not implement the "
"ClutterModelIter::next() virtual function",
G_STRLOC,
g_type_name (G_OBJECT_TYPE (iter)));
return NULL;
}
static ClutterModelIter *
clutter_model_iter_prev_unimplemented (ClutterModelIter *iter)
{
g_warning ("%s: Iterator of type '%s' does not implement the "
"ClutterModelIter::prev() virtual function",
G_STRLOC,
g_type_name (G_OBJECT_TYPE (iter)));
return NULL;
}
static ClutterModelIter *
clutter_model_iter_copy_unimplemented (ClutterModelIter *iter)
{
g_warning ("%s: Iterator of type '%s' does not implement the "
"ClutterModelIter::copy() virtual function",
G_STRLOC,
g_type_name (G_OBJECT_TYPE (iter)));
return NULL;
}
static void
clutter_model_iter_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ClutterModelIter *iter = CLUTTER_MODEL_ITER (object);
ClutterModelIterPrivate *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, priv->row);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
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 = iter->priv;
switch (prop_id)
{
case ITER_PROP_MODEL:
priv->model = g_value_get_object (value);
break;
case ITER_PROP_ROW:
priv->row = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clutter_model_iter_class_init (ClutterModelIterClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
gobject_class->get_property = clutter_model_iter_get_property;
gobject_class->set_property = clutter_model_iter_set_property;
klass->get_model = clutter_model_iter_real_get_model;
klass->get_row = clutter_model_iter_real_get_row;
klass->is_first = clutter_model_iter_is_first_unimplemented;
klass->is_last = clutter_model_iter_is_last_unimplemented;
klass->next = clutter_model_iter_next_unimplemented;
klass->prev = clutter_model_iter_prev_unimplemented;
klass->get_value = clutter_model_iter_get_value_unimplemented;
klass->set_value = clutter_model_iter_set_value_unimplemented;
klass->copy = clutter_model_iter_copy_unimplemented;
/* Properties */
/**
* ClutterModelIter:model:
*
* A reference to the #ClutterModel that this iter belongs to.
*
* Since: 0.6
*/
pspec = g_param_spec_object ("model",
"Model",
"The model to which the iterator belongs to",
CLUTTER_TYPE_MODEL,
CLUTTER_PARAM_READWRITE);
g_object_class_install_property (gobject_class, ITER_PROP_MODEL, pspec);
/**
* ClutterModelIter:row:
*
* The row number to which this iter points to.
*
* Since: 0.6
*/
pspec = g_param_spec_uint ("row",
"Row",
"The row to which the iterator points to",
0, G_MAXUINT, 0,
CLUTTER_PARAM_READWRITE);
g_object_class_install_property (gobject_class, ITER_PROP_ROW, pspec);
}
static void
clutter_model_iter_init (ClutterModelIter *self)
{
self->priv = clutter_model_iter_get_instance_private (self);
}
/*
* Public functions
*/
static inline void
clutter_model_iter_set_value_internal (ClutterModelIter *iter,
guint column,
const GValue *value)
{
CLUTTER_MODEL_ITER_GET_CLASS (iter)->set_value (iter, column, value);
}
static void
clutter_model_iter_set_internal_valist (ClutterModelIter *iter,
va_list args)
{
ClutterModelIterPrivate *priv = iter->priv;
ClutterModel *model = priv->model;
guint column = 0;
gboolean sort = FALSE;
g_assert (CLUTTER_IS_MODEL (model));
column = va_arg (args, gint);
while (column != -1)
{
GValue value = G_VALUE_INIT;
gchar *error = NULL;
GType col_type;
if (column >= clutter_model_get_n_columns (model))
{
g_warning ("%s: Invalid column number %d added to iter "
"(remember to end you list of columns with a -1)",
G_STRLOC, column);
break;
}
col_type = clutter_model_get_column_type (model, column);
G_VALUE_COLLECT_INIT (&value, col_type, 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_internal (iter, column, &value);
g_value_unset (&value);
if (column == clutter_model_get_sorting_column (model))
sort = TRUE;
column = va_arg (args, gint);
}
if (sort)
clutter_model_resort (model);
}
static void inline
clutter_model_iter_emit_row_changed (ClutterModelIter *iter)
{
ClutterModelIterPrivate *priv = iter->priv;
ClutterModel *model = priv->model;
g_assert (CLUTTER_IS_MODEL (model));
g_signal_emit (model, model_signals[ROW_CHANGED], 0, iter);
}
/**
* 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)
{
g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter));
clutter_model_iter_set_internal_valist (iter, args);
clutter_model_iter_emit_row_changed (iter);
}
/**
* clutter_model_iter_get:
* @iter: a #ClutterModelIter
* @...: a list of column/return location pairs, terminated 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 use:
* <informalexample><programlisting>
* clutter_model_iter_get (iter, 0, &place_string_here, -1);
* </programlisting></informalexample>
*
* 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);
}
static inline void
clutter_model_iter_get_value_internal (ClutterModelIter *iter,
guint column,
GValue *value)
{
CLUTTER_MODEL_ITER_GET_CLASS (iter)->get_value (iter, column, value);
}
/**
* clutter_model_iter_get_value:
* @iter: a #ClutterModelIter
* @column: column number to retrieve the value from
* @value: (out): 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)
{
ClutterModel *model;
g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter));
model = iter->priv->model;
if (G_VALUE_TYPE (value) == G_TYPE_INVALID)
g_value_init (value, clutter_model_get_column_type (model, column));
CLUTTER_MODEL_ITER_GET_CLASS (iter)->get_value (iter, column, value);
}
/**
* clutter_model_iter_get_valist:
* @iter: a #ClutterModelIter
* @args: a list of column/return location pairs, terminated 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)
{
ClutterModelIterPrivate *priv;
ClutterModel *model;
guint column = 0;
g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter));
priv = iter->priv;
model = priv->model;
g_assert (CLUTTER_IS_MODEL (model));
column = va_arg (args, gint);
while (column != -1)
{
GValue value = G_VALUE_INIT;
gchar *error = NULL;
GType col_type;
if (column >= clutter_model_get_n_columns (model))
{
g_warning ("%s: Invalid column number %d added to iter "
"(remember to end you list of columns with a -1)",
G_STRLOC, column);
break;
}
col_type = clutter_model_get_column_type (model, column);
g_value_init (&value, col_type);
clutter_model_iter_get_value_internal (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
* @...: a list of column/return location pairs, terminated 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, use:
* <informalexample><programlisting>
* clutter_model_iter_set (iter, 0, "foo", -1);
* </programlisting></informalexample>
*
* 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_internal_valist (iter, args);
clutter_model_iter_emit_row_changed (iter);
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)
{
g_return_if_fail (CLUTTER_IS_MODEL_ITER (iter));
clutter_model_iter_set_value_internal (iter, column, value);
clutter_model_iter_emit_row_changed (iter);
}
/**
* clutter_model_iter_is_first:
* @iter: a #ClutterModelIter
*
* Gets whether the current iterator is at the beginning of the model
* to which it belongs.
*
* Return value: #TRUE if @iter is the first iter in the filtered model
*
* Since: 0.6
*/
gboolean
clutter_model_iter_is_first (ClutterModelIter *iter)
{
g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), FALSE);
return CLUTTER_MODEL_ITER_GET_CLASS (iter)->is_first (iter);
}
/**
* clutter_model_iter_is_last:
* @iter: a #ClutterModelIter
*
* Gets whether the iterator is at the end of the model to which it
* belongs.
*
* Return value: #TRUE if @iter is the last iter in the filtered model.
*
* Since: 0.6
*/
gboolean
clutter_model_iter_is_last (ClutterModelIter *iter)
{
g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), FALSE);
return CLUTTER_MODEL_ITER_GET_CLASS (iter)->is_last (iter);
}
/**
* clutter_model_iter_next:
* @iter: a #ClutterModelIter
*
* Updates the @iter to point at the next position in the model.
* The model implementation should take into account the presence of
* a filter function.
*
* Return value: (transfer none): The passed iterator, updated to point at the next
* row in the model.
*
* Since: 0.6
*/
ClutterModelIter *
clutter_model_iter_next (ClutterModelIter *iter)
{
g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), NULL);
return CLUTTER_MODEL_ITER_GET_CLASS (iter)->next (iter);
}
/**
* clutter_model_iter_prev:
* @iter: a #ClutterModelIter
*
* Sets the @iter to point at the previous position in the model.
* The model implementation should take into account the presence of
* a filter function.
*
* Return value: (transfer none): The passed iterator, updated to point at the previous
* row in the model.
*
* Since: 0.6
*/
ClutterModelIter *
clutter_model_iter_prev (ClutterModelIter *iter)
{
g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), NULL);
return CLUTTER_MODEL_ITER_GET_CLASS (iter)->prev (iter);
}
/**
* clutter_model_iter_get_model:
* @iter: a #ClutterModelIter
*
* Retrieves a pointer to the #ClutterModel that this iter is part of.
*
* Return value: (transfer none): a pointer to a #ClutterModel.
*
* Since: 0.6
*/
ClutterModel *
clutter_model_iter_get_model (ClutterModelIter *iter)
{
g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), NULL);
return CLUTTER_MODEL_ITER_GET_CLASS (iter)->get_model (iter);
}
/**
* clutter_model_iter_get_row:
* @iter: a #ClutterModelIter
*
* Retrieves 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)
{
g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), 0);
return CLUTTER_MODEL_ITER_GET_CLASS (iter)->get_row (iter);
}
/**
* clutter_model_iter_copy:
* @iter: a #ClutterModelIter
*
* Copies the passed iterator.
*
* Return value: (transfer full): a copy of the iterator, or %NULL
*
* Since: 0.8
*/
ClutterModelIter *
clutter_model_iter_copy (ClutterModelIter *iter)
{
g_return_val_if_fail (CLUTTER_IS_MODEL_ITER (iter), NULL);
return CLUTTER_MODEL_ITER_GET_CLASS (iter)->copy (iter);
}