mx/mx/mx-list-view.c

618 lines
16 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* mx-list-view.c: MxBoxLayout powered by a model
*
* Copyright 2008, 2009 Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU Lesser General Public License,
* version 2.1, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
* Boston, MA 02111-1307, USA.
*
* Written by: Thomas Wood <thomas.wood@intel.com>
*
*/
/**
* SECTION:mx-list-view
* @short_description: a box layout driven by a model.
*
* #MxListView is a box layout container driven by a #ClutterModel. Children
* are created for each row in the model, either by creating actors from the
* supplied #ClutterActor derived type, or from a #MxItemFactory.
*
* Data is set on the children by mapping columns in the model to object
* properties on the children.
*/
#include "mx-list-view.h"
#include "mx-box-layout.h"
#include "mx-private.h"
#include "mx-item-factory.h"
G_DEFINE_TYPE (MxListView, mx_list_view, MX_TYPE_BOX_LAYOUT)
#define LIST_VIEW_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), MX_TYPE_LIST_VIEW, MxListViewPrivate))
typedef struct
{
gchar *name;
gint col;
} AttributeData;
enum
{
PROP_0,
PROP_MODEL,
PROP_ITEM_TYPE,
PROP_FACTORY
};
struct _MxListViewPrivate
{
ClutterModel *model;
GSList *attributes;
GType item_type;
MxItemFactory *factory;
gulong filter_changed;
gulong row_added;
gulong row_changed;
gulong row_removed;
gulong sort_changed;
guint is_frozen : 1;
};
/* gobject implementations */
static void
mx_list_view_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
MxListViewPrivate *priv = MX_LIST_VIEW (object)->priv;
switch (property_id)
{
case PROP_MODEL:
g_value_set_object (value, priv->model);
break;
case PROP_ITEM_TYPE:
g_value_set_gtype (value, priv->item_type);
break;
case PROP_FACTORY:
g_value_set_object (value, priv->factory);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
mx_list_view_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id)
{
case PROP_MODEL:
mx_list_view_set_model ((MxListView*) object,
(ClutterModel*) g_value_get_object (value));
break;
case PROP_ITEM_TYPE:
mx_list_view_set_item_type ((MxListView*) object,
g_value_get_gtype (value));
break;
case PROP_FACTORY:
mx_list_view_set_factory ((MxListView*) object,
(MxItemFactory*) g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
mx_list_view_dispose (GObject *object)
{
MxListViewPrivate *priv = MX_LIST_VIEW (object)->priv;
/* This will cause the unref of the model and also disconnect the signals */
mx_list_view_set_model (MX_LIST_VIEW (object), NULL);
if (priv->factory)
{
g_object_unref (priv->factory);
priv->factory = NULL;
}
G_OBJECT_CLASS (mx_list_view_parent_class)->dispose (object);
}
static void
free_attribute (AttributeData *data)
{
g_free (data->name);
g_free (data);
}
static void
mx_list_view_finalize (GObject *object)
{
MxListViewPrivate *priv = MX_LIST_VIEW (object)->priv;
if (priv->attributes)
{
g_slist_foreach (priv->attributes, (GFunc) free_attribute, NULL);
g_slist_free (priv->attributes);
priv->attributes = NULL;
}
G_OBJECT_CLASS (mx_list_view_parent_class)->finalize (object);
}
static void
mx_list_view_class_init (MxListViewClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
g_type_class_add_private (klass, sizeof (MxListViewPrivate));
object_class->get_property = mx_list_view_get_property;
object_class->set_property = mx_list_view_set_property;
object_class->dispose = mx_list_view_dispose;
object_class->finalize = mx_list_view_finalize;
pspec = g_param_spec_object ("model",
"model",
"The model for the item view",
CLUTTER_TYPE_MODEL,
MX_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_MODEL, pspec);
pspec = g_param_spec_gtype ("item-type",
"Item Type",
"The GType to use as the items in the view. "
"Must be a subclass of ClutterActor",
CLUTTER_TYPE_ACTOR,
MX_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_ITEM_TYPE, pspec);
/* Note, interfaces aren't necessarily objects, so you can't use
* MX_TYPE_ITEM_FACTORY here. The function mx_list_view_set_factory does
* a type check, so this is still safe.
*/
pspec = g_param_spec_object ("factory",
"Factory",
"The MxItemFactory used for creating new items.",
G_TYPE_OBJECT /*MX_TYPE_ITEM_FACTORY*/,
MX_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_FACTORY, pspec);
}
static void
mx_list_view_init (MxListView *list_view)
{
list_view->priv = LIST_VIEW_PRIVATE (list_view);
mx_box_layout_set_orientation (MX_BOX_LAYOUT (list_view), MX_ORIENTATION_VERTICAL);
}
/* model monitors */
static void
model_changed_cb (ClutterModel *model,
MxListView *list_view)
{
GSList *p;
GList *l, *children;
MxListViewPrivate *priv = list_view->priv;
ClutterModelIter *iter = NULL;
gint model_n = 0, child_n = 0;
/* bail out if we don't yet have an item type or a factory */
if (!priv->item_type && !priv->factory)
return;
if (priv->is_frozen)
return;
if (priv->item_type)
{
/* check the item-type is an descendant of ClutterActor */
if (!g_type_is_a (priv->item_type, CLUTTER_TYPE_ACTOR))
{
g_warning ("%s is not a subclass of ClutterActor and therefore"
" cannot be used as items in an MxListView",
g_type_name (priv->item_type));
return;
}
}
children = clutter_container_get_children (CLUTTER_CONTAINER (list_view));
child_n = g_list_length (children);
if (model)
model_n = clutter_model_get_n_rows (priv->model);
else
model_n = 0;
/* add children as needed */
while (model_n > child_n)
{
ClutterActor *new_child;
if (priv->item_type)
{
new_child = g_object_new (priv->item_type, NULL);
}
else
{
new_child = mx_item_factory_create (priv->factory);
}
clutter_container_add_actor (CLUTTER_CONTAINER (list_view),
new_child);
child_n++;
}
/* remove children as needed */
l = g_list_last (children);
while (child_n > model_n)
{
clutter_container_remove_actor (CLUTTER_CONTAINER (list_view),
(ClutterActor*) l->data);
l = g_list_previous (l);
child_n--;
}
g_list_free (children);
if (!priv->model)
return;
children = clutter_container_get_children (CLUTTER_CONTAINER (list_view));
/* set the properties on the children */
iter = clutter_model_get_first_iter (priv->model);
l = children;
while (iter && !clutter_model_iter_is_last (iter))
{
GObject *child;
child = G_OBJECT (l->data);
g_object_freeze_notify (child);
for (p = priv->attributes; p; p = p->next)
{
GValue value = { 0, };
AttributeData *attr = p->data;
clutter_model_iter_get_value (iter, attr->col, &value);
g_object_set_property (child, attr->name, &value);
g_value_unset (&value);
}
g_object_thaw_notify (child);
l = g_list_next (l);
clutter_model_iter_next (iter);
}
g_list_free (children);
if (iter)
g_object_unref (iter);
}
static void
row_changed_cb (ClutterModel *model,
ClutterModelIter *iter,
MxListView *list_view)
{
model_changed_cb (model, list_view);
}
static void
row_removed_cb (ClutterModel *model,
ClutterModelIter *iter,
MxListView *list_view)
{
GList *children;
GList *l;
ClutterActor *child;
if (list_view->priv->is_frozen)
return;
children = clutter_container_get_children (CLUTTER_CONTAINER (list_view));
l = g_list_nth (children, clutter_model_iter_get_row (iter));
child = (ClutterActor *) l->data;
clutter_container_remove_actor (CLUTTER_CONTAINER (list_view), child);
g_list_free (children);
}
/* public api */
/**
* mx_list_view_new:
*
* Create a new #MxListView
*
* Returns: a newly allocated #MxListView
*/
ClutterActor *
mx_list_view_new (void)
{
return g_object_new (MX_TYPE_LIST_VIEW, NULL);
}
/**
* mx_list_view_get_item_type:
* @list_view: An #MxListView
*
* Get the item type currently being used to create items
*
* Returns: a #GType
*/
GType
mx_list_view_get_item_type (MxListView *list_view)
{
g_return_val_if_fail (MX_IS_LIST_VIEW (list_view), G_TYPE_INVALID);
return list_view->priv->item_type;
}
/**
* mx_list_view_set_item_type:
* @list_view: An #MxListView
* @item_type: A #GType
*
* Set the item type used to create items representing each row in the
* model
*/
void
mx_list_view_set_item_type (MxListView *list_view,
GType item_type)
{
g_return_if_fail (MX_IS_LIST_VIEW (list_view));
g_return_if_fail (g_type_is_a (item_type, CLUTTER_TYPE_ACTOR));
list_view->priv->item_type = item_type;
/* update the view */
model_changed_cb (list_view->priv->model, list_view);
}
/**
* mx_list_view_get_model:
* @list_view: An #MxListView
*
* Get the model currently used by the #MxListView
*
* Returns: (transfer none): the current #ClutterModel
*/
ClutterModel*
mx_list_view_get_model (MxListView *list_view)
{
g_return_val_if_fail (MX_IS_LIST_VIEW (list_view), NULL);
return list_view->priv->model;
}
/**
* mx_list_view_set_model:
* @list_view: An #MxListView
* @model: A #ClutterModel
*
* Set the model used by the #MxListView
*/
void
mx_list_view_set_model (MxListView *list_view,
ClutterModel *model)
{
MxListViewPrivate *priv;
g_return_if_fail (MX_IS_LIST_VIEW (list_view));
g_return_if_fail (model == NULL || CLUTTER_IS_MODEL (model));
priv = list_view->priv;
if (priv->model)
{
g_signal_handlers_disconnect_by_func (priv->model,
(GCallback) model_changed_cb,
list_view);
g_signal_handlers_disconnect_by_func (priv->model,
(GCallback) row_changed_cb,
list_view);
g_signal_handlers_disconnect_by_func (priv->model,
(GCallback) row_removed_cb,
list_view);
g_object_unref (priv->model);
priv->model = NULL;
}
if (model)
{
g_return_if_fail (CLUTTER_IS_MODEL (model));
priv->model = g_object_ref (model);
priv->filter_changed = g_signal_connect (priv->model,
"filter-changed",
G_CALLBACK (model_changed_cb),
list_view);
priv->row_added = g_signal_connect (priv->model,
"row-added",
G_CALLBACK (row_changed_cb),
list_view);
priv->row_changed = g_signal_connect (priv->model,
"row-changed",
G_CALLBACK (row_changed_cb),
list_view);
/*
* model_changed_cb (called from row_changed_cb) expect the row to already
* have been removed, thus we need to use _after
*/
priv->row_removed = g_signal_connect_after (priv->model,
"row-removed",
G_CALLBACK (row_removed_cb),
list_view);
priv->sort_changed = g_signal_connect (priv->model,
"sort-changed",
G_CALLBACK (model_changed_cb),
list_view);
/*
* Only do this inside this block, setting the model to NULL should have
* the effect of preserving the view; just disconnect the handlers
*/
model_changed_cb (priv->model, list_view);
}
}
/**
* mx_list_view_add_attribute:
* @list_view: An #MxListView
* @attribute: Name of the attribute
* @column: Column number
*
* Adds an attribute mapping between the current model and the objects from the
* cell renderer.
*
*/
void
mx_list_view_add_attribute (MxListView *list_view,
const gchar *_attribute,
gint column)
{
MxListViewPrivate *priv;
AttributeData *prop;
g_return_if_fail (MX_IS_LIST_VIEW (list_view));
g_return_if_fail (_attribute != NULL);
g_return_if_fail (column >= 0);
priv = list_view->priv;
prop = g_new (AttributeData, 1);
prop->name = g_strdup (_attribute);
prop->col = column;
priv->attributes = g_slist_prepend (priv->attributes, prop);
model_changed_cb (priv->model, list_view);
}
/**
* mx_list_view_freeze
* @list_view: An #MxListView
*
* Freeze the view. This means that the view will not act on changes to the
* model until it is thawed. Call #mx_list_view_thaw to thaw the view.
*/
void
mx_list_view_freeze (MxListView *list_view)
{
MxListViewPrivate *priv;
g_return_if_fail (MX_IS_LIST_VIEW (list_view));
priv = list_view->priv;
priv->is_frozen = TRUE;
}
/**
* mx_list_view_thaw
* @list_view: An #MxListView
*
* Thaw the view. This means that the view will now act on changes to the
* model.
*/
void
mx_list_view_thaw (MxListView *list_view)
{
MxListViewPrivate *priv;
g_return_if_fail (MX_IS_LIST_VIEW (list_view));
priv = list_view->priv;
priv->is_frozen = FALSE;
/* Repopulate */
model_changed_cb (priv->model, list_view);
}
/**
* mx_list_view_set_factory:
* @list_view: A #MxListView
* @factory: (allow-none): A #MxItemFactory
*
* Sets @factory to be the factory used for creating new list items
*/
void
mx_list_view_set_factory (MxListView *list_view,
MxItemFactory *factory)
{
MxListViewPrivate *priv;
g_return_if_fail (MX_IS_LIST_VIEW (list_view));
g_return_if_fail (!factory || MX_IS_ITEM_FACTORY (factory));
priv = list_view->priv;
if (priv->factory == factory)
return;
if (priv->factory)
{
g_object_unref (priv->factory);
priv->factory = NULL;
}
if (factory)
priv->factory = g_object_ref (factory);
g_object_notify (G_OBJECT (list_view), "factory");
}
/**
* mx_list_view_get_factory:
* @list_view: A #MxListView
*
* Gets the #MxItemFactory used for creating new list items.
*
* Returns: (transfer none): A #MxItemFactory.
*/
MxItemFactory *
mx_list_view_get_factory (MxListView *list_view)
{
g_return_val_if_fail (MX_IS_LIST_VIEW (list_view), NULL);
return list_view->priv->factory;
}