/* gtk-clutter-window.c: GtkWindow which provides a hidden ClutterStage
 *
 * Copyright (C) 2009 Collabora Ltd.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not see <http://www.fsf.org/licensing>.
 *
 * Authors:
 *   Danielle Madeley <danielle.madeley@collabora.co.uk>
 */

/**
 * SECTION:gtk-clutter-window
 * @Title: GtkClutterWindow
 * @short_description: a GtkWindow that embeds its contents onto a stage
 *
 * #GtkClutterWindow is a #GtkWindow sub-class that embeds a Clutter stage.
 *
 * #GtkClutterWindow behaves exactly like a #GtkWindow, except that its
 * child is automatically embedded inside a #GtkClutterActor and it is
 * thus part of the embedded #ClutterStage.
 *
 * Clutter actors can be added to the same stage by calling
 * gtk_clutter_window_get_stage().
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gtk-clutter-window.h"
#include "gtk-clutter-actor.h"
#include "gtk-clutter-embed.h"
#include "gtk-clutter-util.h"

#include <glib-object.h>

G_DEFINE_TYPE (GtkClutterWindow, gtk_clutter_window, GTK_TYPE_WINDOW);

#define GTK_CLUTTER_WINDOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_CLUTTER_TYPE_WINDOW, GtkClutterWindowPrivate))

struct _GtkClutterWindowPrivate
{
  GtkWidget *embed;

  ClutterActor *actor;
};

static void
gtk_clutter_window_finalize (GObject *self)
{
  G_OBJECT_CLASS (gtk_clutter_window_parent_class)->finalize (self);
}

static void
gtk_clutter_window_get_preferred_width (GtkWidget *self,
                                        gint      *minimum,
                                        gint      *natural)
{
  GtkClutterWindowPrivate *priv = GTK_CLUTTER_WINDOW (self)->priv;
  GtkWidget *bin;

  bin = gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->actor));
  gtk_widget_get_preferred_width (gtk_bin_get_child (GTK_BIN (bin)),
                                  minimum,
                                  natural);
}

static void
gtk_clutter_window_get_preferred_height (GtkWidget *self,
                                         gint      *minimum,
                                         gint      *natural)
{
  GtkClutterWindowPrivate *priv = GTK_CLUTTER_WINDOW (self)->priv;
  GtkWidget *bin;

  bin = gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->actor));
  gtk_widget_get_preferred_height (gtk_bin_get_child (GTK_BIN (bin)),
                                   minimum,
                                   natural);
}

static void
gtk_clutter_window_add (GtkContainer *self,
                        GtkWidget    *widget)
{
  GtkClutterWindowPrivate *priv;
  GtkWidget *bin;

  g_return_if_fail (GTK_CLUTTER_IS_WINDOW (self));
  priv = GTK_CLUTTER_WINDOW (self)->priv;

  bin = gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->actor));
  GTK_CONTAINER_GET_CLASS (bin)->add (GTK_CONTAINER (bin), widget);
}

static void
gtk_clutter_window_remove (GtkContainer *self,
                           GtkWidget    *widget)
{
  GtkClutterWindowPrivate *priv;
  GtkWidget *bin;

  g_return_if_fail (GTK_CLUTTER_IS_WINDOW (self));
  priv = GTK_CLUTTER_WINDOW (self)->priv;

  bin = gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->actor));
  GTK_CONTAINER_GET_CLASS (bin)->remove (GTK_CONTAINER (bin), widget);
}

static void
gtk_clutter_window_forall (GtkContainer *self,
                           gboolean      include_internals,
                           GtkCallback   callback,
                           gpointer      callback_data)
{
  GtkClutterWindowPrivate *priv;
  GtkWidget *bin;

  g_return_if_fail (GTK_CLUTTER_IS_WINDOW (self));
  priv = GTK_CLUTTER_WINDOW (self)->priv;

  /* this is particularly dodgy -- if we have asked to include_internals
   * let's only return the internals, on the assumption that when events
   * are sent to those internals, the child container will be iterated;
   * otherwise, we don't want anyone to know about the container, so we
   * return the contents of the bin */
  if (include_internals)
    {
      GTK_CONTAINER_CLASS (gtk_clutter_window_parent_class)->forall (self,
              include_internals, callback, callback_data);
    }
  else
    {
      bin = gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->actor));
      GTK_CONTAINER_GET_CLASS (bin)->forall (GTK_CONTAINER (bin),
              include_internals, callback, callback_data);
    }
}

static void
gtk_clutter_window_set_focus_child (GtkContainer *self,
                                    GtkWidget    *widget)
{
  GtkClutterWindowPrivate *priv;
  GtkWidget *bin;

  g_return_if_fail (GTK_CLUTTER_IS_WINDOW (self));
  priv = GTK_CLUTTER_WINDOW (self)->priv;

  bin = gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->actor));
  GTK_CONTAINER_GET_CLASS (bin)->set_focus_child (GTK_CONTAINER (bin), widget);
}

