mutter/clutter/clutter-list-model.c
Emmanuele Bassi 79acb088e7 Remove mentions of the FSF address
Since using addresses that might change is something that finally
the FSF acknowledge as a plausible scenario (after changing address
twice), the license blurb in the source files should use the URI
for getting the license in case the library did not come with it.

Not that URIs cannot possibly change, but at least it's easier to
set up a redirection at the same place.

As a side note: this commit closes the oldes bug in Clutter's bug
report tool.

http://bugzilla.openedhand.com/show_bug.cgi?id=521
2010-03-01 12:56:10 +00:00

834 lines
23 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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/>.
*
*
*/
/**
* SECTION:clutter-list-model
* @short_description: List model implementation
*
* #ClutterListModel is a #ClutterModel implementation provided by
* Clutter. #ClutterListModel uses a #GSequence for storing the
* values for each row, so it's optimized for insertion and look up
* in sorted lists.
*
* #ClutterListModel is available since Clutter 0.6
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <glib-object.h>
#include "clutter-model.h"
#include "clutter-model-private.h"
#include "clutter-list-model.h"
#include "clutter-private.h"
#include "clutter-debug.h"
#define CLUTTER_TYPE_LIST_MODEL_ITER \
(clutter_list_model_iter_get_type())
#define CLUTTER_LIST_MODEL_ITER(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj), \
CLUTTER_TYPE_LIST_MODEL_ITER, \
ClutterListModelIter))
#define CLUTTER_IS_LIST_MODEL_ITER(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj), \
CLUTTER_TYPE_LIST_MODEL_ITER))
#define CLUTTER_LIST_MODEL_ITER_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST ((klass), \
CLUTTER_TYPE_LIST_MODEL_ITER, \
ClutterListModelIterClass))
#define CLUTTER_IS_LIST_MODEL_ITER_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE ((klass), \
CLUTTER_TYPE_LIST_MODEL_ITER))
#define CLUTTER_LIST_MODEL_ITER_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj), \
CLUTTER_TYPE_LIST_MODEL_ITER, \
ClutterListModelIterClass))
typedef struct _ClutterListModelIter ClutterListModelIter;
typedef struct _ClutterModelIterClass ClutterListModelIterClass;
#define CLUTTER_LIST_MODEL_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_LIST_MODEL, ClutterListModelPrivate))
struct _ClutterListModelPrivate
{
GSequence *sequence;
ClutterModelIter *temp_iter;
};
struct _ClutterListModelIter
{
ClutterModelIter parent_instance;
GSequenceIter *seq_iter;
};
/*
* ClutterListModel
*/
G_DEFINE_TYPE (ClutterListModelIter,
clutter_list_model_iter,
CLUTTER_TYPE_MODEL_ITER);
static void
clutter_list_model_iter_get_value (ClutterModelIter *iter,
guint column,
GValue *value)
{
ClutterListModelIter *iter_default;
GValueArray *value_array;
GValue *iter_value;
GValue real_value = { 0, };
gboolean converted = FALSE;
iter_default = CLUTTER_LIST_MODEL_ITER (iter);
g_assert (iter_default->seq_iter != NULL);
value_array = g_sequence_get (iter_default->seq_iter);
iter_value = g_value_array_get_nth (value_array, column);
g_assert (iter_value != NULL);
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",
G_STRLOC,
g_type_name (G_VALUE_TYPE (value)),
g_type_name (G_VALUE_TYPE (iter_value)));
return;
}
if (!g_value_transform (iter_value, &real_value))
{
g_warning ("%s: Unable to make conversion from %s to %s",
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);
g_value_unset (&real_value);
}
else
g_value_copy (iter_value, value);
}
static void
clutter_list_model_iter_set_value (ClutterModelIter *iter,
guint column,
const GValue *value)
{
ClutterListModelIter *iter_default;
GValueArray *value_array;
GValue *iter_value;
GValue real_value = { 0, };
gboolean converted = FALSE;
iter_default = CLUTTER_LIST_MODEL_ITER (iter);
g_assert (iter_default->seq_iter != NULL);
value_array = g_sequence_get (iter_default->seq_iter);
iter_value = g_value_array_get_nth (value_array, column);
g_assert (iter_value != NULL);
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);
}
static gboolean
clutter_list_model_iter_is_first (ClutterModelIter *iter)
{
ClutterListModelIter *iter_default;
ClutterModel *model;
ClutterModelIter *temp_iter;
GSequence *sequence;
GSequenceIter *begin, *end;
iter_default = CLUTTER_LIST_MODEL_ITER (iter);
g_assert (iter_default->seq_iter != NULL);
model = clutter_model_iter_get_model (iter);
sequence = CLUTTER_LIST_MODEL (model)->priv->sequence;
begin = g_sequence_get_begin_iter (sequence);
end = iter_default->seq_iter;
temp_iter = CLUTTER_LIST_MODEL (model)->priv->temp_iter;
while (!g_sequence_iter_is_begin (begin))
{
CLUTTER_LIST_MODEL_ITER (temp_iter)->seq_iter = begin;
if (clutter_model_filter_iter (model, temp_iter))
{
end = begin;
break;
}
begin = g_sequence_iter_next (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);
return iter_default->seq_iter == end;
}
static gboolean
clutter_list_model_iter_is_last (ClutterModelIter *iter)
{
ClutterListModelIter *iter_default;
ClutterModelIter *temp_iter;
ClutterModel *model;
GSequence *sequence;
GSequenceIter *begin, *end;
iter_default = CLUTTER_LIST_MODEL_ITER (iter);
g_assert (iter_default->seq_iter != NULL);
if (g_sequence_iter_is_end (iter_default->seq_iter))
return TRUE;
model = clutter_model_iter_get_model (iter);
sequence = CLUTTER_LIST_MODEL (model)->priv->sequence;
begin = g_sequence_get_end_iter (sequence);
begin = g_sequence_iter_prev (begin);
end = iter_default->seq_iter;
temp_iter = CLUTTER_LIST_MODEL (model)->priv->temp_iter;
while (!g_sequence_iter_is_begin (begin))
{
CLUTTER_LIST_MODEL_ITER (temp_iter)->seq_iter = begin;
if (clutter_model_filter_iter (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);
return iter_default->seq_iter == end;
}
static ClutterModelIter *
clutter_list_model_iter_next (ClutterModelIter *iter)
{
ClutterListModelIter *iter_default;
ClutterModelIter *temp_iter;
ClutterModel *model = NULL;
GSequenceIter *filter_next;
guint row;
iter_default = CLUTTER_LIST_MODEL_ITER (iter);
g_assert (iter_default->seq_iter != NULL);
model = clutter_model_iter_get_model (iter);
row = clutter_model_iter_get_row (iter);
filter_next = g_sequence_iter_next (iter_default->seq_iter);
g_assert (filter_next != NULL);
temp_iter = CLUTTER_LIST_MODEL (model)->priv->temp_iter;
while (!g_sequence_iter_is_end (filter_next))
{
CLUTTER_LIST_MODEL_ITER (temp_iter)->seq_iter = filter_next;
if (clutter_model_filter_iter (model, temp_iter))
{
row += 1;
break;
}
filter_next = g_sequence_iter_next (filter_next);
}
if (g_sequence_iter_is_end (filter_next))
row += 1;
/* update the iterator and return it */
clutter_model_iter_set_row (CLUTTER_MODEL_ITER (iter_default), row);
iter_default->seq_iter = filter_next;
return CLUTTER_MODEL_ITER (iter_default);
}
static ClutterModelIter *
clutter_list_model_iter_prev (ClutterModelIter *iter)
{
ClutterListModelIter *iter_default;
ClutterModelIter *temp_iter;
ClutterModel *model;
GSequenceIter *filter_prev;
guint row;
iter_default = CLUTTER_LIST_MODEL_ITER (iter);
g_assert (iter_default->seq_iter != NULL);
model = clutter_model_iter_get_model (iter);
row = clutter_model_iter_get_row (iter);
filter_prev = g_sequence_iter_prev (iter_default->seq_iter);
g_assert (filter_prev != NULL);
temp_iter = CLUTTER_LIST_MODEL (model)->priv->temp_iter;
while (!g_sequence_iter_is_begin (filter_prev))
{
CLUTTER_LIST_MODEL_ITER (temp_iter)->seq_iter = filter_prev;
if (clutter_model_filter_iter (model, temp_iter))
{
row -= 1;
break;
}
filter_prev = g_sequence_iter_prev (filter_prev);
}
if (g_sequence_iter_is_begin (filter_prev))
row -= 1;
/* update the iterator and return it */
clutter_model_iter_set_row (CLUTTER_MODEL_ITER (iter_default), row);
iter_default->seq_iter = filter_prev;
return CLUTTER_MODEL_ITER (iter_default);
}
static ClutterModelIter *
clutter_list_model_iter_copy (ClutterModelIter *iter)
{
ClutterListModelIter *iter_default;
ClutterListModelIter *iter_copy;
ClutterModel *model;
guint row;
iter_default = CLUTTER_LIST_MODEL_ITER (iter);
model = clutter_model_iter_get_model (iter);
row = clutter_model_iter_get_row (iter) - 1;
iter_copy = g_object_new (CLUTTER_TYPE_LIST_MODEL_ITER,
"model", model,
"row", row,
NULL);
/* this is safe, because the seq_iter pointer on the passed
* iterator will be always be overwritten in ::next or ::prev
*/
iter_copy->seq_iter = iter_default->seq_iter;
return CLUTTER_MODEL_ITER (iter_copy);
}
static void
clutter_list_model_iter_class_init (ClutterListModelIterClass *klass)
{
ClutterModelIterClass *iter_class = CLUTTER_MODEL_ITER_CLASS (klass);
iter_class->get_value = clutter_list_model_iter_get_value;
iter_class->set_value = clutter_list_model_iter_set_value;
iter_class->is_first = clutter_list_model_iter_is_first;
iter_class->is_last = clutter_list_model_iter_is_last;
iter_class->next = clutter_list_model_iter_next;
iter_class->prev = clutter_list_model_iter_prev;
iter_class->copy = clutter_list_model_iter_copy;
}
static void
clutter_list_model_iter_init (ClutterListModelIter *iter)
{
iter->seq_iter = NULL;
}
/*
* ClutterListModel
*/
G_DEFINE_TYPE (ClutterListModel, clutter_list_model, CLUTTER_TYPE_MODEL);
static ClutterModelIter *
clutter_list_model_get_iter_at_row (ClutterModel *model,
guint row)
{
ClutterListModel *model_default = CLUTTER_LIST_MODEL (model);
GSequence *sequence = model_default->priv->sequence;
gint seq_length = g_sequence_get_length (sequence);
ClutterListModelIter *retval;
if (row >= seq_length)
return NULL;
retval = g_object_new (CLUTTER_TYPE_LIST_MODEL_ITER,
"model", model,
"row", row,
NULL);
/* short-circuit in case we don't have a filter in place */
if (!clutter_model_get_filter_set (model))
{
retval->seq_iter = g_sequence_get_iter_at_pos (sequence, row);
return CLUTTER_MODEL_ITER (retval);
}
if (row == 0)
{
GSequenceIter *filter_next;
gboolean row_found = FALSE;
filter_next = g_sequence_get_begin_iter (sequence);
g_assert (filter_next != NULL);
while (!g_sequence_iter_is_end (filter_next))
{
retval->seq_iter = filter_next;
if (clutter_model_filter_iter (model, CLUTTER_MODEL_ITER (retval)))
{
/* We've found a row that is valid under the filter */
row_found = TRUE;
break;
}
filter_next = g_sequence_iter_next (filter_next);
}
/* Everything has been filtered -> there is no first row */
if (!row_found)
{
g_object_unref (retval);
return NULL;
}
}
else
{
GSequenceIter *filter_prev;
filter_prev = g_sequence_get_end_iter (sequence);
g_assert (filter_prev != NULL);
filter_prev = g_sequence_iter_prev (filter_prev);
while (!g_sequence_iter_is_begin (filter_prev))
{
retval->seq_iter = filter_prev;
if (clutter_model_filter_iter (model, CLUTTER_MODEL_ITER (retval)))
break;
filter_prev = g_sequence_iter_prev (filter_prev);
}
}
return CLUTTER_MODEL_ITER (retval);
}
static ClutterModelIter *
clutter_list_model_insert_row (ClutterModel *model,
gint index_)
{
ClutterListModel *model_default = CLUTTER_LIST_MODEL (model);
GSequence *sequence = model_default->priv->sequence;
ClutterListModelIter *retval;
guint n_columns, i, pos;
GValueArray *array;
GSequenceIter *seq_iter;
n_columns = clutter_model_get_n_columns (model);
array = g_value_array_new (n_columns);
for (i = 0; i < n_columns; i++)
{
GValue *value = NULL;
g_value_array_append (array, NULL);
value = g_value_array_get_nth (array, i);
g_value_init (value, clutter_model_get_column_type (model, i));
}
if (index_ < 0)
{
seq_iter = g_sequence_append (sequence, array);
pos = g_sequence_get_length (sequence) - 1;
}
else if (index_ == 0)
{
seq_iter = g_sequence_prepend (sequence, array);
pos = 0;
}
else
{
seq_iter = g_sequence_get_iter_at_pos (sequence, index_);
seq_iter = g_sequence_insert_before (seq_iter, array);
pos = index_;
}
retval = g_object_new (CLUTTER_TYPE_LIST_MODEL_ITER,
"model", model,
"row", pos,
NULL);
retval->seq_iter = seq_iter;
return CLUTTER_MODEL_ITER (retval);
}
static void
clutter_list_model_remove_row (ClutterModel *model,
guint row)
{
ClutterListModel *model_default = CLUTTER_LIST_MODEL (model);
GSequence *sequence = model_default->priv->sequence;
GSequenceIter *seq_iter;
guint pos = 0;
seq_iter = g_sequence_get_begin_iter (sequence);
while (!g_sequence_iter_is_end (seq_iter))
{
if (clutter_model_filter_row (model, pos))
{
if (pos == row)
{
ClutterModelIter *iter;
iter = g_object_new (CLUTTER_TYPE_LIST_MODEL_ITER,
"model", model,
"row", pos,
NULL);
CLUTTER_LIST_MODEL_ITER (iter)->seq_iter = seq_iter;
/* the actual row is removed from the sequence inside
* the ::row-removed signal class handler, so that every
* handler connected to ::row-removed will still get
* a valid iterator, and every signal connected to
* ::row-removed with the AFTER flag will get an updated
* model
*/
g_signal_emit_by_name (model, "row-removed", iter);
g_object_unref (iter);
break;
}
}
pos += 1;
seq_iter = g_sequence_iter_next (seq_iter);
}
}
typedef struct
{
ClutterModel *model;
guint column;
ClutterModelSortFunc func;
gpointer data;
} SortClosure;
static gint
sort_model_default (gconstpointer a,
gconstpointer b,
gpointer data)
{
GValueArray *row_a = (GValueArray *) a;
GValueArray *row_b = (GValueArray *) b;
SortClosure *clos = data;
return clos->func (clos->model,
g_value_array_get_nth (row_a, clos->column),
g_value_array_get_nth (row_b, clos->column),
clos->data);
}
static void
clutter_list_model_resort (ClutterModel *model,
ClutterModelSortFunc func,
gpointer data)
{
SortClosure sort_closure = { NULL, 0, NULL, NULL };
sort_closure.model = model;
sort_closure.column = clutter_model_get_sorting_column (model);
sort_closure.func = func;
sort_closure.data = data;
g_sequence_sort (CLUTTER_LIST_MODEL (model)->priv->sequence,
sort_model_default,
&sort_closure);
}
static void
clutter_list_model_row_removed (ClutterModel *model,
ClutterModelIter *iter)
{
ClutterListModelIter *iter_default;
GValueArray *array;
iter_default = CLUTTER_LIST_MODEL_ITER (iter);
array = g_sequence_get (iter_default->seq_iter);
g_value_array_free (array);
g_sequence_remove (iter_default->seq_iter);
iter_default->seq_iter = NULL;
}
static void
clutter_list_model_finalize (GObject *gobject)
{
ClutterListModel *model = CLUTTER_LIST_MODEL (gobject);
GSequence *sequence = model->priv->sequence;
GSequenceIter *iter;
iter = g_sequence_get_begin_iter (sequence);
while (!g_sequence_iter_is_end (iter))
{
GValueArray *value_array = g_sequence_get (iter);
g_value_array_free (value_array);
iter = g_sequence_iter_next (iter);
}
g_sequence_free (sequence);
G_OBJECT_CLASS (clutter_list_model_parent_class)->finalize (gobject);
}
static void
clutter_list_model_dispose (GObject *gobject)
{
ClutterListModel *model = CLUTTER_LIST_MODEL (gobject);
if (model->priv->temp_iter)
{
g_object_unref (model->priv->temp_iter);
model->priv->temp_iter = NULL;
}
G_OBJECT_CLASS (clutter_list_model_parent_class)->dispose (gobject);
}
static void
clutter_list_model_class_init (ClutterListModelClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterModelClass *model_class = CLUTTER_MODEL_CLASS (klass);
g_type_class_add_private (klass, sizeof (ClutterListModelPrivate));
gobject_class->finalize = clutter_list_model_finalize;
gobject_class->dispose = clutter_list_model_dispose;
model_class->get_iter_at_row = clutter_list_model_get_iter_at_row;
model_class->insert_row = clutter_list_model_insert_row;
model_class->remove_row = clutter_list_model_remove_row;
model_class->resort = clutter_list_model_resort;
model_class->row_removed = clutter_list_model_row_removed;
}
static void
clutter_list_model_init (ClutterListModel *model)
{
model->priv = CLUTTER_LIST_MODEL_GET_PRIVATE (model);
model->priv->sequence = g_sequence_new (NULL);
model->priv->temp_iter = g_object_new (CLUTTER_TYPE_LIST_MODEL_ITER,
"model",
model,
NULL);
}
/**
* clutter_list_model_new:
* @n_columns: number of columns in the model
* @Varargs: @n_columns number of #GType and string pairs
*
* Creates a new default model with @n_columns columns with the types
* and names passed in.
*
* For example:
*
* <informalexample><programlisting>
* model = clutter_list_model_new (3,
* G_TYPE_INT, "Score",
* G_TYPE_STRING, "Team",
* GDK_TYPE_PIXBUF, "Logo");
* </programlisting></informalexample>
*
* will create a new #ClutterModel with three columns of type int,
* string and #GdkPixbuf respectively.
*
* Note that the name of the column can be set to %NULL, in which case
* the canonical name of the type held by the column will be used as
* the title.
*
* Return value: a new #ClutterListModel
*
* Since: 0.6
*/
ClutterModel *
clutter_list_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_LIST_MODEL, NULL);
clutter_model_set_n_columns (model, n_columns, TRUE, TRUE);
va_start (args, n_columns);
for (i = 0; i < n_columns; i++)
{
GType type = va_arg (args, GType);
const gchar *name = va_arg (args, gchar*);
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);
clutter_model_set_column_name (model, i, name);
}
va_end (args);
return model;
}
/**
* clutter_list_model_newv:
* @n_columns: number of columns in the model
* @types: an array of #GType types for the columns, from first to last
* @names: an array of names for the columns, from first to last
*
* Non-vararg version of clutter_list_model_new(). This function is
* useful for language bindings.
*
* Return value: a new default #ClutterModel
*
* Since: 0.6
*/
ClutterModel *
clutter_list_model_newv (guint n_columns,
GType *types,
const gchar * const names[])
{
ClutterModel *model;
gint i;
g_return_val_if_fail (n_columns > 0, NULL);
model = g_object_new (CLUTTER_TYPE_LIST_MODEL, NULL);
clutter_model_set_n_columns (model, n_columns, TRUE, TRUE);
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]);
clutter_model_set_column_name (model, i, names[i]);
}
return model;
}