/*
 * Copyright (C) 2008 Dell Inc.
 * Copyright (C) 2008 Canonical Ltd
 *
 * 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 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 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.
 *
 * Authored by Neil Jagdish Patel <neil.patel@canonical.com>
 *
 */

#include <glib/gi18n.h>

#include <tidy/tidy.h>

#include "launcher-app.h"
#include "launcher-behave.h"
#include "launcher-catbar.h"
#include "launcher-category.h"
#include "launcher-config.h"
#include "launcher-defines.h"
#include "launcher-util.h"
#include "launcher-category-editor.h"

#include "clutter-drag-server.h"

G_DEFINE_TYPE (LauncherCategory, launcher_category, CLUTTER_TYPE_DRAG_DEST);

#define LAUNCHER_CATEGORY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  LAUNCHER_TYPE_CATEGORY, \
  LauncherCategoryPrivate))

static ClutterTimeline       *main_time = NULL;
static ClutterEffectTemplate *main_temp = NULL;
static ClutterActor          *bg_texture = NULL;
static ClutterActor          *i_texture = NULL;

struct _LauncherCategoryPrivate
{
  LauncherMenuCategory *cat;

  ClutterActor *bg;
  ClutterActor *hilight;
  ClutterActor *icon;
  ClutterActor *text;
  ClutterActor *toggle;

  ClutterTimeline  *flip_time;
  ClutterBehaviour *behave;
  ClutterActor     *options;
  gboolean          flipped;

  ClutterTimeline *timeline;

  gboolean active;

  gint x; gint y;
};

enum
{
  CAT_SELECTED,
  FLIPPED,

  LAST_SIGNAL
};
static guint _category_signals[LAST_SIGNAL] = { 0 };

gboolean    
launcher_category_get_active (LauncherCategory *category)
{
  g_return_val_if_fail (LAUNCHER_IS_CATEGORY (category), FALSE);

  return category->priv->active;
}

static void
reset_rotation (ClutterActor *actor)
{
  clutter_actor_set_rotation (actor, CLUTTER_Y_AXIS, 0, CAW (actor)/2, 0, 0);
}

void           
launcher_category_set_active (LauncherCategory *cat,
                              gboolean          active)
{
  LauncherCategoryPrivate *priv;
  LauncherConfig *cfg = launcher_config_get_default ();
  gint alpha = 0;
  ClutterColor *text_col = &cfg->cat_font_color;

  g_return_if_fail (LAUNCHER_IS_CATEGORY (cat));
  priv = cat->priv;

  priv->active = active;

  if (CLUTTER_IS_TIMELINE (priv->timeline)  
        && clutter_timeline_is_playing (priv->timeline))
  {
    clutter_timeline_stop (priv->timeline);
    g_object_unref (priv->timeline);
  }

  if (active)
  {
    alpha = 255;
    text_col = &cfg->cat_font_active_color;

    clutter_effect_rotate (main_temp, priv->icon, CLUTTER_Y_AXIS,
                           360.0, CAW (priv->icon)/2, 0, 0, CLUTTER_ROTATE_CW,
                           (ClutterEffectCompleteFunc)reset_rotation, NULL);
  }

  clutter_effect_fade (main_temp, priv->bg, alpha, NULL, NULL);
  
  clutter_effect_fade (main_temp, priv->toggle, alpha, NULL, NULL);
  clutter_label_set_color (CLUTTER_LABEL (priv->text), text_col);
}

static gboolean
on_enter (ClutterActor *bg,
          ClutterMotionEvent *event,
          LauncherCategory *cat)
{
  LauncherCategoryPrivate *priv;

  g_return_val_if_fail (LAUNCHER_IS_CATEGORY (cat), FALSE);
  priv = cat->priv;

  if (priv->active)
    return FALSE;

  if (CLUTTER_IS_TIMELINE (priv->timeline)  
        && clutter_timeline_is_playing (priv->timeline))
  {
    clutter_timeline_stop (priv->timeline);
    g_object_unref (priv->timeline);
  }

  priv->timeline = clutter_effect_fade (main_temp, priv->bg, 100, NULL, NULL);
  clutter_effect_fade (main_temp, priv->toggle, 255, NULL, NULL);

  return TRUE;
}

