aboutsummaryrefslogblamecommitdiffstats
path: root/e-util/e-html-editor-view.c
blob: 19184ef1dba40bbb277ce5ede9a81ca7ab5b98ac (plain) (tree)
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
5192
5193
5194
5195
5196
5197
5198
5199
5200
5201
5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
5215
5216
5217
5218
5219
5220
5221
5222
5223
5224
5225
5226
5227
5228
5229
5230
5231
5232
5233
5234
5235
5236
5237
5238
5239
5240
5241
5242
5243
5244
5245
5246
5247
5248
5249
5250
5251
5252
5253
5254
5255
5256
5257
5258
5259
5260
5261
5262
5263
5264
5265
5266
5267
5268
5269
5270
5271
5272
5273
5274
5275
5276
5277
5278
5279
5280
5281
5282
5283
5284
5285
5286
5287
5288
5289
5290
5291
5292
5293
5294
5295
5296
5297
5298
5299
5300
5301
5302
5303
5304
5305
5306
5307
5308
5309
5310
5311
5312
5313
5314
5315
5316
5317
5318
5319
5320
5321
5322
5323
5324
5325
5326
5327
5328
5329
5330
5331
5332
5333
5334
5335
5336
5337
5338
5339
5340
5341
5342
5343
5344
5345
5346
5347
5348
5349
5350
5351
5352
5353
5354
5355
5356
5357
5358
5359
5360
5361
5362
5363
5364
5365
5366
5367
5368
5369
5370
5371
5372
5373
5374
5375
5376
5377
5378
5379
5380
5381
5382
5383
5384
5385
5386
5387
5388
5389
5390
5391
5392
5393
5394
5395
5396
5397
5398
5399
5400
5401
5402
5403
5404
5405
5406
5407
5408
5409
5410
5411
5412
5413
5414
5415
5416
5417
5418
5419
5420
5421
5422
5423
5424
5425
5426
5427
5428
5429
5430
5431
5432
5433
5434
5435
5436
5437
5438
5439
5440
5441
5442
5443
5444
5445
5446
5447
5448
5449
5450
5451
5452
5453
5454
5455
5456
5457
5458
5459
5460
5461
5462
5463
5464
5465
5466
5467
5468
5469
5470
5471
5472
5473
5474
5475
5476
5477
5478
5479
5480
5481
5482
5483
5484
5485
5486
5487
5488
5489
5490
5491
5492
5493
5494
5495
5496
5497
5498
5499
5500
5501
5502
5503
5504
5505
5506
5507
5508
5509
5510
5511
5512
5513
5514
5515
5516
5517
5518
5519
5520
5521
5522
5523
5524
5525
5526
5527
5528
5529
5530
5531
5532
5533
5534
5535
5536
5537
5538
5539
5540
5541
5542
5543
5544
5545
5546
5547
5548
5549
5550
5551
5552
5553
5554
5555
5556
5557
5558
5559
5560
5561
5562
5563
5564
5565
5566
5567
5568
5569
5570
5571
5572
5573
5574
5575
5576
5577
5578
5579
5580
5581
5582
5583
5584
5585
5586
5587
5588
5589
5590
5591
5592
5593
5594
5595
5596
5597
5598
5599
5600
5601
5602
5603
5604
5605
5606
5607
5608
5609
5610
5611
5612
5613
5614
5615
5616
5617
5618
5619





































                                                                             
                               















































                                                                           

                                 































































































































































































                                                                                                      
                                             





































































































                                                                                                      
                                             
















































                                                                                     


                                                                                    









































































































































































                                                                                                      
                                                          
































                                                                                   
                                   



































































































                                                                                                   








                                                  















                                                                   

                                                                 














































































































































































                                                                                                             
                                                           




































                                                                                                    
                                                           







































































































































































































































































































                                                                                             

                                                              


















































































































































































































































                                                                                       




                                                          














































































































































































































                                                                                               
                                    














































































                                                                                                

                                                                
































                                                                                              
                                                               
                                                                   
 
                                                                        
                                                                               





































































































































































































































































































































































































                                                                                                       

























































                                                                                   
                                                 







































                                                                             
                                                                

                                               



                                                               








                                                                    
 
                                                            
                                                









                                                       
                                                                                    







                                                                                   
                                                 














































































































































                                                                                             
                                                                             

                 
                                   



























































































































































































































































                                                                                                 





















                                                                                                  



























                                                                         

                                                                                                                             































                                                                                              
                          
                                                 

                                                                      
                                                           
                                                                      







                                                                                            
                                  
                                                          
                                                                              






































































































                                                                                    

                                                                









































































































































                                                                                                                                   
                                                                                    


































































































                                                                                   
                                                     











































































                                                                                                  

                                   









































































































































                                                                                                        
                                                   











































































































































                                                                                                        

                                                                   






















































                                                                                                





                                                                                

                                                                                            

                                                                 






































































































































































































































































































































































































































































































































































































































                                                                                                                       
                                                                              



                                                                        
                                                                                    





















                                                                                           









                                                                                   






























































                                                                                                                      

                                                    














                                                                                       
                                                                                            













































































































































                                                                                                  
                                                    









































                                                                                            
                                                                                         




























                                                                                            

                                                                   













                                                              

                                                                     




























































                                                                                    




































                                                                                                  
                                                               



                                                                                   



                                                                               
                                 





                                                                                        
























































































































                                                                                   



                                                                                    



























































































































































































                                                                                          
                                   








                                                                         
                                     















































































                                                                                      

                                                                        















































                                                                                            



                                       











































































































































































































































































































































































































































                                                                                                           
                                              























































































































































































































































                                                                             
                                          






                                           

                                          






                                           


                                          






                                           



                                          






                                           




                                          




































































































































                                                                                                    

                                                                          
















































                                                                                   
                                                       













                                                                            

                                                                        






















                                                                                           
                                                                                    
































                                                                                              
                                                                                           














































































                                                                                              
/*
 * e-html-editor-view.c
 *
 * Copyright (C) 2012 Dan Vrátil <dvratil@redhat.com>
 *
 * 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/>
 *
 */

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

#include "e-html-editor-view.h"
#include "e-html-editor.h"
#include "e-emoticon-chooser.h"

#include <e-util/e-util.h>
#include <e-util/e-marshal.h>
#include <glib/gi18n-lib.h>
#include <gdk/gdkkeysyms.h>

#define E_HTML_EDITOR_VIEW_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), E_TYPE_HTML_EDITOR_VIEW, EHTMLEditorViewPrivate))

#define UNICODE_ZERO_WIDTH_SPACE "\xe2\x80\x8b"
#define UNICODE_NBSP "\xc2\xa0"

#define URL_PATTERN \
    "((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[\\-;:&=\\+\\$,\\w]+@)?" \
    "[A-Za-z0-9\\.\\-]+|(?:www\\.|[\\-;:&=\\+\\$,\\w]+@)" \
    "[A-Za-z0-9\\.\\-]+)((?:\\/[\\+~%\\/\\.\\w\\-]*)?\\?" \
    "?(?:[\\-\\+=&;%@\\.\\w]*)#?(?:[\\.\\!\\/\\\\w]*))?)"

#define URL_PATTERN_SPACE URL_PATTERN "\\s"

#define QUOTE_SYMBOL ">"

/* Keep synchronized with the same value in EHTMLEditorSelection */
#define SPACES_PER_LIST_LEVEL 8

/**
 * EHTMLEditorView:
 *
 * The #EHTMLEditorView is a WebKit-based rich text editor. The view itself
 * only provides means to configure global behavior of the editor. To work
 * with the actual content, current cursor position or current selection,
 * use #EHTMLEditorSelection object.
 */

struct _EHTMLEditorViewPrivate {
    gint changed        : 1;
    gint inline_spelling    : 1;
    gint magic_links    : 1;
    gint magic_smileys  : 1;
    gint can_copy       : 1;
    gint can_cut        : 1;
    gint can_paste      : 1;
    gint can_redo       : 1;
    gint can_undo       : 1;
    gint reload_in_progress : 1;
    gint html_mode      : 1;

    EHTMLEditorSelection *selection;

    WebKitDOMElement *element_under_mouse;

    GHashTable *inline_images;

    GSettings *font_settings;
    GSettings *aliasing_settings;

    gboolean convertor_insert;

    WebKitWebView *convertor_web_view;

    GHashTable *old_settings;
};

enum {
    PROP_0,
    PROP_CAN_COPY,
    PROP_CAN_CUT,
    PROP_CAN_PASTE,
    PROP_CAN_REDO,
    PROP_CAN_UNDO,
    PROP_CHANGED,
    PROP_HTML_MODE,
    PROP_INLINE_SPELLING,
    PROP_MAGIC_LINKS,
    PROP_MAGIC_SMILEYS,
    PROP_SPELL_CHECKER
};

enum {
    POPUP_EVENT,
    PASTE_PRIMARY_CLIPBOARD,
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

static CamelDataCache *emd_global_http_cache = NULL;

G_DEFINE_TYPE_WITH_CODE (
    EHTMLEditorView,
    e_html_editor_view,
    WEBKIT_TYPE_WEB_VIEW,
    G_IMPLEMENT_INTERFACE (
        E_TYPE_EXTENSIBLE, NULL))

static WebKitDOMRange *
html_editor_view_get_dom_range (EHTMLEditorView *view)
{
    WebKitDOMDocument *document;
    WebKitDOMDOMWindow *window;
    WebKitDOMDOMSelection *selection;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    window = webkit_dom_document_get_default_view (document);
    selection = webkit_dom_dom_window_get_selection (window);

    if (webkit_dom_dom_selection_get_range_count (selection) < 1) {
        return NULL;
    }

    return webkit_dom_dom_selection_get_range_at (selection, 0, NULL);
}

static void
html_editor_view_user_changed_contents_cb (EHTMLEditorView *view,
                                           gpointer user_data)
{
    WebKitWebView *web_view;
    gboolean can_redo, can_undo;

    web_view = WEBKIT_WEB_VIEW (view);

    e_html_editor_view_set_changed (view, TRUE);

    can_redo = webkit_web_view_can_redo (web_view);
    if (view->priv->can_redo != can_redo) {
        view->priv->can_redo = can_redo;
        g_object_notify (G_OBJECT (view), "can-redo");
    }

    can_undo = webkit_web_view_can_undo (web_view);
    if (view->priv->can_undo != can_undo) {
        view->priv->can_undo = can_undo;
        g_object_notify (G_OBJECT (view), "can-undo");
    }
}

static void
html_editor_view_selection_changed_cb (EHTMLEditorView *view,
                                       gpointer user_data)
{
    WebKitWebView *web_view;
    gboolean can_copy, can_cut, can_paste;

    web_view = WEBKIT_WEB_VIEW (view);

    /* When the webview is being (re)loaded, the document is in an
     * inconsistant state and there is no selection, so don't propagate
     * the signal further to EHTMLEditorSelection and others and wait until
     * the load is finished. */
    if (view->priv->reload_in_progress) {
        g_signal_stop_emission_by_name (view, "selection-changed");
        return;
    }

    can_copy = webkit_web_view_can_copy_clipboard (web_view);
    if (view->priv->can_copy != can_copy) {
        view->priv->can_copy = can_copy;
        g_object_notify (G_OBJECT (view), "can-copy");
    }

    can_cut = webkit_web_view_can_cut_clipboard (web_view);
    if (view->priv->can_cut != can_cut) {
        view->priv->can_cut = can_cut;
        g_object_notify (G_OBJECT (view), "can-cut");
    }

    can_paste = webkit_web_view_can_paste_clipboard (web_view);
    if (view->priv->can_paste != can_paste) {
        view->priv->can_paste = can_paste;
        g_object_notify (G_OBJECT (view), "can-paste");
    }
}

static gboolean
html_editor_view_should_show_delete_interface_for_element (EHTMLEditorView *view,
                                                           WebKitDOMHTMLElement *element)
{
    return FALSE;
}

void
e_html_editor_view_force_spell_check_for_current_paragraph (EHTMLEditorView *view)
{
    EHTMLEditorSelection *selection;
    WebKitDOMDocument *document;
    WebKitDOMDOMSelection *dom_selection;
    WebKitDOMDOMWindow *window;
    WebKitDOMElement *caret, *parent, *element;
    WebKitDOMRange *end_range, *actual;
    WebKitDOMText *text;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    window = webkit_dom_document_get_default_view (document);
    dom_selection = webkit_dom_dom_window_get_selection (window);

    element = webkit_dom_document_query_selector (
        document, "body[spellcheck=true]", NULL);

    if (!element)
        return;

    selection = e_html_editor_view_get_selection (view);
    caret = e_html_editor_selection_save_caret_position (selection);

    /* Block callbacks of selection-changed signal as we don't want to
     * recount all the block format things in EHTMLEditorSelection and here as well
     * when we are moving with caret */
    g_signal_handlers_block_by_func (
        view, html_editor_view_selection_changed_cb, NULL);
    e_html_editor_selection_block_selection_changed (selection);

    parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (caret));
    element = caret;

    while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
        element = parent;
        parent = webkit_dom_node_get_parent_element (
            WEBKIT_DOM_NODE (parent));
    }

    /* Append some text on the end of the element */
    text = webkit_dom_document_create_text_node (document, "-x-evo-end");
    webkit_dom_node_append_child (
        WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (text), NULL);

    /* Create range that's pointing on the end of this text */
    end_range = webkit_dom_document_create_range (document);
    webkit_dom_range_select_node_contents (
        end_range, WEBKIT_DOM_NODE (text), NULL);
    webkit_dom_range_collapse (end_range, FALSE, NULL);

    /* Move on the beginning of the paragraph */
    actual = webkit_dom_document_create_range (document);
    webkit_dom_range_select_node_contents (
        actual, WEBKIT_DOM_NODE (element), NULL);
    webkit_dom_range_collapse (actual, TRUE, NULL);
    webkit_dom_dom_selection_remove_all_ranges (dom_selection);
    webkit_dom_dom_selection_add_range (dom_selection, actual);

    /* Go through all words to spellcheck them. To avoid this we have to wait for
     * http://www.w3.org/html/wg/drafts/html/master/editing.html#dom-forcespellcheck */
    actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
    /* We are moving forward word by word until we hit the text on the end of
     * the paragraph that we previously inserted there */
    while (actual && webkit_dom_range_compare_boundary_points (end_range, 2, actual, NULL) != 0) {
        webkit_dom_dom_selection_modify (
            dom_selection, "move", "forward", "word");
        actual = webkit_dom_dom_selection_get_range_at (
            dom_selection, 0, NULL);
    }

    /* Remove the text that we inserted on the end of the paragraph */
    remove_node (WEBKIT_DOM_NODE (text));

    /* Unblock the callbacks */
    g_signal_handlers_unblock_by_func (
        view, html_editor_view_selection_changed_cb, NULL);
    e_html_editor_selection_unblock_selection_changed (selection);

    e_html_editor_selection_restore_caret_position (selection);
}

static void
move_caret_into_element (WebKitDOMDocument *document,
                         WebKitDOMElement *element)
{
    WebKitDOMDOMWindow *window;
    WebKitDOMDOMSelection *window_selection;
    WebKitDOMRange *new_range;

    if (!element)
        return;

    window = webkit_dom_document_get_default_view (document);
    window_selection = webkit_dom_dom_window_get_selection (window);
    new_range = webkit_dom_document_create_range (document);

    webkit_dom_range_select_node_contents (
        new_range, WEBKIT_DOM_NODE (element), NULL);
    webkit_dom_range_collapse (new_range, FALSE, NULL);
    webkit_dom_dom_selection_remove_all_ranges (window_selection);
    webkit_dom_dom_selection_add_range (window_selection, new_range);
}

static void
refresh_spell_check (EHTMLEditorView *view,
                     gboolean enable_spell_check)
{
    EHTMLEditorSelection *selection;
    WebKitDOMDocument *document;
    WebKitDOMDOMSelection *dom_selection;
    WebKitDOMDOMWindow *window;
    WebKitDOMHTMLElement *body;
    WebKitDOMRange *end_range, *actual;
    WebKitDOMText *text;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    window = webkit_dom_document_get_default_view (document);
    dom_selection = webkit_dom_dom_window_get_selection (window);

    /* Enable/Disable spellcheck in composer */
    body = webkit_dom_document_get_body (document);
    webkit_dom_element_set_attribute (
        WEBKIT_DOM_ELEMENT (body),
        "spellcheck",
        enable_spell_check ? "true" : "false",
        NULL);

    selection = e_html_editor_view_get_selection (view);
    e_html_editor_selection_save_caret_position (selection);

    /* Sometimes the web view is not event focused, so we have to move caret
     * into body */
    if (!webkit_dom_document_get_element_by_id (document, "-x-evo-caret-position")) {
        move_caret_into_element (
            document,
            WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document)));
        e_html_editor_selection_save_caret_position (selection);
    }

    /* Block callbacks of selection-changed signal as we don't want to
     * recount all the block format things in EHTMLEditorSelection and here as well
     * when we are moving with caret */
    g_signal_handlers_block_by_func (
        view, html_editor_view_selection_changed_cb, NULL);
    e_html_editor_selection_block_selection_changed (selection);

    /* Append some text on the end of the body */
    text = webkit_dom_document_create_text_node (document, "-x-evo-end");
    webkit_dom_node_append_child (
        WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (text), NULL);

    /* Create range that's pointing on the end of this text */
    end_range = webkit_dom_document_create_range (document);
    webkit_dom_range_select_node_contents (
        end_range, WEBKIT_DOM_NODE (text), NULL);
    webkit_dom_range_collapse (end_range, FALSE, NULL);

    /* Move on the beginning of the document */
    webkit_dom_dom_selection_modify (
        dom_selection, "move", "backward", "documentboundary");

    /* Go through all words to spellcheck them. To avoid this we have to wait for
     * http://www.w3.org/html/wg/drafts/html/master/editing.html#dom-forcespellcheck */
    actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
    /* We are moving forward word by word until we hit the text on the end of
     * the body that we previously inserted there */
    while (actual && webkit_dom_range_compare_boundary_points (end_range, 2, actual, NULL) != 0) {
        webkit_dom_dom_selection_modify (
            dom_selection, "move", "forward", "word");
        actual = webkit_dom_dom_selection_get_range_at (
            dom_selection, 0, NULL);
    }

    /* Remove the text that we inserted on the end of the body */
    remove_node (WEBKIT_DOM_NODE (text));

    /* Unblock the callbacks */
    g_signal_handlers_unblock_by_func (
        view, html_editor_view_selection_changed_cb, NULL);
    e_html_editor_selection_unblock_selection_changed (selection);

    e_html_editor_selection_restore_caret_position (selection);
}

void
e_html_editor_view_turn_spell_check_off (EHTMLEditorView *view)
{
    refresh_spell_check (view, FALSE);
}

void
e_html_editor_view_force_spell_check (EHTMLEditorView *view)
{
    refresh_spell_check (view, TRUE);
}

static void
body_input_event_cb (WebKitDOMElement *element,
                     WebKitDOMEvent *event,
                     EHTMLEditorView *view)
{
    WebKitDOMNode *node;
    WebKitDOMRange *range = html_editor_view_get_dom_range (view);

    e_html_editor_view_set_changed (view, TRUE);

    node = webkit_dom_range_get_end_container (range, NULL);

    /* After toggling monospaced format, we are using UNICODE_ZERO_WIDTH_SPACE
     * to move caret into right space. When this callback is called it is not
     * necassary anymore so remove it */
    if (e_html_editor_view_get_html_mode (view)) {
        WebKitDOMElement *parent = webkit_dom_node_get_parent_element (node);

        if (parent) {
            WebKitDOMNode *prev_sibling;

            prev_sibling = webkit_dom_node_get_previous_sibling (
                WEBKIT_DOM_NODE (parent));

            if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling)) {
                gchar *text = webkit_dom_node_get_text_content (
                    prev_sibling);

                if (g_strcmp0 (text, UNICODE_ZERO_WIDTH_SPACE) == 0)
                    remove_node (prev_sibling);

                g_free (text);
            }

        }
    }

    /* If text before caret includes UNICODE_ZERO_WIDTH_SPACE character, remove it */
    if (WEBKIT_DOM_IS_TEXT (node)) {
        gchar *text = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (node));
        glong length = g_utf8_strlen (text, -1);
        WebKitDOMNode *parent;

        /* We have to preserve empty paragraphs with just UNICODE_ZERO_WIDTH_SPACE
         * character as when we will remove it it will collapse */
        if (length > 1) {
            if (g_str_has_prefix (text, UNICODE_ZERO_WIDTH_SPACE))
                webkit_dom_character_data_replace_data (
                    WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL);
            else if (g_str_has_suffix (text, UNICODE_ZERO_WIDTH_SPACE))
                webkit_dom_character_data_replace_data (
                    WEBKIT_DOM_CHARACTER_DATA (node), length - 1, 1, "", NULL);
        }
        g_free (text);

        parent = webkit_dom_node_get_parent_node (node);
        if ((WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (parent) ||
            WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent)) &&
            !element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph")) {
            if (e_html_editor_view_get_html_mode (view)) {
                element_add_class (
                    WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph");
            } else {
                e_html_editor_selection_set_paragraph_style (
                    e_html_editor_view_get_selection (view),
                    WEBKIT_DOM_ELEMENT (parent),
                    -1, 0, "");
            }
        }

        /* When new smiley is added we have to use UNICODE_HIDDEN_SPACE to set the
         * caret position to right place. It is removed when user starts typing. But
         * when the user will press left arrow he will move the caret into
         * smiley wrapper. If he will start to write there we have to move the written
         * text out of the wrapper and move caret to right place */
        if (WEBKIT_DOM_IS_ELEMENT (parent) &&
            element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-smiley-wrapper")) {
            WebKitDOMDocument *document;

            document = webkit_web_view_get_dom_document (
                WEBKIT_WEB_VIEW (view));

            webkit_dom_node_insert_before (
                webkit_dom_node_get_parent_node (parent),
                e_html_editor_selection_get_caret_position_node (
                    document),
                webkit_dom_node_get_next_sibling (parent),
                NULL);
            webkit_dom_node_insert_before (
                webkit_dom_node_get_parent_node (parent),
                node,
                webkit_dom_node_get_next_sibling (parent),
                NULL);
            e_html_editor_selection_restore_caret_position (
                e_html_editor_view_get_selection (view));
        }
    }
}

static void
set_base64_to_element_attribute (EHTMLEditorView *view,
                                 WebKitDOMElement *element,
                                 const gchar *attribute)
{
    gchar *attribute_value;
    const gchar *base64_src;

    attribute_value = webkit_dom_element_get_attribute (element, attribute);

    if ((base64_src = g_hash_table_lookup (view->priv->inline_images, attribute_value)) != NULL) {
        const gchar *base64_data = strstr (base64_src, ";") + 1;
        gchar *name;
        glong name_length;

        name_length =
            g_utf8_strlen (base64_src, -1) -
            g_utf8_strlen (base64_data, -1) - 1;
        name = g_strndup (base64_src, name_length);

        webkit_dom_element_set_attribute (element, "data-inline", "", NULL);
        webkit_dom_element_set_attribute (element, "data-name", name, NULL);
        webkit_dom_element_set_attribute (element, attribute, base64_data, NULL);

        g_free (name);
    }
}

