/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */

/* eel-wrap-box.c - A table that can wrap its contents as needed.

   Copyright (C) 2000 Eazel, Inc.

   The Gnome 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.

   The Gnome 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 the Gnome Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Authors: Ramiro Estrugo <ramiro@eazel.com>
*/

#include <config.h>
#include "eel-wrap-table.h"

#include "eel-art-extensions.h"
#include "eel-art-gtk-extensions.h"
#include "eel-gtk-extensions.h"
#include "eel-gtk-macros.h"
#include "eel-types.h"
#include <gtk/gtk.h>

/* Arguments */
enum
{
	PROP_0,
	PROP_X_SPACING,
	PROP_Y_SPACING,
	PROP_X_JUSTIFICATION,
	PROP_Y_JUSTIFICATION,
	PROP_HOMOGENEOUS
};

/* Detail member struct */
struct EelWrapTableDetails
{
	guint x_spacing;
	guint y_spacing;
	EelJustification x_justification;
	EelJustification y_justification;
	gboolean homogeneous;
	GList *children;

	guint is_scrolled : 1;
	guint cols;
};

static void          eel_wrap_table_class_init           (EelWrapTableClass   *wrap_table_class);
static void          eel_wrap_table_init                 (EelWrapTable        *wrap);
/* GObjectClass methods */
static void          eel_wrap_table_finalize             (GObject             *object);
static void          eel_wrap_table_set_property         (GObject             *object,
							  guint                property_id,
							  const GValue        *value,
							  GParamSpec          *pspec);
static void          eel_wrap_table_get_property         (GObject             *object,
							  guint                property_id,
							  GValue              *value,
							  GParamSpec          *pspec);
/* GtkWidgetClass methods */
static void          eel_wrap_table_size_request         (GtkWidget           *widget,
							  GtkRequisition      *requisition);
static int           eel_wrap_table_expose_event         (GtkWidget           *widget,
							  GdkEventExpose      *event);
static void          eel_wrap_table_size_allocate        (GtkWidget           *widget,
							  GtkAllocation       *allocation);
static void          eel_wrap_table_map                  (GtkWidget           *widget);
static void          eel_wrap_table_unmap                (GtkWidget           *widget);
static void          eel_wrap_table_realize              (GtkWidget           *widget);

/* GtkContainerClass methods */
static void          eel_wrap_table_add                  (GtkContainer        *container,
							  GtkWidget           *widget);
static void          eel_wrap_table_remove               (GtkContainer        *container,
							  GtkWidget           *widget);
static void          eel_wrap_table_forall               (GtkContainer        *container,
							  gboolean             include_internals,
							  GtkCallback          callback,
							  gpointer             callback_data);
static GType         eel_wrap_table_child_type           (GtkContainer        *container);


/* Private EelWrapTable methods */
static EelDimensions wrap_table_irect_max_dimensions     (const EelDimensions *one,
							  const EelDimensions *two);
static EelDimensions wrap_table_get_max_child_dimensions (const EelWrapTable  *wrap_table);
static EelDimensions wrap_table_get_content_dimensions   (const EelWrapTable  *wrap_table);
static EelIRect      wrap_table_get_content_bounds       (const EelWrapTable  *wrap_table);
static gboolean      wrap_table_child_focus_in           (GtkWidget           *widget,
							  GdkEventFocus       *event,
							  gpointer             data);
static void          wrap_table_layout                   (EelWrapTable        *wrap_table);


EEL_CLASS_BOILERPLATE (EelWrapTable, eel_wrap_table, GTK_TYPE_CONTAINER)

