/*
 * Borrowed from Moblin-Web-Browser: The web browser for Moblin
 * Copyright (c) 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.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <math.h>
#include <string.h>
#include <gtk/gtk.h>
#include "e-mail-tab.h"

#define E_MAIL_PIXBOUND(u) ((gfloat)((gint)(u)))

static void mx_draggable_iface_init (MxDraggableIface *iface);

G_DEFINE_TYPE_WITH_CODE (
	EMailTab,
	e_mail_tab,
	MX_TYPE_WIDGET,
	G_IMPLEMENT_INTERFACE (
		MX_TYPE_DRAGGABLE, mx_draggable_iface_init))

enum {
	PROP_0,
	PROP_ICON,
	PROP_TEXT,
	PROP_CAN_CLOSE,
	PROP_TAB_WIDTH,
	PROP_DOCKING,
	PROP_PREVIEW,
	PROP_PREVIEW_MODE,
	PROP_PREVIEW_DURATION,
	PROP_SPACING,
	PROP_PRIVATE,
	PROP_ACTIVE,
	PROP_DRAG_THRESHOLD,
	PROP_DRAG_AXIS,
	PROP_DRAG_CONTAINMENT_AREA,
	PROP_DRAG_ENABLED,
	PROP_DRAG_ACTOR,
};

enum {
	CLICKED,
	CLOSED,
	TRANSITION_COMPLETE,
	LAST_SIGNAL
};

/* Animation stage lengths */
#define TAB_S1_ANIM 0.75
#define TAB_S2_ANIM (1.0-TAB_S1_ANIM)

static guint signals[LAST_SIGNAL] = { 0, };

static void e_mail_tab_close_clicked_cb (MxButton *button, EMailTab *self);

struct _EMailTabPrivate {
	ClutterActor *icon;
	ClutterActor *default_icon;
	ClutterActor *label;
	ClutterActor *close_button;
	gboolean can_close;
	gint width;
	gboolean docking;
	gfloat spacing;
	gboolean private;
	guint alert_count;
	guint alert_source;
	gboolean has_text;

	guint active	: 1;
	guint pressed : 1;
	guint hover	 : 1;

	ClutterActor *preview;
	gboolean preview_mode;
	ClutterTimeline *preview_timeline;
	gdouble preview_height_progress;
	guint anim_length;

	ClutterActor *old_bg;

	ClutterActor *drag_actor;
	ClutterActorBox drag_area;
	gboolean drag_enabled;
	MxDragAxis drag_axis;
	gint drag_threshold;
	gulong drag_threshold_handler;
	gfloat press_x;
	gfloat press_y;
	gboolean in_drag;
};

static void
e_mail_tab_drag_begin (MxDraggable *draggable,
                       gfloat event_x,
                       gfloat event_y,
                       gint event_button,
                       ClutterModifierType modifiers)
{
	gfloat x, y;

	EMailTabPrivate *priv = E_MAIL_TAB (draggable)->priv;
	ClutterActor *self = CLUTTER_ACTOR (draggable);
	ClutterActor *actor = mx_draggable_get_drag_actor (draggable);
	ClutterActor *stage = clutter_actor_get_stage (self);

	priv->in_drag = TRUE;

	clutter_actor_get_transformed_position (self, &x, &y);
	clutter_actor_set_position (actor, x, y);

	/* Start up animation */
	if (CLUTTER_IS_TEXTURE (actor)) {
		/* TODO: Some neat deformation effect? */
	} else {
		/* Fade in */
		clutter_actor_set_opacity (actor, 0x00);
		clutter_actor_animate (
			actor, CLUTTER_LINEAR, 150,
			"opacity", 0xff,
			NULL);
	}

	clutter_container_add_actor (CLUTTER_CONTAINER (stage), actor);
}

static void
e_mail_tab_drag_motion (MxDraggable *draggable,
                        gfloat delta_x,
                        gfloat delta_y)
{
	ClutterActor *actor = mx_draggable_get_drag_actor (draggable);
	clutter_actor_move_by (actor, delta_x, delta_y);
}

static void
e_mail_tab_drag_end_anim_cb (ClutterAnimation *animation,
                             EMailTab *tab)
{
	ClutterActor *actor =
		CLUTTER_ACTOR (clutter_animation_get_object (animation));
	ClutterActor *parent = clutter_actor_get_parent (actor);

	if (parent)
		clutter_container_remove_actor (
			CLUTTER_CONTAINER (parent), actor);
}

static void
e_mail_tab_drag_end (MxDraggable *draggable,
                     gfloat event_x,
                     gfloat event_y)
{
	EMailTab *self = E_MAIL_TAB (draggable);
	EMailTabPrivate *priv = self->priv;

	priv->in_drag = FALSE;

	if (priv->drag_actor) {
		ClutterActor *parent = clutter_actor_get_parent (priv->drag_actor);
		if (parent) {
			/* Animate drop */
			if (CLUTTER_IS_TEXTURE (priv->drag_actor)) {
				/* TODO: Some neat deformation effect? */
				clutter_container_remove_actor (
					CLUTTER_CONTAINER (parent),
					priv->drag_actor);
			} else {
				clutter_actor_animate (
					priv->drag_actor,
					CLUTTER_LINEAR, 150,
					"opacity", 0x00,
					"signal::completed",
					G_CALLBACK (e_mail_tab_drag_end_anim_cb),
					self, NULL);
			}
		}

		g_object_unref (priv->drag_actor);
		priv->drag_actor = NULL;
	}
}

static void
mx_draggable_iface_init (MxDraggableIface *iface)
{
	iface->drag_begin = e_mail_tab_drag_begin;
	iface->drag_motion = e_mail_tab_drag_motion;
	iface->drag_end = e_mail_tab_drag_end;
}

