/* gtkassistant.c
 * Copyright (C) 1999  Red Hat, Inc.
 * Copyright (C) 2002  Anders Carlsson <andersca@gnu.org>
 * Copyright (C) 2003  Matthias Clasen <maclas@gmx.de>
 *
 * 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 "gtkassistant.h"
#include <gtk/gtk.h>
#include <gtk/gtkintl.h>
#include <gtk/gtkmarshalers.h>

/* based on GnomeDruid and EggDruid */

static void gtk_assistant_class_init (GtkAssistantClass *klass);
static void gtk_assistant_init       (GtkAssistant      *assistant);
static void gtk_assistant_destroy    (GtkObject     *object);

static void gtk_assistant_size_request  (GtkWidget      *widget,
				     GtkRequisition *requisition);
static void gtk_assistant_size_allocate (GtkWidget      *widget,
				     GtkAllocation  *allocation);
static void gtk_assistant_map           (GtkWidget      *widget);
static void gtk_assistant_unmap         (GtkWidget       *widget);

static void gtk_assistant_add    (GtkContainer *container,
			      GtkWidget    *page);
static void gtk_assistant_remove (GtkContainer *container,
			      GtkWidget    *page);
static void gtk_assistant_forall (GtkContainer *container,
			      gboolean      include_internals,
			      GtkCallback   callback,
			      gpointer      callback_data);
static void gtk_assistant_set_child_property (GtkContainer      *container,
					  GtkWidget         *child,
					  guint              property_id,
					  const GValue      *value,
					  GParamSpec        *pspec);
static void gtk_assistant_get_child_property (GtkContainer      *container,
					  GtkWidget         *child,
					  guint              property_id,
					  GValue            *value,
					  GParamSpec        *pspec);
static void    gtk_assistant_back_callback   (GtkWidget         *button,
					      GtkAssistant          *assistant);
static void    gtk_assistant_next_callback   (GtkWidget         *button,
					      GtkAssistant          *assistant);
static void    gtk_assistant_cancel_callback (GtkWidget         *button,
					      GtkAssistant          *assistant);
static void    gtk_assistant_close_callback (GtkWidget         *button,
					     GtkAssistant          *assistant);

enum
{
  CHILD_PROP_0,
  CHILD_PROP_PAGE_TYPE,
  CHILD_PROP_TITLE
};

enum 
{
  CANCEL,
  PREPARE,
  BACK,
  NEXT,
  FINISH,
  CLOSE,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

#define GTK_ASSISTANT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),                  \
								     GTK_TYPE_ASSISTANT,     \
								     GtkAssistantPrivate))

typedef struct _GtkAssistantPage GtkAssistantPage;

struct _GtkAssistantPage
{
  GtkWidget *page;
  gchar *title;
  GtkAssistantPageType type;
};

struct _GtkAssistantPrivate
{
  GtkWidget *header;
  GtkWidget *header_box;
  GtkWidget *header_image;
  GtkWidget *header_title;
  GtkWidget *header_description;
  
  GtkWidget *sidebar;
  GtkWidget *sidebar_image;
  
  GtkWidget *back;
  GtkWidget *next;
  GtkWidget *cancel;
  GtkWidget *finish;
  GtkWidget *close;
  GtkWidget *bbox;

  GtkWidget *hbox;
  GtkWidget *content;

  GList *pages;
  GtkWidget *current;
};

static GtkContainerClass *parent_class = NULL;

GType
gtk_assistant_get_type (void)
{
  static GType our_type = 0;

  if (!our_type)
    {
      static const GTypeInfo our_type_info =
      {
	sizeof (GtkAssistantClass),
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) gtk_assistant_class_init,
	(GClassFinalizeFunc) NULL,
	NULL,
	sizeof (GtkAssistant),
	0, /* n_preallocs */
	(GInstanceInitFunc) gtk_assistant_init
      };

      our_type = g_type_register_static (GTK_TYPE_CONTAINER, "GtkAssistant",
					&our_type_info, 0);
    }

  return our_type;
}

