/*
 * glade-palette-expander.c - A container which can hide its child
 *
 * Copyright (C) 2007 Vincent Geddes
 *
 * Author: Vincent Geddes <vincent.geddes@gmail.com>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include <config.h>

#include "glade-palette-expander.h"

#include <gtk/gtkcontainer.h>
#include <gtk/gtkbutton.h>
#include <gtk/gtkalignment.h>
#include <gtk/gtklabel.h>
#include <gtk/gtkhbox.h>
#include <gtk/gtkarrow.h>
#include <gtk/gtkdnd.h>

#define GLADE_PALETTE_EXPANDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
					       GLADE_TYPE_PALETTE_EXPANDER,      \
					       GladePaletteExpanderPrivate))

enum
{
	PROP_0,
	PROP_EXPANDED,
	PROP_LABEL,
	PROP_SPACING,
	PROP_USE_MARKUP
};

struct _GladePaletteExpanderPrivate
{
	GtkWidget *button;
	GtkWidget *arrow;
	GtkWidget *label;

	gboolean   expanded;
	gboolean   use_markup;
	gboolean   button_down;
	guint      spacing;
	guint      expand_timer_handle;
	
};

static void glade_palette_expander_set_property (GObject      *object,
						 guint         prop_id,
						 const GValue *value,
						 GParamSpec   *pspec);
static void glade_palette_expander_get_property (GObject    *object,
						 guint       prop_id,
						 GValue     *value,
						 GParamSpec *pspec);
static void   glade_palette_expander_size_request  (GtkWidget      *widget,
						    GtkRequisition *requisition);
static void   glade_palette_expander_size_allocate (GtkWidget      *widget,
						    GtkAllocation  *allocation);			     
static void   glade_palette_expander_map           (GtkWidget      *widget);
static void   glade_palette_expander_unmap         (GtkWidget      *widget);
static void   glade_palette_expander_add           (GtkContainer   *container,
						    GtkWidget      *widget);
static void   glade_palette_expander_remove        (GtkContainer   *container,
						    GtkWidget      *widget);	    
static void   glade_palette_expander_forall        (GtkContainer   *container,
						    gboolean        include_internals,
						    GtkCallback     callback,
						    gpointer        callback_data);
static void        button_clicked (GtkButton *button,
				   GladePaletteExpander *expander);
				   

static gboolean    button_drag_motion (GtkWidget        *widget,
				       GdkDragContext   *context,
				       gint              x,
				       gint              y,
				       guint             time,
				       GladePaletteExpander *expander);

static void        button_drag_leave (GtkWidget      *widget,
				      GdkDragContext *context,
				      guint           time,
				      GladePaletteExpander *expander);
		   
static void   glade_palette_expander_activate      (GladePaletteExpander *expander);			       
void          glade_palette_expander_set_expanded  (GladePaletteExpander *expander,
						    gboolean              expanded);
gboolean      glade_palette_expander_get_expanded  (GladePaletteExpander *expander);
void          glade_palette_expander_set_label     (GladePaletteExpander *expander,
						    const gchar          *label);
const gchar  *glade_palette_expander_get_label     (GladePaletteExpander *expander);
void          glade_palette_expander_set_spacing   (GladePaletteExpander *expander,
						    guint                 spacing);
guint         glade_palette_expander_get_spacing   (GladePaletteExpander *expander);


G_DEFINE_TYPE (GladePaletteExpander, glade_palette_expander, GTK_TYPE_BIN);

static void
glade_palette_expander_class_init (GladePaletteExpanderClass *klass)
{
	GObjectClass      *object_class;
	GtkWidgetClass    *widget_class;
	GtkContainerClass *container_class;

	object_class    = G_OBJECT_CLASS (klass);
	widget_class    = GTK_WIDGET_CLASS (klass);
	container_class = GTK_CONTAINER_CLASS (klass);

	object_class->set_property = glade_palette_expander_set_property;
	object_class->get_property = glade_palette_expander_get_property;

	container_class->add    = glade_palette_expander_add;
	container_class->remove = glade_palette_expander_remove;
	container_class->forall = glade_palette_expander_forall;
	
	widget_class->size_request         = glade_palette_expander_size_request;
	widget_class->size_allocate        = glade_palette_expander_size_allocate;
	widget_class->map                  = glade_palette_expander_map;
	widget_class->unmap                = glade_palette_expander_unmap;

	klass->activate = glade_palette_expander_activate;

	g_object_class_install_property (object_class,
					 PROP_EXPANDED,
					 g_param_spec_boolean ("expanded",
							       "Expanded",
							       "Whether the expander has been opened to "
							       "reveal the child widget",
					 		       FALSE,
							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));

	g_object_class_install_property (object_class,
					 PROP_LABEL,
					 g_param_spec_string ("label",
							      "Label",
							      "Text of the expander's label",
							      NULL,
					 		      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
					 		      
	g_object_class_install_property (object_class,
					 PROP_SPACING,
					 g_param_spec_uint ("spacing",
							    "Spacing",
							    "Space to put between the expander and the child widget",
							    0,
							    G_MAXUINT,
							    0,
					 		    G_PARAM_READWRITE));
					 		    
	g_object_class_install_property (object_class,
					 PROP_USE_MARKUP,
					 g_param_spec_boolean ("use-markup",
							       "Use Markup",
							       "The text of the label includes Pango XML markup"
							       "reveal the child widget",
					 		       FALSE,
							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
					 		      
	widget_class->activate_signal =
		g_signal_new ("activate",
			  G_TYPE_FROM_CLASS (object_class),
			  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
			  G_STRUCT_OFFSET (GladePaletteExpanderClass, activate),
			  NULL, NULL,
			  g_cclosure_marshal_VOID__VOID,
			  G_TYPE_NONE, 0);

	g_type_class_add_private (klass, sizeof (GladePaletteExpanderPrivate));
}

static void
glade_palette_expander_init (GladePaletteExpander *expander)
{
	GladePaletteExpanderPrivate *priv;
	GtkWidget *hbox;
	GtkWidget *alignment;

	expander->priv = priv = GLADE_PALETTE_EXPANDER_GET_PRIVATE (expander);
	
	GTK_WIDGET_SET_FLAGS (expander, GTK_NO_WINDOW);
	
	priv->spacing = 0;
	priv->expanded = FALSE;
	priv->use_markup = FALSE;
	priv->button_down = FALSE;
	
	gtk_widget_push_composite_child ();
	
	priv->button = gtk_button_new ();
	gtk_button_set_focus_on_click (GTK_BUTTON (priv->button), FALSE);
	priv->arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
	priv->label = gtk_label_new (NULL);
	gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);

	alignment = gtk_alignment_new (0.0, 0.5, 1, 1);

	hbox = gtk_hbox_new (FALSE, 3);
	
	gtk_container_add (GTK_CONTAINER (alignment), hbox);
	gtk_container_add (GTK_CONTAINER (priv->button), alignment);
	gtk_box_pack_start (GTK_BOX (hbox), priv->arrow, FALSE, FALSE, 0);
	gtk_box_pack_start (GTK_BOX (hbox), priv->label, FALSE, FALSE, 0);
	
	gtk_widget_set_parent (priv->button, GTK_WIDGET (expander));
	
	gtk_widget_pop_composite_child ();
			  
	g_signal_connect (priv->button, "clicked",
			  G_CALLBACK (button_clicked),
			  expander);

	g_signal_connect (priv->button, "drag-motion",
			  G_CALLBACK (button_drag_motion),
			  expander);

	g_signal_connect (priv->button, "drag-leave",
			  G_CALLBACK (button_drag_leave),
			  expander);				  

	gtk_widget_show_all (priv->button);
	
	gtk_drag_dest_set (GTK_WIDGET (priv->button), 0, NULL, 0, 0);
	gtk_drag_dest_set_track_motion (GTK_WIDGET (priv->button), TRUE);
}

static void
glade_palette_expander_set_property (GObject      *object,
				     guint         prop_id,
				     const GValue *value,
				     GParamSpec   *pspec)
{
	GladePaletteExpander *expander = GLADE_PALETTE_EXPANDER (object);

	switch (prop_id)
	{
	case PROP_EXPANDED:
		glade_palette_expander_set_expanded (expander, g_value_get_boolean (value));
		break;
	case PROP_LABEL:
		glade_palette_expander_set_label (expander, g_value_get_string (value));
		break;
	case PROP_SPACING:
		glade_palette_expander_set_spacing (expander, g_value_get_int (value));
		break;
	case PROP_USE_MARKUP:
		glade_palette_expander_set_use_markup (expander, g_value_get_boolean (value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
glade_palette_expander_get_property (GObject    *object,
				     guint       prop_id,
				     GValue     *value,
				     GParamSpec *pspec)
{
	GladePaletteExpander *expander = GLADE_PALETTE_EXPANDER (object);

	switch (prop_id)
	{
	case PROP_EXPANDED:
		g_value_set_boolean (value, glade_palette_expander_get_expanded (expander));
		break;
	case PROP_LABEL:
		g_value_set_string (value, glade_palette_expander_get_label (expander));
		break;
	case PROP_SPACING:
		g_value_set_int (value, glade_palette_expander_get_spacing (expander));
		break;
	case PROP_USE_MARKUP:
		g_value_set_boolean (value, glade_palette_expander_get_use_markup (expander));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
glade_palette_expander_size_request (GtkWidget       *widget,
				     GtkRequisition  *requisition)
{
	GladePaletteExpanderPrivate *priv;
	GladePaletteExpander *expander;
	GtkBin *bin;
	gint border_width;

	bin = GTK_BIN (widget);
	expander = GLADE_PALETTE_EXPANDER (widget);
	priv = expander->priv;

	border_width = GTK_CONTAINER (widget)->border_width;

	requisition->width = 0;
	requisition->height =  0;


	if (GTK_WIDGET_VISIBLE (priv->button))
	{
		GtkRequisition button_requisition;

		gtk_widget_size_request (priv->button, &button_requisition);

		requisition->width  += button_requisition.width;
		requisition->height += button_requisition.height;
	}

	if (bin->child && gtk_widget_get_child_visible (bin->child))
	{
		GtkRequisition child_requisition;

		gtk_widget_size_request (bin->child, &child_requisition);

		requisition->width = MAX (requisition->width, child_requisition.width);
		requisition->height += child_requisition.height + priv->spacing;
	}

	requisition->width  += 2 * border_width;
	requisition->height += 2 * border_width;
}

static void
glade_palette_expander_size_allocate (GtkWidget     *widget,
				      GtkAllocation *allocation)
{
	GladePaletteExpanderPrivate *priv;
	GladePaletteExpander *expander;
	GtkBin *bin;
	GtkRequisition child_requisition;
	gboolean child_visible = FALSE;
	gint border_width;
	gint button_height;

	expander = GLADE_PALETTE_EXPANDER (widget);
	bin = GTK_BIN (widget);
	priv = expander->priv;

	border_width = GTK_CONTAINER (widget)->border_width;

	child_requisition.width = 0;
	child_requisition.height = 0;	
	
	if (bin->child && gtk_widget_get_child_visible (bin->child))
	{
		child_visible = TRUE;
		gtk_widget_get_child_requisition (bin->child, &child_requisition);
	}

	widget->allocation = *allocation;

	if (GTK_WIDGET_VISIBLE (priv->button))
	{
		GtkAllocation button_allocation;
		GtkRequisition button_requisition;

		gtk_widget_get_child_requisition (priv->button, &button_requisition);

		button_allocation.x = widget->allocation.x + border_width;
		button_allocation.y = widget->allocation.y + border_width;

		button_allocation.width = MAX (allocation->width - 2 * border_width, 1);    

		button_allocation.height = MIN (button_requisition.height,
					        allocation->height - 2 * border_width
					        - (child_visible ? priv->spacing : 0));
					         
		button_allocation.height = MAX (button_allocation.height, 1);

		gtk_widget_size_allocate (priv->button, &button_allocation);

		button_height = button_allocation.height;
	}
	else
	{
		button_height = 0;
	}

	if (child_visible)
	{
		GtkAllocation child_allocation;

		child_allocation.x = widget->allocation.x + border_width;
		child_allocation.y = widget->allocation.y + border_width + button_height + priv->spacing;

		child_allocation.width = MAX (allocation->width - 2 * border_width, 1);

		child_allocation.height = allocation->height - button_height -
					  2 * border_width - priv->spacing;
		child_allocation.height = MAX (child_allocation.height, 1);

		gtk_widget_size_allocate (bin->child, &child_allocation);
	}
}

static void
glade_palette_expander_map (GtkWidget *widget)
{
	GladePaletteExpanderPrivate *priv = GLADE_PALETTE_EXPANDER (widget)->priv;

	gtk_widget_map (priv->button);

	GTK_WIDGET_CLASS (glade_palette_expander_parent_class)->map (widget);
}

static void
glade_palette_expander_unmap (GtkWidget *widget)
{
	GladePaletteExpanderPrivate *priv = GLADE_PALETTE_EXPANDER (widget)->priv;

	GTK_WIDGET_CLASS (glade_palette_expander_parent_class)->unmap (widget);

	gtk_widget_unmap (priv->button);
}

static void
glade_palette_expander_add (GtkContainer *container,
			    GtkWidget    *widget)
{
	GTK_CONTAINER_CLASS (glade_palette_expander_parent_class)->add (container, widget);

	gtk_widget_set_child_visible (widget, GLADE_PALETTE_EXPANDER (container)->priv->expanded);
	gtk_widget_queue_resize (GTK_WIDGET (container));
}

static void
glade_palette_expander_remove (GtkContainer *container,
			       GtkWidget    *widget)
{
  GladePaletteExpander *expander = GLADE_PALETTE_EXPANDER (container);

	if (expander->priv->button == widget)
	{	
		gtk_widget_unparent (expander->priv->button);
		expander->priv->button = NULL;

		if (GTK_WIDGET_VISIBLE (expander))
			gtk_widget_queue_resize (GTK_WIDGET (expander));

		g_object_notify (G_OBJECT (expander), "label");
	}
	else
	{
    		GTK_CONTAINER_CLASS (glade_palette_expander_parent_class)->remove (container, widget);
	}

}

static void
glade_palette_expander_forall (GtkContainer *container,
			       gboolean      include_internals,
			       GtkCallback   callback,
			       gpointer      callback_data)
{
	GtkBin *bin = GTK_BIN (container);
	GladePaletteExpanderPrivate *priv = GLADE_PALETTE_EXPANDER_GET_PRIVATE (container);
	
	if (bin->child)
		(* callback) (bin->child, callback_data);
	
	if (priv->button)	
		(* callback) (priv->button, callback_data);
}

static gboolean
expand_timeout (gpointer data)
{
	GladePaletteExpander *expander = (GladePaletteExpander *) (data);
	GladePaletteExpanderPrivate *priv = expander->priv;

	GDK_THREADS_ENTER ();

	priv->expand_timer_handle = 0;
	glade_palette_expander_set_expanded (expander, TRUE);

	GDK_THREADS_LEAVE ();

	return FALSE;
}

static gboolean
button_drag_motion (GtkWidget        *widget,
		    GdkDragContext   *context,
		    gint              x,
		    gint              y,
		    guint             time,
		    GladePaletteExpander *expander)
{
	GladePaletteExpanderPrivate *priv = expander->priv;

	if (!priv->expanded && !priv->expand_timer_handle)
	{
		GtkSettings *settings;
		guint timeout;

		settings = gtk_widget_get_settings (widget);
		g_object_get (settings, "gtk-timeout-expand", &timeout, NULL);

		priv->expand_timer_handle = g_timeout_add (timeout,
							   (GSourceFunc) expand_timeout,
							   expander);
	}

  return TRUE;
}

static void
button_drag_leave (GtkWidget      *widget,
		   GdkDragContext *context,
		   guint           time,
		   GladePaletteExpander *expander)
{
	GladePaletteExpanderPrivate *priv = expander->priv;

	if (priv->expand_timer_handle)
	{
		g_source_remove (priv->expand_timer_handle);
		priv->expand_timer_handle = 0;
	}
}

static void
button_clicked (GtkButton            *button,
		GladePaletteExpander *expander)
{
	gtk_widget_activate (GTK_WIDGET (expander));
}

static void
glade_palette_expander_activate (GladePaletteExpander *expander)
{
	glade_palette_expander_set_expanded (expander, !expander->priv->expanded);
}

void
glade_palette_expander_set_expanded (GladePaletteExpander *expander, gboolean expanded)
{
	GladePaletteExpanderPrivate *priv;

	g_return_if_fail (GLADE_IS_PALETTE_EXPANDER (expander));

	priv = expander->priv;

	expanded = expanded != FALSE;

	if (priv->expanded != expanded)
	{
		priv->expanded = expanded;

		if (GTK_BIN (expander)->child)
		{
			gtk_widget_set_child_visible (GTK_BIN (expander)->child, priv->expanded);
			gtk_widget_queue_resize (GTK_WIDGET (expander));
		}

		gtk_arrow_set (GTK_ARROW (priv->arrow),
			       priv->expanded ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
			       GTK_SHADOW_NONE);

		g_object_notify (G_OBJECT (expander), "expanded");
	}
}

gboolean
glade_palette_expander_get_expanded (GladePaletteExpander *expander)
{
	g_return_val_if_fail (GLADE_IS_PALETTE_EXPANDER (expander), FALSE);
	
	return expander->priv->expanded;
}

void
glade_palette_expander_set_label (GladePaletteExpander *expander, const gchar *label)
{
	GladePaletteExpanderPrivate *priv;
	g_return_if_fail (GLADE_IS_PALETTE_EXPANDER (expander));

	priv = GLADE_PALETTE_EXPANDER_GET_PRIVATE (expander);

	gtk_label_set_label (GTK_LABEL (priv->label), label);
	g_object_notify (G_OBJECT (expander), "label"); 
}

const gchar *
glade_palette_expander_get_label (GladePaletteExpander *expander)
{
	g_return_val_if_fail (GLADE_IS_PALETTE_EXPANDER (expander), NULL);
	
	return gtk_label_get_label (GTK_LABEL (expander->priv->label));
}

void
glade_palette_expander_set_spacing (GladePaletteExpander *expander,
				    guint        spacing)
{
	g_return_if_fail (GLADE_IS_PALETTE_EXPANDER (expander));

	if (expander->priv->spacing != spacing)
	{
		expander->priv->spacing = spacing;

		gtk_widget_queue_resize (GTK_WIDGET (expander));

		g_object_notify (G_OBJECT (expander), "spacing");
	}
}

guint
glade_palette_expander_get_spacing (GladePaletteExpander *expander)
{
	g_return_val_if_fail (GLADE_IS_PALETTE_EXPANDER (expander), 0);

	return expander->priv->spacing;
}

void
glade_palette_expander_set_use_markup (GladePaletteExpander *expander,
				       gboolean              use_markup)
{
	GladePaletteExpanderPrivate *priv;

	g_return_if_fail (GLADE_IS_PALETTE_EXPANDER (expander));

	priv = expander->priv;

	use_markup = use_markup != FALSE;

	if (priv->use_markup != use_markup)
	{
		priv->use_markup = use_markup;

		gtk_label_set_use_markup (GTK_LABEL (priv->label), use_markup);

		g_object_notify (G_OBJECT (expander), "use-markup");
	}
}

gboolean
glade_palette_expander_get_use_markup (GladePaletteExpander *expander)
{
	g_return_val_if_fail (GLADE_IS_PALETTE_EXPANDER (expander), FALSE);

	return expander->priv->use_markup;
}

GtkWidget *
glade_palette_expander_new (const gchar *label)
{
	return g_object_new (GLADE_TYPE_PALETTE_EXPANDER,
			     "label", label,
			     NULL);
}



syntax highlighted by Code2HTML, v. 0.9.1