aboutsummaryrefslogblamecommitdiffstats
path: root/mail/e-mail-tab-picker.c
blob: c5751ca90f6fc21a10fb519c8d1f6dd4edfa2d5a (plain) (tree)


















                                                                           
                   



                              



                                                               


                                                                             





                                                            
 


                                                                                    

    
               
 

                          



    

                        
 
                   





                                           




                              



                             

























                                              



                                                  
                                                  




                                                   
                                                   



           





                                                      
 

                                 
                             







                                                               
 

                                       
 

                                                                      



                                                                                      
                                              





















                                                                                     
                                                                                               















                                                                                           




                                                 


                                                     


           



                                                  
 















                                                                           


           



                                                    
 















                                                                         




                                           

                                                            
 



































                                                                              




                                             

                                     
                                    
 
                                                     
 
                                                                            
 
                                                        
 
                                                       
 

                                                    
 


                                                     
 





                                                            
 


                                                                         
 
                                                   
 



                                                             
 

                                           
 


                                                                                 
         




















                                                                                         
         


           

                                                  
 


                                                     
 

                                                                                  
 

                                                



                                                           


                                                               
 

                          
                                    
 
                                                     
 


                                                          
 




                                                                 
 


                                                     
 


                                                               
 




                                                          



                                                                   



                                                                 
 







































                                                                             
         





                                                                  



                                                             


                                                                  
 


                                                      


           



                                                                        
 










                                                                 
         




                                                                            
         
 
















































                                                                             



                                                                 

                                                                  
 








                                                                               



                                                                 
                                                                  
 
                                                       
 



                                                                               
 



                                               



                                                        

                                              
 

















                                                                                

                                                                           

                                                           

                                                                           
         
 
                                                       


           


                                                         
 

















                                                               
                                             

                                                                       
 










































                                                                          
 



                                                     
 

































                                                                               
                      
 



                                                                   
                                                       



                                                                      
 


                                             
 
                                                                          
 




                                                                             




                                           
                 


                                                     
 
                                                                          
 


                                                                 
 



                                                               




                                             
                 


                                                     
 
                                                                            
 


                                                                   
 



                                                                 


           
                                                         
 


























                                                                                    
                                                 













                                                                     
                                 








                                                                       



                                                           
                                                             
 
                                                            


               


                                                             
 

                                                           






                                                     
                                                                     




                                                     
                                                                     

                              
 
                    


           


                                                            
 




                                                             
                                                   

                                                                      


           
                                               
 
                                                              
 
                                                                  
 
                                                        
                                

                                                             
                                  

                                                             
 
                                                      
                                

                                                           
                                  


                                                                        
 
                                         
                                                                       



                                                         
                                  


                                                                      

                          

                                                                           
                          

                                                                           
                          
                                       
                                                                      


           

                                               
 

                                                               
 
                                            


           
                                                
                                                         
 
                                                   
















                                                                               
                                                                               

                       
 

                                               
 

                                                                       
                                                               




                            
                                                           


           





                                                                   
 






                                                                  


           



                                                          
 






                                                                   


    


                                                  
 

























                                                                               


    

                                                     
 































                                                                              
         
 



                                                               
 
                                                              




                                                   
                            
 
                                                   
 




                                                                 
 
                        


          

                                                  
 

                                                                               


    

                                                     
 






                                                              




                                                          
                                         


    

                                                          
 

                                                   
 


                                                             
 

                                         
 

                                               
 
                                                             
 



                                                                      



                                                  

                                             
 




























                                                                    




                                                     
                                    



                                                

                                             
 


                                                              



                                                
                                             
 
















                                                                                
         


    

                                                           
 
                 
 
                                                   
 
                                                              
                       
 
                                     
 




                                                                 
 



                                                                              
 


                                                                    
                                                                   

                                                            
                                                                   










































                                                                                




                                                           

                                                   


    

                                                      
 
                                                   
 
                                                             
                       
 




                                                             
 
                                                       

 
/*
 * 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"

#define E_MAIL_TAB_PICKER_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), E_TYPE_MAIL_TAB_PICKER, EMailTabPickerPrivate))

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;
    ClutterActor *parent;
    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;

    parent = clutter_actor_get_parent (CLUTTER_ACTOR (draggable));
    if (parent != (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 (
                E_MAIL_TAB_PICKER (object)));
            return;

        case PROP_DROP_ENABLED:
            g_value_set_boolean (value, priv->drop_enabled);
            return;
    }

    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 (
                E_MAIL_TAB_PICKER (object),
                g_value_get_boolean (value));
            return;

        case PROP_DROP_ENABLED:
            priv->drop_enabled = g_value_get_boolean (value);
            return;
    }

    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);
    }

    /* Chain up to parent's dispose() method. */
    G_OBJECT_CLASS (e_mail_tab_picker_parent_class)->dispose (object);
}

