aboutsummaryrefslogblamecommitdiffstats
path: root/em-format/e-mail-formatter.c
blob: dc3cb896942abeebc7aab7220217c092294f1951 (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>

#define d(x)

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

struct _EMailFormatterPrivate {
    EMailImageLoadingPolicy image_loading_policy;

    guint show_sender_photo : 1;
    guint show_real_date    : 1;
        guint animate_images    : 1;

    gchar *charset;
    gchar *default_charset;

    GQueue *header_list;
};

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

static gpointer e_mail_formatter_parent_class = 0;

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

enum {
    NEED_REDRAW,
    LAST_SIGNAL
};

static gint signals[LAST_SIGNAL];

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_BODY_COLOR:
            e_mail_formatter_set_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_BODY,
                g_value_get_boxed (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_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_int (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_SENDER_PHOTO:
            e_mail_formatter_set_show_sender_photo (
                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_TEXT_COLOR:
            e_mail_formatter_set_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_TEXT,
                g_value_get_boxed (value));
            return;

        case PROP_ANIMATE_IMAGES:
            e_mail_formatter_set_animate_images (
                E_MAIL_FORMATTER (object),
                g_value_get_boolean (value));
            return;

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

        case PROP_DEFAULT_CHARSET:
            e_mail_formatter_set_default_charset (
                E_MAIL_FORMATTER (object),
                g_value_get_string (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_BODY_COLOR:
            g_value_set_boxed (
                value,
                e_mail_formatter_get_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_BODY));
            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_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_int (
                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_SENDER_PHOTO:
            g_value_set_boolean (
                value,
                e_mail_formatter_get_show_sender_photo (
                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_TEXT_COLOR:
            g_value_set_boxed (
                value,
                e_mail_formatter_get_color (
                E_MAIL_FORMATTER (object),
                E_MAIL_FORMATTER_COLOR_TEXT));
            return;

        case PROP_ANIMATE_IMAGES:
            g_value_set_boolean (
                value,
                e_mail_formatter_get_animate_images (
                E_MAIL_FORMATTER (object)));
            return;

        case PROP_CHARSET:
            g_value_set_string (
                value,
                e_mail_formatter_get_charset (
                E_MAIL_FORMATTER (object)));
            return;

        case PROP_DEFAULT_CHARSET:
            g_value_set_string (
                value,
                e_mail_formatter_get_default_charset (
                E_MAIL_FORMATTER (object)));
            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 (object)->priv;

    if (priv->charset) {
        g_free (priv->charset);
        priv->charset = NULL;
    }

    if (priv->default_charset) {
        g_free (priv->default_charset);
        priv->default_charset = NULL;
    }

    if (priv->header_list) {
        e_mail_formatter_clear_headers (E_MAIL_FORMATTER (object));
        g_queue_free (priv->header_list);
        priv->header_list = NULL;
    }

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

static void
e_mail_formatter_constructed (GObject *object)
{
    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;
        gboolean ok;

        if (g_cancellable_is_cancelled (cancellable))
            break;

        if (part->is_hidden && !part->is_error) {
            if (g_str_has_suffix (part->id, ".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) {

            if (!part->mime_type)
                continue;

            ok = e_mail_formatter_format_as (
                formatter, context, part, stream,
                part->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 && g_str_has_suffix (part->id, ".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 (g_str_has_suffix (part->id, ".headers") ||
                g_str_has_suffix (part->id, "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 (g_str_has_suffix (part->id, ".rfc822")) {

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

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

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

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

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

static void
mail_formatter_set_style (EMailFormatter *formatter,
                          GtkStyle *style,
                          GtkStateType state)
{
    GdkColor *color;
    EMailFormatterColorType type;

    g_object_freeze_notify (G_OBJECT (formatter));

    color = &style->bg[state];
    type = E_MAIL_FORMATTER_COLOR_BODY;
    e_mail_formatter_set_color (formatter, type, color);

    color = &style->base[GTK_STATE_NORMAL];
    type = E_MAIL_FORMATTER_COLOR_CONTENT;
    e_mail_formatter_set_color  (formatter, type, color);

    color = &style->dark[state];
    type = E_MAIL_FORMATTER_COLOR_FRAME;
    e_mail_formatter_set_color  (formatter, type, color);

    color = &style->fg[state];
    type = E_MAIL_FORMATTER_COLOR_HEADER;
    e_mail_formatter_set_color  (formatter, type, color);

    color = &style->text[state];
    type = E_MAIL_FORMATTER_COLOR_TEXT;
    e_mail_formatter_set_color  (formatter, type, color);

    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;
    GdkColor *color;

    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->set_style = mail_formatter_set_style;

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

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

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

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

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

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

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

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

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

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

    /* FIXME Make this a proper enum property. */
    g_object_class_install_property (
        object_class,
        PROP_IMAGE_LOADING_POLICY,
        g_param_spec_int (
            "image-loading-policy",
            "Image Loading Policy",
            NULL,
            E_MAIL_IMAGE_LOADING_POLICY_NEVER,
            E_MAIL_IMAGE_LOADING_POLICY_ALWAYS,
            E_MAIL_IMAGE_LOADING_POLICY_NEVER,
            G_PARAM_READWRITE));

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

    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_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_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_object_class_install_property (
        object_class,
        PROP_ANIMATE_IMAGES,
        g_param_spec_boolean (
            "animate-images",
            "Animate images",
            NULL,
            FALSE,
            G_PARAM_READWRITE));

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

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

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

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

    formatter->priv->header_list = g_queue_new ();
    e_mail_formatter_set_default_headers (formatter);
}

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 *parts,
                              CamelStream *stream,
                              EMailFormatterHeaderFlags flags,
                              EMailFormatterMode mode,
                              GCancellable *cancellable)
{
    EMailFormatterContext *context;
    EMailFormatterClass *formatter_class;

    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));
    g_return_if_fail (CAMEL_IS_STREAM (stream));

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

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

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

    mail_formatter_free_context (context);
}

static void
mail_format_async_prepare (GSimpleAsyncResult *result,
                           GObject *object,
                           GCancellable *cancellable)
{
    EMailFormatterContext *context;
    EMailFormatterClass *formatter_class;
    CamelStream *stream;

    context = g_object_get_data (G_OBJECT (result), "context");
    stream = g_object_get_data (G_OBJECT (result), "stream");

    formatter_class = E_MAIL_FORMATTER_GET_CLASS (object);
    formatter_class->run (
        E_MAIL_FORMATTER (object), context, stream, cancellable);
}

void
e_mail_formatter_format (EMailFormatter *formatter,
                         EMailPartList *parts,
                         CamelStream *stream,
                         EMailFormatterHeaderFlags flags,
                         EMailFormatterMode mode,
                         GAsyncReadyCallback callback,
                         GCancellable *cancellable,
                         gpointer user_data)
{
    GSimpleAsyncResult *simple;
    EMailFormatterContext *context;
    EMailFormatterClass *formatter_class;

    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));
    g_return_if_fail (CAMEL_IS_STREAM (stream));

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

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

    if (!parts) {
        if (callback)
            callback (G_OBJECT (formatter), G_ASYNC_RESULT (simple), user_data);
        g_object_unref (simple);
        return;
    }

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

    g_object_set_data (G_OBJECT (simple), "context", context);
    g_object_set_data (G_OBJECT (simple), "stream", stream);

    g_simple_async_result_run_in_thread (
        simple, mail_format_async_prepare,
        G_PRIORITY_DEFAULT, cancellable);

    g_object_unref (simple);
}

CamelStream *
e_mail_formatter_format_finished (EMailFormatter *formatter,
                                  GAsyncResult *result,
                                  GError *error)
{
    EMailFormatterContext *context;

    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), NULL);
    g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);

    context = g_object_get_data (G_OBJECT (result), "context");

    mail_formatter_free_context (context);

    return g_object_get_data (G_OBJECT (result), "stream");
}

/**
 * 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 *reg;
    GQueue *formatters;
    GList *iter;
    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, FALSE);
    g_return_val_if_fail (CAMEL_IS_STREAM (stream), FALSE);

    if (!as_mime_type || !*as_mime_type)
        as_mime_type = part->mime_type;

    if (!as_mime_type || !*as_mime_type)
        return FALSE;

    reg = e_mail_formatter_get_extension_registry (formatter);
    formatters = e_mail_extension_registry_get_for_mime_type (
            reg, as_mime_type);
    if (!formatters) {
        formatters = e_mail_extension_registry_get_fallback (
            reg, 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) {
        for (iter = formatters->head; iter; iter = iter->next) {

            EMailFormatterExtension *extension;

            extension = iter->data;
            if (!extension)
                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;
    CamelMimeFilterWindows *windows = NULL;
    CamelStream *mem_stream = NULL;
    CamelDataWrapper *dw;

    if (g_cancellable_is_cancelled (cancellable))
        return;

    dw = CAMEL_DATA_WRAPPER (part->part);

    if (formatter->priv->charset) {
        charset = formatter->priv->charset;
    } else if (dw->mime_type
           && (charset = camel_content_type_param (dw->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 = (CamelMimeFilterWindows *) camel_mime_filter_windows_new (charset);
        camel_stream_filter_add (
            CAMEL_STREAM_FILTER (filter_stream),
            CAMEL_MIME_FILTER (windows));

        camel_data_wrapper_decode_to_stream_sync (
            dw, (CamelStream *) filter_stream, cancellable, NULL);
        camel_stream_flush ((CamelStream *) filter_stream, cancellable, NULL);
        g_object_unref (filter_stream);

        charset = camel_mime_filter_windows_real_charset (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);

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

    camel_data_wrapper_decode_to_stream_sync (
            camel_medium_get_content ((CamelMedium *) dw),
            (CamelStream *) filter_stream, cancellable, NULL);
    camel_stream_flush ((CamelStream *) 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, (CamelStream *) stream, cancellable, NULL);
    camel_stream_flush ((CamelStream *) mem_stream, cancellable, NULL);

    if (windows) {
        g_object_unref (windows);
    }

    g_object_unref (mem_stream);
}

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 Component\" />\n"
        "<title>Evolution Mail Display</title>\n"
        "<link type=\"text/css\" rel=\"stylesheet\" href=\"evo-file://" EVOLUTION_PRIVDATADIR "/theme/webview.css\" />\n"
        "<style type=\"text/css\">\n"
        "  table th { color: #%06x; font-weight: bold; }\n"
        "</style>\n"
        "</head><body bgcolor=\"#%06x\" text=\"#%06x\">",
        e_color_to_value ((GdkColor *)
            e_mail_formatter_get_color (
                formatter, E_MAIL_FORMATTER_COLOR_HEADER)),
        e_color_to_value ((GdkColor *)
            e_mail_formatter_get_color (
                formatter, E_MAIL_FORMATTER_COLOR_BODY)),
        e_color_to_value ((GdkColor *)
            e_mail_formatter_get_color (
                formatter, E_MAIL_FORMATTER_COLOR_TEXT)));
}

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

    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), NULL);

    formatter_class = E_MAIL_FORMATTER_GET_CLASS (formatter);
    return E_MAIL_EXTENSION_REGISTRY (formatter_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 GdkColor *
e_mail_formatter_get_color (EMailFormatter *formatter,
                            EMailFormatterColorType 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,
                            EMailFormatterColorType type,
                            const GdkColor *color)
{
    GdkColor *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_color_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_set_style (EMailFormatter *formatter,
                            GtkStyle *style,
                            GtkStateType state)
{
    EMailFormatterClass *formatter_class;

    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));
    g_return_if_fail (GTK_IS_STYLE (style));

    formatter_class = E_MAIL_FORMATTER_GET_CLASS (formatter);
    g_return_if_fail (formatter_class->set_style != NULL);

    formatter_class->set_style (formatter, style, 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;
}

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

    if (g_strcmp0 (formatter->priv->charset, charset) == 0)
        return;

    g_free (formatter->priv->charset);

    if (!charset) {
        formatter->priv->charset = NULL;
    } else {
        formatter->priv->charset = g_strdup (charset);
    }

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

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

    if (g_strcmp0 (formatter->priv->default_charset, default_charset) == 0)
        return;

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

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

/* note: also copied in em-mailer-prefs.c */
static const struct {
    const gchar *name;
    guint32 flags;
} default_headers[] = {
    { N_("From"), E_MAIL_FORMATTER_HEADER_FLAG_BOLD },
    { N_("Reply-To"), E_MAIL_FORMATTER_HEADER_FLAG_BOLD },
    { N_("To"), E_MAIL_FORMATTER_HEADER_FLAG_BOLD },
    { N_("Cc"), E_MAIL_FORMATTER_HEADER_FLAG_BOLD },
    { N_("Bcc"), E_MAIL_FORMATTER_HEADER_FLAG_BOLD },
    { N_("Subject"), E_MAIL_FORMATTER_HEADER_FLAG_BOLD },
    { N_("Date"), E_MAIL_FORMATTER_HEADER_FLAG_BOLD },
    { N_("Newsgroups"), E_MAIL_FORMATTER_HEADER_FLAG_BOLD },
    { N_("Face"), 0 },
};

/**
 * e_mail_formatter_get_headers:
 * @formatter: an #EMailFormatter
 *
 * Returns list of currently set headers.
 *
 * Return Value: A #GQueue of headers which you should not modify or unref
 */
const GQueue *
e_mail_formatter_get_headers (EMailFormatter *formatter)
{
    g_return_val_if_fail (E_IS_MAIL_FORMATTER (formatter), NULL);

    return formatter->priv->header_list;
}

/**
 * e_mail_formatter_clear_headers:
 * @formatter: an #EMailFormatter
 *
 * Clear the list of headers to be displayed.  This will force all headers to
 * be shown.
 **/
void
e_mail_formatter_clear_headers (EMailFormatter *formatter)
{
    EMailFormatterHeader *header;

    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));

    while ((header = g_queue_pop_head (formatter->priv->header_list)) != NULL) {
        e_mail_formatter_header_free (header);
    }
}

/**
 * e_mail_formatter_set_default_headers:
 * @formatter: an #EMailFormatter
 *
 * Clear the list of headers and sets the default ones, e.g. "To", "From", "Cc"
 * "Subject", etc...
 */
void
e_mail_formatter_set_default_headers (EMailFormatter *formatter)
{
    gint ii;

    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));

    /* Set the default headers */
    e_mail_formatter_clear_headers (formatter);
    for (ii = 0; ii < G_N_ELEMENTS (default_headers); ii++) {
        e_mail_formatter_add_header (
            formatter, default_headers[ii].name, NULL,
            default_headers[ii].flags);
    }
}

/**
 * e_mail_formatter_add_header:
 * @formatter:
 * @name: The name of the header, as it will appear during output.
 * @value: Value of the header. Can be %NULL.
 * @flags: a set of #EMailFormatterHeaderFlags to control display attributes.
 *
 * Add a specific header to show.  If any headers are set, they will
 * be displayed in the order set by this function.  Certain known
 * headers included in this list will be shown using special
 * formatting routines.
 **/
void
e_mail_formatter_add_header (EMailFormatter *formatter,
                             const gchar *name,
                             const gchar *value,
                             EMailFormatterHeaderFlags flags)
{
    EMailFormatterHeader *h;

    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));
    g_return_if_fail (name && *name);

    h = e_mail_formatter_header_new (name, value);
    h->flags = flags;
    g_queue_push_tail (formatter->priv->header_list, h);

    g_signal_emit (formatter, signals[NEED_REDRAW], 0, NULL);
}