static void
change_cid_images_src_to_base64 (EHTMLEditorView *view)
{
    gint ii, length;
    WebKitDOMDocument *document;
    WebKitDOMElement *document_element;
    WebKitDOMNamedNodeMap *attributes;
    WebKitDOMNodeList *list;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    document_element = webkit_dom_document_get_document_element (document);

    list = webkit_dom_document_query_selector_all (document, "img[src^=\"cid:\"]", NULL);
    length = webkit_dom_node_list_get_length (list);
    for (ii = 0; ii < length; ii++) {
        WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);

        set_base64_to_element_attribute (view, WEBKIT_DOM_ELEMENT (node), "src");
    }

    /* Namespaces */
    attributes = webkit_dom_element_get_attributes (document_element);
    length = webkit_dom_named_node_map_get_length (attributes);
    for (ii = 0; ii < length; ii++) {
        gchar *name;
        WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);

        name = webkit_dom_node_get_local_name (node);

        if (g_str_has_prefix (name, "xmlns:")) {
            const gchar *ns = name + 6;
            gchar *attribute_ns = g_strconcat (ns, ":src", NULL);
            gchar *selector = g_strconcat ("img[", ns, "\\:src^=\"cid:\"]", NULL);
            gint ns_length, jj;

            list = webkit_dom_document_query_selector_all (
                document, selector, NULL);
            ns_length = webkit_dom_node_list_get_length (list);
            for (jj = 0; jj < ns_length; jj++) {
                WebKitDOMNode *node = webkit_dom_node_list_item (list, jj);

                set_base64_to_element_attribute (
                    view, WEBKIT_DOM_ELEMENT (node), attribute_ns);
            }

            g_free (attribute_ns);
            g_free (selector);
        }
        g_free (name);
    }

    list = webkit_dom_document_query_selector_all (
        document, "[background^=\"cid:\"]", NULL);
    length = webkit_dom_node_list_get_length (list);
    for (ii = 0; ii < length; ii++) {
        WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);

        set_base64_to_element_attribute (
            view, WEBKIT_DOM_ELEMENT (node), "background");
    }
    g_hash_table_remove_all (view->priv->inline_images);
}

/* For purpose of this function see e-mail-formatter-quote.c */
static void
put_body_in_citation (WebKitDOMDocument *document)
{
    WebKitDOMElement *cite_body = webkit_dom_document_query_selector (
        document, "span.-x-evo-cite-body", NULL);

    if (cite_body) {
        WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document);
        gchar *inner_html, *with_citation;

        remove_node (WEBKIT_DOM_NODE (cite_body));

        inner_html = webkit_dom_html_element_get_inner_html (body);
        with_citation = g_strconcat (
            "<blockquote type=\"cite\" id=\"-x-evo-main-cite\">",
            inner_html, "</span>", NULL);
        webkit_dom_html_element_set_inner_html (body, with_citation, NULL);
        g_free (inner_html);
        g_free (with_citation);
    }
}

/* For purpose of this function see e-mail-formatter-quote.c */
static void
move_elements_to_body (WebKitDOMDocument *document)
{
    WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document);
    WebKitDOMNodeList *list;
    gint ii;

    list = webkit_dom_document_query_selector_all (
        document, "span.-x-evo-to-body", NULL);
    for (ii = webkit_dom_node_list_get_length (list) - 1; ii >= 0; ii--) {
        WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);

        while (webkit_dom_node_has_child_nodes (node)) {
            webkit_dom_node_insert_before (
                WEBKIT_DOM_NODE (body),
                webkit_dom_node_get_first_child (node),
                webkit_dom_node_get_first_child (
                    WEBKIT_DOM_NODE (body)),
                NULL);
        }

        remove_node (node);
    }
}

static void
repair_gmail_blockquotes (WebKitDOMDocument *document)
{
    WebKitDOMNodeList *list;
    gint ii, length;

    list = webkit_dom_document_query_selector_all (
        document, "blockquote.gmail_quote", NULL);
    length = webkit_dom_node_list_get_length (list);
    for (ii = 0; ii < length; ii++) {
        WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);

        webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "class");
        webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "style");
        webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "type", "cite", NULL);
    }
}

static void
html_editor_view_load_status_changed (EHTMLEditorView *view)
{
    WebKitDOMDocument *document;
    WebKitDOMHTMLElement *body;
    WebKitLoadStatus status;

    status = webkit_web_view_get_load_status (WEBKIT_WEB_VIEW (view));
    if (status != WEBKIT_LOAD_FINISHED)
        return;

    view->priv->reload_in_progress = FALSE;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    body = webkit_dom_document_get_body (document);

    webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (body), "style");
    webkit_dom_element_set_attribute (
        WEBKIT_DOM_ELEMENT (body), "data-message", "", NULL);

    put_body_in_citation (document);
    move_elements_to_body (document);
    repair_gmail_blockquotes (document);

    /* Register on input event that is called when the content (body) is modified */
    webkit_dom_event_target_add_event_listener (
        WEBKIT_DOM_EVENT_TARGET (body),
        "input",
        G_CALLBACK (body_input_event_cb),
        FALSE,
        view);

    if (view->priv->html_mode)
        change_cid_images_src_to_base64 (view);
}

/* Based on original use_pictograms() from GtkHTML */
static const gchar *emoticons_chars =
    /*  0 */ "DO)(|/PQ*!"
    /* 10 */ "S\0:-\0:\0:-\0"
    /* 20 */ ":\0:;=-\"\0:;"
    /* 30 */ "B\"|\0:-'\0:X"
    /* 40 */ "\0:\0:-\0:\0:-"
    /* 50 */ "\0:\0:-\0:\0:-"
    /* 60 */ "\0:\0:\0:-\0:\0"
    /* 70 */ ":-\0:\0:-\0:\0";
static gint emoticons_states[] = {
    /*  0 */  12,  17,  22,  34,  43,  48,  53,  58,  65,  70,
    /* 10 */  75,   0, -15,  15,   0, -15,   0, -17,  20,   0,
    /* 20 */ -17,   0, -14, -20, -14,  28,  63,   0, -14, -20,
    /* 30 */  -3,  63, -18,   0, -12,  38,  41,   0, -12,  -2,
    /* 40 */   0,  -4,   0, -10,  46,   0, -10,   0, -19,  51,
    /* 50 */   0, -19,   0, -11,  56,   0, -11,   0, -13,  61,
    /* 60 */   0, -13,   0,  -6,   0,  68,  -7,   0,  -7,   0,
    /* 70 */ -16,  73,   0, -16,   0, -21,  78,   0, -21,   0 };
static const gchar *emoticons_icon_names[] = {
    "face-angel",
    "face-angry",
    "face-cool",
    "face-crying",
    "face-devilish",
    "face-embarrassed",
    "face-kiss",
    "face-laugh",       /* not used */
    "face-monkey",      /* not used */
    "face-plain",
    "face-raspberry",
    "face-sad",
    "face-sick",
    "face-smile",
    "face-smile-big",
    "face-smirk",
    "face-surprise",
    "face-tired",
    "face-uncertain",
    "face-wink",
    "face-worried"
};

static gboolean
is_return_key (GdkEventKey *event)
{
    return (
        (event->keyval == GDK_KEY_Return) ||
        (event->keyval == GDK_KEY_Linefeed) ||
        (event->keyval == GDK_KEY_KP_Enter));
}

static void
html_editor_view_check_magic_links (EHTMLEditorView *view,
                                    WebKitDOMRange *range,
                                    gboolean include_space_by_user,
                                    GdkEventKey *event)
{
    gchar *node_text;
    gchar **urls;
    GRegex *regex = NULL;
    GMatchInfo *match_info;
    gint start_pos_url, end_pos_url;
    WebKitDOMNode *node;
    gboolean include_space = FALSE;
    gboolean return_pressed = FALSE;

    if (event != NULL) {
        return_pressed = is_return_key (event);
        include_space = (event->keyval == GDK_KEY_space);
    } else {
        include_space = include_space_by_user;
    }

    node = webkit_dom_range_get_end_container (range, NULL);

    if (return_pressed)
        node = webkit_dom_node_get_previous_sibling (node);

    if (!node)
        return;

    if (!WEBKIT_DOM_IS_TEXT (node)) {
        if (webkit_dom_node_has_child_nodes (node))
            node = webkit_dom_node_get_first_child (node);
        if (!WEBKIT_DOM_IS_TEXT (node))
            return;
    }

    node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
    if (!node_text || !(*node_text) || !g_utf8_validate (node_text, -1, NULL))
        return;

    regex = g_regex_new (include_space ? URL_PATTERN_SPACE : URL_PATTERN, 0, 0, NULL);

    if (!regex) {
        g_free (node_text);
        return;
    }

    g_regex_match_all (regex, node_text, G_REGEX_MATCH_NOTEMPTY, &match_info);
    urls = g_match_info_fetch_all (match_info);

    if (urls) {
        gchar *final_url, *url_end_raw;
        glong url_start, url_end, url_length;
        WebKitDOMDocument *document;
        WebKitDOMNode *url_text_node_clone;
        WebKitDOMText *url_text_node;
        WebKitDOMElement *anchor;
        const gchar* url_text;

        document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

        if (!return_pressed)
            e_html_editor_selection_save_caret_position (
                e_html_editor_view_get_selection (view));

        g_match_info_fetch_pos (match_info, 0, &start_pos_url, &end_pos_url);

        /* Get start and end position of url in node's text because positions
         * that we get from g_match_info_fetch_pos are not UTF-8 aware */
        url_end_raw = g_strndup(node_text, end_pos_url);
        url_end = g_utf8_strlen (url_end_raw, -1);

        url_length = g_utf8_strlen (urls[0], -1);
        url_start = url_end - url_length;

        webkit_dom_text_split_text (
            WEBKIT_DOM_TEXT (node),
            include_space ? url_end - 1 : url_end,
            NULL);

        url_text_node = webkit_dom_text_split_text (
            WEBKIT_DOM_TEXT (node), url_start, NULL);
        url_text_node_clone = webkit_dom_node_clone_node (
            WEBKIT_DOM_NODE (url_text_node), TRUE);
        url_text = webkit_dom_text_get_whole_text (
            WEBKIT_DOM_TEXT (url_text_node_clone));

        final_url = g_strconcat (
            g_str_has_prefix (url_text, "www") ? "http://" : "", url_text, NULL);

        /* Create and prepare new anchor element */
        anchor = webkit_dom_document_create_element (document, "A", NULL);

        webkit_dom_html_element_set_inner_html (
            WEBKIT_DOM_HTML_ELEMENT (anchor),
            url_text,
            NULL);

        webkit_dom_html_anchor_element_set_href (
            WEBKIT_DOM_HTML_ANCHOR_ELEMENT (anchor),
            final_url);

        /* Insert new anchor element into document */
        webkit_dom_node_replace_child (
            webkit_dom_node_get_parent_node (node),
            WEBKIT_DOM_NODE (anchor),
            WEBKIT_DOM_NODE (url_text_node),
            NULL);

        if (!return_pressed)
            e_html_editor_selection_restore_caret_position (
                e_html_editor_view_get_selection (view));

        g_free (url_end_raw);
        g_free (final_url);
    } else {
        WebKitDOMElement *parent;
        WebKitDOMNode *prev_sibling;
        gchar *href, *text, *url;
        gint diff;
        const char* text_to_append;
        gboolean appending_to_link = FALSE;

        parent = webkit_dom_node_get_parent_element (node);
        prev_sibling = webkit_dom_node_get_previous_sibling (node);

        /* If previous sibling is ANCHOR and actual text node is not beginning with
         * space => we're appending to link */
        if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) {
            text_to_append = webkit_dom_node_get_text_content (node);
            if (g_strcmp0 (text_to_append, "") != 0 &&
                !g_unichar_isspace (g_utf8_get_char (text_to_append))) {

                appending_to_link = TRUE;
                parent = WEBKIT_DOM_ELEMENT (prev_sibling);
            }
        }

        /* If parent is ANCHOR => we're editing the link */
        if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) && !appending_to_link) {
            g_match_info_free (match_info);
            g_regex_unref (regex);
            g_free (node_text);
            return;
        }

        /* edit only if href and description are the same */
        href = webkit_dom_html_anchor_element_get_href (
            WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent));

        if (appending_to_link) {
            gchar *inner_text;

            inner_text =
                webkit_dom_html_element_get_inner_text (
                    WEBKIT_DOM_HTML_ELEMENT (parent)),

            text = g_strconcat (inner_text, text_to_append, NULL);
            g_free (inner_text);
        } else
            text = webkit_dom_html_element_get_inner_text (
                    WEBKIT_DOM_HTML_ELEMENT (parent));

        if (strstr (href, "://") && !strstr (text, "://")) {
            url = strstr (href, "://") + 3;
            diff = strlen (text) - strlen (url);

            if (text [strlen (text) - 1] != '/')
                diff++;

            if ((g_strcmp0 (url, text) != 0 && ABS (diff) == 1) || appending_to_link) {
                gchar *inner_html, *protocol, *new_href;

                protocol = g_strndup (href, strstr (href, "://") - href + 3);
                inner_html = webkit_dom_html_element_get_inner_html (
                    WEBKIT_DOM_HTML_ELEMENT (parent));
                new_href = g_strconcat (
                    protocol, inner_html, appending_to_link ? text_to_append : "", NULL);

                webkit_dom_html_anchor_element_set_href (
                    WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent),
                    new_href);

                if (appending_to_link) {
                    gchar *tmp;

                    tmp = g_strconcat (inner_html, text_to_append, NULL);
                    webkit_dom_html_element_set_inner_html (
                        WEBKIT_DOM_HTML_ELEMENT (parent),
                        tmp,
                        NULL);

                    remove_node (node);

                    g_free (tmp);
                }

                g_free (new_href);
                g_free (protocol);
                g_free (inner_html);
            }
        } else {
            diff = strlen (text) - strlen (href);
            if (text [strlen (text) - 1] != '/')
                diff++;

            if ((g_strcmp0 (href, text) != 0 && ABS (diff) == 1) || appending_to_link) {
                gchar *inner_html;
                gchar *new_href;

                inner_html = webkit_dom_html_element_get_inner_html (
                    WEBKIT_DOM_HTML_ELEMENT (parent));
                new_href = g_strconcat (
                        inner_html,
                        appending_to_link ? text_to_append : "",
                        NULL);

                webkit_dom_html_anchor_element_set_href (
                    WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent),
                    new_href);

                if (appending_to_link) {
                    gchar *tmp;

                    tmp = g_strconcat (inner_html, text_to_append, NULL);
                    webkit_dom_html_element_set_inner_html (
                        WEBKIT_DOM_HTML_ELEMENT (parent),
                        tmp,
                        NULL);

                    remove_node (node);

                    g_free (tmp);
                }

                g_free (new_href);
                g_free (inner_html);
            }

        }
        g_free (text);
        g_free (href);
    }

    g_match_info_free (match_info);
    g_regex_unref (regex);
    g_free (node_text);
}

typedef struct _LoadContext LoadContext;

struct _LoadContext {
    EHTMLEditorView *view;
    gchar *content_type;
    gchar *name;
    EEmoticon *emoticon;
};

static LoadContext *
emoticon_load_context_new (EHTMLEditorView *view,
                           EEmoticon *emoticon)
{
    LoadContext *load_context;

    load_context = g_slice_new0 (LoadContext);
    load_context->view = view;
    load_context->emoticon = emoticon;

    return load_context;
}

static void
emoticon_load_context_free (LoadContext *load_context)
{
    g_free (load_context->content_type);
    g_free (load_context->name);
    g_slice_free (LoadContext, load_context);
}

static void
emoticon_read_async_cb (GFile *file,
                        GAsyncResult *result,
                        LoadContext *load_context)
{
    EHTMLEditorView *view = load_context->view;
    EEmoticon *emoticon = load_context->emoticon;
    GError *error = NULL;
    gchar *html, *node_text = NULL, *mime_type;
    gchar *base64_encoded, *output, *data;
    const gchar *emoticon_start;
    GFileInputStream *input_stream;
    GOutputStream *output_stream;
    gssize size;
    WebKitDOMDocument *document;
    WebKitDOMElement *span, *caret_position;
    WebKitDOMNode *node;
    WebKitDOMRange *range;

    input_stream = g_file_read_finish (file, result, &error);
    g_return_if_fail (!error && input_stream);

    output_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);

    size = g_output_stream_splice (
        output_stream, G_INPUT_STREAM (input_stream),
        G_OUTPUT_STREAM_SPLICE_NONE, NULL, &error);

    if (error || (size == -1))
        goto out;

    caret_position = e_html_editor_selection_save_caret_position (
        e_html_editor_view_get_selection (view));

    if (caret_position) {
        WebKitDOMNode *parent;

        parent = webkit_dom_node_get_parent_node (
            WEBKIT_DOM_NODE (caret_position));

        /* Situation when caret is restored in body and not in paragraph */
        if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
            caret_position = WEBKIT_DOM_ELEMENT (
                webkit_dom_node_remove_child (
                    WEBKIT_DOM_NODE (parent),
                    WEBKIT_DOM_NODE (caret_position),
                    NULL));

            caret_position = WEBKIT_DOM_ELEMENT (
                webkit_dom_node_insert_before (
                    webkit_dom_node_get_first_child (
                        WEBKIT_DOM_NODE (parent)),
                    WEBKIT_DOM_NODE (caret_position),
                    webkit_dom_node_get_first_child (
                        webkit_dom_node_get_first_child (
                            WEBKIT_DOM_NODE (parent))),
                    NULL));
        }
    }

    mime_type = g_content_type_get_mime_type (load_context->content_type);
    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    range = html_editor_view_get_dom_range (view);
    node = webkit_dom_range_get_end_container (range, NULL);
    if (WEBKIT_DOM_IS_TEXT (node))
        node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
    span = webkit_dom_document_create_element (document, "SPAN", NULL);

    data = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (output_stream));

    base64_encoded = g_base64_encode ((const guchar *) data, size);
    output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);

    /* Insert span with image representation and another one with text
     * represetation and hide/show them dependant on active composer mode */
    /* &#8203 == UNICODE_ZERO_WIDTH_SPACE */
    html = g_strdup_printf (
        "<span class=\"-x-evo-smiley-wrapper -x-evo-resizable-wrapper\">"
        "<img src=\"%s\" alt=\"%s\" x-evo-smiley=\"%s\" "
        "class=\"-x-evo-smiley-img\" data-inline data-name=\"%s\"/>"
        "<span class=\"-x-evo-smiley-text\" style=\"display: none;\">%s"
        "</span></span>&#8203;",
        output, emoticon ? emoticon->text_face : "", emoticon->icon_name,
        load_context->name, emoticon ? emoticon->text_face : "");

    span = WEBKIT_DOM_ELEMENT (
        webkit_dom_node_insert_before (
            webkit_dom_node_get_parent_node (
                WEBKIT_DOM_NODE (caret_position)),
            WEBKIT_DOM_NODE (span),
            WEBKIT_DOM_NODE (caret_position),
            NULL));

    webkit_dom_html_element_set_outer_html (
        WEBKIT_DOM_HTML_ELEMENT (span), html, NULL);

    if (node_text) {
        emoticon_start = g_utf8_strrchr (
            node_text, -1, g_utf8_get_char (emoticon->text_face));
        if (emoticon_start) {
            webkit_dom_character_data_delete_data (
                WEBKIT_DOM_CHARACTER_DATA (node),
                g_utf8_strlen (node_text, -1) - strlen (emoticon_start),
                strlen (emoticon->text_face),
                NULL);
        }
    }

    e_html_editor_selection_restore_caret_position (
        e_html_editor_view_get_selection (view));

    e_html_editor_view_set_changed (view, TRUE);

    g_free (html);
    g_free (node_text);
    g_free (base64_encoded);
    g_free (output);
    g_free (mime_type);
    g_object_unref (output_stream);
 out:
    emoticon_load_context_free (load_context);
}

static void
emoticon_query_info_async_cb (GFile *file,
                              GAsyncResult *result,
                              LoadContext *load_context)
{
    GError *error = NULL;
    GFileInfo *info;

    info = g_file_query_info_finish (file, result, &error);
    g_return_if_fail (!error && info);

    load_context->content_type = g_strdup (g_file_info_get_content_type (info));
    load_context->name = g_strdup (g_file_info_get_name (info));

    g_file_read_async (
        file, G_PRIORITY_DEFAULT, NULL,
        (GAsyncReadyCallback) emoticon_read_async_cb, load_context);

    g_object_unref (info);
}

void
e_html_editor_view_insert_smiley (EHTMLEditorView *view,
                                  EEmoticon *emoticon)
{
    GFile *file;
    gchar *filename_uri;
    LoadContext *load_context;

    filename_uri = e_emoticon_get_uri (emoticon);
    g_return_if_fail (filename_uri != NULL);

    load_context = emoticon_load_context_new (view, emoticon);

    file = g_file_new_for_uri (filename_uri);
    g_file_query_info_async (
        file,  "standard::*", G_FILE_QUERY_INFO_NONE,
        G_PRIORITY_DEFAULT, NULL,
        (GAsyncReadyCallback) emoticon_query_info_async_cb, load_context);

    g_free (filename_uri);
    g_object_unref (file);
}

static void
html_editor_view_check_magic_smileys (EHTMLEditorView *view,
                                      WebKitDOMRange *range)
{
    gint pos;
    gint state;
    gint relative;
    gint start;
    gchar *node_text;
    gunichar uc;
    WebKitDOMNode *node;

    node = webkit_dom_range_get_end_container (range, NULL);
    if (!WEBKIT_DOM_IS_TEXT (node))
        return;

    node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
    if (node_text == NULL)
        return;

    start = webkit_dom_range_get_end_offset (range, NULL) - 1;
    pos = start;
    state = 0;
    while (pos >= 0) {
        uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos));
        relative = 0;
        while (emoticons_chars[state + relative]) {
            if (emoticons_chars[state + relative] == uc)
                break;
            relative++;
        }
        state = emoticons_states[state + relative];
        /* 0 .. not found, -n .. found n-th */
        if (state <= 0)
            break;
        pos--;
    }

    /* Special case needed to recognize angel and devilish. */
    if (pos > 0 && state == -14) {
        uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1));
        if (uc == 'O') {
            state = -1;
            pos--;
        } else if (uc == '>') {
            state = -5;
            pos--;
        }
    }

    if (state < 0) {
        const EEmoticon *emoticon;

        if (pos > 0) {
            uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1));
            if (!g_unichar_isspace (uc)) {
                g_free (node_text);
                return;
            }
        }

        emoticon = (e_emoticon_chooser_lookup_emoticon (
            emoticons_icon_names[-state - 1]));
        e_html_editor_view_insert_smiley (view, (EEmoticon *) emoticon);
    }

    g_free (node_text);
}

static void
html_editor_view_set_links_active (EHTMLEditorView *view,
                                   gboolean active)
{
    WebKitDOMDocument *document;
    WebKitDOMElement *style;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

    if (active) {
        style = webkit_dom_document_get_element_by_id (
                document, "--evolution-editor-style-a");
        if (style)
            remove_node (WEBKIT_DOM_NODE (style));
    } else {
        WebKitDOMHTMLHeadElement *head;
        head = webkit_dom_document_get_head (document);

        style = webkit_dom_document_create_element (document, "STYLE", NULL);
        webkit_dom_element_set_id (style, "--evolution-editor-style-a");
        webkit_dom_html_element_set_inner_text (
            WEBKIT_DOM_HTML_ELEMENT (style), "a { cursor: text; }", NULL);

        webkit_dom_node_append_child (
            WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style), NULL);
    }
}

