/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * glade-clipboard.c - An object for handling Cut/Copy/Paste.
 *
 * Copyright (C) 2005 The GNOME Foundation.
 *
 * Author(s):
 *      Tristan Van Berkom <tvb@gnome.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.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <glib-object.h>
#include <glib/gi18n-lib.h>
#include <string.h>
#include "glade-builtins.h"


struct _GladeParamSpecObjects {
	GParamSpec    parent_instance;
	
	GType         type; /* Object or interface type accepted
			     * in this object list.
			     */
};

struct _GladeParamSpecAccel {
	GParamSpec    parent_instance;
	
	GType         type; /* The type this accel key is for; this allows
			     * us to verify the validity of any signals for
			     * this type.
			     */
};

typedef struct _GladeStockItem {
	gchar *value_name;
	gchar *value_nick;
	gchar *clean_name;
	gint  value;
} GladeStockItem;


/************************************************************
 *      Auto-generate the enum type for stock properties    *
 ************************************************************/
 
/* Hard-coded list of stock images from gtk+ that are not stock "items" */
static const gchar *builtin_stock_images[] = 
{
	"gtk-dialog-authentication", /* GTK_STOCK_DIALOG_AUTHENTICATION */
	"gtk-dnd",                   /* GTK_STOCK_DND */
	"gtk-dnd-multiple",          /* GTK_STOCK_DND_MULTIPLE */
	"gtk-color-picker",          /* GTK_STOCK_COLOR_PICKER */
	"gtk-directory",             /* GTK_STOCK_DIRECTORY */
	"gtk-file",                  /* GTK_STOCK_FILE */
	"gtk-missing-image"          /* GTK_STOCK_MISSING_IMAGE */
};

static GSList *stock_prefixs = NULL;
static gboolean stock_prefixs_done = FALSE;

/* FIXME: func needs documentation
 */
void
glade_standard_stock_append_prefix (const gchar *prefix)
{
	if (stock_prefixs_done)
	{
		g_warning ("glade_standard_stock_append_prefix should be used in catalog init-function");
		return;
	}
	
	stock_prefixs = g_slist_append (stock_prefixs, g_strdup (prefix));
}


static GladeStockItem *
new_from_values (const gchar *name, const gchar *nick, gint value)
{
	GladeStockItem *new_gsi = NULL;
	gchar *clean_name;
	size_t len = 0;
	guint i = 0;
	guint j = 0;
	
	new_gsi = (GladeStockItem *) g_malloc0 (sizeof(GladeStockItem));

	new_gsi->value_name = g_strdup (name);
	new_gsi->value_nick = g_strdup (nick);
	new_gsi->value = value;

	clean_name = g_strdup (name);
	len = strlen (clean_name);

	while (i+j <= len)
		{
			if (clean_name[i+j] == '_')
				j++;
			
			clean_name[i] = clean_name[i+j];
			i++;				
		}

	new_gsi->clean_name = g_utf8_collate_key (clean_name, i - 1);
	
	g_free (clean_name);

	return new_gsi;
}


static gint
compare_two_gsi (gconstpointer a, gconstpointer b)
{
	GladeStockItem *gsi1 = (GladeStockItem *) a;
	GladeStockItem *gsi2 = (GladeStockItem *) b;

	return strcmp (gsi1->clean_name, gsi2->clean_name);
}


