/* dia-canvas-groupable.c
 * Copyright (C) 2003  Arjan Molenaar
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * 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 "dia-canvas-groupable.h"
#include "dia-canvas-i18n.h"
#include "diamarshal.h"
#include "../config.h"

static void dia_canvas_groupable_base_init (gpointer klass);

GType
dia_canvas_groupable_get_type (void)
{
	static GType groupable_type = 0;

	if (!groupable_type) {
		static const GTypeInfo groupable_info =
		{
			sizeof (DiaCanvasGroupableIface), /* class_size */
			dia_canvas_groupable_base_init,   /* base_init */
			NULL,           /* base_finalize */
			NULL,
			NULL,           /* class_finalize */
			NULL,           /* class_data */
			0,
			0,              /* n_preallocs */
			NULL
		};

		groupable_type = g_type_register_static (G_TYPE_INTERFACE,
							 "DiaCanvasGroupable",
							 &groupable_info, 0);
		g_type_interface_add_prerequisite (groupable_type,
						   DIA_TYPE_CANVAS_ITEM);
	}

	return groupable_type;
}

static void
dia_canvas_groupable_base_init (gpointer iface)
{
	static gboolean initialized = FALSE;

	if (!initialized) {
		  g_signal_new ("add",
				G_TYPE_FROM_INTERFACE (iface),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (DiaCanvasGroupableIface, add),
				NULL, NULL,
				dia_marshal_VOID__OBJECT,
				G_TYPE_NONE, 1,
				G_TYPE_OBJECT);
		  g_signal_new ("remove",
				G_TYPE_FROM_INTERFACE (iface),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (DiaCanvasGroupableIface, remove),
				NULL, NULL,
				dia_marshal_VOID__OBJECT,
				G_TYPE_NONE, 1,
				G_TYPE_OBJECT);
		  initialized = TRUE;
	}
}

/**
 * dia_canvas_groupable_add:
 * @group: 
 * @item: 
 *
 * Append an item to the groupable object. This is done by emiting the 'add'
 * signal. The @group should add the item (or not) and return %TRUE or %FALSE.
 * The group should not worry about any attributes that need to be set on the
 * @item, since this method takes care of that.
 *
 * This function also creates a weak reference from the item to its parent and
 * from the item to the canvas. If one of them is destroyed, their reference
 * is set to %NULL automatically.
 *
 * If you, in your callback function, could not add the @item, stop
 * signal emission with g_signal_stop_emission() or
 * g_signal_stop_emission_by_name().
 **/
void
dia_canvas_groupable_add (DiaCanvasGroupable *group, DiaCanvasItem *item)
{
	g_return_if_fail (DIA_IS_CANVAS_GROUPABLE (group));
	g_return_if_fail (DIA_IS_CANVAS_ITEM (item));
	g_return_if_fail (item->parent == NULL);
	g_return_if_fail (item->parent != DIA_CANVAS_ITEM (group));
	
	g_signal_emit_by_name (group, "add", item);
}

/**
 * dia_canvas_groupable_remove:
 * @group: 
 * @item: 
 *
 * Remove @item from the groupable object. This is done by emiting the 'remove'
 * signal. If the signal returns with %TRUE, the @item -> group relationship
 * is removed. The @group should only care about removing it's reference to the
 * @item.
 *
 * If you, in your callback function, could not remove the @item, stop
 * signal emission with g_signal_stop_emission() or
 * g_signal_stop_emission_by_name().
 **/
void
dia_canvas_groupable_remove (DiaCanvasGroupable *group, DiaCanvasItem *item)
{
	g_return_if_fail (DIA_IS_CANVAS_ITEM (group));
	g_return_if_fail (DIA_IS_CANVAS_ITEM (item));
	g_return_if_fail (item->parent == DIA_CANVAS_ITEM (group));

	g_object_ref (item);

	g_signal_emit_by_name (group, "remove", item);

	g_object_unref (item);
}

/**
 * dia_canvas_groupable_get_iter:
 * @group: 
 * @iter: iterator, must already exist in memory.
 *
 * Get an iterator for the groupable object. This works the same as the
 * text iterator and the tree iterator in GTK+. You can get the actual item by
 * calling dia_canvas_groupable_value(). The iterator can be set to the next
 * item with dia_canvas_groupable_next().
 *
 * Return value: FALSE if no iterator is set, TRUE otherwise.
 **/