static void
clipboard_text_received (GtkClipboard *clipboard,
                         const gchar *text,
                         EHTMLEditorView *view)
{
    EHTMLEditorSelection *selection;
    gchar *escaped_text;
    WebKitDOMDocument *document;
    WebKitDOMDOMWindow *window;
    WebKitDOMDOMSelection *dom_selection;
    WebKitDOMElement *blockquote, *element;
    WebKitDOMNode *node;
    WebKitDOMRange *range;

    if (!text || !*text)
        return;

    selection = e_html_editor_view_get_selection (view);

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    window = webkit_dom_document_get_default_view (document);
    dom_selection = webkit_dom_dom_window_get_selection (window);

    /* This is a trick to escape any HTML characters (like <, > or &).
     * <textarea> automatically replaces all these unsafe characters
     * by &lt;, &gt; etc. */
    element = webkit_dom_document_create_element (document, "textarea", NULL);
    webkit_dom_html_element_set_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (element), text, NULL);
    escaped_text = webkit_dom_html_element_get_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (element));

    element = webkit_dom_document_create_element (document, "pre", NULL);

    webkit_dom_html_element_set_inner_text (
        WEBKIT_DOM_HTML_ELEMENT (element), escaped_text, NULL);

    webkit_dom_node_append_child (
        WEBKIT_DOM_NODE (element),
        e_html_editor_selection_get_caret_position_node (document),
        NULL);

    blockquote = webkit_dom_document_create_element (document, "blockquote", NULL);
    webkit_dom_element_set_attribute (blockquote, "type", "cite", NULL);

    webkit_dom_node_append_child (
        WEBKIT_DOM_NODE (blockquote), WEBKIT_DOM_NODE (element), NULL);

    if (!e_html_editor_view_get_html_mode (view))
        e_html_editor_view_quote_plain_text_element (view, element);

    range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
    node = webkit_dom_range_get_end_container (range, NULL);

    webkit_dom_node_append_child (
        webkit_dom_node_get_parent_node (node),
        WEBKIT_DOM_NODE (blockquote),
        NULL);

    e_html_editor_selection_restore_caret_position (selection);

    e_html_editor_view_force_spell_check_for_current_paragraph (view);

    g_free (escaped_text);
}

static void
html_editor_view_set_property (GObject *object,
                               guint property_id,
                               const GValue *value,
                               GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_CHANGED:
            e_html_editor_view_set_changed (
                E_HTML_EDITOR_VIEW (object),
                g_value_get_boolean (value));
            return;

        case PROP_HTML_MODE:
            e_html_editor_view_set_html_mode (
                E_HTML_EDITOR_VIEW (object),
                g_value_get_boolean (value));
            return;

        case PROP_INLINE_SPELLING:
            e_html_editor_view_set_inline_spelling (
                E_HTML_EDITOR_VIEW (object),
                g_value_get_boolean (value));
            return;

        case PROP_MAGIC_LINKS:
            e_html_editor_view_set_magic_links (
                E_HTML_EDITOR_VIEW (object),
                g_value_get_boolean (value));
            return;

        case PROP_MAGIC_SMILEYS:
            e_html_editor_view_set_magic_smileys (
                E_HTML_EDITOR_VIEW (object),
                g_value_get_boolean (value));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
html_editor_view_get_property (GObject *object,
                               guint property_id,
                               GValue *value,
                               GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_CAN_COPY:
            g_value_set_boolean (
                value, webkit_web_view_can_copy_clipboard (
                WEBKIT_WEB_VIEW (object)));
            return;

        case PROP_CAN_CUT:
            g_value_set_boolean (
                value, webkit_web_view_can_cut_clipboard (
                WEBKIT_WEB_VIEW (object)));
            return;

        case PROP_CAN_PASTE:
            g_value_set_boolean (
                value, webkit_web_view_can_paste_clipboard (
                WEBKIT_WEB_VIEW (object)));
            return;

        case PROP_CAN_REDO:
            g_value_set_boolean (
                value, webkit_web_view_can_redo (
                WEBKIT_WEB_VIEW (object)));
            return;

        case PROP_CAN_UNDO:
            g_value_set_boolean (
                value, webkit_web_view_can_undo (
                WEBKIT_WEB_VIEW (object)));
            return;

        case PROP_CHANGED:
            g_value_set_boolean (
                value, e_html_editor_view_get_changed (
                E_HTML_EDITOR_VIEW (object)));
            return;

        case PROP_HTML_MODE:
            g_value_set_boolean (
                value, e_html_editor_view_get_html_mode (
                E_HTML_EDITOR_VIEW (object)));
            return;

        case PROP_INLINE_SPELLING:
            g_value_set_boolean (
                value, e_html_editor_view_get_inline_spelling (
                E_HTML_EDITOR_VIEW (object)));
            return;

        case PROP_MAGIC_LINKS:
            g_value_set_boolean (
                value, e_html_editor_view_get_magic_links (
                E_HTML_EDITOR_VIEW (object)));
            return;

        case PROP_MAGIC_SMILEYS:
            g_value_set_boolean (
                value, e_html_editor_view_get_magic_smileys (
                E_HTML_EDITOR_VIEW (object)));
            return;

        case PROP_SPELL_CHECKER:
            g_value_set_object (
                value, e_html_editor_view_get_spell_checker (
                E_HTML_EDITOR_VIEW (object)));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
html_editor_view_dispose (GObject *object)
{
    EHTMLEditorViewPrivate *priv;

    priv = E_HTML_EDITOR_VIEW_GET_PRIVATE (object);

    g_clear_object (&priv->selection);

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

    if (priv->aliasing_settings != NULL) {
        g_signal_handlers_disconnect_matched (
            priv->aliasing_settings, G_SIGNAL_MATCH_DATA,
            0, 0, NULL, NULL, object);
        g_object_unref (priv->aliasing_settings);
        priv->aliasing_settings = NULL;
    }

    if (priv->font_settings != NULL) {
        g_signal_handlers_disconnect_matched (
            priv->font_settings, G_SIGNAL_MATCH_DATA,
            0, 0, NULL, NULL, object);
        g_object_unref (priv->font_settings);
        priv->font_settings = NULL;
    }

    g_hash_table_remove_all (priv->inline_images);

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

static void
html_editor_view_finalize (GObject *object)
{
    EHTMLEditorViewPrivate *priv;

    priv = E_HTML_EDITOR_VIEW_GET_PRIVATE (object);

    g_hash_table_destroy (priv->inline_images);

    if (priv->old_settings) {
        g_hash_table_destroy (priv->old_settings);
        priv->old_settings = NULL;
    }

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

static void
html_editor_view_constructed (GObject *object)
{
    e_extensible_load_extensions (E_EXTENSIBLE (object));

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

static void
html_editor_view_save_element_under_mouse_click (GtkWidget *widget)
{
    gint x, y;
    GdkDeviceManager *device_manager;
    GdkDevice *pointer;
    EHTMLEditorView *view;
    WebKitDOMDocument *document;
    WebKitDOMElement *element;

    g_return_if_fail (E_IS_HTML_EDITOR_VIEW (widget));

    device_manager = gdk_display_get_device_manager (
        gtk_widget_get_display (GTK_WIDGET (widget)));
    pointer = gdk_device_manager_get_client_pointer (device_manager);
    gdk_window_get_device_position (
        gtk_widget_get_window (GTK_WIDGET (widget)), pointer, &x, &y, NULL);

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (widget));
    element = webkit_dom_document_element_from_point (document, x, y);

    view = E_HTML_EDITOR_VIEW (widget);
    view->priv->element_under_mouse = element;
}

static gboolean
html_editor_view_button_press_event (GtkWidget *widget,
                                     GdkEventButton *event)
{
    gboolean event_handled;

    if (event->button == 2) {
        /* Middle click paste */
        g_signal_emit (widget, signals[PASTE_PRIMARY_CLIPBOARD], 0);
        event_handled = TRUE;
    } else if (event->button == 3) {
        html_editor_view_save_element_under_mouse_click (widget);
        g_signal_emit (
            widget, signals[POPUP_EVENT],
            0, event, &event_handled);
    } else {
        event_handled = FALSE;
    }

    if (event_handled)
        return TRUE;

    /* Chain up to parent's button_press_event() method. */
    return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
        button_press_event (widget, event);
}

static gboolean
html_editor_view_button_release_event (GtkWidget *widget,
                                       GdkEventButton *event)
{
    WebKitWebView *webview;
    WebKitHitTestResult *hit_test;
    WebKitHitTestResultContext context;
    gchar *uri;

    webview = WEBKIT_WEB_VIEW (widget);
    hit_test = webkit_web_view_get_hit_test_result (webview, event);

    g_object_get (
        hit_test,
        "context", &context,
        "link-uri", &uri,
        NULL);

    g_object_unref (hit_test);

    /* Left click on a link */
    if ((context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) &&
        (event->button == 1)) {

        /* Ctrl + Left Click on link opens it, otherwise ignore the
         * click completely */
        if (event->state & GDK_CONTROL_MASK) {
            GtkWidget *toplevel;
            GdkScreen *screen;

            toplevel = gtk_widget_get_toplevel (widget);
            screen = gtk_window_get_screen (GTK_WINDOW (toplevel));
            gtk_show_uri (screen, uri, event->time, NULL);
            g_free (uri);
        }

        return TRUE;
    }

    g_free (uri);

    /* Chain up to parent's button_release_event() method. */
    return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
        button_release_event (widget, event);
}

static gboolean
insert_new_line_into_citation (EHTMLEditorView *view)
{
    EHTMLEditorSelection *selection;
    gboolean html_mode, ret_val;

    html_mode = e_html_editor_view_get_html_mode (view);
    selection = e_html_editor_view_get_selection (view);

    ret_val = e_html_editor_view_exec_command (
        view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL);

    if (ret_val && !html_mode) {
        WebKitDOMElement *element;
        WebKitDOMDocument *document;
        WebKitDOMNode *next_sibling;

        document = webkit_web_view_get_dom_document (
            WEBKIT_WEB_VIEW (view));

        element = webkit_dom_document_query_selector (
            document, "body>br", NULL);

        next_sibling = webkit_dom_node_get_next_sibling (
            WEBKIT_DOM_NODE (element));

        if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (next_sibling)) {
            /* Quote content */
            next_sibling = WEBKIT_DOM_NODE (
                e_html_editor_view_quote_plain_text_element (
                    view, WEBKIT_DOM_ELEMENT (next_sibling)));
            /* Renew spellcheck */
            e_html_editor_view_force_spell_check (view);
            /* Insert caret node on right position */
            webkit_dom_node_insert_before (
                webkit_dom_node_get_parent_node (
                    WEBKIT_DOM_NODE (element)),
                e_html_editor_selection_get_caret_position_node (
                    document),
                WEBKIT_DOM_NODE (element),
                NULL);
            /* Restore caret position */
            e_html_editor_selection_restore_caret_position (
                selection);
        }
    }

    return ret_val;
}

static gboolean
prevent_from_deleting_last_element_in_body (EHTMLEditorView *view)
{
    gboolean ret_val = FALSE;
    WebKitDOMDocument *document;
    WebKitDOMHTMLElement *body;
    WebKitDOMNodeList *list;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    body = webkit_dom_document_get_body (document);

    list = webkit_dom_node_get_child_nodes (WEBKIT_DOM_NODE (body));

    if (webkit_dom_node_list_get_length (list) <= 1) {
        gchar *content;

        content = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (body));

        if (!*content)
            ret_val = TRUE;

        g_free (content);

        if (webkit_dom_element_query_selector (WEBKIT_DOM_ELEMENT (body), "img", NULL))
            ret_val = FALSE;
    }

    return ret_val;
}

static gboolean
html_editor_view_key_press_event (GtkWidget *widget,
                                  GdkEventKey *event)
{
    EHTMLEditorView *view = E_HTML_EDITOR_VIEW (widget);

    if (event->keyval == GDK_KEY_Tab)
        return e_html_editor_view_exec_command (
            view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, "\t");

    if ((event->keyval == GDK_KEY_Control_L) ||
        (event->keyval == GDK_KEY_Control_R)) {

        html_editor_view_set_links_active (view, TRUE);
    }

    if (is_return_key (event)) {
        EHTMLEditorSelection *selection;

        selection = e_html_editor_view_get_selection (view);
        /* When user presses ENTER in a citation block, WebKit does
         * not break the citation automatically, so we need to use
         * the special command to do it. */
        if (e_html_editor_selection_is_citation (selection))
            return insert_new_line_into_citation (view);
    }

    /* BackSpace in indented block decrease indent level by one */
    if (event->keyval == GDK_KEY_BackSpace) {
        EHTMLEditorSelection *selection;

        selection = e_html_editor_view_get_selection (view);
        if (e_html_editor_selection_is_indented (selection)) {
            WebKitDOMElement *caret;

            caret = e_html_editor_selection_save_caret_position (selection);

            if (!webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (caret))) {
                e_html_editor_selection_clear_caret_position_marker (selection);
                e_html_editor_selection_unindent (selection);
                return TRUE;
            } else
                e_html_editor_selection_clear_caret_position_marker (selection);
        }

        if (prevent_from_deleting_last_element_in_body (view))
            return TRUE;
    }

    /* Chain up to parent's key_press_event() method. */
    return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
        key_press_event (widget, event);
}

static void
mark_node_as_paragraph_after_ending_list (EHTMLEditorSelection *selection,
                                          WebKitDOMDocument *document)
{
    gint ii, length;
    WebKitDOMNodeList *list;

    /* When pressing Enter on empty line in the list WebKit will end that
     * list and inserts <div><br></div> so mark it for wrapping */
    list = webkit_dom_document_query_selector_all (
        document, "body > div:not(.-x-evo-paragraph) > br", NULL);

    length = webkit_dom_node_list_get_length (list);
    for (ii = 0; ii < length; ii++) {
        WebKitDOMNode *node = webkit_dom_node_get_parent_node (
            webkit_dom_node_list_item (list, ii));

        e_html_editor_selection_set_paragraph_style (
            selection, WEBKIT_DOM_ELEMENT (node), -1, 0, "");
    }
}

static gboolean
surround_text_with_paragraph_if_needed (EHTMLEditorSelection *selection,
                                        WebKitDOMDocument *document,
                                        WebKitDOMNode *node)
{
    WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (node);
    WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (node);
    WebKitDOMElement *element;

    /* All text in composer has to be written in div elements, so if
     * we are writing something straight to the body, surround it with
     * paragraph */
    if (WEBKIT_DOM_IS_TEXT (node) &&
        WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (node))) {
        element = e_html_editor_selection_put_node_into_paragraph (
            selection,
            document,
            node,
            e_html_editor_selection_get_caret_position_node (document));

        if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling))
            remove_node (next_sibling);

        /* Tab character */
        if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
            element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "Apple-tab-span")) {
            webkit_dom_node_insert_before (
                WEBKIT_DOM_NODE (element),
                prev_sibling,
                webkit_dom_node_get_first_child (
                    WEBKIT_DOM_NODE (element)),
                NULL);
        }

        return TRUE;
    }

    return FALSE;
}

static gboolean
html_editor_view_key_release_event (GtkWidget *widget,
                                    GdkEventKey *event)
{
    WebKitDOMDocument *document;
    WebKitDOMRange *range;
    EHTMLEditorView *view;
    EHTMLEditorSelection *selection;

    view = E_HTML_EDITOR_VIEW (widget);
    range = html_editor_view_get_dom_range (view);
    selection = e_html_editor_view_get_selection (view);

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (widget));

    if (view->priv->magic_smileys && view->priv->html_mode)
        html_editor_view_check_magic_smileys (view, range);

    if (is_return_key (event) || (event->keyval == GDK_KEY_space)) {
        html_editor_view_check_magic_links (view, range, FALSE, event);
        mark_node_as_paragraph_after_ending_list (selection, document);
    } else {
        WebKitDOMNode *node;

        node = webkit_dom_range_get_end_container (range, NULL);

        if (surround_text_with_paragraph_if_needed (selection, document, node)) {
            e_html_editor_selection_restore_caret_position (selection);
            node = webkit_dom_range_get_end_container (range, NULL);
            range = html_editor_view_get_dom_range (view);
        }

        if (WEBKIT_DOM_IS_TEXT (node)) {
            gchar *text;

            text = webkit_dom_node_get_text_content (node);

            if (g_strcmp0 (text, "") != 0 && !g_unichar_isspace (g_utf8_get_char (text))) {
                WebKitDOMNode *prev_sibling;

                prev_sibling = webkit_dom_node_get_previous_sibling (node);

                if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling))
                    html_editor_view_check_magic_links (view, range, FALSE, event);
            }
            g_free (text);
        }
    }

    if ((event->keyval == GDK_KEY_Control_L) ||
        (event->keyval == GDK_KEY_Control_R)) {

        html_editor_view_set_links_active (view, FALSE);
    }

    /* Chain up to parent's key_release_event() method. */
    return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
        key_release_event (widget, event);
}

static void
html_editor_view_paste_clipboard_quoted (EHTMLEditorView *view)
{
    GtkClipboard *clipboard;

    clipboard = gtk_clipboard_get_for_display (
        gdk_display_get_default (),
        GDK_SELECTION_CLIPBOARD);

    gtk_clipboard_request_text (
        clipboard,
        (GtkClipboardTextReceivedFunc) clipboard_text_received,
        view);
}

static gboolean
html_editor_view_image_exists_in_cache (const gchar *image_uri)
{
    gchar *filename;
    gchar *hash;
    gboolean exists = FALSE;

    g_return_val_if_fail (emd_global_http_cache != NULL, FALSE);

    hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, image_uri, -1);
    filename = camel_data_cache_get_filename (
        emd_global_http_cache, "http", hash);

    if (filename != NULL) {
        exists = g_file_test (filename, G_FILE_TEST_EXISTS);
        g_free (filename);
    }

    g_free (hash);

    return exists;
}

static gchar *
html_editor_view_redirect_uri (EHTMLEditorView *view,
                               const gchar *uri)
{
    EImageLoadingPolicy image_policy;
    GSettings *settings;
    gboolean uri_is_http;

    uri_is_http =
        g_str_has_prefix (uri, "http:") ||
        g_str_has_prefix (uri, "https:") ||
        g_str_has_prefix (uri, "evo-http:") ||
        g_str_has_prefix (uri, "evo-https:");

    /* Redirect http(s) request to evo-http(s) protocol.
     * See EMailRequest for further details about this. */
    if (uri_is_http) {
        gchar *new_uri;
        SoupURI *soup_uri;
        gboolean image_exists;

        /* Check Evolution's cache */
        image_exists = html_editor_view_image_exists_in_cache (uri);

        settings = g_settings_new ("org.gnome.evolution.mail");
        image_policy = g_settings_get_enum (settings, "image-loading-policy");
        g_object_unref (settings);
        /* If the URI is not cached and we are not allowed to load it
         * then redirect to invalid URI, so that webkit would display
         * a native placeholder for it. */
        if (!image_exists && (image_policy == E_IMAGE_LOADING_POLICY_NEVER)) {
            return g_strdup ("about:blank");
        }

        new_uri = g_strconcat ("evo-", uri, NULL);
        soup_uri = soup_uri_new (new_uri);
        g_free (new_uri);

        new_uri = soup_uri_to_string (soup_uri, FALSE);

        soup_uri_free (soup_uri);

        return new_uri;
    }

    return g_strdup (uri);
}

static void
html_editor_view_resource_requested (WebKitWebView *web_view,
                                     WebKitWebFrame *frame,
                                     WebKitWebResource *resource,
                                     WebKitNetworkRequest *request,
                                     WebKitNetworkResponse *response,
                                     gpointer user_data)
{
    const gchar *original_uri;

    original_uri = webkit_network_request_get_uri (request);

    if (original_uri != NULL) {
        gchar *redirected_uri;

        redirected_uri = html_editor_view_redirect_uri (
            E_HTML_EDITOR_VIEW (web_view), original_uri);

        webkit_network_request_set_uri (request, redirected_uri);

        g_free (redirected_uri);
    }
}

