/* gtkcomposite.c
 * Copyright (C) 2010 Tristan Van Berkom <tristan.van.berkom@gmail.com>
 *
 * 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 "gtkcomposite.h"
#include "gtkbuilder.h"


/**
 * SECTION:gtkcomposite
 * @Short_Description: An abstract type for building composite objects
 * @Title: GtkComposite
 *
 * Composite derived widgets use an internal #GtkBuilder user interface
 * definition to connect to routines of their class to handle events
 * from the user interface.
 *
 * Furthermore, Composite objects can define properties as #GtkParamSpecComposite
 * which results in their values being automatically assigned after the children
 * are built.
 *
 * <note><para>Every implementing class must either make a call to 
 * gtk_composite_class_set_template(), gtk_composite_class_set_template_file() or
 * in the case of extending a concrete class they can call 
 * gtk_composite_class_use_parent_template()</para></note>
 */
static void       gtk_composite_class_init          (GtkCompositeClass     *klass);
static void       gtk_composite_init                (GtkComposite          *composite);
static void       gtk_composite_base_class_init     (GtkCompositeClass     *klass);
static void       gtk_composite_base_class_finalize (GtkCompositeClass     *klass);
static GObject *  gtk_composite_constructor         (GType                  type,
						     guint                  n_construct_properties,
						     GObjectConstructParam *construct_properties);
static void       gtk_composite_size_request        (GtkWidget             *widget,
						     GtkRequisition        *requisition);
static void       gtk_composite_size_allocate       (GtkWidget             *widget,
						     GtkAllocation         *allocation);

static GObjectClass *gtk_composite_parent_class;

GType
gtk_composite_get_type (void)
{
  static GType composite_type = 0;

  if (G_UNLIKELY (composite_type == 0))
    {
      const GTypeInfo composite_info =
      {
	sizeof (GtkCompositeClass),
	(GBaseInitFunc) gtk_composite_base_class_init,	
	(GBaseFinalizeFunc) gtk_composite_base_class_finalize,
	(GClassInitFunc) gtk_composite_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_init */
	sizeof (GtkComposite),
	0,		/* n_preallocs */
	(GInstanceInitFunc) gtk_composite_init,
	NULL,		/* value_table */
      };

      composite_type = g_type_register_static (GTK_TYPE_BIN, "GtkComposite",
					       &composite_info, G_TYPE_FLAG_ABSTRACT);

    }
  return composite_type;
}

static void
gtk_composite_class_init (GtkCompositeClass *klass)
{
  GObjectClass *object_class = (GObjectClass *)klass;
  GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;

  gtk_composite_parent_class = 
    (GObjectClass *)g_type_class_peek_parent (klass);

  object_class->constructor   = gtk_composite_constructor;

  widget_class->size_request  = gtk_composite_size_request;
  widget_class->size_allocate = gtk_composite_size_allocate;
}

static void
gtk_composite_base_class_init (GtkCompositeClass *klass)
{
  /* Every subclass must set a builder file or explicitly use the parent one */
  klass->template = NULL;
  klass->template_file = NULL;
}

static void
gtk_composite_base_class_finalize (GtkCompositeClass *klass)
{
  g_free (klass->template);
  g_free (klass->template_file);
}

static void
gtk_composite_init (GtkComposite *composite)
{
  gtk_widget_set_has_window (GTK_WIDGET (composite), FALSE);
}