static void
e_mail_tab_get_property (GObject *object,
                         guint property_id,
                         GValue *value,
                         GParamSpec *pspec)
{
	EMailTab *tab = E_MAIL_TAB (object);
	EMailTabPrivate *priv = tab->priv;

	switch (property_id) {
		case PROP_ICON:
			g_value_set_object (value, e_mail_tab_get_icon (tab));
			break;

		case PROP_TEXT:
			g_value_set_string (value, e_mail_tab_get_text (tab));
			break;

		case PROP_CAN_CLOSE:
			g_value_set_boolean (value, e_mail_tab_get_can_close (tab));
			break;

		case PROP_TAB_WIDTH:
			g_value_set_int (value, e_mail_tab_get_width (tab));
			break;

		case PROP_DOCKING:
			g_value_set_boolean (value, e_mail_tab_get_docking (tab));
			break;

		case PROP_PREVIEW:
			g_value_set_object (value, e_mail_tab_get_preview_actor (tab));
			break;

		case PROP_PREVIEW_MODE:
			g_value_set_boolean (value, e_mail_tab_get_preview_mode (tab));
			break;

		case PROP_PREVIEW_DURATION:
			g_value_set_uint (value, e_mail_tab_get_preview_duration (tab));
			break;

		case PROP_SPACING:
			g_value_set_float (value, e_mail_tab_get_spacing (tab));
			break;

		case PROP_PRIVATE:
			g_value_set_boolean (value, e_mail_tab_get_private (tab));
			break;

		case PROP_ACTIVE:
			g_value_set_boolean (value, e_mail_tab_get_active (tab));
			break;

		case PROP_DRAG_THRESHOLD:
			g_value_set_uint (value, (guint) priv->drag_threshold);
			break;

		case PROP_DRAG_AXIS:
			g_value_set_enum (value, priv->drag_axis);
			break;

		case PROP_DRAG_CONTAINMENT_AREA:
			g_value_set_boxed (value, &priv->drag_area);
			break;

		case PROP_DRAG_ENABLED:
			g_value_set_boolean (value, priv->drag_enabled);
			break;

		case PROP_DRAG_ACTOR:
			if (!priv->drag_actor)
				priv->drag_actor = g_object_ref_sink (
					clutter_clone_new (CLUTTER_ACTOR (tab)));
			g_value_set_object (value, priv->drag_actor);
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		}
}

static void
e_mail_tab_set_property (GObject *object,
                         guint property_id,
                         const GValue *value,
                         GParamSpec *pspec)
{
	EMailTab *tab = E_MAIL_TAB (object);
	EMailTabPrivate *priv = tab->priv;

	switch (property_id) {
		case PROP_ICON:
			e_mail_tab_set_icon (
				tab, g_value_get_object (value));
			break;

		case PROP_TEXT:
			e_mail_tab_set_text (
				tab, g_value_get_string (value));
			break;

		case PROP_CAN_CLOSE:
			e_mail_tab_set_can_close (
				tab, g_value_get_boolean (value));

		case PROP_TAB_WIDTH:
			e_mail_tab_set_width (
				tab, g_value_get_int (value));
			break;

		case PROP_DOCKING:
			e_mail_tab_set_docking (
				tab, g_value_get_boolean (value));
			break;

		case PROP_PREVIEW:
			e_mail_tab_set_preview_actor (
				tab, g_value_get_object (value));
			break;

		case PROP_PREVIEW_MODE:
			e_mail_tab_set_preview_mode (
				tab, g_value_get_boolean (value));
			break;

		case PROP_PREVIEW_DURATION:
			e_mail_tab_set_preview_duration (
				tab, g_value_get_uint (value));
			break;

		case PROP_SPACING:
			e_mail_tab_set_spacing (
				tab, g_value_get_float (value));
			break;

		case PROP_PRIVATE:
			e_mail_tab_set_private (
				tab, g_value_get_boolean (value));
			break;

		case PROP_ACTIVE:
			e_mail_tab_set_active (
				tab, g_value_get_boolean (value));
			break;

		case PROP_DRAG_THRESHOLD:
			break;

		case PROP_DRAG_AXIS:
			priv->drag_axis = g_value_get_enum (value);
			break;

		case PROP_DRAG_CONTAINMENT_AREA:
		{
			ClutterActorBox *box = g_value_get_boxed (value);

			if (box)
				priv->drag_area = *box;
			else
				memset (
					&priv->drag_area, 0,
					sizeof (ClutterActorBox));
			break;
		}

		case PROP_DRAG_ENABLED:
			priv->drag_enabled = g_value_get_boolean (value);
			break;

		case PROP_DRAG_ACTOR:
		{
			ClutterActor *new_actor = g_value_get_object (value);

			if (priv->drag_actor) {
				ClutterActor *parent;

				parent = clutter_actor_get_parent (priv->drag_actor);

				/* We know it's a container because we added it ourselves */
				if (parent)
					clutter_container_remove_actor (
						CLUTTER_CONTAINER (parent),
						priv->drag_actor);

				g_object_unref (priv->drag_actor);
				priv->drag_actor = NULL;
			}

			if (new_actor)
				priv->drag_actor = g_object_ref_sink (new_actor);

			break;
		}

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
	}
}

static void
e_mail_tab_dispose_old_bg (EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->old_bg) {
		ClutterActor *parent;

		parent = clutter_actor_get_parent (priv->old_bg);
		if (parent == (ClutterActor *) tab)
			clutter_actor_unparent (priv->old_bg);
		g_object_unref (priv->old_bg);
		priv->old_bg = NULL;
	}
}

static void
e_mail_tab_dispose (GObject *object)
{
	EMailTab *tab = E_MAIL_TAB (object);
	EMailTabPrivate *priv = tab->priv;

	e_mail_tab_dispose_old_bg (tab);

	if (priv->icon) {
		clutter_actor_unparent (priv->icon);
		priv->icon = NULL;
	}

	if (priv->default_icon) {
		g_object_unref (priv->default_icon);
		priv->default_icon = NULL;
	}

	if (priv->label) {
		clutter_actor_unparent (CLUTTER_ACTOR (priv->label));
		priv->label = NULL;
	}

	if (priv->close_button) {
		clutter_actor_unparent (CLUTTER_ACTOR (priv->close_button));
		priv->close_button = NULL;
	}

	if (priv->preview) {
		clutter_actor_unparent (priv->preview);
		priv->preview = NULL;
	}

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

	if (priv->drag_actor) {
		ClutterActor *parent;

		parent = clutter_actor_get_parent (priv->drag_actor);
		if (parent)
			clutter_container_remove_actor (
				CLUTTER_CONTAINER (parent),
				priv->drag_actor);

		g_object_unref (priv->drag_actor);
		priv->drag_actor = NULL;
	}

	if (priv->drag_threshold_handler)
		g_signal_handler_disconnect (
			gtk_settings_get_default (),
			priv->drag_threshold_handler);
			priv->drag_threshold_handler = 0;

	G_OBJECT_CLASS (e_mail_tab_parent_class)->dispose (object);
}