static void
gtk_assistant_class_init (GtkAssistantClass *klass)
{
  GObjectClass *gobject_class;
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;
  GtkContainerClass *container_class;
  
  parent_class = g_type_class_peek_parent (klass);

  gobject_class = (GObjectClass*) klass;
  object_class = (GtkObjectClass*) klass;
  widget_class = (GtkWidgetClass *)klass;
  container_class = (GtkContainerClass *)klass;

  object_class->destroy = gtk_assistant_destroy;
  
  widget_class->size_request = gtk_assistant_size_request;
  widget_class->size_allocate = gtk_assistant_size_allocate;
  widget_class->map = gtk_assistant_map;
  widget_class->unmap = gtk_assistant_unmap;

  container_class->add = gtk_assistant_add;
  container_class->remove = gtk_assistant_remove;
  container_class->forall = gtk_assistant_forall;
  container_class->set_child_property = gtk_assistant_set_child_property;
  container_class->get_child_property = gtk_assistant_get_child_property;

  /**
   * GtkAssistant::cancel:
   * @assistant: the #GtkAssistant
   * @page: the current page
   *
   * The ::cancel signal is emitted when then the cancel button is clicked.
   */
  signals[CANCEL] =
    g_signal_new ("cancel",
		  G_TYPE_FROM_CLASS (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkAssistantClass, cancel),
		  NULL, NULL,
		  g_cclosure_marshal_VOID__OBJECT,
		  G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
  
  
  /**
   * GtkAssistant::prepare:
   * @assistant: the #GtkAssistant
   * @page: the current page
   *
   * The ::prepared signal is emitted when a new page is set as the assistant's 
   * current page, before making the new page visible. A handler for this signal 
   * can do any preparation which are necessary before showing @page, e.g. use 
   * gtk_assistant_set_buttons_sensitive() to make the next button insensitive until 
   * some required fields are filled in.
   */
  signals[PREPARE] =
    g_signal_new ("prepare",
		  G_TYPE_FROM_CLASS (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkAssistantClass, prepare),
		  NULL, NULL,
		  g_cclosure_marshal_VOID__OBJECT,
		  G_TYPE_NONE, 1, GTK_TYPE_WIDGET);

  /**
   * GtkAssistant::back:
   * @assistant: the @GtkAssistant
   * @page: the current page
   *
   * The ::back signal is emitted when the back button is clicked. The default
   * behavior of the #GtkAssistant is to switch to the page before the current page.
   * For more complicated, nonlinear wizards, a handler for this signal can use
   * gtk_assistant_set_current_page() to go to a different page and return %TRUE
   * to suppress the default behavior.
   *
   * Return value: %TRUE to suppress the default behavior
   */
  signals[BACK] =
    g_signal_new ("back",
		  G_TYPE_FROM_CLASS (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkAssistantClass, back),
		  NULL, NULL,
		  _gtk_marshal_BOOLEAN__OBJECT,
		  G_TYPE_BOOLEAN, 1, GTK_TYPE_WIDGET);

  /**
   * GtkAssistant::next:
   * @assistant: the @GtkAssistant
   * @page: the current page
   *
   * The ::next signal is emitted when the next button is clicked. The default
   * behavior of the #GtkAssistant is to switch to the page after the current page.
   * For more complicated, nonlinear wizards, a handler for this signal can use
   * gtk_assistant_set_current_page() to go to a different page and return %TRUE
   * to suppress the default behavior.
   *
   * Return value: %TRUE to suppress the default behavior
   */
  signals[NEXT] =
    g_signal_new ("next",
		  G_TYPE_FROM_CLASS (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkAssistantClass, next),
		  NULL, NULL,
		  _gtk_marshal_BOOLEAN__OBJECT,
		  G_TYPE_BOOLEAN, 1, GTK_TYPE_WIDGET);

  /**
   * GtkAssistant::finish:
   * @assistant: the @GtkAssistant
   * @page: the current page
   *
   * The ::finish signal is emitted when the finish button is clicked. The default
   * behavior of the #GtkAssistant is to switch to the page after the current page,
   * unless the current page is the last one.
   *
   * A handler for the ::finish signal should carry out the actions for which the
   * wizard has collected data. If the action takes a long time to complete, you
   * might consider to put a page displaying the progress of the operation after the
   * confirmation page with the finish button.
   *
   * Return value: %TRUE to suppress the default behavior
   */
  signals[FINISH] =
    g_signal_new ("finish",
		  G_TYPE_FROM_CLASS (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkAssistantClass, finish),
		  NULL, NULL,
		  _gtk_marshal_BOOLEAN__OBJECT,
		  G_TYPE_BOOLEAN, 1, GTK_TYPE_WIDGET);

  /**
   * GtkAssistant::close:
   * @assistant: the #GtkAssistant
   * @page: the current page
   *
   * The ::close signal is emitted when the close button is clicked.
   */
  signals[CLOSE] =
    g_signal_new ("close",
		  G_TYPE_FROM_CLASS (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkAssistantClass, close),
		  NULL, NULL,
		  g_cclosure_marshal_VOID__OBJECT,
		  G_TYPE_NONE, 1, GTK_TYPE_WIDGET);

  gtk_widget_class_install_style_property (widget_class,
					   g_param_spec_int ("header_padding",
							     _("Header Padding"),
							     _("Number of pixels around the header."),
							     0,
							     G_MAXINT,
							     4,
							     G_PARAM_READABLE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_PAGE_TYPE,
					      g_param_spec_enum ("page_type", 
								 _("Page type"),
								 _("The type of the assistant page"),
								 GTK_TYPE_ASSISTANT_PAGE_TYPE,
								 GTK_ASSISTANT_PAGE_CONTENT,
								 G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TITLE,
					      g_param_spec_string ("title", 
								    _("Title"),
								    _("The title of the assistant page"),
								    NULL,
								    G_PARAM_READWRITE));

  g_type_class_add_private (gobject_class, sizeof (GtkAssistantPrivate));
}

static void
gtk_assistant_init (GtkAssistant *assistant)
{
  GtkWidget *vbox, *align;
  PangoFontDescription *font_desc;
  GtkAssistantPrivate *priv;
  
  GTK_WIDGET_SET_FLAGS (assistant, GTK_NO_WINDOW);

  priv = GTK_ASSISTANT_GET_PRIVATE (assistant);
  assistant->private_data = priv;
  gtk_widget_push_composite_child ();
  priv->sidebar = gtk_event_box_new ();
  gtk_widget_set_parent (priv->sidebar, GTK_WIDGET (assistant));
  priv->sidebar_image = gtk_image_new ();
  gtk_container_add (GTK_CONTAINER (priv->sidebar), priv->sidebar_image);

  priv->header = gtk_event_box_new ();
  gtk_widget_set_parent (priv->header, GTK_WIDGET (assistant));
  priv->header_box = gtk_hbox_new (FALSE, 0);
  gtk_container_add (GTK_CONTAINER (priv->header), priv->header_box);
  priv->header_image = gtk_image_new ();
  gtk_box_pack_end (GTK_BOX (priv->header_box), priv->header_image, FALSE, FALSE, 0);

  vbox = gtk_vbox_new (FALSE, 0);
  align = gtk_alignment_new (0.0, 0.5, 1.0, 0.0);
  gtk_container_add (GTK_CONTAINER (align), vbox);
  gtk_box_pack_start (GTK_BOX (priv->header_box), align, TRUE, TRUE, 0);

  priv->header_title = gtk_label_new (NULL);
  gtk_misc_set_alignment (GTK_MISC (priv->header_title), 0.0, 0.5);
  font_desc = pango_font_description_from_string ("bold");
  gtk_widget_modify_font (priv->header_title, font_desc);
  pango_font_description_free (font_desc);
  gtk_label_set_text (GTK_LABEL (priv->header_title), "Test");
  gtk_box_pack_start (GTK_BOX (vbox), priv->header_title, FALSE, FALSE, 0);

  priv->header_description = gtk_label_new (NULL);
  gtk_misc_set_alignment (GTK_MISC (priv->header_description), 0.0, 0.5);
  gtk_label_set_markup (GTK_LABEL (priv->header_description), "A nice little description");
  gtk_box_pack_start (GTK_BOX (vbox), priv->header_description, FALSE, FALSE, 0);
  
  gtk_widget_show_all (priv->header);

  /* set up the buttons */
  priv->back = gtk_button_new_from_stock (GTK_STOCK_GO_BACK);
  GTK_WIDGET_SET_FLAGS (priv->back, GTK_CAN_DEFAULT);
  priv->next = gtk_button_new_from_stock (GTK_STOCK_GO_FORWARD);
  GTK_WIDGET_SET_FLAGS (priv->next, GTK_CAN_DEFAULT);
  priv->cancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
  GTK_WIDGET_SET_FLAGS (priv->cancel, GTK_CAN_DEFAULT);
  priv->finish = gtk_button_new_from_stock (GTK_STOCK_APPLY);
  GTK_WIDGET_SET_FLAGS (priv->finish, GTK_CAN_DEFAULT);
  priv->close = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
  GTK_WIDGET_SET_FLAGS (priv->close, GTK_CAN_DEFAULT);
  gtk_widget_show (priv->next);
  gtk_widget_show (priv->cancel);

  priv->bbox = gtk_hbutton_box_new ();
  gtk_button_box_set_layout (GTK_BUTTON_BOX (priv->bbox), GTK_BUTTONBOX_END);
  /* FIXME: use a style property for these */
  gtk_box_set_spacing (GTK_BOX (priv->bbox), 10);
  gtk_container_set_border_width (GTK_CONTAINER (priv->bbox), 5);
  gtk_widget_set_parent (priv->bbox, GTK_WIDGET (assistant));
  gtk_widget_show (priv->bbox);
  
  gtk_box_pack_end (GTK_BOX (priv->bbox), priv->cancel, FALSE, TRUE, 0);
  gtk_box_pack_end (GTK_BOX (priv->bbox), priv->back, FALSE, TRUE, 0);
  gtk_box_pack_end (GTK_BOX (priv->bbox), priv->next, FALSE, TRUE, 0);
  gtk_box_pack_end (GTK_BOX (priv->bbox), priv->finish, FALSE, TRUE, 0);
  gtk_box_pack_end (GTK_BOX (priv->bbox), priv->close, FALSE, TRUE, 0);

  gtk_widget_pop_composite_child ();

  priv->pages = NULL;
  priv->current = NULL;

  g_signal_connect (priv->back, "clicked",
		    G_CALLBACK (gtk_assistant_back_callback), assistant);
  g_signal_connect (priv->next, "clicked",
		    G_CALLBACK (gtk_assistant_next_callback), assistant);
  g_signal_connect (priv->finish, "clicked",
		    G_CALLBACK (gtk_assistant_next_callback), assistant);
  g_signal_connect (priv->cancel, "clicked",
		    G_CALLBACK (gtk_assistant_cancel_callback), assistant);
  g_signal_connect (priv->close, "clicked",
		    G_CALLBACK (gtk_assistant_close_callback), assistant);
  
  gtk_widget_grab_focus (priv->next);
}

static void
gtk_assistant_set_child_property (GtkContainer    *container,
				  GtkWidget       *child,
				  guint            property_id,
				  const GValue    *value,
				  GParamSpec      *pspec)
{
  switch (property_id)
    {
    case CHILD_PROP_PAGE_TYPE:
      gtk_assistant_set_page_type (GTK_ASSISTANT (container), child,
				   g_value_get_enum (value));
      break;
    case CHILD_PROP_TITLE:
      gtk_assistant_set_page_title (GTK_ASSISTANT (container), child,
				    g_value_get_string (value));
      break;
    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      break;
    }
}

static void
gtk_assistant_get_child_property (GtkContainer *container,
				  GtkWidget    *child,
				  guint         property_id,
				  GValue       *value,
				  GParamSpec   *pspec)
{
  switch (property_id)
    {
    case CHILD_PROP_PAGE_TYPE:
      g_value_set_enum (value, 
			gtk_assistant_get_page_type (GTK_ASSISTANT (container), child));
      break;
    case CHILD_PROP_TITLE:
      g_value_set_string (value, 
			  gtk_assistant_get_page_title (GTK_ASSISTANT (container), child));
      break;
    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      break;
    }
}

static GList *
find_page (GtkAssistant  *assistant,
	   GtkWidget *page)
{
  GtkAssistantPrivate *priv = GTK_ASSISTANT_GET_PRIVATE (assistant);
  GList *child = priv->pages;
  
  while (child)
    {
      GtkAssistantPage *page_info = child->data;
      if (page_info->page == page)
	return child;

      child = child->next;
    }
  
  return NULL;
}


/**
 * gtk_assistant_set_page_title:
 * @assistant: a #GtkAssistant
 * @page: a page of @assitant
 * @title: the new title for @page
 * 
 * Sets a title for @page. The title is displayed in the header
 * area of the assistant when @page is the current page.
 **/
void
gtk_assistant_set_page_title (GtkAssistant    *assistant,
			      GtkWidget   *page,
			      const gchar *title)
{
  GList *child;
  GtkAssistantPage *page_info;

  child = find_page (assistant, page);

  g_return_if_fail (child != NULL);
  
  page_info = child->data;
  
  if (title != page_info->title) 
    {
      g_free (page_info->title);
      page_info->title = g_strdup (title);

      gtk_widget_child_notify (page, "title");
    }
}

/**
 * gtk_assistant_get_page_title:
 * @assistant: a #GtkAssistant
 * @page: a page of @assitant
 * 
 * Gets the title for @page. 
 * 
 * Return value: the title for @page.
 **/
gchar *
gtk_assistant_get_page_title (GtkAssistant    *assistant,
			      GtkWidget   *page)
{
  GList *child;
  GtkAssistantPage *page_info;
  
  child = find_page (assistant, page);
  
  g_return_val_if_fail (child != NULL, NULL);
  
  page_info = child->data;
  
  return page_info->title;
}

static void
gtk_assistant_size_request (GtkWidget      *widget,
			    GtkRequisition *requisition)
{
  GtkAssistant *assistant;
  GtkAssistantPrivate *priv;
  GtkRequisition child_requisition;
  gint temp_width, temp_height;
  gint header_padding;
  GList *list;
  
  assistant = GTK_ASSISTANT (widget);
  priv = GTK_ASSISTANT_GET_PRIVATE (assistant);

  gtk_widget_style_get (widget, "header_padding", &header_padding, NULL);
  gtk_container_set_border_width (GTK_CONTAINER (priv->header_box), header_padding);
  
  temp_width = 0;
  temp_height = 0;

  list = priv->pages;
  while (list)
    {
      GtkAssistantPage *page_info = list->data;
      GtkWidget *child = page_info->page;

      if (GTK_WIDGET_VISIBLE (child))
	{
	  gtk_widget_size_request (GTK_WIDGET (child), &child_requisition);
	  temp_width = MAX (temp_width, child_requisition.width);
	  temp_height = MAX (temp_height, child_requisition.height);
	  if (GTK_WIDGET_MAPPED (child) && child != priv->current)
	    gtk_widget_unmap (GTK_WIDGET (child));
	}
      
      list = list->next;
    }

  gtk_widget_size_request (priv->header, &child_requisition);
  temp_width = MAX (temp_width, child_requisition.width);
  temp_height += child_requisition.height;
  
  temp_width += GTK_CONTAINER (assistant)->border_width * 2;
  temp_height += GTK_CONTAINER (assistant)->border_width * 2;
  
  gtk_widget_size_request (priv->bbox, &child_requisition);

  temp_width = MAX (temp_width, child_requisition.width);
  temp_height += child_requisition.height;

  gtk_widget_size_request (priv->sidebar, &child_requisition);
  temp_height = MAX (temp_height, child_requisition.height);
  temp_width += child_requisition.width;

  requisition->width = temp_width;
  requisition->height = temp_height;
}

static void
gtk_assistant_size_allocate (GtkWidget      *widget,
			     GtkAllocation  *allocation)
{
  GtkAssistant *assistant;
  GtkAssistantPrivate *priv;
  GtkAllocation child_allocation;
  GList *list;
  gint button_height;
  gboolean is_rtl;
  
  assistant = GTK_ASSISTANT (widget);
  priv = GTK_ASSISTANT_GET_PRIVATE (assistant);

  is_rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);

  widget->allocation = *allocation;

  button_height = priv->bbox->requisition.height;
  
  if (is_rtl) 
    child_allocation.x = allocation->x + allocation->width - GTK_CONTAINER (widget)->border_width
      - priv->sidebar->requisition.width;
  else
    child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width;
  child_allocation.y = allocation->y + GTK_CONTAINER (widget)->border_width;
  child_allocation.width = priv->sidebar->requisition.width;
  child_allocation.height = allocation->height - 2 * GTK_CONTAINER (widget)->border_width;

  gtk_widget_size_allocate (priv->sidebar, &child_allocation);

  if (is_rtl)
    child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width;
  else
    child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width + priv->sidebar->allocation.width;
  child_allocation.y = allocation->y + GTK_CONTAINER (widget)->border_width;
  child_allocation.height = priv->header->requisition.height;
  child_allocation.width = allocation->width - GTK_CONTAINER (widget)->border_width * 2 -
    priv->sidebar->allocation.width;
  gtk_widget_size_allocate (priv->header, &child_allocation);

  if (is_rtl)
    child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width;
  else
    child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width + priv->sidebar->allocation.width;
  child_allocation.y = allocation->y + allocation->height - button_height;
  child_allocation.height = button_height;
  child_allocation.width = allocation->width - GTK_CONTAINER (widget)->border_width * 2 -
    priv->sidebar->allocation.width;
  gtk_widget_size_allocate (priv->bbox, &child_allocation);

  if (is_rtl)
    child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width;
  else
    child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width + priv->sidebar->allocation.width;
  child_allocation.y = allocation->y + GTK_CONTAINER (widget)->border_width + priv->header->allocation.height;
  child_allocation.width = allocation->width - GTK_CONTAINER (widget)->border_width * 2 -
    priv->sidebar->allocation.width;
  child_allocation.height = allocation->height - GTK_CONTAINER (widget)->border_width * 2 -
    priv->header->allocation.height - button_height;
  
  list = priv->pages;
  while (list)
    {
      GtkAssistantPage *page_info = list->data;
      GtkWidget *page = page_info->page;

      if (GTK_WIDGET_VISIBLE (page))
	gtk_widget_size_allocate (GTK_WIDGET (page), &child_allocation);
      
      list = list->next;
    }
}

static void
gtk_assistant_map (GtkWidget *widget)
{
  GtkAssistant *assistant;
  
  assistant = GTK_ASSISTANT (widget);

  GTK_WIDGET_SET_FLAGS (assistant, GTK_MAPPED);

  if (assistant->private_data->sidebar &&
      GTK_WIDGET_VISIBLE (assistant->private_data->sidebar) &&
      !GTK_WIDGET_MAPPED (assistant->private_data->sidebar))
    gtk_widget_map (assistant->private_data->sidebar);
  gtk_widget_map (assistant->private_data->header);
  gtk_widget_map (assistant->private_data->bbox);

  if (assistant->private_data->current &&
      GTK_WIDGET_VISIBLE (assistant->private_data->current) &&
      !GTK_WIDGET_MAPPED (assistant->private_data->current))
    {
      g_signal_emit (assistant, signals [PREPARE], 0, assistant->private_data->current);  
      gtk_widget_map (GTK_WIDGET (assistant->private_data->current));
    }
}


static void
gtk_assistant_unmap (GtkWidget *widget)
{
  GtkAssistant *assistant;
  
  g_return_if_fail (GTK_IS_ASSISTANT (widget));
  
  assistant = GTK_ASSISTANT (widget);
  GTK_WIDGET_UNSET_FLAGS (assistant, GTK_MAPPED);

  gtk_widget_unmap (assistant->private_data->bbox);
  gtk_widget_unmap (assistant->private_data->header);
  if (assistant->private_data->sidebar &&
      GTK_WIDGET_VISIBLE (assistant->private_data->sidebar) &&
      GTK_WIDGET_MAPPED (assistant->private_data->sidebar))
    gtk_widget_unmap (assistant->private_data->sidebar);

  if (assistant->private_data->current &&
      GTK_WIDGET_VISIBLE (assistant->private_data->current) &&
      GTK_WIDGET_MAPPED (assistant->private_data->current))
    gtk_widget_unmap (GTK_WIDGET (assistant->private_data->current));
}


static void
gtk_assistant_forall (GtkContainer *container,
		      gboolean      include_internals,
		      GtkCallback   callback,
		      gpointer      callback_data)
{
  GtkAssistant *assistant;
  GList *list;
  
  assistant = GTK_ASSISTANT (container);
  
  if (include_internals)
    {
      (* callback) (assistant->private_data->sidebar, callback_data);
      (* callback) (assistant->private_data->header, callback_data);  
      (* callback) (assistant->private_data->bbox, callback_data);  
    }
  
  list = assistant->private_data->pages;
  while (list)
    {
      GtkAssistantPage *page_info = list->data;
      GtkWidget *page = page_info->page;
      list = list->next;

      (* callback) (GTK_WIDGET (page), callback_data);
    }
}


/**
 * gtk_assistant_new:
 * 
 * Creates a new #GtkAssistant.
 *
 * Return value: a newly created #GtkAssistant
 **/
GtkWidget *
gtk_assistant_new (void)
{
  GtkAssistant *assistant;

  assistant = g_object_new (GTK_TYPE_ASSISTANT, NULL);

  return GTK_WIDGET (assistant);
}

/**
 * gtk_assistant_set_sidebar_image:
 * @assistant: a #GtkAssistant
 * @image: an image, or %NULL
 * 
 * Sets the sidebar image for @assistant. The image is displayed in
 * the sidebar on the left of the @assistant. If no image is set the 
 * sidebar area is hidden.
 *
 * In order to show a different sidebar image for each page, call this
 * function from the ::prepare signal handler. 
 **/
void
gtk_assistant_set_sidebar_image (GtkAssistant  *assistant,
				 GdkPixbuf *image)
{
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  g_return_if_fail (image == NULL || GDK_IS_PIXBUF (image));
  
  if (image == NULL)
    gtk_widget_hide (assistant->private_data->sidebar);
  else
    {
      gtk_image_set_from_pixbuf (GTK_IMAGE (assistant->private_data->sidebar_image), image);
      gtk_widget_show_all (assistant->private_data->sidebar);
    }
}

/**
 * gtk_assistant_set_sidebar_image_alignment:
 * @assistant: a #GtkAssistant
 * @alignment: the vertical alignment for the sidebar image, between 0 (top) and 1 (bottom).
 * 
 * Changes the alignment of the sidebar image.
 **/
void
gtk_assistant_set_sidebar_image_alignment (GtkAssistant *assistant,
					   gfloat alignment)
{
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  
  alignment = CLAMP (alignment, 0.0, 1.0);

  gtk_misc_set_alignment (GTK_MISC (assistant->private_data->sidebar_image), 0.5, alignment);
}

/**
 * gtk_assistant_set_sidebar_color:
 * @assistant: a #GtkAssistant
 * @color: a color
 * 
 * Changes the background color for the sidebar area.
 **/
void
gtk_assistant_set_sidebar_color (GtkAssistant  *assistant,
				 GdkColor  *color)
{
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  g_return_if_fail (color != NULL);

  gtk_widget_modify_bg (assistant->private_data->sidebar, GTK_STATE_NORMAL, color);
}


/**
 * gtk_assistant_set_header_image:
 * @assistant: a #GtkAssistant
 * @image: an image
 * 
 * Sets the header image for @assistant. The image is displayed in
 * the header area at the top of the @assistant. 
 *
 * In order to show a different header image for each page, call this
 * function from the ::prepare signal handler. 
 **/
void
gtk_assistant_set_header_image (GtkAssistant  *assistant,
				GdkPixbuf *image)
{
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  g_return_if_fail (GDK_IS_PIXBUF (image));

  gtk_image_set_from_pixbuf (GTK_IMAGE (assistant->private_data->header_image), image);
}


/**
 * gtk_assistant_set_header_color:
 * @assistant: a #GtkAssistant
 * @color: a color
 * 
 * Changes the background color for the header area. If you want to use the 
 * same background color in the sidebar, see gtk_assistant_set_sidebar_color().
 **/
void
gtk_assistant_set_header_color (GtkAssistant  *assistant,
				GdkColor  *color)
{
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  g_return_if_fail (color != NULL);

  gtk_widget_modify_bg (assistant->private_data->header, GTK_STATE_NORMAL, color);
}

/**
 * gtk_assistant_set_header_text_color:
 * @assistant: a #GtkAssistant
 * @color: a color
 * 
 * Sets the color in which the title and description are displayed in the header
 * area of @assistant. You should make sure that the color contrasts with the background
 * color of the header area, see gtk_assistant_set_header_color().
 **/
void
gtk_assistant_set_header_text_color (GtkAssistant  *assistant,
				     GdkColor  *color)
{
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  g_return_if_fail (color != NULL);

  gtk_widget_modify_fg (assistant->private_data->header_title, GTK_STATE_NORMAL, color);
  gtk_widget_modify_fg (assistant->private_data->header_description, GTK_STATE_NORMAL, color);  
}

/**
 * gtk_assistant_insert_page:
 * @assistant: a #GtkAssistant
 * @sibling: a #GtkWidget which is already added as a page of @assistant, or %NULL to
 *   insert @page at the end
 * @page: a widget to insert before @sibling 
 * 
 * Adds a page to a #GtkAssistant. The page is inserted before @sibling. 
 **/
void
gtk_assistant_insert_page (GtkAssistant  *assistant,
			   GtkWidget *sibling,
			   GtkWidget *page)
{
  GList *list;
  
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  g_return_if_fail (sibling == NULL || GTK_IS_WIDGET (sibling));
  g_return_if_fail (GTK_IS_WIDGET (page));
  
  list = find_page (assistant, sibling);

  GtkAssistantPage *page_info = g_new0 (GtkAssistantPage, 1);
  page_info->page = page;

  assistant->private_data->pages = g_list_insert_before (assistant->private_data->pages, 
							    list, page_info);

  gtk_widget_set_parent (GTK_WIDGET (page), GTK_WIDGET (assistant));
  
  if (GTK_WIDGET_REALIZED (GTK_WIDGET (assistant)))
    gtk_widget_realize (GTK_WIDGET (page));

  if (assistant->private_data->pages->next == NULL)
    assistant->private_data->current = page;
}
		       

/**
 * gtk_assistant_append_page:
 * @assistant: a #GtkAssistant
 * @page: a widget to add as a page to @assistant
 * 
 * Adds a page to a #GtkAssistant. The page is put after all other pages 
 * of @assistant.
 **/
void
gtk_assistant_append_page (GtkAssistant  *assistant,
			   GtkWidget *page)
{
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  g_return_if_fail (GTK_IS_WIDGET (page));

  gtk_assistant_insert_page (assistant, NULL, page);
}

/**
 * gtk_assistant_prepend_page:
 * @assistant: a #GtkAssistant
 * @page: a widget to add as a page to @assistant
 * 
 * Adds a page to a #GtkAssistant. The page is put before all other pages 
 * of @assistant.
 **/
void 
gtk_assistant_prepend_page (GtkAssistant  *assistant,
			    GtkWidget     *page)
{
  GtkWidget *sibling;
  GtkAssistantPage *page_info;

  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  g_return_if_fail (GTK_IS_WIDGET (page));
  
  if (assistant->private_data->pages != NULL) 
    {
      page_info = assistant->private_data->pages->data;
      sibling = page_info->page;
    }
  else
    sibling = NULL;

  gtk_assistant_insert_page (assistant, sibling, page);
}

/**
 * gtk_assistant_get_current_page:
 * @assistant: a #GtkAssistant
 * 
 * Returns the current page of @assistant.
 * 
 * Return value: the currently visible page 
 **/
GtkWidget *
gtk_assistant_get_current_page (GtkAssistant *assistant)
{
  g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), NULL);

  return assistant->private_data->current;
}