/* Class init methods */
static void
eel_wrap_table_class_init (EelWrapTableClass *wrap_table_class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (wrap_table_class);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (wrap_table_class);
	GtkContainerClass *container_class = GTK_CONTAINER_CLASS (wrap_table_class);

	/* GObjectClass */
	gobject_class->finalize = eel_wrap_table_finalize;
	gobject_class->set_property = eel_wrap_table_set_property;
	gobject_class->get_property = eel_wrap_table_get_property;
	
 	/* GtkWidgetClass */
 	widget_class->size_request = eel_wrap_table_size_request;
	widget_class->size_allocate = eel_wrap_table_size_allocate;
 	widget_class->expose_event = eel_wrap_table_expose_event;
	widget_class->map = eel_wrap_table_map;
	widget_class->unmap = eel_wrap_table_unmap;
	widget_class->realize = eel_wrap_table_realize;

 	/* GtkContainerClass */
	container_class->add = eel_wrap_table_add;
	container_class->remove = eel_wrap_table_remove;
	container_class->forall = eel_wrap_table_forall;
	container_class->child_type = eel_wrap_table_child_type;

	/* Register some the enum types we need */
	eel_type_init ();

	/* Arguments */
	g_object_class_install_property
		(gobject_class,
		 PROP_X_SPACING,
		 g_param_spec_uint ("x_spacing", NULL, NULL,
                                    0, G_MAXINT, 0, G_PARAM_READWRITE));

	g_object_class_install_property
		(gobject_class,
		 PROP_Y_SPACING,
		 g_param_spec_uint ("y_spacing", NULL, NULL,
				    0, G_MAXINT, 0, G_PARAM_READWRITE));
	
	g_object_class_install_property
		(gobject_class,
		 PROP_X_JUSTIFICATION,
		 g_param_spec_enum ("x_justification", NULL, NULL,
				    EEL_TYPE_JUSTIFICATION,
				    EEL_JUSTIFICATION_BEGINNING,
				    G_PARAM_READWRITE));
				    
	g_object_class_install_property
		(gobject_class,
		 PROP_Y_JUSTIFICATION,
		 g_param_spec_enum ("y_justification", NULL, NULL,
				    EEL_TYPE_JUSTIFICATION,
				    EEL_JUSTIFICATION_BEGINNING,
				    G_PARAM_READWRITE));
				    
	g_object_class_install_property
		(gobject_class,
		 PROP_HOMOGENEOUS,
		 g_param_spec_boolean ("homogenous", NULL, NULL,
				       FALSE, G_PARAM_READWRITE));
}

static void
eel_wrap_table_init (EelWrapTable *wrap_table)
{
	gtk_widget_set_has_window (GTK_WIDGET (wrap_table), FALSE);

	wrap_table->details = g_new0 (EelWrapTableDetails, 1);
	wrap_table->details->x_justification = EEL_JUSTIFICATION_BEGINNING;
	wrap_table->details->y_justification = EEL_JUSTIFICATION_END;
	wrap_table->details->cols = 1;
}

static void
eel_wrap_table_finalize (GObject *object)
{
 	EelWrapTable *wrap_table;
	
	wrap_table = EEL_WRAP_TABLE (object);

	g_list_free (wrap_table->details->children);
	g_free (wrap_table->details);

	EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}

/* GObjectClass methods */

static void
eel_wrap_table_set_property (GObject      *object,
			     guint         property_id,
			     const GValue *value,
			     GParamSpec   *pspec)
{
	EelWrapTable *wrap_table;
	
	g_assert (EEL_IS_WRAP_TABLE (object));

 	wrap_table = EEL_WRAP_TABLE (object);

 	switch (property_id)
	{
	case PROP_X_SPACING:
		eel_wrap_table_set_x_spacing (wrap_table, g_value_get_uint (value));
		break;

	case PROP_Y_SPACING:
		eel_wrap_table_set_y_spacing (wrap_table, g_value_get_uint (value));
		break;

	case PROP_X_JUSTIFICATION:
		eel_wrap_table_set_x_justification (wrap_table, g_value_get_enum (value));
		break;

	case PROP_Y_JUSTIFICATION:
		eel_wrap_table_set_y_justification (wrap_table, g_value_get_enum (value));
		break;

	case PROP_HOMOGENEOUS:
		eel_wrap_table_set_homogeneous (wrap_table, g_value_get_boolean (value));
		break;

 	default:
		g_assert_not_reached ();
	}
}

