aboutsummaryrefslogblamecommitdiffstats
path: root/em-format/e-mail-formatter.c
blob: f3f09670d4f9d8b11967c798cd43c4e6877e1212 (plain) (tree)



















                                                                             



                                       




                                    

                                             

            


                                                               
 



                                                                
 


                                                     


                                   
 

                             

                               

  




















                                                                              




                                                  
                            
                        
                     

                            
                             

                          
                                  
                            
                            

                               

  




                    
                                 
 








                                                   
                              


                                                         
                                                               
 
                                   
                                       
 
                                                       
 
                                                                               
 





                                                      


           
                                                            
 


                                                    
                         


           





                                                   





                                                             






                                                            





                                                            













                                                                





                                                              
















                                                                   
                                                          







                                                             

                                                             



                                                             

                                                                









                                                             











                                                                       






                                                                     
                                     


                                                            



                                                              






                                                              
                                         


                                                            




                                                                  


                                                            



                                                                 






                                                                      
                                      


                                                            




                                                               


                                                            




                                                                
                                          






                                                                           

                                                                     


                                                            
                                         
                                             
                                      
                                                                     


                                                            
                                            
                                             
                                      
                                                                        



                                                            


                                                            


                                                              





                                                                       



                                           
                                                     
 

                                       
 

                                             
                                                     



                                                                          

                                              
                                                        










                                                                             

                                    





                                                                   
                                                                        
 
                                               
 
                                                                    
                                             
                                     

                            

                                                    


                                                             
                                                         
                                                                          
                                                                                    

                         
                                         






                                                                    
                                               
 

                                                                     



                                                                 
                                                        




                                                                          
                                                                                
                                                                                    
 
                                                 










                                                           

                                                                               







                                                                                 
                                                                 




                                                                                 
                                                                          

                                    
                                                          
                                                                                            

                                                      
                                                                  
                                                       
 
                                                 




                                              
                                          
                                                           
 



                                                                                
                                                       
                                                 
 


                                       


                                                      




                                                                   



                                                                             
 
                                                                             

                                                               
 


                          

                                                                
 


                                                                           
                                                                  

                                                                 
 



                                                                      
 



                                                                      


                                           




                                                    
                                                       
 












                                                                    
                                                  

                                                                


                                                  

                                      
                                                          
 
                                


                                                         
                                                            



                                                       
                                                           
 
                                                   


           
                                                        

                                   
                      
 

                                                                         
 





                                                                   
                                                             
                                        
                                                          
 

                                                           
 

                                                              
 

                                                            
 

                                                             
 

                                                           
 

                                         










                                                 




                                     
                                      












                                                 







                                         
                                      

                                                 







                                         
                                      












                                                 







                                         
                                      

                                                 







                                         
                                      

                                                 
 


                                          
                                   


                                               
                                                         
                                                          

                                                 








                                         
                                           
                                                 









                                                      

                                                 


                                         
                                       
                                      

                                            

                              


                                                 


                                         



                                     
                             


                                                 

                                             





                                                                   


           



                                                                   
                                                       


           

































                                                                                       


                                                         
 

                                                                               






                                                        
                                                       
                                                  
                                                              



                                                        
                                   

                                                           
                                        

                                                    

                                                       
 
                                                 
                                                   
 
                                                             
 
                                              


           


                                                         
 
                                    
 
                                                                           
 






                                                 



                                                   
                                                  
                                             
                                                         





                                                      

                                    

                                                           
                                        

                                                    






                                                       

                                            

                                                    


                                                                          

                                                                            
 

                                                                    
 





                                                                



                                



                                                          
 
                                   
 



                                                 
 
                                                
 

                                                                      

























                                                                                
                                                   
                           











                                                                      
                                                   

                                                               
                                                          
                                                                
 
                                                          

                             

                                                                    
                                                                  

                                                  
                                                                     
                                                          


                   



                                                                                         
 

                                   
 


                                                                            

                                                           

                                               


                                                                

                                                              
 


                                                                           
                                                                       
                                                               


























                                                        
                                        
                                       
                                 
                                    



                                                     

                                                              
 
                                               
                                                   

                                                                                 
                                                                           










                                                                      
                                                                  
                                         
                                                                      

                                                          

                                                          
                                                                      

                                               

                                                                  






                                                             

                                                                  
                                         
                                                                     



                                                  
                                                                    

                                                              




                                                                             

                                                           
 
                            
                                         

                                    

                                   





                                                            



                                                                         
                                                         

                                                             
                                             
                                                                   
                            

                                                          
                                 
                                                    
                                                                           
                                 

                                                                         
                                 

                                                                          




                                                                   
                                    


                                                                     

                                                                     

 
                          






                                                                       
               
                                                      
                                                     








                                                                             
                                                     
                                                 
 
                              







                                                                             
                                                 
































                                                              
                                                         
                                                   
 
                                   

                                                           
 

                                                       
 
                                               




















































                                                                          












                                                                      
                                                                    

                       


















                                                                      
                                                              

                       


















                                                                      
                                                              

                       












                                                                     


                                                        

                               



                                                                     



                                                             

                                                         
                         

 




                                                           
 
                                                       
 

                                                                 
                       
         
 
                                          
                                                      
 

                                                         










                                                                     


                                                                

                               



                                                                     



                                                                     

                                                         
                         

 






                                                                   



                                                                                 
                       
         
 
                                                  

                                                                      

                                                         


                                                                  
/*
 * e-mail-formatter.c
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 */

#include "e-mail-formatter.h"

