/* File import from gal to gnumeric by import-gal.  Do not edit.  */

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * color-group.c - Utility to keep a shered memory of custom colors
 *                 between arbitrary widgets.
 * Copyright 2000, Michael Levy
 * Copyright 2001, Almer S. Tigelaar
 *
 * Authors:
 * 	Michael Levy (mlevy@genoscope.cns.fr)
 * Revised and polished by:
 *   Almer S. Tigelaar <almer@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License, version 2, as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include <string.h>
#include "color-group.h"
#include <glib-object.h>
#include <gdk/gdkcolor.h>
#include "gnm-marshalers.h"
#include "abi-gal-utils.h"

#define PARENT_TYPE	G_TYPE_OBJECT

enum {
        CUSTOM_COLOR_ADD,
        LAST_SIGNAL
};

static GObjectClass *parent_class;

static GQuark color_group_signals [LAST_SIGNAL] = { 0 };

static void color_group_finalize (GObject *obj);

static void
color_group_class_init (ColorGroupClass *klass)
{
	GObjectClass *object_class;

	object_class = (GObjectClass*) klass;

	object_class->finalize = &color_group_finalize;
	parent_class = g_type_class_peek (PARENT_TYPE);

	color_group_signals [CUSTOM_COLOR_ADD] =
		g_signal_new ("custom_color_add",
			COLOR_GROUP_TYPE,
			G_SIGNAL_RUN_LAST,
			G_STRUCT_OFFSET (ColorGroupClass, custom_color_add),
			(GSignalAccumulator) NULL, NULL,
			g_cclosure_marshal_VOID__POINTER,
			G_TYPE_NONE,
			1, G_TYPE_POINTER);
}

static void
color_group_init (ColorGroup *cg)
{
	cg->name = NULL;
	cg->history = NULL;
	cg->history_size = 0;
}

E_MAKE_TYPE(color_group,
            "ColorGroup",
            ColorGroup,
            color_group_class_init,
            color_group_init,
            PARENT_TYPE)


/* Hash table used to ensure unicity in newly created names*/
static GHashTable *group_names = NULL;

static guint
cg_hash (gconstpointer	key)
{
	/* Do NOT use smart type checking it will not work for the tmp_key */
	return g_str_hash (((ColorGroup *)key)->name);
}

static gint
cg_cmp (gconstpointer a, gconstpointer	b)
{
	/* Do NOT use smart type checking it will not work for the tmp_key */
	ColorGroup const *cg_a = (ColorGroup *)a;
	ColorGroup const *cg_b = (ColorGroup *)b;
	if (cg_a == cg_b)
		return TRUE;
	if (cg_a->context != cg_b->context)
		return FALSE;
	return g_str_equal (cg_a->name, cg_b->name);
}

static void
initialize_group_names (void)
{
	g_assert (group_names == NULL);
	group_names = g_hash_table_new (cg_hash, cg_cmp);
}

/**
 * color_group_get :
 * @name :
 * @context :
 *
 * Look up the name/context specific color-group.  Return NULL if it is not found.
 * No reference is added if it is found.
 */
ColorGroup *
color_group_get (const gchar * name, gpointer context)
{
	ColorGroup tmp_key;
	gpointer res;

	g_assert(group_names);

	g_return_val_if_fail(name != NULL, NULL);

	tmp_key.name = (char *)name;
	tmp_key.context = context;
	res = g_hash_table_lookup (group_names, &tmp_key);

	if (res != NULL)
		return COLOR_GROUP (res);
	else
		return NULL;
}

static gchar *
create_unique_name (gpointer context)
{
	const gchar *prefix = "__cg_autogen_name__";
	static gint latest_suff = 0;
	gchar *new_name;

	for(;;latest_suff++) {
		new_name = g_strdup_printf("%s%i", prefix, latest_suff);
		if (color_group_get (new_name, context) == NULL)
			return new_name;
		else
			g_free(new_name);
	}
	g_assert_not_reached();
}

static void
color_group_finalize (GObject *obj)
{
	ColorGroup *cg;

	g_return_if_fail(obj != NULL);
	g_return_if_fail(IS_COLOR_GROUP(obj));
	g_assert(group_names != NULL);

	cg = COLOR_GROUP (obj);

	/* make this name available */
	if (cg->name) {
		g_hash_table_remove (group_names, cg);
		g_free (cg->name);
		cg->name = NULL;
	}

	if (cg->history) {
		/* Free the whole colour history */
		while ((int) cg->history->len > 0)
			gdk_color_free ((GdkColor *)
					g_ptr_array_remove_index (cg->history, 0));
		g_ptr_array_free (cg->history, TRUE);
		cg->history = NULL;
	}

	if (parent_class->finalize)
		(parent_class->finalize) (obj);
}