void
e_mail_formatter_add_header_struct (EMailFormatter *formatter,
                                    const EMailFormatterHeader *header)
{
    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));
    g_return_if_fail (header && header->name);

    e_mail_formatter_add_header (formatter, header->name, header->value, header->flags);
}

void e_mail_formatter_remove_header (EMailFormatter *formatter,
                     const gchar *name,
                     const gchar *value)
{
    GList *iter = NULL;

    g_return_if_fail (E_IS_MAIL_FORMATTER (formatter));
    g_return_if_fail (name && *name);

    iter = g_queue_peek_head_link (formatter->priv->header_list);
    while (iter) {
        EMailFormatterHeader *header = iter->data;

        if (!header->value || !*header->value) {
            GList *next = iter->next;
            if (g_strcmp0 (name, header->name) == 0)
                g_queue_delete_link (formatter->priv->header_list, iter);

            iter = next;
            continue;
        }

        if (value && *value) {
            if ((g_strcmp0 (name, header->name) == 0) &&
                (g_strcmp0 (value, header->value) == 0))
                break;
        } else {
            if (g_strcmp0 (name, header->name) == 0)
                break;
        }

        iter = iter->next;
    }

    if (iter) {
        e_mail_formatter_header_free (iter->data);
        g_queue_delete_link (formatter->priv->header_list, iter);
    }
}

void
e_mail_formatter_remove_header_struct (EMailFormatter *formatter,
                                       const EMailFormatterHeader *header)
{
    g_return_if_fail (header != NULL);

    e_mail_formatter_remove_header (formatter, header->name, header->value);
}

EMailFormatterHeader *
e_mail_formatter_header_new (const gchar *name,
                             const gchar *value)
{
    EMailFormatterHeader *header;

    g_return_val_if_fail (name && *name, NULL);

    header = g_new0 (EMailFormatterHeader, 1);
    header->name = g_strdup (name);
    if (value && *value)
        header->value = g_strdup (value);

    return header;
}

void
e_mail_formatter_header_free (EMailFormatterHeader *header)
{
    g_return_if_fail (header);

    if (header->name) {
        g_free (header->name);
        header->name = NULL;
    }

    if (header->value) {
        g_free (header->value);
        header->value = NULL;
    }

    g_free (header);
}