static void
e_mail_tab_get_preferred_width (ClutterActor *actor,
                                gfloat for_height,
                                gfloat *min_width_p,
                                gfloat *natural_width_p)
{
	MxPadding padding;
	EMailTabPrivate *priv = E_MAIL_TAB (actor)->priv;

	/* Get padding */
	mx_widget_get_padding (MX_WIDGET (actor), &padding);
	if (min_width_p)
		*min_width_p = padding.left + padding.right;
	if (natural_width_p)
		*natural_width_p = padding.left + padding.right;

	if (priv->width >= 0) {
		if (natural_width_p)
			*natural_width_p += priv->width;
	} else {
		gfloat min_width, nat_width, acc_min_width, acc_nat_width;

		acc_min_width = acc_nat_width = 0;

		if (priv->has_text)
			clutter_actor_get_preferred_size (
				CLUTTER_ACTOR (priv->label),
				&acc_min_width, NULL,
				&acc_nat_width, NULL);

		if (priv->icon)
			clutter_actor_get_preferred_size (
				priv->icon,
				&min_width, NULL,
				&nat_width, NULL);
				acc_min_width += min_width;
				acc_nat_width += nat_width;

		if (priv->can_close)
			clutter_actor_get_preferred_size (
				CLUTTER_ACTOR (priv->close_button),
				&min_width, NULL,
				&nat_width, NULL);
				acc_min_width += min_width;
				acc_nat_width += nat_width;

		if (priv->preview && priv->preview_mode) {
			clutter_actor_get_preferred_size (
				priv->preview,
				&min_width, NULL,
				&nat_width, NULL);

			if (min_width > acc_min_width)
				acc_min_width = min_width;
			if (nat_width > acc_nat_width)
				acc_nat_width = nat_width;
		}

		if (min_width_p)
			*min_width_p += acc_min_width;
		if (natural_width_p)
			*natural_width_p += acc_nat_width;
	}
}

void
e_mail_tab_get_height_no_preview (EMailTab *tab,
                                  gfloat for_width,
                                  gfloat *min_height_p,
                                  gfloat *natural_height_p)
{
	MxPadding padding;
	gfloat min_height, nat_height, tmp_min_height, tmp_nat_height;

	ClutterActor *actor = CLUTTER_ACTOR (tab);
	EMailTabPrivate *priv = tab->priv;

	/* Get padding */
	mx_widget_get_padding (MX_WIDGET (actor), &padding);
	if (min_height_p)
		*min_height_p = padding.top + padding.bottom;
	if (natural_height_p)
		*natural_height_p = padding.top + padding.bottom;

	min_height = nat_height = 0;
	if (priv->has_text)
		clutter_actor_get_preferred_height (
			CLUTTER_ACTOR (priv->label), -1,
			&min_height, &nat_height);

	if (priv->icon) {
		clutter_actor_get_preferred_height (
			priv->icon, -1, &tmp_min_height, &tmp_nat_height);
		if (tmp_min_height > min_height)
			min_height = tmp_min_height;
		if (tmp_nat_height > nat_height)
			nat_height = tmp_nat_height;
	}

	if (priv->can_close) {
		clutter_actor_get_preferred_height (
			CLUTTER_ACTOR (priv->close_button),
			-1, &tmp_min_height, &tmp_nat_height);

		if (tmp_min_height > min_height)
			min_height = tmp_min_height;
		if (tmp_nat_height > nat_height)
			nat_height = tmp_nat_height;
	}

	if (min_height_p)
		*min_height_p += min_height;
	if (natural_height_p)
		*natural_height_p += nat_height;
}

static void
e_mail_tab_get_preferred_height (ClutterActor *actor,
                                 gfloat for_width,
                                 gfloat *min_height_p,
                                 gfloat *natural_height_p)
{
	EMailTab *tab = E_MAIL_TAB (actor);
	EMailTabPrivate *priv = tab->priv;

	e_mail_tab_get_height_no_preview (
		tab, for_width, min_height_p, natural_height_p);

	if (priv->preview) {
		MxPadding padding;
		gfloat min_height, nat_height;
		gfloat label_min_height, label_nat_height;

		/* Get preview + padding height */
		mx_widget_get_padding (MX_WIDGET (actor), &padding);

		clutter_actor_get_preferred_height (
			priv->preview,
			(gfloat) priv->width,
			&min_height, &nat_height);

		/* Add label height */
		clutter_actor_get_preferred_height (
			CLUTTER_ACTOR (priv->label), -1,
			&label_min_height, &label_nat_height);

		min_height =
			(min_height * priv->preview_height_progress) +
			padding.top + padding.bottom + priv->spacing +
			label_min_height;

		nat_height =
			(nat_height * priv->preview_height_progress) +
			padding.top + padding.bottom + priv->spacing +
			label_nat_height;

		/* Sometimes the preview's natural height will be nan due to
		 * keeping of the aspect ratio. This guards against that and
		 * stops Clutter from warning that the natural height is less
		 * than the minimum height. */
		if (isnan (nat_height))
			nat_height = min_height;

		if (min_height_p && (min_height > *min_height_p))
			*min_height_p = min_height;
		if (natural_height_p && (nat_height > *natural_height_p))
			*natural_height_p = nat_height;
	}
}

