[evolution/tabs-rewrite-v1: 20/31] Add Tab/Tab picker under clutter.



commit edce7026ab616f6a5758d54b8f60471928dfb22d
Author: Srinivasa Ragavan <sragavan gnome org>
Date:   Thu Jul 15 22:42:57 2010 +0530

    Add Tab/Tab picker under clutter.

 mail/e-mail-tab-picker.c | 1332 ++++++++++++++++++++++++++++++++
 mail/e-mail-tab-picker.h |  112 +++
 mail/e-mail-tab.c        | 1901 ++++++++++++++++++++++++++++++++++++++++++++++
 mail/e-mail-tab.h        |  108 +++
 4 files changed, 3453 insertions(+), 0 deletions(-)
---
diff --git a/mail/e-mail-tab-picker.c b/mail/e-mail-tab-picker.c
new file mode 100644
index 0000000..6e8f12f
--- /dev/null
+++ b/mail/e-mail-tab-picker.c
@@ -0,0 +1,1332 @@
+/*
+ * 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 "e-mail-tab-picker.h"
+
+static void mx_droppable_iface_init (MxDroppableIface *iface);
+static gint e_mail_tab_picker_find_tab_cb (gconstpointer a, gconstpointer b);
+
+G_DEFINE_TYPE_WITH_CODE (EMailTabPicker,
+                         e_mail_tab_picker,
+                         MX_TYPE_WIDGET,
+                         G_IMPLEMENT_INTERFACE (MX_TYPE_DROPPABLE,
+                                                mx_droppable_iface_init))
+
+#define TAB_PICKER_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), E_MAIL_TYPE_TAB_PICKER, EMailTabPickerPrivate))
+
+enum
+{
+  PROP_0,
+
+  PROP_PREVIEW_MODE,
+  PROP_DROP_ENABLED,
+};
+
+enum
+{
+  TAB_ACTIVATED,
+  CHOOSER_CLICKED,
+
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+typedef struct
+{
+  EMailTab      *tab;
+  gfloat       position;
+  gfloat       width;
+  gboolean     docking;
+  gboolean     docked;
+} EMailTabPickerProps;
+
+struct _EMailTabPickerPrivate
+{
+  GList        *tabs;
+  gint          n_tabs;
+  ClutterActor *chooser_button;
+  ClutterActor *close_button;
+  gint          current_tab;
+  gboolean      preview_mode;
+  gboolean      drop_enabled;
+  gboolean      in_drag;
+  gboolean      drag_preview;
+
+  gint          width;
+  gint          total_width;
+  gint          max_offset;
+  gboolean      docked_tabs;
+
+  ClutterTimeline *scroll_timeline;
+  ClutterAlpha    *scroll_alpha;
+  gint             scroll_start;
+  gint             scroll_end;
+  gint             scroll_offset;
+  gboolean         keep_current_visible;
+  MxAdjustment    *scroll_adjustment;
+  ClutterActor    *scroll_bar;
+
+  ClutterTimeline *preview_timeline;
+  gfloat           preview_progress;
+};
+
+static void
+e_mail_tab_picker_over_in (MxDroppable *droppable,
+                        MxDraggable *draggable)
+{
+}
+
+static void
+e_mail_tab_picker_over_out (MxDroppable *droppable,
+                         MxDraggable *draggable)
+{
+}
+
+static void
+e_mail_tab_picker_drop (MxDroppable         *droppable,
+                     MxDraggable         *draggable,
+                     gfloat               event_x,
+                     gfloat               event_y,
+                     gint                 button,
+                     ClutterModifierType  modifiers)
+{
+  GList *t;
+  EMailTabPickerProps *tab;
+  gint current_position, new_position;
+
+  EMailTabPicker *picker = E_MAIL_TAB_PICKER (droppable);
+  EMailTabPickerPrivate *priv = picker->priv;
+
+  /* Make sure this is a valid drop */
+  if (!priv->drop_enabled)
+    return;
+
+  if (!E_MAIL_IS_TAB (draggable))
+    return;
+
+  if (clutter_actor_get_parent (CLUTTER_ACTOR (draggable)) !=
+      (ClutterActor *)picker)
+    return;
+
+  /* Get current position and property data structure */
+  t = g_list_find_custom (priv->tabs, draggable, e_mail_tab_picker_find_tab_cb);
+  tab = (EMailTabPickerProps *)t->data;
+  if (!tab)
+    {
+      g_warning ("Tab that's parented to a picker not actually in picker");
+      return;
+    }
+  current_position = g_list_position (priv->tabs, t);
+
+  /* Work out new position */
+  for (new_position = 0, t = priv->tabs; t; t = t->next)
+    {
+      EMailTabPickerProps *props = t->data;
+
+      /* Ignore docked tabs */
+      if (!props->docked)
+        {
+          /* If the tab is beyond the dragged tab and not draggable,
+           * we don't want to drag past it.
+           */
+          if ((event_x >= props->position + priv->scroll_offset) &&
+              (tab->position + tab->width <= props->position) &&
+              !mx_draggable_is_enabled (MX_DRAGGABLE (props->tab)))
+            {
+              new_position --;
+              break;
+            }
+
+          /* The same check for dragging left instead of right */
+          if ((event_x < props->position + props->width + priv->scroll_offset)&&
+              (tab->position >= props->position) &&
+              !mx_draggable_is_enabled (MX_DRAGGABLE (props->tab)))
+            break;
+
+          /* If the tab-end position is after the drop position,
+           * break - we want to drop before here.
+           */
+          if (props->position + props->width + priv->scroll_offset > event_x)
+            break;
+        }
+
+      /* Increment the position */
+      new_position++;
+    }
+
+  /* Re-order */
+  e_mail_tab_picker_reorder (picker, current_position, new_position);
+}
+
+static void
+mx_droppable_iface_init (MxDroppableIface *iface)
+{
+  iface->over_in = e_mail_tab_picker_over_in;
+  iface->over_out = e_mail_tab_picker_over_out;
+  iface->drop = e_mail_tab_picker_drop;
+}
+
+static void
+e_mail_tab_picker_get_property (GObject *object, guint property_id,
+                             GValue *value, GParamSpec *pspec)
+{
+  EMailTabPicker *tab_picker = E_MAIL_TAB_PICKER (object);
+  EMailTabPickerPrivate *priv = tab_picker->priv;
+
+  switch (property_id)
+    {
+    case PROP_PREVIEW_MODE:
+      g_value_set_boolean (value, e_mail_tab_picker_get_preview_mode (tab_picker));
+      break;
+
+    case PROP_DROP_ENABLED:
+      g_value_set_boolean (value, priv->drop_enabled);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+e_mail_tab_picker_set_property (GObject *object, guint property_id,
+                              const GValue *value, GParamSpec *pspec)
+{
+  EMailTabPicker *tab_picker = E_MAIL_TAB_PICKER (object);
+  EMailTabPickerPrivate *priv = tab_picker->priv;
+
+  switch (property_id)
+    {
+    case PROP_PREVIEW_MODE:
+      e_mail_tab_picker_set_preview_mode (tab_picker, g_value_get_boolean (value));
+      break;
+
+    case PROP_DROP_ENABLED:
+      priv->drop_enabled = g_value_get_boolean (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+e_mail_tab_picker_dispose (GObject *object)
+{
+  EMailTabPicker *picker = E_MAIL_TAB_PICKER (object);
+  EMailTabPickerPrivate *priv = picker->priv;
+
+  if (priv->scroll_bar)
+    {
+      clutter_actor_unparent (CLUTTER_ACTOR (priv->scroll_bar));
+      priv->scroll_bar = NULL;
+    }
+
+  if (priv->scroll_timeline)
+    {
+      clutter_timeline_stop (priv->scroll_timeline);
+      g_object_unref (priv->scroll_alpha);
+      g_object_unref (priv->scroll_timeline);
+      priv->scroll_timeline = NULL;
+      priv->scroll_alpha = NULL;
+    }
+
+  if (priv->preview_timeline)
+    {
+      clutter_timeline_stop (priv->preview_timeline);
+      g_object_unref (priv->preview_timeline);
+      priv->preview_timeline = NULL;
+    }
+
+  if (priv->chooser_button)
+    {
+      clutter_actor_unparent (CLUTTER_ACTOR (priv->chooser_button));
+      priv->chooser_button = NULL;
+    }
+
+  if (priv->close_button)
+    {
+      clutter_actor_unparent (CLUTTER_ACTOR (priv->close_button));
+      priv->close_button = NULL;
+    }
+
+  while (priv->tabs)
+    {
+      EMailTabPickerProps *props = priv->tabs->data;
+      e_mail_tab_picker_remove_tab (picker, props->tab);
+    }
+
+  G_OBJECT_CLASS (e_mail_tab_picker_parent_class)->dispose (object);
+}
+
+static void
+e_mail_tab_picker_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (e_mail_tab_picker_parent_class)->finalize (object);
+}
+
+static void
+e_mail_tab_picker_paint (ClutterActor *actor)
+{
+  GList *t;
+  gfloat width, height, offset;
+
+  EMailTabPickerPrivate *priv = E_MAIL_TAB_PICKER (actor)->priv;
+
+  CLUTTER_ACTOR_CLASS (e_mail_tab_picker_parent_class)->paint (actor);
+
+  clutter_actor_get_size (actor, &width, &height);
+
+  cogl_clip_push_rectangle (0, 0, width, height);
+
+  offset = priv->scroll_offset;
+  cogl_translate (-priv->scroll_offset, 0, 0);
+
+  /* Draw normal tabs */
+  for (t = priv->tabs; t; t = t->next)
+    {
+      EMailTabPickerProps *props = t->data;
+
+      if (props->docked)
+        continue;
+      if (props->position + props->width < offset)
+        continue;
+      if (props->position > width + offset)
+        break;
+
+      if (CLUTTER_ACTOR_IS_MAPPED (props->tab))
+        clutter_actor_paint (CLUTTER_ACTOR (props->tab));
+    }
+
+  cogl_translate (priv->scroll_offset, 0, 0);
+
+  /* Draw docked tabs */
+  if (priv->docked_tabs)
+    {
+      for (t = priv->tabs; t; t = t->next)
+        {
+          EMailTabPickerProps *props = t->data;
+
+          if (!props->docked)
+            continue;
+
+          if (CLUTTER_ACTOR_IS_MAPPED (props->tab))
+            clutter_actor_paint (CLUTTER_ACTOR (props->tab));
+        }
+    }
+
+  cogl_clip_pop ();
+
+  /* Draw tab chooser button */
+  if (CLUTTER_ACTOR_IS_MAPPED (priv->chooser_button))
+    clutter_actor_paint (CLUTTER_ACTOR (priv->chooser_button));
+
+  /* Draw scrollbar */
+  if (CLUTTER_ACTOR_IS_MAPPED (priv->scroll_bar))
+    {
+      gfloat height;
+      clutter_actor_get_preferred_height (CLUTTER_ACTOR (priv->close_button),
+                                          -1, NULL, &height);
+      height *= priv->preview_progress;
+      if (height >= 1.0)
+        {
+          cogl_clip_push_rectangle (0, 0, width, height);
+          if (CLUTTER_ACTOR_IS_MAPPED (priv->close_button))
+            clutter_actor_paint (CLUTTER_ACTOR (priv->close_button));
+          clutter_actor_paint (CLUTTER_ACTOR (priv->scroll_bar));
+          cogl_clip_pop ();
+        }
+    }
+}
+
+static void
+e_mail_tab_picker_pick (ClutterActor       *actor,
+                     const ClutterColor *color)
+{
+  EMailTabPickerPrivate *priv = E_MAIL_TAB_PICKER (actor)->priv;
+
+  /* Chain up to paint background */
+  CLUTTER_ACTOR_CLASS (e_mail_tab_picker_parent_class)->pick (actor, color);
+
+  if (!priv->in_drag)
+    e_mail_tab_picker_paint (actor);
+}
+
+static void
+e_mail_tab_picker_get_preferred_width (ClutterActor *actor,
+                                    gfloat        for_height,
+                                    gfloat       *min_width_p,
+                                    gfloat       *natural_width_p)
+{
+  GList *t;
+  MxPadding padding;
+
+  EMailTabPickerPrivate *priv = E_MAIL_TAB_PICKER (actor)->priv;
+
+  clutter_actor_get_preferred_width (CLUTTER_ACTOR (priv->chooser_button),
+                                     for_height, min_width_p, natural_width_p);
+
+  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;
+
+  for (t = priv->tabs; t; t = t->next)
+    {
+      gfloat min_width, natural_width;
+
+      EMailTabPickerProps *props = t->data;
+
+      clutter_actor_get_preferred_width (CLUTTER_ACTOR (props->tab), for_height,
+                                         &min_width, &natural_width);
+
+      if (min_width_p && !t->prev)
+        *min_width_p += min_width;
+      if (natural_width_p)
+        *natural_width_p += natural_width;
+    }
+}
+
+void
+e_mail_tab_picker_get_preferred_height (EMailTabPicker *tab_picker,
+                                     gfloat        for_width,
+                                     gfloat       *min_height_p,
+                                     gfloat       *natural_height_p,
+                                     gboolean      with_previews)
+{
+  MxPadding padding;
+
+  ClutterActor *actor = CLUTTER_ACTOR (tab_picker);
+  EMailTabPickerPrivate *priv = tab_picker->priv;
+
+  clutter_actor_get_preferred_height (CLUTTER_ACTOR (priv->chooser_button),
+                                     for_width, min_height_p, natural_height_p);
+
+  if (priv->tabs)
+    {
+      gfloat min_height, natural_height, scroll_height;
+
+      EMailTabPickerProps *props = priv->tabs->data;
+
+      /* Get the height of the first tab - it's assumed that tabs are
+       * fixed height.
+       */
+      if (with_previews)
+        {
+          clutter_actor_get_preferred_height (CLUTTER_ACTOR (props->tab),
+                                              for_width,
+                                              &min_height,
+                                              &natural_height);
+          if (CLUTTER_ACTOR_IS_VISIBLE (priv->scroll_bar))
+            {
+              /* Add the height of the scrollbar-section */
+              clutter_actor_get_preferred_height (
+                CLUTTER_ACTOR (priv->close_button), -1, NULL, &scroll_height);
+              scroll_height *= priv->preview_progress;
+
+              min_height += scroll_height;
+              natural_height += scroll_height;
+            }
+        }
+      else
+        e_mail_tab_get_height_no_preview (props->tab, for_width,
+                                       &min_height, &natural_height);
+
+      if (min_height_p && (*min_height_p < min_height))
+        *min_height_p = min_height;
+      if (natural_height_p && (*natural_height_p < natural_height))
+        *natural_height_p = natural_height;
+    }
+
+  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;
+}
+
+static void
+_e_mail_tab_picker_get_preferred_height (ClutterActor *actor,
+                                      gfloat        for_width,
+                                      gfloat       *min_height_p,
+                                      gfloat       *natural_height_p)
+{
+  e_mail_tab_picker_get_preferred_height (E_MAIL_TAB_PICKER (actor), for_width,
+                                       min_height_p, natural_height_p, TRUE);
+}
+
+static void
+e_mail_tab_picker_allocate_docked (EMailTabPicker           *tab_picker,
+                                const ClutterActorBox  *picker_box_p,
+                                const ClutterActorBox  *chooser_box_p,
+                                ClutterAllocationFlags  flags)
+{
+  GList *t;
+  MxPadding padding;
+  ClutterActorBox picker_box, chooser_box, child_box;
+  gfloat offset, width, left, right, height;
+
+  EMailTabPickerPrivate *priv = tab_picker->priv;
+
+  if (!picker_box_p)
+    {
+      clutter_actor_get_allocation_box (CLUTTER_ACTOR (tab_picker),
+                                        &picker_box);
+      picker_box_p = &picker_box;
+    }
+  if (!chooser_box_p)
+    {
+      clutter_actor_get_allocation_box (CLUTTER_ACTOR (priv->chooser_button),
+                                        &chooser_box);
+      chooser_box_p = &chooser_box;
+    }
+
+  mx_widget_get_padding (MX_WIDGET (tab_picker), &padding);
+
+  /* Calculate available width and height */
+  width = picker_box_p->x2 - picker_box_p->x1 - padding.right;
+
+  e_mail_tab_picker_get_preferred_height (tab_picker, -1, NULL, &height, FALSE);
+  child_box.y2 = picker_box_p->y2 - picker_box_p->y1 - padding.bottom;
+  child_box.y1 = child_box.y2 - height;
+
+  /* Don't dock over the chooser button */
+  width -= chooser_box_p->x2 - chooser_box_p->x1;
+
+  offset = priv->scroll_offset;
+
+  left = 0;
+  right = width;
+  priv->docked_tabs = FALSE;
+
+  for (t = g_list_last (priv->tabs); t; t = t->prev)
+    {
+      EMailTabPickerProps *props = t->data;
+
+      props->docked = FALSE;
+
+      if (!props->docking)
+        continue;
+
+      if (props->position < offset)
+        {
+          /* Dock left */
+          priv->docked_tabs = TRUE;
+          props->docked = TRUE;
+          child_box.x1 = left;
+          child_box.x2 = child_box.x1 + props->width;
+          left += props->width;
+        }
+      else if (props->position + props->width > width + offset)
+        {
+          /* Dock right */
+          priv->docked_tabs = TRUE;
+          props->docked = TRUE;
+          child_box.x2 = right;
+          child_box.x1 = child_box.x2 - props->width;
+          right -= props->width;
+        }
+      else
+        {
+          child_box.x1 = props->position;
+          child_box.x2 = child_box.x1 + props->width;
+        }
+
+      clutter_actor_allocate (CLUTTER_ACTOR (props->tab), &child_box, flags);
+    }
+}
+
+static void
+e_mail_tab_picker_scroll_new_frame_cb (ClutterTimeline *timeline,
+                                    guint            msecs,
+                                    EMailTabPicker    *tab_picker)
+{
+  EMailTabPickerPrivate *priv = tab_picker->priv;
+  gdouble alpha = clutter_alpha_get_alpha (priv->scroll_alpha);
+
+  priv->scroll_offset = (priv->scroll_start * (1.0 - alpha)) +
+                        (priv->scroll_end * alpha);
+  mx_adjustment_set_value (priv->scroll_adjustment, priv->scroll_offset);
+  e_mail_tab_picker_allocate_docked (tab_picker, NULL, NULL, 0);
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (tab_picker));
+}
+
+static void
+e_mail_tab_picker_scroll_completed_cb (ClutterTimeline *timeline,
+                                    EMailTabPicker    *tab_picker)
+{
+  EMailTabPickerPrivate *priv = tab_picker->priv;
+
+  priv->scroll_offset = priv->scroll_end;
+  mx_adjustment_set_value (priv->scroll_adjustment, priv->scroll_offset);
+  e_mail_tab_picker_allocate_docked (tab_picker, NULL, NULL, 0);
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (tab_picker));
+
+  g_object_unref (priv->scroll_alpha);
+  g_object_unref (priv->scroll_timeline);
+  priv->scroll_alpha = NULL;
+  priv->scroll_timeline = NULL;
+}
+
+static void
+e_mail_tab_picker_scroll_to (EMailTabPicker *tab_picker,
+                          gint          destination,
+                          guint         duration)
+{
+  EMailTabPickerPrivate *priv = tab_picker->priv;
+
+  priv->scroll_start = priv->scroll_offset;
+  priv->scroll_end = CLAMP (destination, 0, priv->max_offset);
+
+  if (priv->scroll_timeline)
+    {
+      clutter_timeline_stop (priv->scroll_timeline);
+      clutter_timeline_rewind (priv->scroll_timeline);
+      clutter_timeline_set_duration (priv->scroll_timeline, duration);
+    }
+  else
+    {
+      if (priv->scroll_end == priv->scroll_offset)
+        return;
+
+      priv->scroll_timeline = clutter_timeline_new (duration);
+      priv->scroll_alpha = clutter_alpha_new_full (priv->scroll_timeline,
+                                                   CLUTTER_EASE_OUT_QUAD);
+      g_signal_connect (priv->scroll_timeline, "new_frame",
+                        G_CALLBACK (e_mail_tab_picker_scroll_new_frame_cb),
+                        tab_picker);
+      g_signal_connect (priv->scroll_timeline, "completed",
+                        G_CALLBACK (e_mail_tab_picker_scroll_completed_cb),
+                        tab_picker);
+    }
+
+  clutter_timeline_start (priv->scroll_timeline);
+}
+
+static void
+e_mail_tab_picker_allocate (ClutterActor           *actor,
+                         const ClutterActorBox  *box,
+                         ClutterAllocationFlags  flags)
+{
+  GList *t;
+  MxPadding padding;
+  gint old_max_offset, old_scroll_offset;
+  ClutterActorBox child_box, scroll_box;
+  gfloat width, total_width, height;
+
+  EMailTabPicker *tab_picker = E_MAIL_TAB_PICKER (actor);
+  EMailTabPickerPrivate *priv = tab_picker->priv;
+
+  mx_widget_get_padding (MX_WIDGET (actor), &padding);
+
+  /* Allocate for scroll-bar and close button */
+  clutter_actor_get_preferred_size (CLUTTER_ACTOR (priv->close_button),
+                                    NULL, NULL, &width, &height);
+  child_box.x1 = 0;
+  child_box.x2 = box->x2 - box->x1 - padding.right;
+  child_box.y1 = 0;
+  child_box.y2 = child_box.y1 + height;
+  clutter_actor_allocate (CLUTTER_ACTOR (priv->close_button),
+                          &child_box,
+                          flags);
+
+  /* FIXME: Make this a property */
+#define SPACING 4.0
+  /* Work out allocation for scroll-bar, but allocate it later */
+  scroll_box = child_box;
+  scroll_box.x2 -= width + SPACING;
+  scroll_box.x1 += SPACING;
+  scroll_box.y1 += SPACING;
+  scroll_box.y2 -= SPACING;
+
+  child_box.y1 += (height * priv->preview_progress) + padding.top;
+
+  /* Allocate for tabs */
+  total_width = 0;
+  child_box.x1 = padding.left;
+  e_mail_tab_picker_get_preferred_height (tab_picker, -1, NULL, &height, FALSE);
+  for (t = priv->tabs; t; t = t->next)
+    {
+      EMailTabPickerProps *props = t->data;
+      ClutterActor *actor = CLUTTER_ACTOR (props->tab);
+
+      clutter_actor_get_preferred_width (actor, child_box.y2, NULL, &width);
+
+      /* Fill out data - note it's ok to fill out docking here as when it 
+       * changes, the tab queues a relayout.
+       */
+      props->docking = e_mail_tab_get_docking (props->tab);
+      props->position = child_box.x1;
+      props->width = width;
+
+      total_width += width;
+
+      /* Don't stretch tabs without a preview to fit tabs with a preview */
+      if (e_mail_tab_get_preview_actor (props->tab))
+        child_box.y2 = box->y2 - box->y1 - padding.bottom;
+      else
+        child_box.y2 = child_box.y1 + height;
+
+      child_box.x2 = child_box.x1 + width;
+      clutter_actor_allocate (actor, &child_box, flags);
+
+      child_box.x1 = child_box.x2;
+    }
+
+  /* Allocate for the chooser button */
+  clutter_actor_get_preferred_width (CLUTTER_ACTOR (priv->chooser_button),
+                                     box->y2 - box->y1, NULL, &width);
+
+  child_box.x2 = box->x2 - box->x1 - padding.right;
+  child_box.x1 = child_box.x2 - width;
+  child_box.y1 = 0;
+  child_box.y2 = child_box.y1 + height;
+  clutter_actor_allocate (CLUTTER_ACTOR (priv->chooser_button),
+                          &child_box, flags);
+
+  /* Cache some useful size values */
+  priv->width = (gint)(box->x2 - box->x1);
+
+  priv->total_width = (gint)(total_width + padding.left + padding.right);
+
+  old_max_offset = priv->max_offset;
+  priv->max_offset = priv->total_width - priv->width +
+                     (gint)(child_box.x2 - child_box.x1);
+  if (priv->max_offset < 0)
+    priv->max_offset = 0;
+
+  /* Allocate for tab picker */
+  old_scroll_offset = priv->scroll_offset;
+  priv->scroll_offset = CLAMP (priv->scroll_offset, 0, priv->max_offset);
+  e_mail_tab_picker_allocate_docked (tab_picker, box, &child_box, flags);
+
+  /* Chain up (store box) */
+  CLUTTER_ACTOR_CLASS (e_mail_tab_picker_parent_class)->
+    allocate (actor, box, flags);
+
+  /* Sync up the scroll-bar properties */
+  g_object_set (G_OBJECT (priv->scroll_adjustment),
+                "page-increment", (gdouble)(box->x2 - box->x1),
+                "page-size", (gdouble)(box->x2 - box->x1),
+                "upper", (gdouble)total_width,
+                NULL);
+
+  if ((priv->max_offset != old_max_offset) ||
+      (priv->scroll_offset != old_scroll_offset))
+    mx_adjustment_set_value (priv->scroll_adjustment,
+                             (gdouble)priv->scroll_offset);
+
+  /* Allocate for scroll-bar */
+  clutter_actor_allocate (CLUTTER_ACTOR (priv->scroll_bar), &scroll_box, flags);
+
+  /* Keep current tab visible */
+  if (priv->keep_current_visible)
+    {
+      EMailTabPickerProps *current =
+        g_list_nth_data (priv->tabs, priv->current_tab);
+
+      if ((current->position < priv->scroll_offset) ||
+          (current->position + current->width >= priv->max_offset))
+        e_mail_tab_picker_scroll_to (tab_picker, current->position, 150);
+    }
+}
+
+static void
+e_mail_tab_picker_map (ClutterActor *actor)
+{
+  GList *t;
+  EMailTabPickerPrivate *priv = E_MAIL_TAB_PICKER (actor)->priv;
+
+  CLUTTER_ACTOR_CLASS (e_mail_tab_picker_parent_class)->map (actor);
+
+  clutter_actor_map (CLUTTER_ACTOR (priv->chooser_button));
+  clutter_actor_map (CLUTTER_ACTOR (priv->close_button));
+  clutter_actor_map (CLUTTER_ACTOR (priv->scroll_bar));
+
+  for (t = priv->tabs; t; t = t->next)
+    {
+      EMailTabPickerProps *props = t->data;
+      clutter_actor_map (CLUTTER_ACTOR (props->tab));
+    }
+}
+
+static void
+e_mail_tab_picker_unmap (ClutterActor *actor)
+{
+  GList *t;
+  EMailTabPickerPrivate *priv = E_MAIL_TAB_PICKER (actor)->priv;
+
+  CLUTTER_ACTOR_CLASS (e_mail_tab_picker_parent_class)->unmap (actor);
+
+  clutter_actor_unmap (CLUTTER_ACTOR (priv->chooser_button));
+  clutter_actor_unmap (CLUTTER_ACTOR (priv->close_button));
+  clutter_actor_unmap (CLUTTER_ACTOR (priv->scroll_bar));
+
+  for (t = priv->tabs; t; t = t->next)
+    {
+      EMailTabPickerProps *props = t->data;
+      clutter_actor_unmap (CLUTTER_ACTOR (props->tab));
+    }
+}
+
+static void
+e_mail_tab_picker_class_init (EMailTabPickerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (EMailTabPickerPrivate));
+
+  object_class->get_property = e_mail_tab_picker_get_property;
+  object_class->set_property = e_mail_tab_picker_set_property;
+  object_class->dispose = e_mail_tab_picker_dispose;
+  object_class->finalize = e_mail_tab_picker_finalize;
+
+  actor_class->paint = e_mail_tab_picker_paint;
+  actor_class->pick = e_mail_tab_picker_pick;
+  actor_class->get_preferred_width = e_mail_tab_picker_get_preferred_width;
+  actor_class->get_preferred_height = _e_mail_tab_picker_get_preferred_height;
+  actor_class->allocate = e_mail_tab_picker_allocate;
+  actor_class->map = e_mail_tab_picker_map;
+  actor_class->unmap = e_mail_tab_picker_unmap;
+
+  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_NAME |
+                                                         G_PARAM_STATIC_NICK |
+                                                         G_PARAM_STATIC_BLURB));
+
+  g_object_class_override_property (object_class,
+                                    PROP_DROP_ENABLED,
+                                    "drop-enabled");
+
+  signals[TAB_ACTIVATED] =
+    g_signal_new ("tab-activated",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (EMailTabPickerClass, tab_activated),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__OBJECT,
+                  G_TYPE_NONE, 1, E_MAIL_TYPE_TAB);
+
+  signals[CHOOSER_CLICKED] =
+    g_signal_new ("chooser-clicked",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (EMailTabPickerClass, chooser_clicked),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+}
+
+static void
+e_mail_tab_picker_chooser_clicked_cb (ClutterActor *button,
+                                   EMailTabPicker *self)
+{
+  g_signal_emit (self, signals[CHOOSER_CLICKED], 0);
+}
+
+static gboolean
+e_mail_tab_picker_scroll_event_cb (ClutterActor       *actor,
+                                ClutterScrollEvent *event,
+                                gpointer            user_data)
+{
+  EMailTabPicker *self = E_MAIL_TAB_PICKER (actor);
+  EMailTabPickerPrivate *priv = self->priv;
+
+  priv->keep_current_visible = FALSE;
+
+  switch (event->direction)
+    {
+    case CLUTTER_SCROLL_UP :
+    case CLUTTER_SCROLL_LEFT :
+      e_mail_tab_picker_scroll_to (self, priv->scroll_end - 200, 150);
+      break;
+
+    case CLUTTER_SCROLL_DOWN :
+    case CLUTTER_SCROLL_RIGHT :
+      e_mail_tab_picker_scroll_to (self, priv->scroll_end + 200, 150);
+      break;
+    }
+  
+  return TRUE;
+}
+
+static void
+e_mail_tab_picker_scroll_value_cb (MxAdjustment   *adjustment,
+                                GParamSpec     *pspec,
+                                EMailTabPicker   *picker)
+{
+  EMailTabPickerPrivate *priv = picker->priv;
+  gdouble value = mx_adjustment_get_value (adjustment);
+
+  if ((gint)value != priv->scroll_offset)
+    {
+      priv->keep_current_visible = FALSE;
+      priv->scroll_offset = (gint)value;
+      clutter_actor_queue_relayout (CLUTTER_ACTOR (picker));
+    }
+}
+
+static void
+e_mail_tab_picker_init (EMailTabPicker *self)
+{
+  EMailTabPickerPrivate *priv = self->priv = TAB_PICKER_PRIVATE (self);
+
+  clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE);
+
+  priv->chooser_button = mx_button_new ();
+  clutter_actor_set_name (CLUTTER_ACTOR (priv->chooser_button),
+                          "chooser-button");
+  clutter_actor_set_parent (CLUTTER_ACTOR (priv->chooser_button),
+                            CLUTTER_ACTOR (self));
+
+  priv->close_button = mx_button_new ();
+  clutter_actor_set_name (CLUTTER_ACTOR (priv->close_button),
+                          "chooser-close-button");
+  clutter_actor_set_parent (CLUTTER_ACTOR (priv->close_button),
+                            CLUTTER_ACTOR (self));
+  clutter_actor_hide (CLUTTER_ACTOR (priv->close_button));
+
+  priv->scroll_adjustment = mx_adjustment_new_with_values (0, 0, 0, 100, 200, 200);
+  priv->scroll_bar = mx_scroll_bar_new_with_adjustment (priv->scroll_adjustment);
+  g_object_unref (priv->scroll_adjustment);
+  clutter_actor_set_parent (CLUTTER_ACTOR (priv->scroll_bar),
+                            CLUTTER_ACTOR (self));
+  clutter_actor_hide (CLUTTER_ACTOR (priv->scroll_bar));
+
+  g_signal_connect (priv->chooser_button, "clicked",
+                    G_CALLBACK (e_mail_tab_picker_chooser_clicked_cb), self);
+  g_signal_connect (priv->close_button, "clicked",
+                    G_CALLBACK (e_mail_tab_picker_chooser_clicked_cb), self);
+  g_signal_connect (self, "scroll-event",
+                    G_CALLBACK (e_mail_tab_picker_scroll_event_cb), NULL);
+}
+
+static gint
+e_mail_tab_picker_find_tab_cb (gconstpointer a, gconstpointer b)
+{
+  EMailTabPickerProps *props = (EMailTabPickerProps *)a;
+  EMailTab *tab = (EMailTab *)b;
+
+  if (props->tab == tab)
+    return 0;
+  else
+    return -1;
+}
+
+static void
+e_mail_tab_picker_tab_clicked_cb (EMailTab *tab, EMailTabPicker *self)
+{
+  EMailTabPickerPrivate *priv = self->priv;
+  EMailTab *old_tab =
+    ((EMailTabPickerProps *)g_list_nth_data (priv->tabs, priv->current_tab))->tab;
+  GList *new_tab_link = g_list_find_custom (priv->tabs, tab,
+                                            e_mail_tab_picker_find_tab_cb);
+
+  if (!new_tab_link)
+    return;
+
+  priv->keep_current_visible = TRUE;
+
+  /* If the same tab is clicked, make sure we remain active and return */
+  if (tab == old_tab)
+    {
+      e_mail_tab_set_active (tab, TRUE);
+      if (priv->preview_mode)
+        g_signal_emit (self, signals[TAB_ACTIVATED], 0, tab);
+      return;
+    }
+
+  /* Deselect old tab */
+  e_mail_tab_set_active (old_tab, FALSE);
+
+  /* Set new tab */
+  priv->current_tab = g_list_position (priv->tabs, new_tab_link);
+  g_signal_emit (self, signals[TAB_ACTIVATED], 0, tab);
+}
+
+ClutterActor *
+e_mail_tab_picker_new (void)
+{
+  return g_object_new (E_MAIL_TYPE_TAB_PICKER, NULL);
+}
+
+static void
+e_mail_tab_picker_tab_drag_begin_cb (MxDraggable         *draggable,
+                                  gfloat               event_x,
+                                  gfloat               event_y,
+                                  gint                 event_button,
+                                  ClutterModifierType  modifiers,
+                                  EMailTabPicker        *picker)
+{
+  EMailTabPickerPrivate *priv = picker->priv;
+  priv->in_drag = TRUE;
+
+  if (!priv->preview_mode)
+    {
+      e_mail_tab_picker_set_preview_mode (picker, TRUE);
+      priv->drag_preview = TRUE;
+    }
+}
+
+static void
+e_mail_tab_picker_tab_drag_end_cb (MxDraggable  *draggable,
+                                gfloat        event_x,
+                                gfloat        event_y,
+                                EMailTabPicker *picker)
+{
+  EMailTabPickerPrivate *priv = picker->priv;
+  priv->in_drag = FALSE;
+
+  if (priv->drag_preview)
+    {
+      e_mail_tab_picker_set_preview_mode (picker, FALSE);
+      priv->drag_preview = FALSE;
+    }
+}
+
+void
+e_mail_tab_picker_add_tab (EMailTabPicker *picker, EMailTab *tab, gint position)
+{
+  EMailTabPickerProps *props;
+  EMailTabPickerPrivate *priv = picker->priv;
+
+  if (priv->tabs && (priv->current_tab >= position))
+    priv->current_tab ++;
+
+  props = g_slice_new (EMailTabPickerProps);
+  props->tab = tab;
+  priv->tabs = g_list_insert (priv->tabs, props, position);
+  priv->n_tabs ++;
+
+  clutter_actor_set_parent (CLUTTER_ACTOR (tab), CLUTTER_ACTOR (picker));
+  mx_draggable_set_axis (MX_DRAGGABLE (tab), MX_DRAG_AXIS_X);
+
+  g_signal_connect_after (tab, "clicked",
+                          G_CALLBACK (e_mail_tab_picker_tab_clicked_cb), picker);
+  g_signal_connect (tab, "drag-begin",
+                    G_CALLBACK (e_mail_tab_picker_tab_drag_begin_cb), picker);
+  g_signal_connect (tab, "drag-end",
+                    G_CALLBACK (e_mail_tab_picker_tab_drag_end_cb), picker);
+
+  e_mail_tab_set_preview_mode (tab, priv->preview_mode);
+  clutter_actor_queue_relayout (CLUTTER_ACTOR (picker));
+}
+
+void
+e_mail_tab_picker_remove_tab (EMailTabPicker *picker, EMailTab *tab)
+{
+  GList *tab_link;
+  EMailTabPickerPrivate *priv = picker->priv;
+
+  tab_link = g_list_find_custom (priv->tabs, tab, e_mail_tab_picker_find_tab_cb);
+
+  if (!tab_link)
+    return;
+
+  g_signal_handlers_disconnect_by_func (tab,
+                                        e_mail_tab_picker_tab_clicked_cb,
+                                        picker);
+  g_signal_handlers_disconnect_by_func (tab,
+                                        e_mail_tab_picker_tab_drag_begin_cb,
+                                        picker);
+  g_signal_handlers_disconnect_by_func (tab,
+                                        e_mail_tab_picker_tab_drag_end_cb,
+                                        picker);
+
+  /* We don't want to do this during dispose, checking if chooser_button
+   * exists is a way of checking if we're in dispose without keeping an
+   * extra variable around.
+   */
+  if (priv->chooser_button)
+    {
+      gint position = g_list_position (priv->tabs, tab_link);
+      if (priv->current_tab)
+        {
+          if (priv->current_tab > position)
+            priv->current_tab --;
+          else if (priv->current_tab == position)
+            e_mail_tab_picker_set_current_tab (picker, priv->current_tab - 1);
+        }
+      else if (priv->tabs->next && (position == 0))
+        {
+          e_mail_tab_picker_set_current_tab (picker, priv->current_tab + 1);
+          priv->current_tab --;
+        }
+    }
+
+  g_slice_free (EMailTabPickerProps, tab_link->data);
+  priv->tabs = g_list_delete_link (priv->tabs, tab_link);
+  clutter_actor_unparent (CLUTTER_ACTOR (tab));
+  priv->n_tabs --;
+
+  clutter_actor_queue_relayout (CLUTTER_ACTOR (picker));
+}
+
+GList *
+e_mail_tab_picker_get_tabs (EMailTabPicker *picker)
+{
+  GList *tab_list, *t;
+  
+  EMailTabPickerPrivate *priv = picker->priv;
+  
+  tab_list = NULL;
+  for (t = g_list_last (priv->tabs); t; t = t->prev)
+    {
+      EMailTabPickerProps *props = t->data;
+      tab_list = g_list_prepend (tab_list, props->tab);
+    }
+
+  return tab_list;
+}
+
+EMailTab *
+e_mail_tab_picker_get_tab (EMailTabPicker *picker, gint tab)
+{
+  EMailTabPickerProps *props = g_list_nth_data (picker->priv->tabs, tab);
+  return props->tab;
+}
+
+gint
+e_mail_tab_picker_get_tab_no (EMailTabPicker *picker, EMailTab *tab)
+{
+  GList *tab_link = g_list_find_custom (picker->priv->tabs, tab,
+                                        e_mail_tab_picker_find_tab_cb);
+  return g_list_position (picker->priv->tabs, tab_link);
+}
+
+gint
+e_mail_tab_picker_get_current_tab (EMailTabPicker *picker)
+{
+  return picker->priv->current_tab;
+}
+
+void
+e_mail_tab_picker_set_current_tab (EMailTabPicker *picker, gint tab_no)
+{
+  EMailTabPickerPrivate *priv = picker->priv;
+  EMailTabPickerProps *props;
+
+  printf("OLD %d new %d\n", priv->current_tab, tab_no);
+  if (priv->n_tabs == 0)
+    return;
+
+  if (ABS (tab_no) >= priv->n_tabs)
+    return;
+
+  if (tab_no < 0)
+    tab_no = priv->n_tabs + tab_no;
+
+  props = g_list_nth_data (priv->tabs, (guint)tab_no);
+
+  if (props)
+    {
+      e_mail_tab_picker_tab_clicked_cb (props->tab, picker);
+      e_mail_tab_set_active (props->tab, TRUE);
+    }
+}
+
+void
+e_mail_tab_picker_reorder (EMailTabPicker *picker,
+                        gint          old_position,
+                        gint          new_position)
+{
+  GList *link;
+  gpointer data;
+
+  EMailTabPickerPrivate *priv = picker->priv;
+
+  if (old_position == new_position)
+    return;
+
+  if (!(link = g_list_nth (priv->tabs, old_position)))
+    return;
+
+  data = link->data;
+  priv->tabs = g_list_delete_link (priv->tabs, link);
+  priv->tabs = g_list_insert (priv->tabs, data, new_position);
+
+  if (priv->current_tab == old_position)
+    {
+      if (new_position < 0)
+        priv->current_tab = priv->n_tabs - 1;
+      else
+        priv->current_tab = CLAMP (new_position, 0, priv->n_tabs - 1);
+    }
+  else if ((priv->current_tab > old_position) &&
+           (new_position >= priv->current_tab))
+    priv->current_tab --;
+  else if ((priv->current_tab < old_position) &&
+           (new_position <= priv->current_tab))
+    priv->current_tab ++;
+
+  clutter_actor_queue_relayout (CLUTTER_ACTOR (picker));
+}
+
+gint
+e_mail_tab_picker_get_n_tabs (EMailTabPicker *picker)
+{
+  return picker->priv->n_tabs;
+}
+
+static void
+preview_new_frame_cb (ClutterTimeline *timeline,
+                      guint            msecs,
+                      EMailTabPicker    *picker)
+{
+  picker->priv->preview_progress = clutter_timeline_get_progress (timeline);
+  clutter_actor_queue_relayout (CLUTTER_ACTOR (picker));
+}
+
+static void
+preview_completed_cb (ClutterTimeline *timeline,
+                      EMailTabPicker    *picker)
+{
+  EMailTabPickerPrivate *priv = picker->priv;
+
+  if (priv->preview_timeline)
+    {
+      g_object_unref (priv->preview_timeline);
+      priv->preview_timeline = NULL;
+
+      if (priv->preview_mode)
+        {
+          priv->preview_progress = 1.0;
+          clutter_actor_hide (CLUTTER_ACTOR (priv->chooser_button));
+        }
+      else
+        {
+          priv->preview_progress = 0.0;
+          clutter_actor_hide (CLUTTER_ACTOR (priv->scroll_bar));
+          clutter_actor_hide (CLUTTER_ACTOR (priv->close_button));
+        }
+      clutter_actor_queue_relayout (CLUTTER_ACTOR (picker));
+    }
+}
+
+void
+e_mail_tab_picker_set_preview_mode (EMailTabPicker *picker, gboolean preview)
+{
+  GList *t;
+
+  EMailTabPickerPrivate *priv = picker->priv;
+
+  if (priv->preview_mode == preview)
+    return;
+
+  priv->preview_mode = preview;
+
+  /* Put all tabs in preview mode */
+  for (t = priv->tabs; t; t = t->next)
+    {
+      EMailTabPickerProps *prop = t->data;
+      e_mail_tab_set_preview_mode (prop->tab, preview);
+    }
+
+  /* Slide in the scroll-bar */
+  if (!priv->preview_timeline)
+    {
+      if (preview)
+        clutter_actor_show (CLUTTER_ACTOR (priv->scroll_bar));
+
+      priv->preview_timeline = clutter_timeline_new (150);
+      g_signal_connect (priv->preview_timeline, "new-frame",
+                        G_CALLBACK (preview_new_frame_cb), picker);
+      g_signal_connect (priv->preview_timeline, "completed",
+                        G_CALLBACK (preview_completed_cb), picker);
+      clutter_timeline_start (priv->preview_timeline);
+    }
+  clutter_timeline_set_direction (priv->preview_timeline,
+                                  preview ? CLUTTER_TIMELINE_FORWARD :
+                                            CLUTTER_TIMELINE_BACKWARD);
+
+  /* Connect/disconnect the scrollbar */
+  if (preview)
+    g_signal_connect (priv->scroll_adjustment, "notify::value",
+                      G_CALLBACK (e_mail_tab_picker_scroll_value_cb), picker);
+  else
+    g_signal_handlers_disconnect_by_func (priv->scroll_adjustment,
+                                          e_mail_tab_picker_scroll_value_cb,
+                                          picker);
+
+  if (preview)
+    {
+      /* Fade out the chooser button show close button */
+      clutter_actor_animate (CLUTTER_ACTOR (priv->chooser_button),
+                             CLUTTER_EASE_IN_OUT_QUAD, 150,
+                             "opacity", 0x00,
+                             NULL);
+      clutter_actor_show (CLUTTER_ACTOR (priv->close_button));
+    }
+  else
+    {
+      /* Fade in the chooser button */
+      clutter_actor_show (CLUTTER_ACTOR (priv->chooser_button));
+      clutter_actor_animate (CLUTTER_ACTOR (priv->chooser_button),
+                             CLUTTER_EASE_IN_OUT_QUAD, 150,
+                             "opacity", 0xff,
+                             NULL);
+    }
+  clutter_actor_set_reactive (CLUTTER_ACTOR (priv->chooser_button), !preview);
+
+  /* Remove the hover state, which likely got stuck when we clicked it */
+  if (!preview)
+    mx_stylable_set_style_pseudo_class (MX_STYLABLE (priv->chooser_button),
+                                        NULL);
+
+  g_object_notify (G_OBJECT (picker), "preview-mode");
+}
+
+gboolean
+e_mail_tab_picker_get_preview_mode (EMailTabPicker *picker)
+{
+  EMailTabPickerPrivate *priv = picker->priv;
+  return priv->preview_mode;
+}
+
+void
+e_mail_tab_picker_enable_drop (EMailTabPicker *picker, gboolean enable)
+{
+  EMailTabPickerPrivate *priv = picker->priv;
+
+  if (priv->drop_enabled == enable)
+    return;
+
+  priv->drop_enabled = enable;
+  if (enable)
+    mx_droppable_enable (MX_DROPPABLE (picker));
+  else
+    mx_droppable_disable (MX_DROPPABLE (picker));
+
+  g_object_notify (G_OBJECT (picker), "enabled");
+}
+
diff --git a/mail/e-mail-tab-picker.h b/mail/e-mail-tab-picker.h
new file mode 100644
index 0000000..7de3fac
--- /dev/null
+++ b/mail/e-mail-tab-picker.h
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+
+#ifndef _E_MAIL_TAB_PICKER_H
+#define _E_MAIL_TAB_PICKER_H
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+#include <mx/mx.h>
+#include "e-mail-tab.h"
+
+G_BEGIN_DECLS
+
+#define E_MAIL_TYPE_TAB_PICKER e_mail_tab_picker_get_type()
+
+#define E_MAIL_TAB_PICKER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+  E_MAIL_TYPE_TAB_PICKER, EMailTabPicker))
+
+#define E_MAIL_TAB_PICKER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+  E_MAIL_TYPE_TAB_PICKER, EMailTabPickerClass))
+
+#define E_MAIL_IS_TAB_PICKER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+  E_MAIL_TYPE_TAB_PICKER))
+
+#define E_MAIL_IS_TAB_PICKER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+  E_MAIL_TYPE_TAB_PICKER))
+
+#define E_MAIL_TAB_PICKER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+  E_MAIL_TYPE_TAB_PICKER, EMailTabPickerClass))
+
+typedef struct _EMailTabPickerPrivate EMailTabPickerPrivate;
+
+typedef struct {
+  MxWidget parent;
+
+  EMailTabPickerPrivate *priv;
+} EMailTabPicker;
+
+typedef struct {
+  MxWidgetClass parent_class;
+
+  void (* tab_activated)   (EMailTabPicker *picker, EMailTab *tab);
+  void (* chooser_clicked) (EMailTabPicker *picker);
+} EMailTabPickerClass;
+
+GType e_mail_tab_picker_get_type (void);
+
+ClutterActor *e_mail_tab_picker_new (void);
+
+void e_mail_tab_picker_add_tab (EMailTabPicker *picker, EMailTab *tab, gint position);
+
+void e_mail_tab_picker_remove_tab (EMailTabPicker *picker, EMailTab *tab);
+
+GList *e_mail_tab_picker_get_tabs (EMailTabPicker *picker);
+
+gint e_mail_tab_picker_get_n_tabs (EMailTabPicker *picker);
+
+EMailTab *e_mail_tab_picker_get_tab (EMailTabPicker *picker, gint tab);
+
+gint e_mail_tab_picker_get_tab_no (EMailTabPicker *picker, EMailTab *tab);
+
+gint e_mail_tab_picker_get_current_tab (EMailTabPicker *picker);
+
+void e_mail_tab_picker_set_current_tab (EMailTabPicker *picker, gint tab);
+
+void e_mail_tab_picker_reorder (EMailTabPicker *picker,
+                             gint          old_position,
+                             gint          new_position);
+
+void e_mail_tab_picker_set_tab_width (EMailTabPicker *picker,
+                                   gint          width);
+
+gint e_mail_tab_picker_get_tab_width (EMailTabPicker *picker);
+
+void
+e_mail_tab_picker_get_preferred_height (EMailTabPicker *tab_picker,
+                                     gfloat        for_width,
+                                     gfloat       *min_height_p,
+                                     gfloat       *natural_height_p,
+                                     gboolean      with_previews);
+
+void e_mail_tab_picker_set_preview_mode (EMailTabPicker *picker, gboolean preview);
+
+gboolean e_mail_tab_picker_get_preview_mode (EMailTabPicker *picker);
+
+void e_mail_tab_picker_enable_drop (EMailTabPicker *picker, gboolean enable);
+
+G_END_DECLS
+
+#endif /* _E_MAIL_TAB_PICKER_H */
+
diff --git a/mail/e-mail-tab.c b/mail/e-mail-tab.c
new file mode 100644
index 0000000..125f174
--- /dev/null
+++ b/mail/e-mail-tab.c
@@ -0,0 +1,1901 @@
+/*
+ * 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))
+
+#define TAB_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), E_MAIL_TYPE_TAB, EMailTabPrivate))
+
+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_TYPE,
+  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;
+ // MxDragContainment    containment;
+  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_TYPE:
+  //    g_value_set_enum (value, priv->containment);
+    //  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)
+        {
+          ClutterActor *fbo =
+            /*clutter_texture_new_from_actor (CLUTTER_ACTOR (tab));*/
+            NULL;
+          if (fbo)
+            {
+              /* This is where we'd setup deformations, or something along
+               * those lines.
+               */
+              priv->drag_actor = g_object_ref_sink (fbo);
+            }
+          else
+            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,
+                                 CLUTTER_ACTOR (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_TYPE:
+ //     priv->containment = 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 = 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)
+    {
+      if (clutter_actor_get_parent (priv->old_bg) == (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 = 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_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (e_mail_tab_parent_class)->finalize (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, 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, label_width, label_height,
+         close_width, close_height, 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 *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+  g_type_class_add_private (klass, 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;
+  object_class->finalize = e_mail_tab_finalize;
+
+  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_NAME |
+                                                        G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (object_class,
+                                   PROP_TEXT,
+                                   g_param_spec_string ("text",
+                                                        "Text",
+                                                        "Tab text.",
+                                                        "",
+                                                        G_PARAM_READWRITE |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  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_NAME |
+                                                         G_PARAM_STATIC_NICK |
+                                                         G_PARAM_STATIC_BLURB));
+
+  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_NAME |
+                                                     G_PARAM_STATIC_NICK |
+                                                     G_PARAM_STATIC_BLURB));
+
+  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_NAME |
+                                                         G_PARAM_STATIC_NICK |
+                                                         G_PARAM_STATIC_BLURB));
+
+  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_NAME |
+                                                        G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  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_NAME |
+                                                         G_PARAM_STATIC_NICK |
+                                                         G_PARAM_STATIC_BLURB));
+
+  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_NAME |
+                                                      G_PARAM_STATIC_NICK |
+                                                      G_PARAM_STATIC_BLURB));
+
+  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_NAME |
+                                                       G_PARAM_STATIC_NICK |
+                                                       G_PARAM_STATIC_BLURB));
+
+  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_NAME |
+                                                         G_PARAM_STATIC_NICK |
+                                                         G_PARAM_STATIC_BLURB));
+
+  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_NAME |
+                                                         G_PARAM_STATIC_NICK |
+                                                         G_PARAM_STATIC_BLURB));
+
+  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_TYPE,
+     //                               "containment-type");
+  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 (klass),
+                  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 (klass),
+                  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 (klass),
+                  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 = self->priv = TAB_PRIVATE (self);
+
+  priv->width = -1;
+  priv->anim_length = 200;
+  priv->spacing = 6.0;
+  priv->can_close = TRUE;
+
+  priv->label = mx_label_new ();
+  g_object_set (G_OBJECT (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) ? TRUE : FALSE;
+  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));
+}
+
diff --git a/mail/e-mail-tab.h b/mail/e-mail-tab.h
new file mode 100644
index 0000000..3966d27
--- /dev/null
+++ b/mail/e-mail-tab.h
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+#ifndef _E_MAIL_TAB_H
+#define _E_MAIL_TAB_H
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+#include <mx/mx.h>
+
+G_BEGIN_DECLS
+
+#define E_MAIL_TYPE_TAB e_mail_tab_get_type()
+
+#define E_MAIL_TAB(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+  E_MAIL_TYPE_TAB, EMailTab))
+
+#define E_MAIL_TAB_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+  E_MAIL_TYPE_TAB, EMailTabClass))
+
+#define E_MAIL_IS_TAB(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+  E_MAIL_TYPE_TAB))
+
+#define E_MAIL_IS_TAB_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+  E_MAIL_TYPE_TAB))
+
+#define E_MAIL_TAB_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+  E_MAIL_TYPE_TAB, EMailTabClass))
+
+typedef struct _EMailTabPrivate EMailTabPrivate;
+
+typedef struct {
+  MxWidget parent;
+
+  EMailTabPrivate *priv;
+} EMailTab;
+
+typedef struct {
+  MxWidgetClass parent_class;
+
+  void (* clicked)             (EMailTab *tab);
+  void (* closed)              (EMailTab *tab);
+  void (* transition_complete) (EMailTab *tab);
+} EMailTabClass;
+
+GType e_mail_tab_get_type (void);
+
+ClutterActor *e_mail_tab_new (void);
+ClutterActor *e_mail_tab_new_full (const gchar  *text,
+                                ClutterActor *icon,
+                                gint          width);
+
+void e_mail_tab_set_text                (EMailTab *tab, const gchar  *text);
+void e_mail_tab_set_default_icon        (EMailTab *tab, ClutterActor *icon);
+void e_mail_tab_set_icon                (EMailTab *tab, ClutterActor *icon);
+void e_mail_tab_set_can_close           (EMailTab *tab, gboolean      can_close);
+void e_mail_tab_set_width               (EMailTab *tab, gint          width);
+void e_mail_tab_set_docking             (EMailTab *tab, gboolean      docking);
+void e_mail_tab_set_preview_actor       (EMailTab *tab, ClutterActor *actor);
+void e_mail_tab_set_preview_mode        (EMailTab *tab, gboolean      preview);
+void e_mail_tab_set_preview_duration    (EMailTab *tab, guint         duration);
+void e_mail_tab_set_spacing             (EMailTab *tab, gfloat        spacing);
+void e_mail_tab_set_private             (EMailTab *tab, gboolean      private);
+void e_mail_tab_set_active              (EMailTab *tab, gboolean      active);
+
+const gchar  *e_mail_tab_get_text                (EMailTab *tab);
+ClutterActor *e_mail_tab_get_icon                (EMailTab *tab);
+gboolean      e_mail_tab_get_can_close           (EMailTab *tab);
+gint          e_mail_tab_get_width               (EMailTab *tab);
+gboolean      e_mail_tab_get_docking             (EMailTab *tab);
+ClutterActor *e_mail_tab_get_preview_actor       (EMailTab *tab);
+gboolean      e_mail_tab_get_preview_mode        (EMailTab *tab);
+void          e_mail_tab_get_height_no_preview   (EMailTab *tab,
+                                               gfloat  for_width,
+                                               gfloat *min_height_p,
+                                               gfloat *natural_height_p);
+guint         e_mail_tab_get_preview_duration    (EMailTab *tab);
+gfloat        e_mail_tab_get_spacing             (EMailTab *tab);
+gboolean      e_mail_tab_get_private             (EMailTab *tab);
+gboolean      e_mail_tab_get_active              (EMailTab *tab);
+
+void e_mail_tab_alert                   (EMailTab *tab);
+void e_mail_tab_enable_drag             (EMailTab *tab, gboolean enable);
+
+G_END_DECLS
+
+#endif /* _E_MAIL_TAB_H */
+



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]