/* 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 * * 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 #include "guppi-attribute-bag.h" #include #include #include #include #include #include #include #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); }