aboutsummaryrefslogblamecommitdiffstats
path: root/e-util/e-html-editor-view.c
blob: a8559f17937a474a20dd1d076a17601d6d636a8a (plain) (tree)
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
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
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
4647
4648
4649
4650
4651
4652
4653
4654
4655
4656
4657
4658
4659
4660
4661
4662
4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723





































                                                                             
                               













































                                                                           
                                                

                                          

                                 































































































































































































                                                                                                      
                                             





































































































                                                                                                      
                                             




















                                                                      

































































































                                                                                             



























                                                                                     


                                                                                    

































































                                                                                                    

























































































































                                                                                                    







































































































                                                                                                      
                                                          
































                                                                                   
                                   



































































































                                                                                                   








                                                  















                                                                   

                                                                 














































































































































































                                                                                                             
                                                           




































                                                                                                    
                                                           























































                                                      

                                                             





                                              


                                                                               













                                                                                
                                                                               
 

































                                                                                  
 









                                                                                  
         




























                                                                                                   

                                                                              



                                                                                    

                                                                                        


                                                                                    



                                                                                  







                                                                                 
                                   
                                                                                 

                                                                        
 



                                                                           
                                               






















                                                                                















                                                                                        
                                         










































































































































                                                                                             

                                                              


















































































































































































































































                                                                                       




                                                          















































































































                                                                                    





























                                                                                               






























































































                                                                                            














                                                                            
                                    






                                                                           
                                                                                         

         



                                                                    













                                                                                          






















                                                                                                




















                                                                                           








































                                                                                       

                                                                






























                                                                                              

                                                           

                                                                               
                                                               
                                                                   
 
                                                                        
                                                                               
                                                                               

                                                                                                        




































































































































































































































































































































































































                                                                                                       








































































                                                              















                                                                              
                                                       






                                                                



                                                                 


































                                                                                   
                                                 







































                                                                             




















                                                                                              
 


                                                           
 

                                                                    
                                               





                                                             
 
                                                









                                                       
                                                                                    







                                                                                   
                                                 






















                                                                                   

                                    























































































































                                                                                             
                                                                             

                 
                                   



























































































































































































































































                                                                                                 





















                                                                                                  



























                                                                         

                                                                                                                             































                                                                                              
                          
                                                 

                                                                      
                                                           
                                                                      







                                                                                            
                                  
                                                          
                                                                              





















                                                                          
                                                           
















































































                                                                                    

                                                                









































































































































                                                                                                                                   
                                                                                    


































































































                                                                                   
                                                     











































































                                                                                                  

                                   



                           






















                                                                            





















































                                                                                           
                                                                              
                                         



                                                                                   





























                                                                                                



                                                                            













































                                                                                                        
                                                   
































































































                                                                                                        

















                                                                                     

                                                                   






















































                                                                                                





                                                                                

                                                                                            

                                                                 




















































                                                                                      
                                          




                                                                                          
                                                              

















































































































































































































































































































































































































































































































































                                                                                                                       
                                                                              



                                                                        
                                                                                    





















                                                                                           









                                                                                   






























































                                                                                                                      

                                                    









                                                                                       
                                                                                      



                                                                                
                                                                                            




















































































                                                                                                 


                                                                       
























































                                                                                                  
                                                    









































                                                                                            
                                                                                         

















                                                                                
                                                 









                                                                                            

                                                                   













                                                              

                                                                     




























































                                                                                    




































                                                                                                  
                                                               



                                                                                   



                                                                               
                                 





                                                                                        
























































































































                                                                                   



                                                                                    



























































































































































































                                                                                          
                                   








                                                                         
                                     















































































                                                                                      

                                                                        















































                                                                                            



                                       

















































































                                                                                             
                                                 























































































































































































































                                                                                        









                                                                                     
































































































































                                                                                                           
                                              








































































































































































                                                                             
                                                      
                     
                                               






                                             




























































                                                   






                                                

                                
                                          






                                           

                                          






                                           


                                          






                                           



                                          






                                           




                                          




































































































































                                                                                                    

                                                                          
















































                                                                                   
                                                       













                                                                            

                                                                        















                                                                                           

                                



                                                                           




                                                                      
                                                                                 
                                                                                    
                                                            
 

                                                                   
 
                                                                        
                 

                                                                     
                             
                             






                                                             

                                  



                                                                           




                                                                                    
                                                                                 
                                                                                           

                                                                    




                                                                                            


                             
                             























































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

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