#include "e-mail-formatter-extension.h"
#include "e-mail-formatter-utils.h"
#include "e-mail-part.h"

#include <e-util/e-util.h>
#include <libebackend/libebackend.h>
#include <gdk/gdk.h>
#include <glib/gi18n.h>

#include "libemail-engine/e-mail-enumtypes.h"

#define d(x)

#define E_MAIL_FORMATTER_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), E_TYPE_MAIL_FORMATTER, EMailFormatterPrivate))\

#define STYLESHEET_URI \
    "evo-file://" EVOLUTION_PRIVDATADIR "/theme/webview.css"

typedef struct _AsyncContext AsyncContext;

struct _EMailFormatterPrivate {
    EMailImageLoadingPolicy image_loading_policy;

    gboolean show_sender_photo;
    gboolean show_real_date;
    gboolean animate_images;

    GMutex property_lock;

    gchar *charset;
    gchar *default_charset;
};

struct _AsyncContext {
    CamelStream *stream;
    EMailPartList *part_list;
    EMailFormatterHeaderFlags flags;
    EMailFormatterMode mode;
};

/* internal formatter extensions */
GType e_mail_formatter_attachment_get_type (void);
GType e_mail_formatter_attachment_bar_get_type (void);
GType e_mail_formatter_error_get_type (void);
GType e_mail_formatter_headers_get_type (void);
GType e_mail_formatter_image_get_type (void);
GType e_mail_formatter_message_rfc822_get_type (void);
GType e_mail_formatter_secure_button_get_type (void);
GType e_mail_formatter_source_get_type (void);
GType e_mail_formatter_text_enriched_get_type (void);
GType e_mail_formatter_text_html_get_type (void);
GType e_mail_formatter_text_plain_get_type (void);

void e_mail_formatter_internal_extensions_load (EMailExtensionRegistry *ereg);

static gpointer e_mail_formatter_parent_class = 0;

enum {
    PROP_0,
    PROP_ANIMATE_IMAGES,
    PROP_BODY_COLOR,
    PROP_CHARSET,
    PROP_CITATION_COLOR,
    PROP_CONTENT_COLOR,
    PROP_DEFAULT_CHARSET,
    PROP_FRAME_COLOR,
    PROP_HEADER_COLOR,
    PROP_IMAGE_LOADING_POLICY,
    PROP_MARK_CITATIONS,
    PROP_SHOW_REAL_DATE,
    PROP_SHOW_SENDER_PHOTO,
    PROP_TEXT_COLOR
};

enum {
    NEED_REDRAW,
    LAST_SIGNAL
};

static gint signals[LAST_SIGNAL];

static void
async_context_free (AsyncContext *async_context)
{
    g_clear_object (&async_context->part_list);
    g_clear_object (&async_context->stream);

    g_slice_free (AsyncContext, async_context);
}

static EMailFormatterContext *
mail_formatter_create_context (EMailFormatter *formatter,
                               EMailPartList *part_list,
                               EMailFormatterMode mode,
                               EMailFormatterHeaderFlags flags)
{
    EMailFormatterClass *class;
    EMailFormatterContext *context;

    class = E_MAIL_FORMATTER_GET_CLASS (formatter);

    g_warn_if_fail (class->context_size >= sizeof (EMailFormatterContext));

    context = g_malloc0 (class->context_size);
    context->part_list = g_object_ref (part_list);
    context->mode = mode;
    context->flags = flags;

    return context;
}

static void
mail_formatter_free_context (EMailFormatterContext *context)
{
    if (context->part_list != NULL)
        g_object_unref (context->part_list);

    g_free (context);
}

static void
e_mail_formatter_set_property (GObject *object,
                               guint property_id,
                               const GValue *value,
                               GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_ANIMATE_IMAGES:
            e_mail_formatter_set_animate_images (
                E_MAIL_FORMATTER (object),
                g_value_get_boolean (value));
            return;

        case PROP_BODY_COLOR:
            e_mail_formatter_set_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_BODY,
                g_value_get_boxed (value));
            return;

        case PROP_CHARSET:
            e_mail_formatter_set_charset (
                E_MAIL_FORMATTER (object),
                g_value_get_string (value));
            return;

        case PROP_CITATION_COLOR:
            e_mail_formatter_set_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_CITATION,
                g_value_get_boxed (value));
            return;

        case PROP_CONTENT_COLOR:
            e_mail_formatter_set_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_CONTENT,
                g_value_get_boxed (value));
            return;

        case PROP_DEFAULT_CHARSET:
            e_mail_formatter_set_default_charset (
                E_MAIL_FORMATTER (object),
                g_value_get_string (value));
            return;

        case PROP_FRAME_COLOR:
            e_mail_formatter_set_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_FRAME,
                g_value_get_boxed (value));
            return;

        case PROP_HEADER_COLOR:
            e_mail_formatter_set_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_HEADER,
                g_value_get_boxed (value));
            return;

        case PROP_IMAGE_LOADING_POLICY:
            e_mail_formatter_set_image_loading_policy (
                E_MAIL_FORMATTER (object),
                g_value_get_enum (value));
            return;

        case PROP_MARK_CITATIONS:
            e_mail_formatter_set_mark_citations (
                E_MAIL_FORMATTER (object),
                g_value_get_boolean (value));
            return;

        case PROP_SHOW_REAL_DATE:
            e_mail_formatter_set_show_real_date (
                E_MAIL_FORMATTER (object),
                g_value_get_boolean (value));
            return;

        case PROP_SHOW_SENDER_PHOTO:
            e_mail_formatter_set_show_sender_photo (
                E_MAIL_FORMATTER (object),
                g_value_get_boolean (value));
            return;

        case PROP_TEXT_COLOR:
            e_mail_formatter_set_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_TEXT,
                g_value_get_boxed (value));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