/**
 * gtk_assistant_set_current_page:
 * @assistant: a #GtkAssistant
 * @page: a #GtkWidget which has been added as a page of @assistant, or %NULL 
 * 
 * Changes the currently displayed page of @assistant. 
 **/
void
gtk_assistant_set_current_page (GtkAssistant  *assistant,
				GtkWidget *page)
{
  GList *list;
  GtkWidget *old_page;
  GtkAssistantPage *page_info;
  GtkAssistantPageType type;
  
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  g_return_if_fail (page == NULL || GTK_IS_WIDGET (page));

  if (page)
    list = find_page (assistant, page);
  else
    list = assistant->private_data->pages;

  g_return_if_fail (list != NULL);

  page_info = list->data;

  if (assistant->private_data->current == page_info->page)
    return;

  type = page_info->type;
  if (type == GTK_ASSISTANT_PAGE_CONTENT) 
    {
      if (list->prev == NULL)
	type = GTK_ASSISTANT_PAGE_INTRO;
      else if (list->next == NULL)
	type = GTK_ASSISTANT_PAGE_CONFIRM;
    }

  switch (type) 
    {
    case GTK_ASSISTANT_PAGE_INTRO:
      gtk_widget_set_sensitive (assistant->private_data->cancel, TRUE);
      gtk_widget_set_sensitive (assistant->private_data->next, TRUE);
      gtk_widget_show (assistant->private_data->cancel);
      gtk_widget_show (assistant->private_data->next);
      gtk_widget_hide (assistant->private_data->back);
      gtk_widget_hide (assistant->private_data->finish);
      gtk_widget_hide (assistant->private_data->close);
      break;
    case GTK_ASSISTANT_PAGE_CONFIRM:
      gtk_widget_set_sensitive (assistant->private_data->cancel, TRUE);
      gtk_widget_set_sensitive (assistant->private_data->back, TRUE);
      gtk_widget_set_sensitive (assistant->private_data->finish, TRUE);
      gtk_widget_show (assistant->private_data->cancel);
      gtk_widget_show (assistant->private_data->back);
      gtk_widget_show (assistant->private_data->finish);
      gtk_widget_hide (assistant->private_data->next);
      gtk_widget_hide (assistant->private_data->close);
      break;
    case GTK_ASSISTANT_PAGE_CONTENT:
      gtk_widget_set_sensitive (assistant->private_data->cancel, TRUE);
      gtk_widget_show (assistant->private_data->cancel);
      gtk_widget_set_sensitive (assistant->private_data->back, TRUE);
      gtk_widget_show (assistant->private_data->back);
      gtk_widget_set_sensitive (assistant->private_data->next, TRUE);
      gtk_widget_show (assistant->private_data->next);
      gtk_widget_hide (assistant->private_data->finish);
      gtk_widget_hide (assistant->private_data->close);
      break;
    case GTK_ASSISTANT_PAGE_SUMMARY:
      gtk_widget_set_sensitive (assistant->private_data->close, TRUE);
      gtk_widget_show (assistant->private_data->close);
      gtk_widget_hide (assistant->private_data->cancel);
      gtk_widget_hide (assistant->private_data->back);
      gtk_widget_hide (assistant->private_data->next);
      gtk_widget_hide (assistant->private_data->finish);
      break;
    }

  if (assistant->private_data->current &&
      GTK_WIDGET_VISIBLE (assistant->private_data->current) &&
      GTK_WIDGET_MAPPED (assistant->private_data->current))
    old_page = GTK_WIDGET (assistant->private_data->current);
  else
    old_page = NULL;  

  assistant->private_data->current = page_info->page;

  g_signal_emit (assistant, signals [PREPARE], 0, assistant->private_data->current);  

  gtk_label_set_text (GTK_LABEL (assistant->private_data->header_title), page_info->title);      
  
  if (GTK_WIDGET_VISIBLE (assistant->private_data->current) && GTK_WIDGET_MAPPED (assistant))
    gtk_widget_map (GTK_WIDGET (assistant->private_data->current));

  if (old_page && GTK_WIDGET_MAPPED (old_page))
    gtk_widget_unmap (old_page);
  
}