static gboolean
on_leave (ClutterActor *bg, 
          ClutterMotionEvent *event, 
          LauncherCategory *cat)
{
  LauncherCategoryPrivate *priv;

  g_return_val_if_fail (LAUNCHER_IS_CATEGORY (cat), FALSE);
  priv = cat->priv;

  if (priv->active)
    return FALSE;

  if (CLUTTER_IS_TIMELINE (priv->timeline)  
        && clutter_timeline_is_playing (priv->timeline))
  {
    clutter_timeline_stop (priv->timeline);
    g_object_unref (priv->timeline);
  }

  priv->timeline = clutter_effect_fade (main_temp, priv->bg, 0, NULL, NULL);
  clutter_effect_fade (main_temp, priv->toggle, 0, NULL, NULL);

  return TRUE;
}

static void
rotate_func (ClutterBehaviour *behave,
             guint32           alpha_value,
             LauncherCategory *cat)
{
  LauncherCategoryPrivate *priv;
  gfloat factor;

  g_return_if_fail (LAUNCHER_IS_CATEGORY (cat));
  priv = cat->priv;

  factor = (gfloat)alpha_value / CLUTTER_ALPHA_MAX_ALPHA;

  if (priv->flipped)
  {
    /* We're restoring the tile to it's original state */
    clutter_actor_set_rotation (priv->bg, CLUTTER_Y_AXIS,
                                180.0 + (180.0*factor),
                                CAW (priv->bg)/2, 0, 0);
    if (CLUTTER_IS_ACTOR (priv->hilight))
      clutter_actor_set_rotation (priv->hilight, CLUTTER_Y_AXIS,
                                  180.0 + (180.0*factor),
                                  CAW (priv->hilight)/2, 0, 0);
    clutter_actor_set_rotation (priv->icon, CLUTTER_Y_AXIS,
                                180.0 + (180.0*factor),
                                CAW (priv->icon)/2, 0, 0);
    clutter_actor_set_rotation (priv->text, CLUTTER_Y_AXIS,
                                180.0 + (180.0*factor),
                                CAW (priv->text)/2, 0, 0);
    
    if (factor * 180 > 90 && CLUTTER_IS_ACTOR (priv->options))
    {
      clutter_actor_set_opacity (priv->icon, 255);
      clutter_actor_set_opacity (priv->text, 255);
      clutter_actor_destroy (priv->options);
      priv->options = NULL;
    }

    if (CLUTTER_IS_ACTOR (priv->options))
    {
      clutter_actor_set_rotation (priv->options, CLUTTER_Y_AXIS,
                                  180.0*factor,
                                  CAW (priv->options)/2, 0, 0);
    }

  }
  else
  {
    /* We're flipping the tile */
    clutter_actor_set_rotation (priv->bg, CLUTTER_Y_AXIS,
                                180.0*factor,
                                CAW (priv->bg)/2, 0, 0);
    if (CLUTTER_IS_ACTOR (priv->hilight))
      clutter_actor_set_rotation (priv->hilight, CLUTTER_Y_AXIS,
                                  180.0*factor,
                                  CAW (priv->hilight)/2, 0, 0);
    clutter_actor_set_rotation (priv->icon, CLUTTER_Y_AXIS,
                                180.0*factor,
                                CAW (priv->icon)/2, 0, 0);
    clutter_actor_set_rotation (priv->text, CLUTTER_Y_AXIS,
                                180.0*factor,
                                CAW (priv->text)/2, 0, 0);
    if (factor * 180 > 90)
    {
      clutter_actor_set_opacity (priv->icon, 0);
      clutter_actor_set_opacity (priv->text, 0);
      clutter_actor_set_opacity (priv->options, 255);
    }

    clutter_actor_set_rotation (priv->options, CLUTTER_Y_AXIS,
                                180 + (180.0*factor),
                                CAW (priv->options)/2, 0, 0);
  }

  clutter_actor_queue_redraw (CLUTTER_ACTOR (cat));
}