static void
eel_wrap_table_get_property (GObject    *object,
			     guint       property_id,
			     GValue     *value,
			     GParamSpec *pspec)
{
	EelWrapTable *wrap_table;

	g_assert (EEL_IS_WRAP_TABLE (object));
	
	wrap_table = EEL_WRAP_TABLE (object);

 	switch (property_id)
	{
	case PROP_X_SPACING:
		g_value_set_uint (value, eel_wrap_table_get_x_spacing (wrap_table));
		break;

	case PROP_Y_SPACING:
		g_value_set_uint (value, eel_wrap_table_get_y_spacing (wrap_table));
		break;

	case PROP_X_JUSTIFICATION:
		g_value_set_enum (value, eel_wrap_table_get_x_justification (wrap_table));
		break;

	case PROP_Y_JUSTIFICATION:
		g_value_set_enum (value, eel_wrap_table_get_y_justification (wrap_table));
		break;

	case PROP_HOMOGENEOUS:
		g_value_set_boolean (value, eel_wrap_table_get_homogeneous (wrap_table));
		break;

 	default:
		g_assert_not_reached ();
	}
}

/* GtkWidgetClass methods */
static void
eel_wrap_table_size_request (GtkWidget *widget,
				  GtkRequisition *requisition)
{
	EelWrapTable *wrap_table;
	EelDimensions content_dimensions;

 	g_assert (EEL_IS_WRAP_TABLE (widget));
 	g_assert (requisition != NULL);

  	wrap_table = EEL_WRAP_TABLE (widget);

	content_dimensions = wrap_table_get_content_dimensions (wrap_table);

	/* The -1 tells Satan to use as much space as is available */
	requisition->width = -1;
	requisition->height = content_dimensions.height + gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
}

static void
eel_wrap_table_size_allocate (GtkWidget *widget,
				   GtkAllocation *allocation)
{
	EelWrapTable *wrap_table;

 	g_assert (EEL_IS_WRAP_TABLE (widget));
 	g_assert (allocation != NULL);
	
  	wrap_table = EEL_WRAP_TABLE (widget);
	
	gtk_widget_set_allocation (widget, allocation);

	wrap_table_layout (wrap_table);
}

static int
eel_wrap_table_expose_event (GtkWidget *widget,
			     GdkEventExpose *event)
{
	EelWrapTable *wrap_table;
	GList *iterator;
	
	g_assert (EEL_IS_WRAP_TABLE (widget));
	g_assert (gtk_widget_get_realized (widget));
	g_assert (event != NULL);

  	wrap_table = EEL_WRAP_TABLE (widget);

	for (iterator = wrap_table->details->children; iterator; iterator = iterator->next) {
		g_assert (GTK_IS_WIDGET (iterator->data));
		gtk_container_propagate_expose (GTK_CONTAINER (widget),
						GTK_WIDGET (iterator->data),
						event);
	}

	return FALSE;
}

static void
eel_wrap_table_map (GtkWidget *widget)
{
	EelWrapTable *wrap_table;
	GList *iterator;

	g_assert (EEL_IS_WRAP_TABLE (widget));
	
	wrap_table = EEL_WRAP_TABLE (widget);

	gtk_widget_set_mapped (widget, TRUE);
	
	for (iterator = wrap_table->details->children; iterator; iterator = iterator->next) {
		GtkWidget *item;

		item = iterator->data;

		if (gtk_widget_get_visible (item) && !gtk_widget_get_mapped (item)) {
			gtk_widget_map (item);
		}
	}
}

static void
eel_wrap_table_unmap (GtkWidget *widget)
{
	EelWrapTable *wrap_table;
	GList *iterator;

	g_assert (EEL_IS_WRAP_TABLE (widget));
	
	wrap_table = EEL_WRAP_TABLE (widget);

	gtk_widget_set_mapped (widget, FALSE);

	for (iterator = wrap_table->details->children; iterator; iterator = iterator->next) {
		GtkWidget *item;

		item = iterator->data;

		if (gtk_widget_get_visible (item) && gtk_widget_get_mapped (item)) {
			gtk_widget_unmap (item);
		}
	}
}

static void
eel_wrap_table_realize (GtkWidget *widget)
{
	g_assert (EEL_IS_WRAP_TABLE (widget));

	GTK_WIDGET_CLASS (parent_class)->realize (widget);

	gtk_widget_queue_resize (widget);
}