static void
gtk_assistant_add (GtkContainer *widget,
		   GtkWidget    *page)
{
  g_return_if_fail (GTK_IS_ASSISTANT (widget));
  g_return_if_fail (page != NULL);
  
  gtk_assistant_append_page (GTK_ASSISTANT (widget), page);
}

static void
gtk_assistant_remove (GtkContainer *widget,
		      GtkWidget    *page)
{
  GtkAssistant *assistant;
  GtkAssistantPage *page_info;
  GList *list;
  
  g_return_if_fail (GTK_IS_ASSISTANT (widget));
  g_return_if_fail (page != NULL);

  assistant = GTK_ASSISTANT (widget);
  
  list = find_page (assistant, page);
  if (list != NULL) 
    {
      /* If we are mapped and visible, we want to deal with changing the page. */
      if ((GTK_WIDGET_MAPPED (GTK_WIDGET (widget))) &&
	  (page == assistant->private_data->current) &&
	  (list->next != NULL)) 
	{
	  page_info = list->next->data;
	  gtk_assistant_set_current_page (assistant, page_info->page);
	}
      assistant->private_data->pages = g_list_remove (assistant->private_data->pages, list->data);
      gtk_widget_unparent (page);
    }
}

static void
gtk_assistant_cancel_callback (GtkWidget *button, 
			       GtkAssistant  *assistant)
{
  g_signal_emit (assistant, signals [CANCEL], 0, assistant->private_data->current);
}