static GObject *
gtk_composite_constructor (GType                  type,
			   guint                  n_construct_properties,
			   GObjectConstructParam *construct_properties)
{
  GtkBuilder    *builder;
  GParamSpec   **pspecs;
  GError        *error = NULL;
  GtkWidget     *composite_toplevel;
  guint          ret = 0, n_pspecs = 0, i;
  GObject       *ret_obj;
  GtkComposite  *composite;
  
  ret_obj = G_OBJECT_CLASS (gtk_composite_parent_class)->constructor
    (type, n_construct_properties, construct_properties);
  
  composite = GTK_COMPOSITE (ret_obj);

  builder = gtk_builder_new ();

  /* Marco's patch */
  gtk_builder_expose_object (builder, "composite", composite);

  if (GTK_COMPOSITE_GET_CLASS (composite)->template)
    ret = gtk_builder_add_from_string (builder, GTK_COMPOSITE_GET_CLASS (composite)->template, -1, &error);
  else if (GTK_COMPOSITE_GET_CLASS (composite)->template_file)
    ret = gtk_builder_add_from_file (builder, GTK_COMPOSITE_GET_CLASS (composite)->template_file, &error);

  if (!ret)
    {
      if (error)
	{
	  g_critical ("Unable to build GtkComposite class %s from template: %s", 
		      G_OBJECT_TYPE_NAME (composite),
		      error->message);
	  g_error_free (error);
	}
      else
	g_critical ("No template specified for GtkComposite class %s", 
		    G_OBJECT_TYPE_NAME (composite));
      return ret_obj;
    }

  if ((composite_toplevel = (GtkWidget *)
       gtk_builder_get_object (builder, "toplevel")) == NULL)
    {
      g_critical ("Unable to find required key 'toplevel' in GtkComposite class %s builder template", 
		  G_OBJECT_TYPE_NAME (composite));
      return ret_obj;
    }

  if (GTK_IS_WIDGET (composite_toplevel) == FALSE)
    {
      g_critical ("Toplevel is not a GtkWidget in GtkComposite class %s builder template", 
		  G_OBJECT_TYPE_NAME (composite));
      return ret_obj;
    }

  pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (composite), &n_pspecs);
  for (i = 0; i < n_pspecs; i++)
    {
      if (GTK_IS_PARAM_SPEC_COMPOSITE (pspecs[i]))
	{
	  GObject *composite_child;

	  if ((composite_child = 
	       gtk_builder_get_object (builder, (pspecs[i])->name)) != NULL)
	    {
	      /* Let GObject fire a warning if there is an object type mismatch */
	      g_object_set (composite, (pspecs[i])->name, composite_child, NULL);
	    }
	  else
	    g_critical ("Expected internal child %s not found for composite class %s",
			(pspecs[i])->name, G_OBJECT_TYPE_NAME (composite));
	}
    }
  g_free (pspecs);

  /* Any unspecified user data will still default to the composite instance,
   * but will not be connected with connect_object(). */
  gtk_builder_connect_signals (builder, composite);

  /* Take a reference to the toplevel by adding it, anything not in
   * the hierarchy should have properties of type GtkParamSpecComposite 
   * to reference them 
   */
  gtk_container_add (GTK_CONTAINER (composite), composite_toplevel);

  /* All done with builder now... */
  g_object_unref (builder);
  
  return ret_obj;
}