/* GtkContainerClass methods */
static void
eel_wrap_table_add (GtkContainer *container,
			GtkWidget *child)
{
	EelWrapTable *wrap_table;
	GtkWidget *widget;

	g_assert (container != NULL);
	g_assert (EEL_IS_WRAP_TABLE (container));
	g_assert (GTK_IS_WIDGET (child));

	widget = GTK_WIDGET (container);
	wrap_table = EEL_WRAP_TABLE (container);

	gtk_widget_set_parent (child, GTK_WIDGET (container));

	wrap_table->details->children = g_list_append (wrap_table->details->children, child);

	if (gtk_widget_get_realized (widget)) {
		gtk_widget_realize (child);
	}

	if (gtk_widget_get_visible (widget) && gtk_widget_get_visible (child)) {
		if (gtk_widget_get_mapped (widget)) {
			gtk_widget_map (child);
		}
		
		gtk_widget_queue_resize (child);
	}

	if (wrap_table->details->is_scrolled) {
		g_signal_connect (child, "focus_in_event", 
				  G_CALLBACK (wrap_table_child_focus_in), 
				  wrap_table);
	}
}

static void
eel_wrap_table_remove (GtkContainer *container,
			    GtkWidget *child)
{
	EelWrapTable *wrap_table;
	gboolean child_was_visible;
	
	g_assert (EEL_IS_WRAP_TABLE (container));
	g_assert (GTK_IS_WIDGET (child));
	
	wrap_table = EEL_WRAP_TABLE (container);;

	child_was_visible = gtk_widget_get_visible (child);
	gtk_widget_unparent (child);
	wrap_table->details->children = g_list_remove (wrap_table->details->children, child);

	if (child_was_visible) {
		gtk_widget_queue_resize (GTK_WIDGET (container));
	}

	if (wrap_table->details->is_scrolled) {
		g_signal_handlers_disconnect_by_func (
			child,
			G_CALLBACK (wrap_table_child_focus_in), 
			wrap_table);
	}
}

static void
eel_wrap_table_forall (GtkContainer *container,
			    gboolean include_internals,
			    GtkCallback callback,
			    gpointer callback_data)
{
	EelWrapTable *wrap_table;
	GList *node;
	GList *next;
	
	g_assert (EEL_IS_WRAP_TABLE (container));
	g_assert (callback != NULL);
	
	wrap_table = EEL_WRAP_TABLE (container);;
	
	for (node = wrap_table->details->children; node != NULL; node = next) {
		g_assert (GTK_IS_WIDGET (node->data));
		next = node->next;
		(* callback) (GTK_WIDGET (node->data), callback_data);	
	}
}

static GType
eel_wrap_table_child_type (GtkContainer   *container)
{
	return GTK_TYPE_WIDGET;
}

/* Private EelWrapTable methods */
static int
wrap_table_get_num_fitting (int available,
			    int spacing,
			    int max_child_size)
{
	int num;

  	g_assert (max_child_size > 0);
  	g_assert (spacing >= 0);

	available = MAX (available, 0);
	
	num = (available + spacing) / (max_child_size + spacing);
	num = MAX (num, 1);

	return num;
}