static void
on_rotation_completed (ClutterTimeline *timeline, LauncherCategory *cat)
{
  LauncherCategoryPrivate *priv;

  g_return_if_fail (LAUNCHER_IS_CATEGORY (cat));
  priv = cat->priv;

  priv->flipped = !priv->flipped;
}

static void
rotate (LauncherCategory *cat)
{
  LauncherCategoryPrivate *priv = cat->priv;

  if (!clutter_timeline_is_playing (priv->flip_time))
    clutter_timeline_start (priv->flip_time);
}
static void
on_delete_reponse (GtkWidget *dialog, gint res, LauncherCategory *cat)
{
  if (!LAUNCHER_IS_CATEGORY (cat))
  {
    gtk_widget_destroy (dialog);
    return;
  }

  if (res == GTK_RESPONSE_YES)
  {
    launcher_util_remove_category (cat->priv->cat);
  }

  gtk_widget_destroy (dialog);
}

static void 
popup_delete_dialog (LauncherCategory *cat)
{
  LauncherCategoryPrivate *priv = cat->priv;
  GtkWidget *dialog;

  launcher_app_lock (launcher_app_get_default (), TRUE);

  dialog = gtk_message_dialog_new_with_markup (NULL, 0,
                                   GTK_MESSAGE_QUESTION,
                                   GTK_BUTTONS_NONE,
    (g_list_length (priv->cat->applications)) ?
      _("<b>This category is not empty.</b>\n Are you sure you want to remove the %s category?") 
      : _("Are you sure you want to remove the %s category?"),
                                   priv->cat->name
                                   );
  gtk_dialog_add_buttons (GTK_DIALOG (dialog), 
                          "gtk-cancel", GTK_RESPONSE_CANCEL,
                          "gtk-delete", GTK_RESPONSE_YES,
                          NULL);
  g_object_set (dialog, 
                "title", _("Remove Category"),
                "accept-focus", TRUE,
                "focus-on-map", TRUE,
                "window-position", GTK_WIN_POS_CENTER_ALWAYS,
                NULL);
  g_signal_connect (dialog, "response",
                    G_CALLBACK (on_delete_reponse), cat);
                                   
  launcher_util_present_window (GTK_WINDOW (dialog));
}

static gboolean
on_delete_clicked (ClutterActor        *close,
                   ClutterButtonEvent *event,
                   LauncherCategory   *cat)
{
  LauncherCategoryPrivate *priv;
   
  g_return_val_if_fail (LAUNCHER_IS_CATEGORY (cat), FALSE);
  priv = cat->priv;

  popup_delete_dialog (cat);

  rotate (cat);

  return TRUE;
}


static void
on_edit_reponse (GtkWidget *dialog, gint res, LauncherCategory *cat)
{
  if (!LAUNCHER_IS_CATEGORY (cat))
  {
    gtk_widget_destroy (dialog);
    return;
  }

  if (res == GTK_RESPONSE_YES)
  {

     gchar *name = NULL, *icon_name = NULL;
     g_object_get (dialog, 
                   "category-name", &name,
                   "category-icon", &icon_name,
                   NULL);
     launcher_menu_edit_category (launcher_menu_get_default (),
                                  cat->priv->cat, 
                                  name, icon_name);
  }

  gtk_widget_destroy (dialog);
}