static void
e_html_editor_view_class_init (EHTMLEditorViewClass *class)
{
    GObjectClass *object_class;
    GtkWidgetClass *widget_class;

    g_type_class_add_private (class, sizeof (EHTMLEditorViewPrivate));

    object_class = G_OBJECT_CLASS (class);
    object_class->get_property = html_editor_view_get_property;
    object_class->set_property = html_editor_view_set_property;
    object_class->dispose = html_editor_view_dispose;
    object_class->finalize = html_editor_view_finalize;
    object_class->constructed = html_editor_view_constructed;

    widget_class = GTK_WIDGET_CLASS (class);
    widget_class->button_press_event = html_editor_view_button_press_event;
    widget_class->button_release_event = html_editor_view_button_release_event;
    widget_class->key_press_event = html_editor_view_key_press_event;
    widget_class->key_release_event = html_editor_view_key_release_event;

    class->paste_clipboard_quoted = html_editor_view_paste_clipboard_quoted;

    /**
     * EHTMLEditorView:can-copy
     *
     * Determines whether it's possible to copy to clipboard. The action
     * is usually disabled when there is no selection to copy.
     */
    g_object_class_install_property (
        object_class,
        PROP_CAN_COPY,
        g_param_spec_boolean (
            "can-copy",
            "Can Copy",
            NULL,
            FALSE,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));

    /**
     * EHTMLEditorView:can-cut
     *
     * Determines whether it's possible to cut to clipboard. The action
     * is usually disabled when there is no selection to cut.
     */
    g_object_class_install_property (
        object_class,
        PROP_CAN_CUT,
        g_param_spec_boolean (
            "can-cut",
            "Can Cut",
            NULL,
            FALSE,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));

    /**
     * EHTMLEditorView:can-paste
     *
     * Determines whether it's possible to paste from clipboard. The action
     * is usually disabled when there is no valid content in clipboard to
     * paste.
     */
    g_object_class_install_property (
        object_class,
        PROP_CAN_PASTE,
        g_param_spec_boolean (
            "can-paste",
            "Can Paste",
            NULL,
            FALSE,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));

    /**
     * EHTMLEditorView:can-redo
     *
     * Determines whether it's possible to redo previous action. The action
     * is usually disabled when there is no action to redo.
     */
    g_object_class_install_property (
        object_class,
        PROP_CAN_REDO,
        g_param_spec_boolean (
            "can-redo",
            "Can Redo",
            NULL,
            FALSE,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));

    /**
     * EHTMLEditorView:can-undo
     *
     * Determines whether it's possible to undo last action. The action
     * is usually disabled when there is no previous action to undo.
     */
    g_object_class_install_property (
        object_class,
        PROP_CAN_UNDO,
        g_param_spec_boolean (
            "can-undo",
            "Can Undo",
            NULL,
            FALSE,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));

    /**
     * EHTMLEditorView:changed
     *
     * Determines whether document has been modified
     */
    g_object_class_install_property (
        object_class,
        PROP_CHANGED,
        g_param_spec_boolean (
            "changed",
            _("Changed property"),
            _("Whether editor changed"),
            FALSE,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    /**
     * EHTMLEditorView:html-mode
     *
     * Determines whether HTML or plain text mode is enabled.
     **/
    g_object_class_install_property (
        object_class,
        PROP_HTML_MODE,
        g_param_spec_boolean (
            "html-mode",
            "HTML Mode",
            "Edit HTML or plain text",
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT |
            G_PARAM_STATIC_STRINGS));

    /**
     * EHTMLEditorView::inline-spelling
     *
     * Determines whether automatic spellchecking is enabled.
     */
    g_object_class_install_property (
        object_class,
        PROP_INLINE_SPELLING,
        g_param_spec_boolean (
            "inline-spelling",
            "Inline Spelling",
            "Check your spelling as you type",
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT |
            G_PARAM_STATIC_STRINGS));

    /**
     * EHTMLEditorView:magic-links
     *
     * Determines whether automatic conversion of text links into
     * HTML links is enabled.
     */
    g_object_class_install_property (
        object_class,
        PROP_MAGIC_LINKS,
        g_param_spec_boolean (
            "magic-links",
            "Magic Links",
            "Make URIs clickable as you type",
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT |
            G_PARAM_STATIC_STRINGS));

    /**
     * EHTMLEditorView:magic-smileys
     *
     * Determines whether automatic conversion of text smileys into
     * images is enabled.
     */
    g_object_class_install_property (
        object_class,
        PROP_MAGIC_SMILEYS,
        g_param_spec_boolean (
            "magic-smileys",
            "Magic Smileys",
            "Convert emoticons to images as you type",
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT |
            G_PARAM_STATIC_STRINGS));

    /**
     * EHTMLEditorView:spell-checker:
     *
     * The #ESpellChecker used for spell checking.
     **/
    g_object_class_install_property (
        object_class,
        PROP_SPELL_CHECKER,
        g_param_spec_object (
            "spell-checker",
            "Spell Checker",
            "The spell checker",
            E_TYPE_SPELL_CHECKER,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));

    /**
     * EHTMLEditorView:popup-event
     *
     * Emitted whenever a context menu is requested.
     */
    signals[POPUP_EVENT] = g_signal_new (
        "popup-event",
        G_TYPE_FROM_CLASS (class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (EHTMLEditorViewClass, popup_event),
        g_signal_accumulator_true_handled, NULL,
        e_marshal_BOOLEAN__BOXED,
        G_TYPE_BOOLEAN, 1,
        GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
    /**
     * EHTMLEditorView:paste-primary-clipboad
     *
     * Emitted when user presses middle button on EHTMLEditorView
     */
    signals[PASTE_PRIMARY_CLIPBOARD] = g_signal_new (
        "paste-primary-clipboard",
        G_TYPE_FROM_CLASS (class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (EHTMLEditorViewClass, paste_primary_clipboard),
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);
}

/* This parses the HTML code (that contains just text, &nbsp; and BR elements)
 * into paragraphs.
 * HTML code in that format we can get by taking innerText from some element,
 * setting it to another one and finally getting innerHTML from it */
static void
parse_html_into_paragraphs (EHTMLEditorView *view,
                            WebKitDOMDocument *document,
                            WebKitDOMElement *blockquote,
                            const gchar *html,
                            gboolean use_pre)
{
    const gchar *prev_br, *next_br;
    gchar *inner_html;
    gint citation_level = 0;
    GString *start, *end;
    gboolean ignore_next_br = FALSE;

    webkit_dom_html_element_set_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (blockquote), "", NULL);

    prev_br = html;
    next_br = strstr (prev_br, "<br>");

    while (next_br) {
        gboolean local_ignore_next_br = ignore_next_br;
        const gchar *citation = NULL, *citation_end = NULL;
        const gchar *rest = NULL, *with_br = NULL;
        gchar *to_insert = NULL;
        WebKitDOMElement *paragraph;

        to_insert = g_utf8_substring (
            prev_br, 0, g_utf8_pointer_to_offset (prev_br, next_br));

        with_br = strstr (to_insert, "<br>");

        ignore_next_br = FALSE;

        citation = strstr (to_insert, "##CITATION_");
        if (citation) {
            if (strstr (to_insert, "##CITATION_START##"))
                citation_level++;
            else
                citation_level--;

            citation_end = strstr (citation + 2, "##");
            if (citation_end)
                rest = citation_end + 2;
        } else {
            rest = with_br ?
                to_insert + 4 + (with_br - to_insert) : to_insert;
        }

        if (use_pre) {
            paragraph = webkit_dom_document_create_element (
                document, "pre", NULL);
        } else {
            paragraph = e_html_editor_selection_get_paragraph_element (
                e_html_editor_view_get_selection (view),
                document, -1, 0);
        }

        if (with_br && !*rest && !citation &&!local_ignore_next_br) {
            WebKitDOMNode *paragraph_clone;

            paragraph_clone = webkit_dom_node_clone_node (
                WEBKIT_DOM_NODE (paragraph), TRUE);

            webkit_dom_html_element_set_inner_html (
                WEBKIT_DOM_HTML_ELEMENT (paragraph_clone),
                "&nbsp;",
                NULL);

            webkit_dom_node_append_child (
                WEBKIT_DOM_NODE (blockquote),
                paragraph_clone,
                NULL);
        }

        if (citation) {
            WebKitDOMText *text;
            gchar *citation_mark;

            citation_mark = g_utf8_substring (
                citation, 0,
                g_utf8_pointer_to_offset (
                    citation, citation_end + 2));

            text = webkit_dom_document_create_text_node (
                document, citation_mark);

            webkit_dom_node_append_child (
                WEBKIT_DOM_NODE (blockquote),
                WEBKIT_DOM_NODE (text),
                NULL);

            g_free (citation_mark);
        }

        if (rest && *rest){
            gchar *rest_to_insert = g_strdup (rest);
            GString *space_to_nbsp;

            g_strchomp (rest_to_insert);
            space_to_nbsp = e_str_replace_string (
                rest_to_insert, " ", "&nbsp;");

            webkit_dom_html_element_set_inner_html (
                WEBKIT_DOM_HTML_ELEMENT (paragraph),
                space_to_nbsp->str,
                NULL);

            webkit_dom_node_append_child (
                WEBKIT_DOM_NODE (blockquote),
                WEBKIT_DOM_NODE (paragraph),
                NULL);

            g_string_free (space_to_nbsp, TRUE);
            g_free (rest_to_insert);
        }

        if (citation_end)
            ignore_next_br = TRUE;

        prev_br = next_br;
        next_br = strstr (prev_br + 4, "<br>");
        g_free (to_insert);
    }

    if (g_utf8_strlen (prev_br, -1) > 0 && (g_strcmp0 (prev_br, "<br>") != 0)) {
        WebKitDOMElement *paragraph;

        if (use_pre) {
            paragraph = webkit_dom_document_create_element (
                document, "pre", NULL);
        } else {
            paragraph = e_html_editor_selection_get_paragraph_element (
                e_html_editor_view_get_selection (view),
                document, -1, 0);
        }

        webkit_dom_html_element_set_inner_html (
            WEBKIT_DOM_HTML_ELEMENT (paragraph),
            g_str_has_prefix (prev_br, "<br>") ? prev_br + 4 : prev_br,
            NULL);

        webkit_dom_node_append_child (
            WEBKIT_DOM_NODE (blockquote),
            WEBKIT_DOM_NODE (paragraph),
            NULL);
    }

    /* Replace text markers with actual HTML blockquotes */
    inner_html = webkit_dom_html_element_get_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (blockquote));
    start = e_str_replace_string (
        inner_html, "##CITATION_START##","<blockquote type=\"cite\">");
    end = e_str_replace_string (
        start->str, "##CITATION_END##", "</blockquote>");
    webkit_dom_html_element_set_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (blockquote), end->str, NULL);

    g_free (inner_html);
    g_string_free (start, TRUE);
    g_string_free (end, TRUE);
}

static void
mark_citation (WebKitDOMElement *citation)
{
    gchar *inner_html, *surrounded;

    inner_html = webkit_dom_html_element_get_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (citation));

    surrounded = g_strconcat (
        "<span>##CITATION_START##</span>", inner_html,
        "<span>##CITATION_END##</span>", NULL);

    webkit_dom_html_element_set_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (citation), surrounded, NULL);

    element_add_class (citation, "marked");

    g_free (inner_html);
    g_free (surrounded);
}

static gint
create_text_markers_for_citations_in_document (WebKitDOMDocument *document)
{
    gint count = 0;
    WebKitDOMElement *citation;

    citation = webkit_dom_document_query_selector (
        document, "blockquote[type=cite]:not(.marked)", NULL);

    while (citation) {
        mark_citation (citation);
        count ++;

        citation = webkit_dom_document_query_selector (
            document, "blockquote[type=cite]:not(.marked)", NULL);
    }

    return count;
}

static gint
create_text_markers_for_citations_in_element (WebKitDOMElement *element)
{
    gint count = 0;
    WebKitDOMElement *citation;

    citation = webkit_dom_element_query_selector (
        element, "blockquote[type=cite]:not(.marked)", NULL);

    while (citation) {
        mark_citation (citation);
        count ++;

        citation = webkit_dom_element_query_selector (
            element, "blockquote[type=cite]:not(.marked)", NULL);
    }

    return count;
}

static void
html_editor_view_process_document_from_convertor (EHTMLEditorView *view,
                                                  WebKitDOMDocument *document_convertor)
{
    EHTMLEditorSelection *selection = e_html_editor_view_get_selection (view);
    gboolean start_bottom;
    gchar *inner_text, *inner_html;
    gint ii;
    GSettings *settings;
    WebKitDOMDocument *document;
    WebKitDOMElement *paragraph, *new_blockquote, *top_signature;
    WebKitDOMElement *cite_body, *signature;
    WebKitDOMHTMLElement *body, *body_convertor;
    WebKitDOMNodeList *list;

    settings = g_settings_new ("org.gnome.evolution.mail");
    start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom");
    g_object_unref (settings);

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    body = webkit_dom_document_get_body (document);
    body_convertor = webkit_dom_document_get_body (document_convertor);

    webkit_dom_element_set_attribute (
        WEBKIT_DOM_ELEMENT (body), "data-converted", "", NULL);

    paragraph = webkit_dom_document_get_element_by_id (document, "-x-evo-input-start");
    if (!paragraph) {
        paragraph = e_html_editor_selection_get_paragraph_element (
            selection, document, -1, 0);
        webkit_dom_element_set_id (paragraph, "-x-evo-input-start");
        webkit_dom_html_element_set_inner_text (
            WEBKIT_DOM_HTML_ELEMENT (paragraph), UNICODE_ZERO_WIDTH_SPACE, NULL);
        webkit_dom_node_append_child (
            WEBKIT_DOM_NODE (webkit_dom_document_get_body (document)),
            WEBKIT_DOM_NODE (paragraph),
            NULL);
    }

    list = webkit_dom_document_query_selector_all (
        document_convertor, "span.-x-evo-to-body", NULL);
    for (ii = webkit_dom_node_list_get_length (list) - 1; ii >= 0; ii--) {
        WebKitDOMNode *node;

        node = webkit_dom_node_list_item (list, ii);
        while (webkit_dom_node_has_child_nodes (node)) {
            webkit_dom_node_insert_before (
                WEBKIT_DOM_NODE (body),
                webkit_dom_node_clone_node (
                    webkit_dom_node_get_first_child (node), TRUE),
                webkit_dom_node_get_next_sibling (
                    WEBKIT_DOM_NODE (paragraph)),
                NULL);

            remove_node (webkit_dom_node_get_first_child (node));
        }

        remove_node (node);
    }

    repair_gmail_blockquotes (document_convertor);

    create_text_markers_for_citations_in_document (document_convertor);

    /* Get innertText from convertor */
    inner_text = webkit_dom_html_element_get_inner_text (body_convertor);

    cite_body = webkit_dom_document_query_selector (
        document_convertor, "span.-x-evo-cite-body", NULL);

    top_signature = webkit_dom_document_query_selector (
        document, ".-x-evo-top-signature", NULL);
    signature = webkit_dom_document_query_selector (
        document, "span.-x-evo-signature", NULL);

    if (cite_body) {
        if (!(top_signature && start_bottom))
            e_html_editor_selection_save_caret_position (selection);
    } else {
        webkit_dom_node_append_child (
            WEBKIT_DOM_NODE (paragraph),
            WEBKIT_DOM_NODE (
                e_html_editor_selection_get_caret_position_node (
                    document)),
            NULL);
    }

    new_blockquote = webkit_dom_document_create_element (
        document, "blockquote", NULL);
    webkit_dom_element_set_attribute (
        new_blockquote, "type", "cite", NULL);

    webkit_dom_html_element_set_inner_text (
        WEBKIT_DOM_HTML_ELEMENT (new_blockquote), inner_text, NULL);
    inner_html = webkit_dom_html_element_get_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (new_blockquote));

    if (cite_body) {
        webkit_dom_element_set_attribute (
            new_blockquote, "id", "-x-evo-main-cite", NULL);

        parse_html_into_paragraphs (
            view, document, new_blockquote, inner_html, FALSE);

        if (start_bottom) {
            if (signature) {
                WebKitDOMNode *parent =
                    webkit_dom_node_get_parent_node (
                        WEBKIT_DOM_NODE (signature));
                if (top_signature) {
                    webkit_dom_node_append_child (
                        WEBKIT_DOM_NODE (body),
                        WEBKIT_DOM_NODE (new_blockquote),
                        NULL);
                    webkit_dom_node_append_child (
                        WEBKIT_DOM_NODE (body),
                        WEBKIT_DOM_NODE (paragraph),
                        NULL);
                    webkit_dom_node_append_child (
                        WEBKIT_DOM_NODE (paragraph),
                        e_html_editor_selection_get_caret_position_node (
                            document),
                        NULL);
                } else {
                    webkit_dom_node_insert_before (
                        WEBKIT_DOM_NODE (body),
                        WEBKIT_DOM_NODE (new_blockquote),
                        WEBKIT_DOM_NODE (parent),
                        NULL);
                    webkit_dom_node_insert_before (
                        WEBKIT_DOM_NODE (body),
                        WEBKIT_DOM_NODE (paragraph),
                        webkit_dom_node_get_next_sibling (
                            WEBKIT_DOM_NODE (new_blockquote)),
                        NULL);
                }
            } else {
                webkit_dom_node_append_child (
                    WEBKIT_DOM_NODE (body),
                    WEBKIT_DOM_NODE (new_blockquote),
                    NULL);
                webkit_dom_node_insert_before (
                    WEBKIT_DOM_NODE (body),
                    WEBKIT_DOM_NODE (paragraph),
                    webkit_dom_node_get_next_sibling (
                        WEBKIT_DOM_NODE (new_blockquote)),
                    NULL);
            }
        } else {
            if (signature) {
                WebKitDOMNode *parent =
                    webkit_dom_node_get_parent_node (
                        WEBKIT_DOM_NODE (signature));

                if (top_signature) {
                    WebKitDOMElement *br;

                    br = webkit_dom_document_create_element (
                        document, "BR", NULL);

                    webkit_dom_node_append_child (
                        WEBKIT_DOM_NODE (body),
                        WEBKIT_DOM_NODE (new_blockquote),
                        NULL);
                    /* Insert NL after signature */
                    webkit_dom_node_insert_before (
                        WEBKIT_DOM_NODE (body),
                        WEBKIT_DOM_NODE (br),
                        webkit_dom_node_get_next_sibling (
                            WEBKIT_DOM_NODE (paragraph)),
                        NULL);
                    webkit_dom_node_insert_before (
                        WEBKIT_DOM_NODE (body),
                        WEBKIT_DOM_NODE (parent),
                        WEBKIT_DOM_NODE (br),
                        NULL);
                } else
                    webkit_dom_node_insert_before (
                        WEBKIT_DOM_NODE (body),
                        WEBKIT_DOM_NODE (new_blockquote),
                        WEBKIT_DOM_NODE (parent),
                        NULL);
            } else {
                webkit_dom_node_append_child (
                    WEBKIT_DOM_NODE (body),
                    WEBKIT_DOM_NODE (new_blockquote),
                    NULL);
            }
        }
    } else {
        WebKitDOMNode *signature_clone, *first_child;

        if (signature) {
            signature_clone = webkit_dom_node_clone_node (
                webkit_dom_node_get_parent_node (
                    WEBKIT_DOM_NODE (signature)),
                TRUE);
        }

        parse_html_into_paragraphs (
            view, document, WEBKIT_DOM_ELEMENT (body),
            inner_html, FALSE);

        if (signature) {
            if (!top_signature) {
                signature_clone = webkit_dom_node_append_child (
                    WEBKIT_DOM_NODE (body),
                    signature_clone,
                    NULL);
            } else {
                webkit_dom_node_insert_before (
                    WEBKIT_DOM_NODE (body),
                    signature_clone,
                    webkit_dom_node_get_first_child (
                        WEBKIT_DOM_NODE (body)),
                    NULL);
            }
        }

        first_child = webkit_dom_node_get_first_child (
            WEBKIT_DOM_NODE (body));

        webkit_dom_node_insert_before (
            first_child,
            e_html_editor_selection_get_caret_position_node (
                document),
            webkit_dom_node_get_first_child (first_child),
            NULL);
    }

    if (!e_html_editor_view_get_html_mode (view))
        e_html_editor_selection_wrap_paragraphs_in_document (
            selection, document);
    if (webkit_dom_document_query_selector (document, "blockquote[type=cite]", NULL))
        body = WEBKIT_DOM_HTML_ELEMENT (
            e_html_editor_view_quote_plain_text (view));

    e_html_editor_selection_restore_caret_position (selection);
    e_html_editor_view_force_spell_check (view);

    /* Register on input event that is called when the content (body) is modified */
    webkit_dom_event_target_add_event_listener (
        WEBKIT_DOM_EVENT_TARGET (body),
        "input",
        G_CALLBACK (body_input_event_cb),
        FALSE,
        view);

    g_free (inner_html);
    g_free (inner_text);
}

static void
html_editor_view_insert_converted_html_into_selection (EHTMLEditorView *view,
                                                       WebKitDOMDocument *document_convertor)
{
    gchar *inner_text, *inner_html;
    WebKitDOMDocument *document;
    WebKitDOMElement *element;
    WebKitDOMHTMLElement *convertor_body;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

    convertor_body = webkit_dom_document_get_body (document_convertor);

    inner_text = webkit_dom_html_element_get_inner_text (convertor_body);
    element = webkit_dom_document_create_element (document, "div", NULL);
    webkit_dom_html_element_set_inner_text (
        WEBKIT_DOM_HTML_ELEMENT (element), inner_text, NULL);
    inner_html = webkit_dom_html_element_get_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (element));

    parse_html_into_paragraphs (
        view, document, element, inner_html, FALSE);

    g_free (inner_html);

    inner_html = webkit_dom_html_element_get_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (element));

    e_html_editor_view_exec_command (
        view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML, inner_html);

    e_html_editor_view_force_spell_check (view);

    g_free (inner_html);
    g_free (inner_text);
}

static void
html_plain_text_convertor_load_status_changed (WebKitWebView *web_view,
                                               GParamSpec *pspec,
                                               EHTMLEditorView *view)
{
    WebKitDOMDocument *document_convertor;

    if (webkit_web_view_get_load_status (web_view) != WEBKIT_LOAD_FINISHED)
        return;

    document_convertor = webkit_web_view_get_dom_document (web_view);

    if (view->priv->convertor_insert)
        html_editor_view_insert_converted_html_into_selection (
            view, document_convertor);
    else
        html_editor_view_process_document_from_convertor (
            view, document_convertor);
}

static void
e_html_editor_settings_changed_cb (GSettings *settings,
                   const gchar *key,
                   EHTMLEditorView *view)
{
    GVariant *new_value, *old_value;

    new_value = g_settings_get_value (settings, key);
    old_value = g_hash_table_lookup (view->priv->old_settings, key);

    if (!new_value || !old_value || !g_variant_equal (new_value, old_value)) {
        if (new_value)
            g_hash_table_insert (view->priv->old_settings, g_strdup (key), new_value);
        else
            g_hash_table_remove (view->priv->old_settings, key);

        e_html_editor_view_update_fonts (view);
    } else if (new_value) {
        g_variant_unref (new_value);
    }
}

static void
e_html_editor_view_init (EHTMLEditorView *view)
{
    WebKitWebSettings *settings;
    GSettings *g_settings;
    GSettingsSchema *settings_schema;
    ESpellChecker *checker;
    gchar **languages;
    gchar *comma_separated;
    const gchar *user_cache_dir;

    view->priv = E_HTML_EDITOR_VIEW_GET_PRIVATE (view);

    webkit_web_view_set_editable (WEBKIT_WEB_VIEW (view), TRUE);
    settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));

    g_object_set (
        G_OBJECT (settings),
        "enable-developer-extras", TRUE,
        "enable-dom-paste", TRUE,
        "enable-file-access-from-file-uris", TRUE,
        "enable-plugins", FALSE,
        "enable-scripts", FALSE,
        "enable-spell-checking", TRUE,
        "respect-image-orientation", TRUE,
        NULL);

    webkit_web_view_set_settings (WEBKIT_WEB_VIEW (view), settings);

    view->priv->old_settings = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);

    /* Override the spell-checker, use our own */
    checker = e_spell_checker_new ();
    webkit_set_text_checker (G_OBJECT (checker));
    g_object_unref (checker);

    /* Don't use CSS when possible to preserve compatibility with older
     * versions of Evolution or other MUAs */
    e_html_editor_view_exec_command (
        view, E_HTML_EDITOR_VIEW_COMMAND_STYLE_WITH_CSS, "false");

    g_signal_connect (
        view, "user-changed-contents",
        G_CALLBACK (html_editor_view_user_changed_contents_cb), NULL);
    g_signal_connect (
        view, "selection-changed",
        G_CALLBACK (html_editor_view_selection_changed_cb), NULL);
    g_signal_connect (
        view, "should-show-delete-interface-for-element",
        G_CALLBACK (html_editor_view_should_show_delete_interface_for_element), NULL);
    g_signal_connect (
        view, "resource-request-starting",
        G_CALLBACK (html_editor_view_resource_requested), NULL);
    g_signal_connect (
        view, "notify::load-status",
        G_CALLBACK (html_editor_view_load_status_changed), NULL);

    view->priv->selection = g_object_new (
        E_TYPE_HTML_EDITOR_SELECTION,
        "html-editor-view", view,
        NULL);

    g_settings = g_settings_new ("org.gnome.desktop.interface");
    g_signal_connect (
        g_settings, "changed::font-name",
        G_CALLBACK (e_html_editor_settings_changed_cb), view);
    g_signal_connect (
        g_settings, "changed::monospace-font-name",
        G_CALLBACK (e_html_editor_settings_changed_cb), view);
    view->priv->font_settings = g_settings;

    /* This schema is optional.  Use if available. */
    settings_schema = g_settings_schema_source_lookup (
        g_settings_schema_source_get_default (),
        "org.gnome.settings-daemon.plugins.xsettings", FALSE);
    if (settings_schema != NULL) {
        g_settings = g_settings_new ("org.gnome.settings-daemon.plugins.xsettings");
        g_signal_connect (
            settings, "changed::antialiasing",
            G_CALLBACK (e_html_editor_settings_changed_cb), view);
        view->priv->aliasing_settings = g_settings;
    }

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

    e_html_editor_view_update_fonts (view);

    /* Give spell check languages to WebKit */
    languages = e_spell_checker_list_active_languages (checker, NULL);
    comma_separated = g_strjoinv (",", languages);
    g_strfreev (languages);

    g_object_set (
        G_OBJECT (settings),
        "spell-checking-languages", comma_separated,
        NULL);

    g_free (comma_separated);

    view->priv->convertor_insert = FALSE;

    view->priv->convertor_web_view =
        g_object_ref_sink (WEBKIT_WEB_VIEW (webkit_web_view_new ()));
    settings = webkit_web_view_get_settings (view->priv->convertor_web_view);

    g_object_set (
        G_OBJECT (settings),
        "enable-scripts", FALSE,
        "enable-plugins", FALSE,
        NULL);

    g_signal_connect (
        view->priv->convertor_web_view, "notify::load-status",
        G_CALLBACK (html_plain_text_convertor_load_status_changed), view);

    /* Make WebKit think we are displaying a local file, so that it
     * does not block loading resources from file:// protocol */
    webkit_web_view_load_string (
        WEBKIT_WEB_VIEW (view), "", "text/html", "UTF-8", "file://");

    html_editor_view_set_links_active (view, FALSE);

    if (emd_global_http_cache == NULL) {
        user_cache_dir = e_get_user_cache_dir ();
        emd_global_http_cache = camel_data_cache_new (user_cache_dir, NULL);

        /* cache expiry - 2 hour access, 1 day max */
        camel_data_cache_set_expire_age (
            emd_global_http_cache, 24 * 60 * 60);
        camel_data_cache_set_expire_access (
            emd_global_http_cache, 2 * 60 * 60);
    }
}

/**
 * e_html_editor_view_new:
 *
 * Returns a new instance of the editor.
 *
 * Returns: A newly created #EHTMLEditorView. [transfer-full]
 */
EHTMLEditorView *
e_html_editor_view_new (void)
{
    return g_object_new (E_TYPE_HTML_EDITOR_VIEW, NULL);
}

/**
 * e_html_editor_view_get_selection:
 * @view: an #EHTMLEditorView
 *
 * Returns an #EHTMLEditorSelection object which represents current selection or
 * cursor position within the editor document. The #EHTMLEditorSelection allows
 * programmer to manipulate with formatting, selection, styles etc.
 *
 * Returns: An always valid #EHTMLEditorSelection object. The object is owned by
 * the @view and should never be free'd.
 */
EHTMLEditorSelection *
e_html_editor_view_get_selection (EHTMLEditorView *view)
{
    g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), NULL);

    return view->priv->selection;
}