static void
e_mail_tab_allocate (ClutterActor *actor,
                     const ClutterActorBox *box,
                     ClutterAllocationFlags flags)
{
	MxPadding padding;
	ClutterActorBox child_box;
	gfloat icon_width, icon_height;
	gfloat label_width, label_height;
	gfloat close_width, close_height;
	gfloat preview_width, preview_height;

	EMailTabPrivate *priv = E_MAIL_TAB (actor)->priv;

	/* Chain up to store box */
	CLUTTER_ACTOR_CLASS (e_mail_tab_parent_class)->allocate (actor, box, flags);

	/* Possibly synchronise an axis if we're dragging */
	if (priv->in_drag) {
		ClutterActor *drag_actor =
			mx_draggable_get_drag_actor (MX_DRAGGABLE (actor));

		if (drag_actor) {
			gfloat x, y;
			clutter_actor_get_transformed_position (actor, &x, &y);

			switch (mx_draggable_get_axis (MX_DRAGGABLE (actor))) {
				case MX_DRAG_AXIS_X :
					/* Synchronise y axis */
					clutter_actor_set_y (drag_actor, y);
					break;
				case MX_DRAG_AXIS_Y :
					/* Synchronise x axis */
					clutter_actor_set_x (drag_actor, x);
					break;
				default:
					break;
			}
		}
	}

	/* Allocate old background texture */
	if (priv->old_bg) {
		child_box.x1 = 0;
		child_box.y1 = 0;
		child_box.x2 = box->x2 - box->x1;
		child_box.y2 = box->y2 - box->y1;
		clutter_actor_allocate (priv->old_bg, &child_box, flags);
	}

	mx_widget_get_padding (MX_WIDGET (actor), &padding);

	/* Get the preferred width/height of the icon,
	 * label and close-button first. */
	if (priv->icon)
		clutter_actor_get_preferred_size (
			priv->icon, NULL, NULL,
			&icon_width, &icon_height);

	clutter_actor_get_preferred_size (
		CLUTTER_ACTOR (priv->label), NULL, NULL,
		&label_width, &label_height);

	if (priv->can_close)
		clutter_actor_get_preferred_size (
			CLUTTER_ACTOR (priv->close_button),
			NULL, NULL, &close_width, &close_height);

	/* Allocate for icon */
	if (priv->icon) {
		child_box.x1 = padding.left;
		child_box.x2 = child_box.x1 + icon_width;
		child_box.y1 = E_MAIL_PIXBOUND ((box->y2 - box->y1) / 2 - icon_height / 2);
		child_box.y2 = child_box.y1 + icon_height;
		clutter_actor_allocate (priv->icon, &child_box, flags);
	}

	/* Allocate for close button */
	if (priv->can_close) {
		child_box.x2 = box->x2 - box->x1 - padding.right;
		child_box.x1 = child_box.x2 - close_width;
		child_box.y1 = E_MAIL_PIXBOUND ((box->y2 - box->y1) / 2 - close_height / 2);
		child_box.y2 = child_box.y1 + close_height;
		clutter_actor_allocate (
			CLUTTER_ACTOR (priv->close_button),
			&child_box, flags);
	}

	/* Allocate for preview widget */
	preview_height = 0;
	if (priv->preview) {
		preview_width =
			(box->x2 - box->x1 -
			 padding.left - padding.right);
		preview_height =
			(box->y2 - box->y1 -
			 padding.top - padding.bottom -
			 priv->spacing - label_height);

		child_box.x1 = E_MAIL_PIXBOUND (padding.left);
		child_box.y1 = E_MAIL_PIXBOUND (padding.top);
		child_box.x2 = child_box.x1 + preview_width;
		child_box.y2 = child_box.y1 + preview_height;

		clutter_actor_allocate (priv->preview, &child_box, flags);
	}

	/* Allocate for label */
	if ((priv->preview_height_progress <= TAB_S1_ANIM) || (!priv->preview)) {
		if (priv->icon)
			child_box.x1 = E_MAIL_PIXBOUND (padding.left + icon_width + priv->spacing);
		else
			child_box.x1 = E_MAIL_PIXBOUND (padding.left);
		child_box.x2 = (box->x2 - box->x1 - padding.right);
		child_box.y1 = E_MAIL_PIXBOUND ((box->y2 - box->y1) / 2 - label_height / 2);
		child_box.y2 = child_box.y1 + label_height;

		/* If close button is visible, don't overlap it */
		if (priv->can_close)
			child_box.x2 -= close_width + priv->spacing;
	} else {
		/* Put label underneath preview */
		child_box.x1 = E_MAIL_PIXBOUND (padding.left);
		child_box.x2 = (box->x2 - box->x1 - padding.right);
		child_box.y1 = E_MAIL_PIXBOUND (
			padding.top + preview_height + priv->spacing);
		child_box.y2 = child_box.y1 + label_height;
	}

	clutter_actor_allocate (CLUTTER_ACTOR (priv->label), &child_box, flags);

	/* If we're in preview mode, re-allocate the background so it doesn't
	 * encompass the label. (A bit hacky?)
	 */
	if (priv->preview && CLUTTER_ACTOR_IS_VISIBLE (priv->preview)) {
		gfloat max_height = padding.top + padding.bottom + preview_height;
		if (box->y2 - box->y1 > max_height) {
			MxWidget *widget = MX_WIDGET (actor);
			ClutterActor *background = mx_widget_get_border_image (widget);

			if (!background)
				background = mx_widget_get_background_image (widget);

			child_box.x1 = 0;
			child_box.x2 = box->x2 - box->x1;
			child_box.y1 = 0;
			child_box.y2 = max_height;

			if (background)
				clutter_actor_allocate (
					background, &child_box, flags);

			if (priv->old_bg && (priv->old_bg != background))
				clutter_actor_allocate (
					priv->old_bg, &child_box, flags);
		}
	}
}

static void
e_mail_tab_paint (ClutterActor *actor)
{
	EMailTabPrivate *priv = E_MAIL_TAB (actor)->priv;

	/* Chain up to paint background */
	CLUTTER_ACTOR_CLASS (e_mail_tab_parent_class)->paint (actor);

	if (priv->old_bg)
		clutter_actor_paint (priv->old_bg);

	if (priv->icon)
		clutter_actor_paint (priv->icon);

	clutter_actor_paint (CLUTTER_ACTOR (priv->label));

	if (priv->can_close)
		clutter_actor_paint (CLUTTER_ACTOR (priv->close_button));

	if (priv->preview)
		clutter_actor_paint (CLUTTER_ACTOR (priv->preview));
}

static void
e_mail_tab_pick (ClutterActor *actor,
                 const ClutterColor *c)
{
	CLUTTER_ACTOR_CLASS (e_mail_tab_parent_class)->pick (actor, c);

	if (clutter_actor_should_pick_paint (actor))
		e_mail_tab_paint (actor);
}

static void
e_mail_tab_map (ClutterActor *actor)
{
	EMailTabPrivate *priv = E_MAIL_TAB (actor)->priv;

	CLUTTER_ACTOR_CLASS (e_mail_tab_parent_class)->map (actor);

	clutter_actor_map (CLUTTER_ACTOR (priv->label));
	clutter_actor_map (CLUTTER_ACTOR (priv->close_button));

	if (priv->icon)
		clutter_actor_map (priv->icon);
	if (priv->preview)
		clutter_actor_map (priv->preview);
	if (priv->old_bg)
		clutter_actor_map (priv->old_bg);
}