static void
popup_edit_dialog (LauncherCategory *cat)
{
  LauncherCategoryPrivate *priv = cat->priv;
  GtkWidget *dialog;
  LauncherMenuCategory *category;

  category = priv->cat;
 
  launcher_app_lock (launcher_app_get_default (), TRUE);

  dialog = launcher_category_editor_new ();
  g_object_set (dialog, 
                "title", _("Edit Category"),
                "category-name", launcher_menu_category_get_name (category),
              "category-icon", launcher_menu_category_get_icon_name (category),
                NULL);
  gtk_dialog_add_buttons (GTK_DIALOG (dialog), 
                          "gtk-cancel", GTK_RESPONSE_CANCEL,
                          GTK_STOCK_APPLY, GTK_RESPONSE_YES,
                          NULL);
  g_signal_connect (dialog, "response",
                    G_CALLBACK (on_edit_reponse), cat);
                                   
  launcher_util_present_window (GTK_WINDOW (dialog));
}

static gboolean
on_edit_clicked (ClutterActor       *info,
                 ClutterButtonEvent *event,
                 LauncherCategory   *cat)
{
  LauncherCategoryPrivate *priv;
  
  g_return_val_if_fail (LAUNCHER_IS_CATEGORY (cat), FALSE);
  priv = cat->priv;

  popup_edit_dialog (cat);

  rotate (cat);

  return TRUE;
}

static void
open_category (ClutterTimeline *timeline, LauncherCategory *cat)
{
  launcher_catbar_set (LAUNCHER_CATBAR (launcher_catbar_get_default ()), cat);
  
  g_signal_handlers_disconnect_by_func (timeline, open_category, cat);
}

static void
on_open_clicked (ClutterActor       *actor, 
                 ClutterButtonEvent *event,  
                 LauncherCategory *cat)
{
  LauncherCategoryPrivate *priv;
  
  g_return_if_fail (LAUNCHER_IS_CATEGORY (cat));
  priv = cat->priv;

  rotate (cat);

  g_signal_connect (priv->flip_time, "completed", 
                    G_CALLBACK (open_category), cat);

}

static ClutterActor *
make_item (const gchar *name, const gchar *icon_name, gint width, gint height)
{
  LauncherConfig *cfg = launcher_config_get_default ();
  ClutterActor *bg, *icon, *group, *label;

  bg = clutter_group_new ();
  clutter_actor_set_size (bg, width, height);
  clutter_actor_show (bg);
  
  icon = launcher_util_texture_new_from_named_icon (icon_name);
  clutter_container_add_actor (CLUTTER_CONTAINER (bg), icon);
  clutter_actor_set_size (icon, height * 0.80, height * 0.80);
  clutter_actor_set_anchor_point_from_gravity (icon, CLUTTER_GRAVITY_CENTER);
  clutter_actor_set_position (icon, height/2, height/2);
  clutter_actor_show (icon);

  group = clutter_group_new ();
  clutter_container_add_actor (CLUTTER_CONTAINER (bg), group);
  clutter_actor_set_size (group, height, width - CAW (icon) - height);
  clutter_actor_set_anchor_point_from_gravity (group, CLUTTER_GRAVITY_WEST);
  clutter_actor_set_position (group, CAW (icon) + height * 0.30, height/2);
  clutter_actor_show (group);
  
  label = clutter_label_new_full (cfg->font_name, name, &cfg->icon_font_color);
  clutter_container_add_actor (CLUTTER_CONTAINER (group), label);
  clutter_actor_set_width (label, width - CAW (icon));
  clutter_label_set_ellipsize (CLUTTER_LABEL (label), PANGO_ELLIPSIZE_END);
  clutter_actor_set_position (label, 0, - (CAH (label)/2));
  clutter_actor_show (label);

  return bg;
}