static WebKitDOMElement *
insert_new_line_into_citation (EHTMLEditorView *view,
                               const gchar *html_to_insert)
{
    gboolean html_mode, ret_val;
    EHTMLEditorSelection *selection;
    WebKitDOMDocument *document;
    WebKitDOMElement *element, *paragraph = NULL;
    WebKitDOMNode *caret;

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

    document = webkit_web_view_get_dom_document (
            WEBKIT_WEB_VIEW (view));

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

    if (!element)
        return NULL;

    if (!html_mode) {
        WebKitDOMNode *next_sibling;

        next_sibling = webkit_dom_node_get_next_sibling (
            WEBKIT_DOM_NODE (element));

        if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (next_sibling)) {
            e_html_editor_view_quote_plain_text_element (
                view, WEBKIT_DOM_ELEMENT (next_sibling));

            e_html_editor_view_force_spell_check (view);
        }
    }

    caret = e_html_editor_selection_get_caret_position_node (document);

    paragraph = e_html_editor_selection_get_paragraph_element (
        selection, document, -1, 0);

    if (html_to_insert && *html_to_insert)
        webkit_dom_html_element_set_inner_html (
            WEBKIT_DOM_HTML_ELEMENT (paragraph),
            html_to_insert,
            NULL);
    else
        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 (paragraph), caret, NULL);

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

    remove_node (WEBKIT_DOM_NODE (element));

    e_html_editor_selection_restore_caret_position (selection);

    return paragraph;
}

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

    /* Writing into quoted content */
    if (!view->priv->html_mode && !view->priv->had_selection_before_key_press) {
        gint citation_level, length, word_wrap_length;
        EHTMLEditorSelection *selection;
        WebKitDOMElement *element;
        WebKitDOMDocument *document;
        WebKitDOMNode *parent;

        document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

        selection = e_html_editor_view_get_selection (view);
        word_wrap_length = e_html_editor_selection_get_word_wrap_length (selection);

        element = webkit_dom_document_query_selector (
            document, "span#-x-evo-selection-start-marker", NULL);
        if (element)
            return;

        e_html_editor_selection_save (selection);
        element = webkit_dom_document_query_selector (
            document, "span#-x-evo-selection-start-marker", NULL);

        /* If the selection was not saved, move it into the first child of body */
        if (!element) {
            WebKitDOMHTMLElement *body;

            body = webkit_dom_document_get_body (document);
            element = webkit_dom_document_create_element (
                document, "SPAN", NULL);
            webkit_dom_element_set_id (
                element, "-x-evo-selection-end-marker");
            webkit_dom_node_insert_before (
                webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
                WEBKIT_DOM_NODE (element),
                webkit_dom_node_get_first_child (
                    webkit_dom_node_get_first_child (
                        WEBKIT_DOM_NODE (body))),
                NULL);
            element = webkit_dom_document_create_element (
                document, "SPAN", NULL);
            webkit_dom_element_set_id (
                element, "-x-evo-selection-start-marker");
            webkit_dom_node_insert_before (
                webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
                WEBKIT_DOM_NODE (element),
                webkit_dom_node_get_first_child (
                    webkit_dom_node_get_first_child (
                        WEBKIT_DOM_NODE (body))),
                NULL);
        }

        /* We have to process elements only inside normal block */
        parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
        if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent)) {
            e_html_editor_selection_restore (selection);
            return;
        }

        citation_level = get_citation_level (WEBKIT_DOM_NODE (element), FALSE);
        length = word_wrap_length - 2 * citation_level;

        if (element && citation_level > 0) {
            WebKitDOMElement *block;
            gboolean remove_quoting = FALSE;

            block = webkit_dom_node_get_parent_element (
                WEBKIT_DOM_NODE (element));
            if (webkit_dom_element_query_selector (
                WEBKIT_DOM_ELEMENT (block), ".-x-evo-quoted", NULL)) {
                WebKitDOMNode *prev_sibling;
                WebKitDOMElement *selection_end_marker;

                selection_end_marker = webkit_dom_document_query_selector (
                    document, "span#-x-evo-selection-end-marker", NULL);
                prev_sibling = webkit_dom_node_get_previous_sibling (
                    WEBKIT_DOM_NODE (selection_end_marker));

                if (WEBKIT_DOM_IS_ELEMENT (prev_sibling))
                    remove_quoting = element_has_class (
                        WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted");
            }

            if (element_has_class (block, "-x-evo-temp-text-wrapper"))
                block = webkit_dom_node_get_parent_element (
                    WEBKIT_DOM_NODE (block));

            /* Wrap and quote the line */
            if (!remove_quoting) {
                remove_quoting_from_element (block);

                block = e_html_editor_selection_wrap_paragraph_length (
                    selection, block, length);
                webkit_dom_node_normalize (WEBKIT_DOM_NODE (block));
                block = e_html_editor_view_quote_plain_text_element (view, block);
                element = webkit_dom_document_query_selector (
                    document, "span#-x-evo-selection-start-marker", NULL);
                if (!element) {
                    WebKitDOMElement *marker;

                    marker = webkit_dom_document_create_element (
                        document, "SPAN", NULL);
                    webkit_dom_element_set_id (
                        marker, "-x-evo-selection-start-marker");
                    webkit_dom_node_append_child (
                        WEBKIT_DOM_NODE (block),
                        WEBKIT_DOM_NODE (marker),
                        NULL);
                    marker = webkit_dom_document_create_element (
                        document, "SPAN", NULL);
                    webkit_dom_element_set_id (
                        marker, "-x-evo-selection-end-marker");
                    webkit_dom_node_append_child (
                        WEBKIT_DOM_NODE (block),
                        WEBKIT_DOM_NODE (marker),
                        NULL);
                }
            }
            e_html_editor_view_force_spell_check (view);
        }
        e_html_editor_selection_restore (selection);
    }
}

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;
    gboolean misplaced_selection = FALSE, empty = FALSE;
    gchar *html, *node_text = NULL, *mime_type, *content;
    gchar *base64_encoded, *output, *data;
    const gchar *emoticon_start;
    GFileInputStream *input_stream;
    GOutputStream *output_stream;
    gssize size;
    WebKitDOMDocument *document;
    WebKitDOMElement *span, *selection_start_marker, *selection_end_marker;
    WebKitDOMNode *node, *insert_before, *prev_sibling, *next_sibling;
    WebKitDOMNode *selection_end_marker_parent;
    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;

    e_html_editor_selection_save (e_html_editor_view_get_selection (view));

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    selection_start_marker = webkit_dom_document_get_element_by_id (
        document, "-x-evo-selection-start-marker");
    selection_end_marker = webkit_dom_document_get_element_by_id (
        document, "-x-evo-selection-end-marker");

    /* If the selection was not saved, move it into the first child of body */
    if (!selection_start_marker || !selection_end_marker) {
        WebKitDOMHTMLElement *body;

        body = webkit_dom_document_get_body (document);
        selection_start_marker = webkit_dom_document_create_element (
            document, "SPAN", NULL);
        webkit_dom_element_set_id (
            selection_start_marker, "-x-evo-selection-start-marker");
        webkit_dom_node_insert_before (
            webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
            WEBKIT_DOM_NODE (selection_start_marker),
            webkit_dom_node_get_first_child (
                webkit_dom_node_get_first_child (
                    WEBKIT_DOM_NODE (body))),
            NULL);
        selection_end_marker = webkit_dom_document_create_element (
            document, "SPAN", NULL);
        webkit_dom_element_set_id (
            selection_end_marker, "-x-evo-selection-end-marker");
        webkit_dom_node_insert_before (
            webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
            WEBKIT_DOM_NODE (selection_end_marker),
            webkit_dom_node_get_first_child (
                webkit_dom_node_get_first_child (
                    WEBKIT_DOM_NODE (body))),
            NULL);
    }

    /* Sometimes selection end marker is in body. Move it into next sibling */
    selection_end_marker_parent = webkit_dom_node_get_parent_node (
        WEBKIT_DOM_NODE (selection_end_marker));
    if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (selection_end_marker_parent)) {
        webkit_dom_node_insert_before (
            webkit_dom_node_get_parent_node (
                WEBKIT_DOM_NODE (selection_start_marker)),
            WEBKIT_DOM_NODE (selection_end_marker),
            WEBKIT_DOM_NODE (selection_start_marker),
            NULL);
    }
    selection_end_marker_parent = webkit_dom_node_get_parent_node (
        WEBKIT_DOM_NODE (selection_end_marker));

    /* Determine before what node we have to insert the smiley */
    insert_before = WEBKIT_DOM_NODE (selection_start_marker);
    prev_sibling = webkit_dom_node_get_previous_sibling (
        WEBKIT_DOM_NODE (selection_start_marker));
    if (prev_sibling) {
        if (webkit_dom_node_is_same_node (
            prev_sibling, WEBKIT_DOM_NODE (selection_end_marker))) {
            insert_before = WEBKIT_DOM_NODE (selection_end_marker);
        } else {
            prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
            if (prev_sibling &&
                webkit_dom_node_is_same_node (
                prev_sibling, WEBKIT_DOM_NODE (selection_end_marker))) {
                insert_before = WEBKIT_DOM_NODE (selection_end_marker);
            }
        }
    } else
        insert_before = WEBKIT_DOM_NODE (selection_start_marker);

    /* Look if selection is misplaced - that means that the selection was
     * restored before the previously inserted smiley in situations when we
     * are writing more smileys in a row */
    next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker));
    if (next_sibling && WEBKIT_DOM_IS_ELEMENT (next_sibling))
        if (element_has_class (WEBKIT_DOM_ELEMENT (next_sibling), "-x-evo-smiley-wrapper"))
            misplaced_selection = TRUE;

    mime_type = g_content_type_get_mime_type (load_context->content_type);
    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));

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

    content = webkit_dom_node_get_text_content (selection_end_marker_parent);
    empty = !*content || (g_strcmp0 (content, UNICODE_ZERO_WIDTH_SPACE) == 0);
    g_free (content);

    /* 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>%s",
        output, emoticon ? emoticon->text_face : "", emoticon->icon_name,
        load_context->name, emoticon ? emoticon->text_face : "",
        empty ? "&#8203;" : "");

    span = webkit_dom_document_create_element (document, "SPAN", NULL);

    if (misplaced_selection) {
        /* Insert smiley and selection markers after it */
        webkit_dom_node_insert_before (
            webkit_dom_node_get_parent_node (insert_before),
            WEBKIT_DOM_NODE (selection_start_marker),
            webkit_dom_node_get_next_sibling (next_sibling),
            NULL);
        webkit_dom_node_insert_before (
            webkit_dom_node_get_parent_node (insert_before),
            WEBKIT_DOM_NODE (selection_end_marker),
            webkit_dom_node_get_next_sibling (next_sibling),
            NULL);
        span = WEBKIT_DOM_ELEMENT (
            webkit_dom_node_insert_before (
                webkit_dom_node_get_parent_node (insert_before),
                WEBKIT_DOM_NODE (span),
                webkit_dom_node_get_next_sibling (next_sibling),
                NULL));
    } else {
        span = WEBKIT_DOM_ELEMENT (
            webkit_dom_node_insert_before (
                webkit_dom_node_get_parent_node (insert_before),
                WEBKIT_DOM_NODE (span),
                insert_before,
                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 (
        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
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
change_quoted_block_to_normal (EHTMLEditorView *view)
{
    gint citation_level, success = FALSE;
    WebKitDOMDocument *document;
    WebKitDOMElement *selection_start_marker, *selection_end_marker, *block;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

    selection_start_marker = webkit_dom_document_query_selector (
        document, "span#-x-evo-selection-start-marker", NULL);
    selection_end_marker = webkit_dom_document_query_selector (
        document, "span#-x-evo-selection-end-marker", NULL);

    if (!selection_start_marker || !selection_end_marker)
        return FALSE;

    block = webkit_dom_node_get_parent_element (
        WEBKIT_DOM_NODE (selection_start_marker));

    citation_level = get_citation_level (
        WEBKIT_DOM_NODE (selection_start_marker), FALSE);

    if (selection_start_marker && citation_level > 0) {
        if (element_has_class (block, "-x-evo-temp-text-wrapper"))
            block = webkit_dom_node_get_parent_element (
                WEBKIT_DOM_NODE (block));

        if (webkit_dom_element_query_selector (
            WEBKIT_DOM_ELEMENT (block), ".-x-evo-quoted", NULL)) {

            WebKitDOMNode *prev_sibling;

            webkit_dom_node_normalize (WEBKIT_DOM_NODE (block));

            prev_sibling = webkit_dom_node_get_previous_sibling (
                WEBKIT_DOM_NODE (selection_end_marker));

            if (WEBKIT_DOM_IS_ELEMENT (prev_sibling))
                success = element_has_class (
                    WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted");
        }

        if (view->priv->html_mode)
            success = WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (
                webkit_dom_node_get_parent_element (
                    WEBKIT_DOM_NODE (block)));

        if (success) {
            gchar *inner_html;
            WebKitDOMElement *paragraph;

            inner_html = webkit_dom_html_element_get_inner_html (
                WEBKIT_DOM_HTML_ELEMENT (block));
            webkit_dom_element_set_id (
                WEBKIT_DOM_ELEMENT (block), "-x-evo-to-remove");

            paragraph = insert_new_line_into_citation (view, inner_html);
            g_free (inner_html);

            if (view->priv->html_mode) {
                webkit_dom_node_insert_before (
                    WEBKIT_DOM_NODE (paragraph),
                    WEBKIT_DOM_NODE (selection_start_marker),
                    webkit_dom_node_get_first_child (
                        WEBKIT_DOM_NODE (paragraph)),
                    NULL);
                webkit_dom_node_insert_before (
                    WEBKIT_DOM_NODE (paragraph),
                    WEBKIT_DOM_NODE (selection_end_marker),
                    webkit_dom_node_get_first_child (
                        WEBKIT_DOM_NODE (paragraph)),
                    NULL);

            }

            remove_quoting_from_element (paragraph);
            remove_wrapping_from_element (paragraph);

            if (block)
                remove_node (WEBKIT_DOM_NODE (block));
            block = webkit_dom_document_get_element_by_id (
                document, "-x-evo-to-remove");
            if (block)
                remove_node (WEBKIT_DOM_NODE (block));
            remove_node_if_empty (
                webkit_dom_node_get_next_sibling (
                    WEBKIT_DOM_NODE (paragraph)));
            e_html_editor_view_force_spell_check (view);
        }
    }

    return success;
}

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, "")) ? TRUE : FALSE;
    }

    if (event->keyval == GDK_KEY_BackSpace) {
        EHTMLEditorSelection *selection;

        selection = e_html_editor_view_get_selection (view);

        /* BackSpace pressed in the beginning of quoted content changes
         * format to normal and inserts text into body */
        if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") == 0) {
            e_html_editor_selection_save (selection);
            if (change_quoted_block_to_normal (view)) {
                e_html_editor_selection_restore (selection);
                return TRUE;
            }
            e_html_editor_selection_restore (selection);
        } else
            view->priv->had_selection_before_key_press = TRUE;

        /* BackSpace in indented block decrease indent level by one */
        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
fix_paragraph_structure_after_pressing_enter_after_smiley (EHTMLEditorSelection *selection,
                                                           WebKitDOMDocument *document)
{
    WebKitDOMElement *element;

    element = webkit_dom_document_query_selector (
        document, "span.-x-evo-smiley-wrapper > br", NULL);

    if (element) {
        WebKitDOMNode *parent;

        parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
        webkit_dom_html_element_set_inner_html (
            WEBKIT_DOM_HTML_ELEMENT (
                webkit_dom_node_get_parent_node (parent)),
            UNICODE_ZERO_WIDTH_SPACE,
            NULL);
    }
}

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

    view->priv->had_selection_before_key_press = FALSE;

    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);
        if (view->priv->html_mode)
            fix_paragraph_structure_after_pressing_enter_after_smiley (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);
}