static void
gtk_composite_size_request (GtkWidget             *widget,
			    GtkRequisition        *requisition)
{
  GtkBin *bin = GTK_BIN (widget);

  requisition->width = GTK_CONTAINER (widget)->border_width * 2;
  requisition->height = GTK_CONTAINER (widget)->border_width * 2;

  if (bin->child && gtk_widget_get_visible (bin->child))
    {
      GtkRequisition child_requisition;
      
      gtk_widget_size_request (bin->child, &child_requisition);

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

static void
gtk_composite_size_allocate (GtkWidget             *widget,
			     GtkAllocation         *allocation)
{
  GtkBin *bin;
  GtkAllocation child_allocation;
  
  widget->allocation = *allocation;
  bin = GTK_BIN (widget);
  
  if (!gtk_widget_get_has_window (widget))
    {
      child_allocation.x = allocation->x + GTK_CONTAINER (widget)->border_width;
      child_allocation.y = allocation->y + GTK_CONTAINER (widget)->border_width;
    }
  else
    {
      child_allocation.x = 0;
      child_allocation.y = 0;
    }
  child_allocation.width = MAX (allocation->width - GTK_CONTAINER (widget)->border_width * 2, 0);
  child_allocation.height = MAX (allocation->height - GTK_CONTAINER (widget)->border_width * 2, 0);
  
  if (bin->child)
    gtk_widget_size_allocate (bin->child, &child_allocation);
}


void
gtk_composite_class_use_parent_template (GtkCompositeClass *composite_class)
{
  GtkCompositeClass *parent_class;

  g_return_if_fail (GTK_IS_COMPOSITE_CLASS(composite_class));

  /* Every derived class will at least be a 'composite' class,
   * this is only to be called by derived code.
   */
  parent_class = g_type_class_peek_parent (composite_class);

  if (parent_class->template)
    gtk_composite_class_set_template (composite_class, 
				      parent_class->template);
  else if (parent_class->template_file)
    gtk_composite_class_set_template_file (composite_class,
					   parent_class->template);
}

void
gtk_composite_class_set_template (GtkCompositeClass *composite_class,
				  const gchar       *template)
{
  g_return_if_fail (GTK_IS_COMPOSITE_CLASS(composite_class));
  g_return_if_fail (template && template[0]);

  if (composite_class->template)
    g_free (composite_class->template);

  composite_class->template = g_strdup (template);

  if (composite_class->template_file)
    composite_class->template_file =
      (g_free (composite_class->template_file), NULL);
}

void
gtk_composite_class_set_template_file (GtkCompositeClass *composite_class,
				       const gchar       *template_file)
{
  g_return_if_fail (GTK_IS_COMPOSITE_CLASS(composite_class));
  g_return_if_fail (template_file && template_file[0]);


  if (composite_class->template_file)
    g_free (composite_class->template_file);

  composite_class->template_file = g_strdup (template_file);

  if (composite_class->template)
    composite_class->template =
      (g_free (composite_class->template), NULL);
}


/*    ---------- Declaration of GtkParamSpecComposite
 */
static void
param_composite_init (GParamSpec *pspec)
{
  /* GtkParamSpecComposite *ospec = GTK_PARAM_SPEC_COMPOSITE (pspec); */
}

static void
param_composite_set_default (GParamSpec *pspec,
			  GValue     *value)
{
  value->data[0].v_pointer = NULL;
}

static gboolean
param_composite_validate (GParamSpec *pspec,
			  GValue     *value)
{
  GtkParamSpecComposite *ospec = GTK_PARAM_SPEC_COMPOSITE (pspec);
  GObject               *object = value->data[0].v_pointer;
  guint changed = 0;
  
  if (object && !g_value_type_compatible (G_OBJECT_TYPE (object), G_PARAM_SPEC_VALUE_TYPE (ospec)))
    {
      g_object_unref (object);
      value->data[0].v_pointer = NULL;
      changed++;
    }
  
  return changed;
}

static gint
param_composite_values_cmp (GParamSpec   *pspec,
			    const GValue *value1,
			    const GValue *value2)
{
  guint8 *p1 = value1->data[0].v_pointer;
  guint8 *p2 = value2->data[0].v_pointer;

  /* not much to compare here, try to at least provide stable lesser/greater result */

  return p1 < p2 ? -1 : p1 > p2;
}

GType 
gtk_param_composite_get_type (void)
{
  static GType composite_type = 0;

  if (composite_type == 0)
    {
      static const GParamSpecTypeInfo pspec_info = {
	sizeof (GtkParamSpecComposite), /* instance_size */
	16,                        /* n_preallocs */
	param_composite_init,	 /* instance_init */
	G_TYPE_OBJECT,		 /* value_type */
	NULL,			 /* finalize */
	param_composite_set_default,	 /* value_set_default */
	param_composite_validate,	 /* value_validate */
	param_composite_values_cmp,	 /* values_cmp */
      };
      composite_type = 
	g_param_type_register_static (g_intern_static_string ("GtkParamComposite"), &pspec_info);
    }      
  return composite_type;
}

/**
 * gtk_param_spec_composite:
 * @name: canonical name of the property specified
 * @nick: nick name for the property specified
 * @blurb: description of the property specified
 * @object_type: %G_TYPE_OBJECT derived type of this property
 * @flags: flags for the property specified
 *
 * Creates a new #GtkParamSpecComposite instance specifying a %G_TYPE_OBJECT
 * derived property, these work exactly the same as #GParamSpecObject properties
 * except that #GtkComposite will automatically assign all properties defined
 * as composite object properties to instances built from the composite derived
 * class's #GtkBuilder UI definition.
 *
 * See g_param_spec_internal() for details on property names.
 *
 * Returns: a newly created parameter specification
 */
GParamSpec*
gtk_param_spec_composite (const gchar *name,
			  const gchar *nick,
			  const gchar *blurb,
			  GType	  object_type,
			  GParamFlags  flags)
{
  GParamSpecObject *ospec;
  
  g_return_val_if_fail (g_type_is_a (object_type, G_TYPE_OBJECT), NULL);
  
  ospec = g_param_spec_internal (GTK_TYPE_PARAM_COMPOSITE,
				 name,
				 nick,
				 blurb,
				 flags);
  G_PARAM_SPEC (ospec)->value_type = object_type;
  
  return G_PARAM_SPEC (ospec);
}
