/* This is -*- C -*- */
/* vim: set sw=2: */
/* $Id: guppi-attribute-flavor.c,v 1.12 2002/01/08 06:31:09 trow Exp $ */

/*
 * guppi-attribute-flavor.c
 *
 * Copyright (C) 2001 The Free Software Foundation, Inc.
 *
 * 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 <locale.h>
#include <stdlib.h>
#include "guppi-attribute-flavor.h"

#include <gtk/gtk.h>
#include "guppi-attribute-flavor-private.h"
#include "guppi-attribute-widget.h"
#include "guppi-metric-entry.h"
#include "guppi-defaults.h"
#include "guppi-memory.h"
#include "guppi-rgb.h"

static guint flavor_count = 0;
static guint flavor_bufsize = 0;
static GuppiAttributeFlavorInfo **flavor_info_buffer = NULL;
static GHashTable *flavor_name_hash = NULL;

static GuppiAttributeFlavor
register_info (GuppiAttributeFlavorInfo *info)
{
  g_assert (info->flavor < 0);
  g_assert (info->name && *info->name);

  g_assert (info->create_default != NULL);
  g_assert (info->destroy != NULL);
  g_assert (info->copy != NULL);
  g_assert (info->va2p != NULL);
  g_assert (info->p2va != NULL);

  if (flavor_count >= flavor_bufsize) {
    if (flavor_bufsize > 0) {
      flavor_bufsize = 2*flavor_bufsize;
      flavor_info_buffer = guppi_renew (GuppiAttributeFlavorInfo *, flavor_info_buffer, flavor_bufsize);
    } else {
      flavor_bufsize = 64;
      flavor_info_buffer = guppi_new (GuppiAttributeFlavorInfo *, flavor_bufsize);
    }
  }

  if (flavor_name_hash == NULL) {
    flavor_name_hash = g_hash_table_new (g_str_hash, g_str_equal);
  }

  if (g_hash_table_lookup (flavor_name_hash, info->name) != NULL) {
    g_warning ("Name collision for attribute flavor '%s'", info->name);
    return (GuppiAttributeFlavor)-1;
  }

  info->flavor = flavor_count;
  flavor_info_buffer[info->flavor] = info;
  g_hash_table_insert (flavor_name_hash, info->name, info);

  ++flavor_count;

  return info->flavor;
}

GuppiAttributeFlavor
guppi_attribute_flavor_register (const gchar *name,
				 AttributeCreateDefaultFn create_default,
				 AttributeDestroyFn destroy,
				 AttributeCopyFn copy,
				 AttributeEqualityFn equality,
				 AttributeGetFromPointerFn getp,
				 AttributeGetFromVarArgsFn va2p,
				 AttributePutToVarArgsFn p2va)
{
  GuppiAttributeFlavorInfo *info = g_new0 (GuppiAttributeFlavorInfo, 1);

  info->flavor         = -1;
  info->name           = g_strdup (name);
  info->create_default = create_default;
  info->destroy        = destroy;
  info->copy           = copy;
  info->equality       = equality;
  info->getp           = getp;
  info->va2p           = va2p;
  info->p2va           = p2va;
  
  return register_info (info);
}


/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static gpointer
object_create_default (void)
{
  return NULL;
}

static void
object_destroy (gpointer ptr)
{
  guppi_unref (ptr);
}

static gpointer
object_copy (gpointer ptr)
{
  guppi_ref (ptr);
  return ptr;
}

static gboolean
object_equality (gpointer a, gpointer b)
{
  return a == b;
}

static gboolean
object_va2p (const gchar *key, const gchar *subkey, va_list *va, gpointer *storage)
{
  GtkObject *obj = va_arg (*va, GtkObject *);
  if (obj != *storage) {
    guppi_unref (*storage);
    *storage = obj;
    if (subkey == NULL || !strcmp (subkey, "ref")) {
      guppi_ref (obj);
    } else if (strcmp (subkey, "adopt")) {
      g_warning ("Unknown subkey %s::%s", key, subkey);
      guppi_ref (obj); /* also ref in this case */
    }
    return TRUE;
  }
  return FALSE;
}

static void
object_p2va (const gchar *key, const gchar *subkey, gpointer ptr, gpointer dest)
{
  *(GtkObject **) dest = ptr;
  if (subkey == NULL || !strcmp (subkey, "ref")) {
    guppi_ref (ptr);
  } else if (strcmp (subkey, "raw")) {
    g_warning ("Unknown subkey %s::%s", key, subkey);
    guppi_ref (ptr); /* also ref in this case */
  }
}