e_mail_formatter_get_property (GObject *object,
                             guint property_id,
                             GValue *value,
                             GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_ANIMATE_IMAGES:
            g_value_set_boolean (
                value,
                e_mail_formatter_get_animate_images (
                E_MAIL_FORMATTER (object)));
            return;

        case PROP_BODY_COLOR:
            g_value_set_boxed (
                value,
                e_mail_formatter_get_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_BODY));
            return;

        case PROP_CHARSET:
            g_value_take_string (
                value,
                e_mail_formatter_dup_charset (
                E_MAIL_FORMATTER (object)));
            return;

        case PROP_CITATION_COLOR:
            g_value_set_boxed (
                value,
                e_mail_formatter_get_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_CITATION));
            return;

        case PROP_CONTENT_COLOR:
            g_value_set_boxed (
                value,
                e_mail_formatter_get_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_CONTENT));
            return;

        case PROP_DEFAULT_CHARSET:
            g_value_take_string (
                value,
                e_mail_formatter_dup_default_charset (
                E_MAIL_FORMATTER (object)));
            return;

        case PROP_FRAME_COLOR:
            g_value_set_boxed (
                value,
                e_mail_formatter_get_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_FRAME));
            return;

        case PROP_HEADER_COLOR:
            g_value_set_boxed (
                value,
                e_mail_formatter_get_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_HEADER));
            return;

        case PROP_IMAGE_LOADING_POLICY:
            g_value_set_enum (
                value,
                e_mail_formatter_get_image_loading_policy (
                E_MAIL_FORMATTER (object)));
            return;

        case PROP_MARK_CITATIONS:
            g_value_set_boolean (
                value,
                e_mail_formatter_get_mark_citations (
                E_MAIL_FORMATTER (object)));
            return;

        case PROP_SHOW_REAL_DATE:
            g_value_set_boolean (
                value,
                e_mail_formatter_get_show_real_date (
                E_MAIL_FORMATTER (object)));
            return;

        case PROP_SHOW_SENDER_PHOTO:
            g_value_set_boolean (
                value,
                e_mail_formatter_get_show_sender_photo (
                E_MAIL_FORMATTER (object)));
            return;

        case PROP_TEXT_COLOR:
            g_value_set_boxed (
                value,
                e_mail_formatter_get_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_TEXT));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
e_mail_formatter_finalize (GObject *object)
{
    EMailFormatterPrivate *priv;

    priv = E_MAIL_FORMATTER_GET_PRIVATE (object);

    g_free (priv->charset);
    g_free (priv->default_charset);

    g_mutex_clear (&priv->property_lock);

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

static void
e_mail_formatter_constructed (GObject *object)
{
    /* Chain up to parent's constructed() method. */
    G_OBJECT_CLASS (e_mail_formatter_parent_class)->constructed (object);

    e_extensible_load_extensions (E_EXTENSIBLE (object));
}

static void
mail_formatter_run (EMailFormatter *formatter,
                    EMailFormatterContext *context,
                    CamelStream *stream,
                    GCancellable *cancellable)
{
    GQueue queue = G_QUEUE_INIT;
    GList *head, *link;
    gchar *hdr;

    hdr = e_mail_formatter_get_html_header (formatter);
    camel_stream_write_string (stream, hdr, cancellable, NULL);
    g_free (hdr);

    e_mail_part_list_queue_parts (context->part_list, NULL, &queue);

    head = g_queue_peek_head_link (&queue);

    for (link = head; link != NULL; link = g_list_next (link)) {
        EMailPart *part = link->data;
        const gchar *part_id;
        gboolean ok;

        part_id = e_mail_part_get_id (part);

        if (g_cancellable_is_cancelled (cancellable))
            break;

        if (part->is_hidden && !part->is_error) {
            if (e_mail_part_id_has_suffix (part, ".rfc822")) {
                link = e_mail_formatter_find_rfc822_end_iter (link);
            }

            if (link == NULL)
                break;

            continue;
        }

        /* Force formatting as source if needed */
        if (context->mode != E_MAIL_FORMATTER_MODE_SOURCE) {
            const gchar *mime_type;

            mime_type = e_mail_part_get_mime_type (part);
            if (mime_type == NULL)
                continue;

            ok = e_mail_formatter_format_as (
                formatter, context, part, stream,
                mime_type, cancellable);

            /* If the written part was message/rfc822 then
             * jump to the end of the message, because content
             * of the whole message has been formatted by
             * message_rfc822 formatter */
            if (ok && e_mail_part_id_has_suffix (part, ".rfc822")) {
                link = e_mail_formatter_find_rfc822_end_iter (link);

                if (link == NULL)
                    break;

                continue;
            }

        } else {
            ok = FALSE;
        }

        if (!ok) {
            /* We don't want to source these */
            if (e_mail_part_id_has_suffix (part, ".headers") ||
                e_mail_part_id_has_suffix (part, "attachment-bar"))
                continue;

            e_mail_formatter_format_as (
                formatter, context, part, stream,
                "application/vnd.evolution.source", cancellable);

            /* .message is the entire message. There's nothing more
             * to be written. */
            if (g_strcmp0 (part_id, ".message") == 0)
                break;

            /* If we just wrote source of a rfc822 message, then jump
             * behind the message (otherwise source of all parts
             * would be rendered twice) */
            if (e_mail_part_id_has_suffix (part, ".rfc822")) {

                do {
                    part = link->data;
                    if (e_mail_part_id_has_suffix (part, ".rfc822.end"))
                        break;

                    link = g_list_next (link);
                } while (link != NULL);

                if (link == NULL)
                    break;
            }
        }
    }

    while (!g_queue_is_empty (&queue))
        g_object_unref (g_queue_pop_head (&queue));

    camel_stream_write_string (stream, "</body></html>", cancellable, NULL);
}

static void
mail_formatter_update_style (EMailFormatter *formatter,
                             GtkStateFlags state)
{
    GtkStyleContext *style_context;
    GtkWidgetPath *widget_path;
    GdkRGBA rgba;

    g_object_freeze_notify (G_OBJECT (formatter));

    /* derive colors from top-level window */
    style_context = gtk_style_context_new ();
    widget_path = gtk_widget_path_new ();
    gtk_widget_path_append_type (widget_path, GTK_TYPE_WINDOW);
    gtk_style_context_set_path (style_context, widget_path);
    gtk_style_context_invalidate (style_context);

    gtk_style_context_save (style_context);
    gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_TOOLBAR);

    gtk_style_context_get_background_color (style_context, state, &rgba);
    e_mail_formatter_set_color (
        formatter, E_MAIL_FORMATTER_COLOR_BODY, &rgba);

    rgba.red *= 0.8;
    rgba.green *= 0.8;
    rgba.blue *= 0.8;
    e_mail_formatter_set_color (
        formatter, E_MAIL_FORMATTER_COLOR_FRAME, &rgba);

    gtk_style_context_restore (style_context);
    gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_ENTRY);

    gtk_style_context_get_color (style_context, state, &rgba);
    e_mail_formatter_set_color (
        formatter, E_MAIL_FORMATTER_COLOR_HEADER, &rgba);

    gtk_style_context_get_background_color (
        style_context, state | GTK_STATE_FLAG_FOCUSED, &rgba);
    e_mail_formatter_set_color  (
        formatter, E_MAIL_FORMATTER_COLOR_CONTENT, &rgba);

    gtk_style_context_get_color (
        style_context, state | GTK_STATE_FLAG_FOCUSED, &rgba);
    e_mail_formatter_set_color (
        formatter, E_MAIL_FORMATTER_COLOR_TEXT, &rgba);

    gtk_widget_path_free (widget_path);
    g_object_unref (style_context);

    g_object_thaw_notify (G_OBJECT (formatter));
}