/**
 * e_html_editor_view_exec_command:
 * @view: an #EHTMLEditorView
 * @command: an #EHTMLEditorViewCommand to execute
 * @value: value of the command (or @NULL if the command does not require value)
 *
 * The function will fail when @value is @NULL or empty but the current @command
 * requires a value to be passed. The @value is ignored when the @command does
 * not expect any value.
 *
 * Returns: @TRUE when the command was succesfully executed, @FALSE otherwise.
 */
gboolean
e_html_editor_view_exec_command (EHTMLEditorView *view,
                                 EHTMLEditorViewCommand command,
                                 const gchar *value)
{
    WebKitDOMDocument *document;
    const gchar *cmd_str = 0;
    gboolean has_value;

    g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);

#define CHECK_COMMAND(cmd,str,val) case cmd:\
    if (val) {\
        g_return_val_if_fail (value && *value, FALSE);\
    }\
    has_value = val; \
    cmd_str = str;\
    break;

    switch (command) {
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_BACKGROUND_COLOR, "BackColor", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_BOLD, "Bold", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_COPY, "Copy", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_CREATE_LINK, "CreateLink", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_CUT, "Cut", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_DEFAULT_PARAGRAPH_SEPARATOR, "DefaultParagraphSeparator", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_DELETE, "Delete", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FIND_STRING, "FindString", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FONT_NAME, "FontName", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE, "FontSize", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE_DELTA, "FontSizeDelta", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FORE_COLOR, "ForeColor", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FORMAT_BLOCK, "FormatBlock", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FORWARD_DELETE, "ForwardDelete", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_HILITE_COLOR, "HiliteColor", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INDENT, "Indent", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_HORIZONTAL_RULE, "InsertHorizontalRule", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML, "InsertHTML", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_IMAGE, "InsertImage", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_LINE_BREAK, "InsertLineBreak", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, "InsertNewlineInQuotedContent", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_ORDERED_LIST, "InsertOrderedList", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_PARAGRAPH, "InsertParagraph", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, "InsertText", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_UNORDERED_LIST, "InsertUnorderedList", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_ITALIC, "Italic", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_CENTER, "JustifyCenter", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_FULL, "JustifyFull", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_LEFT, "JustifyLeft", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_NONE, "JustifyNone", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_RIGHT, "JustifyRight", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_OUTDENT, "Outdent", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PASTE, "Paste", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PASTE_AND_MATCH_STYLE, "PasteAndMatchStyle", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PASTE_AS_PLAIN_TEXT, "PasteAsPlainText", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PRINT, "Print", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_REDO, "Redo", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_REMOVE_FORMAT, "RemoveFormat", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_SELECT_ALL, "SelectAll", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_STRIKETHROUGH, "Strikethrough", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_STYLE_WITH_CSS, "StyleWithCSS", TRUE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_SUBSCRIPT, "Subscript", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_SUPERSCRIPT, "Superscript", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_TRANSPOSE, "Transpose", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNDERLINE, "Underline", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNDO, "Undo", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNLINK, "Unlink", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNSELECT, "Unselect", FALSE)
        CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_USE_CSS, "UseCSS", TRUE)
    }

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    return webkit_dom_document_exec_command (
        document, cmd_str, FALSE, has_value ? value : "" );
}

/**
 * e_html_editor_view_get_changed:
 * @view: an #EHTMLEditorView
 *
 * Whether content of the editor has been changed.
 *
 * Returns: @TRUE when document was changed, @FALSE otherwise.
 */
gboolean
e_html_editor_view_get_changed (EHTMLEditorView *view)
{
    g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);

    return view->priv->changed;
}

/**
 * e_html_editor_view_set_changed:
 * @view: an #EHTMLEditorView
 * @changed: whether document has been changed or not
 *
 * Sets whether document has been changed or not. The editor is tracking changes
 * automatically, but sometimes it's necessary to change the dirty flag to force
 * "Save changes" dialog for example.
 */
void
e_html_editor_view_set_changed (EHTMLEditorView *view,
                                gboolean changed)
{
    g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));

    if (view->priv->changed == changed)
        return;

    view->priv->changed = changed;

    g_object_notify (G_OBJECT (view), "changed");
}

static gboolean
is_citation_node (WebKitDOMNode *node)
{
    char *value;

    if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))
        return FALSE;

    value = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "type");

    /* citation == <blockquote type='cite'> */
    if (g_strcmp0 (value, "cite") == 0) {
        g_free (value);
        return TRUE;
    } else {
        g_free (value);
        return FALSE;
    }
}

static gchar *
get_quotation_for_level (gint quote_level)
{
    gint ii;
    GString *output = g_string_new ("");

    for (ii = 0; ii < quote_level; ii++) {
        g_string_append (output, "<span class=\"-x-evo-quote-character\">");
        g_string_append (output, QUOTE_SYMBOL);
        g_string_append (output, " ");
        g_string_append (output, "</span>");
    }

    return g_string_free (output, FALSE);
}

static void
insert_quote_symbols (WebKitDOMHTMLElement *element,
                      gint quote_level,
                      gboolean skip_first,
                      gboolean insert_newline)
{
    gchar *text;
    gint ii;
    GString *output;
    gchar *quotation;

    if (!WEBKIT_DOM_IS_HTML_ELEMENT (element))
        return;

    text = webkit_dom_html_element_get_inner_html (element);
    output = g_string_new ("");
    quotation = get_quotation_for_level (quote_level);

    if (g_strcmp0 (text, "\n") == 0) {
        g_string_append (output, "<span class=\"-x-evo-quoted\">");
        g_string_append (output, quotation);
        g_string_append (output, "</span>");
        g_string_append (output, "\n");
    } else {
        gchar **lines;

        lines = g_strsplit (text, "\n", 0);

        for (ii = 0; lines[ii]; ii++) {
            if (ii == 0 && skip_first) {
                if (g_strv_length (lines) == 1) {
                    g_strfreev (lines);
                    goto exit;
                }
                g_string_append (output, lines[ii]);
                g_string_append (output, "\n");
            }

            g_string_append (output, "<span class=\"-x-evo-quoted\">");
            g_string_append (output, quotation);
            g_string_append (output, "</span>");

            /* Insert line of text */
            g_string_append (output, lines[ii]);
            if ((ii == g_strv_length (lines) - 1) &&
                !g_str_has_suffix (text, "\n") && !insert_newline) {
                /* If we are on last line and node's text doesn't
                 * end with \n, don't insert it */
                break;
            }
            g_string_append (output, "\n");
        }

        g_strfreev (lines);
    }

    webkit_dom_html_element_set_inner_html (element, output->str, NULL);
 exit:
    g_free (quotation);
    g_free (text);
    g_string_free (output, TRUE);
}

static void
quote_node (WebKitDOMDocument *document,
        WebKitDOMNode *node,
        gint quote_level)
{
    gboolean skip_first = FALSE;
    gboolean insert_newline = FALSE;
    gboolean is_html_node = FALSE;
    WebKitDOMElement *wrapper;
    WebKitDOMNode *node_clone, *prev_sibling, *next_sibling;

    /* Don't quote when we are not in citation */
    if (quote_level == 0)
        return;

    if (WEBKIT_DOM_IS_COMMENT (node))
        return;

    if (WEBKIT_DOM_IS_HTML_ELEMENT (node)) {
        insert_quote_symbols (
            WEBKIT_DOM_HTML_ELEMENT (node), quote_level, FALSE, FALSE);
        return;
    }

    prev_sibling = webkit_dom_node_get_previous_sibling (node);
    next_sibling = webkit_dom_node_get_next_sibling (node);

    is_html_node =
        !WEBKIT_DOM_IS_TEXT (prev_sibling) &&
        !WEBKIT_DOM_IS_COMMENT (prev_sibling) && (
        WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling) ||
        element_has_tag (WEBKIT_DOM_ELEMENT (prev_sibling), "b") ||
        element_has_tag (WEBKIT_DOM_ELEMENT (prev_sibling), "i") ||
        element_has_tag (WEBKIT_DOM_ELEMENT (prev_sibling), "u"));

    if (prev_sibling && is_html_node)
        skip_first = TRUE;

    /* Skip the BR between first blockquote and pre */
    if (quote_level == 1 && next_sibling && WEBKIT_DOM_IS_HTML_PRE_ELEMENT (next_sibling))
        return;

    if (next_sibling && WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling) &&
        WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (webkit_dom_node_get_next_sibling (next_sibling))) {
        insert_newline = TRUE;
    }

    /* Do temporary wrapper */
    wrapper = webkit_dom_document_create_element (document, "SPAN", NULL);
    webkit_dom_element_set_class_name (wrapper, "-x-evo-temp-text-wrapper");

    node_clone = webkit_dom_node_clone_node (node, TRUE);

    webkit_dom_node_append_child (
        WEBKIT_DOM_NODE (wrapper),
        node_clone,
        NULL);

    insert_quote_symbols (
        WEBKIT_DOM_HTML_ELEMENT (wrapper),
        quote_level,
        skip_first,
        insert_newline);

    webkit_dom_node_replace_child (
        webkit_dom_node_get_parent_node (node),
        WEBKIT_DOM_NODE (wrapper),
        node,
        NULL);
}

static void
insert_quote_symbols_before_node (WebKitDOMDocument *document,
                                  WebKitDOMNode *node,
                                  gint quote_level,
                                  gboolean is_html_node)
{
    gchar *quotation;
    WebKitDOMElement *element;

    quotation = get_quotation_for_level (quote_level);
    element = webkit_dom_document_create_element (document, "SPAN", NULL);
    element_add_class (element, "-x-evo-quoted");
    webkit_dom_html_element_set_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (element), quotation, NULL);

    if (is_html_node) {
        WebKitDOMElement *new_br;

        new_br = webkit_dom_document_create_element (document, "br", NULL);
        element_add_class (new_br, "-x-evo-temp-br");

        webkit_dom_node_insert_before (
            webkit_dom_node_get_parent_node (node),
            WEBKIT_DOM_NODE (new_br),
            node,
            NULL);
    }

    webkit_dom_node_insert_before (
        webkit_dom_node_get_parent_node (node),
        WEBKIT_DOM_NODE (element),
        node,
        NULL);

    if (is_html_node)
        remove_node (node);

    g_free (quotation);
}

static void
quote_plain_text_recursive (WebKitDOMDocument *document,
                WebKitDOMNode *node,
                WebKitDOMNode *start_node,
                gint quote_level)
{
    gboolean skip_node = FALSE;
    gboolean move_next = FALSE;
    gboolean suppress_next = FALSE;
    gboolean is_html_node = FALSE;
    WebKitDOMNode *next_sibling, *prev_sibling;

    node = webkit_dom_node_get_first_child (node);

    while (node) {
        skip_node = FALSE;
        move_next = FALSE;
        is_html_node = FALSE;

        if (WEBKIT_DOM_IS_COMMENT (node))
            goto next_node;

        prev_sibling = webkit_dom_node_get_previous_sibling (node);
        next_sibling = webkit_dom_node_get_next_sibling (node);

        if (WEBKIT_DOM_IS_TEXT (node)) {
            /* Start quoting after we are in blockquote */
            if (quote_level > 0 && !suppress_next) {
                /* When quoting text node, we are wrappering it and
                 * afterwards replacing it with that wrapper, thus asking
                 * for next_sibling after quoting will return NULL bacause
                 * that node don't exist anymore */
                quote_node (document, node, quote_level);
                node = next_sibling;
                skip_node = TRUE;
            } else
                suppress_next = FALSE;

            goto next_node;
        }

        if (!(WEBKIT_DOM_IS_ELEMENT (node) || WEBKIT_DOM_IS_HTML_ELEMENT (node)))
            goto next_node;

        if (element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-caret-position")) {
            if (quote_level > 0)
                element_add_class (
                    WEBKIT_DOM_ELEMENT (node), "-x-evo-caret-quoting");

            move_next = TRUE;
            suppress_next = TRUE;
            goto next_node;
        }

        if (element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-selection-start-marker") ||
            element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-selection-end-marker")) {
            move_next = TRUE;
            suppress_next = TRUE;
            goto next_node;
        }

        if (WEBKIT_DOM_IS_HTML_META_ELEMENT (node)) {
            goto next_node;
        }
        if (WEBKIT_DOM_IS_HTML_STYLE_ELEMENT (node)) {
            move_next = TRUE;
            goto next_node;
        }
        if (WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (node)) {
            move_next = TRUE;
            goto next_node;
        }

        if (webkit_dom_element_get_child_element_count (WEBKIT_DOM_ELEMENT (node)) != 0)
            goto with_children;

        /* Even in plain text mode we can have some basic html element
         * like anchor and others. When Forwaring e-mail as Quoted EMFormat
         * generates header that contatains <b> tags (bold font).
         * We have to treat these elements separately to avoid
         * modifications of theirs inner texts */
        is_html_node =
            WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node) ||
            element_has_tag (WEBKIT_DOM_ELEMENT (node), "b") ||
            element_has_tag (WEBKIT_DOM_ELEMENT (node), "i") ||
            element_has_tag (WEBKIT_DOM_ELEMENT (node), "u");

        if (is_html_node) {
            if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling))
                insert_quote_symbols_before_node (
                    document, prev_sibling, quote_level, TRUE);

            move_next = TRUE;
            goto next_node;
        }

        /* If element doesn't have children, we can quote it */
        if (is_citation_node (node)) {
            /* Citation with just text inside */
            quote_node (document, node, quote_level + 1);
            /* Set citation as quoted */
            element_add_class (
                WEBKIT_DOM_ELEMENT (node),
                "-x-evo-plaintext-quoted");

            move_next = TRUE;
            goto next_node;
        }

        if (!WEBKIT_DOM_IS_HTMLBR_ELEMENT (node))
            goto not_br;

        if (!prev_sibling) {
            WebKitDOMNode *parent;

            parent = webkit_dom_node_get_parent_node (node);

            /* BR in the beginning of the citation */
            if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent))
                insert_quote_symbols_before_node (
                    document, node, quote_level, FALSE);
        }

        if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
            WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (next_sibling) &&
            element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-temp-text-wrapper")) {
            /* Situation when anchors are alone on line */
            gchar *text_content;

            text_content = webkit_dom_node_get_text_content (prev_sibling);

            if (g_str_has_suffix (text_content, "\n")) {
                insert_quote_symbols_before_node (
                    document, node, quote_level, FALSE);
                remove_node (node);
                g_free (text_content);
                node = next_sibling;
                skip_node = TRUE;
                goto next_node;
            }
            g_free (text_content);
        }

        if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling)) {
            gchar *quotation, *content;

            quotation = get_quotation_for_level (quote_level);

            content = g_strconcat (
                "<span class=\"-x-evo-quoted\">",
                quotation,
                "</span><br class=\"-x-evo-temp-br\">",
                NULL);

            webkit_dom_html_element_set_outer_html (
                WEBKIT_DOM_HTML_ELEMENT (node),
                content,
                NULL);

            g_free (content);
            g_free (quotation);

            node = next_sibling;
            skip_node = TRUE;
            goto next_node;
        }

        if (!prev_sibling && !next_sibling) {
            WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);

            if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent)) {
                insert_quote_symbols_before_node (
                    document, node, quote_level, FALSE);
            }
        }

        if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
            element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-temp-text-wrapper")) {
            gchar *text_content;

            text_content = webkit_dom_node_get_text_content (prev_sibling);
            if (g_strcmp0 (text_content, "") == 0)
                insert_quote_symbols_before_node (
                    document, node, quote_level, FALSE);

            g_free (text_content);
        }

        if (is_citation_node (prev_sibling)) {
            insert_quote_symbols_before_node (
                document, node, quote_level, FALSE);
        }
 not_br:
        if (g_strcmp0 (webkit_dom_node_get_text_content (node), "") == 0) {
            move_next = TRUE;
            goto next_node;
        }

        quote_node (document, node, quote_level);

        move_next = TRUE;
        goto next_node;

 with_children:
        if (is_citation_node (node)) {
            /* Go deeper and increase level */
            quote_plain_text_recursive (
                document, node, start_node, quote_level + 1);
            /* set citation as quoted */
            element_add_class (
                WEBKIT_DOM_ELEMENT (node),
                "-x-evo-plaintext-quoted");
            move_next = TRUE;
        } else {
            quote_plain_text_recursive (
                document, node, start_node, quote_level);
            move_next = TRUE;
        }
 next_node:
        if (!skip_node) {
            /* Move to next node */
            if (!move_next && webkit_dom_node_has_child_nodes (node)) {
                node = webkit_dom_node_get_first_child (node);
            } else if (webkit_dom_node_get_next_sibling (node)) {
                node = webkit_dom_node_get_next_sibling (node);
            } else {
                return;
            }
        }
    }
}

static gint
get_citation_level (WebKitDOMNode *node,
                    gboolean set_plaintext_quoted)
{
    WebKitDOMNode *parent = node;
    gint level = 0;

    while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
        if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) &&
            webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "type")) {
            level++;

            if (set_plaintext_quoted) {
                element_add_class (
                    WEBKIT_DOM_ELEMENT (parent),
                    "-x-evo-plaintext-quoted");
            }
        }

        parent = webkit_dom_node_get_parent_node (parent);
    }

    return level;
}

WebKitDOMElement *
e_html_editor_view_quote_plain_text_element (EHTMLEditorView *view,
                                             WebKitDOMElement *element)
{
    WebKitDOMDocument *document;
    WebKitDOMNode *element_clone;
    WebKitDOMNodeList *list;
    gint ii, length, level;

    document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element));

    element_clone = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (element), TRUE);
    level = get_citation_level (WEBKIT_DOM_NODE (element), TRUE);

    /* Remove old quote characters if the exists */
    list = webkit_dom_element_query_selector_all (
        WEBKIT_DOM_ELEMENT (element_clone), "span.-x-evo-quoted", NULL);
    length = webkit_dom_node_list_get_length (list);
    for  (ii = 0; ii < length; ii++)
        remove_node (webkit_dom_node_list_item (list, ii));

    quote_plain_text_recursive (
        document, element_clone, element_clone, level);

    /* Replace old element with one, that is quoted */
    webkit_dom_node_replace_child (
        webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
        element_clone,
        WEBKIT_DOM_NODE (element),
        NULL);

    return WEBKIT_DOM_ELEMENT (element_clone);
}

/**
 * e_html_editor_view_quote_plain_text:
 * @view: an #EHTMLEditorView
 *
 * Quote text inside citation blockquotes in plain text mode.
 *
 * As this function is cloning and replacing all citation blockquotes keep on
 * mind that any pointers to nodes inside these blockquotes will be invalidated.
 */
WebKitDOMElement *
e_html_editor_view_quote_plain_text (EHTMLEditorView *view)
{
    WebKitDOMDocument *document;
    WebKitDOMHTMLElement *body;
    WebKitDOMNode *body_clone;
    WebKitDOMNamedNodeMap *attributes;
    WebKitDOMNodeList *list;
    WebKitDOMElement *element;
    gint ii, length;
    gulong attributes_length;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

    /* Check if the document is already quoted */
    element = webkit_dom_document_query_selector (
        document, ".-x-evo-plaintext-quoted", NULL);
    if (element)
        return NULL;

    body = webkit_dom_document_get_body (document);
    body_clone = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (body), TRUE);

    /* Clean unwanted spaces before and after blockquotes */
    list = webkit_dom_element_query_selector_all (
        WEBKIT_DOM_ELEMENT (body_clone), "blockquote[type|=cite]", NULL);
    length = webkit_dom_node_list_get_length (list);
    for (ii = 0; ii < length; ii++) {
        WebKitDOMNode *blockquote = webkit_dom_node_list_item (list, ii);
        WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (blockquote);
        WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (blockquote);

        if (prev_sibling && WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling))
            remove_node (prev_sibling);

        if (next_sibling && WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling))
            remove_node (next_sibling);

        if (webkit_dom_node_has_child_nodes (blockquote)) {
            WebKitDOMNode *child = webkit_dom_node_get_first_child (blockquote);
            if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child))
                remove_node (child);
        }
    }

    quote_plain_text_recursive (document, body_clone, body_clone, 0);

    /* Copy attributes */
    attributes = webkit_dom_element_get_attributes (WEBKIT_DOM_ELEMENT (body));
    attributes_length = webkit_dom_named_node_map_get_length (attributes);
    for (ii = 0; ii < attributes_length; ii++) {
        gchar *name, *value;
        WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);

        name = webkit_dom_node_get_local_name (node);
        value = webkit_dom_node_get_node_value (node);

        webkit_dom_element_set_attribute (
            WEBKIT_DOM_ELEMENT (body_clone), name, value, NULL);

        g_free (name);
        g_free (value);
    }

    /* Replace old BODY with one, that is quoted */
    webkit_dom_node_replace_child (
        webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (body)),
        body_clone,
        WEBKIT_DOM_NODE (body),
        NULL);

    return WEBKIT_DOM_ELEMENT (body_clone);
}

/**
 * e_html_editor_view_dequote_plain_text:
 * @view: an #EHTMLEditorView
 *
 * Dequote already quoted plain text in editor.
 * Editor have to be quoted with e_html_editor_view_quote_plain_text otherwise
 * it's not working.
 */
void
e_html_editor_view_dequote_plain_text (EHTMLEditorView *view)
{
    WebKitDOMDocument *document;
    WebKitDOMNodeList *paragraphs;
    gint length, ii;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

    paragraphs = webkit_dom_document_query_selector_all (
        document, "blockquote.-x-evo-plaintext-quoted", NULL);
    length = webkit_dom_node_list_get_length (paragraphs);
    for (ii = 0; ii < length; ii++) {
        WebKitDOMNodeList *list;
        WebKitDOMElement *element;
        gint jj, list_length;

        element = WEBKIT_DOM_ELEMENT (webkit_dom_node_list_item (paragraphs, ii));

        if (is_citation_node (WEBKIT_DOM_NODE (element))) {
            element_remove_class (element, "-x-evo-plaintext-quoted");

            list = webkit_dom_element_query_selector_all (
                element, "span.-x-evo-quoted", NULL);
            list_length = webkit_dom_node_list_get_length (list);
            for (jj = 0; jj < list_length; jj++) {
                WebKitDOMNode *node = webkit_dom_node_list_item (list, jj);

                webkit_dom_node_remove_child (
                    webkit_dom_node_get_parent_node (node),
                    node,
                    NULL);
            }
            list = webkit_dom_element_query_selector_all (
                element, "span.-x-evo-temp-text-wrapper", NULL);
            list_length = webkit_dom_node_list_get_length (list);
            for (jj = 0; jj < list_length; jj++) {
                WebKitDOMNode *node = webkit_dom_node_list_item (list, jj);

                webkit_dom_node_replace_child (
                    webkit_dom_node_get_parent_node (node),
                    webkit_dom_node_get_first_child (node),
                    node,
                    NULL);
            }
        }
    }
}

/**
 * e_html_editor_view_get_html_mode:
 * @view: an #EHTMLEditorView
 *
 * Whether the editor is in HTML mode or plain text mode. In HTML mode,
 * more formatting options are avilable an the email is sent as
 * multipart/alternative.
 *
 * Returns: @TRUE when HTML mode is enabled, @FALSE otherwise.
 */
gboolean
e_html_editor_view_get_html_mode (EHTMLEditorView *view)
{
    g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);

    return view->priv->html_mode;
}

static gint
get_indentation_level (WebKitDOMElement *element)
{
    WebKitDOMElement *parent;
    gint level = 1;

    parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (element));
    /* Count level of indentation */
    while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
        if (element_has_class (parent, "-x-evo-indented"))
            level++;

        parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (parent));
    }

    return level;
}

