/* This is -*- C -*- */
/* vim: set sw=2: */
/* $Id: guppi-attribute-bag.c,v 1.11 2002/01/08 23:19:19 trow Exp $ */
/*
* guppi-attribute-bag.c
*
* Copyright (C) 2001 The Free Software Foundation
*
* Developed by Jon Trowbridge <trow@gnu.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* 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 <config.h>
#include "guppi-attribute-bag.h"
#include <ctype.h>
#include <string.h>
#include <glib.h>
#include <gtk/gtksignal.h>
#include <gtk/gtkmain.h>
#include <guppi-memory.h>
#include <gnome-xml/xmlmemory.h>
#include "guppi-attribute-flavor-private.h"
static GtkObjectClass *parent_class = NULL;
enum {
ADDED,
CHANGED,
LAST_SIGNAL
};
static guint guppi_attribute_bag_signals[LAST_SIGNAL] = { 0 };
typedef struct _GuppiForwardedSignalInfo GuppiForwardedSignalInfo;
typedef struct _GuppiAttribute GuppiAttribute;
struct _GuppiForwardedSignalInfo {
GuppiAttribute *attr;
guint tag;
gboolean queue;
guint queued_tag;
};
struct _GuppiAttribute {
gchar *key;
gchar *description;
GuppiAttributeFlavorInfo *info;
gpointer data;
gboolean has_default;
gpointer default_data;
GList *signals;
GuppiAttributeBag *bag;
};
struct _GuppiAttributeBagPrivate {
GHashTable *attr_hash;
GList *attr_list;
};
static GuppiAttribute *
guppi_attribute_new (const gchar *key, const gchar *desc, GuppiAttributeFlavor flavor)
{
GuppiAttribute *attr;
GuppiAttributeFlavorInfo *info;
info = guppi_attribute_flavor_get_info (flavor);
if (info == NULL)
return NULL;
g_assert (info->flavor == flavor);
attr = guppi_new0 (GuppiAttribute, 1);
attr->key = guppi_strdup (key);
attr->description = guppi_strdup (desc);
attr->info = info;
return attr;
}
static gint
queued_signal_cb (gpointer closure)
{
GuppiForwardedSignalInfo *info = closure;
gtk_signal_emit (GTK_OBJECT (info->attr->bag), guppi_attribute_bag_signals[CHANGED], info->attr->key);
info->queued_tag = 0;
return 0;
}
/* We ignore the signature of whatever signal we are capturing, and just forward
a changed signal for the appropriate key. To make this work, though, we need to
do this bit of callback marshalling magic... */
static void
attribute_callback_marshal_cb (GtkObject *obj, gpointer data, guint n_args, GtkArg *args)
{
GuppiForwardedSignalInfo *info = data;
if (info->queue) {
if (info->queued_tag == 0) {
info->queued_tag = gtk_idle_add (queued_signal_cb, info);
}
} else {
gtk_signal_emit (GTK_OBJECT (info->attr->bag), guppi_attribute_bag_signals[CHANGED], info->attr->key);
}
}
static void
guppi_attribute_attach_signals (GuppiAttribute *attr, GuppiAttributeBag *bag)
{
GList *iter = attr->info->signals_to_forward;
if (attr->data == NULL)
return;
while (iter != NULL) {
GuppiSignalsToForward *stf = iter->data;
GuppiForwardedSignalInfo *info = guppi_new0 (GuppiForwardedSignalInfo, 1);
info->attr = attr;
info->queue = stf->queue;
info->tag = gtk_signal_connect_full (GTK_OBJECT (attr->data),
(gchar *) stf->name,
NULL,
attribute_callback_marshal_cb,
info,
NULL, FALSE, FALSE);
attr->signals = g_list_prepend (attr->signals, info);
iter = g_list_next (iter);
}
}
static void
guppi_attribute_detatch_signals (GuppiAttribute *attr)
{
GList *iter = attr->signals;
if (attr->data == NULL)
return;
while (iter != NULL) {
GuppiForwardedSignalInfo *info = iter->data;
gtk_signal_disconnect (GTK_OBJECT (attr->data), info->tag);
if (info->queued_tag != 0) {
gtk_idle_remove (info->queued_tag);
info->queued_tag = 0;
guppi_free (info);
}
iter = g_list_next (iter);
}
g_list_free (attr->signals);
attr->signals = NULL;
}
static void
guppi_attribute_free (GuppiAttribute *attr)
{
if (attr != NULL) {
guppi_free0 (attr->key);
guppi_free0 (attr->description);
guppi_attribute_detatch_signals (attr);
if (attr->info->destroy) {
if (attr->has_default)
attr->info->destroy (attr->default_data);
attr->info->destroy (attr->data);
}
guppi_free0 (attr);
}
}
static void
guppi_attribute_bag_finalize (GtkObject *obj)
{
GuppiAttributeBag *x = GUPPI_ATTRIBUTE_BAG(obj);
GList *iter;
for (iter = x->priv->attr_list; iter != NULL; iter = g_list_next (iter)) {
guppi_attribute_free (iter->data);
}
g_list_free (x->priv->attr_list);
g_hash_table_destroy (x->priv->attr_hash);
g_free (x->priv);
x->priv = NULL;
guppi_finalized (obj);
if (parent_class->finalize)
parent_class->finalize (obj);
}
static void
guppi_attribute_bag_class_init (GuppiAttributeBagClass *klass)
{
GtkObjectClass *object_class = (GtkObjectClass *)klass;
parent_class = gtk_type_class (GTK_TYPE_OBJECT);
object_class->finalize = guppi_attribute_bag_finalize;
guppi_attribute_bag_signals[ADDED] =
gtk_signal_new ("added",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (GuppiAttributeBagClass, added),
gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0);
guppi_attribute_bag_signals[CHANGED] =
gtk_signal_new ("changed",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (GuppiAttributeBagClass, changed),
gtk_marshal_NONE__POINTER,
GTK_TYPE_NONE, 1, GTK_TYPE_POINTER);
gtk_object_class_add_signals (object_class, guppi_attribute_bag_signals, LAST_SIGNAL);
}
static void
guppi_attribute_bag_init (GuppiAttributeBag *obj)
{
obj->priv = g_new0 (GuppiAttributeBagPrivate, 1);
obj->priv->attr_hash = g_hash_table_new (g_str_hash, g_str_equal);
}
GtkType
guppi_attribute_bag_get_type (void)
{
static GtkType guppi_attribute_bag_type = 0;
if (!guppi_attribute_bag_type) {
static const GtkTypeInfo guppi_attribute_bag_info = {
"GuppiAttributeBag",
sizeof (GuppiAttributeBag),
sizeof (GuppiAttributeBagClass),
(GtkClassInitFunc)guppi_attribute_bag_class_init,
(GtkObjectInitFunc)guppi_attribute_bag_init,
NULL, NULL, (GtkClassInitFunc)NULL
};
guppi_attribute_bag_type = gtk_type_unique (GTK_TYPE_OBJECT, &guppi_attribute_bag_info);
}
return guppi_attribute_bag_type;
}
GuppiAttributeBag *
guppi_attribute_bag_new (void)
{
return GUPPI_ATTRIBUTE_BAG (guppi_type_new (guppi_attribute_bag_get_type ()));
}
/* This should be optimized at some point. */
GuppiAttributeBag *
guppi_attribute_bag_copy (GuppiAttributeBag *src)
{
GuppiAttributeBag *copy;
xmlNodePtr node;
GuppiXMLDocument *doc;
g_return_val_if_fail (GUPPI_IS_ATTRIBUTE_BAG (src), NULL);
doc = guppi_xml_document_new ();
node = guppi_attribute_bag_export_xml (src, doc);
copy = guppi_attribute_bag_new ();
if (!guppi_attribute_bag_import_xml (copy, doc, node)) {
guppi_unref0 (copy);
}
guppi_xml_document_free (doc);
xmlFreeNode (node);
return copy;
}
static void
register_attribute (GuppiAttributeBag *bag, GuppiAttribute *attr)
{
g_hash_table_insert (bag->priv->attr_hash, attr->key, attr);
bag->priv->attr_list = g_list_append (bag->priv->attr_list, attr);
attr->bag = bag;
gtk_signal_emit (GTK_OBJECT (bag), guppi_attribute_bag_signals[ADDED], attr->key);
}
static gboolean
check_key (const gchar *str)
{
gint count = 0;
while (*str && count < 60) {
if (iscntrl ((gint) *str))
return FALSE;
if (((guint) *str) >= 128)
return FALSE;
++str;
++count;
}
return *str == '\0';
}
static const gchar *
get_subkey (const gchar *key, gchar **base_key)
{
gchar *colon;
colon = strchr (key, ':');
if (colon && *(colon+1) == ':' && *(colon+2)) {
*base_key = guppi_strndup (key, colon - key);
return colon+2;
}
*base_key = guppi_strdup (key);
return NULL;
}
void
guppi_attribute_bag_add (GuppiAttributeBag *bag,
GuppiAttributeFlavor flavor,
const gchar *key,
const gchar *description)
{
GuppiAttribute *attr;
gchar *base_key;
g_return_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag));
g_return_if_fail (flavor >= 0);
g_return_if_fail (key && *key);
get_subkey (key, &base_key);
attr = guppi_attribute_new (base_key, description, flavor);
guppi_free (base_key);
if (attr == NULL) {
g_message ("Bad flavor (%d)", flavor);
return;
}
attr->data = attr->info->create_default ();
register_attribute (bag, attr);
}
void
guppi_attribute_bag_add_with_default (GuppiAttributeBag *bag,
GuppiAttributeFlavor flavor,
const gchar *key,
const gchar *description,
...)
{
GuppiAttribute *attr;
const gchar *subkey;
gchar *base_key;
va_list args;
g_return_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag));
g_return_if_fail (flavor >= 0);
g_return_if_fail (key && *key);
subkey = get_subkey (key, &base_key);
attr = guppi_attribute_new (base_key, description, flavor);
if (attr == NULL) {
g_message ("Bad flavor (%d)", flavor);
return;
}
g_assert (attr->info->va2p);
g_assert (attr->info->copy);
va_start (args, description);
attr->has_default = TRUE;
attr->info->va2p (base_key, subkey, &args, &attr->default_data);
guppi_free (base_key);
attr->data = attr->info->copy (attr->default_data);
va_end (args);
guppi_attribute_attach_signals (attr, bag);
register_attribute (bag, attr);
}
static GuppiAttribute *
get_by_key (GuppiAttributeBag *bag, const gchar *key)
{
return (GuppiAttribute *) g_hash_table_lookup (bag->priv->attr_hash, key);
}
gboolean
guppi_attribute_bag_contains (GuppiAttributeBag *bag,
const gchar *key)
{
g_return_val_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag), FALSE);
g_return_val_if_fail (key && *key, FALSE);
return guppi_attribute_bag_get_flavor (bag, key) >= 0;
}
GuppiAttributeFlavor
guppi_attribute_bag_get_flavor (GuppiAttributeBag *bag,
const gchar *key)
{
GuppiAttribute *attr;
g_return_val_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag), -1);
g_return_val_if_fail (key && *key, -1);
attr = g_hash_table_lookup (bag->priv->attr_hash, key);
return attr && attr->info ? attr->info->flavor : -1;
}
void
guppi_attribute_bag_foreach (GuppiAttributeBag *bag,
GuppiAttributeBagFn fn,
gpointer closure)
{
GList *iter;
g_return_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag));
if (fn == NULL)
return;
for (iter = bag->priv->attr_list; iter != NULL; iter = g_list_next (iter)) {
GuppiAttribute *attr = iter->data;
fn (bag, attr->key, closure);
}
}
gboolean
guppi_attribute_bag_get1 (GuppiAttributeBag *bag, const gchar *key, gpointer dest)
{
GuppiAttribute *attr;
const gchar *subkey;
gchar *base_key;
g_return_val_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag), FALSE);
g_return_val_if_fail (key != NULL, FALSE);
g_return_val_if_fail (dest != NULL, FALSE);
if (!check_key (key)) {
g_warning ("suspicious key");
}
if (GPOINTER_TO_UINT (dest) & 3) {
g_warning ("writing value of '%s' to unaligned pointer", key);
}
subkey = get_subkey (key, &base_key);
attr = get_by_key (bag, base_key);
if (attr == NULL) {
g_message ("Unknown property bag key '%s'", key);
guppi_free (base_key);
return FALSE;
}
attr->info->p2va (base_key, subkey, attr->data, dest);
guppi_free (base_key);
return TRUE;
}
gboolean
guppi_attribute_bag_vget (GuppiAttributeBag *bag, va_list varargs)
{
const gchar *key;
gpointer dest;
g_return_val_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag), FALSE);
do {
key = va_arg (varargs, const gchar *);
if (key != NULL) {
dest = va_arg (varargs, gpointer);
if (! guppi_attribute_bag_get1 (bag, key, dest)) {
return FALSE;
}
}
} while (key != NULL);
return TRUE;
}
gboolean
guppi_attribute_bag_get (GuppiAttributeBag *bag, ...)
{
va_list varargs;
gboolean rv;
g_return_val_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag), FALSE);
va_start (varargs, bag);
rv = guppi_attribute_bag_vget (bag, varargs);
va_end (varargs);
return rv;
}
static void
restore_default (GuppiAttributeBag *bag, GuppiAttribute *attr)
{
gpointer new_data;
gboolean changed = FALSE;
g_assert (attr);
if (attr->has_default) {
new_data = attr->info->copy (attr->default_data);
} else {
new_data = attr->info->create_default ();
}
guppi_attribute_detatch_signals (attr);
if (attr->info->getp) {
changed = attr->info->getp (attr->key, "_default", new_data, &attr->data);
} else {
if (!attr->info->equality (attr->data, new_data)) {
gpointer old_data = attr->data;
attr->data = new_data;
attr->info->destroy (old_data);
changed = TRUE;
} else {
attr->info->destroy (new_data);
}
}
guppi_attribute_attach_signals (attr, bag);
if (changed) {
gtk_signal_emit (GTK_OBJECT (bag), guppi_attribute_bag_signals[CHANGED], attr->key);
}
}
gboolean
guppi_attribute_bag_vset1 (GuppiAttributeBag *bag, const gchar *key, va_list *varargs)
{
GuppiAttribute *attr;
gchar *base_key;
const gchar *subkey;
gboolean changed;
g_return_val_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag), FALSE);
g_return_val_if_fail (key != NULL, FALSE);
g_return_val_if_fail (varargs != NULL, FALSE);
if (! check_key (key)) {
g_warning ("suspicious key");
}
subkey = get_subkey (key, &base_key);
attr = get_by_key (bag, key);
if (attr == NULL) {
guppi_free (base_key);
g_warning ("Unknown property bag key '%s'", key);
return FALSE;
}
guppi_attribute_detatch_signals (attr);
changed = attr->info->va2p (base_key, subkey, varargs, &attr->data);
guppi_attribute_attach_signals (attr, bag);
if (changed)
gtk_signal_emit (GTK_OBJECT (bag), guppi_attribute_bag_signals[CHANGED], attr->key);
guppi_free (base_key);
return TRUE;
}
gboolean
guppi_attribute_bag_vset (GuppiAttributeBag *bag, va_list *varargs)
{
const gchar *key;
g_return_val_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag), FALSE);
g_return_val_if_fail (varargs != NULL, FALSE);
do {
key = va_arg (*varargs, const gchar *);
if (key) {
if (! guppi_attribute_bag_vset1 (bag, key, varargs)) {
return FALSE;
}
}
} while (key != NULL);
return TRUE;
}
gboolean
guppi_attribute_bag_set (GuppiAttributeBag *bag, ...)
{
va_list varargs;
gboolean rv;
g_return_val_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag), FALSE);
va_start (varargs, bag);
rv = guppi_attribute_bag_vset (bag, &varargs);
va_end (varargs);
return rv;
}
void
guppi_attribute_bag_restore_default (GuppiAttributeBag *bag, const gchar *key)
{
GuppiAttribute *attr;
g_return_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag));
g_return_if_fail (key && *key);
attr = get_by_key (bag, key);
g_return_if_fail (attr != NULL);
g_return_if_fail (attr->has_default);
restore_default (bag, attr);
}
void
guppi_attribute_bag_restore_all_defaults (GuppiAttributeBag *bag)
{
GList *iter;
g_return_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag));
for (iter = bag->priv->attr_list; iter != NULL; iter = g_list_next (iter)) {
restore_default (bag, (GuppiAttribute *) iter->data);
}
}
void
guppi_attribute_bag_dump (GuppiAttributeBag *bag)
{
GList *iter;
g_return_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag));
putchar ('\n');
for (iter = bag->priv->attr_list; iter != NULL; iter = g_list_next (iter)) {
GuppiAttribute *attr = iter->data;
gchar *str;
str = attr->info->export_string ? attr->info->export_string (attr->data) : g_strdup ("--- N/A ---");
printf ("%12s %-8s [%s]\n",
attr->key,
attr->info->name,
str);
guppi_free (str);
}
putchar ('\n');
}
xmlNodePtr
guppi_attribute_bag_export_xml (GuppiAttributeBag *bag, GuppiXMLDocument *doc)
{
xmlNodePtr bag_node;
GList *iter;
g_return_val_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag), NULL);
g_return_val_if_fail (doc != NULL, NULL);
bag_node = xmlNewNode (doc->ns, "AttributeBag");
for (iter = bag->priv->attr_list; iter != NULL; iter = g_list_next (iter)) {
GuppiAttribute *attr = iter->data;
if (!attr->has_default || !attr->info->equality (attr->data, attr->default_data)) {
xmlNodePtr attr_node = xmlNewNode (doc->ns, "Attribute");
xmlNewProp (attr_node, "key", attr->key);
xmlNewProp (attr_node, "type", attr->info->name);
if (attr->info->export_string) {
gchar *str = attr->info->export_string (attr->data);
xmlAddChild (attr_node, xmlNewText (str));
guppi_free (str);
} else if (attr->info->export_xml) {
xmlNodePtr data_node = attr->info->export_xml (doc, attr->data);
xmlAddChild (attr_node, data_node);
} else {
g_assert_not_reached ();
}
xmlAddChild (bag_node, attr_node);
}
}
return bag_node;
}
gboolean
guppi_attribute_bag_import_xml (GuppiAttributeBag *bag, GuppiXMLDocument *doc, xmlNodePtr node)
{
g_return_val_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag), FALSE);
g_return_val_if_fail (doc != NULL, FALSE);
g_return_val_if_fail (node != NULL, FALSE);
if (strcmp (node->name, "AttributeBag"))
return FALSE;
guppi_attribute_bag_restore_all_defaults (bag);
for (node = node->xmlChildrenNode; node != NULL; node = node->next) {
gchar *key = NULL;
gchar *type = NULL;
GuppiAttribute *attr = NULL;
gboolean success = FALSE;
xmlNodePtr value_node = NULL;
if (!strcmp (node->name, "Attribute")) {
key = xmlGetProp (node, "key");
type = xmlGetProp (node, "type");
if (key && type) {
attr = get_by_key (bag, key);
if (attr) {
if (!strcmp (attr->info->name, type)) {
value_node = node->xmlChildrenNode;
if (attr->info->import_string) {
gchar *str = xmlNodeGetContent (value_node);
success = attr->info->import_string (str, &attr->data);
xmlFree (str);
} else if (attr->info->import_xml) {
success = attr->info->import_xml (doc, value_node, &attr->data);
} else {
g_assert_not_reached ();
}
if (!success) {
g_warning ("Couldn't parse data for %s/%s", key, type);
}
} else {
g_warning ("type mismatch (%s vs %s) in %s", type, attr->info->name, key);
}
}
}
xmlFree (key);
xmlFree (type);
}
}
return TRUE;
}
void
guppi_attribute_bag_spew_xml (GuppiAttributeBag *bag)
{
GuppiXMLDocument *doc;
g_return_if_fail (GUPPI_IS_ATTRIBUTE_BAG (bag));
doc = guppi_xml_document_new ();
guppi_xml_document_set_root (doc, guppi_attribute_bag_export_xml (bag, doc));
guppi_xml_document_spew (doc);
guppi_xml_document_free (doc);
}
syntax highlighted by Code2HTML, v. 0.9.1