static void
e_mail_tab_unmap (ClutterActor *actor)
{
	EMailTabPrivate *priv = E_MAIL_TAB (actor)->priv;

	CLUTTER_ACTOR_CLASS (e_mail_tab_parent_class)->unmap (actor);

	clutter_actor_unmap (CLUTTER_ACTOR (priv->label));
	clutter_actor_unmap (CLUTTER_ACTOR (priv->close_button));

	if (priv->icon)
		clutter_actor_unmap (priv->icon);
	if (priv->preview)
		clutter_actor_unmap (priv->preview);
	if (priv->old_bg)
		clutter_actor_unmap (priv->old_bg);
}

static gboolean
e_mail_tab_button_press_event (ClutterActor *actor,
                               ClutterButtonEvent *event)
{
	EMailTabPrivate *priv = E_MAIL_TAB (actor)->priv;

	if (event->button == 1) {
		mx_stylable_set_style_pseudo_class (
			MX_STYLABLE (actor), "active");
		clutter_grab_pointer (actor);
		priv->pressed = TRUE;

		priv->press_x = event->x;
		priv->press_y = event->y;
	}

	/* We have to always return false, or dragging won't work */
	return FALSE;
}

static gboolean
e_mail_tab_button_release_event (ClutterActor *actor,
                                 ClutterButtonEvent *event)
{
	EMailTab *tab = E_MAIL_TAB (actor);
	EMailTabPrivate *priv = tab->priv;

	if (priv->pressed) {
		clutter_ungrab_pointer ();
		priv->pressed = FALSE;

		/* Note, no need to set the pseudo class here as clicking
		 * always results in being set active. */
		if (priv->hover) {
			if (!priv->active)
				e_mail_tab_set_active (tab, TRUE);

			g_signal_emit (actor, signals[CLICKED], 0);
		}
	}

	return FALSE;
}

static gboolean
e_mail_tab_motion_event (ClutterActor *actor,
                         ClutterMotionEvent *event)
{
	EMailTab *tab = E_MAIL_TAB (actor);
	EMailTabPrivate *priv = tab->priv;

	if (priv->pressed && priv->drag_enabled) {
		if ((ABS (event->x - priv->press_x) >= priv->drag_threshold) ||
		    (ABS (event->y - priv->press_y) >= priv->drag_threshold)) {
			/* Ungrab the pointer so that the MxDraggable code can take over */
			clutter_ungrab_pointer ();
			priv->pressed = FALSE;
			if (!priv->active) {
				if (priv->hover)
					mx_stylable_set_style_pseudo_class (
						MX_STYLABLE (actor), "hover");
				else
					mx_stylable_set_style_pseudo_class (
						MX_STYLABLE (actor), NULL);
			}
		}
	}

	return FALSE;
}

static gboolean
e_mail_tab_enter_event (ClutterActor *actor,
                        ClutterCrossingEvent *event)
{
	EMailTabPrivate *priv = E_MAIL_TAB (actor)->priv;

	if (event->source != actor)
		return FALSE;

	priv->hover = TRUE;

	if (priv->pressed)
		mx_stylable_set_style_pseudo_class (
			MX_STYLABLE (actor), "active");
	else if (!priv->active)
		mx_stylable_set_style_pseudo_class (
			MX_STYLABLE (actor), "hover");

	return FALSE;
}

static gboolean
e_mail_tab_leave_event (ClutterActor *actor,
                        ClutterCrossingEvent *event)
{
	EMailTabPrivate *priv = E_MAIL_TAB (actor)->priv;

	if ((event->source != actor) ||
		(event->related == (ClutterActor *) priv->close_button))
		return FALSE;

	priv->hover = FALSE;

	if (!priv->active)
		mx_stylable_set_style_pseudo_class (MX_STYLABLE (actor), NULL);

	return FALSE;
}