static void
process_blockquote (WebKitDOMElement *blockquote)
{
    WebKitDOMNodeList *list;
    int jj, length;

    /* First replace wrappers */
    list = webkit_dom_element_query_selector_all (
        blockquote, "span.-x-evo-temp-text-wrapper", NULL);
    length = webkit_dom_node_list_get_length (list);
    for (jj = 0; jj < length; jj++) {
        WebKitDOMNode *quoted_node;
        gchar *text_content, *tmp = NULL;

        quoted_node = webkit_dom_node_list_item (list, jj);
        text_content = webkit_dom_node_get_text_content (quoted_node);
        if (webkit_dom_node_get_previous_sibling (quoted_node)) {
            tmp = g_strconcat ("<br>", text_content, NULL);
            g_free (text_content);
            text_content = tmp;
        }

        webkit_dom_html_element_set_outer_html (
            WEBKIT_DOM_HTML_ELEMENT (quoted_node), text_content, NULL);

        g_free (text_content);
    }

    /* Afterwards replace quote nodes with symbols */
    list = webkit_dom_element_query_selector_all (
        blockquote, "span.-x-evo-quoted", NULL);
    length = webkit_dom_node_list_get_length (list);
    for (jj = 0; jj < length; jj++) {
        WebKitDOMNode *quoted_node;
        gchar *text_content, *tmp = NULL;

        quoted_node = webkit_dom_node_list_item (list, jj);
        text_content = webkit_dom_node_get_text_content (quoted_node);
        if (webkit_dom_node_get_previous_sibling (quoted_node)) {
            tmp = g_strconcat ("<br>", text_content, NULL);
            g_free (text_content);
            text_content = tmp;
        }

        webkit_dom_html_element_set_outer_html (
            WEBKIT_DOM_HTML_ELEMENT (quoted_node), text_content, NULL);

        g_free (text_content);
    }

    if (element_has_class (blockquote, "-x-evo-indented")) {
        WebKitDOMNode *child;
        gchar *spaces;

        spaces = g_strnfill (4 * get_indentation_level (blockquote), ' ');

        child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (blockquote));
        while (child) {
            /* If next sibling is indented blockqoute skip it,
             * it will be processed afterwards */
            if (WEBKIT_DOM_IS_ELEMENT (child) &&
                element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-indented"))
                child = webkit_dom_node_get_next_sibling (child);

            if (WEBKIT_DOM_IS_TEXT (child)) {
                gchar *text_content;
                gchar *indented_text;

                text_content = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (child));
                indented_text = g_strconcat (spaces, text_content, NULL);

                webkit_dom_text_replace_whole_text (
                    WEBKIT_DOM_TEXT (child),
                    indented_text,
                    NULL);

                g_free (text_content);
                g_free (indented_text);
            }

            if (!child)
                break;

            /* Move to next node */
            if (webkit_dom_node_has_child_nodes (child))
                child = webkit_dom_node_get_first_child (child);
            else if (webkit_dom_node_get_next_sibling (child))
                child = webkit_dom_node_get_next_sibling (child);
            else {
                if (webkit_dom_node_is_equal_node (WEBKIT_DOM_NODE (blockquote), child))
                    break;

                child = webkit_dom_node_get_parent_node (child);
                if (child)
                    child = webkit_dom_node_get_next_sibling (child);
            }
        }
        g_free (spaces);

        webkit_dom_element_remove_attribute (blockquote, "style");
    }
}

/* Taken from GtkHTML */
static gchar *
get_alpha_value (gint value,
                 gboolean lower)
{
    GString *str;
    gchar *rv;
    gint add = lower ? 'a' : 'A';

    str = g_string_new (". ");

    do {
        g_string_prepend_c (str, ((value - 1) % 26) + add);
        value = (value - 1) / 26;
    } while (value);

    rv = str->str;
    g_string_free (str, FALSE);

    return rv;
}

/* Taken from GtkHTML */
static gchar *
get_roman_value (gint value,
                 gboolean lower)
{
    GString *str;
    const gchar *base = "IVXLCDM";
    gchar *rv;
    gint b, r, add = lower ? 'a' - 'A' : 0;

    if (value > 3999)
        return g_strdup ("?. ");

    str = g_string_new (". ");

    for (b = 0; value > 0 && b < 7 - 1; b += 2, value /= 10) {
        r = value % 10;
        if (r != 0) {
            if (r < 4) {
                for (; r; r--)
                    g_string_prepend_c (str, base[b] + add);
            } else if (r == 4) {
                g_string_prepend_c (str, base[b + 1] + add);
                g_string_prepend_c (str, base[b] + add);
            } else if (r == 5) {
                g_string_prepend_c (str, base[b + 1] + add);
            } else if (r < 9) {
                for (; r > 5; r--)
                    g_string_prepend_c (str, base[b] + add);
                g_string_prepend_c (str, base[b + 1] + add);
            } else if (r == 9) {
                g_string_prepend_c (str, base[b + 2] + add);
                g_string_prepend_c (str, base[b] + add);
            }
        }
    }

    rv = str->str;
    g_string_free (str, FALSE);

    return rv;
}

static void
process_list_to_plain_text (EHTMLEditorView *view,
                            WebKitDOMElement *element,
                            gint level,
                            GString *output)
{
    EHTMLEditorSelectionBlockFormat format;
    EHTMLEditorSelectionAlignment alignment;
    gint counter = 1;
    gchar *indent_per_level = g_strnfill (SPACES_PER_LIST_LEVEL, ' ');
    WebKitDOMNode *item;
    gint word_wrap_length = e_html_editor_selection_get_word_wrap_length (
        e_html_editor_view_get_selection (view));

    format = e_html_editor_selection_get_list_format_from_node (
        WEBKIT_DOM_NODE (element));

    /* Process list items to plain text */
    item = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element));
    while (item) {
        if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (item))
            g_string_append (output, "\n");

        if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
            gchar *space, *item_str = NULL;
            gint ii = 0;
            WebKitDOMElement *wrapped;
            GString *item_value = g_string_new ("");

            alignment = e_html_editor_selection_get_list_alignment_from_node (
                WEBKIT_DOM_NODE (item));

            wrapped = webkit_dom_element_query_selector (
                WEBKIT_DOM_ELEMENT (item), ".-x-evo-wrap-br", NULL);
            /* Wrapped text */
            if (wrapped) {
                WebKitDOMNode *node = webkit_dom_node_get_first_child (item);
                GString *line = g_string_new ("");
                while (node) {
                    if (WEBKIT_DOM_IS_TEXT (node)) {
                        /* append text from line */
                        gchar *text_content;
                        text_content = webkit_dom_node_get_text_content (node);
                        g_string_append (line, text_content);
                        g_free (text_content);
                    }
                    if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (node) &&
                        element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br")) {
                        /* put spaces before line characters -> wordwraplength - indentation */
                        g_string_append (line, "\n");
                        /* put spaces before line characters -> wordwraplength - indentation */
                        for (ii = 0; ii < level; ii++)
                            g_string_append (line, indent_per_level);
                        g_string_append (item_value, line->str);
                        g_string_erase (line, 0, -1);
                    }
                    node = webkit_dom_node_get_next_sibling (node);
                }

                if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT)
                    g_string_append (item_value, line->str);

                if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER) {
                    gchar *fill = NULL;
                    gint fill_length;

                    fill_length = word_wrap_length - g_utf8_strlen (line->str, -1);
                        fill_length -= ii * SPACES_PER_LIST_LEVEL;
                    fill_length /= 2;

                    if (fill_length < 0)
                        fill_length = 0;

                    fill = g_strnfill (fill_length, ' ');

                    g_string_append (item_value, fill);
                    g_string_append (item_value, line->str);
                    g_free (fill);
                }

                if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT) {
                    gchar *fill = NULL;
                    gint fill_length;

                    fill_length = word_wrap_length - g_utf8_strlen (line->str, -1);
                        fill_length -= ii * SPACES_PER_LIST_LEVEL;

                    if (fill_length < 0)
                        fill_length = 0;

                    fill = g_strnfill (fill_length, ' ');

                    g_string_append (item_value, fill);
                    g_string_append (item_value, line->str);
                    g_free (fill);
                }
                g_string_free (line, TRUE);
                /* that same here */
            } else {
                gchar *text_content =
                    webkit_dom_node_get_text_content (item);
                g_string_append (item_value, text_content);
                g_free (text_content);
            }

            if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST) {
                space = g_strnfill (SPACES_PER_LIST_LEVEL - 2, ' ');
                item_str = g_strdup_printf (
                    "%s* %s", space, item_value->str);
                g_free (space);
            }

            if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST) {
                gint length = 1, tmp = counter;

                while ((tmp = tmp / 10) > 1)
                    length++;

                if (tmp == 1)
                    length++;

                space = g_strnfill (SPACES_PER_LIST_LEVEL - 2 - length, ' ');
                item_str = g_strdup_printf (
                    "%s%d. %s", space, counter, item_value->str);
                g_free (space);
            }

            if (format > E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST) {
                gchar *value;

                if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA)
                    value = get_alpha_value (counter, FALSE);
                else
                    value = get_roman_value (counter, FALSE);

                /* Value already containes dot and space */
                space = g_strnfill (SPACES_PER_LIST_LEVEL - strlen (value), ' ');
                item_str = g_strdup_printf (
                    "%s%s%s", space, value, item_value->str);
                g_free (space);
                g_free (value);
            }

            if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT) {
                for (ii = 0; ii < level - 1; ii++) {
                    g_string_append (output, indent_per_level);
                }
                g_string_append (output, item_str);
            }

            if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT) {
                if (!wrapped) {
                    gchar *fill = NULL;
                    gint fill_length;

                    fill_length = word_wrap_length - g_utf8_strlen (item_str, -1);
                        fill_length -= ii * SPACES_PER_LIST_LEVEL;

                    if (fill_length < 0)
                        fill_length = 0;

                    if (g_str_has_suffix (item_str, " "))
                        fill_length++;

                    fill = g_strnfill (fill_length, ' ');

                    g_string_append (output, fill);
                    g_free (fill);
                }
                if (g_str_has_suffix (item_str, " "))
                    g_string_append_len (output, item_str, g_utf8_strlen (item_str, -1) - 1);
                else
                    g_string_append (output, item_str);
            }

            if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER) {
                if (!wrapped) {
                    gchar *fill = NULL;
                    gint fill_length = 0;

                    for (ii = 0; ii < level - 1; ii++)
                        g_string_append (output, indent_per_level);

                    fill_length = word_wrap_length - g_utf8_strlen (item_str, -1);
                        fill_length -= ii * SPACES_PER_LIST_LEVEL;
                    fill_length /= 2;

                    if (fill_length < 0)
                        fill_length = 0;

                    if (g_str_has_suffix (item_str, " "))
                        fill_length++;

                    fill = g_strnfill (fill_length, ' ');

                    g_string_append (output, fill);
                    g_free (fill);
                }
                if (g_str_has_suffix (item_str, " "))
                    g_string_append_len (output, item_str, g_utf8_strlen (item_str, -1) - 1);
                else
                    g_string_append (output, item_str);
            }

            counter++;
            item = webkit_dom_node_get_next_sibling (item);
            if (item)
                g_string_append (output, "\n");

            g_free (item_str);
            g_string_free (item_value, TRUE);
        } else if (WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (item) ||
               WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (item)) {
            process_list_to_plain_text (
                view, WEBKIT_DOM_ELEMENT (item), level + 1, output);
            item = webkit_dom_node_get_next_sibling (item);
        } else {
            item = webkit_dom_node_get_next_sibling (item);
        }
    }

    if (webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element)))
        g_string_append (output, "\n");

    g_free (indent_per_level);
}

static void
remove_base_attributes (WebKitDOMElement *element)
{
    webkit_dom_element_remove_attribute (element, "class");
    webkit_dom_element_remove_attribute (element, "id");
    webkit_dom_element_remove_attribute (element, "name");
}

static void
remove_evolution_attributes (WebKitDOMElement *element)
{
    webkit_dom_element_remove_attribute (element, "x-evo-smiley");
    webkit_dom_element_remove_attribute (element, "data-converted");
    webkit_dom_element_remove_attribute (element, "data-edit-as-new");
    webkit_dom_element_remove_attribute (element, "data-inline");
    webkit_dom_element_remove_attribute (element, "data-uri");
    webkit_dom_element_remove_attribute (element, "data-message");
    webkit_dom_element_remove_attribute (element, "data-name");
    webkit_dom_element_remove_attribute (element, "data-new-message");
    webkit_dom_element_remove_attribute (element, "spellcheck");
}
/*
static void
remove_style_attributes (WebKitDOMElement *element)
{
    webkit_dom_element_remove_attribute (element, "bgcolor");
    webkit_dom_element_remove_attribute (element, "background");
    webkit_dom_element_remove_attribute (element, "style");
}
*/
static void
process_elements (EHTMLEditorView *view,
                  WebKitDOMNode *node,
                  gboolean to_html,
                  gboolean changing_mode,
                  gboolean to_plain_text,
                  GString *buffer)
{
    WebKitDOMNodeList *nodes;
    gulong ii, length;
    gchar *content;
    gboolean skip_nl = FALSE;

    if (to_plain_text && !buffer)
        return;

    if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node)) {
        if (changing_mode && to_plain_text) {
            WebKitDOMNamedNodeMap *attributes;
            gulong attributes_length;

            /* Copy attributes */
            g_string_append (buffer, "<html><head></head><body ");
            attributes = webkit_dom_element_get_attributes (
                WEBKIT_DOM_ELEMENT (node));
            attributes_length =
                webkit_dom_named_node_map_get_length (attributes);

            for (ii = 0; ii < attributes_length; ii++) {
                gchar *name, *value;
                WebKitDOMNode *node =
                    webkit_dom_named_node_map_item (
                        attributes, ii);

                name = webkit_dom_node_get_local_name (node);
                value = webkit_dom_node_get_node_value (node);

                g_string_append (buffer, name);
                g_string_append (buffer, "=\"");
                g_string_append (buffer, value);
                g_string_append (buffer, "\" ");

                g_free (name);
                g_free (value);
            }
            g_string_append (buffer, ">");
        }
        if (to_html)
            remove_evolution_attributes (WEBKIT_DOM_ELEMENT (node));
    }

    nodes = webkit_dom_node_get_child_nodes (node);
    length = webkit_dom_node_list_get_length (nodes);

    for (ii = 0; ii < length; ii++) {
        WebKitDOMNode *child;
        gboolean skip_node = FALSE;

        child = webkit_dom_node_list_item (nodes, ii);

        if (WEBKIT_DOM_IS_TEXT (child)) {
            gchar *content, *tmp;
            GRegex *regex;

            content = webkit_dom_node_get_text_content (child);
            /* Replace tabs with 8 whitespaces, otherwise they got
             * replaced by single whitespace */
            if (strstr (content, "\x9")) {
                regex = g_regex_new ("\x9", 0, 0, NULL);
                tmp = g_regex_replace (
                    regex, content, -1, 0, "        ", 0, NULL);
                webkit_dom_node_set_text_content (child, tmp, NULL);
                g_free (content);
                g_free (tmp);
                content = webkit_dom_node_get_text_content (child);
                g_regex_unref (regex);
            }

            if (strstr (content, UNICODE_ZERO_WIDTH_SPACE)) {
                regex = g_regex_new (UNICODE_ZERO_WIDTH_SPACE, 0, 0, NULL);
                tmp = g_regex_replace (
                    regex, content, -1, 0, "", 0, NULL);
                webkit_dom_node_set_text_content (child, tmp, NULL);
                g_free (tmp);
                g_free (content);
                content = webkit_dom_node_get_text_content (child);
                g_regex_unref (regex);
            }

            if (to_plain_text && !changing_mode) {
                gchar *style;
                const gchar *css_align;

                if (strstr (content, UNICODE_NBSP)) {
                    GString *nbsp_free;

                    nbsp_free = e_str_replace_string (
                        content, UNICODE_NBSP, " ");

                    g_free (content);
                    content = g_string_free (nbsp_free, FALSE);
                }

                style = webkit_dom_element_get_attribute (
                    WEBKIT_DOM_ELEMENT (node), "style");

                if ((css_align = strstr (style, "text-align: "))) {
                    gchar *align;
                    gchar *content_with_align;
                    gint length;
                    gint word_wrap_length =
                        e_html_editor_selection_get_word_wrap_length (
                            e_html_editor_view_get_selection (view));

                    if (!g_str_has_prefix (css_align + 12, "left")) {
                        if (g_str_has_prefix (css_align + 12, "center"))
                            length = (word_wrap_length - g_utf8_strlen (content, -1)) / 2;
                        else
                            length = word_wrap_length - g_utf8_strlen (content, -1);

                        if (g_str_has_suffix (content, " ")) {
                            char *tmp;

                            length++;
                            align = g_strnfill (length, ' ');

                            tmp = g_strndup (content, g_utf8_strlen (content, -1) -1);

                            content_with_align = g_strconcat (
                                align, tmp, NULL);
                            g_free (tmp);
                        } else {
                            align = g_strnfill (length, ' ');

                            content_with_align = g_strconcat (
                                align, content, NULL);
                        }

                        g_free (content);
                        g_free (align);
                        content = content_with_align;
                    }
                }

                g_free (style);
            }

            if (to_plain_text || changing_mode)
                g_string_append (buffer, content);

            g_free (content);

            goto next;
        }

        if (WEBKIT_DOM_IS_COMMENT (child) || !WEBKIT_DOM_IS_ELEMENT (child))
            goto next;

        /* Leave caret position untouched */
        if (element_has_id (WEBKIT_DOM_ELEMENT (child), "-x-evo-caret-position")) {
            if (changing_mode && to_plain_text) {
                content = webkit_dom_html_element_get_outer_html (
                    WEBKIT_DOM_HTML_ELEMENT (child));
                g_string_append (buffer, content);
                g_free (content);
            }
            if (to_html)
                remove_node (child);

            skip_node = TRUE;
        }

        if (element_has_class (WEBKIT_DOM_ELEMENT (child), "Apple-tab-span")) {
            if (!changing_mode && to_plain_text) {
                gchar *content, *tmp;
                GRegex *regex;

                content = webkit_dom_node_get_text_content (child);
                /* Replace tabs with 4 whitespaces, otherwise they got
                 * replaced by single whitespace */
                if (strstr (content, "\x9")) {
                    regex = g_regex_new ("\x9", 0, 0, NULL);
                    tmp = g_regex_replace (
                        regex, content, -1, 0, "        ", 0, NULL);
                    g_string_append (buffer, tmp);
                    g_free (tmp);
                    content = webkit_dom_node_get_text_content (child);
                    g_regex_unref (regex);
                }
            }
            if (to_html) {
                element_remove_class (
                    WEBKIT_DOM_ELEMENT (child),
                    "Applet-tab-span");
            }

            skip_node = TRUE;
        }

        /* Leave blockquotes as they are */
        if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (child)) {
            if (changing_mode && to_plain_text) {
                content = webkit_dom_html_element_get_outer_html (
                    WEBKIT_DOM_HTML_ELEMENT (child));
                g_string_append (buffer, content);
                g_free (content);
                skip_node = TRUE;
            } else {
                if (!changing_mode && to_plain_text) {
                    if (get_citation_level (child, FALSE) == 0) {
                        gchar *value;
                        value = webkit_dom_element_get_attribute (
                            WEBKIT_DOM_ELEMENT (child), "type");
                        if (value && g_strcmp0 (value, "cite") == 0) {
                            g_string_append (buffer, "\n");
                        }
                        g_free (value);
                    }
                }
                process_blockquote (WEBKIT_DOM_ELEMENT (child));
            }
        }

        if (WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (child) ||
            WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (child)) {
            if (to_plain_text) {
                if (changing_mode) {
                    content = webkit_dom_html_element_get_outer_html (
                        WEBKIT_DOM_HTML_ELEMENT (child));
                    g_string_append (buffer, content);
                    g_free (content);
                } else {
                    process_list_to_plain_text (
                        view, WEBKIT_DOM_ELEMENT (child), 1, buffer);
                }
                skip_node = TRUE;
                goto next;
            }
        }

        if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-resizable-wrapper") &&
            !element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-smiley-wrapper")) {
            WebKitDOMNode *image =
                webkit_dom_node_get_first_child (child);

            if (to_html && WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (image)) {
                remove_evolution_attributes (
                    WEBKIT_DOM_ELEMENT (image));

                webkit_dom_node_replace_child (
                    node, image, child, NULL);
            }

            skip_node = TRUE;
        }

        /* Leave paragraphs as they are */
        if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-paragraph")) {
            if (changing_mode && to_plain_text) {
                content = webkit_dom_html_element_get_outer_html (
                    WEBKIT_DOM_HTML_ELEMENT (child));
                g_string_append (buffer, content);
                g_free (content);
                skip_node = TRUE;
            }
            if (to_html) {
                remove_base_attributes (WEBKIT_DOM_ELEMENT (child));
                remove_evolution_attributes (WEBKIT_DOM_ELEMENT (child));
            }
        }

        /* Signature */
        if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (child)) {
            WebKitDOMNode *first_child;

            first_child = webkit_dom_node_get_first_child (child);
            if (WEBKIT_DOM_IS_ELEMENT (first_child)) {
                if (element_has_class (
                    WEBKIT_DOM_ELEMENT (first_child),
                    "-x-evo-signature")) {

                    if (to_html) {
                        remove_base_attributes (
                            WEBKIT_DOM_ELEMENT (first_child));
                        remove_evolution_attributes (
                            WEBKIT_DOM_ELEMENT (first_child));
                    }
                    if (to_plain_text && !changing_mode) {
                        g_string_append (buffer, "\n");
                        content = webkit_dom_html_element_get_inner_text (
                            WEBKIT_DOM_HTML_ELEMENT (first_child));
                        g_string_append (buffer, content);
                        g_free (content);
                        skip_nl = TRUE;
                    }
                    skip_node = TRUE;
                }
            }
        }

        /* Replace smileys with their text representation */
        if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-smiley-wrapper")) {
            if (to_plain_text && !changing_mode) {
                WebKitDOMNode *text_version;

                text_version = webkit_dom_node_get_last_child (child);
                content = webkit_dom_html_element_get_inner_text (
                    WEBKIT_DOM_HTML_ELEMENT (text_version));
                g_string_append (buffer, content);
                g_free (content);
                skip_node = TRUE;
            }
            if (to_html) {
                WebKitDOMElement *img;

                img = WEBKIT_DOM_ELEMENT (
                    webkit_dom_node_get_first_child (child)),

                remove_evolution_attributes (img);
                remove_base_attributes (img);

                webkit_dom_node_insert_before (
                    webkit_dom_node_get_parent_node (child),
                    WEBKIT_DOM_NODE (img),
                    child,
                    NULL);
                remove_node (child);
                skip_node = TRUE;
            }
        }

        /* Leave PRE elements untouched */
        if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (child)) {
            if (changing_mode && to_plain_text) {
                content = webkit_dom_html_element_get_outer_html (
                    WEBKIT_DOM_HTML_ELEMENT (child));
                g_string_append (buffer, content);
                g_free (content);
                skip_node = TRUE;
            }
            if (to_html)
                remove_evolution_attributes (WEBKIT_DOM_ELEMENT (child));
        }

        if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child)) {
            if (to_plain_text) {
                /* Insert new line when we hit BR element */
                g_string_append (buffer, changing_mode ? "<br>" : "\n");
            }
        }
 next:
        if (webkit_dom_node_has_child_nodes (child) && !skip_node)
            process_elements (
                view, child, to_html, changing_mode, to_plain_text, buffer);
    }

    if (to_plain_text && (
        WEBKIT_DOM_IS_HTML_DIV_ELEMENT (node) ||
        WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (node) ||
        WEBKIT_DOM_IS_HTML_PRE_ELEMENT (node) ||
        WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))) {

        gboolean add_br = TRUE;
        WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (node);

        /* If we don't have next sibling (last element in body) or next element is
         * signature we are not adding the BR element */
        if (!next_sibling)
            add_br = FALSE;
        else if (next_sibling && WEBKIT_DOM_IS_HTML_DIV_ELEMENT (next_sibling)) {
            if (webkit_dom_element_query_selector (
                WEBKIT_DOM_ELEMENT (next_sibling),
                "span.-x-evo-signature", NULL)) {

                add_br = FALSE;
            }
        }

        content = webkit_dom_node_get_text_content (node);
        if (add_br && g_utf8_strlen (content, -1) > 0 && !skip_nl)
            g_string_append (buffer, changing_mode ? "<br>" : "\n");

        g_free (content);
    }

}