static void
gtk_assistant_close_callback (GtkWidget *button, 
			      GtkAssistant  *assistant)
{
  g_signal_emit (assistant, signals [CLOSE], 0, assistant->private_data->current);
}

static void
gtk_assistant_back_callback (GtkWidget *button, 
			     GtkAssistant  *assistant)
{
  GList *list;
  GtkAssistantPage *page_info;
  gboolean retval;

  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  g_return_if_fail (assistant->private_data->current != NULL);

  retval = FALSE;
  g_signal_emit (assistant, signals [BACK], 0, assistant->private_data->current, &retval);
  
  if (!retval) 
    {
      list = find_page (assistant, assistant->private_data->current);
      do 
	{
	  list = list->prev;
	  if (!list)
	    return;
	  page_info = list->data;
	}
      while (!GTK_WIDGET_VISIBLE (page_info->page));

      gtk_assistant_set_current_page (assistant, page_info->page);
    }
}

static void
gtk_assistant_next_callback (GtkWidget *button, 
			     GtkAssistant  *assistant)
{
  GList *list;
  GtkAssistantPage *page_info;
  gboolean retval;

  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  g_return_if_fail (assistant->private_data->current != NULL);

  list = find_page (assistant, assistant->private_data->current);
  page_info = list->data;
  
  retval = FALSE;
  if (page_info->type == GTK_ASSISTANT_PAGE_CONFIRM ||
      (page_info->type == GTK_ASSISTANT_PAGE_CONTENT && list->next == NULL))
    g_signal_emit (assistant, signals [FINISH], 0, assistant->private_data->current, &retval);
  else
    g_signal_emit (assistant, signals [NEXT], 0, assistant->private_data->current, &retval);
  
  
  if (!retval) 
    {
      do 
	{
	  list = list->next;
	  if (!list)
	    return;
	  page_info = list->data;
	}
      while (!GTK_WIDGET_VISIBLE (page_info->page));

      gtk_assistant_set_current_page (assistant, page_info->page);
    }
}