static void
e_mail_tab_picker_paint (ClutterActor *actor)
{
    GList *t;
    gfloat width, height, offset;
    EMailTabPickerPrivate *priv;

    priv = E_MAIL_TAB_PICKER_GET_PRIVATE (actor);

    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;

    priv = E_MAIL_TAB_PICKER_GET_PRIVATE (actor);

    /* 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;

    priv = E_MAIL_TAB_PICKER_GET_PRIVATE (actor);

    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 (
        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;

        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;

    priv = E_MAIL_TAB_PICKER_GET_PRIVATE (actor);

    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;

    priv = E_MAIL_TAB_PICKER_GET_PRIVATE (actor);

    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 *class)
{
    GObjectClass *object_class = G_OBJECT_CLASS (class);
    ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (class);

    g_type_class_add_private (class, 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;

    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_STRINGS));

    g_object_class_override_property (
        object_class,
        PROP_DROP_ENABLED,
        "drop-enabled");

    signals[TAB_ACTIVATED] = g_signal_new (
        "tab-activated",
        G_TYPE_FROM_CLASS (class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (EMailTabPickerClass, tab_activated),
        NULL, NULL,
        g_cclosure_marshal_VOID__OBJECT,
        G_TYPE_NONE, 1,
        E_TYPE_MAIL_TAB);

    signals[CHOOSER_CLICKED] = g_signal_new (
        "chooser-clicked",
        G_TYPE_FROM_CLASS (class),
        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 *picker)
{
    g_signal_emit (picker, signals[CHOOSER_CLICKED], 0);
}

static gboolean
e_mail_tab_picker_scroll_event_cb (ClutterActor *actor,
                                   ClutterScrollEvent *event,
                                   gpointer user_data)
{
    EMailTabPicker *picker = E_MAIL_TAB_PICKER (actor);
    EMailTabPickerPrivate *priv = picker->priv;

    priv->keep_current_visible = FALSE;

    switch (event->direction) {
        case CLUTTER_SCROLL_UP :
        case CLUTTER_SCROLL_LEFT :
            e_mail_tab_picker_scroll_to (
                picker, priv->scroll_end - 200, 150);
            break;

        case CLUTTER_SCROLL_DOWN :
        case CLUTTER_SCROLL_RIGHT :
            e_mail_tab_picker_scroll_to (
                picker, 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 *picker)
{
    picker->priv = E_MAIL_TAB_PICKER_GET_PRIVATE (picker);

    clutter_actor_set_reactive (CLUTTER_ACTOR (picker), TRUE);

    picker->priv->chooser_button = mx_button_new ();
    clutter_actor_set_name (
        CLUTTER_ACTOR (picker->priv->chooser_button),
        "chooser-button");
    clutter_actor_set_parent (
        CLUTTER_ACTOR (picker->priv->chooser_button),
        CLUTTER_ACTOR (picker));

    picker->priv->close_button = mx_button_new ();
    clutter_actor_set_name (
        CLUTTER_ACTOR (picker->priv->close_button),
        "chooser-close-button");
    clutter_actor_set_parent (
        CLUTTER_ACTOR (picker->priv->close_button),
        CLUTTER_ACTOR (picker));
    clutter_actor_hide (CLUTTER_ACTOR (picker->priv->close_button));

    picker->priv->scroll_adjustment =
        mx_adjustment_new_with_values (0, 0, 0, 100, 200, 200);
    picker->priv->scroll_bar =
        mx_scroll_bar_new_with_adjustment (
        picker->priv->scroll_adjustment);
    g_object_unref (picker->priv->scroll_adjustment);
    clutter_actor_set_parent (
        CLUTTER_ACTOR (picker->priv->scroll_bar),
        CLUTTER_ACTOR (picker));
    clutter_actor_hide (CLUTTER_ACTOR (picker->priv->scroll_bar));

    g_signal_connect (
        picker->priv->chooser_button, "clicked",
        G_CALLBACK (e_mail_tab_picker_chooser_clicked_cb), picker);
    g_signal_connect (
        picker->priv->close_button, "clicked",
        G_CALLBACK (e_mail_tab_picker_chooser_clicked_cb), picker);
    g_signal_connect (
        picker, "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;

    return (props->tab == tab) ? 0 : -1;
}

static void
e_mail_tab_picker_tab_clicked_cb (EMailTab *tab,
                                  EMailTabPicker *picker)
{
    EMailTabPickerPrivate *priv = picker->priv;
    EMailTab *old_tab;
    GList *new_tab_link;

    old_tab = ((EMailTabPickerProps *) g_list_nth_data (
        priv->tabs, priv->current_tab))->tab;
    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 (picker, 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 (picker, signals[TAB_ACTIVATED], 0, tab);
}

ClutterActor *
e_mail_tab_picker_new (void)
{
    return g_object_new (E_TYPE_MAIL_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;

    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 ? 1 : 0) == (preview ? 1 : 0))
        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 ? 1 : 0) == (enable ? 1 : 0))
        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");
}