gboolean
dia_canvas_groupable_get_iter (DiaCanvasGroupable *group, DiaCanvasIter *iter)
{
	gboolean retval;

	g_return_val_if_fail (DIA_IS_CANVAS_GROUPABLE (group), FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);
	g_return_val_if_fail (DIA_CANVAS_GROUPABLE_GET_IFACE (group)->get_iter != NULL, FALSE);

	dia_canvas_iter_init (iter);

	retval = (* DIA_CANVAS_GROUPABLE_GET_IFACE (group)->get_iter) (group, iter);
	if (!retval)
		dia_canvas_iter_destroy (iter);

	return retval;
}

/**
 * dia_canvas_groupable_next:
 * @group: 
 * @iter: 
 *
 * Make the iterator point to the next item. If no more items are left
 * (FALSE is returned) the iterator is automatically destroyed.
 * If you quit iterating before the last item is requested, make sure you
 * call dia_canvas_iter_destroy().
 *
 * Return value: TRUE if the iterator points to a valid object, FALSE otherwise.
 **/
gboolean
dia_canvas_groupable_next (DiaCanvasGroupable *group, DiaCanvasIter *iter)
{
	gboolean retval;

	g_return_val_if_fail (DIA_IS_CANVAS_GROUPABLE (group), FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);
	g_return_val_if_fail (DIA_CANVAS_GROUPABLE_GET_IFACE (group)->next != NULL, FALSE);

	retval = (* DIA_CANVAS_GROUPABLE_GET_IFACE (group)->next) (group, iter);

	if (!retval)
		dia_canvas_iter_destroy (iter);

	return retval;
}

/**
 * dia_canvas_groupable_value:
 * @group: 
 * @iter: 
 *
 * Retrieve the value pointer to by the iterator.
 *
 * Return value: The #DiaCanvasItem pointed to by the iterator.
 **/
DiaCanvasItem*
dia_canvas_groupable_value (DiaCanvasGroupable *group, DiaCanvasIter *iter)
{
	DiaCanvasItem *value = NULL;

	g_return_val_if_fail (DIA_IS_CANVAS_GROUPABLE (group), NULL);
	g_return_val_if_fail (iter != NULL, NULL);
	g_return_val_if_fail (DIA_CANVAS_GROUPABLE_GET_IFACE (group)->value != NULL, NULL);

	value = (* DIA_CANVAS_GROUPABLE_GET_IFACE (group)->value) (group, iter);

	g_assert (value == NULL || DIA_IS_CANVAS_ITEM (value));

	return value;
}

/**
 * dia_canvas_groupable_length:
 * @group: 
 *
 * Get the amount of items the groupable object holds.
 *
 * Return value: number of child items, -1 on error.
 **/
gint
dia_canvas_groupable_length (DiaCanvasGroupable *group)
{
	DiaCanvasIter iter;
	gint count = 0;

	g_return_val_if_fail (DIA_IS_CANVAS_GROUPABLE (group), -1);

	if (dia_canvas_groupable_get_iter (group, &iter)) do {
		count++;
	} while (dia_canvas_groupable_next (group, &iter));

	return count;
}

/**
 * dia_canvas_groupable_pos:
 * @group: 
 * @item: 
 *
 * Get the position of @item in  the group.
 *
 * Return value: Position of @item, -1 on error.
 **/
gint
dia_canvas_groupable_pos (DiaCanvasGroupable *group, DiaCanvasItem *item)
{
	DiaCanvasIter iter;
	gint count = 0;

	g_return_val_if_fail (DIA_IS_CANVAS_GROUPABLE (group), -1);
	g_return_val_if_fail (DIA_IS_CANVAS_ITEM (item), -1);

	if (dia_canvas_groupable_get_iter (group, &iter)) do {
		if (item == dia_canvas_groupable_value (group, &iter)) {
			dia_canvas_iter_destroy (&iter);
			return count;
		}
		count++;
	} while (dia_canvas_groupable_next (group, &iter));

	return -1;
}