static void
wrap_table_layout (EelWrapTable *wrap_table)
{
	GList *iterator;
	EelIPoint pos;
	EelDimensions max_child_dimensions;
	EelIRect content_bounds;
	guint num_cols;
	GtkAllocation allocation;

 	g_assert (EEL_IS_WRAP_TABLE (wrap_table));

	max_child_dimensions = wrap_table_get_max_child_dimensions (wrap_table);
	content_bounds = wrap_table_get_content_bounds (wrap_table);
	pos.x = content_bounds.x0;
	pos.y = content_bounds.y0;

	gtk_widget_get_allocation (GTK_WIDGET (wrap_table), &allocation);
	num_cols = wrap_table_get_num_fitting (allocation.width -
					       gtk_container_get_border_width (GTK_CONTAINER (wrap_table)) * 2,
					       wrap_table->details->x_spacing,
					       max_child_dimensions.width);
	if (num_cols != wrap_table->details->cols) {
		wrap_table->details->cols = num_cols;
		gtk_widget_queue_resize (GTK_WIDGET (wrap_table));
		return;
	}

	for (iterator = wrap_table->details->children; iterator; iterator = iterator->next) {
		GtkWidget *item;

		item = iterator->data;
		
		if (gtk_widget_get_visible (item)) {
			GtkAllocation item_allocation;

			if (wrap_table->details->homogeneous) {
				item_allocation.x = pos.x;
				item_allocation.y = pos.y;
				item_allocation.width = max_child_dimensions.width;
				item_allocation.height = max_child_dimensions.height;
				
				if ((pos.x + max_child_dimensions.width) > content_bounds.x1) {
					pos.x = content_bounds.x0 + wrap_table->details->x_spacing + max_child_dimensions.width;
					pos.y += (max_child_dimensions.height + wrap_table->details->y_spacing);
					item_allocation.x = content_bounds.x0;
					item_allocation.y = pos.y;
				} else {
					pos.x += (wrap_table->details->x_spacing + max_child_dimensions.width);
				}
			} else {
				GtkRequisition item_requisition;
				
				gtk_widget_size_request (item, &item_requisition);
				
				item_allocation.x = pos.x;
				item_allocation.y = pos.y;
				item_allocation.width = item_requisition.width;
				item_allocation.height = item_requisition.height;
				
				g_assert (item_allocation.width <= max_child_dimensions.width);
				g_assert (item_allocation.height <= max_child_dimensions.height);
				
				if ((pos.x + max_child_dimensions.width) > content_bounds.x1) {
					pos.x = content_bounds.x0 + wrap_table->details->x_spacing + max_child_dimensions.width;
					pos.y += (max_child_dimensions.height + wrap_table->details->y_spacing);
					item_allocation.x = content_bounds.x0;
					item_allocation.y = pos.y;
				} else {
					pos.x += (wrap_table->details->x_spacing + max_child_dimensions.width);
				}
				
				switch (wrap_table->details->x_justification) {
				case EEL_JUSTIFICATION_MIDDLE:
					item_allocation.x += (max_child_dimensions.width - (int) item_allocation.width) / 2;
					break;
				case EEL_JUSTIFICATION_END:
					item_allocation.x += (max_child_dimensions.width - (int) item_allocation.width);
					break;
				default:
					break;
				}
				
				switch (wrap_table->details->y_justification) {
				case EEL_JUSTIFICATION_MIDDLE:
					item_allocation.y += (max_child_dimensions.height - (int) item_allocation.height) / 2;
					break;
				case EEL_JUSTIFICATION_END:
					item_allocation.y += (max_child_dimensions.height - (int) item_allocation.height);
					break;
				default:
					break;
				}
			}
			
			gtk_widget_size_allocate (item, &item_allocation);
		}
	}
}

static EelDimensions
wrap_table_irect_max_dimensions (const EelDimensions *one,
				     const EelDimensions *two)
{
	EelDimensions max_dimensions;

	g_assert (one != NULL);
	g_assert (two != NULL);

	max_dimensions.width = MAX (one->width, two->width);
	max_dimensions.height = MAX (one->height, two->height);

	return max_dimensions;
}

static EelDimensions
wrap_table_get_max_child_dimensions (const EelWrapTable *wrap_table)
{
	EelDimensions max_dimensions;
	GList *iterator;

  	g_assert (EEL_IS_WRAP_TABLE (wrap_table));

	max_dimensions = eel_dimensions_empty;

	for (iterator = wrap_table->details->children; iterator; iterator = iterator->next) {
		GtkWidget *child;
		
		child = iterator->data;
		
		if (gtk_widget_get_visible (child)) {
 			GtkRequisition child_requisition;
			EelDimensions child_dimensions;

 			gtk_widget_size_request (child, &child_requisition);

			child_dimensions.width = (int) child_requisition.width;
			child_dimensions.height = (int) child_requisition.height;

			max_dimensions = wrap_table_irect_max_dimensions (&child_dimensions, &max_dimensions);
		}
	}

	return max_dimensions;
}