static GArray *
list_stock_items (gboolean include_images)
{
	GtkStockItem  item;
	GSList       *l = NULL, *stock_list = NULL, *p = NULL;
	gchar        *stock_id = NULL, *prefix = NULL;
	gint          stock_enum = 1, i = 0;
	GEnumValue    value;
	GArray       *values = NULL;
	GladeStockItem *gsi;
	GSList         *gsi_list = NULL;
	GSList         *gsi_list_list = NULL;

	stock_list = g_slist_reverse (gtk_stock_list_ids ());

	values = g_array_sized_new (TRUE, TRUE, sizeof (GEnumValue),
				    g_slist_length (stock_list) + 1);
	
	/* Add first "no stock" element which is sorted alone ! */
	gsi = new_from_values ("None", "glade-none", 0);
	gsi_list = g_slist_insert_sorted (gsi_list, gsi, (GCompareFunc) compare_two_gsi);
	gsi_list_list = g_slist_append (gsi_list_list, gsi_list);
	gsi_list = NULL; 

	/* We want gtk+ stock items to appear first */
	if ((stock_prefixs && strcmp (stock_prefixs->data, "gtk-")) || 
	    stock_prefixs == NULL)
		stock_prefixs = g_slist_prepend (stock_prefixs, g_strdup ("gtk-")); 
	
	for (p = stock_prefixs; p; p = g_slist_next (p))
	{
		prefix = p->data;

		for (l = stock_list; l; l = g_slist_next (l))
		{
			stock_id = l->data;
			if (g_str_has_prefix (stock_id, prefix) == FALSE ||
			    gtk_stock_lookup (stock_id, &item) == FALSE )
				continue;

			gsi = new_from_values (item.label, stock_id, stock_enum++ );
			gsi_list = g_slist_insert_sorted (gsi_list, gsi, (GCompareFunc) compare_two_gsi);
		}

		gsi_list_list = g_slist_append (gsi_list_list, gsi_list);
		gsi_list = NULL;

		/* Images are appended after the gtk+ group of items */
		if (include_images && !strcmp (prefix, "gtk-"))
		{
			for (i = 0; i < G_N_ELEMENTS (builtin_stock_images); i++)
			{
				gsi = new_from_values (builtin_stock_images[i], builtin_stock_images[i], stock_enum++);
				gsi_list = g_slist_insert_sorted (gsi_list, gsi, (GCompareFunc) compare_two_gsi);	
			}
			gsi_list_list = g_slist_append (gsi_list_list, gsi_list);
			gsi_list = NULL;
		}	
	}

	for (p = gsi_list_list; p; p = g_slist_next (p))
	{
		
		for (l = (GSList *) p->data; l; l = g_slist_next (l))
		{
			gsi = (GladeStockItem *) l->data;
			value.value = gsi->value;
			value.value_name = g_strdup (gsi->value_name);
			value.value_nick = g_strdup (gsi->value_nick);
			values = g_array_append_val (values, value);

			g_free (gsi->value_nick);
			g_free (gsi->value_name);
			g_free (gsi->clean_name);
			g_free (gsi);
		}
		g_slist_free ((GSList *) p->data);	
	}

	g_slist_free (gsi_list_list);

	/* Add the trailing end marker */
	value.value      = 0;
	value.value_name = NULL;
	value.value_nick = NULL;
	values = g_array_append_val (values, value);

	stock_prefixs_done = TRUE;
	g_slist_free (stock_list);

	return values;
}

GType
glade_standard_stock_get_type (void)
{
	static GType etype = 0;

	if (etype == 0) {
		GArray *values = list_stock_items (FALSE);

		etype = g_enum_register_static ("GladeStock", 
						(GEnumValue *)g_array_free (values, FALSE));
	}
	return etype;
}


GType
glade_standard_stock_image_get_type (void)
{
	static GType etype = 0;

	if (etype == 0) {
		GArray *values = list_stock_items (TRUE);

		etype = g_enum_register_static ("GladeStockImage", 
						(GEnumValue *)g_array_free (values, FALSE));
	}
	return etype;
}

GParamSpec *
glade_standard_stock_spec (void)
{
	return g_param_spec_enum ("stock", _("Stock"), 
				  _("A builtin stock item"),
				  GLADE_TYPE_STOCK,
				  0, G_PARAM_READWRITE);
}

GParamSpec *
glade_standard_stock_image_spec (void)
{
	return g_param_spec_enum ("stock-image", _("Stock Image"), 
				  _("A builtin stock image"),
				  GLADE_TYPE_STOCK_IMAGE,
				  0, G_PARAM_READWRITE);
}


/****************************************************************
 *  A GList boxed type used by GladeParamSpecObjects and        *
 *  GladeParamSpecAccel                                         *
 ****************************************************************/
GType
glade_glist_get_type (void)
{
	static GType type_id = 0;

	if (!type_id)
		type_id = g_boxed_type_register_static
			("GladeGList", 
			 (GBoxedCopyFunc) g_list_copy,
			 (GBoxedFreeFunc) g_list_free);
	return type_id;
}