static gboolean
create_anchor_for_link (const GMatchInfo *info,
                        GString *res,
                        gpointer data)
{
    gchar *match;
    gboolean address_surrounded;

    match = g_match_info_fetch (info, 0);

    address_surrounded =
        strstr (match, "@") &&
        g_str_has_prefix (match, "&lt;") &&
        g_str_has_suffix (match, "&gt;");

    if (address_surrounded)
        g_string_append (res, "&lt;");

    g_string_append (res, "<a href=\"");
    if (strstr (match, "@")) {
        g_string_append (res, "mailto:");
        if (address_surrounded) {
            g_string_append (res, match + 4);
            g_string_truncate (res, res->len - 4);
        } else
            g_string_append (res, match);

        g_string_append (res, "\">");
        if (address_surrounded) {
            g_string_append (res, match + 4);
            g_string_truncate (res, res->len - 4);
        } else
            g_string_append (res, match);
    } else {
        g_string_append (res, match);
        g_string_append (res, "\">");
        g_string_append (res, match);
    }
    g_string_append (res, "</a>");

    if (address_surrounded)
        g_string_append (res, "&gt;");

    g_warning ("%s", res->str);
    g_free (match);

    return FALSE;
}

static gboolean
replace_to_nbsp (const GMatchInfo *info,
                 GString *res,
                 gpointer data)
{
    gchar *match;
    gint ii, length = 0;

    match = g_match_info_fetch (info, 0);

    /* Replace tabs with 8 whitespaces */
    if (strstr (match, "\x9"))
        length = 8;
    else
        length = strlen (match);

    for (ii = 0; ii < length; ii++)
        g_string_append (res, "&nbsp;");

    g_free (match);

    return FALSE;
}

