/*
 * mx-floating-widget: always-on-top base actor
 *
 * Copyright 2009 Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Written by: Thomas Wood <thomas.wood@intel.com>
 *
 */

#include "mx-floating-widget.h"

G_DEFINE_ABSTRACT_TYPE (MxFloatingWidget, mx_floating_widget, MX_TYPE_WIDGET)

#define FLOATING_WIDGET_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), MX_TYPE_FLOATING_WIDGET, MxFloatingWidgetPrivate))

struct _MxFloatingWidgetPrivate
{
  ClutterActor *stage;

  CoglMatrix paint_matrix;
  CoglMatrix pick_matrix;

  gulong pick_handler;
  gulong paint_handler;
};


static void
mx_floating_widget_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
{
  switch (property_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
mx_floating_widget_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
{
  switch (property_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
mx_floating_widget_dispose (GObject *object)
{
  G_OBJECT_CLASS (mx_floating_widget_parent_class)->dispose (object);
}

static void
mx_floating_widget_finalize (GObject *object)
{
  G_OBJECT_CLASS (mx_floating_widget_parent_class)->finalize (object);
}

static void
stage_weak_notify (MxFloatingWidget *widget,
                   ClutterStage     *stage)
{
  widget->priv->stage = NULL;
}

static void
mx_floating_widget_pick_from_stage (ClutterActor       *stage,
                                    const ClutterColor *color,
                                    MxFloatingWidget   *widget)
{
  MxFloatingWidgetClass *klass;
  MxFloatingWidgetPrivate *priv = widget->priv;
  gboolean has_clip;

  if (!CLUTTER_ACTOR_IS_REACTIVE (widget))
    return;

  klass = MX_FLOATING_WIDGET_CLASS (G_OBJECT_GET_CLASS (widget));

  cogl_push_matrix ();

  cogl_set_modelview_matrix (&(priv->pick_matrix));

  has_clip = clutter_actor_has_clip (CLUTTER_ACTOR (widget));

  if (has_clip)
    {
      gfloat x, y, w, h;
      clutter_actor_get_clip (CLUTTER_ACTOR (widget), &x, &y, &w, &h);
#if CLUTTER_CHECK_VERSION (1, 1, 3)
      cogl_clip_push_rectangle (x, y, x + w, y + h);
#else
      cogl_clip_push (x, y, w, h);
#endif
    }

  if (klass->floating_pick)
    klass->floating_pick (CLUTTER_ACTOR (widget), color);

  if (has_clip)
    cogl_clip_pop ();

  cogl_pop_matrix ();
}

static void
mx_floating_widget_paint_from_stage (ClutterActor     *stage,
                                     MxFloatingWidget *widget)
{
  MxFloatingWidgetClass *klass;
  MxFloatingWidgetPrivate *priv = widget->priv;
  gboolean has_clip;

  klass = MX_FLOATING_WIDGET_CLASS (G_OBJECT_GET_CLASS (widget));

  cogl_push_matrix ();

  cogl_set_modelview_matrix (&(priv->paint_matrix));

  has_clip = clutter_actor_has_clip (CLUTTER_ACTOR (widget));

  if (has_clip)
    {
      gfloat x, y, w, h;
      clutter_actor_get_clip (CLUTTER_ACTOR (widget), &x, &y, &w, &h);
#if CLUTTER_CHECK_VERSION (1, 1, 3)
      cogl_clip_push_rectangle (x, y, x + w, y + h);
#else
      cogl_clip_push (x, y, w, h);
#endif
    }

  if (klass->floating_paint)
    klass->floating_paint (CLUTTER_ACTOR (widget));

  if (has_clip)
    cogl_clip_pop ();

  cogl_pop_matrix ();
}

static void
mx_floating_widget_map (ClutterActor *actor)
{
  MxFloatingWidgetPrivate *priv = MX_FLOATING_WIDGET (actor)->priv;

  CLUTTER_ACTOR_CLASS (mx_floating_widget_parent_class)->map (actor);

  /* connect after the paint and pick signals on the stage have run, so that
   * we can be sure to be painted and picked above all other actors else */
  priv->stage = clutter_actor_get_stage (actor);
  g_object_weak_ref (G_OBJECT (priv->stage), (GWeakNotify) stage_weak_notify,
                     actor);

  priv->paint_handler =
    g_signal_connect_after (priv->stage,
                            "paint",
                            G_CALLBACK (mx_floating_widget_paint_from_stage),
                            actor);

  priv->pick_handler =
    g_signal_connect_after (priv->stage,
                            "pick",
                            G_CALLBACK (mx_floating_widget_pick_from_stage),
                            actor);
}

static void
mx_floating_widget_unmap (ClutterActor *actor)
{
  MxFloatingWidgetPrivate *priv = MX_FLOATING_WIDGET (actor)->priv;

  CLUTTER_ACTOR_CLASS (mx_floating_widget_parent_class)->unmap (actor);

  if (priv->stage)
    {
      g_signal_handler_disconnect (priv->stage, priv->paint_handler);
      priv->paint_handler = 0;

      g_signal_handler_disconnect (priv->stage, priv->pick_handler);
      priv->pick_handler = 0;

      g_object_weak_unref (G_OBJECT (priv->stage),
                           (GWeakNotify) stage_weak_notify,
                           actor);
      priv->stage = NULL;
    }

}

static void
mx_floating_widget_paint_null (ClutterActor *actor)
{
  /* store the matrix for later
   * the widget is painted after the stage has painted, so that it appears above
   * everything else */
  cogl_get_modelview_matrix (&(MX_FLOATING_WIDGET (actor)->priv->paint_matrix));
}

static void
mx_floating_widget_pick_null (ClutterActor       *actor,
                              const ClutterColor *color)
{
  /* store the matrix for later
   * the widget is picked after the stage has picked, so that it appears above
   * everything else */
  cogl_get_modelview_matrix (&(MX_FLOATING_WIDGET (actor)->priv->pick_matrix));
}


static void
mx_floating_widget_floating_pick (ClutterActor      *widget,
                                  const ClutterColor *color)
{
  /* default implementation is to chain up to the original pick */
  CLUTTER_ACTOR_CLASS (mx_floating_widget_parent_class)->pick (widget, color);
}

static void
mx_floating_widget_floating_paint (ClutterActor *widget)
{
  /* default implementation is to chain up to the original paint */
  CLUTTER_ACTOR_CLASS (mx_floating_widget_parent_class)->paint (widget);
}

static void
mx_floating_widget_class_init (MxFloatingWidgetClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
  MxFloatingWidgetClass *float_class = MX_FLOATING_WIDGET_CLASS (klass);

  g_type_class_add_private (klass, sizeof (MxFloatingWidgetPrivate));

  object_class->get_property = mx_floating_widget_get_property;
  object_class->set_property = mx_floating_widget_set_property;
  object_class->dispose = mx_floating_widget_dispose;
  object_class->finalize = mx_floating_widget_finalize;

  actor_class->map = mx_floating_widget_map;
  actor_class->unmap = mx_floating_widget_unmap;
  actor_class->pick = mx_floating_widget_pick_null;
  actor_class->paint = mx_floating_widget_paint_null;

  float_class->floating_pick = mx_floating_widget_floating_pick;
  float_class->floating_paint = mx_floating_widget_floating_paint;
}

static void
mx_floating_widget_init (MxFloatingWidget *self)
{
  self->priv = FLOATING_WIDGET_PRIVATE (self);
}