static EelDimensions
wrap_table_get_content_dimensions (const EelWrapTable *wrap_table)
{
	EelDimensions content_dimensions;
	guint num_children;

  	g_assert (EEL_IS_WRAP_TABLE (wrap_table));

	content_dimensions = eel_dimensions_empty;
	
	num_children = g_list_length (wrap_table->details->children);

	if (num_children > 0) {
		EelDimensions max_child_dimensions;
		EelDimensions dimensions;
		int num_cols;
		int num_rows;

		dimensions = eel_gtk_widget_get_dimensions (GTK_WIDGET (wrap_table));
		max_child_dimensions = wrap_table_get_max_child_dimensions (wrap_table);

		max_child_dimensions.width = MAX (max_child_dimensions.width, 1);
		max_child_dimensions.height = MAX (max_child_dimensions.height, 1);

		num_cols = wrap_table_get_num_fitting (dimensions.width -
						       gtk_container_get_border_width (GTK_CONTAINER (wrap_table)) * 2,
						       wrap_table->details->x_spacing,
						       max_child_dimensions.width);
		num_rows = num_children / num_cols;
		num_rows = MAX (num_rows, 1);
		
		if ((num_children % num_rows) > 0) {
			num_rows++;
		}
		
		content_dimensions.width = dimensions.width;
		content_dimensions.height = num_rows * max_child_dimensions.height;

		content_dimensions.width += (num_cols - 1) * wrap_table->details->x_spacing;
		content_dimensions.height += (num_rows - 1) * wrap_table->details->y_spacing;
	}

	return content_dimensions;
}

static EelIRect
wrap_table_get_content_bounds (const EelWrapTable *wrap_table)
{
	EelIRect content_bounds;
	guint border;

  	g_assert (EEL_IS_WRAP_TABLE (wrap_table));

	content_bounds = eel_gtk_widget_get_bounds (GTK_WIDGET (wrap_table));

	border = gtk_container_get_border_width (GTK_CONTAINER (wrap_table));
	content_bounds.x0 += border;
	content_bounds.y0 += border;
	content_bounds.x1 -= border;
	content_bounds.y1 -= border;

	return content_bounds;
}

static gboolean
wrap_table_child_focus_in (GtkWidget *widget, 
			   GdkEventFocus *event, 
			   gpointer data)
{
	GtkWidget *parent, *pparent;
	GtkAllocation allocation;

	parent = gtk_widget_get_parent (widget);
	if (parent)
		pparent = gtk_widget_get_parent (parent);
	g_assert (parent && pparent);
	g_assert (GTK_IS_VIEWPORT (pparent));

	gtk_widget_get_allocation (widget, &allocation);
	eel_gtk_viewport_scroll_to_rect (GTK_VIEWPORT (pparent),
					 &allocation);
	
	return FALSE;
}

/**
 * eel_wrap_table_new:
 *
 */
GtkWidget*
eel_wrap_table_new (gboolean homogeneous)
{
	EelWrapTable *wrap_table;

	wrap_table = EEL_WRAP_TABLE (gtk_widget_new (eel_wrap_table_get_type (), NULL));

	eel_wrap_table_set_homogeneous (wrap_table, homogeneous);
	
	return GTK_WIDGET (wrap_table);
}

/**
 * eel_wrap_table_set_x_spacing:
 * @wrap_table: A EelWrapTable.
 * @x_spacing: The new horizontal spacing between wraps.
 *
 */
void
eel_wrap_table_set_x_spacing (EelWrapTable *wrap_table,
				   guint x_spacing)
{
	g_return_if_fail (EEL_IS_WRAP_TABLE (wrap_table));
	
	if (wrap_table->details->x_spacing == x_spacing) {
		return;
	}
	
	wrap_table->details->x_spacing = x_spacing;

	gtk_widget_queue_resize (GTK_WIDGET (wrap_table));
}

/**
 * eel_wrap_table_get_item_spacing:
 * @wrap_table: A EelWrapTable.
 *
 * Returns: The horizontal spacing between wraps.
 */
guint
eel_wrap_table_get_x_spacing (const EelWrapTable *wrap_table)
{
	g_return_val_if_fail (EEL_IS_WRAP_TABLE (wrap_table), 0);
	
	return wrap_table->details->x_spacing;
}

/**
 * eel_wrap_table_set_y_spacing:
 * @wrap_table: A EelWrapTable.
 * @y_spacing: The new horizontal spacing between wraps.
 *
 */