GList *
glade_accel_list_copy (GList *accels)
{
	GList          *ret = NULL, *list;
	GladeAccelInfo *info, *dup_info;

	for (list = accels; list; list = list->next)
	{
		info = list->data;

		dup_info            = g_new0 (GladeAccelInfo, 1);
		dup_info->signal    = g_strdup (info->signal);
		dup_info->key       = info->key;
		dup_info->modifiers = info->modifiers;

		ret = g_list_prepend (ret, dup_info);
	}

	return g_list_reverse (ret);
}

void
glade_accel_list_free (GList *accels)
{
	GList          *list;
	GladeAccelInfo *info;

	for (list = accels; list; list = list->next)
	{
		info = list->data;

		g_free (info->signal);
		g_free (info);
	}
	g_list_free (accels);
}

GType
glade_accel_glist_get_type (void)
{
	static GType type_id = 0;

	if (!type_id)
		type_id = g_boxed_type_register_static
			("GladeAccelGList", 
			 (GBoxedCopyFunc) glade_accel_list_copy,
			 (GBoxedFreeFunc) glade_accel_list_free);
	return type_id;
}


/****************************************************************
 *  Built-in GladeParamSpecAccel for accelerator properties     *
 ****************************************************************/
gboolean
glade_keyval_valid (guint val)
{
	gint i;

	for (i = 0; GladeKeys[i].name != NULL; i++)
	{
		if (GladeKeys[i].value == val)
			return TRUE;
	}
	return FALSE;
}


static void
param_accel_init (GParamSpec *pspec)
{
	GladeParamSpecAccel *ospec = GLADE_PARAM_SPEC_ACCEL (pspec);
	ospec->type = G_TYPE_OBJECT;
}

static void
param_accel_set_default (GParamSpec *pspec,
			 GValue     *value)
{
	if (value->data[0].v_pointer != NULL)
	{
		g_free (value->data[0].v_pointer);
	}
	value->data[0].v_pointer = NULL;
}

static gboolean
param_accel_validate (GParamSpec *pspec,
		      GValue     *value)
{
	/* GladeParamSpecAccel *aspec = GLADE_PARAM_SPEC_ACCEL (pspec); */
	GList               *accels, *list, *toremove = NULL;
	GladeAccelInfo      *info;

	accels = value->data[0].v_pointer;

	for (list = accels; list; list = list->next)
	{
		info = list->data;
		
		/* Is it an invalid key ? */
		if (glade_keyval_valid (info->key) == FALSE ||
		    /* Does the modifier contain any unwanted bits ? */
		    info->modifiers & GDK_MODIFIER_MASK ||
		    /* Do we have a signal ? */
		    /* FIXME: Check if the signal is valid for 'type' */
		    info->signal == NULL)
			toremove = g_list_prepend (toremove, info);
	}

	for (list = toremove; list; list = list->next)
		accels = g_list_remove (accels, list->data);

	if (toremove) g_list_free (toremove);
 
	value->data[0].v_pointer = accels;

	return toremove != NULL;
}

static gint
param_accel_values_cmp (GParamSpec   *pspec,
			  const GValue *value1,
			  const GValue *value2)
{
  guint8 *p1 = value1->data[0].v_pointer;
  guint8 *p2 = value2->data[0].v_pointer;

  /* not much to compare here, try to at least provide stable lesser/greater result */

  return p1 < p2 ? -1 : p1 > p2;
}

GType
glade_param_accel_get_type (void)
{
	static GType accel_type = 0;

	if (accel_type == 0)
	{
		static /* const */ GParamSpecTypeInfo pspec_info = {
			sizeof (GladeParamSpecAccel),  /* instance_size */
			16,                         /* n_preallocs */
			param_accel_init,         /* instance_init */
			0xdeadbeef,                 /* value_type, assigned further down */
			NULL,                       /* finalize */
			param_accel_set_default,  /* value_set_default */
			param_accel_validate,     /* value_validate */
			param_accel_values_cmp,   /* values_cmp */
		};
		pspec_info.value_type = GLADE_TYPE_ACCEL_GLIST;

		accel_type = g_param_type_register_static
			("GladeParamAccel", &pspec_info);
	}
	return accel_type;
}