/* 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;
    GRegex *regex_nbsp = NULL, *regex_links = NULL;

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

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

    /* Replace tabs and 2+ spaces with non breaking spaces */
    regex_nbsp = g_regex_new ("\\s{2,}|\x9", 0, 0, NULL);
    regex_links = g_regex_new (URL_PATTERN, 0, 0, NULL);

    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 *truncated = g_strdup (rest);
            gchar *rest_to_insert;

            g_strchomp (truncated);

            rest_to_insert = g_regex_replace_eval (
                regex_nbsp, truncated, -1, 0, 0, replace_to_nbsp, NULL, NULL);
            g_free (truncated);

            if (strstr (rest_to_insert, "http") ||
                strstr (rest_to_insert, "ftp") ||
                strstr (rest_to_insert, "@")) {
                truncated = g_regex_replace_eval (
                    regex_links,
                    rest_to_insert,
                    -1,
                    0,
                    0,
                    create_anchor_for_link,
                    NULL,
                    NULL);

                g_free (rest_to_insert);
                rest_to_insert = truncated;
            }

            webkit_dom_html_element_set_inner_html (
                WEBKIT_DOM_HTML_ELEMENT (paragraph),
                rest_to_insert,
                NULL);

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

            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_regex_unref (regex_nbsp);
    g_regex_unref (regex_links);
    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->had_selection_before_key_press = FALSE;
    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 gboolean