static void
e_mail_tab_class_init (EMailTabClass *class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (class);
	ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (class);

	g_type_class_add_private (class, sizeof (EMailTabPrivate));

	object_class->get_property = e_mail_tab_get_property;
	object_class->set_property = e_mail_tab_set_property;
	object_class->dispose = e_mail_tab_dispose;

	actor_class->get_preferred_width = e_mail_tab_get_preferred_width;
	actor_class->get_preferred_height = e_mail_tab_get_preferred_height;
	actor_class->button_press_event = e_mail_tab_button_press_event;
	actor_class->button_release_event = e_mail_tab_button_release_event;
	actor_class->motion_event = e_mail_tab_motion_event;
	actor_class->enter_event = e_mail_tab_enter_event;
	actor_class->leave_event = e_mail_tab_leave_event;
	actor_class->allocate = e_mail_tab_allocate;
	actor_class->paint = e_mail_tab_paint;
	actor_class->pick = e_mail_tab_pick;
	actor_class->map = e_mail_tab_map;
	actor_class->unmap = e_mail_tab_unmap;

	g_object_class_install_property (
		object_class,
		PROP_ICON,
		g_param_spec_object (
			"icon",
			"Icon",
			"Icon actor.",
			CLUTTER_TYPE_ACTOR,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_TEXT,
		g_param_spec_string (
			"text",
			"Text",
			"Tab text.",
			"",
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_CAN_CLOSE,
		g_param_spec_boolean (
			"can-close",
			"Can close",
			"Whether the tab can "
			"close.",
			TRUE,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_TAB_WIDTH,
		g_param_spec_int (
			"tab-width",
			"Tab width",
			"Tab width.",
			-1, G_MAXINT, -1,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_DOCKING,
		g_param_spec_boolean (
			"docking",
			"Docking",
			"Whether the tab should dock to edges when scrolled.",
			FALSE,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_PREVIEW,
		g_param_spec_object (
			"preview",
			"Preview actor",
			"ClutterActor used "
			"when in preview mode.",
			CLUTTER_TYPE_ACTOR,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_PREVIEW_MODE,
		g_param_spec_boolean (
			"preview-mode",
			"Preview mode",
			"Whether to display "
			"in preview mode.",
			FALSE,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_PREVIEW_DURATION,
		g_param_spec_uint (
			"preview-duration",
			"Preview duration",
			"How long the transition "
			"between preview mode "
			"states lasts, in ms.",
			0, G_MAXUINT, 200,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_SPACING,
		g_param_spec_float (
			"spacing",
			"Spacing",
			"Spacing between "
			"tab elements.",
			0, G_MAXFLOAT,
			6.0,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_PRIVATE,
		g_param_spec_boolean (
			"private",
			"Private",
			"Set if the tab is "
			"'private'.",
			FALSE,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (
		object_class,
		PROP_ACTIVE,
		g_param_spec_boolean (
			"active",
			"Active",
			"Set if the tab is "
			"active.",
			FALSE,
			G_PARAM_READWRITE |
			G_PARAM_STATIC_STRINGS));

	g_object_class_override_property (
		object_class,
		PROP_DRAG_THRESHOLD,
		"drag-threshold");

	g_object_class_override_property (
		object_class,
		PROP_DRAG_AXIS,
		"axis");

	g_object_class_override_property (
		object_class,
		PROP_DRAG_CONTAINMENT_AREA,
		"containment-area");

	g_object_class_override_property (
		object_class,
		PROP_DRAG_ENABLED,
		"drag-enabled");

	g_object_class_override_property (
		object_class,
		PROP_DRAG_ACTOR,
		"drag-actor");

	signals[CLICKED] = g_signal_new (
		"clicked",
		G_TYPE_FROM_CLASS (class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (EMailTabClass, clicked),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

	signals[CLOSED] = g_signal_new (
		"closed",
		G_TYPE_FROM_CLASS (class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (EMailTabClass, closed),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

	signals[TRANSITION_COMPLETE] = g_signal_new (
		"transition-complete",
		G_TYPE_FROM_CLASS (class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (EMailTabClass, transition_complete),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);
}

static void
e_mail_tab_close_clicked_cb (MxButton *button,
                             EMailTab *self)
{
	g_signal_emit (self, signals[CLOSED], 0);
}

static void
e_mail_tab_anim_completed_cb (ClutterAnimation *animation,
                              EMailTab *tab)
{
	e_mail_tab_dispose_old_bg (tab);
}

static void
e_mail_tab_style_changed_cb (MxWidget *widget)
{
	EMailTabPrivate *priv = E_MAIL_TAB (widget)->priv;

	/* Don't transition on hover */
	if (g_strcmp0 (mx_stylable_get_style_pseudo_class (
		MX_STYLABLE (widget)), "hover") == 0)
		return;

	if (priv->old_bg) {
		if (!clutter_actor_get_parent (priv->old_bg)) {
			ClutterActorBox box;
			ClutterActor *background;
			ClutterActor *actor = CLUTTER_ACTOR (widget);

			clutter_actor_set_parent (priv->old_bg, actor);

			/* Try to allocate the same size as the background
			 * widget, otherwise allocate the same size as the
			 * widget itself. */
			background = mx_widget_get_border_image (widget);
			if (!background)
				background = mx_widget_get_background_image (widget);

			if (background)
				clutter_actor_get_allocation_box (background, &box);
			else {
				clutter_actor_get_allocation_box (actor, &box);
				box.x2 -= box.x1;
				box.y2 -= box.y1;
				box.x1 = 0;
				box.y1 = 0;
			}

			clutter_actor_allocate (priv->old_bg, &box, 0);
		}

		clutter_actor_animate (
			priv->old_bg, CLUTTER_LINEAR, 150,
			"opacity", 0, "signal::completed",
			G_CALLBACK (e_mail_tab_anim_completed_cb),
			widget, NULL);
	}
}

static void
e_mail_tab_stylable_changed_cb (MxStylable *stylable)
{
	EMailTab *tab = E_MAIL_TAB (stylable);
	EMailTabPrivate *priv = tab->priv;

	e_mail_tab_dispose_old_bg (tab);

	priv->old_bg = mx_widget_get_border_image (MX_WIDGET (tab));
	if (priv->old_bg)
		g_object_ref (priv->old_bg);
}

static void
e_mail_tab_dnd_notify_cb (GObject *settings,
                          GParamSpec *pspec,
                          EMailTab *tab)
{
	g_object_get (
		settings,
		"gtk-dnd-drag-threshold", &tab->priv->drag_threshold,
		NULL);
}

static void
e_mail_tab_init (EMailTab *self)
{
	ClutterActor *text;
	GtkSettings *settings;
	EMailTabPrivate *priv;

	priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (
	self, E_MAIL_TYPE_TAB, EMailTabPrivate);

	priv->width = -1;
	priv->anim_length = 200;
	priv->spacing = 6.0;
	priv->can_close = TRUE;

	priv->label = mx_label_new ();
	g_object_set (priv->label, "clip-to-allocation", TRUE, NULL);
	text = mx_label_get_clutter_text (MX_LABEL (priv->label));
	clutter_text_set_ellipsize (CLUTTER_TEXT (text), PANGO_ELLIPSIZE_END);
	clutter_actor_set_parent (
		CLUTTER_ACTOR (priv->label), CLUTTER_ACTOR (self));

	priv->close_button = mx_button_new ();
	clutter_actor_set_name (
		CLUTTER_ACTOR (priv->close_button), "tab-close-button");
	clutter_actor_set_parent (
		CLUTTER_ACTOR (priv->close_button), CLUTTER_ACTOR (self));

	g_signal_connect (
		priv->close_button, "clicked",
		G_CALLBACK (e_mail_tab_close_clicked_cb), self);

	/* Connect up styling signals */
	g_signal_connect (
		self, "style-changed",
		G_CALLBACK (e_mail_tab_style_changed_cb), NULL);
	g_signal_connect (
		self, "stylable-changed",
		G_CALLBACK (e_mail_tab_stylable_changed_cb), NULL);

	clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE);

	settings = gtk_settings_get_default ();
	priv->drag_threshold_handler = g_signal_connect (
		settings, "notify::gtk-dnd-drag-threshold",
		G_CALLBACK (e_mail_tab_dnd_notify_cb), self);
	g_object_get (
		G_OBJECT (settings),
		"gtk-dnd-drag-threshold", &priv->drag_threshold,
		NULL);
}

ClutterActor *
e_mail_tab_new (void)
{
	return g_object_new (E_MAIL_TYPE_TAB, NULL);
}

ClutterActor *
e_mail_tab_new_full (const gchar *text,
                     ClutterActor *icon,
                     gint width)
{
	return g_object_new (
		E_MAIL_TYPE_TAB,
		"text", text,
		"icon", icon,
		"tab-width", width,
		NULL);
}

void
e_mail_tab_set_text (EMailTab *tab,
                     const gchar *text)
{
	EMailTabPrivate *priv = tab->priv;

	if (!text)
		text = "";

	priv->has_text = (text[0] != '\0');

	if (priv->label)
		mx_label_set_text (MX_LABEL (priv->label), text);

	g_object_notify (G_OBJECT (tab), "text");
}

void
e_mail_tab_set_default_icon (EMailTab *tab,
                             ClutterActor *icon)
{
	EMailTabPrivate *priv = tab->priv;
	gboolean changed = !priv->icon || (priv->icon == priv->default_icon);

	if (icon)
		g_object_ref_sink (icon);

	if (priv->default_icon)
		g_object_unref (priv->default_icon);

	priv->default_icon = icon;

	if (changed)
		e_mail_tab_set_icon (tab, NULL);
}

void
e_mail_tab_set_icon (EMailTab *tab,
                     ClutterActor *icon)
{
	EMailTabPrivate *priv = tab->priv;

	/* passing NULL for icon will use default icon if available */

	if (priv->icon)
		clutter_actor_unparent (priv->icon);

	if (icon)
		priv->icon = icon;
	else
		priv->icon = priv->default_icon;

	if (priv->icon)
		clutter_actor_set_parent (priv->icon, CLUTTER_ACTOR (tab));

	clutter_actor_queue_relayout (CLUTTER_ACTOR (tab));

	g_object_notify (G_OBJECT (tab), "icon");
}

const gchar *
e_mail_tab_get_text (EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->label)
		return mx_label_get_text (MX_LABEL (priv->label));
	else
		return NULL;
}

ClutterActor *
e_mail_tab_get_icon (EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;
	return priv->icon == priv->default_icon ? NULL : priv->icon;
}

void
e_mail_tab_set_can_close (EMailTab *tab,
                          gboolean can_close)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->can_close == can_close)
		return;

	priv->can_close = can_close;

	clutter_actor_queue_relayout (CLUTTER_ACTOR (tab));

	g_object_notify (G_OBJECT (tab), "can-close");
}

gboolean
e_mail_tab_get_can_close (EMailTab *tab)
{
	return tab->priv->can_close;
}

void
e_mail_tab_set_width (EMailTab *tab,
                      gint width)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->width == width)
		return;

	priv->width = width;

	clutter_actor_queue_relayout (CLUTTER_ACTOR (tab));

	g_object_notify (G_OBJECT (tab), "tab-width");
}

gint
e_mail_tab_get_width (EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;
	return priv->width;
}

void
e_mail_tab_set_docking (EMailTab *tab,
                        gboolean docking)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->docking == docking)
		return;

	priv->docking = docking;

	clutter_actor_queue_relayout (CLUTTER_ACTOR (tab));

	g_object_notify (G_OBJECT (tab), "docking");
}

gboolean
e_mail_tab_get_docking (EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;
	return priv->docking;
}

void
e_mail_tab_set_preview_actor (EMailTab *tab,
                              ClutterActor *actor)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->preview)
		clutter_actor_unparent (priv->preview);

	priv->preview = actor;

	if (actor) {
		clutter_actor_set_parent (actor, CLUTTER_ACTOR (tab));

		clutter_actor_set_opacity (
			actor, priv->preview_mode ? 0xff : 0x00);
		if (!priv->preview_mode)
			clutter_actor_hide (actor);
	}

	clutter_actor_queue_relayout (CLUTTER_ACTOR (tab));

	g_object_notify (G_OBJECT (tab), "preview");
}

ClutterActor *
e_mail_tab_get_preview_actor (EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;
	return priv->preview;
}

static void
preview_new_frame_cb (ClutterTimeline *timeline,
                      guint msecs,
                      EMailTab *tab)
{
	gboolean forwards;
	EMailTabPrivate *priv = tab->priv;

	forwards =
		(clutter_timeline_get_direction (timeline) ==
		 CLUTTER_TIMELINE_FORWARD);
	if (priv->preview_mode)
		forwards = !forwards;

	priv->preview_height_progress =
		clutter_timeline_get_progress (timeline);
	if (forwards)
		priv->preview_height_progress =
			1.0 - priv->preview_height_progress;

	if (priv->preview)
		clutter_actor_queue_relayout (CLUTTER_ACTOR (tab));
}

static void
preview_completed_cb (ClutterTimeline *timeline,
                      EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->preview_timeline) {
		clutter_timeline_stop (priv->preview_timeline);
		g_object_unref (priv->preview_timeline);
		priv->preview_timeline = NULL;

		if (priv->preview_mode)
			priv->preview_height_progress = 1.0;
		else {
			priv->preview_height_progress = 0.0;
			if (priv->preview)
				clutter_actor_hide (priv->preview);
			if (priv->can_close)
				clutter_actor_set_reactive (
					CLUTTER_ACTOR (priv->close_button),
					TRUE);
		}

		/* Remove style hint if we're not in preview mode */
		if (priv->preview) {
			if (!priv->preview_mode)
				clutter_actor_set_name (
					CLUTTER_ACTOR (tab),
					priv->private ? "private-tab" : NULL);
		} else {
			/* If there's no preview actor, disable the tab */
			clutter_actor_set_reactive (
				CLUTTER_ACTOR (tab), !priv->preview_mode);
		}

		if (priv->preview)
			clutter_actor_queue_relayout (CLUTTER_ACTOR (tab));

		g_signal_emit (tab, signals[TRANSITION_COMPLETE], 0);
	}
}

static void
preview_s1_started_cb (ClutterTimeline *timeline,
                       EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;

	if (!priv->preview)
		clutter_actor_animate_with_timeline (
			CLUTTER_ACTOR (priv->label),
			CLUTTER_EASE_IN_OUT_QUAD,
			timeline,
			"opacity", 0xff,
			NULL);
}

static void
preview_s2_started_cb (ClutterTimeline *timeline,
                       EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->preview)
		clutter_actor_animate_with_timeline (
			CLUTTER_ACTOR (priv->label),
			CLUTTER_EASE_IN_OUT_QUAD,
			timeline,
			"opacity", 0xff,
			NULL);
}

void
e_mail_tab_set_preview_mode (EMailTab *tab,
                             gboolean preview)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->preview_mode != preview) {
		ClutterTimeline *timeline, *timeline2;
		gdouble progress, total_duration, duration1, duration2;

		priv->preview_mode = preview;

		/* Disable the close button in preview mode */
		if (preview && priv->can_close)
			clutter_actor_set_reactive (CLUTTER_ACTOR (priv->close_button), FALSE);

#define DEBUG_MULT 1
		if (priv->preview_timeline) {
			progress = 1.0 - clutter_timeline_get_progress (
				priv->preview_timeline);
			clutter_timeline_stop (priv->preview_timeline);
			g_object_unref (priv->preview_timeline);
		} else
			progress = 0.0;

		total_duration = priv->anim_length * (1.0 - progress) * DEBUG_MULT;
		duration1 = total_duration * TAB_S1_ANIM;
		duration2 = total_duration * TAB_S2_ANIM;

		priv->preview_timeline =
			clutter_timeline_new (priv->anim_length * DEBUG_MULT);
		clutter_timeline_skip (
			priv->preview_timeline, clutter_timeline_get_duration (
			priv->preview_timeline) * progress);

		g_signal_connect (
			priv->preview_timeline, "completed",
			G_CALLBACK (preview_completed_cb), tab);

		clutter_timeline_start (priv->preview_timeline);

		if (!priv->preview) {
			clutter_actor_animate_with_timeline (
				CLUTTER_ACTOR (tab),
				CLUTTER_EASE_IN_OUT_QUAD,
				priv->preview_timeline,
				"opacity", preview ? 0x00 : 0xff,
				NULL);
			return;
		}

		g_signal_connect (
			priv->preview_timeline, "new-frame",
			G_CALLBACK (preview_new_frame_cb), tab);

		timeline = clutter_timeline_new ((guint) duration1);
		timeline2 = clutter_timeline_new ((guint) duration2);

		g_signal_connect (
			timeline, "started",
			G_CALLBACK (preview_s1_started_cb), tab);
		g_signal_connect (
			timeline2, "started",
			G_CALLBACK (preview_s2_started_cb), tab);

		if (preview)
			clutter_timeline_set_delay (timeline2, duration1);
		else
			clutter_timeline_set_delay (timeline, duration2);

		/* clutter_actor_animate_with_timeline will start the timelines */
		clutter_actor_animate_with_timeline (
			CLUTTER_ACTOR (priv->label),
			CLUTTER_EASE_IN_OUT_QUAD,
			preview ? timeline : timeline2,
			"opacity", 0x00, NULL);

		if (priv->icon)
			clutter_actor_animate_with_timeline (
				priv->icon,
				CLUTTER_EASE_IN_OUT_QUAD,
				timeline,
				"opacity", preview ? 0x00 : 0xff,
				NULL);

		if (priv->can_close)
			clutter_actor_animate_with_timeline (
				CLUTTER_ACTOR (priv->close_button),
				CLUTTER_EASE_IN_OUT_QUAD,
				timeline,
				"opacity", preview ? 0x00 : 0xff,
				NULL);

		if (priv->preview)
			clutter_actor_show (priv->preview);
			clutter_actor_animate_with_timeline (
				priv->preview,
				CLUTTER_EASE_IN_OUT_QUAD,
				timeline2,
				"opacity", preview ? 0xff : 0x00,
				NULL);

		/* The animations have references on these, drop ours */
		g_object_unref (timeline);
		g_object_unref (timeline2);

		/* Add an actor name, for style */
		clutter_actor_set_name (
			CLUTTER_ACTOR (tab),
			priv->private ? "private-preview-tab" :
			"preview-tab");
	}
}

gboolean
e_mail_tab_get_preview_mode (EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;
	return priv->preview_mode;
}

void
e_mail_tab_set_preview_duration (EMailTab *tab,
                                 guint duration)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->anim_length != duration) {
		priv->anim_length = duration;
		g_object_notify (G_OBJECT (tab), "preview-duration");
	}
}

