aboutsummaryrefslogblamecommitdiffstats
path: root/composer/e-composer-private.c
blob: 477aae4296d2a76367e560c0c2cf8968a56022bf (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11

                                                                           
  



                                                                



                                                                    
                                                  

                                                                   
                                                                             

                                                        

   



                    
                               
                                  
 


                                            


                                                    
                                 



                          
                                                                               
                                                      
                                                            
 


                                                                               



                                               
                                                   





                                                       
 
                                                  


           

























                                                                    
                                                                      

                                                   




                                                                         
                                      





                                                                       






















                                                                          
    
                                                       

                                                   
                                     
                      
                                       
                                  
                              
                                 
                          
                             
                          
                               
                          
                          
                                   
                                       
                


                                           
                                                            
 
                                                    
                                                            
                                                          
                                                                  
 
                                
     



                                                                      
                  


                                                                      
                   

                                                                                  
      
                                                                                     

                                                                      



                                         




                                                                 
                                                             





                                                                   







                                                            
                                                 


                                                          

                                      


                                                                       
                                                                       

                          

                                                                 
                                                                   
                                                                           
 















                                                                               
 







                                                             

















                                                                           

                                 















                                                                          
                                                     
                                                                   
                                                                          
                              
                                                                       
            
                                                                       




                                                   
                                
                                                     
                                                           

                                                                                    
 




                                                                        




                                       
                                
                                                            
                                 
 

                                                           
                                        

                                                                       



                                                  

                                                           
                                                                           
                                                                
                                                                           
                                          
                                                           


                                                       
                                                                                   
                                                                         


                                                                   

                                                                                     


                                                                           

                                                              
                                                                           
                                                                     
                                                                           
                                          
                                                           


                                                       
                                                                                   
                                                                         


                                                                   

                                                                                     
 
                                          
                                                                                 
         
 


















                                                                         
 

                                                                     
 
                                       
                                                
                                                




                                                  

                                                         














                                                                           
 


















                                                                        







                                                                
                                        
                                            


                                                 
 
                                        
                                          


                                                 
         
 











                                                                       
                                          
                                                                      




                                                   






                                                         




                                                              









                                                              




                                                                  




                                                               




                                                              




                                                               









                                                                  




                                                                         



                                                                       
                                                          



















































                                                                      
                            
                       
 
                                                               
 
                                                                       

                                                
                                                                        


                                                                          
                                                                      


                                                          


                 
                                  

                            
                                                                   






                                                
















































































                                                                        
        




















                                                                   









































































                                                                      




















                                                                   































                                                                      







































































                                                                                
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 * 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/>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 */

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

#include "e-composer-private.h"
#include "e-util/e-util-private.h"

/* Initial height of the picture gallery. */
#define GALLERY_INITIAL_HEIGHT 150

static void
composer_setup_charset_menu (EMsgComposer *composer)
{
    GtkUIManager *ui_manager;
    const gchar *path;
    GList *list;
    guint merge_id;

    ui_manager = gtkhtml_editor_get_ui_manager (GTKHTML_EDITOR (composer));
    path = "/main-menu/options-menu/charset-menu";
    merge_id = gtk_ui_manager_new_merge_id (ui_manager);

    list = gtk_action_group_list_actions (composer->priv->charset_actions);
    list = g_list_sort (list, (GCompareFunc) e_action_compare_by_label);

    while (list != NULL) {
        GtkAction *action = list->data;

        gtk_ui_manager_add_ui (
            ui_manager, merge_id, path,
            gtk_action_get_name (action),
            gtk_action_get_name (action),
            GTK_UI_MANAGER_AUTO, FALSE);

        list = g_list_delete_link (list, list);
    }

    gtk_ui_manager_ensure_update (ui_manager);
}

static void
msg_composer_url_requested_cb (GtkHTML *html,
                               const gchar *uri,
                               GtkHTMLStream *stream,
                               EMsgComposer *composer)
{
    GByteArray *array;
    GHashTable *hash_table;
    CamelDataWrapper *wrapper;
    CamelStream *camel_stream;
    CamelMimePart *mime_part;

    hash_table = composer->priv->inline_images_by_url;
    mime_part = g_hash_table_lookup (hash_table, uri);

    if (mime_part == NULL) {
        hash_table = composer->priv->inline_images;
        mime_part = g_hash_table_lookup (hash_table, uri);
    }

    /* If this is not an inline image request,
     * allow the signal emission to continue. */
    if (mime_part == NULL)
        return;

    array = g_byte_array_new ();
    camel_stream = camel_stream_mem_new_with_byte_array (array);
    wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
    camel_data_wrapper_decode_to_stream_sync (
        wrapper, camel_stream, NULL, NULL);

    gtk_html_write (html, stream, (gchar *) array->data, array->len);

    gtk_html_end (html, stream, GTK_HTML_STREAM_OK);

    g_object_unref (camel_stream);

    /* gtk_html_end() destroys the GtkHTMLStream, so we need to
     * stop the signal emission so nothing else tries to use it. */
    g_signal_stop_emission_by_name (html, "url-requested");
}

static void
composer_update_gallery_visibility (EMsgComposer *composer)
{
    GtkhtmlEditor *editor;
    GtkToggleAction *toggle_action;
    gboolean gallery_active;
    gboolean html_mode;

    editor = GTKHTML_EDITOR (composer);
    html_mode = gtkhtml_editor_get_html_mode (editor);

    toggle_action = GTK_TOGGLE_ACTION (ACTION (PICTURE_GALLERY));
    gallery_active = gtk_toggle_action_get_active (toggle_action);

    if (html_mode && gallery_active) {
        gtk_widget_show (composer->priv->gallery_scrolled_window);
        gtk_widget_show (composer->priv->gallery_icon_view);
    } else {
        gtk_widget_hide (composer->priv->gallery_scrolled_window);
        gtk_widget_hide (composer->priv->gallery_icon_view);
    }
}

void
e_composer_private_constructed (EMsgComposer *composer)
{
    EMsgComposerPrivate *priv = composer->priv;
    EFocusTracker *focus_tracker;
    EShell *shell;
    EShellSettings *shell_settings;
    EWebViewGtkHTML *web_view;
    GtkhtmlEditor *editor;
    GtkUIManager *ui_manager;
    GtkAction *action;
    GtkWidget *container;
    GtkWidget *widget;
    GtkWidget *send_widget;
    GtkWindow *window;
    const gchar *path;
    gboolean small_screen_mode;
    gchar *filename, *gallery_path;
    gint ii;
    GError *error = NULL;

    editor = GTKHTML_EDITOR (composer);
    ui_manager = gtkhtml_editor_get_ui_manager (editor);

    shell = e_msg_composer_get_shell (composer);
    shell_settings = e_shell_get_shell_settings (shell);
    web_view = e_msg_composer_get_web_view (composer);
    small_screen_mode = e_shell_get_small_screen_mode (shell);

    if (small_screen_mode) {
#if 0
        /* In the lite composer, for small screens, we are not
         * ready yet to hide the menubar.  It still has useful
         * items like the ones to show/hide the various header
         * fields, plus the security options.
         *
         * When we move those options out of the menu and into
         * the composer's toplevel, we can probably get rid of
         * the menu.
         */
        widget = gtkhtml_editor_get_managed_widget (editor, "/main-menu");
        gtk_widget_hide (widget);
#endif
        widget = gtkhtml_editor_get_managed_widget (editor, "/main-toolbar");
        gtk_toolbar_set_style (
            GTK_TOOLBAR (widget), GTK_TOOLBAR_BOTH_HORIZ);
        gtk_widget_hide (widget);

    }

    /* Each composer window gets its own window group. */
    window = GTK_WINDOW (composer);
    priv->window_group = gtk_window_group_new ();
    gtk_window_group_add_window (priv->window_group, window);

    priv->async_actions = gtk_action_group_new ("async");
    priv->charset_actions = gtk_action_group_new ("charset");
    priv->composer_actions = gtk_action_group_new ("composer");

    priv->extra_hdr_names = g_ptr_array_new ();
    priv->extra_hdr_values = g_ptr_array_new ();

    priv->inline_images = g_hash_table_new_full (
        g_str_hash, g_str_equal,
        (GDestroyNotify) g_free,
        (GDestroyNotify) NULL);

    priv->inline_images_by_url = g_hash_table_new_full (
        g_str_hash, g_str_equal,
        (GDestroyNotify) g_free,
        (GDestroyNotify) g_object_unref);

    priv->charset = e_composer_get_default_charset ();

    priv->is_from_message = FALSE;

    e_composer_actions_init (composer);

    filename = e_composer_find_data_file ("evolution-composer.ui");
    gtk_ui_manager_add_ui_from_file (ui_manager, filename, &error);
    g_free (filename);

    /* We set the send button as important to have a label */
    path = "/main-toolbar/pre-main-toolbar/send";
    send_widget = gtk_ui_manager_get_widget (ui_manager, path);
    gtk_tool_item_set_is_important (GTK_TOOL_ITEM (send_widget), TRUE);

    /* Tooltips for all buttons in toolbar that don't yet have one */
    path = "/main-toolbar/undo";
    widget = gtk_ui_manager_get_widget (ui_manager, path);
    gtk_widget_set_tooltip_text (widget, _("Undo the last action"));

    path = "/main-toolbar/redo";
    widget = gtk_ui_manager_get_widget (ui_manager, path);
    gtk_widget_set_tooltip_text (widget, _("Redo the last undone action"));

    path = "/main-toolbar/show-find";
    widget = gtk_ui_manager_get_widget (ui_manager, path);
    gtk_widget_set_tooltip_text (widget, _("Search for text"));

    path = "/main-toolbar/show-replace";
    widget = gtk_ui_manager_get_widget (ui_manager, path);
    gtk_widget_set_tooltip_text (widget, _("Search for and replace text"));

    composer_setup_charset_menu (composer);

    if (error != NULL) {
        /* Henceforth, bad things start happening. */
        g_critical ("%s", error->message);
        g_clear_error (&error);
    }

    /* Configure an EFocusTracker to manage selection actions. */

    focus_tracker = e_focus_tracker_new (GTK_WINDOW (composer));

    action = gtkhtml_editor_get_action (editor, "cut");
    e_focus_tracker_set_cut_clipboard_action (focus_tracker, action);

    action = gtkhtml_editor_get_action (editor, "copy");
    e_focus_tracker_set_copy_clipboard_action (focus_tracker, action);

    action = gtkhtml_editor_get_action (editor, "paste");
    e_focus_tracker_set_paste_clipboard_action (focus_tracker, action);

    action = gtkhtml_editor_get_action (editor, "select-all");
    e_focus_tracker_set_select_all_action (focus_tracker, action);

    priv->focus_tracker = focus_tracker;

    container = editor->vbox;

    /* Construct the activity bar. */

    widget = e_activity_bar_new ();
    gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
    priv->activity_bar = g_object_ref (widget);
    /* EActivityBar controls its own visibility. */

    /* Construct the alert bar for errors. */

    widget = e_alert_bar_new ();
    gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
    priv->alert_bar = g_object_ref (widget);
    /* EAlertBar controls its own visibility. */

    /* Construct the header table. */

    widget = e_composer_header_table_new (shell);
    gtk_container_set_border_width (GTK_CONTAINER (widget), 6);
    gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
    if (small_screen_mode)
        gtk_box_reorder_child (GTK_BOX (container), widget, 1);
    else
        gtk_box_reorder_child (GTK_BOX (container), widget, 2);
    priv->header_table = g_object_ref (widget);
    gtk_widget_show (widget);

    /* Construct the attachment paned. */

    if (small_screen_mode) {
        /* Short attachment bar for Anjal. */
        e_attachment_paned_set_default_height (75);
        e_attachment_icon_view_set_default_icon_size (GTK_ICON_SIZE_BUTTON);
    }

    widget = e_attachment_paned_new ();
    gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
    priv->attachment_paned = g_object_ref (widget);
    gtk_widget_show (widget);

    g_object_bind_property (
        web_view, "editable",
        widget, "editable",
        G_BINDING_SYNC_CREATE);

    if (small_screen_mode) {
        GtkWidget *tmp, *tmp1, *tmp_box, *container;
        GtkWidget *combo;

        combo = e_attachment_paned_get_view_combo (
            E_ATTACHMENT_PANED (widget));
        gtk_widget_hide (combo);
        container = e_attachment_paned_get_controls_container (
            E_ATTACHMENT_PANED (widget));

        tmp_box = gtk_hbox_new (FALSE, 0);

        tmp = gtk_hbox_new (FALSE, 0);
        tmp1 = gtk_image_new_from_icon_name (
            "mail-send", GTK_ICON_SIZE_BUTTON);
        gtk_box_pack_start ((GtkBox *) tmp, tmp1, FALSE, FALSE, 0);
        tmp1 = gtk_label_new_with_mnemonic (_("S_end"));
        gtk_box_pack_start ((GtkBox *) tmp, tmp1, FALSE, FALSE, 6);
        gtk_widget_show_all (tmp);
        gtk_widget_reparent (send_widget, tmp_box);
        gtk_box_set_child_packing (
            GTK_BOX (tmp_box), send_widget,
            FALSE, FALSE, 6, GTK_PACK_END);
        gtk_tool_item_set_is_important (GTK_TOOL_ITEM (send_widget), TRUE);
        send_widget = gtk_bin_get_child ((GtkBin *) send_widget);
        gtk_container_remove (
            GTK_CONTAINER (send_widget),
            gtk_bin_get_child (GTK_BIN (send_widget)));
        gtk_container_add ((GtkContainer *) send_widget, tmp);
        gtk_button_set_relief ((GtkButton *) send_widget, GTK_RELIEF_NORMAL);
        path = "/main-toolbar/pre-main-toolbar/save-draft";
        send_widget = gtk_ui_manager_get_widget (ui_manager, path);
        tmp = gtk_hbox_new (FALSE, 0);
        tmp1 = gtk_image_new_from_stock (
            GTK_STOCK_SAVE, GTK_ICON_SIZE_BUTTON);
        gtk_box_pack_start ((GtkBox *) tmp, tmp1, FALSE, FALSE, 0);
        tmp1 = gtk_label_new_with_mnemonic (_("Save draft"));
        gtk_box_pack_start ((GtkBox *) tmp, tmp1, FALSE, FALSE, 3);
        gtk_widget_show_all (tmp);
        gtk_widget_reparent (send_widget, tmp_box);
        gtk_box_set_child_packing (
            GTK_BOX (tmp_box), send_widget,
            FALSE, FALSE, 6, GTK_PACK_END);
        gtk_tool_item_set_is_important (GTK_TOOL_ITEM (send_widget), TRUE);
        send_widget = gtk_bin_get_child ((GtkBin *) send_widget);
        gtk_container_remove (
            GTK_CONTAINER (send_widget),
            gtk_bin_get_child (GTK_BIN (send_widget)));
        gtk_container_add ((GtkContainer *) send_widget, tmp);
        gtk_button_set_relief ((GtkButton *) send_widget, GTK_RELIEF_NORMAL);

        gtk_widget_show (tmp_box);
        gtk_box_pack_end (GTK_BOX (container), tmp_box, FALSE, FALSE, 3);
    }

    container = e_attachment_paned_get_content_area (
        E_ATTACHMENT_PANED (priv->attachment_paned));

    widget = gtk_vpaned_new ();
    gtk_container_add (GTK_CONTAINER (container), widget);
    gtk_widget_show (widget);

    container = widget;

    widget = gtk_scrolled_window_new (NULL, NULL);
    gtk_scrolled_window_set_policy (
        GTK_SCROLLED_WINDOW (widget),
        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_scrolled_window_set_shadow_type (
        GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
    gtk_widget_set_size_request (widget, -1, GALLERY_INITIAL_HEIGHT);
    gtk_paned_pack1 (GTK_PANED (container), widget, FALSE, FALSE);
    priv->gallery_scrolled_window = g_object_ref (widget);
    gtk_widget_show (widget);

    /* Reparent the scrolled window containing the GtkHTML widget
     * into the content area of the top attachment pane. */

    widget = GTK_WIDGET (web_view);
    widget = gtk_widget_get_parent (widget);
    gtk_widget_reparent (widget, container);

    /* Construct the picture gallery. */

    container = priv->gallery_scrolled_window;

    gallery_path = e_shell_settings_get_string (
        shell_settings, "composer-gallery-path");
    widget = e_picture_gallery_new (gallery_path);
    gtk_container_add (GTK_CONTAINER (container), widget);
    priv->gallery_icon_view = g_object_ref (widget);
    g_free (gallery_path);

    g_signal_connect (
        composer, "notify::html-mode",
        G_CALLBACK (composer_update_gallery_visibility), NULL);

    g_signal_connect_swapped (
        ACTION (PICTURE_GALLERY), "toggled",
        G_CALLBACK (composer_update_gallery_visibility), composer);

    /* XXX What is this for? */
    g_object_set_data (G_OBJECT (composer), "vbox", editor->vbox);

    /* Bind headers to their corresponding actions. */

    for (ii = 0; ii < E_COMPOSER_NUM_HEADERS; ii++) {
        EComposerHeaderTable *table;
        EComposerHeader *header;
        GtkAction *action;

        table = E_COMPOSER_HEADER_TABLE (priv->header_table);
        header = e_composer_header_table_get_header (table, ii);

        switch (ii) {
            case E_COMPOSER_HEADER_BCC:
                action = ACTION (VIEW_BCC);
                break;

            case E_COMPOSER_HEADER_CC:
                action = ACTION (VIEW_CC);
                break;

            case E_COMPOSER_HEADER_REPLY_TO:
                action = ACTION (VIEW_REPLY_TO);
                break;

            default:
                continue;
        }

        g_object_bind_property (
            header, "sensitive",
            action, "sensitive",
            G_BINDING_BIDIRECTIONAL |
            G_BINDING_SYNC_CREATE);

        g_object_bind_property (
            header, "visible",
            action, "active",
            G_BINDING_BIDIRECTIONAL |
            G_BINDING_SYNC_CREATE);
    }

    /* Install a handler for inline images. */

    /* XXX We no longer use GtkhtmlEditor::uri-requested because it
     *     conflicts with EWebView's url_requested() method, which
     *     unconditionally launches an async operation.  I changed
     *     GtkHTML::url-requested to be a G_SIGNAL_RUN_LAST so that
     *     our handler runs first.  If we can handle the request
     *     we'll stop the signal emission to prevent EWebView from
     *     launching an async operation.  Messy, but works until we
     *     switch to WebKit.  --mbarnes */

    g_signal_connect (
        web_view, "url-requested",
        G_CALLBACK (msg_composer_url_requested_cb), composer);
}

void
e_composer_private_dispose (EMsgComposer *composer)
{
    if (composer->priv->shell != NULL) {
        g_object_remove_weak_pointer (
            G_OBJECT (composer->priv->shell),
            &composer->priv->shell);
        composer->priv->shell = NULL;
    }

    if (composer->priv->header_table != NULL) {
        g_object_unref (composer->priv->header_table);
        composer->priv->header_table = NULL;
    }

    if (composer->priv->activity_bar != NULL) {
        g_object_unref (composer->priv->activity_bar);
        composer->priv->activity_bar = NULL;
    }

    if (composer->priv->alert_bar != NULL) {
        g_object_unref (composer->priv->alert_bar);
        composer->priv->alert_bar = NULL;
    }

    if (composer->priv->attachment_paned != NULL) {
        g_object_unref (composer->priv->attachment_paned);
        composer->priv->attachment_paned = NULL;
    }

    if (composer->priv->focus_tracker != NULL) {
        g_object_unref (composer->priv->focus_tracker);
        composer->priv->focus_tracker = NULL;
    }

    if (composer->priv->window_group != NULL) {
        g_object_unref (composer->priv->window_group);
        composer->priv->window_group = NULL;
    }

    if (composer->priv->async_actions != NULL) {
        g_object_unref (composer->priv->async_actions);
        composer->priv->async_actions = NULL;
    }

    if (composer->priv->charset_actions != NULL) {
        g_object_unref (composer->priv->charset_actions);
        composer->priv->charset_actions = NULL;
    }

    if (composer->priv->composer_actions != NULL) {
        g_object_unref (composer->priv->composer_actions);
        composer->priv->composer_actions = NULL;
    }

    if (composer->priv->gallery_scrolled_window != NULL) {
        g_object_unref (composer->priv->gallery_scrolled_window);
        composer->priv->gallery_scrolled_window = NULL;
    }

    g_hash_table_remove_all (composer->priv->inline_images);
    g_hash_table_remove_all (composer->priv->inline_images_by_url);

    if (composer->priv->redirect != NULL) {
        g_object_unref (composer->priv->redirect);
        composer->priv->redirect = NULL;
    }
}

void
e_composer_private_finalize (EMsgComposer *composer)
{
    GPtrArray *array;

    array = composer->priv->extra_hdr_names;
    g_ptr_array_foreach (array, (GFunc) g_free, NULL);
    g_ptr_array_free (array, TRUE);

    array = composer->priv->extra_hdr_values;
    g_ptr_array_foreach (array, (GFunc) g_free, NULL);
    g_ptr_array_free (array, TRUE);

    g_free (composer->priv->charset);
    g_free (composer->priv->mime_type);
    g_free (composer->priv->mime_body);

    g_hash_table_destroy (composer->priv->inline_images);
    g_hash_table_destroy (composer->priv->inline_images_by_url);
}

gchar *
e_composer_find_data_file (const gchar *basename)
{
    gchar *filename;

    g_return_val_if_fail (basename != NULL, NULL);

    /* Support running directly from the source tree. */
    filename = g_build_filename (".", basename, NULL);
    if (g_file_test (filename, G_FILE_TEST_EXISTS))
        return filename;
    g_free (filename);

    /* XXX This is kinda broken. */
    filename = g_build_filename (EVOLUTION_UIDIR, basename, NULL);
    if (g_file_test (filename, G_FILE_TEST_EXISTS))
        return filename;
    g_free (filename);

    g_critical ("Could not locate '%s'", basename);

    return NULL;
}

gchar *
e_composer_get_default_charset (void)
{
    GSettings *settings;
    gchar *charset;

    settings = g_settings_new ("org.gnome.evolution.mail");

    charset = g_settings_get_string (settings, "composer-charset");

    /* See what charset the mailer is using.
     * XXX We should not have to know where this lives in GSettings.
     *     Need a mail_config_get_default_charset() that does this. */
    if (!charset || charset[0] == '\0') {
        g_free (charset);
        charset = g_settings_get_string (settings, "charset");
        if (charset != NULL && *charset == '\0') {
            g_free (charset);
            charset = NULL;
        }
    }

    g_object_unref (settings);

    if (charset == NULL)
        charset = g_strdup (camel_iconv_locale_charset ());

    if (charset == NULL)
        charset = g_strdup ("us-ascii");

    return charset;
}

gchar *
e_composer_decode_clue_value (const gchar *encoded_value)
{
    GString *buffer;
    const gchar *cp;

    /* Decode a GtkHtml "ClueFlow" value. */

    g_return_val_if_fail (encoded_value != NULL, NULL);

    buffer = g_string_sized_new (strlen (encoded_value));

    /* Copy the value, decoding escaped characters as we go. */
    cp = encoded_value;
    while (*cp != '\0') {
        if (*cp == '.') {
            cp++;
            switch (*cp) {
                case '.':
                    g_string_append_c (buffer, '.');
                    break;
                case '1':
                    g_string_append_c (buffer, '"');
                    break;
                case '2':
                    g_string_append_c (buffer, '=');
                    break;
                default:
                    /* Invalid escape sequence. */
                    g_string_free (buffer, TRUE);
                    return NULL;
            }
        } else
            g_string_append_c (buffer, *cp);
        cp++;
    }

    return g_string_free (buffer, FALSE);
}

gchar *
e_composer_encode_clue_value (const gchar *decoded_value)
{
    gchar *encoded_value;
    gchar **strv;

    /* Encode a GtkHtml "ClueFlow" value. */

    g_return_val_if_fail (decoded_value != NULL, NULL);

    /* XXX This is inefficient but easy to understand. */

    encoded_value = g_strdup (decoded_value);

    /* Substitution: '.' --> '..' (do this first) */
    if (strchr (encoded_value, '.') != NULL) {
        strv = g_strsplit (encoded_value, ".", 0);
        g_free (encoded_value);
        encoded_value = g_strjoinv ("..", strv);
        g_strfreev (strv);
    }

    /* Substitution: '"' --> '.1' */
    if (strchr (encoded_value, '"') != NULL) {
        strv = g_strsplit (encoded_value, """", 0);
        g_free (encoded_value);
        encoded_value = g_strjoinv (".1", strv);
        g_strfreev (strv);
    }

    /* Substitution: '=' --> '.2' */
    if (strchr (encoded_value, '=') != NULL) {
        strv = g_strsplit (encoded_value, "=", 0);
        g_free (encoded_value);
        encoded_value = g_strjoinv (".2", strv);
        g_strfreev (strv);
    }

    return encoded_value;
}

gboolean
e_composer_paste_html (EMsgComposer *composer,
                       GtkClipboard *clipboard)
{
    GtkhtmlEditor *editor;
    gchar *html;

    g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
    g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);

    html = e_clipboard_wait_for_html (clipboard);
    g_return_val_if_fail (html != NULL, FALSE);

    editor = GTKHTML_EDITOR (composer);
    gtkhtml_editor_insert_html (editor, html);

    g_free (html);

    return TRUE;
}

gboolean
e_composer_paste_image (EMsgComposer *composer,
                        GtkClipboard *clipboard)
{
    GtkhtmlEditor *editor;
    EAttachmentStore *store;
    EAttachmentView *view;
    GdkPixbuf *pixbuf = NULL;
    gchar *filename = NULL;
    gchar *uri = NULL;
    gboolean success = FALSE;
    GError *error = NULL;

    g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
    g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);

    editor = GTKHTML_EDITOR (composer);
    view = e_msg_composer_get_attachment_view (composer);
    store = e_attachment_view_get_store (view);

    /* Extract the image data from the clipboard. */
    pixbuf = gtk_clipboard_wait_for_image (clipboard);
    g_return_val_if_fail (pixbuf != NULL, FALSE);

    /* Reserve a temporary file. */
    filename = e_mktemp (NULL);
    if (filename == NULL) {
        g_set_error (
            &error, G_FILE_ERROR,
            g_file_error_from_errno (errno),
            "Could not create temporary file: %s",
            g_strerror (errno));
        goto exit;
    }

    /* Save the pixbuf as a temporary file in image/png format. */
    if (!gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL))
        goto exit;

    /* Convert the filename to a URI. */
    uri = g_filename_to_uri (filename, NULL, &error);
    if (uri == NULL)
        goto exit;

    /* In HTML mode, paste the image into the message body.
     * In text mode, add the image to the attachment store. */
    if (gtkhtml_editor_get_html_mode (editor))
        gtkhtml_editor_insert_image (editor, uri);
    else {
        EAttachment *attachment;

        attachment = e_attachment_new_for_uri (uri);
        e_attachment_store_add_attachment (store, attachment);
        e_attachment_load_async (
            attachment, (GAsyncReadyCallback)
            e_attachment_load_handle_error, composer);
        g_object_unref (attachment);
    }

    success = TRUE;

exit:
    if (error != NULL) {
        g_warning ("%s", error->message);
        g_error_free (error);
    }

    g_object_unref (pixbuf);
    g_free (filename);
    g_free (uri);

    return success;
}

gboolean
e_composer_paste_text (EMsgComposer *composer,
                       GtkClipboard *clipboard)
{
    GtkhtmlEditor *editor;
    gchar *text;

    g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
    g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);

    text = gtk_clipboard_wait_for_text (clipboard);
    g_return_val_if_fail (text != NULL, FALSE);

    editor = GTKHTML_EDITOR (composer);
    gtkhtml_editor_insert_text (editor, text);

    g_free (text);

    return TRUE;
}

gboolean
e_composer_paste_uris (EMsgComposer *composer,
                       GtkClipboard *clipboard)
{
    EAttachmentStore *store;
    EAttachmentView *view;
    gchar **uris;
    gint ii;

    g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
    g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);

    view = e_msg_composer_get_attachment_view (composer);
    store = e_attachment_view_get_store (view);

    /* Extract the URI data from the clipboard. */
    uris = gtk_clipboard_wait_for_uris (clipboard);
    g_return_val_if_fail (uris != NULL, FALSE);

    /* Add the URIs to the attachment store. */
    for (ii = 0; uris[ii] != NULL; ii++) {
        EAttachment *attachment;

        attachment = e_attachment_new_for_uri (uris[ii]);
        e_attachment_store_add_attachment (store, attachment);
        e_attachment_load_async (
            attachment, (GAsyncReadyCallback)
            e_attachment_load_handle_error, composer);
        g_object_unref (attachment);
    }

    return TRUE;
}

gboolean
e_composer_selection_is_image_uris (EMsgComposer *composer,
                                    GtkSelectionData *selection)
{
    gboolean all_image_uris = TRUE;
    gchar **uris;
    guint ii;

    g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
    g_return_val_if_fail (selection != NULL, FALSE);

    uris = gtk_selection_data_get_uris (selection);

    if (uris == NULL)
        return FALSE;

    for (ii = 0; uris[ii] != NULL; ii++) {
        GFile *file;
        GFileInfo *file_info;
        GdkPixbufLoader *loader;
        const gchar *attribute;
        const gchar *content_type;
        gchar *mime_type = NULL;

        file = g_file_new_for_uri (uris[ii]);
        attribute = G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE;

        /* XXX This blocks, but we're requesting the fast content
         *     type (which only inspects filenames), so hopefully
         *     it won't be noticeable.  Also, this is best effort
         *     so we don't really care if it fails. */
        file_info = g_file_query_info (
            file, attribute, G_FILE_QUERY_INFO_NONE, NULL, NULL);

        if (file_info == NULL) {
            g_object_unref (file);
            all_image_uris = FALSE;
            break;
        }

        content_type = g_file_info_get_attribute_string (
            file_info, attribute);
        mime_type = g_content_type_get_mime_type (content_type);

        g_object_unref (file_info);
        g_object_unref (file);

        if (mime_type == NULL) {
            all_image_uris = FALSE;
            break;
        }

        /* Easy way to determine if a MIME type is a supported
         * image format: try creating a GdkPixbufLoader for it. */
        loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, NULL);

        g_free (mime_type);

        if (loader == NULL) {
            all_image_uris = FALSE;
            break;
        }

        gdk_pixbuf_loader_close (loader, NULL);
        g_object_unref (loader);
    }

    g_strfreev (uris);

    return all_image_uris;
}