void
eel_wrap_table_set_y_spacing (EelWrapTable *wrap_table,
				   guint y_spacing)
{
	g_return_if_fail (EEL_IS_WRAP_TABLE (wrap_table));
	
	if (wrap_table->details->y_spacing == y_spacing) {
		return;
	}
	
	wrap_table->details->y_spacing = y_spacing;

	gtk_widget_queue_resize (GTK_WIDGET (wrap_table));
}

/**
 * eel_wrap_table_get_item_spacing:
 * @wrap_table: A EelWrapTable.
 *
 * Returns: The horizontal spacing between wraps.
 */
guint
eel_wrap_table_get_y_spacing (const EelWrapTable *wrap_table)
{
	g_return_val_if_fail (EEL_IS_WRAP_TABLE (wrap_table), 0);
	
	return wrap_table->details->y_spacing;
}


/**
 * eel_wrap_table_find_child_at_event_point:
 * @wrap_table: A EelWrapTable.
 * @x: Event x;
 * @y: Event y;
 *
 * Returns: Child found at given coordinates or NULL of no child is found.
 */
GtkWidget *
eel_wrap_table_find_child_at_event_point (const EelWrapTable *wrap_table,
					  int x,
					  int y)
{
	GList *iterator;
	
  	g_return_val_if_fail (EEL_IS_WRAP_TABLE (wrap_table), NULL);

	for (iterator = wrap_table->details->children; iterator; iterator = iterator->next) {
		GtkWidget *child;
		
		child = iterator->data;
		
		if (gtk_widget_get_visible (child)) {
			EelIRect child_bounds;

			child_bounds = eel_gtk_widget_get_bounds (child);
 			
			if (eel_irect_contains_point (child_bounds, x, y)) {
				return child;
			}
		}
	}

	return NULL;
}

/**
 * eel_wrap_table_set_x_justification:
 * @wrap_table: A EelWrapTable.
 * @x_justification: The new horizontal justification between wraps.
 *
 */
void
eel_wrap_table_set_x_justification (EelWrapTable *wrap_table,
					 EelJustification x_justification)
{
	g_return_if_fail (EEL_IS_WRAP_TABLE (wrap_table));
	g_return_if_fail (x_justification >= EEL_JUSTIFICATION_BEGINNING);
	g_return_if_fail (x_justification <= EEL_JUSTIFICATION_END);
	
	if (wrap_table->details->x_justification == x_justification) {
		return;
	}
	
	wrap_table->details->x_justification = x_justification;
	gtk_widget_queue_resize (GTK_WIDGET (wrap_table));
}

/**
 * eel_wrap_table_get_item_justification:
 * @wrap_table: A EelWrapTable.
 *
 * Returns: The horizontal justification between wraps.
 */
EelJustification 
eel_wrap_table_get_x_justification (const EelWrapTable *wrap_table)
{
	g_return_val_if_fail (EEL_IS_WRAP_TABLE (wrap_table), 0);
	
	return wrap_table->details->x_justification;
}

/**
 * eel_wrap_table_set_y_justification:
 * @wrap_table: A EelWrapTable.
 * @y_justification: The new horizontal justification between wraps.
 *
 */
void
eel_wrap_table_set_y_justification (EelWrapTable *wrap_table,
					 EelJustification y_justification)
{
	g_return_if_fail (EEL_IS_WRAP_TABLE (wrap_table));
	g_return_if_fail (y_justification >= EEL_JUSTIFICATION_BEGINNING);
	g_return_if_fail (y_justification <= EEL_JUSTIFICATION_END);
	
	if (wrap_table->details->y_justification == y_justification) {
		return;
	}
	
	wrap_table->details->y_justification = y_justification;
	gtk_widget_queue_resize (GTK_WIDGET (wrap_table));
}

/**
 * eel_wrap_table_get_item_justification:
 * @wrap_table: A EelWrapTable.
 *
 * Returns: The horizontal justification between wraps.
 */
EelJustification 
eel_wrap_table_get_y_justification (const EelWrapTable *wrap_table)
{
	g_return_val_if_fail (EEL_IS_WRAP_TABLE (wrap_table), 0);
	
	return wrap_table->details->y_justification;
}