static void
gtk_assistant_destroy (GtkObject *object)
{
  GtkAssistant *assistant;

  /* remember, destroy can be run multiple times! */
  
  g_return_if_fail (GTK_IS_ASSISTANT (object));
  
  assistant = GTK_ASSISTANT (object);
  
  if(assistant->private_data->bbox) 
    {
      gtk_widget_destroy (assistant->private_data->bbox);
      assistant->private_data->bbox = NULL;
      assistant->private_data->back = NULL;
      assistant->private_data->next = NULL;
      assistant->private_data->cancel = NULL;
      assistant->private_data->finish = NULL;
      assistant->private_data->close = NULL;
    }

  /* Remove all pages, we set current to NULL so
   * that the remove code doesn't try to do anything funny */
  assistant->private_data->current = NULL;
  while (assistant->private_data->pages != NULL) 
    {
      GtkAssistantPage *page_info = assistant->private_data->pages->data;
      gtk_container_remove (GTK_CONTAINER (assistant), GTK_WIDGET (page_info->page));
    }
  
  GTK_OBJECT_CLASS (parent_class)->destroy (object);
}

/**
 * gtk_assistant_set_page_type:
 * @assistant: a #GtkAssistant
 * @page: a #GtkWidget which has been added as a page of @assistant
 * @type: the type of @page
 * 
 * Sets the page type of @page in @assistant.
 **/
