You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gnucash/lib/goffice/graph/gog-object.c

874 lines
22 KiB

/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* gog-object.c :
*
* Copyright (C) 2003-2004 Jody Goldberg (jody@gnome.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#include <goffice/goffice-config.h>
#include <goffice/graph/gog-object.h>
#include <goffice/graph/gog-graph-impl.h> /* for gog_graph_request_update */
#include <goffice/graph/gog-data-set.h>
#include <goffice/graph/go-data.h>
#include <gsf/gsf-impl-utils.h>
#include <glib/gi18n.h>
#include <string.h>
#include <stdlib.h>
enum {
CHILD_ADDED,
CHILD_REMOVED,
CHILD_NAME_CHANGED,
CHILDREN_REORDERED,
NAME_CHANGED,
CHANGED,
LAST_SIGNAL
};
static gulong gog_object_signals [LAST_SIGNAL] = { 0, };
static GObjectClass *parent_klass;
static void
gog_object_finalize (GObject *gobj)
{
GogObject *obj = GOG_OBJECT (gobj);
g_free (obj->user_name); obj->user_name = NULL;
g_free (obj->id); obj->id = NULL;
g_slist_foreach (obj->children, (GFunc) g_object_unref, NULL);
g_slist_free (obj->children);
obj->children = NULL;
(parent_klass->finalize) (gobj);
}
static void
gog_object_parent_changed (GogObject *child, gboolean was_set)
{
GSList *ptr = child->children;
for (; ptr != NULL ; ptr = ptr->next) {
GogObjectClass *klass = GOG_OBJECT_GET_CLASS (ptr->data);
(*klass->parent_changed) (ptr->data, was_set);
}
if (IS_GOG_DATASET (child))
gog_dataset_parent_changed (GOG_DATASET (child), was_set);
}
static void
gog_object_class_init (GObjectClass *klass)
{
GogObjectClass *gog_klass = (GogObjectClass *)klass;
parent_klass = g_type_class_peek_parent (klass);
klass->finalize = gog_object_finalize;
gog_klass->parent_changed = gog_object_parent_changed;
gog_object_signals [CHILD_ADDED] = g_signal_new ("child-added",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GogObjectClass, child_added),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, G_TYPE_OBJECT);
gog_object_signals [CHILD_REMOVED] = g_signal_new ("child-removed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GogObjectClass, child_removed),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, G_TYPE_OBJECT);
gog_object_signals [CHILD_NAME_CHANGED] = g_signal_new ("child-name-changed",
G_TYPE_FROM_CLASS (gog_klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GogObjectClass, child_name_changed),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, G_TYPE_OBJECT);
gog_object_signals [CHILDREN_REORDERED] = g_signal_new ("children-reordered",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GogObjectClass, children_reordered),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
gog_object_signals [NAME_CHANGED] = g_signal_new ("name-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GogObjectClass, name_changed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
gog_object_signals [CHANGED] = g_signal_new ("changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GogObjectClass, changed),
NULL, NULL,
g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
}
static void
gog_object_init (GogObject *obj)
{
obj->children = NULL;
obj->user_name = NULL;
obj->id = NULL;
obj->needs_update = FALSE;
obj->being_updated = FALSE;
}
GSF_CLASS (GogObject, gog_object,
gog_object_class_init, gog_object_init,
G_TYPE_OBJECT)
static char *
gog_object_generate_name (GogObject *obj)
{
GogObjectClass *klass = GOG_OBJECT_GET_CLASS (obj);
GogObject *tmp;
char const *type_name;
unsigned name_len, i, max_index = 0;
GSList *ptr;
g_return_val_if_fail (klass != NULL, NULL);
g_return_val_if_fail (obj->role != NULL, NULL);
switch (obj->role->naming_conv) {
default :
case GOG_OBJECT_NAME_MANUALLY :
g_warning ("Role %s should not be autogenerating names",
obj->role->id);
case GOG_OBJECT_NAME_BY_ROLE :
g_return_val_if_fail (obj->role != NULL, NULL);
type_name = _(obj->role->id);
break;
case GOG_OBJECT_NAME_BY_TYPE :
g_return_val_if_fail (klass->type_name != NULL, NULL);
type_name = _((*klass->type_name) (obj));
break;
}
g_return_val_if_fail (type_name != NULL, NULL);
name_len = strlen (type_name);
for (ptr = obj->parent->children; ptr != NULL ; ptr = ptr->next) {
tmp = GOG_OBJECT (ptr->data);
if (tmp->id != NULL &&
0 == strncmp (type_name, tmp->id, name_len)) {
i = strtol (tmp->id + name_len, NULL, 10);
if (max_index < i)
max_index = i;
}
}
return g_strdup_printf ("%s%d", type_name, max_index + 1);
}
/**
* gog_object_dup :
* @src : #GogObject
* @new_parent : #GogObject the parent tree for the object (can be NULL)
*
* Create a deep copy of @obj using @new_parent as its parent.
**/
GogObject *
gog_object_dup (GogObject const *src, GogObject *new_parent)
{
gint n, last;
GParamSpec **props;
GogObject *dst = NULL;
GSList *ptr;
GValue val = { 0 };
if (src == NULL)
return NULL;
g_return_val_if_fail (GOG_OBJECT (src) != NULL, NULL);
if (src->role == NULL || src->explicitly_typed_role)
dst = g_object_new (G_OBJECT_TYPE (src), NULL);
if (new_parent)
dst = gog_object_add_by_role (new_parent, src->role, dst);
dst->position = src->position;
/* properties */
props = g_object_class_list_properties (G_OBJECT_GET_CLASS (src), &n);
while (n-- > 0)
if (props[n]->flags & GOG_PARAM_PERSISTENT) {
g_value_init (&val, props[n]->value_type);
g_object_get_property (G_OBJECT (src), props[n]->name, &val);
g_object_set_property (G_OBJECT (dst), props[n]->name, &val);
g_value_unset (&val);
}
g_free (props);
if (IS_GOG_DATASET (src)) { /* convenience to save data */
GogDataset const *src_set = GOG_DATASET (src);
GogDataset *dst_set = GOG_DATASET (dst);
gog_dataset_dims (src_set, &n, &last);
for ( ; n <= last ; n++)
gog_dataset_set_dim (dst_set, n,
go_data_dup (gog_dataset_get_dim (src_set, n)),
NULL);
}
for (ptr = src->children; ptr != NULL ; ptr = ptr->next)
/* children added directly to new parent, no need to use the
* function result */
gog_object_dup (ptr->data, dst);
return dst;
}
/**
* gog_object_get_parent :
* @obj : a #GogObject
*
* Returns @obj's parent, potentially NULL if it has not been added to a
* heirarchy yet. does not change ref-count in any way.
**/
GogObject *
gog_object_get_parent (GogObject const *obj)
{
g_return_val_if_fail (GOG_OBJECT (obj) != NULL, NULL);
return obj->parent;
}
/**
* gog_object_get_parent_typed :
* @obj : a #GogObject
* @type : a #GType
*
* Returns @obj's parent of type @type, potentially NULL if it has not been
* added to a heirarchy yet or none of the parents are of type @type.
**/
GogObject *
gog_object_get_parent_typed (GogObject const *obj, GType t)
{
g_return_val_if_fail (GOG_OBJECT (obj) != NULL, NULL);
for (; obj != NULL ; obj = obj->parent)
if (G_TYPE_CHECK_INSTANCE_TYPE (obj, t))
return GOG_OBJECT (obj); /* const cast */
return NULL;
}
/**
* gog_object_get_graph :
* @obj : const * #GogObject
*
* Returns the parent graph.
**/
GogGraph *
gog_object_get_graph (GogObject const *obj)
{
g_return_val_if_fail (GOG_OBJECT (obj) != NULL, NULL);
for (; obj != NULL ; obj = obj->parent)
if (IS_GOG_GRAPH (obj))
return GOG_GRAPH (obj);
return NULL;
}
GogTheme *
gog_object_get_theme (GogObject const *obj)
{
GogGraph *graph = gog_object_get_graph (obj);
return (graph != NULL) ? gog_graph_get_theme (graph) : NULL;
}
/**
* gog_object_get_name :
* @obj : a #GogObject
*
* No need to free the result
**/
char const *
gog_object_get_name (GogObject const *obj)
{
g_return_val_if_fail (GOG_OBJECT (obj) != NULL, NULL);
return (obj->user_name != NULL && *obj->user_name != '\0') ? obj->user_name : obj->id;
}
/**
* gog_object_set_name :
* @obj : #GogObject
* @name :
* @err : #GError
*
* Assign the new name and signals that it has changed.
* NOTE : it _absorbs_ @name rather than copying it, and generates a new name
* if @name == NULL
**/
void
gog_object_set_name (GogObject *obj, char *name, GError **err)
{
GogObject *tmp;
g_return_if_fail (GOG_OBJECT (obj) != NULL);
if (obj->user_name == name)
return;
g_free (obj->user_name);
obj->user_name = name;
g_signal_emit (G_OBJECT (obj),
gog_object_signals [NAME_CHANGED], 0);
for (tmp = obj; tmp != NULL ; tmp = tmp->parent)
g_signal_emit (G_OBJECT (tmp),
gog_object_signals [CHILD_NAME_CHANGED], 0, obj);
}
/**
* gog_object_get_children :
* @obj : a #GogObject
* @filter : an optional #GogObjectRole to use as a filter
*
* The list needs to be Freed
**/
GSList *
gog_object_get_children (GogObject const *obj, GogObjectRole const *filter)
{
GSList *ptr, *res = NULL;
g_return_val_if_fail (GOG_OBJECT (obj) != NULL, NULL);
if (filter == NULL)
return g_slist_copy (obj->children);
for (ptr = obj->children ; ptr != NULL ; ptr = ptr->next)
if (GOG_OBJECT (ptr->data)->role == filter)
res = g_slist_prepend (res, ptr->data);
return g_slist_reverse (res);
}
/**
* gog_object_get_child_by_role :
* @obj : a #GogObject
* @role : a #GogObjectRole to use as a filter
*
* A convenience routine to handle a unique child
* Returns NULL and spews an error if there is more than one.
**/
GogObject *
gog_object_get_child_by_role (GogObject const *obj, GogObjectRole const *role)
{
GogObject *res = NULL;
GSList *children = gog_object_get_children (obj, role);
if (children != NULL && children->next == NULL)
res = children->data;
g_slist_free (children);
return res;
}
/**
* gog_object_is_deletable :
* @obj : a #GogObject
*
* Can the specified @obj be deleted ?
**/
gboolean
gog_object_is_deletable (GogObject const *obj)
{
g_return_val_if_fail (GOG_OBJECT (obj) != NULL, FALSE);
if (IS_GOG_GRAPH (obj))
return FALSE;
return obj->role == NULL || obj->role->can_remove == NULL ||
(obj->role->can_remove) (obj);
}
struct possible_add_closure {
GSList *res;
GogObject const *parent;
};
static void
cb_collect_possible_additions (char const *name, GogObjectRole const *role,
struct possible_add_closure *data)
{
if (role->can_add == NULL || (role->can_add) (data->parent))
data->res = g_slist_prepend (data->res, (gpointer)role);
}
static int
gog_object_position_cmp (GogObjectPosition pos)
{
if (pos & GOG_POSITION_COMPASS)
return 0;
if (pos == GOG_POSITION_SPECIAL)
return 2;
return 1; /* GOG_POSITION_MANUAL */
}
static int
gog_role_cmp (GogObjectRole const *a, GogObjectRole const *b)
{
int index_a = gog_object_position_cmp (a->allowable_positions);
int index_b = gog_object_position_cmp (b->allowable_positions);
/* intentionally reverse to put SPECIAL at the top */
if (index_a < index_b)
return 1;
else if (index_a > index_b)
return -1;
return b->priority - a->priority;
}
static int
gog_role_cmp_full (GogObjectRole const *a, GogObjectRole const *b)
{
int res = gog_role_cmp (a, b);
if (res != 0)
return res;
return g_utf8_collate (a->id, b->id);
}
/**
* gog_object_possible_additions :
* @parent : a #GogObject
*
* returns a list of GogObjectRoles that could be added
*
* The resulting list needs to be freed
**/
GSList *
gog_object_possible_additions (GogObject const *parent)
{
GogObjectClass *klass = GOG_OBJECT_GET_CLASS (parent);
g_return_val_if_fail (klass != NULL, NULL);
if (klass->roles != NULL) {
struct possible_add_closure data;
data.res = NULL;
data.parent = parent;
g_hash_table_foreach (klass->roles,
(GHFunc) cb_collect_possible_additions, &data);
return g_slist_sort (data.res, (GCompareFunc) gog_role_cmp_full);
}
return NULL;
}
/**
* gog_object_can_reorder :
* @obj : #GogObject
* @inc_ok : possibly NULL pointer.
* @dec_ok : possibly NULL pointer.
*
* If @obj can move forward or backward in its parents child list
**/
void
gog_object_can_reorder (GogObject const *obj, gboolean *inc_ok, gboolean *dec_ok)
{
GogObject const *parent;
GSList *ptr;
g_return_if_fail (GOG_OBJECT (obj) != NULL);
if (inc_ok != NULL)
*inc_ok = FALSE;
if (dec_ok != NULL)
*dec_ok = FALSE;
if (obj->parent == NULL || gog_object_get_graph (obj) == NULL)
return;
parent = obj->parent;
ptr = parent->children;
g_return_if_fail (ptr != NULL);
/* find a pointer to the previous sibling */
if (ptr->data != obj) {
while (ptr->next != NULL && ptr->next->data != obj)
ptr = ptr->next;
g_return_if_fail (ptr->next != NULL);
if (inc_ok != NULL &&
!gog_role_cmp (((GogObject *)ptr->data)->role, obj->role))
*inc_ok = TRUE;
ptr = ptr->next;
}
/* ptr now points at @obj */
if (dec_ok != NULL && ptr->next != NULL &&
!gog_role_cmp (obj->role, ((GogObject *)ptr->next->data)->role))
*dec_ok = TRUE;
}
/**
* gog_object_reorder :
* @obj : #GogObject
* @inc :
* @goto_max :
*
* Returns the object just before @obj in the new ordering.
**/
GogObject *
gog_object_reorder (GogObject const *obj, gboolean inc, gboolean goto_max)
{
GogObject *parent, *obj_follows;
GSList **ptr, *tmp;
g_return_val_if_fail (GOG_OBJECT (obj) != NULL, NULL);
if (obj->parent == NULL || gog_object_get_graph (obj) == NULL)
return NULL;
parent = obj->parent;
if (inc)
parent->children = g_slist_reverse (parent->children);
for (ptr = &parent->children; *ptr != NULL && (*ptr)->data != obj ;)
ptr = &(*ptr)->next;
g_return_val_if_fail (*ptr != NULL, NULL);
g_return_val_if_fail ((*ptr)->next != NULL, NULL);
tmp = *ptr;
*ptr = tmp->next;
ptr = &(*ptr)->next;
while (goto_max && *ptr != NULL &&
!gog_role_cmp (obj->role, ((GogObject *)((*ptr)->data))->role))
ptr = &(*ptr)->next;
tmp->next = *ptr;
*ptr = tmp;
if (inc)
parent->children = g_slist_reverse (parent->children);
if (parent->children->data != obj) {
for (tmp = parent->children ; tmp->next->data != obj ; )
tmp = tmp->next;
obj_follows = tmp->data;
} else
obj_follows = NULL;
/* Pass the sibling that precedes obj, or NULL if is the head */
g_signal_emit (G_OBJECT (parent),
gog_object_signals [CHILDREN_REORDERED], 0);
gog_object_emit_changed (parent, TRUE);
return obj_follows;
}
/**
* gog_object_get_editor :
* @obj : #GogObject
* @dalloc : #GogDataAllocator
* @cc : #GnmCmdContext
*
**/
gpointer
gog_object_get_editor (GogObject *obj, GogDataAllocator *dalloc,
GnmCmdContext *cc)
{
GogObjectClass *klass = GOG_OBJECT_GET_CLASS (obj);
g_return_val_if_fail (klass != NULL, NULL);
if (klass->editor) {
/* If there are pending updates do them before creating the editor
* to avoid expensive widget changes later */
gog_graph_force_update (gog_object_get_graph (obj));
return (*klass->editor) (obj, dalloc, cc);
}
return NULL;
}
/**
* gog_object_new_view :
* @obj : a #GogObject
* @data :
**/
GogView *
gog_object_new_view (GogObject const *obj, GogView *parent)
{
GogObjectClass *klass = GOG_OBJECT_GET_CLASS (obj);
g_return_val_if_fail (klass != NULL, NULL);
if (klass->view_type != 0)
/* set model before parent */
return g_object_new (klass->view_type,
"model", obj,
"parent", parent,
NULL);
return NULL;
}
void
gog_object_update (GogObject *obj)
{
GogObjectClass *klass = GOG_OBJECT_GET_CLASS (obj);
GSList *ptr;
g_return_if_fail (klass != NULL);
ptr = obj->children; /* depth first */
for (; ptr != NULL ; ptr = ptr->next)
gog_object_update (ptr->data);
if (obj->needs_update) {
obj->needs_update = FALSE;
obj->being_updated = TRUE;
gog_debug (0, g_warning ("updating %s (%p)", G_OBJECT_TYPE_NAME (obj), obj););
if (klass->update != NULL)
(*klass->update) (obj);
obj->being_updated = FALSE;
}
}
gboolean
gog_object_request_update (GogObject *obj)
{
GogGraph *graph;
g_return_val_if_fail (GOG_OBJECT (obj), FALSE);
g_return_val_if_fail (!obj->being_updated, FALSE);
if (obj->needs_update)
return FALSE;
graph = gog_object_get_graph (obj);
if (graph == NULL) /* we are not linked into a graph yet */
return FALSE;
gog_graph_request_update (graph);
obj->needs_update = TRUE;
return TRUE;
}
void
gog_object_emit_changed (GogObject *obj, gboolean resize)
{
GogObjectClass *gog_klass;
g_return_if_fail (GOG_OBJECT (obj));
gog_klass = GOG_OBJECT_GET_CLASS (obj);
if (gog_klass->use_parent_as_proxy) {
obj = obj->parent;
if (obj != NULL) {
g_return_if_fail (IS_GOG_OBJECT (obj));
gog_object_emit_changed (obj, resize);
}
return;
}
g_signal_emit (G_OBJECT (obj),
gog_object_signals [CHANGED], 0, resize);
}
/******************************************************************************/
/**
* gog_object_clear_parent :
* @obj : #GogObject
*
* Does _not_ unref the child, which in effect adds a ref by freeing up the ref
* previously associated with the parent.
**/
gboolean
gog_object_clear_parent (GogObject *obj)
{
GogObjectClass *klass = GOG_OBJECT_GET_CLASS (obj);
GogObject *parent;
g_return_val_if_fail (GOG_OBJECT (obj), FALSE);
g_return_val_if_fail (obj->parent != NULL, FALSE);
g_return_val_if_fail (gog_object_is_deletable (obj), FALSE);
parent = obj->parent;
g_signal_emit (G_OBJECT (parent),
gog_object_signals [CHILD_REMOVED], 0, obj);
(*klass->parent_changed) (obj, FALSE);
if (obj->role != NULL && obj->role->pre_remove != NULL)
(obj->role->pre_remove) (parent, obj);
parent->children = g_slist_remove (parent->children, obj);
obj->parent = NULL;
if (obj->role != NULL && obj->role->post_remove != NULL)
(obj->role->post_remove) (parent, obj);
obj->role = NULL;
return TRUE;
}
/**
* gog_object_set_parent :
* @child : #GogObject.
* @parent : #GogObject.
* @id : optionally %NULL.
* @role : a static string that can be sent to @parent::add
*
* Absorbs a ref to @child
**/
gboolean
gog_object_set_parent (GogObject *child, GogObject *parent,
GogObjectRole const *role, char *id)
{
GogObjectClass *klass = GOG_OBJECT_GET_CLASS (child);
GSList **step;
g_return_val_if_fail (GOG_OBJECT (child), FALSE);
g_return_val_if_fail (child->parent == NULL, FALSE);
g_return_val_if_fail (role != NULL, FALSE);
child->parent = parent;
child->role = role;
child->position = role->default_position;
/* Insert sorted based on hokey little ordering */
step = &parent->children;
while (*step != NULL &&
gog_role_cmp_full (GOG_OBJECT ((*step)->data)->role, role) >= 0)
step = &((*step)->next);
*step = g_slist_prepend (*step, child);
g_free (child->id);
g_free (child->user_name);
child->id = (id != NULL) ? id : gog_object_generate_name (child);
if (child->id == NULL) child->id = g_strdup ("BROKEN");
if (role->post_add != NULL)
(role->post_add) (parent, child);
(*klass->parent_changed) (child, TRUE);
g_signal_emit (G_OBJECT (parent),
gog_object_signals [CHILD_ADDED], 0, child);
return TRUE;
}
GogObject *
gog_object_add_by_role (GogObject *parent, GogObjectRole const *role, GogObject *child)
{
GType is_a;
gboolean const explicitly_typed_role = (child != NULL);
g_return_val_if_fail (role != NULL, NULL);
g_return_val_if_fail (GOG_OBJECT (parent) != NULL, NULL);
is_a = g_type_from_name (role->is_a_typename);
g_return_val_if_fail (is_a != 0, NULL);
if (child == NULL)
child = (role->allocate)
? (role->allocate) (parent)
: g_object_new (is_a, NULL);
g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (child, is_a), NULL);
child->explicitly_typed_role = explicitly_typed_role;
if (gog_object_set_parent (child, parent, role, NULL))
return child;
g_object_unref (child);
return NULL;
}
/**
* gog_object_add_by_name :
* @parent : #GogObject
* @role :
* @child : optionally null #GogObject
*
* Returns a newly created child of @parent in @role. If @child is provided,
* it is assumed to be an unaffiliated object that will be assigned in @role.
* On failure return NULL.
**/
GogObject *
gog_object_add_by_name (GogObject *parent,
char const *role, GogObject *child)
{
return gog_object_add_by_role (parent,
gog_object_find_role_by_name (parent, role), child);
}
GogObjectPosition
gog_object_get_pos (GogObject const *obj)
{
g_return_val_if_fail (GOG_OBJECT (obj) != NULL, GOG_POSITION_SPECIAL);
return obj->position;
}
/**
* gog_object_set_pos :
* @obj : #GogObject
* @pos : #GogObjectPosition
*
* Attempts to set the position of @obj to @pos.
* Returns TRUE the new position is permitted.
**/
gboolean
gog_object_set_pos (GogObject *obj, GogObjectPosition pos)
{
g_return_val_if_fail (GOG_OBJECT (obj) != NULL, FALSE);
g_return_val_if_fail (obj->role != NULL, FALSE);
if (obj->position == pos)
return TRUE;
if ((obj->role->allowable_positions & pos) !=
(pos & (GOG_POSITION_COMPASS|GOG_POSITION_ANY_MANUAL)))
return FALSE;
obj->position = pos;
gog_object_emit_changed (obj, TRUE);
return TRUE;
}
GogObjectRole const *
gog_object_find_role_by_name (GogObject const *obj, char const *role)
{
GogObjectClass *klass = GOG_OBJECT_GET_CLASS (obj);
g_return_val_if_fail (klass != NULL, NULL);
return g_hash_table_lookup (klass->roles, role);
}
void
gog_object_register_roles (GogObjectClass *klass,
GogObjectRole const *roles, unsigned n_roles)
{
unsigned i;
if (klass->roles == NULL)
klass->roles = g_hash_table_new (g_str_hash, g_str_equal);
for (i = 0 ; i < n_roles ; i++) {
g_return_if_fail (g_hash_table_lookup (klass->roles,
(gpointer )roles[i].id) == NULL);
g_hash_table_replace (klass->roles,
(gpointer )roles[i].id, (gpointer) (roles + i));
}
}