/*
 * color_group_get_history_size:
 * Get the size of the custom color history
 */
gint
color_group_get_history_size (ColorGroup *cg)
{
	g_return_val_if_fail (cg != NULL, 0);

	return cg->history_size;
}

/*
 * Change the size of the custom color history.
 */
void
color_group_set_history_size (ColorGroup *cg, gint size)
{
	g_return_if_fail(cg != NULL);
	g_return_if_fail(size >= 0);

	/* Remove excess elements (begin with kicking out the oldest) */
	while ((int) cg->history->len > size)
		gdk_color_free ((GdkColor *) g_ptr_array_remove_index (cg->history, 0));
}

/*
 * color_group_fetch :
 * @name :
 * @context :
 *
 * if name is NULL or a name not currently in use by another group
 * then a new group is created and returned. If name was NULL
 * then the new group is given a unique name prefixed by "__cg_autogen_name__"
 * (thereby insuring namespace separation).
 * If name was already used by a group then the reference count is
 * incremented and a pointer to the group is returned.
 */
ColorGroup *
color_group_fetch (const gchar *name, gpointer context)
{
	ColorGroup *cg;
	gchar *new_name;

	if (group_names == NULL)
		initialize_group_names();

	if (name == NULL)
		new_name = create_unique_name (context);
	else
		new_name = g_strdup (name);

	cg = color_group_get (new_name, context);
	if (cg != NULL) {
		g_free (new_name);
		g_object_ref (G_OBJECT (cg));
		return cg;
	}

	/* Take care of creating the new object */
	cg = g_object_new (color_group_get_type (), NULL);
	g_return_val_if_fail(cg != NULL, NULL);

	cg->name = new_name;
	cg->context = context;

	/* Create history */
	cg->history = g_ptr_array_new ();

	/* FIXME: Why not 8? We never use more then 8 on the palette,
	 * maybe we can't free colors while they are still on the palette and
	 * need to be sure they are not on it when we free them and thus we
	 * make the upper limit twice the size of the number of displayed items
	 * (2 * 8) ?
	 */
	cg->history_size = 16;

	/* lastly register this name */
	g_hash_table_insert (group_names, cg, cg);

	return cg;
}

/*
 * color_group_get_custom_colors:
 * Retrieve all custom colors currently in the history using a callback
 * mechanism. The custom colors will be passed from the oldest to the newest.
 */
void
color_group_get_custom_colors (ColorGroup *cg, CbCustomColors callback, gpointer user_data)
{
	int i;

	g_return_if_fail (cg != NULL);

	/* Invoke the callback for our full history */
	for (i = 0; i < (int) cg->history->len; i++) {
		GdkColor const * const color = g_ptr_array_index (cg->history, i);

		callback (color, user_data);
	}
}

/*
 * color_group_add_color:
 * Changes the colors. The color to be set should always be a custom
 * color! It has no use adding a color which is already in the default
 * palette.
 */
void
color_group_add_color (ColorGroup *cg, GdkColor const * const color)
{
	int i;

	g_return_if_fail(cg != NULL);
	g_return_if_fail(color != NULL); /* Can't be NULL */

	/* Let's be smart and see if it's already in our history, no need to add it again*/
	for (i = 0; i < (int) cg->history->len; i++) {
		GdkColor *current = g_ptr_array_index (cg->history, i);

		if (gdk_color_equal (color, current))
			return;
	}

	/*
	 * We make our own private copy of the color passed and put
	 * it in the history, this is freed later.
	 */
	if (cg->history_size > 0)
		g_ptr_array_add (cg->history, gdk_color_copy (color));

	/* Shift out the oldest item if we grow beyond our set size */
	if ((int) cg->history->len > cg->history_size)
		gdk_color_free ((GdkColor *) g_ptr_array_remove_index (cg->history, 0));

	/* Tell color-palette's that use this group that
	 * a new custom color was added.
	 */
	g_signal_emit (G_OBJECT(cg),
		color_group_signals [CUSTOM_COLOR_ADD],
		0,
		color);
}