static void
remove_wrapping (EHTMLEditorView *view)
{
    gint length;
    gint ii;
    WebKitDOMDocument *document;
    WebKitDOMNodeList *list;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    list = webkit_dom_document_query_selector_all (document, "br.-x-evo-wrap-br", NULL);

    length = webkit_dom_node_list_get_length (list);
    for (ii = 0; ii < length; ii++)
        remove_node (webkit_dom_node_list_item (list, ii));
}

static void
remove_images_in_element (EHTMLEditorView *view,
                          WebKitDOMElement *element,
                          gboolean html_mode)
{
    gint length, ii;
    WebKitDOMNodeList *images;

    images = webkit_dom_element_query_selector_all (
        element, "img:not(.-x-evo-smiley-img)", NULL);

    length = webkit_dom_node_list_get_length (images);
    for (ii = 0; ii < length; ii++)
        remove_node (webkit_dom_node_list_item (images, ii));
}

static void
remove_images (EHTMLEditorView *view)
{
    WebKitDOMDocument *document;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

    remove_images_in_element (
        view,
        WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document)),
        view->priv->html_mode);
}

static void
toggle_smileys (EHTMLEditorView *view)
{
    gboolean html_mode;
    gint length;
    gint ii;
    WebKitDOMDocument *document;
    WebKitDOMNodeList *smileys;

    html_mode = e_html_editor_view_get_html_mode (view);

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    smileys = webkit_dom_document_query_selector_all (
        document, "img.-x-evo-smiley-img", NULL);

    length = webkit_dom_node_list_get_length (smileys);
    for (ii = 0; ii < length; ii++) {
        WebKitDOMNode *img = webkit_dom_node_list_item (smileys, ii);
        WebKitDOMNode *text = webkit_dom_node_get_next_sibling (img);
        WebKitDOMElement *parent = webkit_dom_node_get_parent_element (img);

        webkit_dom_element_set_attribute (
            WEBKIT_DOM_ELEMENT (html_mode ? text : img),
            "style",
            "display: none",
            NULL);

        webkit_dom_element_remove_attribute (
            WEBKIT_DOM_ELEMENT (html_mode ? img : text), "style");

        if (html_mode)
            element_add_class (parent, "-x-evo-resizable-wrapper");
        else
            element_remove_class (parent, "-x-evo-resizable-wrapper");
    }
}

static void
toggle_paragraphs_style_in_element (EHTMLEditorView *view,
                                    WebKitDOMElement *element,
                    gboolean html_mode)
{
    EHTMLEditorSelection *selection;
    gint ii, length;
    WebKitDOMNodeList *paragraphs;

    selection = e_html_editor_view_get_selection (view);

    paragraphs = webkit_dom_element_query_selector_all (
        element, ".-x-evo-paragraph", NULL);

    length = webkit_dom_node_list_get_length (paragraphs);

    for (ii = 0; ii < length; ii++) {
        gchar *style;
        const gchar *css_align;
        WebKitDOMNode *node = webkit_dom_node_list_item (paragraphs, ii);

        if (html_mode) {
            style = webkit_dom_element_get_attribute (
                WEBKIT_DOM_ELEMENT (node), "style");

            if ((css_align = strstr (style, "text-align: "))) {
                webkit_dom_element_set_attribute (
                    WEBKIT_DOM_ELEMENT (node),
                    "style",
                    g_str_has_prefix (css_align + 12, "center") ?
                        "text-align: center" :
                        "text-align: right",
                    NULL);
            } else {
                /* In HTML mode the paragraphs don't have width limit */
                webkit_dom_element_remove_attribute (
                    WEBKIT_DOM_ELEMENT (node), "style");
            }
            g_free (style);
        } else {
            WebKitDOMNode *parent;

            parent = webkit_dom_node_get_parent_node (node);
            /* If the paragraph is inside indented paragraph don't set
             * the style as it will be inherited */
            if (!element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-indented")) {
                const gchar *style_to_add = "";
                style = webkit_dom_element_get_attribute (
                    WEBKIT_DOM_ELEMENT (node), "style");

                if ((css_align = strstr (style, "text-align: "))) {
                    style_to_add = g_str_has_prefix (
                        css_align + 12, "center") ?
                            "text-align: center;" :
                            "text-align: right;";
                }

                /* In plain text mode the paragraphs have width limit */
                e_html_editor_selection_set_paragraph_style (
                    selection, WEBKIT_DOM_ELEMENT (node),
                    -1, 0, style_to_add);

                g_free (style);
            }
        }
    }
}

static void
toggle_paragraphs_style (EHTMLEditorView *view)
{
    WebKitDOMDocument *document;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

    toggle_paragraphs_style_in_element (
        view,
        WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document)),
        view->priv->html_mode);
}

static gchar *
process_content_for_saving_as_draft (EHTMLEditorView *view)
{
    WebKitDOMDocument *document;
    WebKitDOMHTMLElement *body;
    WebKitDOMElement *element, *document_element;
    gchar *content;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    body = webkit_dom_document_get_body (document);
    element = webkit_dom_document_get_element_by_id (
        document, "-x-evo-caret-position");

    if (element)
        webkit_dom_element_set_attribute (
            element, "style", "display: none; color: red;", NULL);

    webkit_dom_element_set_attribute (
        WEBKIT_DOM_ELEMENT (body), "data-evo-draft", "", NULL);

    document_element = webkit_dom_document_get_document_element (document);
    content = webkit_dom_html_element_get_outer_html (
        WEBKIT_DOM_HTML_ELEMENT (document_element));

    webkit_dom_element_remove_attribute (
        WEBKIT_DOM_ELEMENT (body), "data-evo-draft");

    if (element)
        webkit_dom_element_set_attribute (
            element, "style", "color: red;", NULL);

    return content;
}

static gchar *
process_content_for_mode_change (EHTMLEditorView *view)
{
    WebKitDOMDocument *document;
    WebKitDOMNode *body;
    GString *plain_text;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));

    plain_text = g_string_sized_new (1024);

    process_elements (view, body, FALSE, TRUE, TRUE, plain_text);

    g_string_append (plain_text, "</body></html>");

    return g_string_free (plain_text, FALSE);
}

static void
convert_element_from_html_to_plain_text (EHTMLEditorView *view,
                                         WebKitDOMElement *element,
                                         gboolean *wrap,
                                         gboolean *quote)
{
    EHTMLEditorSelection *selection;
    gint blockquotes_count;
    gchar *inner_text, *inner_html;
    gboolean restore = TRUE;
    WebKitDOMDocument *document;
    WebKitDOMElement *top_signature, *signature, *blockquote, *main_blockquote;
    WebKitDOMNode *signature_clone, *from;

    selection = e_html_editor_view_get_selection (view);

    document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element));

    top_signature = webkit_dom_element_query_selector (
        element, ".-x-evo-top-signature", NULL);
    signature = webkit_dom_element_query_selector (
        element, "span.-x-evo-signature", NULL);
    main_blockquote = webkit_dom_element_query_selector (
        element, "#-x-evo-main-cite", NULL);

    blockquote = webkit_dom_document_create_element (
        document, "blockquote", NULL);

    if (main_blockquote) {
        WebKitDOMElement *input_start;

        webkit_dom_element_set_attribute (
            blockquote, "type", "cite", NULL);

        input_start = webkit_dom_element_query_selector (
            element, "#-x-evo-input-start", NULL);

        restore = input_start ? TRUE : FALSE;

        if (input_start) {
            webkit_dom_node_append_child (
                WEBKIT_DOM_NODE (input_start),
                e_html_editor_selection_get_caret_position_node (
                    document),
                NULL);
        }
        from = WEBKIT_DOM_NODE (main_blockquote);
    } else {
        if (signature) {
            WebKitDOMNode *parent = webkit_dom_node_get_parent_node (
                WEBKIT_DOM_NODE (signature));
            signature_clone = webkit_dom_node_clone_node (parent, TRUE);
            remove_node (parent);
        }
        from = WEBKIT_DOM_NODE (element);
    }

    blockquotes_count = create_text_markers_for_citations_in_element (
        WEBKIT_DOM_ELEMENT (from));

    inner_text = webkit_dom_html_element_get_inner_text (
        WEBKIT_DOM_HTML_ELEMENT (from));

    webkit_dom_html_element_set_inner_text (
        WEBKIT_DOM_HTML_ELEMENT (blockquote), inner_text, NULL);

    inner_html = webkit_dom_html_element_get_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (blockquote));

    parse_html_into_paragraphs (
        view, document,
        main_blockquote ? blockquote : WEBKIT_DOM_ELEMENT (element),
        inner_html,
        FALSE);

    if (main_blockquote) {
        webkit_dom_node_replace_child (
            webkit_dom_node_get_parent_node (
                WEBKIT_DOM_NODE (main_blockquote)),
            WEBKIT_DOM_NODE (blockquote),
            WEBKIT_DOM_NODE (main_blockquote),
            NULL);

        remove_evolution_attributes (WEBKIT_DOM_ELEMENT (element));
        *wrap = TRUE;
    } else {
        WebKitDOMNode *first_child;

        if (signature) {
            if (!top_signature) {
                signature_clone = webkit_dom_node_append_child (
                    WEBKIT_DOM_NODE (element),
                    signature_clone,
                    NULL);
            } else {
                webkit_dom_node_insert_before (
                    WEBKIT_DOM_NODE (element),
                    signature_clone,
                    webkit_dom_node_get_first_child (
                        WEBKIT_DOM_NODE (element)),
                    NULL);
            }
        }

        first_child = webkit_dom_node_get_first_child (
            WEBKIT_DOM_NODE (element));

        if (!webkit_dom_node_has_child_nodes (first_child)) {
            webkit_dom_html_element_set_inner_html (
                WEBKIT_DOM_HTML_ELEMENT (first_child),
                UNICODE_ZERO_WIDTH_SPACE,
                NULL);
        }
        webkit_dom_node_insert_before (
            first_child,
            e_html_editor_selection_get_caret_position_node (
                document),
            webkit_dom_node_get_first_child (first_child),
            NULL);

        *wrap = TRUE;
    }

    *quote = main_blockquote || blockquotes_count > 0;

    webkit_dom_element_set_attribute (
        WEBKIT_DOM_ELEMENT (element), "data-converted", "", NULL);

    g_free (inner_text);
    g_free (inner_html);

    if (restore)
        e_html_editor_selection_restore_caret_position (selection);
}

static gchar *
process_content_for_plain_text (EHTMLEditorView *view)
{
    gboolean converted, wrap = FALSE, quote = FALSE, clean = FALSE;
    gint length, ii;
    GString *plain_text;
    WebKitDOMDocument *document;
    WebKitDOMNode *body, *source;
    WebKitDOMNodeList *paragraphs;

    plain_text = g_string_sized_new (1024);

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
    converted = webkit_dom_element_has_attribute (
        WEBKIT_DOM_ELEMENT (body), "data-converted");
    source = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (body), TRUE);

    /* If composer is in HTML mode we have to move the content to plain version */
    if (view->priv->html_mode) {
        if (converted) {
            toggle_paragraphs_style_in_element (
                view, WEBKIT_DOM_ELEMENT (source), FALSE);
            remove_images_in_element (
                view, WEBKIT_DOM_ELEMENT (source), FALSE);
        } else {
            gchar *inner_html;
            WebKitDOMElement *div;

            inner_html = webkit_dom_html_element_get_inner_html (
                WEBKIT_DOM_HTML_ELEMENT (body));

            div = webkit_dom_document_create_element (
                document, "div", NULL);

            webkit_dom_html_element_set_inner_html (
                WEBKIT_DOM_HTML_ELEMENT (div), inner_html, NULL);

            webkit_dom_node_append_child (
                WEBKIT_DOM_NODE (body),
                WEBKIT_DOM_NODE (div),
                NULL);

            paragraphs = webkit_dom_element_query_selector_all (
                div, "#-x-evo-input-start", NULL);

            length = webkit_dom_node_list_get_length (paragraphs);
            for (ii = 0; ii < length; ii++) {
                WebKitDOMNode *paragraph;

                paragraph = webkit_dom_node_list_item (paragraphs, ii);

                webkit_dom_element_set_id (
                    WEBKIT_DOM_ELEMENT (paragraph), "");
            }

            convert_element_from_html_to_plain_text (
                view, div, &wrap, &quote);

            source = WEBKIT_DOM_NODE (div);

            clean = TRUE;
        }
    }

    paragraphs = webkit_dom_element_query_selector_all (
        WEBKIT_DOM_ELEMENT (source), ".-x-evo-paragraph", NULL);

    length = webkit_dom_node_list_get_length (paragraphs);
    for (ii = 0; ii < length; ii++) {
        WebKitDOMNode *paragraph;

        paragraph = webkit_dom_node_list_item (paragraphs, ii);

        if (WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (paragraph) ||
            WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (paragraph)) {
            WebKitDOMNode *item = webkit_dom_node_get_first_child (paragraph);

            while (item) {
                WebKitDOMNode *next_item =
                    webkit_dom_node_get_next_sibling (item);

                if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
                    e_html_editor_selection_wrap_paragraph (
                        e_html_editor_view_get_selection (view),
                        WEBKIT_DOM_ELEMENT (item));
                }
                item = next_item;
            }
        } else {
            e_html_editor_selection_wrap_paragraph (
                e_html_editor_view_get_selection (view),
                WEBKIT_DOM_ELEMENT (paragraph));
        }
    }

    paragraphs = webkit_dom_element_query_selector_all (
        WEBKIT_DOM_ELEMENT (source),
        "span[id^=\"-x-evo-selection-\"], span#-x-evo-caret-position",
        NULL);

    length = webkit_dom_node_list_get_length (paragraphs);
    for (ii = 0; ii < length; ii++) {
        WebKitDOMNode *node = webkit_dom_node_list_item (paragraphs, ii);
        WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);

        remove_node (node);
        webkit_dom_node_normalize (parent);
    }

    if (view->priv->html_mode || quote)
        quote_plain_text_recursive (document, source, source, 0);

    process_elements (view, source, FALSE, FALSE, TRUE, plain_text);

    if (clean)
        remove_node (source);

    /* Return text content between <body> and </body> */
    return g_string_free (plain_text, FALSE);
}

static gchar *
process_content_for_html (EHTMLEditorView *view)
{
    WebKitDOMDocument *document;
    WebKitDOMNode *body;
    WebKitDOMElement *element;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
    process_elements (view, body, TRUE, FALSE, FALSE, NULL);
    element = webkit_dom_document_get_document_element (document);
    return webkit_dom_html_element_get_outer_html (
        WEBKIT_DOM_HTML_ELEMENT (element));
}

static gboolean
show_lose_formatting_dialog (EHTMLEditorView *view)
{
    gint result;
    GtkWidget *toplevel, *dialog;
    GtkWindow *parent = NULL;

    toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));

    if (GTK_IS_WINDOW (toplevel))
        parent = GTK_WINDOW (toplevel);

    dialog = gtk_message_dialog_new (
        parent,
        GTK_DIALOG_DESTROY_WITH_PARENT,
        GTK_MESSAGE_WARNING,
        GTK_BUTTONS_NONE,
        _("Turning HTML mode off will cause the text "
        "to lose all formatting. Do you want to continue?"));
    gtk_dialog_add_buttons (
        GTK_DIALOG (dialog),
        _("_Don't lose formatting"), GTK_RESPONSE_CANCEL,
        _("_Lose formatting"), GTK_RESPONSE_OK,
        NULL);

    result = gtk_dialog_run (GTK_DIALOG (dialog));

    if (result != GTK_RESPONSE_OK) {
        gtk_widget_destroy (dialog);
        /* Nothing has changed, but notify anyway */
        g_object_notify (G_OBJECT (view), "html-mode");
        return FALSE;
    }

    gtk_widget_destroy (dialog);

    return TRUE;
}

static void
clear_attributes (WebKitDOMDocument *document)
{
    gint length, ii;
    WebKitDOMNamedNodeMap *attributes;
    WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document);
    WebKitDOMHTMLHeadElement *head = webkit_dom_document_get_head (document);
    WebKitDOMElement *document_element =
        webkit_dom_document_get_document_element (document);

    /* Remove all attributes from HTML element */
    attributes = webkit_dom_element_get_attributes (document_element);
    length = webkit_dom_named_node_map_get_length (attributes);
    for (ii = length - 1; ii >= 0; ii--) {
        WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);

        webkit_dom_element_remove_attribute_node (
            document_element, WEBKIT_DOM_ATTR (node), NULL);
    }

    /* Remove everything from HEAD element */
    while (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (head)))
        remove_node (WEBKIT_DOM_NODE (head));

    /* Remove non Evolution attributes from BODY element */
    attributes = webkit_dom_element_get_attributes (WEBKIT_DOM_ELEMENT (body));
    length = webkit_dom_named_node_map_get_length (attributes);
    for (ii = length - 1; ii >= 0; ii--) {
        gchar *name;
        WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);

        name = webkit_dom_node_get_local_name (node);

        if (!g_str_has_prefix (name, "data-") ||
            g_str_has_prefix (name, "data-inline") ||
            g_str_has_prefix (name, "data-name")) {
            webkit_dom_element_remove_attribute_node (
                WEBKIT_DOM_ELEMENT (body),
                WEBKIT_DOM_ATTR (node),
                NULL);
        }

        g_free (name);
    }
}

static void
convert_when_changing_composer_mode (EHTMLEditorView *view)
{
    EHTMLEditorSelection *selection;
    gboolean quote = FALSE, wrap = FALSE;
    WebKitDOMDocument *document;
    WebKitDOMHTMLElement *body;

    selection = e_html_editor_view_get_selection (view);

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    body = webkit_dom_document_get_body (document);

    convert_element_from_html_to_plain_text (
        view, WEBKIT_DOM_ELEMENT (body), &wrap, &quote);

    if (wrap)
        e_html_editor_selection_wrap_paragraphs_in_document (selection, document);

    if (quote) {
        e_html_editor_selection_save_caret_position (selection);
        body = WEBKIT_DOM_HTML_ELEMENT (e_html_editor_view_quote_plain_text (view));
        e_html_editor_selection_restore_caret_position (selection);
    }

    toggle_paragraphs_style (view);
    toggle_smileys (view);
    remove_images (view);

    clear_attributes (document);

    webkit_dom_element_set_attribute (
        WEBKIT_DOM_ELEMENT (body), "data-converted", "", NULL);

    /* Update fonts - in plain text we only want monospace */
    e_html_editor_view_update_fonts (view);

    e_html_editor_view_force_spell_check (view);
}

/**
 * e_html_editor_view_set_html_mode:
 * @view: an #EHTMLEditorView
 * @html_mode: @TRUE to enable HTML mode, @FALSE to enable plain text mode
 *
 * When switching from HTML to plain text mode, user will be prompted whether
 * he/she really wants to switch the mode and lose all formatting. When user
 * declines, the property is not changed. When they accept, the all formatting
 * is lost.
 */
void
e_html_editor_view_set_html_mode (EHTMLEditorView *view,
                                  gboolean html_mode)
{
    EHTMLEditorSelection *selection;
    gboolean is_from_new_message, converted, edit_as_new, message, convert;
    gboolean reply, hide;
    WebKitDOMElement *blockquote;
    WebKitDOMHTMLElement *body;
    WebKitDOMDocument *document;

    g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));

    selection = e_html_editor_view_get_selection (view);

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    body = webkit_dom_document_get_body (document);

    is_from_new_message = webkit_dom_element_has_attribute (
        WEBKIT_DOM_ELEMENT (body), "data-new-message");
    converted = webkit_dom_element_has_attribute (
        WEBKIT_DOM_ELEMENT (body), "data-converted");
    edit_as_new = webkit_dom_element_has_attribute (
        WEBKIT_DOM_ELEMENT (body), "data-edit-as-new");
    message = webkit_dom_element_has_attribute (
        WEBKIT_DOM_ELEMENT (body), "data-message");

    reply = !is_from_new_message && !edit_as_new && message;
    hide = !reply && !converted;

    convert = message && ((!hide && reply && !converted) || (edit_as_new && !converted));

    /* If toggling from HTML to plain text mode, ask user first */
    if (convert && view->priv->html_mode && !html_mode) {
        if (!show_lose_formatting_dialog (view))
            return;

        view->priv->html_mode = html_mode;

        convert_when_changing_composer_mode (view);

        goto out;
    }

    if (html_mode == view->priv->html_mode)
        return;

    view->priv->html_mode = html_mode;

    /* Update fonts - in plain text we only want monospace */
    e_html_editor_view_update_fonts (view);

    blockquote = webkit_dom_document_query_selector (
        document, "blockquote[type|=cite]", NULL);

    if (view->priv->html_mode) {
        if (blockquote)
            e_html_editor_view_dequote_plain_text (view);

        toggle_paragraphs_style (view);
        toggle_smileys (view);
        remove_wrapping (view);
    } else {
        gchar *plain;

        /* Save caret position -> it will be restored in e-composer-private.c */
        e_html_editor_selection_save_caret_position (selection);

        if (blockquote) {
            e_html_editor_selection_wrap_paragraphs_in_document (
                selection, document);
            e_html_editor_view_quote_plain_text (view);
        }

        toggle_paragraphs_style (view);
        toggle_smileys (view);
        remove_images (view);

        plain = process_content_for_mode_change (view);

        if (*plain)
            webkit_web_view_load_string (
                WEBKIT_WEB_VIEW (view), plain, NULL, NULL, "file://");

        g_free (plain);
    }

 out:
    g_object_notify (G_OBJECT (view), "html-mode");
}

/**
 * e_html_editor_view_get_inline_spelling:
 * @view: an #EHTMLEditorView
 *
 * Returns whether automatic spellchecking is enabled or not. When enabled,
 * editor will perform spellchecking as user is typing. Otherwise spellcheck
 * has to be run manually from menu.
 *
 * Returns: @TRUE when automatic spellchecking is enabled, @FALSE otherwise.
 */
gboolean
e_html_editor_view_get_inline_spelling (EHTMLEditorView *view)
{
    g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);

    return view->priv->inline_spelling;
}

/**
 * e_html_editor_view_set_inline_spelling:
 * @view: an #EHTMLEditorView
 * @inline_spelling: @TRUE to enable automatic spellchecking, @FALSE otherwise
 *
 * Enables or disables automatic spellchecking.
 */
void
e_html_editor_view_set_inline_spelling (EHTMLEditorView *view,
                                        gboolean inline_spelling)
{
    g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));

    if (view->priv->inline_spelling == inline_spelling)
        return;

    view->priv->inline_spelling = inline_spelling;

    g_object_notify (G_OBJECT (view), "inline-spelling");
}

/**
 * e_html_editor_view_get_magic_links:
 * @view: an #EHTMLEditorView
 *
 * Returns whether automatic links conversion is enabled. When enabled, the editor
 * will automatically convert any HTTP links into clickable HTML links.
 *
 * Returns: @TRUE when magic links are enabled, @FALSE otherwise.
 */
gboolean
e_html_editor_view_get_magic_links (EHTMLEditorView *view)
{
    g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);

    return view->priv->magic_links;
}

/**
 * e_html_editor_view_set_magic_links:
 * @view: an #EHTMLEditorView
 * @magic_links: @TRUE to enable magic links, @FALSE to disable them
 *
 * Enables or disables automatic links conversion.
 */
void
e_html_editor_view_set_magic_links (EHTMLEditorView *view,
                                    gboolean magic_links)
{
    g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));

    if (view->priv->magic_links == magic_links)
        return;

    view->priv->magic_links = magic_links;

    g_object_notify (G_OBJECT (view), "magic-links");
}