static void
e_mail_formatter_base_init (EMailFormatterClass *class)
{
    /* Register internal extensions. */
    g_type_ensure (e_mail_formatter_attachment_get_type ());
    g_type_ensure (e_mail_formatter_attachment_bar_get_type ());
    g_type_ensure (e_mail_formatter_error_get_type ());
    g_type_ensure (e_mail_formatter_headers_get_type ());
    g_type_ensure (e_mail_formatter_image_get_type ());
    g_type_ensure (e_mail_formatter_message_rfc822_get_type ());
    g_type_ensure (e_mail_formatter_secure_button_get_type ());
    g_type_ensure (e_mail_formatter_source_get_type ());
    g_type_ensure (e_mail_formatter_text_enriched_get_type ());
    g_type_ensure (e_mail_formatter_text_html_get_type ());
    g_type_ensure (e_mail_formatter_text_plain_get_type ());

    class->extension_registry = g_object_new (
        E_TYPE_MAIL_FORMATTER_EXTENSION_REGISTRY, NULL);

    e_mail_formatter_extension_registry_load (
        class->extension_registry,
        E_TYPE_MAIL_FORMATTER_EXTENSION);

    e_extensible_load_extensions (
        E_EXTENSIBLE (class->extension_registry));

    class->text_html_flags =
        CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS |
        CAMEL_MIME_FILTER_TOHTML_CONVERT_NL |
        CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES |
        CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES |
        CAMEL_MIME_FILTER_TOHTML_MARK_CITATION;
}

static void
e_mail_formatter_base_finalize (EMailFormatterClass *class)
{
    g_object_unref (class->extension_registry);
}