static gboolean
on_toggle_clicked (ClutterActor *close,
                   ClutterButtonEvent *event,
                   LauncherCategory *cat)
{
  LauncherCategoryPrivate *priv;
    
  g_return_val_if_fail (LAUNCHER_IS_CATEGORY (cat), FALSE);
  priv = cat->priv;

  if (clutter_timeline_is_playing (priv->flip_time))
    return TRUE;

  if (!priv->flipped)
  {
    ClutterActor *bg, *item;

    priv->options = clutter_group_new ();
    clutter_container_add_actor (CLUTTER_CONTAINER (cat), priv->options);
    clutter_actor_set_size (priv->options, CAW (cat), CAH (cat));
    clutter_actor_set_position (priv->options, 0, 0);
    clutter_actor_set_rotation (priv->options, CLUTTER_Y_AXIS, 
                                180, CAW (priv->options)/2, 0, 0);
    clutter_actor_set_opacity (priv->options, 0);
    clutter_actor_show (priv->options);

    bg = item = launcher_util_texture_new_from_file (PKGDATADIR"/options.svg");
    clutter_actor_set_size (bg, CAW (cat)*0.98, CAH (cat) * 0.75);
    clutter_container_add_actor (CLUTTER_CONTAINER (priv->options), bg);
    clutter_actor_set_anchor_point_from_gravity (bg, CLUTTER_GRAVITY_NORTH);
    clutter_actor_set_position (bg, CAW (cat)/2, CAH (cat) *0.2);
    clutter_actor_show (bg);

    clutter_actor_show (item);

    item = make_item (priv->active ? _("Close") :_("Open"), 
                      priv->active ? "gtk-close" : "gtk-open",
                      CAW (bg), CAH (bg)/3);
    clutter_container_add_actor (CLUTTER_CONTAINER (priv->options), item);
    clutter_actor_set_position (item, 
                                CAW (cat) * 0.01,
                                clutter_actor_get_y (bg));
    clutter_actor_show (item);
    clutter_actor_set_reactive (item, TRUE);
    g_signal_connect (item, "button-release-event",
                      G_CALLBACK (on_open_clicked), cat);

    item = make_item (_("Edit"), "gtk-properties",
                      CAW (bg), CAH (bg)/3);
    clutter_container_add_actor (CLUTTER_CONTAINER (priv->options), item);
    clutter_actor_set_position (item, 
                                CAW (cat) * 0.01,
                                clutter_actor_get_y (bg) + CAH (bg)/3);
    clutter_actor_show (item);
    clutter_actor_set_reactive (item, TRUE);
    g_signal_connect (item, "button-release-event",
                      G_CALLBACK (on_edit_clicked), cat);

    item = make_item (_("Delete"), "gtk-delete",
                      CAW (bg), CAH (bg)/3);
    clutter_container_add_actor (CLUTTER_CONTAINER (priv->options), item);
    clutter_actor_set_position (item, 
                                CAW (cat) * 0.01,
                                clutter_actor_get_y (bg) + ((CAH (bg)/3)*2));
    clutter_actor_show (item);
    clutter_actor_set_reactive (item, TRUE);
    g_signal_connect (item, "button-release-event",
                      G_CALLBACK (on_delete_clicked), cat);

    g_signal_emit (cat, _category_signals[FLIPPED], 0); 
  }

  rotate (cat);

  return TRUE;
}

static gboolean
show_menu (ClutterActor       *actor,
           ClutterButtonEvent *event,
           LauncherCategory   *cat)
{
  if (event->button != 3)
  {
    return FALSE;
  }

  on_toggle_clicked (actor, event, cat);

  return TRUE;
}