guint
e_mail_tab_get_preview_duration (EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;
	return priv->anim_length;
}

void
e_mail_tab_set_spacing (EMailTab *tab,
                        gfloat spacing)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->spacing != spacing) {
		priv->spacing = spacing;
		g_object_notify (G_OBJECT (tab), "spacing");
		clutter_actor_queue_relayout (CLUTTER_ACTOR (tab));
	}
}

gfloat
e_mail_tab_get_spacing (EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;
	return priv->spacing;
}

void
e_mail_tab_set_private (EMailTab *tab,
                        gboolean private)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->private == private)
		return;

	priv->private = private;

	if (!priv->preview_mode)
		clutter_actor_set_name (
			CLUTTER_ACTOR (tab), private ? "private-tab" : NULL);

	g_object_notify (G_OBJECT (tab), "private");
}

gboolean
e_mail_tab_get_private (EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;
	return priv->private;
}

void
e_mail_tab_set_active (EMailTab *tab,
                       gboolean active)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->active == active)
		return;

	priv->active = active;

	g_object_notify (G_OBJECT (tab), "active");

	if (active)
		mx_stylable_set_style_pseudo_class (MX_STYLABLE (tab), "active");
	else if (!priv->pressed) {
		if (priv->hover)
			mx_stylable_set_style_pseudo_class (MX_STYLABLE (tab), "hover");
		else
			mx_stylable_set_style_pseudo_class (MX_STYLABLE (tab), NULL);
	}
}