void 
gtk_assistant_set_page_type (GtkAssistant        *assistant,
			     GtkWidget           *page,
			     GtkAssistantPageType type)
{
  GList *child;
  GtkAssistantPage *page_info;

  child = find_page (assistant, page);

  g_return_if_fail (child != NULL);
  
  page_info = child->data;
  
  if (type != page_info->type) 
    {
      page_info->type = type;

      gtk_widget_child_notify (page, "page_type");
    }  
}

/**
 * gtk_assistant_get_page_type:
 * @assistant: a #GtkAssistant
 * @page: a #GtkWidget which has been added as a page of @assistant
 * 
 * Gets the page type of @page in @assistant.
 *
 * Returns: the type of @page
 **/
GtkAssistantPageType 
gtk_assistant_get_page_type (GtkAssistant *assistant,
			     GtkWidget    *page)
{
  GList *child;
  GtkAssistantPage *page_info;
  
  child = find_page (assistant, page);
  
  g_return_val_if_fail (child != NULL, GTK_ASSISTANT_PAGE_CONTENT);
  
  page_info = child->data;
  
  return page_info->type;
}

/**
 * gtk_assistant_set_current_page_num:
 * @assistant: a #GtkAssistant
 * @page_num: an nonnegative integer less than the number of pages of @assistant
 * 
 * Changes the currently displayed page of @assistant to the @page_num<!-- -->th
 * page.
 **/