static void
launcher_category_set_menu_category (LauncherCategory     *cat,
                                     LauncherMenuCategory *category)
{
  LauncherCategoryPrivate *priv;
  LauncherConfig *cfg = launcher_config_get_default ();
  gint pos = 0;
  gint cat_width = cfg->cat_width;
  gint cat_height = cfg->cat_height;
  gint cat_padding = cfg->cat_padding;
  ClutterActor *sep;
  ClutterColor black = { 0x00, 0x00, 0x00, 0xff };
  ClutterColor white = { 0xff, 0xff, 0xff, 0xff };
  
  g_return_if_fail (LAUNCHER_IS_CATEGORY (cat));
  g_return_if_fail (category);
  priv = cat->priv;

  priv->cat = category;

  /* Bg texture first */
  priv->bg = tidy_texture_frame_new (CLUTTER_TEXTURE (bg_texture), 
                                     2, 2, 2, 2);
  clutter_container_add_actor (CLUTTER_CONTAINER (cat), priv->bg);
  clutter_actor_set_size (priv->bg, cat_width, cat_height);
  clutter_actor_set_position (priv->bg, 0, 0);
  clutter_actor_set_opacity (priv->bg, 0);
  clutter_actor_show (priv->bg);

  clutter_actor_set_reactive (priv->bg, TRUE);
  g_signal_connect (priv->bg, "enter-event", G_CALLBACK (on_enter), cat);
  g_signal_connect (priv->bg, "leave-event", G_CALLBACK (on_leave), cat);
  g_signal_connect (priv->bg, "button-release-event", 
                    G_CALLBACK (show_menu), cat);

  /* Vsep */
  sep = clutter_rectangle_new_with_color (&black);
  clutter_container_add_actor (CLUTTER_CONTAINER (cat), sep);
  clutter_actor_set_size (sep, 1, cat_height);
  clutter_actor_set_position (sep, cat_width-1, 0);
  clutter_actor_set_opacity (sep, 50);
  clutter_actor_show (sep);

  sep = clutter_rectangle_new_with_color (&white);
  clutter_container_add_actor (CLUTTER_CONTAINER (cat), sep);
  clutter_actor_set_size (sep, 1, cat_height);
  clutter_actor_set_position (sep, cat_width, 0);
  clutter_actor_set_opacity (sep, 50);
  clutter_actor_show (sep);

  /* Category */
  priv->icon = clutter_texture_new_from_pixbuf (
                                    launcher_menu_category_get_icon (category));
  clutter_container_add_actor (CLUTTER_CONTAINER (cat), priv->icon);
  pos = (cat_height - CAT_ICON_SIZE()) / 2;
  clutter_actor_set_position (priv->icon, 
                              (cat_width - CAT_ICON_SIZE())/2, 
                              pos - (cat_padding*1.5));
  clutter_actor_show (priv->icon); 

  /* Text */
  priv->text = clutter_label_new_full (cfg->font_name, 
                                       category->name,
                                       &cfg->cat_font_color);
  clutter_container_add_actor (CLUTTER_CONTAINER (cat), priv->text);
  clutter_label_set_ellipsize (CLUTTER_LABEL (priv->text), 
                               PANGO_ELLIPSIZE_END);
  clutter_label_set_alignment (CLUTTER_LABEL (priv->text),  
                               PANGO_ALIGN_CENTER);
  clutter_label_set_text (CLUTTER_LABEL (priv->text), category->name);
  clutter_actor_set_position (priv->text, 
                              0,//cat_width/2,
                              pos + CAH(priv->icon));
  clutter_actor_set_width (priv->text, cat_width);
  clutter_actor_show (priv->text);

  /* Info icon */
  priv->toggle = clutter_clone_texture_new (CLUTTER_TEXTURE (i_texture));
  clutter_container_add_actor (CLUTTER_CONTAINER (cat), priv->toggle);
  clutter_actor_set_size (priv->toggle, cat_height*0.2, cat_height*0.2);
  clutter_actor_set_position (priv->toggle, cat_width-CAW (priv->toggle)-3, 3);
  clutter_actor_set_opacity (priv->toggle, 0);
  clutter_actor_show (priv->toggle);
  clutter_actor_set_reactive (priv->toggle, TRUE);
  g_signal_connect (priv->toggle, "button-release-event",
                    G_CALLBACK (on_toggle_clicked), cat);
  g_signal_connect (priv->toggle, "enter-event",
                    G_CALLBACK (on_enter), cat);
  g_signal_connect (priv->toggle, "leave-event",
                    G_CALLBACK (on_leave), cat);
}