/**
 * eel_wrap_table_set_homogeneous:
 * @wrap_table: A EelWrapTable.
 * @homogeneous: The new horizontal spacing between wraps.
 *
 */
void
eel_wrap_table_set_homogeneous (EelWrapTable *wrap_table,
				     gboolean homogeneous)
{
	g_return_if_fail (EEL_IS_WRAP_TABLE (wrap_table));
	
	if (wrap_table->details->homogeneous == homogeneous) {
		return;
	}
	
	wrap_table->details->homogeneous = homogeneous;

	gtk_widget_queue_resize (GTK_WIDGET (wrap_table));
}

/**
 * eel_wrap_table_get_item_spacing:
 * @wrap_table: A EelWrapTable.
 *
 * Returns: The horizontal spacing between wraps.
 */
gboolean
eel_wrap_table_get_homogeneous (const EelWrapTable *wrap_table)
{
	g_return_val_if_fail (EEL_IS_WRAP_TABLE (wrap_table), FALSE);
	
	return wrap_table->details->homogeneous;
}

/**
 * eel_wrap_table_reorder_child:
 * @wrap_table: A EelWrapTable.
 * @child: Child to reorder.
 * @position: New position to put child at.
 *
 * Reorder the given chilren into the given position.
 *
 * Position is interpreted as follows:
 *
 *  0 - Place child at start of table.
 * -1 - Place child at end of table.
 *  n - Place child at nth position.  Count starts at 0.
 */
void
eel_wrap_table_reorder_child (EelWrapTable *wrap_table,
				   GtkWidget *child,
				   int position)
{
	GList *node;
	gboolean found_child = FALSE;

	g_return_if_fail (EEL_IS_WRAP_TABLE (wrap_table));
	g_return_if_fail (g_list_length (wrap_table->details->children) > 0);

	if (position == -1) {
		position = g_list_length (wrap_table->details->children) - 1;
	}

	g_return_if_fail (position >= 0);
	g_return_if_fail ((guint) position < g_list_length (wrap_table->details->children));

	for (node = wrap_table->details->children; node != NULL; node = node->next) {
		GtkWidget *next_child;
		next_child = node->data;
		
		if (next_child == child) {
			g_assert (found_child == FALSE);
			found_child = TRUE;
		}
	}

	g_return_if_fail (found_child);

	wrap_table->details->children = g_list_remove (wrap_table->details->children, child);
	wrap_table->details->children = g_list_insert (wrap_table->details->children, child, position);

	gtk_widget_queue_resize (GTK_WIDGET (wrap_table));
}

/**
 * eel_wrap_table_get_num_children:
 * @wrap_table: A EelWrapTable.
 *
 * Returns: The number of children being managed by the wrap table.
 */
guint
eel_wrap_table_get_num_children (const EelWrapTable *wrap_table)
{
	g_return_val_if_fail (EEL_IS_WRAP_TABLE (wrap_table), 0);
	
	return g_list_length (wrap_table->details->children);
}

GtkWidget *
eel_scrolled_wrap_table_new (gboolean homogenous,
			     GtkShadowType shadow_type,
			     GtkWidget **wrap_table_out)
{
	GtkWidget *scrolled_window;
	GtkWidget *wrap_table;
	GtkWidget *viewport;
	
	g_return_val_if_fail (wrap_table_out != NULL, NULL);
	
	scrolled_window = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
					GTK_POLICY_NEVER,
					GTK_POLICY_AUTOMATIC);

	viewport = gtk_viewport_new (gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (scrolled_window)),
				     gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window)));
	gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport),
				      shadow_type);
	
	gtk_container_add (GTK_CONTAINER (scrolled_window),
			   viewport);
	
	wrap_table = eel_wrap_table_new (homogenous);
	gtk_container_add (GTK_CONTAINER (viewport),
			   wrap_table);

	gtk_widget_show (wrap_table);
	gtk_widget_show (viewport);

	EEL_WRAP_TABLE (wrap_table)->details->is_scrolled = 1;

	*wrap_table_out = wrap_table;
	return scrolled_window;
}