/**
 * e_html_editor_view_get_magic_smileys:
 * @view: an #EHTMLEditorView
 *
 * Returns whether automatic conversion of smileys is enabled or disabled. When
 * enabled, the editor will automatically convert text smileys ( :-), ;-),...)
 * into images.
 *
 * Returns: @TRUE when magic smileys are enabled, @FALSE otherwise.
 */
gboolean
e_html_editor_view_get_magic_smileys (EHTMLEditorView *view)
{
    g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);

    return view->priv->magic_smileys;
}

/**
 * e_html_editor_view_set_magic_smileys:
 * @view: an #EHTMLEditorView
 * @magic_smileys: @TRUE to enable magic smileys, @FALSE to disable them
 *
 * Enables or disables magic smileys.
 */
void
e_html_editor_view_set_magic_smileys (EHTMLEditorView *view,
                                      gboolean magic_smileys)
{
    g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));

    if (view->priv->magic_smileys == magic_smileys)
        return;

    view->priv->magic_smileys = magic_smileys;

    g_object_notify (G_OBJECT (view), "magic-smileys");
}

/**
 * e_html_editor_view_get_spell_checker:
 * @view: an #EHTMLEditorView
 *
 * Returns an #ESpellChecker object that is used to perform spellchecking.
 *
 * Returns: An always-valid #ESpellChecker object
 */
ESpellChecker *
e_html_editor_view_get_spell_checker (EHTMLEditorView *view)
{
    return E_SPELL_CHECKER (webkit_get_text_checker ());
}

/**
 * e_html_editor_view_get_text_html:
 * @view: an #EHTMLEditorView:
 *
 * Returns processed HTML content of the editor document (with elements attributes
 * used in Evolution composer)
 *
 * Returns: A newly allocated string
 */
gchar *
e_html_editor_view_get_text_html (EHTMLEditorView *view)
{
    return process_content_for_html (view);
}

/**
 * e_html_editor_view_get_text_html_for_drafts:
 * @view: an #EHTMLEditorView:
 *
 * Returns HTML content of the editor document (without elements attributes
 * used in Evolution composer)
 *
 * Returns: A newly allocated string
 */
gchar *
e_html_editor_view_get_text_html_for_drafts (EHTMLEditorView *view)
{
    return process_content_for_saving_as_draft (view);
}

/**
 * e_html_editor_view_get_text_plain:
 * @view: an #EHTMLEditorView
 *
 * Returns plain text content of the @view. The algorithm removes any
 * formatting or styles from the document and keeps only the text and line
 * breaks.
 *
 * Returns: A newly allocated string with plain text content of the document.
 */
gchar *
e_html_editor_view_get_text_plain (EHTMLEditorView *view)
{
    return process_content_for_plain_text (view);
}

static void
convert_and_load_html_to_plain_text (EHTMLEditorView *view,
                                     const gchar *html)
{
    view->priv->convertor_insert = FALSE;

    webkit_web_view_load_string (
        view->priv->convertor_web_view, html, NULL, NULL, "file://");
}

void
e_html_editor_view_convert_and_insert_html_to_plain_text (EHTMLEditorView *view,
                                                          const gchar *html)
{
    view->priv->convertor_insert = TRUE;

    webkit_web_view_load_string (
        view->priv->convertor_web_view, html, NULL, NULL, "file://");
}

/**
 * e_html_editor_view_set_text_html:
 * @view: an #EHTMLEditorView
 * @text: HTML code to load into the editor
 *
 * Loads given @text into the editor, destroying any content already present.
 */
void
e_html_editor_view_set_text_html (EHTMLEditorView *view,
                                  const gchar *text)
{
    view->priv->reload_in_progress = TRUE;

    /* Only convert messages that are in HTML */
    if (!view->priv->html_mode && *text && !strstr (text, "data-evo-draft")) {
        if (strstr (text, "<!-- text/html -->")) {
            if (!show_lose_formatting_dialog (view)) {
                e_html_editor_view_set_html_mode (view, TRUE);
                webkit_web_view_load_string (
                    WEBKIT_WEB_VIEW (view), text, NULL, NULL, "file://");
                return;
            }
            convert_and_load_html_to_plain_text (view, text);
        } else {
            convert_and_load_html_to_plain_text (view, text);
        }
    } else {
        webkit_web_view_load_string (
            WEBKIT_WEB_VIEW (view), text, NULL, NULL, "file://");
    }
}

/**
 * e_html_editor_view_set_text_plain:
 * @view: an #EHTMLEditorView
 * @text: A plain text to load into the editor
 *
 * Loads given @text into the editor, destryoing any content already present.
 */
void
e_html_editor_view_set_text_plain (EHTMLEditorView *view,
                                   const gchar *text)
{
    view->priv->reload_in_progress = TRUE;

    webkit_web_view_load_string (
        WEBKIT_WEB_VIEW (view), text, "text/plain", NULL, "file://");
}

/**
 * e_html_editor_view_paste_clipboard_quoted:
 * @view: an #EHTMLEditorView
 *
 * Pastes current content of clipboard into the editor as quoted text
 */
void
e_html_editor_view_paste_clipboard_quoted (EHTMLEditorView *view)
{
    EHTMLEditorViewClass *class;

    g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));

    class = E_HTML_EDITOR_VIEW_GET_CLASS (view);
    g_return_if_fail (class->paste_clipboard_quoted != NULL);

    class->paste_clipboard_quoted (view);
}

void
e_html_editor_view_embed_styles (EHTMLEditorView *view)
{
    WebKitWebSettings *settings;
    WebKitDOMDocument *document;
    WebKitDOMElement *sheet;
    gchar *stylesheet_uri;
    gchar *stylesheet_content;
    const gchar *stylesheet;
    gsize length;

    settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

    g_object_get (
        G_OBJECT (settings),
        "user-stylesheet-uri", &stylesheet_uri,
        NULL);

    stylesheet = strstr (stylesheet_uri, ",");
    stylesheet_content = (gchar *) g_base64_decode (stylesheet, &length);
    g_free (stylesheet_uri);

    if (length == 0) {
        g_free (stylesheet_content);
        return;
    }

    e_web_view_create_and_add_css_style_sheet (document, "-x-evo-composer-sheet");

    sheet = webkit_dom_document_get_element_by_id (document, "-x-evo-composer-sheet");
    webkit_dom_element_set_attribute (
        sheet,
        "type",
        "text/css",
        NULL);

    webkit_dom_html_element_set_inner_html (WEBKIT_DOM_HTML_ELEMENT (sheet), stylesheet_content, NULL);

    g_free (stylesheet_content);
}

void
e_html_editor_view_remove_embed_styles (EHTMLEditorView *view)
{
    WebKitDOMDocument *document;
    WebKitDOMElement *sheet;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    sheet = webkit_dom_document_get_element_by_id (
            document, "-x-evo-composer-sheet");

    remove_node (WEBKIT_DOM_NODE (sheet));
}

static const gchar *
citation_color_level_1 (void)
{
    return "rgb(114,159,207)";  /* Sky Blue 1 */
}

static const gchar *
citation_color_level_2 (void)
{
    return "rgb(173,127,168)";  /* Plum 1 */
}

static const gchar *
citation_color_level_3 (void)
{
    return "rgb(138,226,52)";  /* Chameleon 1 */
}

static const gchar *
citation_color_level_4 (void)
{
    return "rgb(252,175,62)";  /* Orange 1 */
}

static const gchar *
citation_color_level_5 (void)
{
    return "rgb(233,185,110)";  /* Chocolate 1 */
}

/**
 * e_html_editor_view_update_fonts:
 * @view: an #EHTMLEditorView
 *
 * Forces the editor to reload font settings from WebKitWebSettings and apply
 * it on the content of the editor document.
 */
void
e_html_editor_view_update_fonts (EHTMLEditorView *view)
{
    GString *stylesheet;
    gchar *base64;
    gchar *aa = NULL;
    WebKitWebSettings *settings;
    PangoFontDescription *ms, *vw;
    const gchar *styles[] = { "normal", "oblique", "italic" };
    const gchar *smoothing = NULL;
    GtkStyleContext *context;
    GdkColor *link = NULL;
    GdkColor *visited = NULL;
    gchar *font;

    font = g_settings_get_string (
        view->priv->font_settings,
        "monospace-font-name");
    ms = pango_font_description_from_string (
        font ? font : "monospace 10");
    g_free (font);

    if (view->priv->html_mode) {
        font = g_settings_get_string (
                view->priv->font_settings,
                "font-name");
        vw = pango_font_description_from_string (
                font ? font : "serif 10");
        g_free (font);
    } else {
        /* When in plain text mode, force monospace font */
        vw = pango_font_description_copy (ms);
    }

    stylesheet = g_string_new ("");
    g_string_append_printf (
        stylesheet,
        "body {\n"
        "  font-family: '%s';\n"
        "  font-size: %dpt;\n"
        "  font-weight: %d;\n"
        "  font-style: %s;\n",
        pango_font_description_get_family (vw),
        pango_font_description_get_size (vw) / PANGO_SCALE,
        pango_font_description_get_weight (vw),
        styles[pango_font_description_get_style (vw)]);

    if (view->priv->aliasing_settings != NULL)
        aa = g_settings_get_string (
            view->priv->aliasing_settings, "antialiasing");

    if (g_strcmp0 (aa, "none") == 0)
        smoothing = "none";
    else if (g_strcmp0 (aa, "grayscale") == 0)
        smoothing = "antialiased";
    else if (g_strcmp0 (aa, "rgba") == 0)
        smoothing = "subpixel-antialiased";

    if (smoothing != NULL)
        g_string_append_printf (
            stylesheet,
            " -webkit-font-smoothing: %s;\n",
            smoothing);

    g_free (aa);

    g_string_append (stylesheet, "}\n");

    g_string_append_printf (
        stylesheet,
        "pre,code,.pre {\n"
        "  font-family: '%s';\n"
        "  font-size: %dpt;\n"
        "  font-weight: %d;\n"
        "  font-style: %s;\n"
        "}",
        pango_font_description_get_family (ms),
        pango_font_description_get_size (ms) / PANGO_SCALE,
        pango_font_description_get_weight (ms),
        styles[pango_font_description_get_style (ms)]);

    context = gtk_widget_get_style_context (GTK_WIDGET (view));
    gtk_style_context_get_style (
        context,
        "link-color", &link,
        "visited-link-color", &visited,
        NULL);

    if (link == NULL) {
        link = g_slice_new0 (GdkColor);
        link->blue = G_MAXINT16;
    }

    if (visited == NULL) {
        visited = g_slice_new0 (GdkColor);
        visited->red = G_MAXINT16;
    }

    g_string_append_printf (
        stylesheet,
        "a {\n"
        "  color: #%06x;\n"
        "}\n"
        "a:visited {\n"
        "  color: #%06x;\n"
        "}\n",
        e_color_to_value (link),
        e_color_to_value (visited));

    /* See bug #689777 for details */
    g_string_append (
        stylesheet,
        "p,pre,code,address {\n"
        "  margin: 0;\n"
        "}\n"
        "h1,h2,h3,h4,h5,h6 {\n"
        "  margin-top: 0.2em;\n"
        "  margin-bottom: 0.2em;\n"
        "}\n");

    g_string_append (
        stylesheet,
        "img "
        "{\n"
        "  height: inherit; \n"
        "  width: inherit; \n"
        "}\n");

    g_string_append (
        stylesheet,
        "span.-x-evo-resizable-wrapper "
        "{\n"
        "  resize: both; \n"
        "  overflow: hidden; \n"
        "  display: inline-block; \n"
        "}\n");

    g_string_append (
        stylesheet,
        "span.-x-evo-resizable-wrapper:hover "
        "{\n"
        "  outline: 1px dashed red; \n"
        "}\n");

    g_string_append (
        stylesheet,
        "ul,ol "
        "{\n"
        "  -webkit-padding-start: 7ch; \n"
        "}\n");

    g_string_append (
        stylesheet,
        ".-x-evo-list-item-alignt-left "
        "{\n"
        "  text-align: left; \n"
        "}\n");

    g_string_append (
        stylesheet,
        ".-x-evo-list-item-align-center "
        "{\n"
        "  text-align: center; \n"
        "  -webkit-padding-start: 0ch; \n"
        "  margin-left: -3ch; \n"
        "  margin-right: 1ch; \n"
        "  list-style-position: inside; \n"
        "}\n");

    g_string_append (
        stylesheet,
        ".-x-evo-list-item-align-right "
        "{\n"
        "  text-align: right; \n"
        "  -webkit-padding-start: 0ch; \n"
        "  margin-left: -3ch; \n"
        "  margin-right: 1ch; \n"
        "  list-style-position: inside; \n"
        "}\n");

    g_string_append (
        stylesheet,
        "ol,ul "
        "{\n"
        "  -webkit-margin-before: 0em; \n"
        "  -webkit-margin-after: 0em; \n"
        "}\n");

    g_string_append (
        stylesheet,
        "blockquote "
        "{\n"
        "  -webkit-margin-before: 0em; \n"
        "  -webkit-margin-after: 0em; \n"
        "}\n");

    g_string_append (
        stylesheet,
        "blockquote[type=cite] "
        "{\n"
        "  padding: 0.0ex 0ex;\n"
        "  margin: 0ex;\n"
        "  -webkit-margin-start: 0em; \n"
        "  -webkit-margin-end : 0em; \n"
        "  color: #737373 !important;\n"
        "}\n");

    g_string_append_printf (
        stylesheet,
        ".-x-evo-quote-character "
        "{\n"
        "  color: %s;\n"
        "}\n",
        citation_color_level_1 ());

    g_string_append_printf (
        stylesheet,
        ".-x-evo-quote-character+"
        ".-x-evo-quote-character"
        "{\n"
        "  color: %s;\n"
        "}\n",
        citation_color_level_2 ());

    g_string_append_printf (
        stylesheet,
        ".-x-evo-quote-character+"
        ".-x-evo-quote-character+"
        ".-x-evo-quote-character"
        "{\n"
        "  color: %s;\n"
        "}\n",
        citation_color_level_3 ());

    g_string_append_printf (
        stylesheet,
        ".-x-evo-quote-character+"
        ".-x-evo-quote-character+"
        ".-x-evo-quote-character+"
        ".-x-evo-quote-character"
        "{\n"
        "  color: %s;\n"
        "}\n",
        citation_color_level_4 ());

    g_string_append_printf (
        stylesheet,
        ".-x-evo-quote-character+"
        ".-x-evo-quote-character+"
        ".-x-evo-quote-character+"
        ".-x-evo-quote-character+"
        ".-x-evo-quote-character"
        "{\n"
        "  color: %s;\n"
        "}\n",
        citation_color_level_5 ());

    g_string_append (
        stylesheet,
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "{\n"
        "  padding: 0.4ex 1ex;\n"
        "  margin: 1ex;\n"
        "  border-width: 0px 2px 0px 2px;\n"
        "  border-style: none solid none solid;\n"
        "  border-radius: 2px;\n"
        "}\n");

    /* Block quote border colors are borrowed from Thunderbird. */

    g_string_append_printf (
        stylesheet,
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "{\n"
        "  border-color: %s;\n"
        "}\n",
        citation_color_level_1 ());

    g_string_append_printf (
        stylesheet,
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "{\n"
        "  border-color: %s;\n"
        "}\n",
        citation_color_level_2 ());

    g_string_append_printf (
        stylesheet,
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "{\n"
        "  border-color: %s;\n"
        "}\n",
        citation_color_level_3 ());

    g_string_append_printf (
        stylesheet,
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "{\n"
        "  border-color: %s;\n"
        "}\n",
        citation_color_level_4 ());

    g_string_append_printf (
        stylesheet,
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
        "{\n"
        "  border-color: %s;\n"
        "}\n",
        citation_color_level_5 ());

    gdk_color_free (link);
    gdk_color_free (visited);

    base64 = g_base64_encode ((guchar *) stylesheet->str, stylesheet->len);
    g_string_free (stylesheet, TRUE);

    stylesheet = g_string_new ("data:text/css;charset=utf-8;base64,");
    g_string_append (stylesheet, base64);
    g_free (base64);

    settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
    g_object_set (
        G_OBJECT (settings),
        "default-font-size", pango_font_description_get_size (vw) / PANGO_SCALE,
        "default-font-family", pango_font_description_get_family (vw),
        "monospace-font-family", pango_font_description_get_family (ms),
        "default-monospace-font-size", (pango_font_description_get_size (ms) / PANGO_SCALE),
        "user-stylesheet-uri", stylesheet->str,
        NULL);

    g_string_free (stylesheet, TRUE);

    pango_font_description_free (ms);
    pango_font_description_free (vw);
}

/**
 * e_html_editor_view_get_element_under_mouse_click:
 * @view: an #EHTMLEditorView
 *
 * Returns DOM element, that was clicked on.
 *
 * Returns: DOM element on that was clicked.
 */
WebKitDOMElement *
e_html_editor_view_get_element_under_mouse_click (EHTMLEditorView *view)
{
    g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), NULL);

    return view->priv->element_under_mouse;
}

/**
 * e_html_editor_view_check_magic_links
 * @view: an #EHTMLEditorView
 * @include_space: If TRUE the pattern for link expects space on end
 *
 * Check if actual selection in given editor is link. If so, it is surrounded
 * with ANCHOR element.
 */
void
e_html_editor_view_check_magic_links (EHTMLEditorView *view,
                                      gboolean include_space)
{
    WebKitDOMRange *range;

    g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));

    range = html_editor_view_get_dom_range (view);
    html_editor_view_check_magic_links (view, range, include_space, NULL);
}

static CamelMimePart *
e_html_editor_view_add_inline_image_from_element (EHTMLEditorView *view,
                                                  WebKitDOMElement *element,
                                                  const gchar *attribute,
                          const gchar *uid_domain)
{
    CamelStream *stream;
    CamelDataWrapper *wrapper;
    CamelMimePart *part = NULL;
    gsize decoded_size;
    gssize size;
    gchar *mime_type = NULL;
    gchar *element_src, *cid, *name;
    const gchar *base64_encoded_data;
    guchar *base64_decoded_data;

    if (!WEBKIT_DOM_IS_ELEMENT (element)) {
        return NULL;
    }

    element_src = webkit_dom_element_get_attribute (
        WEBKIT_DOM_ELEMENT (element), attribute);

    base64_encoded_data = strstr (element_src, ";base64,");
    if (!base64_encoded_data)
        goto out;

    mime_type = g_strndup (
        element_src + 5,
        base64_encoded_data - (strstr (element_src, "data:") + 5));

    /* Move to actual data */
    base64_encoded_data += 8;

    base64_decoded_data = g_base64_decode (base64_encoded_data, &decoded_size);

    stream = camel_stream_mem_new ();
    size = camel_stream_write (
        stream, (gchar *) base64_decoded_data, decoded_size, NULL, NULL);

    if (size == -1)
        goto out;

    wrapper = camel_data_wrapper_new ();
    camel_data_wrapper_construct_from_stream_sync (
        wrapper, stream, NULL, NULL);
    g_object_unref (stream);

    camel_data_wrapper_set_mime_type (wrapper, mime_type);

    part = camel_mime_part_new ();
    camel_medium_set_content (CAMEL_MEDIUM (part), wrapper);
    g_object_unref (wrapper);

    cid = camel_header_msgid_generate (uid_domain);
    camel_mime_part_set_content_id (part, cid);
    name = webkit_dom_element_get_attribute (element, "data-name");
    camel_mime_part_set_filename (part, name);
    g_free (name);
    camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_BASE64);
out:
    g_free (mime_type);
    g_free (element_src);
    g_free (base64_decoded_data);

    return part;
}

GList *
e_html_editor_view_get_parts_for_inline_images (EHTMLEditorView *view,
                        const gchar *uid_domain)
{
    GHashTable *added;
    GList *parts = NULL;
    gint length, ii;
    WebKitDOMDocument *document;
    WebKitDOMNodeList *list;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW  (view));
    list = webkit_dom_document_query_selector_all (document, "img[data-inline]", NULL);

    length = webkit_dom_node_list_get_length (list);
    if (length == 0)
        return parts;

    added = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
    for (ii = 0; ii < length; ii++) {
        CamelMimePart *part;
        WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
        gchar *src = webkit_dom_element_get_attribute (
            WEBKIT_DOM_ELEMENT (node), "src");

        if (!g_hash_table_lookup (added, src)) {
            part = e_html_editor_view_add_inline_image_from_element (
                view, WEBKIT_DOM_ELEMENT (node), "src", uid_domain);
            parts = g_list_append (parts, part);
            g_hash_table_insert (
                added, src, (gpointer) camel_mime_part_get_content_id (part));
        }
        g_free (src);
    }

    for (ii = 0; ii < length; ii++) {
        WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
        const gchar *id;
        gchar *src = webkit_dom_element_get_attribute (
            WEBKIT_DOM_ELEMENT (node), "src");

        if ((id = g_hash_table_lookup (added, src)) != NULL) {
            gchar *cid = g_strdup_printf ("cid:%s", id);
            webkit_dom_element_set_attribute (
                WEBKIT_DOM_ELEMENT (node), "src", cid, NULL);
            g_free (cid);
        }
        g_free (src);
    }

    list = webkit_dom_document_query_selector_all (
        document, "[data-inline][background]", NULL);
    length = webkit_dom_node_list_get_length (list);
    for (ii = 0; ii < length; ii++) {
        CamelMimePart *part;
        WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
        gchar *src = webkit_dom_element_get_attribute (
            WEBKIT_DOM_ELEMENT (node), "background");

        if (!g_hash_table_lookup (added, src)) {
            part = e_html_editor_view_add_inline_image_from_element (
                view, WEBKIT_DOM_ELEMENT (node), "background", uid_domain);
            if (part) {
                parts = g_list_append (parts, part);
                g_hash_table_insert (
                    added, src,
                    (gpointer) camel_mime_part_get_content_id (part));
            }
        }
        g_free (src);
    }

    for (ii = 0; ii < length; ii++) {
        WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
        gchar *src = webkit_dom_element_get_attribute (
            WEBKIT_DOM_ELEMENT (node), "background");
        const gchar *id;

        if ((id = g_hash_table_lookup (added, src)) != NULL) {
            gchar *cid = g_strdup_printf ("cid:%s", id);
            webkit_dom_element_set_attribute (
                WEBKIT_DOM_ELEMENT (node), "background", cid, NULL);
            g_free (cid);
        }
        g_free (src);
    }

    g_hash_table_destroy (added);

    return parts;
}

/**
 * e_html_editor_view_add_inline_image_from_mime_part:
 * @composer: a composer object
 * @part: a CamelMimePart containing image data
 *
 * This adds the mime part @part to @composer as an inline image.
 **/
void
e_html_editor_view_add_inline_image_from_mime_part (EHTMLEditorView *view,
                                                    CamelMimePart *part)
{
    CamelDataWrapper *dw;
    CamelStream *stream;
    GByteArray *byte_array;
    gchar *src, *base64_encoded, *mime_type, *cid_src;
    const gchar *cid, *name;

    stream = camel_stream_mem_new ();
    dw = camel_medium_get_content (CAMEL_MEDIUM (part));
    g_return_if_fail (dw);

    mime_type = camel_data_wrapper_get_mime_type (dw);
    camel_data_wrapper_decode_to_stream_sync (dw, stream, NULL, NULL);
    camel_stream_close (stream, NULL, NULL);

    byte_array = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (stream));

    if (!byte_array->data)
        return;

    base64_encoded = g_base64_encode ((const guchar *) byte_array->data, byte_array->len);

    name = camel_mime_part_get_filename (part);
    /* Insert file name before new src */
    src = g_strconcat (name, ";data:", mime_type, ";base64,", base64_encoded, NULL);

    cid = camel_mime_part_get_content_id (part);
    if (!cid) {
        camel_mime_part_set_content_id (part, NULL);
        cid = camel_mime_part_get_content_id (part);
    }
    cid_src = g_strdup_printf ("cid:%s", cid);

    g_hash_table_insert (view->priv->inline_images, cid_src, src);

    g_free (base64_encoded);
    g_free (mime_type);
    g_object_unref (stream);
}