GParamSpec *
glade_param_spec_accel (const gchar   *name,
			const gchar   *nick,
			const gchar   *blurb,
			GType          widget_type,
			GParamFlags    flags)
{
  GladeParamSpecAccel *pspec;

  pspec = g_param_spec_internal (GLADE_TYPE_PARAM_ACCEL,
				 name, nick, blurb, flags);
  
  pspec->type = widget_type;
  return G_PARAM_SPEC (pspec);
}

/****************************************************************
 *  Built-in GladeParamSpecObjects for object list properties   *
 *  (Used as a pspec to desctibe an AtkRelationSet, but can     *
 *  for any object list property)                               *
 ****************************************************************/
static void
param_objects_init (GParamSpec *pspec)
{
	GladeParamSpecObjects *ospec = GLADE_PARAM_SPEC_OBJECTS (pspec);
	ospec->type = G_TYPE_OBJECT;
}

static void
param_objects_set_default (GParamSpec *pspec,
			   GValue     *value)
{
	if (value->data[0].v_pointer != NULL)
	{
		g_free (value->data[0].v_pointer);
	}
	value->data[0].v_pointer = NULL;
}

static gboolean
param_objects_validate (GParamSpec *pspec,
			GValue     *value)
{
	GladeParamSpecObjects *ospec = GLADE_PARAM_SPEC_OBJECTS (pspec);
	GList                 *objects, *list, *toremove = NULL;
	GObject               *object;

	objects = value->data[0].v_pointer;

	for (list = objects; list; list = list->next)
	{
		object = list->data;

		if (G_TYPE_IS_INTERFACE (ospec->type) &&
		    glade_util_class_implements_interface
		    (G_OBJECT_TYPE (object), ospec->type) == FALSE)
			toremove = g_list_prepend (toremove, object);
		else if (G_TYPE_IS_INTERFACE (ospec->type) == FALSE &&
			 g_type_is_a (G_OBJECT_TYPE (object), 
				      ospec->type) == FALSE)
			toremove = g_list_prepend (toremove, object);
	}

	for (list = toremove; list; list = list->next)
	{
		object = list->data;
		objects = g_list_remove (objects, object);
	}
	if (toremove) g_list_free (toremove);
 
	value->data[0].v_pointer = objects;

	return toremove != NULL;
}

static gint
param_objects_values_cmp (GParamSpec   *pspec,
			  const GValue *value1,
			  const GValue *value2)
{
  guint8 *p1 = value1->data[0].v_pointer;
  guint8 *p2 = value2->data[0].v_pointer;

  /* not much to compare here, try to at least provide stable lesser/greater result */

  return p1 < p2 ? -1 : p1 > p2;
}

GType
glade_param_objects_get_type (void)
{
	static GType objects_type = 0;

	if (objects_type == 0)
	{
		static /* const */ GParamSpecTypeInfo pspec_info = {
			sizeof (GladeParamSpecObjects),  /* instance_size */
			16,                         /* n_preallocs */
			param_objects_init,         /* instance_init */
			0xdeadbeef,                 /* value_type, assigned further down */
			NULL,                       /* finalize */
			param_objects_set_default,  /* value_set_default */
			param_objects_validate,     /* value_validate */
			param_objects_values_cmp,   /* values_cmp */
		};
		pspec_info.value_type = GLADE_TYPE_GLIST;

		objects_type = g_param_type_register_static
			("GladeParamObjects", &pspec_info);
	}
	return objects_type;
}

GParamSpec *
glade_param_spec_objects (const gchar   *name,
			  const gchar   *nick,
			  const gchar   *blurb,
			  GType          accepted_type,
			  GParamFlags    flags)
{
  GladeParamSpecObjects *pspec;

  pspec = g_param_spec_internal (GLADE_TYPE_PARAM_OBJECTS,
				 name, nick, blurb, flags);
  
  pspec->type = accepted_type;
  return G_PARAM_SPEC (pspec);
}

void
glade_param_spec_objects_set_type (GladeParamSpecObjects *pspec,
				   GType                  type)
{
	pspec->type = type;
}