static void
e_mail_formatter_class_init (EMailFormatterClass *class)
{
    GObjectClass *object_class;
    GdkRGBA *rgba;

    e_mail_formatter_parent_class = g_type_class_peek_parent (class);
    g_type_class_add_private (class, sizeof (EMailFormatterPrivate));

    object_class = G_OBJECT_CLASS (class);
    object_class->set_property = e_mail_formatter_set_property;
    object_class->get_property = e_mail_formatter_get_property;
    object_class->finalize = e_mail_formatter_finalize;
    object_class->constructed = e_mail_formatter_constructed;

    class->context_size = sizeof (EMailFormatterContext);
    class->run = mail_formatter_run;
    class->update_style = mail_formatter_update_style;

    rgba = &class->colors[E_MAIL_FORMATTER_COLOR_BODY];
    gdk_rgba_parse (rgba, "#eeeeee");

    rgba = &class->colors[E_MAIL_FORMATTER_COLOR_CONTENT];
    gdk_rgba_parse (rgba, "#ffffff");

    rgba = &class->colors[E_MAIL_FORMATTER_COLOR_FRAME];
    gdk_rgba_parse (rgba, "#3f3f3f");

    rgba = &class->colors[E_MAIL_FORMATTER_COLOR_HEADER];
    gdk_rgba_parse (rgba, "#eeeeee");

    rgba = &class->colors[E_MAIL_FORMATTER_COLOR_TEXT];
    gdk_rgba_parse (rgba, "#000000");

    g_object_class_install_property (
        object_class,
        PROP_ANIMATE_IMAGES,
        g_param_spec_boolean (
            "animate-images",
            "Animate images",
            NULL,
            FALSE,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_BODY_COLOR,
        g_param_spec_boxed (
            "body-color",
            "Body Color",
            NULL,
            GDK_TYPE_RGBA,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_CHARSET,
        g_param_spec_string (
            "charset",
            NULL,
            NULL,
            NULL,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_CITATION_COLOR,
        g_param_spec_boxed (
            "citation-color",
            "Citation Color",
            NULL,
            GDK_TYPE_RGBA,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_CONTENT_COLOR,
        g_param_spec_boxed (
            "content-color",
            "Content Color",
            NULL,
            GDK_TYPE_RGBA,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_DEFAULT_CHARSET,
        g_param_spec_string (
            "default-charset",
            NULL,
            NULL,
            NULL,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_FRAME_COLOR,
        g_param_spec_boxed (
            "frame-color",
            "Frame Color",
            NULL,
            GDK_TYPE_RGBA,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_HEADER_COLOR,
        g_param_spec_boxed (
            "header-color",
            "Header Color",
            NULL,
            GDK_TYPE_RGBA,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_IMAGE_LOADING_POLICY,
        g_param_spec_enum (
            "image-loading-policy",
            "Image Loading Policy",
            NULL,
            E_TYPE_MAIL_IMAGE_LOADING_POLICY,
            E_MAIL_IMAGE_LOADING_POLICY_NEVER,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_MARK_CITATIONS,
        g_param_spec_boolean (
            "mark-citations",
            "Mark Citations",
            NULL,
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_SHOW_REAL_DATE,
        g_param_spec_boolean (
            "show-real-date",
            "Show real Date header value",
            NULL,
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_SHOW_SENDER_PHOTO,
        g_param_spec_boolean (
            "show-sender-photo",
            "Show Sender Photo",
            NULL,
            FALSE,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_TEXT_COLOR,
        g_param_spec_boxed (
            "text-color",
            "Text Color",
            NULL,
            GDK_TYPE_COLOR,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    signals[NEED_REDRAW] = g_signal_new (
        "need-redraw",
        E_TYPE_MAIL_FORMATTER,
        G_SIGNAL_RUN_FIRST,
        G_STRUCT_OFFSET (EMailFormatterClass, need_redraw),
        NULL, NULL, NULL,
        G_TYPE_NONE, 0);
}

static void
e_mail_formatter_init (EMailFormatter *formatter)
{
    formatter->priv = E_MAIL_FORMATTER_GET_PRIVATE (formatter);

    g_mutex_init (&formatter->priv->property_lock);
}

static void
e_mail_formatter_extensible_interface_init (EExtensibleInterface *interface)
{

}

EMailFormatter *
e_mail_formatter_new (void)
{
    return g_object_new (E_TYPE_MAIL_FORMATTER, NULL);
}

GType
e_mail_formatter_get_type (void)
{
    static GType type = 0;

    if (G_UNLIKELY (type == 0)) {
        const GTypeInfo type_info = {
            sizeof (EMailFormatterClass),
            (GBaseInitFunc) e_mail_formatter_base_init,
            (GBaseFinalizeFunc) e_mail_formatter_base_finalize,
            (GClassInitFunc) e_mail_formatter_class_init,
            (GClassFinalizeFunc) NULL,
            NULL,   /* class_data */
            sizeof (EMailFormatter),
            0,  /* n_preallocs */
            (GInstanceInitFunc) e_mail_formatter_init,
            NULL    /* value_table */
        };

        const GInterfaceInfo e_extensible_interface_info = {
            (GInterfaceInitFunc) e_mail_formatter_extensible_interface_init
        };

        type = g_type_register_static (
            G_TYPE_OBJECT,
            "EMailFormatter", &type_info, 0);

        g_type_add_interface_static (
            type, E_TYPE_EXTENSIBLE, &e_extensible_interface_info);
    }

    return type;
}

void
e_mail_formatter_format_sync (EMailFormatter *formatter,
                              EMailPartList *part_list,
                              CamelStream *stream,
                              EMailFormatterHeaderFlags flags,
                              EMailFormatterMode mode,
                              GCancellable *cancellable)
{
    EMailFormatterContext *context;
    EMailFormatterClass *class;

    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));
    /* EMailPartList can be NULL. */
    g_return_if_fail (CAMEL_IS_STREAM (stream));

    class = E_MAIL_FORMATTER_GET_CLASS (formatter);
    g_return_if_fail (class->run != NULL);

    context = mail_formatter_create_context (
        formatter, part_list, mode, flags);

    class->run (formatter, context, stream, cancellable);

    mail_formatter_free_context (context);
}

static void
mail_formatter_format_thread (GSimpleAsyncResult *simple,
                              GObject *source_object,
                              GCancellable *cancellable)
{
    AsyncContext *async_context;

    async_context = g_simple_async_result_get_op_res_gpointer (simple);

    e_mail_formatter_format_sync (
        E_MAIL_FORMATTER (source_object),
        async_context->part_list,
        async_context->stream,
        async_context->flags,
        async_context->mode,
        cancellable);
}

void
e_mail_formatter_format (EMailFormatter *formatter,
                         EMailPartList *part_list,
                         CamelStream *stream,
                         EMailFormatterHeaderFlags flags,
                         EMailFormatterMode mode,
                         GAsyncReadyCallback callback,
                         GCancellable *cancellable,
                         gpointer user_data)
{
    GSimpleAsyncResult *simple;
    AsyncContext *async_context;
    EMailFormatterClass *class;

    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));
    /* EMailPartList can be NULL. */
    g_return_if_fail (CAMEL_IS_STREAM (stream));

    class = E_MAIL_FORMATTER_GET_CLASS (formatter);
    g_return_if_fail (class->run != NULL);

    async_context = g_slice_new0 (AsyncContext);
    async_context->stream = g_object_ref (stream);
    async_context->flags = flags;
    async_context->mode = mode;

    simple = g_simple_async_result_new (
        G_OBJECT (formatter), callback,
        user_data, e_mail_formatter_format);

    g_simple_async_result_set_check_cancellable (simple, cancellable);

    g_simple_async_result_set_op_res_gpointer (
        simple, async_context, (GDestroyNotify) async_context_free);

    if (part_list != NULL) {
        async_context->part_list = g_object_ref (part_list);

        g_simple_async_result_run_in_thread (
            simple, mail_formatter_format_thread,
            G_PRIORITY_DEFAULT, cancellable);
    } else {
        g_simple_async_result_complete_in_idle (simple);
    }

    g_object_unref (simple);
}

gboolean
e_mail_formatter_format_finish (EMailFormatter *formatter,
                                GAsyncResult *result,
                                GError **error)
{
    GSimpleAsyncResult *simple;

    g_return_val_if_fail (
        g_simple_async_result_is_valid (
        result, G_OBJECT (formatter),
        e_mail_formatter_format), FALSE);

    simple = G_SIMPLE_ASYNC_RESULT (result);

    /* Assume success unless a GError is set. */
    return !g_simple_async_result_propagate_error (simple, error);
}

/**
 * e_mail_formatter_format_as:
 * @formatter: an #EMailFormatter
 * @context: an #EMailFormatterContext
 * @part: an #EMailPart
 * @stream: a #CamelStream
 * @as_mime_type: (allow-none) mime-type to use for formatting, or %NULL
 * @cancellable: (allow-none) an optional #GCancellable
 *
 * Formats given @part using a @formatter extension for given mime type. When
 * the mime type is %NULL, the function will try to lookup the best formatter
 * for given @part by it's default mime type.
 *
 * Return Value: %TRUE on success, %FALSE when no suitable formatter is found or
 * when it fails to format the part. 
 */
gboolean
e_mail_formatter_format_as (EMailFormatter *formatter,
                            EMailFormatterContext *context,
                            EMailPart *part,
                            CamelStream *stream,
                            const gchar *as_mime_type,
                            GCancellable *cancellable)
{
    EMailExtensionRegistry *extension_registry;
    GQueue *formatters;
    gboolean ok;
    d (
        gint _call_i;
        static gint _call = 0;
        G_LOCK_DEFINE_STATIC (_call);
        G_LOCK (_call);
        _call++;
        _call_i = _call;
        G_UNLOCK (_call)
    );

    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), FALSE);
    g_return_val_if_fail (part != NULL, FALSE);
    g_return_val_if_fail (CAMEL_IS_STREAM (stream), FALSE);

    if (as_mime_type == NULL || *as_mime_type == '\0')
        as_mime_type = e_mail_part_get_mime_type (part);

    if (as_mime_type == NULL || *as_mime_type == '\0')
        return FALSE;

    extension_registry =
        e_mail_formatter_get_extension_registry (formatter);
    formatters = e_mail_extension_registry_get_for_mime_type (
        extension_registry, as_mime_type);
    if (formatters == NULL)
        formatters = e_mail_extension_registry_get_fallback (
            extension_registry, as_mime_type);

    ok = FALSE;

    d (
        printf ("(%d) Formatting for part %s of type %s (found %d formatters)\n",
        _call_i, part->id, as_mime_type,
        formatters ? g_queue_get_length (formatters) : 0));

    if (formatters != NULL) {
        GList *head, *link;

        head = g_queue_peek_head_link (formatters);

        for (link = head; link != NULL; link = g_list_next (link)) {
            EMailFormatterExtension *extension;

            extension = link->data;
            if (extension == NULL)
                continue;

            ok = e_mail_formatter_extension_format (
                extension, formatter, context,
                part, stream, cancellable);

            d (
                printf (
                    "\t(%d) trying %s...%s\n", _call_i,
                    G_OBJECT_TYPE_NAME (extension),
                    ok ? "OK" : "failed"));

            if (ok)
                break;
        }
    }

    return ok;
}

/**
 * em_format_format_text:
 * @part: an #EMailPart to decode
 * @formatter: an #EMailFormatter
 * @stream: Where to write the converted text
 * @cancellable: optional #GCancellable object, or %NULL
 *
 * Decode/output a part's content to @stream.
 **/
void
e_mail_formatter_format_text (EMailFormatter *formatter,
                              EMailPart *part,
                              CamelStream *stream,
                              GCancellable *cancellable)
{
    CamelStream *filter_stream;
    CamelMimeFilter *filter;
    const gchar *charset = NULL;
    CamelMimeFilter *windows = NULL;
    CamelStream *mem_stream = NULL;
    CamelMimePart *mime_part;
    CamelContentType *mime_type;

    if (g_cancellable_is_cancelled (cancellable))
        return;

    mime_part = e_mail_part_ref_mime_part (part);
    mime_type = CAMEL_DATA_WRAPPER (mime_part)->mime_type;

    if (formatter->priv->charset != NULL) {
        charset = formatter->priv->charset;
    } else if (mime_type != NULL
           && (charset = camel_content_type_param (mime_type, "charset"))
           && g_ascii_strncasecmp (charset, "iso-8859-", 9) == 0) {
        CamelStream *null;

        /* Since a few Windows mailers like to claim they sent
         * out iso-8859-# encoded text when they really sent
         * out windows-cp125#, do some simple sanity checking
         * before we move on... */

        null = camel_stream_null_new ();
        filter_stream = camel_stream_filter_new (null);
        g_object_unref (null);

        windows = camel_mime_filter_windows_new (charset);
        camel_stream_filter_add (
            CAMEL_STREAM_FILTER (filter_stream), windows);

        camel_data_wrapper_decode_to_stream_sync (
            CAMEL_DATA_WRAPPER (mime_part),
            filter_stream, cancellable, NULL);
        camel_stream_flush (filter_stream, cancellable, NULL);
        g_object_unref (filter_stream);

        charset = camel_mime_filter_windows_real_charset (
            CAMEL_MIME_FILTER_WINDOWS (windows));
    } else if (charset == NULL) {
        charset = formatter->priv->default_charset;
    }

    mem_stream = (CamelStream *) camel_stream_mem_new ();
    filter_stream = camel_stream_filter_new (mem_stream);

    filter = camel_mime_filter_charset_new (charset, "UTF-8");
    if (filter != NULL) {
        camel_stream_filter_add (
            CAMEL_STREAM_FILTER (filter_stream), filter);
        g_object_unref (filter);
    }

    camel_data_wrapper_decode_to_stream_sync (
        camel_medium_get_content (CAMEL_MEDIUM (mime_part)),
        filter_stream, cancellable, NULL);
    camel_stream_flush (filter_stream, cancellable, NULL);
    g_object_unref (filter_stream);

    g_seekable_seek (G_SEEKABLE (mem_stream), 0, G_SEEK_SET, NULL, NULL);

    camel_stream_write_to_stream (
        mem_stream, stream, cancellable, NULL);
    camel_stream_flush (mem_stream, cancellable, NULL);

    if (windows != NULL)
        g_object_unref (windows);

    g_object_unref (mem_stream);

    g_object_unref (mime_part);
}

gchar *
e_mail_formatter_get_html_header (EMailFormatter *formatter)
{
    return g_strdup_printf (
        "<!DOCTYPE HTML>\n"
        "<html>\n"
        "<head>\n"
        "<meta name=\"generator\" content=\"Evolution Mail\"/>\n"
        "<title>Evolution Mail Display</title>\n"
        "<link type=\"text/css\" rel=\"stylesheet\" "
        "      href=\"" STYLESHEET_URI "\"/>\n"
        "<style type=\"text/css\">\n"
        "  table th { color: #%06x; font-weight: bold; }\n"
        "</style>\n"
        "</head>"
        "<body bgcolor=\"#%06x\" text=\"#%06x\">",
        e_rgba_to_value (
            e_mail_formatter_get_color (
                formatter, E_MAIL_FORMATTER_COLOR_HEADER)),
        e_rgba_to_value (
            e_mail_formatter_get_color (
                formatter, E_MAIL_FORMATTER_COLOR_BODY)),
        e_rgba_to_value (
            e_mail_formatter_get_color (
                formatter, E_MAIL_FORMATTER_COLOR_TEXT)));
}

EMailExtensionRegistry *
e_mail_formatter_get_extension_registry (EMailFormatter *formatter)
{
    EMailFormatterClass * class;

    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), NULL);

    class = E_MAIL_FORMATTER_GET_CLASS (formatter);
    return E_MAIL_EXTENSION_REGISTRY (class->extension_registry);
}

CamelMimeFilterToHTMLFlags
e_mail_formatter_get_text_format_flags (EMailFormatter *formatter)
{
    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), 0);

    return E_MAIL_FORMATTER_GET_CLASS (formatter)->text_html_flags;
}

const GdkRGBA *
e_mail_formatter_get_color (EMailFormatter *formatter,
                            EMailFormatterColor type)
{
    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), NULL);
    g_return_val_if_fail (type < E_MAIL_FORMATTER_NUM_COLOR_TYPES, NULL);

    return &E_MAIL_FORMATTER_GET_CLASS (formatter)->colors[type];
}

void
e_mail_formatter_set_color (EMailFormatter *formatter,
                            EMailFormatterColor type,
                            const GdkRGBA *color)
{
    GdkRGBA *format_color;
    const gchar *property_name;

    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));
    g_return_if_fail (type < E_MAIL_FORMATTER_NUM_COLOR_TYPES);
    g_return_if_fail (color != NULL);

    format_color = &E_MAIL_FORMATTER_GET_CLASS (formatter)->colors[type];

    if (gdk_rgba_equal (color, format_color))
        return;

    format_color->red   = color->red;
    format_color->green = color->green;
    format_color->blue  = color->blue;

    switch (type) {
        case E_MAIL_FORMATTER_COLOR_BODY:
            property_name = "body-color";
            break;
        case E_MAIL_FORMATTER_COLOR_CITATION:
            property_name = "citation-color";
            break;
        case E_MAIL_FORMATTER_COLOR_CONTENT:
            property_name = "content-color";
            break;
        case E_MAIL_FORMATTER_COLOR_FRAME:
            property_name = "frame-color";
            break;
        case E_MAIL_FORMATTER_COLOR_HEADER:
            property_name = "header-color";
            break;
        case E_MAIL_FORMATTER_COLOR_TEXT:
            property_name = "text-color";
            break;
        default:
            g_return_if_reached ();
    }

    g_object_notify (G_OBJECT (formatter), property_name);
}