element_is_selection_marker (WebKitDOMElement *element)
{
    gboolean is_marker = FALSE;

    is_marker =
        element_has_id (element, "-x-evo-selection-start-marker") ||
        element_has_id (element, "-x-evo-selection-end-marker");

    return is_marker;
}

static gboolean
check_if_suppress_next_node (WebKitDOMNode *node)
{
    if (node && WEBKIT_DOM_IS_ELEMENT (node))
        if (element_is_selection_marker (WEBKIT_DOM_ELEMENT (node)))
            if (!webkit_dom_node_get_previous_sibling (node))
                return FALSE;

    return TRUE;
}

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_is_selection_marker (WEBKIT_DOM_ELEMENT (node))) {
            move_next = TRUE;
            /* If there is collapsed selection in the beginning of line
             * we cannot suppress first text that is after the end of
             * selection */
            suppress_next = check_if_suppress_next_node (prev_sibling);
            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 (!prev_sibling)
                insert_quote_symbols_before_node (
                    document, node, quote_level, FALSE);

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

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++) {
        WebKitDOMElement *element;

        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");
            remove_quoting_from_element (element);
        }
    }
}

/**
 * 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 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);
                    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));
            }
            if (!webkit_dom_node_has_child_nodes (child))
                if (!changing_mode && to_plain_text)
                    g_string_append (buffer, "\n");
        }

        /* 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_from_view (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_from_view (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_plain_text (EHTMLEditorView *view,
                                                  const gchar *text)
{
    view->priv->convertor_insert = TRUE;

    webkit_web_view_load_string (
        view->priv->convertor_web_view, text, "text/plain", 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:hover "
        "{\n"
        "  outline: 1px dashed red; \n"
        "  resize: both; \n"
        "  overflow: hidden; \n"
        "  display: inline-block; \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 (
        stylesheet,
        ".-x-evo-quoted "
        "{\n"
        "  -webkit-user-select: none;\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++) {
        const gchar *id;
        gchar *cid;
        WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
        gchar *src = webkit_dom_element_get_attribute (
            WEBKIT_DOM_ELEMENT (node), "src");

        if ((id = g_hash_table_lookup (added, src)) != NULL) {
            cid = g_strdup_printf ("cid:%s", id);
        } else {
            CamelMimePart *part;

            part = e_html_editor_view_add_inline_image_from_element (
                view, WEBKIT_DOM_ELEMENT (node), "src", uid_domain);
            parts = g_list_append (parts, part);

            id = camel_mime_part_get_content_id (part);
            cid = g_strdup_printf ("cid:%s", id);

            g_hash_table_insert (added, src, (gpointer) id);
        }
        webkit_dom_element_set_attribute (
            WEBKIT_DOM_ELEMENT (node), "src", cid, NULL);
        g_free (src);
        g_free (cid);
    }

    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;
        const gchar *id;
        gchar *cid = NULL;
        WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
        gchar *src = webkit_dom_element_get_attribute (
            WEBKIT_DOM_ELEMENT (node), "background");

        if ((id = g_hash_table_lookup (added, src)) != NULL) {
            cid = g_strdup_printf ("cid:%s", id);
            webkit_dom_element_set_attribute (
                WEBKIT_DOM_ELEMENT (node), "background", cid, NULL);
        } else {
            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);
                id = camel_mime_part_get_content_id (part);
                g_hash_table_insert (added, src, (gpointer) id);
                cid = g_strdup_printf ("cid:%s", id);
                webkit_dom_element_set_attribute (
                    WEBKIT_DOM_ELEMENT (node), "background", cid, NULL);
            }
        }
        g_free (src);
        g_free (cid);
    }

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