static gboolean
on_drag_drop (ClutterDragDest *category, gpointer data)
{
  LauncherCategoryPrivate *priv;
  LauncherMenuApplication *app = (LauncherMenuApplication*)data;
 
  g_return_val_if_fail (LAUNCHER_IS_CATEGORY (category), FALSE);
  g_return_val_if_fail (app, FALSE);
  priv = LAUNCHER_CATEGORY_GET_PRIVATE (category);

  launcher_menu_edit_application (launcher_menu_get_default (),
                                  app, priv->cat, NULL, NULL, NULL);

  return TRUE;
}

void    
launcher_category_set_flipped(LauncherCategory *cat, gboolean flipped)
{
  g_return_if_fail (LAUNCHER_IS_CATEGORY (cat));

  if (cat->priv->flipped != flipped)
    rotate (cat); 
}

/* GObject stuff */
static void
launcher_category_finalize (GObject *category)
{
  g_object_unref (main_time);
  g_object_unref (main_temp);
  g_object_unref (bg_texture);

  G_OBJECT_CLASS (launcher_category_parent_class)->finalize (category);
}

static void
launcher_category_class_init (LauncherCategoryClass *klass)
{
  GObjectClass        *obj_class = G_OBJECT_CLASS (klass);
  ClutterDragDestClass *drag_class = CLUTTER_DRAG_DEST_CLASS (klass);

  obj_class->finalize = launcher_category_finalize;
  drag_class->drop = on_drag_drop;

	_category_signals[CAT_SELECTED] =
		g_signal_new ("category-selected",
			      G_OBJECT_CLASS_TYPE (obj_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (LauncherCategoryClass, category_selected),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__POINTER, 
			      G_TYPE_NONE,1, G_TYPE_POINTER);

  _category_signals[FLIPPED] =
		g_signal_new ("flipped",
			      G_OBJECT_CLASS_TYPE (obj_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (LauncherCategoryClass, flipped),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__VOID, 
			      G_TYPE_NONE, 0);

  g_type_class_add_private (obj_class, sizeof (LauncherCategoryPrivate));
}
      
static void
launcher_category_init (LauncherCategory *category)
{
  LauncherCategoryPrivate *priv;
  ClutterAlpha *alpha;

  priv = category->priv = LAUNCHER_CATEGORY_GET_PRIVATE (category);

  priv->flipped = FALSE;

  if (CLUTTER_IS_TIMELINE (main_time))
    g_object_ref (main_time);
  else 
    main_time = launcher_util_timeline_new_fast ();

  if (CLUTTER_IS_EFFECT_TEMPLATE (main_temp))
    g_object_ref (main_temp);
  else
    main_temp = clutter_effect_template_new (main_time, 
                                             clutter_sine_inc_func);
  if (CLUTTER_IS_TEXTURE (bg_texture))
    g_object_ref (bg_texture);
  else
    bg_texture = launcher_util_texture_new_from_file (PKGDATADIR"/catbg.svg");

  if (CLUTTER_IS_TEXTURE (i_texture))
    g_object_ref (i_texture);
  else
    i_texture = launcher_util_texture_new_from_file (PKGDATADIR"/info.svg");

  clutter_drag_dest_enable (CLUTTER_DRAG_DEST (category));

  priv->flip_time = launcher_util_timeline_new_slow ();
  alpha = clutter_alpha_new_full (priv->flip_time,
                                  clutter_sine_inc_func,
                                  NULL, NULL);
  priv->behave = launcher_behave_new (alpha, 
                                      (LauncherBehaveAlphaFunc)rotate_func,
                                      (gpointer)category);

  g_signal_connect (priv->flip_time, "completed",
                    G_CALLBACK (on_rotation_completed), category);

}

ClutterActor *
launcher_category_new (LauncherMenuCategory *cat)
{
  ClutterActor *category;

  category = g_object_new (LAUNCHER_TYPE_CATEGORY, 
                           NULL);
  launcher_category_set_menu_category (LAUNCHER_CATEGORY (category), cat);
  return category;
}