void
e_mail_formatter_update_style (EMailFormatter *formatter,
                               GtkStateFlags state)
{
    EMailFormatterClass *class;

    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));

    class = E_MAIL_FORMATTER_GET_CLASS (formatter);
    g_return_if_fail (class->update_style != NULL);

    class->update_style (formatter, state);
}

EMailImageLoadingPolicy
e_mail_formatter_get_image_loading_policy (EMailFormatter *formatter)
{
    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), 0);

    return formatter->priv->image_loading_policy;
}

void
e_mail_formatter_set_image_loading_policy (EMailFormatter *formatter,
                                           EMailImageLoadingPolicy policy)
{
    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));

    if (policy == formatter->priv->image_loading_policy)
        return;

    formatter->priv->image_loading_policy = policy;

    g_object_notify (G_OBJECT (formatter), "image-loading-policy");
}

gboolean
e_mail_formatter_get_mark_citations (EMailFormatter *formatter)
{
    guint32 flags;

    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), FALSE);

    flags = E_MAIL_FORMATTER_GET_CLASS (formatter)->text_html_flags;

    return ((flags & CAMEL_MIME_FILTER_TOHTML_MARK_CITATION) != 0);
}

void
e_mail_formatter_set_mark_citations (EMailFormatter *formatter,
                                     gboolean mark_citations)
{
    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));

    if (mark_citations)
        E_MAIL_FORMATTER_GET_CLASS (formatter)->text_html_flags |=
            CAMEL_MIME_FILTER_TOHTML_MARK_CITATION;
    else
        E_MAIL_FORMATTER_GET_CLASS (formatter)->text_html_flags &=
            ~CAMEL_MIME_FILTER_TOHTML_MARK_CITATION;

    g_object_notify (G_OBJECT (formatter), "mark-citations");
}