GuppiAttributeFlavor
guppi_attribute_flavor_register_object_semantics (const gchar *name,
						  AttributeCreateDefaultFn create_default,
						  AttributeCopyFn copy,
						  AttributeEqualityFn equality)
{
  return guppi_attribute_flavor_register (name,
					  create_default ? create_default : object_create_default,
					  object_destroy,
					  copy ? copy : object_copy,
					  equality ? equality : object_equality,
					  NULL,
					  object_va2p,
					  object_p2va);
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

void
guppi_attribute_flavor_add_widget (GuppiAttributeFlavor flavor,
				   AttributeWidgetFn widget_fn)
{
  GuppiAttributeFlavorInfo *info = guppi_attribute_flavor_get_info (flavor);

  g_assert (info != NULL);
  
  g_assert (widget_fn != NULL);
  g_assert (info->widget == NULL);

  info->widget = widget_fn;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

void
guppi_attribute_flavor_add_string_serialization (GuppiAttributeFlavor flavor,
						 AttributeExportStringFn export_str,
						 AttributeImportStringFn import_str)
{
  GuppiAttributeFlavorInfo *info = guppi_attribute_flavor_get_info (flavor);

  g_assert (info != NULL);
  
  g_assert (export_str != NULL);
  g_assert (import_str != NULL);

  g_assert (info->export_string == NULL);
  g_assert (info->import_string == NULL);

  info->export_string = export_str;
  info->import_string = import_str;
}

void
guppi_attribute_flavor_add_xml_serialization (GuppiAttributeFlavor flavor,
					      AttributeExportXMLFn export_xml,
					      AttributeImportXMLFn import_xml)
{
  GuppiAttributeFlavorInfo *info = guppi_attribute_flavor_get_info (flavor);

  g_assert (info != NULL);
  
  g_assert (export_xml != NULL);
  g_assert (import_xml != NULL);

  g_assert (info->export_xml == NULL);
  g_assert (info->import_xml == NULL);

  info->export_xml = export_xml;
  info->import_xml = import_xml;
}

void
guppi_attribute_flavor_add_signal_to_forward (GuppiAttributeFlavor flavor,
					      const gchar *signal_name,
					      gboolean queue_signal_emissions)
{
  GuppiAttributeFlavorInfo *info = guppi_attribute_flavor_get_info (flavor);
  GuppiSignalsToForward *stf;

  g_assert (info != NULL);

  stf = guppi_new0 (GuppiSignalsToForward, 1);
  stf->name = guppi_strdup (signal_name);
  stf->queue = queue_signal_emissions;

  guppi_permanent_alloc (stf);
  guppi_permanent_alloc (stf->name);

  info->signals_to_forward = g_list_prepend (info->signals_to_forward, stf);
}

GuppiAttributeFlavor
guppi_attribute_flavor_get_from_name (const gchar *name)
{
  GuppiAttributeFlavorInfo *info = NULL;

  if (name && *name)
    info = g_hash_table_lookup (flavor_name_hash, name);

  return info ? info->flavor : (GuppiAttributeFlavor)-1;
}

const gchar *
guppi_attribute_flavor_get_name (GuppiAttributeFlavor flavor)
{
  if (0 <= flavor && flavor < flavor_count)
    return flavor_info_buffer[flavor]->name;
  else
    return "[UnknownFlavor]";
}

GtkWidget *
guppi_attribute_flavor_get_widget (GuppiAttributeFlavor flavor, const gchar *key)
{
  if (0 <= flavor && flavor < flavor_count && flavor_info_buffer[flavor]->widget)
    return flavor_info_buffer[flavor]->widget (key);
  else
    return gtk_label_new ("N/A");
}

GtkWidget *
guppi_attr_glade (const gchar *label, const gchar *type, const gchar *key)
{
  GuppiAttributeFlavor flavor = guppi_attribute_flavor_get_from_name (type);
  g_return_val_if_fail (flavor != (GuppiAttributeFlavor)-1, NULL);
  return guppi_attribute_flavor_get_widget (flavor, key);
}

GuppiAttributeFlavorInfo *
guppi_attribute_flavor_get_info (GuppiAttributeFlavor flavor)
{
  if (0 <= flavor && flavor < flavor_count)
    return flavor_info_buffer[flavor];
  else
    return NULL;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static gpointer
boolean_create_default (void)
{
  return NULL;
}

static void
boolean_destroy (gpointer ptr)
{
  /* do nothing */
}

static gpointer
boolean_copy (gpointer ptr)
{
  return ptr;
}

static gboolean
boolean_equality (gpointer a, gpointer b)
{
  return a != NULL ? b != NULL : b == NULL;
}

static gboolean
boolean_va2p (const gchar *key, const gchar *subkey, va_list *va, gpointer *storage)
{
  gpointer old_data = *storage;
  *storage = GINT_TO_POINTER (va_arg (*va, gint));
  return old_data != *storage;
}

static void
boolean_p2va (const gchar *key, const gchar *subkey,gpointer ptr, gpointer dest)
{
  *(gboolean *)dest = (gboolean) GPOINTER_TO_INT (ptr);
}

static gboolean
boolean_str_imp (const gchar *str, gpointer *p)
{
  *p = GINT_TO_POINTER ( (gint) (str && (*str == 't' || *str == 'T')));
  return TRUE;
}

static gchar *
boolean_str_exp (gpointer ptr)
{
  return guppi_strdup (((gboolean) GPOINTER_TO_INT (ptr)) ? "true" : "false");
}

static void
boolean_bag_to_widget_cb (GuppiAttributeWidget *gaw, gpointer closure)
{
  GtkToggleButton *tb = GTK_TOGGLE_BUTTON (closure);
  gboolean val;
  guppi_attribute_widget_bag_get (gaw, NULL, &val);
  gtk_toggle_button_set_active (tb, val);
}

static void
boolean_toggled_cb (GtkToggleButton *tb, gpointer closure)
{
  GuppiAttributeWidget *gaw = GUPPI_ATTRIBUTE_WIDGET (closure);
  gboolean val = gtk_toggle_button_get_active (tb);
  guppi_attribute_widget_bag_set (gaw, NULL, val);
}

static GtkWidget *
boolean_widget (const gchar *key)
{
  GtkWidget *gaw = guppi_attribute_widget_new (guppi_attribute_flavor_boolean (), key);
  GtkWidget *w;

  w = gtk_check_button_new ();
  gtk_container_add (GTK_CONTAINER (gaw), w);
  gtk_widget_show (w);
  
  gtk_signal_connect (GTK_OBJECT (gaw),
		      "bag_to_widget",
		      GTK_SIGNAL_FUNC (boolean_bag_to_widget_cb),
		      w);
  gtk_signal_connect (GTK_OBJECT (w),
		      "toggled",
		      GTK_SIGNAL_FUNC (boolean_toggled_cb),
		      gaw);
  return gaw;
}

GuppiAttributeFlavor
guppi_attribute_flavor_boolean (void)
{
  static GuppiAttributeFlavor flavor = -1;

  if (flavor < 0) {

    flavor = guppi_attribute_flavor_register ("boolean",
					      boolean_create_default,
					      boolean_destroy,
					      boolean_copy,
					      boolean_equality,
					      NULL, 
					      boolean_va2p,
					      boolean_p2va);

    guppi_attribute_flavor_add_widget (flavor, boolean_widget);

    guppi_attribute_flavor_add_string_serialization (flavor,
						     boolean_str_exp,
						     boolean_str_imp);

    g_assert (flavor >= 0);
  }

  return flavor;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static gpointer
int_create_default (void)
{
  return NULL;
}

static void
int_destroy (gpointer foo)
{
  /* Do nothing */
}

static gpointer
int_copy (gpointer ptr)
{
  return ptr;
}

static gboolean
int_equality (gpointer a, gpointer b)
{
  return GPOINTER_TO_INT (a) == GPOINTER_TO_INT (b);
}

static gboolean
int_va2p (const gchar *key, const gchar *subkey, va_list *va, gpointer *storage)
{
  gpointer old_data = *storage;
  *storage = GINT_TO_POINTER (va_arg (*va, gint));
  return old_data != *storage;
}

static void
int_p2va (const gchar *key, const gchar *subkey, gpointer ptr, gpointer dest)
{
  *(gint *) dest = GPOINTER_TO_INT (ptr);
}

static gchar *
int_exp_str (gpointer ptr)
{
  return guppi_strdup_printf ("%d", GPOINTER_TO_INT (ptr));
}

static gboolean
int_imp_str (const gchar *str, gpointer *ptr)
{
  gint foo;
  if (sscanf (str, "%d", &foo) == 1) {
    *ptr = GINT_TO_POINTER (foo);
    return TRUE;
  }
  
  return FALSE;
}

static void
int_bag_to_widget_cb (GuppiAttributeWidget *gaw, gpointer closure)
{
  GtkEditable *w = GTK_EDITABLE (closure);
  gchar *str;
  gint val, pos=0;

  guppi_attribute_widget_bag_get (gaw, NULL, &val);
  str = guppi_strdup_printf ("%d", val);
  gtk_editable_delete_text (w, 0, -1);
  gtk_editable_insert_text (w, str, strlen (str), &pos);
  guppi_free (str);
}

static void
int_activate_cb (GtkEditable *w, gpointer closure)
{
  GuppiAttributeWidget *gaw = GUPPI_ATTRIBUTE_WIDGET (closure);
  gchar *str;
  gint val;
  str = gtk_editable_get_chars (w, 0, -1);
  val = atoi (str);
  guppi_attribute_widget_bag_set (gaw, NULL, val);
  g_free (str);
}

static GtkWidget *
int_widget (const gchar *key)
{
  GtkWidget *gaw = guppi_attribute_widget_new (guppi_attribute_flavor_int (), key);
  GtkWidget *w = gtk_entry_new ();
  
  gtk_container_add (GTK_CONTAINER (gaw), w);
  gtk_widget_show (w);

  gtk_signal_connect (GTK_OBJECT (gaw),
		      "bag_to_widget",
		      GTK_SIGNAL_FUNC (int_bag_to_widget_cb),
		      w);

  gtk_signal_connect (GTK_OBJECT (w),
		      "activate",
		      GTK_SIGNAL_FUNC (int_activate_cb),
		      gaw);

  return gaw;
}

GuppiAttributeFlavor
guppi_attribute_flavor_int (void)
{
  static GuppiAttributeFlavor flavor = -1;

  if (flavor < 0) {
    
    flavor = guppi_attribute_flavor_register ("int",
					      int_create_default,
					      int_destroy,
					      int_copy,
					      int_equality,
					      NULL,
					      int_va2p,
					      int_p2va);

    guppi_attribute_flavor_add_widget (flavor, int_widget);

    guppi_attribute_flavor_add_string_serialization (flavor,
						     int_exp_str,
						     int_imp_str);
					      
  }

  return flavor;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static gpointer
uint32_create_default (void)
{
  return NULL;
}

static void
uint32_destroy (gpointer foo)
{
  /* do nothing */
}

static gpointer
uint32_copy (gpointer ptr)
{
  return ptr;
}

static gboolean
uint32_equality (gpointer a, gpointer b)
{
  return GPOINTER_TO_UINT (a) == GPOINTER_TO_UINT (b);
}

static gboolean
uint32_va2p (const gchar *key, const gchar *subkey, va_list *va, gpointer *storage)
{
  gpointer old_data = *storage;
  *storage = GUINT_TO_POINTER ((guint32) va_arg (*va, gint));
  return old_data != *storage;
}

static void
uint32_p2va (const gchar *key, const gchar *subkey, gpointer ptr, gpointer dest)
{
  *(guint32 *) dest = GPOINTER_TO_UINT (ptr);
}

static gchar *
uint32_exp_str (gpointer ptr)
{
  return guppi_strdup_printf ("%u", GPOINTER_TO_UINT (ptr));
}

static gboolean
uint32_imp_str (const gchar *str, gpointer *ptr)
{
  guint32 foo;
  if (sscanf (str, "%u", &foo) == 1) {
    *ptr = GUINT_TO_POINTER (foo);
    return TRUE;
  }
  return FALSE;
}

static void
uint32_bag_to_widget_cb (GuppiAttributeWidget *gaw, gpointer closure)
{
  GtkEditable *w = GTK_EDITABLE (closure);
  gchar *str;
  guint32 val;
  gint pos=0;

  guppi_attribute_widget_bag_get (gaw, NULL, &val);
  str = guppi_strdup_printf ("%u", val);
  gtk_editable_delete_text (w, 0, -1);
  gtk_editable_insert_text (w, str, strlen (str), &pos);
  guppi_free (str);
}

static void
uint32_activate_cb (GtkEditable *w, gpointer closure)
{
  GuppiAttributeWidget *gaw = GUPPI_ATTRIBUTE_WIDGET (closure);
  gchar *str;
  guint32 val;
  str = gtk_editable_get_chars (w, 0, -1);
  val = (guint32) atoi (str);
  guppi_attribute_widget_bag_set (gaw, NULL, val);
  g_free (str);
}

static GtkWidget *
uint32_widget (const gchar *key)
{
  GtkWidget *gaw = guppi_attribute_widget_new (guppi_attribute_flavor_uint32 (), key);
  GtkWidget *w = gtk_entry_new ();
  
  gtk_container_add (GTK_CONTAINER (gaw), w);
  gtk_widget_show (w);

  gtk_signal_connect (GTK_OBJECT (gaw),
		      "bag_to_widget",
		      GTK_SIGNAL_FUNC (uint32_bag_to_widget_cb),
		      w);

  gtk_signal_connect (GTK_OBJECT (w),
		      "activate",
		      GTK_SIGNAL_FUNC (uint32_activate_cb),
		      gaw);

  return gaw;
}

GuppiAttributeFlavor
guppi_attribute_flavor_uint32 (void)
{
  static GuppiAttributeFlavor flavor = -1;

  if (flavor < 0) {

    flavor = guppi_attribute_flavor_register ("uint32",
					      uint32_create_default,
					      uint32_destroy,
					      uint32_copy,
					      uint32_equality,
					      NULL,
					      uint32_va2p,
					      uint32_p2va);

    guppi_attribute_flavor_add_widget (flavor, uint32_widget);

    guppi_attribute_flavor_add_string_serialization (flavor,
						     uint32_exp_str,
						     uint32_imp_str);

  }

  return flavor;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static gpointer
string_create_default (void)
{
  return guppi_strdup ("");
}

static void
string_destroy (gpointer ptr)
{
  guppi_free (ptr);
}

static gpointer
string_copy (gpointer ptr)
{
  return guppi_strdup ((gchar *) ptr);
}

static gboolean
string_equality (gpointer a, gpointer b)
{
  if (a == b)
    return TRUE;

  if (a == NULL || b == NULL)
    return FALSE;

  return !strcmp ((const gchar *) a, (const gchar *) b);
}

static gboolean
string_va2p (const gchar *key, const gchar *subkey, va_list *va, gpointer *storage)
{
  const gchar *str = va_arg (*va, const gchar *);
  gboolean changed = !string_equality ((gpointer) str, *storage);

  guppi_free (*storage);
    
  if (subkey == NULL || !strcmp (subkey, "dup")) {
    *storage = guppi_strdup (str);
  } else if (!strcmp (subkey, "adopt")) {
    *storage = (gpointer) str;
  } else {
    g_warning ("unknown subkey: '%s::%s'", key, subkey);
    *storage = guppi_strdup (str);
  }

  return changed;
}

static void
string_p2va (const gchar *key, const gchar *subkey, gpointer ptr, gpointer dest)
{
  gchar *str = (gchar *) ptr;

  if (subkey == NULL || !strcmp (subkey, "dup") || !strcmp (subkey, "_default")) {
      str = guppi_strdup (str);
  } else if (strcmp (subkey, "raw")) {
    g_warning ("unknown subkey: '%s::%s'", key, subkey);
    str = guppi_strdup (str); /* also dup in this case */
  }

  
  *(gchar **)dest = str;
}

static gchar *
string_exp_str (gpointer ptr)
{
  return guppi_strdup ((gchar *) ptr);
}

static gboolean
string_imp_str (const gchar *str, gpointer *ptr)
{
  guppi_free (*ptr);
  *ptr = guppi_strdup (str);
  return TRUE;
}

static void
string_bag_to_widget_cb (GuppiAttributeWidget *gaw, gpointer closure)
{
  GtkEditable *entry = GTK_EDITABLE (closure);
  gchar *str;
  gint pos = 0;
  guppi_attribute_widget_bag_get (gaw, NULL, &str);
  gtk_editable_delete_text (entry, 0, -1);
  gtk_editable_insert_text (entry, str, strlen (str), &pos);
  guppi_free (str);
}

static void
string_activate_cb (GtkEditable *w, gpointer closure)
{
  GuppiAttributeWidget *gaw = GUPPI_ATTRIBUTE_WIDGET (closure);
  gchar *str;
  str = gtk_editable_get_chars (w, 0, -1);
  guppi_attribute_widget_bag_set (gaw, NULL, str ? str : "");
  g_free (str);
}

static GtkWidget *
string_widget (const gchar *key)
{
  GtkWidget *gaw = guppi_attribute_widget_new (guppi_attribute_flavor_string (), key);
  GtkWidget *w;

  w = gtk_entry_new ();
  gtk_container_add (GTK_CONTAINER (gaw), w);
  gtk_widget_show (w);

  gtk_signal_connect (GTK_OBJECT (gaw),
		      "bag_to_widget",
		      GTK_SIGNAL_FUNC (string_bag_to_widget_cb),
		      w);

  gtk_signal_connect (GTK_OBJECT (w),
		      "activate",
		      GTK_SIGNAL_FUNC (string_activate_cb),
		      gaw);

  return gaw;
}

GuppiAttributeFlavor
guppi_attribute_flavor_string (void)
{
  static GuppiAttributeFlavor flavor = -1;

  if (flavor < 0) {

    flavor = guppi_attribute_flavor_register ("string",
					      string_create_default,
					      string_destroy,
					      string_copy,
					      string_equality,
					      NULL,
					      string_va2p,
					      string_p2va);

    guppi_attribute_flavor_add_widget (flavor, string_widget);

    guppi_attribute_flavor_add_string_serialization (flavor,
						     string_exp_str,
						     string_imp_str);

  }

  return flavor;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static gpointer
double_create_default (void)
{
  double *p = guppi_new (double, 1);
  *p = 0;
  return p;
}

static void
double_destroy (gpointer ptr)
{
  guppi_free (ptr);
}

static gpointer
double_copy (gpointer ptr)
{
  double *p = guppi_new (double, 1);
  *p = *(double *) ptr;
  return p;
}

static gboolean
double_equality (gpointer a, gpointer b)
{
  return *(double *) a == *(double *) b;
}

static gboolean
double_va2p (const gchar *key, const gchar *subkey, va_list *va, gpointer *storage)
{
  double x = *storage ? *(double *)*storage : 0.0;
  double y = va_arg (*va, double);

  guppi_free (*storage);
  *storage = guppi_new (double, 1);
  *(double *)*storage = y;
  return x != y;
}

static void
double_p2va (const gchar *key, const gchar *subkey, gpointer ptr, gpointer dest)
{
  *(double *) dest = *(double *) ptr;
}

static gchar *
double_exp_str (gpointer ptr)
{
  gchar *str;
  gchar *locale;
  
  locale = setlocale (LC_NUMERIC, "C");
  str = guppi_strdup_printf ("%g", *(double *) ptr);
  setlocale (LC_NUMERIC, locale);

  return str;
}

static gboolean
double_imp_str (const gchar *str, gpointer *ptr)
{
  gchar *locale;
  double *p;

  guppi_free (*ptr);

  locale = setlocale (LC_NUMERIC, "C");
  p = guppi_new (double, 1);
  *p = atof (str);
  *ptr = p;
  setlocale (LC_NUMERIC, locale);

  return TRUE;
}

GuppiAttributeFlavor
guppi_attribute_flavor_double (void)
{
  static GuppiAttributeFlavor flavor = -1;
  
  if (flavor < 0) {
    
    flavor = guppi_attribute_flavor_register ("double",
					      double_create_default,
					      double_destroy,
					      double_copy,
					      double_equality,
					      NULL,
					      double_va2p,
					      double_p2va);

    guppi_attribute_flavor_add_string_serialization (flavor,
						     double_exp_str,
						     double_imp_str);
  }

  return flavor;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static void
dimension_bag_to_widget_cb (GuppiAttributeWidget *gaw, gpointer closure)
{
  double pt;
  guppi_attribute_widget_bag_get (gaw, NULL, &pt);
  guppi_metric_entry_set_pt_value (GUPPI_METRIC_ENTRY (closure), pt);
}

static void
dimension_changed_cb (GuppiMetricEntry *w, gpointer closure)
{
  double pt = guppi_metric_entry_pt_value (w);
  guppi_attribute_widget_bag_set (GUPPI_ATTRIBUTE_WIDGET (closure), NULL, pt);
}

static GtkWidget *
dimension_widget (const gchar *key)
{
  GtkWidget *gaw;
  GtkWidget *w;

  gaw = guppi_attribute_widget_new (guppi_attribute_flavor_dimension (), key);
  w = guppi_metric_entry_new ();
  gtk_container_add (GTK_CONTAINER (gaw), w);
  gtk_widget_show (w);

  gtk_signal_connect (GTK_OBJECT (gaw),
		      "bag_to_widget",
		      GTK_SIGNAL_FUNC (dimension_bag_to_widget_cb),
		      w);

  gtk_signal_connect (GTK_OBJECT (w),
		      "changed_value",
		      GTK_SIGNAL_FUNC (dimension_changed_cb),
		      gaw);

  return gaw;
}

GuppiAttributeFlavor
guppi_attribute_flavor_dimension (void)
{
  static GuppiAttributeFlavor flavor = -1;
  
  if (flavor < 0) {
    
    flavor = guppi_attribute_flavor_register ("dimension",
					      double_create_default,
					      double_destroy,
					      double_copy,
					      double_equality,
					      NULL,
					      double_va2p,
					      double_p2va);

    guppi_attribute_flavor_add_widget (flavor, dimension_widget);

    guppi_attribute_flavor_add_string_serialization (flavor,
						     double_exp_str,
						     double_imp_str);
  }

  return flavor;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static gpointer
date_create_default (void)
{
  return g_date_new ();
}

static void
date_destroy (gpointer ptr)
{
  g_date_free ((GDate *) ptr);
}

static gpointer
date_copy (gpointer ptr)
{
  GDate *dt = g_date_new ();
  *dt = *(GDate *) ptr;
  return dt;
}

static gboolean
date_equality (gpointer a, gpointer b)
{
  gboolean x = g_date_valid ((GDate *) a);
  gboolean y = g_date_valid ((GDate *) b);

  if ((x && !y) || (!x && y))
    return FALSE;
  if (!x && !y)
    return TRUE;
  return g_date_compare ((GDate *) a, (GDate *) b) == 0;
}

static gboolean
date_va2p (const gchar *key, const gchar *subkey, va_list *va, gpointer *storage)
{
  GDate *old_date = (GDate *)*storage;
  GDate *dt = g_date_new ();
  *dt = *va_arg (*va, GDate *);
  *storage = dt;
  return old_date == NULL || g_date_compare (old_date, dt) != 0;
}

static void
date_p2va (const gchar *key, const gchar *subkey, gpointer ptr, gpointer dest)
{
  *(GDate *) dest = *(GDate *) ptr;
}

static gchar *
date_exp_str (gpointer ptr)
{
  GDate *dt = (GDate *) ptr;
  if (g_date_valid (dt)) {
    return guppi_strdup_printf ("%04d-%02d-%02d",
			    g_date_year (dt),
			    g_date_month (dt),
			    g_date_day (dt));
  } else {
    return guppi_strdup ("xxxx-xx-xx");
  }
}

static gboolean
date_imp_str (const gchar *str, gpointer *ptr)
{
  GDate *dt = NULL;
  gint y, m, d;
  if (!strcmp (str, "xxxx-xx-xx")) {
    dt = g_date_new ();
  } else if (sscanf (str, "%d-%d-%d", &y, &m, &d) == 3) {
    dt = g_date_new ();
    g_date_set_dmy (dt, d, m, y);

    if (!g_date_valid (dt)) {
      g_date_free (dt);
      dt = NULL;
    }
  }
  
  g_date_free (*ptr);
  *ptr = dt;
  return dt != NULL;
}

GuppiAttributeFlavor
guppi_attribute_flavor_date (void)
{
  static GuppiAttributeFlavor flavor = -1;
  
  if (flavor < 0) {
    
    flavor = guppi_attribute_flavor_register ("date",
					      date_create_default,
					      date_destroy,
					      date_copy,
					      date_equality,
					      NULL,
					      date_va2p,
					      date_p2va);

    guppi_attribute_flavor_add_string_serialization (flavor,
						     date_exp_str,
						     date_imp_str);
  }

  return flavor;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static gchar *
rgb_exp_str (gpointer ptr)
{
  guint32 rgb = GPOINTER_TO_UINT (ptr);
  gint r, g, b;

  UINT_TO_RGB (rgb, &r, &g, &b);
  return guppi_strdup_printf ("#%02x%02x%02x", r, g, b);
}

static gboolean
rgb_imp_str (const gchar *str, gpointer *ptr)
{
  gint r, g, b;
  if (str[0] == '#' && sscanf (str+1, "%2x%2x%2x", &r, &g, &b) == 3) {
    *ptr = GUINT_TO_POINTER (RGB_TO_UINT (r, g, b));
    return TRUE;
  }
  return FALSE;
}

static void
rgb_bag_to_widget_cb (GuppiAttributeWidget *gaw, gpointer closure)
{
  double c[4];
  guint32 rgb;
  gint r, g, b;

  guppi_attribute_widget_bag_get (gaw, NULL, &rgb);
  UINT_TO_RGB (rgb, &r, &g, &b);
  c[0] = r / 255.0;
  c[1] = g / 255.0;
  c[2] = b / 255.0;
  c[3] = 1.0;
  
  gtk_color_selection_set_color (GTK_COLOR_SELECTION (closure), c);
}

static void
rgb_color_changed_cb (GtkColorSelection *w, gpointer closure)
{
  double c[4];
  guint32 rgb;
  gtk_color_selection_get_color (w, c);
  rgb = RGB_TO_UINT ((gint)rint (c[0] * 255),
		     (gint)rint (c[1] * 255),
		     (gint)rint (c[2] * 255));
  guppi_attribute_widget_bag_set (GUPPI_ATTRIBUTE_WIDGET (closure), NULL, c);
}

static GtkWidget *
rgb_widget (const gchar *key)
{
  GtkWidget *gaw = guppi_attribute_widget_new (guppi_attribute_flavor_rgb (), key);
  GtkWidget *w = gtk_color_selection_new ();

  gtk_color_selection_set_opacity ((GtkColorSelection *) w, FALSE);
  gtk_container_add (GTK_CONTAINER (gaw), w);
  gtk_widget_show (w);

  gtk_signal_connect (GTK_OBJECT (gaw),
		      "bag_to_widget",
		      GTK_SIGNAL_FUNC (rgb_bag_to_widget_cb),
		      w);
  gtk_signal_connect (GTK_OBJECT (w),
		      "color_changed",
		      GTK_SIGNAL_FUNC (rgb_color_changed_cb),
		      gaw);

  return gaw;
}

GuppiAttributeFlavor
guppi_attribute_flavor_rgb (void)
{
  static GuppiAttributeFlavor flavor = -1;
  
  if (flavor < 0) {
    
    flavor = guppi_attribute_flavor_register ("rgb",
					      uint32_create_default,
					      uint32_destroy,
					      uint32_copy,
					      uint32_equality,
					      NULL,
					      uint32_va2p,
					      uint32_p2va);

    guppi_attribute_flavor_add_widget (flavor, rgb_widget);

    guppi_attribute_flavor_add_string_serialization (flavor,
						     rgb_exp_str,
						     rgb_imp_str);
  }

  return flavor;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static gchar *
rgba_exp_str (gpointer ptr)
{
  guint32 rgba = GPOINTER_TO_UINT (ptr);
  gint r, g, b, a;

  UINT_TO_RGBA (rgba, &r, &g, &b, &a);
  return guppi_strdup_printf ("#%02x%02x%02x%02x", r, g, b, a);
}

static gboolean
rgba_imp_str (const gchar *str, gpointer *ptr)
{
  gint r, g, b, a;
  if (str[0] == '#' && sscanf (str+1, "%2x%2x%2x%2x", &r, &g, &b, &a) == 4) {
    *ptr = GUINT_TO_POINTER (RGBA_TO_UINT (r, g, b, a));
    return TRUE;
  }
  return FALSE;
}

static void
rgba_bag_to_widget_cb (GuppiAttributeWidget *gaw, gpointer closure)
{
  guint32 rgba;
  gint r, g, b, a;

  guppi_attribute_widget_bag_get (gaw, NULL, &rgba);
  UINT_TO_RGBA (rgba, &r, &g, &b, &a);
  gnome_color_picker_set_i8 (GNOME_COLOR_PICKER (closure), r, g, b, a);
}

static void
rgba_color_set_cb (GnomeColorPicker *w, guint r16, guint g16, guint b16, guint a16, gpointer closure)
{
  guint32 rgba;
  guint8 r, g, b, a;
  gnome_color_picker_get_i8 (w, &r, &g, &b, &a);
  rgba = RGBA_TO_UINT (r, g, b, a);
  guppi_attribute_widget_bag_set (GUPPI_ATTRIBUTE_WIDGET (closure), NULL, rgba);
}

static GtkWidget *
rgba_widget (const gchar *key)
{
  GtkWidget *gaw = guppi_attribute_widget_new (guppi_attribute_flavor_rgba (), key);
  GtkWidget *w = gnome_color_picker_new ();

  gnome_color_picker_set_use_alpha ((GnomeColorPicker *) w, TRUE);
  gtk_container_add (GTK_CONTAINER (gaw), w);
  gtk_widget_show (w);

  gtk_signal_connect (GTK_OBJECT (gaw),
		      "bag_to_widget",
		      GTK_SIGNAL_FUNC (rgba_bag_to_widget_cb),
		      w);
  gtk_signal_connect (GTK_OBJECT (w),
		      "color_set",
		      GTK_SIGNAL_FUNC (rgba_color_set_cb),
		      gaw);

  return gaw;
}

GuppiAttributeFlavor
guppi_attribute_flavor_rgba (void)
{
  static GuppiAttributeFlavor flavor = -1;
  
  if (flavor < 0) {
    
    flavor = guppi_attribute_flavor_register ("rgba",
					      uint32_create_default,
					      uint32_destroy,
					      uint32_copy,
					      uint32_equality,
					      NULL,
					      uint32_va2p,
					      uint32_p2va);

    guppi_attribute_flavor_add_widget (flavor, rgba_widget);

    guppi_attribute_flavor_add_string_serialization (flavor,
						     rgba_exp_str,
						     rgba_imp_str);
  }

  return flavor;
}

/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */

static gpointer
font_create_default (void)
{
  return guppi_default_font_medium ();
}

static void
font_destroy (gpointer ptr)
{
  guppi_unref (ptr);
}

static gpointer
font_copy (gpointer ptr)
{
  guppi_ref (ptr);
  return ptr;
}

static gboolean
font_equality (gpointer a, gpointer b)
{
  gchar *sa, *sb;
  gboolean eq;

  if (a == b)
    return TRUE;

  sa = gnome_font_get_full_name (GNOME_FONT (a));
  sb = gnome_font_get_full_name (GNOME_FONT (b));

  eq = !strcmp (sa, sb);

  g_free (sa);
  g_free (sb);

  return eq;
}

static gboolean
font_va2p (const gchar *key, const gchar *subkey, va_list *va, gpointer *storage)
{
  GnomeFont *old_font = (GnomeFont *)*storage;
  GnomeFont *font = va_arg (*va, GnomeFont *);
  guppi_refcounting_assign (*storage, font);
  return old_font != font;
}

static void
font_p2va (const gchar *key, const gchar *subkey, gpointer ptr, gpointer dest)
{
  *(GnomeFont **) dest = (GnomeFont *) ptr;
  if (subkey == NULL || !strcmp (subkey, "ref")) {
    guppi_ref (ptr);
  } else if (strcmp (subkey, "raw")) {
    g_warning ("Unknown subkey '%s::%s'", key, subkey);
    guppi_ref (ptr); /* we also ref in this case */
  }
}

static gchar *
font_exp_str (gpointer ptr)
{
  return gnome_font_get_full_name (GNOME_FONT (ptr));
}

static gboolean
font_imp_str (const gchar *str, gpointer *ptr)
{
  GnomeFont *font = gnome_font_new_from_full_name (str);
  gpointer old = *ptr;

  if (font) {
    *ptr = font;
  } else {
    *ptr = font_create_default ();
  }
  
  guppi_unref (old);

  return TRUE;
}

GuppiAttributeFlavor
guppi_attribute_flavor_font (void)
{
  static GuppiAttributeFlavor flavor = -1;
  
  if (flavor < 0) {
    
    flavor = guppi_attribute_flavor_register ("font",
					      font_create_default,
					      font_destroy,
					      font_copy,
					      font_equality,
					      NULL,
					      font_va2p,
					      font_p2va);

    guppi_attribute_flavor_add_string_serialization (flavor,
						     font_exp_str,
						     font_imp_str);
  }

  return flavor;
}



syntax highlighted by Code2HTML, v. 0.9.1