static GType
gtk_clutter_window_child_type (GtkContainer *self)
{
  GtkClutterWindowPrivate *priv;
  GtkWidget *bin;

  priv = GTK_CLUTTER_WINDOW (self)->priv;
  bin = gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->actor));

  return GTK_CONTAINER_GET_CLASS (bin)->child_type (GTK_CONTAINER (bin));
}

static char *
gtk_clutter_window_composite_name (GtkContainer *self,
                                   GtkWidget    *widget)
{
  GtkClutterWindowPrivate *priv;
  GtkWidget *bin;

  priv = GTK_CLUTTER_WINDOW (self)->priv;
  bin = gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->actor));

  return GTK_CONTAINER_GET_CLASS (bin)->composite_name (GTK_CONTAINER (bin),
                                                        widget);
}

static void
gtk_clutter_window_set_child_property (GtkContainer *self,
                                       GtkWidget    *widget,
                                       guint         property_id,
                                       const GValue *value,
                                       GParamSpec   *pspec)
{
  GtkClutterWindowPrivate *priv;
  GtkWidget *bin;

  priv = GTK_CLUTTER_WINDOW (self)->priv;
  bin = gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->actor));

  GTK_CONTAINER_GET_CLASS (bin)->set_child_property (GTK_CONTAINER (bin),
                                                     widget,
                                                     property_id,
                                                     value, pspec);
}

static void
gtk_clutter_window_get_child_property (GtkContainer *self,
                                       GtkWidget    *widget,
                                       guint         property_id,
                                       GValue       *value,
                                       GParamSpec   *pspec)
{
  GtkClutterWindowPrivate *priv;
  GtkWidget *bin;

  priv = GTK_CLUTTER_WINDOW (self)->priv;
  bin = gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->actor));

  GTK_CONTAINER_GET_CLASS (bin)->get_child_property (GTK_CONTAINER (bin),
                                                     widget,
                                                     property_id,
                                                     value, pspec);
}

static void
gtk_clutter_window_class_init (GtkClutterWindowClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);

  g_type_class_add_private (klass, sizeof (GtkClutterWindowPrivate));

  gobject_class->finalize = gtk_clutter_window_finalize;

  widget_class->get_preferred_width = gtk_clutter_window_get_preferred_width;
  widget_class->get_preferred_height = gtk_clutter_window_get_preferred_height;

  /* connect all of the container methods up to our bin */
  container_class->add                = gtk_clutter_window_add;
  container_class->remove             = gtk_clutter_window_remove;
  container_class->forall             = gtk_clutter_window_forall;
  container_class->set_focus_child    = gtk_clutter_window_set_focus_child;
  container_class->child_type         = gtk_clutter_window_child_type;
  container_class->composite_name     = gtk_clutter_window_composite_name;
  container_class->set_child_property = gtk_clutter_window_set_child_property;
  container_class->get_child_property = gtk_clutter_window_get_child_property;
}

static void
gtk_clutter_window_init (GtkClutterWindow *self)
{
  GtkClutterWindowPrivate *priv;
  ClutterActor *stage;

  self->priv = priv = GTK_CLUTTER_WINDOW_GET_PRIVATE (self);

  gtk_widget_push_composite_child ();

  priv->embed = gtk_clutter_embed_new ();
  gtk_widget_set_composite_name (priv->embed, "GtkClutterEmbed");

  GTK_CONTAINER_CLASS (gtk_clutter_window_parent_class)->add (GTK_CONTAINER (self), priv->embed);
  gtk_widget_show (priv->embed);

  gtk_widget_pop_composite_child ();

  stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->embed));

  priv->actor = gtk_clutter_actor_new ();
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), priv->actor);
  clutter_actor_set_name (priv->actor, "GtkClutterActor");
  clutter_actor_add_constraint_with_name (priv->actor, "content-x",
                                          clutter_bind_constraint_new (stage, CLUTTER_BIND_WIDTH, 0.0));
  clutter_actor_add_constraint_with_name (priv->actor, "content-y",
                                          clutter_bind_constraint_new (stage, CLUTTER_BIND_HEIGHT, 0.0));
}

/**
 * gtk_clutter_window_new:
 *
 * Creates a new #GtkClutterWindow widget.
 *
 * This window provides a hidden #ClutterStage on which the child
 * #GtkWidget<!-- -->s are placed. This allows other #ClutterActor<!-- -->s
 * to also be placed on the stage.
 *
 * Return value: the newly created #GtkClutterWindow
 */
GtkWidget *
gtk_clutter_window_new (void)
{
  return g_object_new (GTK_CLUTTER_TYPE_WINDOW, NULL);
}

/**
 * gtk_clutter_window_get_stage:
 * @window: the #GtkClutterWindow
 *
 * Retrieves the #ClutterStage that this window is embedding
 *
 * Use this function if you wish to add other actors to the #ClutterStage.
 *
 * Return value: (transfer none): the window's #ClutterStage
 */
ClutterActor *
gtk_clutter_window_get_stage (GtkClutterWindow *window)
{
  GtkClutterWindowPrivate *priv;

  g_return_val_if_fail (GTK_CLUTTER_IS_WINDOW (window), NULL);

  priv = window->priv;

  return gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->embed));
}