void 
gtk_assistant_set_current_page_num (GtkAssistant  *assistant,
				    gint           page_num)
{
  GtkAssistantPage *page_info;
  
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  
  page_info = g_list_nth_data (assistant->private_data->pages, page_num);
  
  gtk_assistant_set_current_page (assistant, page_info->page);
}

/**
 * gtk_assistant_get_current_page_num:
 * @assistant: a #GtkAssistant
 * 
 * Returns the position of the current page of @assistant.
 * 
 * Return value: the position of the current page
 **/
gint 
gtk_assistant_get_current_page_num (GtkAssistant  *assistant)
{
  GList *list;

  g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), -1);
  
  list = find_page (assistant, assistant->private_data->current);

  return g_list_index (assistant->private_data->pages, list->data);
}

/**
 * gtk_assistant_get_n_pages:
 * @assistant: a #GtkAssistant
 * 
 * Returns the number of pages of @assistant.
 * 
 * Return value: the number of pages
 **/
gint 
gtk_assistant_get_n_pages (GtkAssistant  *assistant)
{
  g_return_val_if_fail (GTK_IS_ASSISTANT (assistant), 0);  

  return g_list_length (assistant->private_data->pages);
}


/**
 * gtk_assistant_get_nth_page:
 * @assistant: a #GtkAssistant
 * @page_num: an nonnegative integer less than the number of pages of @assistant
 * 
 * Returns the @page_num<!-- -->th page of @assistant.
 * 
 * Return value: the @page_num<!-- -->th page
 **/
GtkWidget *
gtk_assistant_get_nth_page (GtkAssistant *assistant,
			    gint          page_num)
{
  GtkAssistantPage *page_info;
  
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));
  
  page_info = g_list_nth_data (assistant->private_data->pages, page_num);
  
  return page_info->page;
}

/**
 * gtk_assistant_set_buttons_sensitive:
 * @assistant: a #GtkAssistant
 * @back_sensitive: %TRUE to make the back button sensitive
 * @forward_sensitive: %TRUE to make the next/finish/close buttons sensitive
 * @cancel_sensitive: %TRUE to make the cancel button sensitive
 * 
 * Changes the sensitivity of the @assistant's navigation buttons. Note that
 * the next, finish and close buttons are controlled by a single argument, since
 * only one of them can appear at a time.
 *
 * Note that all visible buttons are made sensitive by default. To make buttons 
 * appear insensitive after a page switch, call this function from the ::prepare
 * signal handler.
 **/
void 
gtk_assistant_set_buttons_sensitive (GtkAssistant *assistant,
				     gboolean      back_sensitive,
				     gboolean      forward_sensitive,
				     gboolean      cancel_sensitive)
{
  g_return_if_fail (GTK_IS_ASSISTANT (assistant));

  gtk_widget_set_sensitive (assistant->private_data->back, back_sensitive);
  gtk_widget_set_sensitive (assistant->private_data->next, forward_sensitive);
  gtk_widget_set_sensitive (assistant->private_data->finish, forward_sensitive);
  gtk_widget_set_sensitive (assistant->private_data->close, forward_sensitive);
  gtk_widget_set_sensitive (assistant->private_data->cancel, cancel_sensitive);
}