GType
glade_param_spec_objects_get_type  (GladeParamSpecObjects *pspec)
{
	return pspec->type;
}

/* This was developed for the purpose of holding a list
 * of 'targets' in an AtkRelation (we are setting it up
 * as a property)
 */
GParamSpec *
glade_standard_objects_spec (void)
{
	return glade_param_spec_objects ("objects", _("Objects"), 
					 _("A list of objects"),
					 G_TYPE_OBJECT,
					 G_PARAM_READWRITE);
}

/* Pixbuf Type */
GParamSpec *
glade_standard_pixbuf_spec (void)
{
	return g_param_spec_object ("pixbuf", _("Pixbuf"),
				     _("A pixbuf value"), GDK_TYPE_PIXBUF,
				     G_PARAM_READWRITE);
}

/* GdkColor */
GParamSpec *
glade_standard_gdkcolor_spec (void)
{
	return g_param_spec_boxed ("gdkcolor", _("GdkColor"),
				     _("A gdk color value"), GDK_TYPE_COLOR,
				     G_PARAM_READWRITE);
}

/* Accelerator spec */
GParamSpec *
glade_standard_accel_spec (void)
{
	return glade_param_spec_accel ("accelerators", _("Accelerators"),
				       _("A list of accelerator keys"), 
				       GTK_TYPE_WIDGET,
				       G_PARAM_READWRITE);
}

/****************************************************************
 *                    Basic types follow                        *
 ****************************************************************/
GParamSpec *
glade_standard_int_spec (void)
{
	return g_param_spec_int ("int", _("Integer"), 
				 _("An integer value"),
				 G_MININT, G_MAXINT,
				 0, G_PARAM_READWRITE);
}

GParamSpec *
glade_standard_uint_spec (void)
{
	return g_param_spec_uint ("uint", _("Unsigned Integer"), 
				  _("An unsigned integer value"),
				  0, G_MAXUINT, 0, G_PARAM_READWRITE);
}

GParamSpec *
glade_standard_string_spec (void)
{
	return g_param_spec_string ("string", _("String"), 
				    _("An entry"), "", 
				    G_PARAM_READWRITE);
}

GParamSpec *
glade_standard_strv_spec (void)
{
	return g_param_spec_boxed ("strv", _("Strv"),
				   _("String array"),
				   G_TYPE_STRV,
				   G_PARAM_READWRITE);
}

GParamSpec *
glade_standard_float_spec (void)
{
	return g_param_spec_float ("float", _("Float"), 
				   _("A floating point entry"),
				   0.0F, G_MAXFLOAT, 0.0F,
				   G_PARAM_READWRITE);
}

GParamSpec *
glade_standard_boolean_spec (void)
{
	return g_param_spec_boolean ("boolean", _("Boolean"),
				     _("A boolean value"), FALSE,
				     G_PARAM_READWRITE);
}

guint
glade_builtin_key_from_string (const gchar *string)
{
	gint i;

	g_return_val_if_fail (string != NULL, 0);

	for (i = 0; GladeKeys[i].name != NULL; i++)
		if (!strcmp (string, GladeKeys[i].name))
			return GladeKeys[i].value;

	return 0;
}

const gchar *
glade_builtin_string_from_key (guint key)
{
	gint i;

	for (i = 0; GladeKeys[i].name != NULL; i++)
		if (GladeKeys[i].value == key)
			return GladeKeys[i].name;
	return NULL;
}

GType
glade_item_appearance_get_type (void)
{
	static GType etype = 0;

	if (etype == 0)
	{
		static const GEnumValue values[] = {
			{ GLADE_ITEM_ICON_AND_LABEL, "GLADE_ITEM_ICON_AND_LABEL", "icon-and-label" },
			{ GLADE_ITEM_ICON_ONLY,      "GLADE_ITEM_ICON_ONLY",      "icon-only" },
			{ GLADE_ITEM_LABEL_ONLY,     "GLADE_ITEM_LABEL_ONLY",     "label-only" },
			{ 0, NULL, NULL }
		};

	etype = g_enum_register_static ("GladeItemAppearance", values);

	}

	return etype;
}


syntax highlighted by Code2HTML, v. 0.9.1