/*
* 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.
*
* Taken from clutter-list-model.c:
*
* Authors:
* Neil Jagdish Patel <njp@o-hand.com>
* Emmanuele Bassi <ebassi@openedhand.com>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <glib-object.h>
#include <clutter/clutter.h>
#include "tweet-status-model.h"
#define TWEET_TYPE_STATUS_MODEL_ITER \
(tweet_status_model_iter_get_type())
#define TWEET_STATUS_MODEL_ITER(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj), \
TWEET_TYPE_STATUS_MODEL_ITER, \
TweetStatusModelIter))
#define TWEET_IS_STATUS_MODEL_ITER(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj), \
TWEET_TYPE_STATUS_MODEL_ITER))
#define TWEET_STATUS_MODEL_ITER_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST ((klass), \
TWEET_TYPE_STATUS_MODEL_ITER, \
TweetStatusModelIterClass))
#define TWEET_IS_STATUS_MODEL_ITER_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE ((klass), \
TWEET_TYPE_STATUS_MODEL_ITER))
#define TWEET_STATUS_MODEL_ITER_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj), \
TWEET_TYPE_STATUS_MODEL_ITER, \
TweetStatusModelIterClass))
typedef struct _TweetStatusModelIter TweetStatusModelIter;
typedef struct _ClutterModelIterClass TweetStatusModelIterClass;
struct _TweetStatusModelIter
{
ClutterModelIter parent_instance;
GSequenceIter *seq_iter;
};
#define TWEET_STATUS_MODEL_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TWEET_TYPE_STATUS_MODEL, TweetStatusModelPrivate))
struct _TweetStatusModelPrivate
{
GSequence *sequence;
gint max_size;
};
static const gchar *model_names[] = {
"Status"
};
static const gint model_columns = G_N_ELEMENTS (model_names);
/*
* TweetStatusModel
*/
G_DEFINE_TYPE (TweetStatusModelIter,
tweet_status_model_iter,
CLUTTER_TYPE_MODEL_ITER);
static void
tweet_status_model_iter_get_value (ClutterModelIter *iter,
guint column,
GValue *value)
{
TweetStatusModelIter *iter_default;
GValueArray *value_array;
GValue *iter_value;
GValue real_value = { 0, };
gboolean converted = FALSE;
iter_default = TWEET_STATUS_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
tweet_status_model_iter_set_value (ClutterModelIter *iter,
guint column,
const GValue *value)
{
TweetStatusModelIter *iter_default;
GValueArray *value_array;
GValue *iter_value;
GValue real_value = { 0, };
gboolean converted = FALSE;
iter_default = TWEET_STATUS_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
tweet_status_model_iter_is_first (ClutterModelIter *iter)
{
TweetStatusModelIter *iter_default;
ClutterModel *model;
ClutterModelIter *temp_iter;
GSequenceIter *begin, *end;
guint row;
iter_default = TWEET_STATUS_MODEL_ITER (iter);
g_assert (iter_default->seq_iter != NULL);
model = clutter_model_iter_get_model (iter);
row = clutter_model_iter_get_row (iter);
begin = g_sequence_get_begin_iter (TWEET_STATUS_MODEL (model)->priv->sequence);
end = iter_default->seq_iter;
temp_iter = g_object_new (TWEET_TYPE_STATUS_MODEL_ITER,
"model", model,
NULL);
while (!g_sequence_iter_is_begin (begin))
{
TWEET_STATUS_MODEL_ITER (temp_iter)->seq_iter = begin;
g_object_set (G_OBJECT (temp_iter), "row", row, NULL);
if (clutter_model_filter_iter (model, temp_iter))
{
end = begin;
break;
}
begin = g_sequence_iter_next (begin);
row += 1;
}
g_object_unref (temp_iter);
/* 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
tweet_status_model_iter_is_last (ClutterModelIter *iter)
{
TweetStatusModelIter *iter_default;
ClutterModelIter *temp_iter;
ClutterModel *model;
GSequenceIter *begin, *end;
guint row;
iter_default = TWEET_STATUS_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);
row = clutter_model_iter_get_row (iter);
begin = g_sequence_get_end_iter (TWEET_STATUS_MODEL (model)->priv->sequence);
begin = g_sequence_iter_prev (begin);
end = iter_default->seq_iter;
temp_iter = g_object_new (TWEET_TYPE_STATUS_MODEL_ITER,
"model", model,
NULL);
while (!g_sequence_iter_is_begin (begin))
{
TWEET_STATUS_MODEL_ITER (temp_iter)->seq_iter = begin;
g_object_set (G_OBJECT (temp_iter), "row", row, NULL);
if (clutter_model_filter_iter (model, temp_iter))
{
end = begin;
break;
}
begin = g_sequence_iter_prev (begin);
row += 1;
}
g_object_unref (temp_iter);
/* 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 *
tweet_status_model_iter_next (ClutterModelIter *iter)
{
TweetStatusModelIter *iter_default;
ClutterModelIter *temp_iter;
ClutterModel *model = NULL;
GSequenceIter *filter_next;
guint row;
iter_default = TWEET_STATUS_MODEL_ITER (iter);
g_assert (iter_default->seq_iter != NULL);
model = clutter_model_iter_get_model (iter);
row = clutter_model_iter_get_row (iter) + 1;
filter_next = g_sequence_iter_next (iter_default->seq_iter);
g_assert (filter_next != NULL);
temp_iter = g_object_new (TWEET_TYPE_STATUS_MODEL_ITER,
"model", model,
NULL);
while (!g_sequence_iter_is_end (filter_next))
{
TWEET_STATUS_MODEL_ITER (temp_iter)->seq_iter = filter_next;
g_object_set (G_OBJECT (temp_iter), "row", row, NULL);
if (clutter_model_filter_iter (model, temp_iter))
break;
filter_next = g_sequence_iter_next (filter_next);
row += 1;
}
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_default->seq_iter)
filter_next = g_sequence_iter_next (filter_next);
/* update the iterator and return it */
g_object_set (G_OBJECT (iter_default), "model", model, "row", row, NULL);
iter_default->seq_iter = filter_next;
return CLUTTER_MODEL_ITER (iter_default);
}
static ClutterModelIter *
tweet_status_model_iter_prev (ClutterModelIter *iter)
{
TweetStatusModelIter *iter_default;
ClutterModelIter *temp_iter;
ClutterModel *model;
GSequenceIter *filter_prev;
guint row;
iter_default = TWEET_STATUS_MODEL_ITER (iter);
g_assert (iter_default->seq_iter != NULL);
model = clutter_model_iter_get_model (iter);
row = clutter_model_iter_get_row (iter) - 1;
filter_prev = g_sequence_iter_prev (iter_default->seq_iter);
g_assert (filter_prev != NULL);
temp_iter = g_object_new (TWEET_TYPE_STATUS_MODEL_ITER,
"model", model,
NULL);
while (!g_sequence_iter_is_begin (filter_prev))
{
TWEET_STATUS_MODEL_ITER (temp_iter)->seq_iter = filter_prev;
g_object_set (G_OBJECT (temp_iter), "row", row, NULL);
if (clutter_model_filter_iter (model, temp_iter))
break;
filter_prev = g_sequence_iter_prev (filter_prev);
row -= 1;
}
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_prev == iter_default->seq_iter)
filter_prev = g_sequence_iter_prev (filter_prev);
/* update the iterator and return it */
g_object_set (G_OBJECT (iter_default), "model", model, "row", row, NULL);
iter_default->seq_iter = filter_prev;
return CLUTTER_MODEL_ITER (iter_default);
}
#if CLUTTER_CHECK_VERSION(0, 7, 0)
static ClutterModelIter *
tweet_status_model_iter_copy (ClutterModelIter *iter)
{
TweetStatusModelIter *iter_default;
TweetStatusModelIter *iter_copy;
ClutterModel *model;
guint row;
iter_default = TWEET_STATUS_MODEL_ITER (iter);
model = clutter_model_iter_get_model (iter);
row = clutter_model_iter_get_row (iter) - 1;
iter_copy = g_object_new (TWEET_TYPE_STATUS_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);
}
#endif /* CLUTTER_CHECK_VERSION(0, 7, 0) */
static void
tweet_status_model_iter_class_init (TweetStatusModelIterClass *klass)
{
ClutterModelIterClass *iter_class = CLUTTER_MODEL_ITER_CLASS (klass);
iter_class->get_value = tweet_status_model_iter_get_value;
iter_class->set_value = tweet_status_model_iter_set_value;
iter_class->is_first = tweet_status_model_iter_is_first;
iter_class->is_last = tweet_status_model_iter_is_last;
iter_class->next = tweet_status_model_iter_next;
iter_class->prev = tweet_status_model_iter_prev;
#if CLUTTER_CHECK_VERSION(0, 7, 0)
iter_class->copy = tweet_status_model_iter_copy;
#endif
}
static void
tweet_status_model_iter_init (TweetStatusModelIter *iter)
{
iter->seq_iter = NULL;
}
/*
* TweetStatusModel
*/
G_DEFINE_TYPE (TweetStatusModel, tweet_status_model, CLUTTER_TYPE_MODEL);
static ClutterModelIter *
tweet_status_model_get_iter_at_row (ClutterModel *model,
guint row)
{
TweetStatusModelPrivate *priv = TWEET_STATUS_MODEL (model)->priv;
TweetStatusModelIter *retval;
if (row >= g_sequence_get_length (priv->sequence))
return NULL;
retval = g_object_new (TWEET_TYPE_STATUS_MODEL_ITER,
"model", model,
"row", row,
NULL);
retval->seq_iter = g_sequence_get_iter_at_pos (priv->sequence, row);
return CLUTTER_MODEL_ITER (retval);
}
static ClutterModelIter *
tweet_status_model_insert_row (ClutterModel *model,
gint index_)
{
TweetStatusModelPrivate *priv = TWEET_STATUS_MODEL (model)->priv;
TweetStatusModelIter *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 (priv->sequence, array);
pos = g_sequence_get_length (priv->sequence);
}
else if (index_ == 0)
{
seq_iter = g_sequence_prepend (priv->sequence, array);
pos = 0;
}
else
{
seq_iter = g_sequence_get_iter_at_pos (priv->sequence, index_);
seq_iter = g_sequence_insert_before (seq_iter, array);
pos = index_;
}
retval = g_object_new (TWEET_TYPE_STATUS_MODEL_ITER,
"model", model,
"row", pos,
NULL);
retval->seq_iter = seq_iter;
return CLUTTER_MODEL_ITER (retval);
}
static void
tweet_status_model_remove_row (ClutterModel *model,
guint row)
{
TweetStatusModelPrivate *priv = TWEET_STATUS_MODEL (model)->priv;
GSequenceIter *seq_iter;
guint pos = 0;
seq_iter = g_sequence_get_begin_iter (priv->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 (TWEET_TYPE_STATUS_MODEL_ITER,
"model", model,
"row", pos,
NULL);
TWEET_STATUS_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);
}
}
static guint
tweet_status_model_get_n_rows (ClutterModel *model)
{
TweetStatusModelPrivate *priv = TWEET_STATUS_MODEL (model)->priv;
return g_sequence_get_length (priv->sequence);
}
typedef struct
{
ClutterModel *model;
guint column;
ClutterModelSortFunc func;
gpointer data;
} SortClosure;
static gint
sort_model_default (gconstpointer a,
gconstpointer b,
gpointer data)<