gboolean
e_mail_formatter_get_show_sender_photo (EMailFormatter *formatter)
{
    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), FALSE);

    return formatter->priv->show_sender_photo;
}

void
e_mail_formatter_set_show_sender_photo (EMailFormatter *formatter,
                                        gboolean show_sender_photo)
{
    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));

    if (formatter->priv->show_sender_photo == show_sender_photo)
        return;

    formatter->priv->show_sender_photo = show_sender_photo;

    g_object_notify (G_OBJECT (formatter), "show-sender-photo");
}

gboolean
e_mail_formatter_get_show_real_date (EMailFormatter *formatter)
{
    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), FALSE);

    return formatter->priv->show_real_date;
}

void
e_mail_formatter_set_show_real_date (EMailFormatter *formatter,
                                     gboolean show_real_date)
{
    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));

    if (formatter->priv->show_real_date == show_real_date)
        return;

    formatter->priv->show_real_date = show_real_date;

    g_object_notify (G_OBJECT (formatter), "show-real-date");
}

gboolean
e_mail_formatter_get_animate_images (EMailFormatter *formatter)
{
    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), FALSE);

    return formatter->priv->animate_images;
}

void
e_mail_formatter_set_animate_images (EMailFormatter *formatter,
                                     gboolean animate_images)
{
    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));

    if (formatter->priv->animate_images == animate_images)
        return;

    formatter->priv->animate_images = animate_images;

    g_object_notify (G_OBJECT (formatter), "animate-images");
}