gboolean
e_mail_tab_get_active (EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;
	return priv->active;
}

static gboolean
e_mail_tab_alert_cb (EMailTab *tab)
{
	const gchar *name;
	EMailTabPrivate *priv = tab->priv;

	/* FIXME: Work in preview mode */

	/* Alternate between private mode and non-private to alert */
	name = (priv->private ^ (priv->alert_count % 2)) ? NULL : "private-tab";
	if (!priv->preview_mode)
		clutter_actor_set_name (CLUTTER_ACTOR (tab), name);
	priv->alert_count++;

	if (priv->alert_count < 4)
		return TRUE;

	/* This call isn't needed, it should be in the correct state as long as
	 * the above check always checks for < (an even number)
	 */
	/*if (!priv->preview_mode)
		clutter_actor_set_name (CLUTTER_ACTOR (tab),
														priv->private ? "private-tab" : NULL);*/
	priv->alert_source = 0;

	return FALSE;
}

void
e_mail_tab_alert (EMailTab *tab)
{
	EMailTabPrivate *priv = tab->priv;

	priv->alert_count = 0;
	if (!priv->alert_source)
		priv->alert_source =
			g_timeout_add_full (G_PRIORITY_HIGH,
				500,
				(GSourceFunc) e_mail_tab_alert_cb,
				tab,
				NULL);
}

void
e_mail_tab_enable_drag (EMailTab *tab,
                        gboolean enable)
{
	EMailTabPrivate *priv = tab->priv;

	if (priv->drag_enabled == enable)
		return;

	priv->drag_enabled = enable;
	if (enable)
		mx_draggable_enable (MX_DRAGGABLE (tab));
	else
		mx_draggable_disable (MX_DRAGGABLE (tab));
}