const gchar *
e_mail_formatter_get_charset (EMailFormatter *formatter)
{
    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), NULL);

    return formatter->priv->charset;
}

gchar *
e_mail_formatter_dup_charset (EMailFormatter *formatter)
{
    const gchar *protected;
    gchar *duplicate;

    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), NULL);

    g_mutex_lock (&formatter->priv->property_lock);

    protected = e_mail_formatter_get_charset (formatter);
    duplicate = g_strdup (protected);

    g_mutex_unlock (&formatter->priv->property_lock);

    return duplicate;
}

void
e_mail_formatter_set_charset (EMailFormatter *formatter,
                              const gchar *charset)
{
    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));

    g_mutex_lock (&formatter->priv->property_lock);

    if (g_strcmp0 (formatter->priv->charset, charset) == 0) {
        g_mutex_unlock (&formatter->priv->property_lock);
        return;
    }

    g_free (formatter->priv->charset);
    formatter->priv->charset = g_strdup (charset);

    g_mutex_unlock (&formatter->priv->property_lock);

    g_object_notify (G_OBJECT (formatter), "charset");
}

const gchar *
e_mail_formatter_get_default_charset (EMailFormatter *formatter)
{
    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), NULL);

    return formatter->priv->default_charset;
}

gchar *
e_mail_formatter_dup_default_charset (EMailFormatter *formatter)
{
    const gchar *protected;
    gchar *duplicate;

    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), NULL);

    g_mutex_lock (&formatter->priv->property_lock);

    protected = e_mail_formatter_get_default_charset (formatter);
    duplicate = g_strdup (protected);

    g_mutex_unlock (&formatter->priv->property_lock);

    return duplicate;
}

void
e_mail_formatter_set_default_charset (EMailFormatter *formatter,
                                      const gchar *default_charset)
{
    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));
    g_return_if_fail (default_charset && *default_charset);

    g_mutex_lock (&formatter->priv->property_lock);

    if (g_strcmp0 (formatter->priv->default_charset, default_charset) == 0) {
        g_mutex_unlock (&formatter->priv->property_lock);
        return;
    }

    g_free (formatter->priv->default_charset);
    formatter->priv->default_charset = g_strdup (default_charset);

    g_mutex_unlock (&formatter->priv->property_lock);

    g_object_notify (G_OBJECT (formatter), "default-charset");
}