aboutsummaryrefslogblamecommitdiffstats
path: root/e-util/e-html-editor-view.c
blob: 7843bd1c87ddbf867d55692e9a0291efbc20b64c (plain) (tree)
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179





































                                                                             
                               












                                                                   
                    




























                                                                           
                                 



                                     
                                          

                                          

                                 























































































































                                                                                         









                                                                             


                                                              






                                                             






                                                                                  

                                                                        


                                           


                                         










                                                                             








                                                                      







                                                                                       
                                                                                     



                                                                             

                                                                 

                                       









                                                                  
                                                        
















                                                                                                      
                                             





                                                                      
                                                    


           






                                                 
                                                                        
















                                                                             









                                                                                  


                                                                              





















                                                                                 




































                                                                                                      
                                             





                                                                      
                                                    










                                                               

                                                 

 
























                                                                                             




















































                                                                                    
                              


                           










































                                                                                            




                                                                                    





                     














                                                                                          



                                                           
                                                       







                                                            

                                                                             
                           

                                                                   


                                                                                   
 
                                                         
 

                                                                                
 
                                                                  
                                                                  
 




                                                                                                  
 



























































                                                                                                    
 

                                                              
 

                                    

         






                                                                      




                                                                                         




                                                                              

                                                                       








                                                                                                       



































                                                                                



























                                                                                     


                                                                                    

































































                                                                                                    

                                         
                                     

















































                                                                                            
                                                                                                








                                                                                       

                                         


                                                        
                                                            














                                                                                                    



                                                                                             
                                                     
                                                                                 




                                                                                       

                                                                                              





















                                                                                              


                                                                                                  
                         


                                                            
















































                                                                                                      
                              

























                                                                                              
                                              




                                              
                                    









                                                                           
                              













                                                                                     
                                                          
































                                                                                   
                                   
         
                              

















                                                                                                   
                              


           






































                                                                                     






















                                                                                 







                                                                                             
                                                                                        
                                                     


                                                       
 


                                                            
                                                               












































                                                                    








                                                  















                                                                   

                                                                 














































































































































































                                                                                                             
                                                           




































                                                                                                    
                                                           























































                                                      

                                                             





                                              


                                                                               













                                                                                
                                                                               
 

































                                                                                  
 
                                                                                  
                                                                        







                                                                            
         




























                                                                                                   

                                                                              



                                                                                    

                                                                                        


                                                                                    



                                                                                  







                                                                                 
                                   
                                                                                 

                                                                        
 



                                                                           
                                               






















                                                                                















                                                                                        
                                         










































































































































                                                                                             

                                                              














                                                                                      














                                                                   





































































































































































































                                                                                       
                                                                                       




                                                         
                                                                                   



                                                     





                                                                                   














                                                                           




                                                          















































































































                                                                                    
























                                                                                               
                              




                       















                                                                                

                                                                      




                                                                 







                                                                              
                                                                          



                                                                                            



                                                                                           





                                                                    
         
 


                                             
 



                                                                        
 

                                                                             
 













                                                                                 
 
                         
 


                                                                 






                                                               




                                                                      




























                                                                                            
 






































                                                                                               

                         










                                                                          






                       














                                                                            
                                    
                                                
                                                       




                                                                           

                                                                      
                                                                                         
                 







                                                                                

         



                                                                    


                                                                               
                                                                       


                                                                            
                                                                                                  



                                                                    
                                                                     

                                                                              

                                                                      
                                                    


                                                                                        












                                                                                                           
















                                                                                                




















                                                                                           


















                                                                             
                              





















                                                                                       

                                                                
































                                                                                              

                                                     
                                                               
                                                                   
 
                                                                        
                                                                               
                                                                               

                                                                                                        



























                                                                                                       































































































                                                                                                                 











                                                                   














                                                                                         




























































































































































































































































































































































                                                                                      
           



                                                    
 



                         
 

                                                  
 


                                                                
 






                                                                           
 
                                                   
 








                                                                    
 


                                                                                   
 








                                                                                 

                 

                                   
 





                                                                            
 









                                                                
 


                                                     
 

                                         
 




                                                                                   
 

                                                                   
 






                                                                           
 

                                         
 


                                                                                              
 


                                                                                
 
                                                             
 



                                          
 




                                                  
 





                                                       
 





                                                              
                      

                                  
 




                                                                              
 




                                                                                       
                                         
 

                                                                                   
 





                                                               
 




                                                       
 

                                   
 

                           
 



                                                       
 


                                                                            
 

                         
 






                                                                            
 
                    


           






















                                                          



                                                        
 




                                                   
 
                                                      
 



                                     
 

                                                 
 

                                                                           
 











                                                                                          
 

                                       
 

                                                                                         
 



                                                                                           
 



                                             
 







                                                                                   
 










                                                              
 



                                                                        
 









                                                                                   
 



                                                                            
 


                                                                                   
 


                                         
 







                                                                       
 


                                         
 




                                                                               
                                    

                                                                                              




                                                          
 

                                              
 




























                                                                                                        
                                                                  


                                            

                 

                                                                                       
 




                                                                            
 


                                                                                                        
 



                                                                                       
 

                                              
 








                                                                                   
 






























                                                                                   
         
 
 







                                                                       
 
                                                                                  
 









                                                                                     
                                                  









                                                                            
                              






























                                                                                
 

                                                                               
 







                                                                                                
 

                                                                                
 

                                                                                
 



                                                                                            
                 
         
                              
 
                                               
                                                                         
 





                                                                                      
 

                                                              
 

                                                                            
 

                               
         
                                    
 





                                                                         
 
                                               

 









                                                                              
 
                                    

                                      


                                                                             




                                                                      
 
                                                                                          
 




                                                                                  
                                    
 
 




                                               
                        

                                    
 
                                             
 





                                                   





                                               




                                                 

                                                      
                                                              

                                             

                                                      
                                                              
                
                                                      
                                             
                                                      




                                              
 
                       
 
                     

 


                                        
                                   
 


                                       
 
                                             














                                                                    
 





                                                                    






                                                

 






                                                                



                                                                              
           




                                                         
 





                                                       
                                      
 

                                                                
 

                                           
 


                                                                        
                                                            
 





                                                                   
 

                                                                                 
 
                                                     
 
                                       
 





                                                                     
 






                                                                                  
 




                                                                                               
 

                                                                             
 

                                                                      
 



                                                                          
 




                                                             
 


                                             
 



                                                                     
 

                                                                     
 



                                                             
 

                                               
 


                                                           
 
                                               
 
                                                               







                                                                     
                                           
 
                                                                          








                                                                  
 







                                                                          
 



                                                                                           



                                                             
 
                                                









                                                                    
                 
 

                                              
 
                                      


                                                       
         
 
                                              



                                                                                    
 




                                                                                               
 
                                       
 
                                                       







                                                             
                                   
 
                                                                  








                                                          
 


                                                   
 

                                                            

                                                                                 

                              





                                                                              





                                                     

         














                                                                               

 

                                          
 
                                       
 

                                                             
 


                                                              
 

                                                                      
 
                                               
 

                            

 

                                                                           
 

                                   
 

                                                                      
 





                                                                              
         

                     

 

                                                                        
 

                                   
 








                                                                             

         
                     


           

                                                                                        
 


                                                                                  
                





                                                                     
 


                                                                                        
 


                                                                             
 

                                                                       
 











                                                                                             
 



                                                                              
 








                                                                                      
 
                                                                             

                 
                                   
         
                              
 
                                                      
 
                                                                           
 

                                                                             
 

                                                                   
 



                                                            
 










                                                                                 
 



                                                             
 



                                                                            
 


                                                                        
 

                                                                           
 
















































                                                                                                 
 

                                                             
 

                                                                                 
 






























                                                                                     
 





                                                                      
 


                                                                  
 

















                                                                                







                                                                                   

                                               



                                                                         


                              






                                                                                              





                                                                                         
 

                                                                   
 






                                                                                        
 

                            

 
           
























                                                                         

                                                                                             
 
                                                                                  
                               


                                       
                                  
                                             

                              
 
                                                     
 
                                                                             
                                                                           
 





                                                                             
                                                                                
 
                            
 
                                                                          




                                                                
                                 


















                                                                                                 
 



                                                                                     
                                                                   











                                                                                           





                                                                                         




                                                                         
                                                           
                                                                  
 




                                                                                                   

                                                       

                                                                         
                                      
 






                                                                                              
                                                           



















                                                                                           











                                                                                                

                                                                                      



                                                                                 
                                      
                 
 
                                                           
 


                                                                                         

                                                                                       
 


                                                                           
                                                           


                                                                                         

                                                                               










                                                                                              

                 


















                                                                                              
 




                                                                          





























                                                                                   
                           







                                                                                                  































                                                                                                
                        
                                                                         

                                                                                       
                 
 






                                                                       
 










                                                                                  
 
                                                                                                  
                                             




                                                                                     
                                                  




                                                                                
 
                                                              





                                                                                             






                                                                          


                                                    

                                                    


                                                            


                            
 





                                                                       
 

                                                                               
 
                                                                         
 






                                                                       
 





                                                         
 

                                                                        
 




                                                                                                  
 




                                                       
 









                                               
 
                                                           
 

                                                                         
 









                                                          
 
                                                                        
 
                                                                                                                             
 



                                                     
 



                                                                           
 














                                                                                              
 



                                              
 







                                                                      
 


                                                                 










                                                                                            
 



                                                           
 
                                               
 



                                                                          
 



                                                            
 
                                 
 
                                                    
                                             
 


                                                                                 
 




                                        
 


                                                                                  
 



                                                                             
 
                                                        
 


                                                                                    
 






                                                             
 










                                                             


   
                                    

                             


                                                                                
  

                                                                                
   

                                                        
 
                                                                  
 

                                     
 



















                                                                                
 
                                                                   
 






                                                               
 

















































                                                                                                                                   

         



                                                                             
 











                                                                   
 
                                   


   
                                  
                             
                                                     
  


                                                                                

    

                                                      
 
                                                        
 

                                           
 
                                      
 
                                                     

















































                                                                                       
                                    


                                                                              




                                                                                   
                              






                                                         
                                    


                                                                              




                                                                                   
                              






































































































































































                                                                                                              































































































































































































                                                                                                                       
                                                                        















                                                                          















                                                          













































                                                                                  
                                                    
















                                                                                
                                            

                                                                           






















                                                                                                    
 




                                                                                           













                                                                                           
                                             

                                                       









                                                                                   

                                                                                                      






                                                                                                 

                                                                                                



                                                                                                                      


                                                                   























                                                                                                                  
                                               




















                                                                                           

                                                    

                                         
                                  





                                                                                       
                                                    

                                                                                   
                                                                                      

                                                                   







                                                                                            
 
                                                                                









                                                                                            












                                                                                           
                                  









                                                                                  
                                          


                                                                                     
                                                                                                 
                                                                                            

                                                                                            
                                                                                       



                                                                                

                                                                                            


                         



                                                                                      






























                                                                                                 
                                  









                                                                                         
                                          




                                                                                         

                                                                             
                                                                       


                               

                                                                                                 


                                                                              













                                                                                  
                         








                                                                                  












                                                                                              
                                          














                                                                                 
                                                    
                                                 
                                          

















                                                                                         




                                                                                                       




                                                                                                





                                                                                         

                                                                                                  
 


                                                                                                                
                                 

                         
















                                                                                  













                                                                                            


                                                                                  

                                                                              




                                                                                          
                                                                                         








                                                                  
                                       




                                                                                
                               


           
                                                 









                                                                                            

                                                                   

                              













                                                              

                                                                     

                                

















































                                                                                    

                                 










                                                              




































                                                                                                  
                                                               



                                                                                   



                                                                               
                                 





                                                                                        



                                               
                                    



















                                                                             
                                           



                                                                             










                                                                               






































































                                                                                   



                                                                                    




















































                                                                                











                                                                                 

                                      


















                                                                           
                                        

                                                            











                                                                             

                                                                

                                                                           

                                                            

                                                                                      
                                                       






























                                                                                       

                                                                              
                         
                                                    



                                                                 

                                                






                                                            
                                                                        
















                                                                                          
                                                                                      




                                                                
                                                                           

                 
                                    










                                                                                 
                                   

                                                   
                                    






                                                                         
                                     

                                        








                                                            
                                             
                            

                                                                             



                                                                                             
                                                                
 
                                                               
                                                          



                                        



























































                                                                                      
                                    

                                                 

                                                                                       




















                                                                                      
                                    


























                                                                                            



                                       

















































































                                                                                             
                                                 


                             
                                                         












                                                                             








                                                                                             











































                                                                              




                                                               





















































































































































                                                                                  









                                                                                     
    









                                                                                     






























                                                                                             
                 
                                                                 


















                                                                             
                                                 


   













                                                                         





































































                                                                                                           
                                              









































                                                                             



                                                          

                                                                  
                            
                                 

                                      
 
                                                        
 



                                                              
                                              

                                                                                       

                              


















                                                                                           



































































































                                                                       
                                                      
                     
                                               






                                             






                                                  





















                                               









































                                                   




                                                             






                                                 








                                                     
 






                                                

                                
                                          






                                           

                                          






                                           


                                          






                                           



                                          






                                           




                                          




































































































































                                                                                                    

                                                                          
















































                                                                                   
                                                       













                                                                            

                                                                        















                                                                                           

                                



                                                                           




                                                                      
                                                                                 
                                                                                    
                                                            
 

                                                                   
 
                                                                        
                 

                                                                     
                             
                             
         
                              





                                                             

                                  



                                                                           




                                                                                    
                                                                                 
                                                                                           

                                                                    




                                                                                            


                             
                             

         
                              





















































                                                                                              
/*
 * 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
#define TAB_LENGTH 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 *mail_settings;
    GSettings *font_settings;
    GSettings *aliasing_settings;

    gboolean convertor_insert;
    gboolean body_input_event_removed;

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

static WebKitDOMElement *
get_parent_block_element (WebKitDOMNode *node)
{
    WebKitDOMElement *parent = webkit_dom_node_get_parent_element (node);

    while (parent &&
           !WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) &&
           !WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) &&
           !WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (parent) &&
           !WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (parent) &&
           !WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent) &&
           !WEBKIT_DOM_IS_HTML_HEADING_ELEMENT (parent) &&
           !element_has_tag (parent, "address")) {
        parent = webkit_dom_node_get_parent_element (
            WEBKIT_DOM_NODE (parent));
    }

    return parent;
}

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

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

    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);
    e_html_editor_selection_save (selection);

    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;

    /* 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 = get_parent_block_element (WEBKIT_DOM_NODE (selection_start_marker));

    /* 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 (get_parent_block_element (
            WEBKIT_DOM_NODE (selection_end_marker))),
        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 (parent), 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 (selection);
}

static void
refresh_spell_check (EHTMLEditorView *view,
                     gboolean enable_spell_check)
{
    EHTMLEditorSelection *selection;
    WebKitDOMDocument *document;
    WebKitDOMDOMSelection *dom_selection;
    WebKitDOMDOMWindow *window;
    WebKitDOMElement *selection_start_marker, *selection_end_marker;
    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 (selection);

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

    /* Sometimes the web view is not focused, so we have to save the selection
     * manually into the body */
    if (!selection_start_marker || !selection_end_marker) {
        if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)))
            return;

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

    /* 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 (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)
{
    if (view->priv->inline_spelling)
        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 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
quote_plain_text_element_after_wrapping (WebKitDOMDocument *document,
                                         WebKitDOMElement *element,
                                         gint quote_level)
{
    WebKitDOMNodeList *list;
    WebKitDOMNode *quoted_node;
    gint length, ii;
    gchar *quotation;

    quoted_node = WEBKIT_DOM_NODE (
        webkit_dom_document_create_element (document, "SPAN", NULL));
    webkit_dom_element_set_class_name (
        WEBKIT_DOM_ELEMENT (quoted_node), "-x-evo-quoted");
    quotation = get_quotation_for_level (quote_level);
    webkit_dom_html_element_set_inner_html (
        WEBKIT_DOM_HTML_ELEMENT (quoted_node), quotation, NULL);

    list = webkit_dom_element_query_selector_all (
        element, "br.-x-evo-wrap-br", NULL);
    webkit_dom_node_insert_before (
        WEBKIT_DOM_NODE (element),
        quoted_node,
        webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)),
        NULL);

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

        webkit_dom_node_insert_before (
            webkit_dom_node_get_parent_node (br),
            webkit_dom_node_clone_node (quoted_node, TRUE),
            webkit_dom_node_get_next_sibling (br),
            NULL);
    }

    g_object_unref (list);
    g_free (quotation);
}

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 gboolean
return_pressed_in_empty_line (EHTMLEditorSelection *selection,
                              WebKitDOMDocument *document)
{
    WebKitDOMDOMSelection *dom_selection;
    WebKitDOMDOMWindow *dom_window;
    WebKitDOMNode *node;
    WebKitDOMRange *range;

    dom_window = webkit_dom_document_get_default_view (document);
    dom_selection = webkit_dom_dom_window_get_selection (dom_window);

    range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
    if (!range)
        return FALSE;

    node = webkit_dom_range_get_start_container (range, NULL);
    if (!WEBKIT_DOM_IS_TEXT (node)) {
        WebKitDOMNode *first_child;

        first_child = webkit_dom_node_get_first_child (node);
        if (first_child && WEBKIT_DOM_IS_ELEMENT (first_child) &&
            element_has_class (WEBKIT_DOM_ELEMENT (first_child), "-x-evo-quoted")) {
            WebKitDOMNode *prev_sibling;

            prev_sibling = webkit_dom_node_get_previous_sibling (node);
            if (!prev_sibling)
                return webkit_dom_range_get_collapsed (range, NULL);
        }
    }

    return FALSE;
}

static WebKitDOMNode *
get_parent_block_node_from_child (WebKitDOMNode *node)
{
    WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);

    if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-temp-text-wrapper") ||
        WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) ||
        element_has_tag (WEBKIT_DOM_ELEMENT (parent), "b") ||
        element_has_tag (WEBKIT_DOM_ELEMENT (parent), "i") ||
        element_has_tag (WEBKIT_DOM_ELEMENT (parent), "u"))
        parent = webkit_dom_node_get_parent_node (parent);

    return parent;
}

static WebKitDOMElement *
insert_new_line_into_citation (EHTMLEditorView *view,
                               const gchar *html_to_insert)
{
    gboolean html_mode, ret_val, avoid_editor_call;
    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);

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

    avoid_editor_call =
        return_pressed_in_empty_line (selection, document);

    if (avoid_editor_call) {
        WebKitDOMElement *selection_start_marker;
        WebKitDOMNode *current_block, *parent, *parent_block, *block_clone;

        e_html_editor_selection_save (selection);

        selection_start_marker = webkit_dom_document_get_element_by_id (
            document, "-x-evo-selection-start-marker");

        current_block = get_parent_block_node_from_child (
            WEBKIT_DOM_NODE (selection_start_marker));

        block_clone = webkit_dom_node_clone_node (current_block, TRUE);
        /* Find selection start marker and restore it after the new line
         * is inserted */
        selection_start_marker = webkit_dom_element_query_selector (
            WEBKIT_DOM_ELEMENT (block_clone), "#-x-evo-selection-start-marker", NULL);

        /* Find parent node that is immediate child of the BODY */
        /* Build the same structure of parent nodes of the current block */
        parent_block = current_block;
        parent = webkit_dom_node_get_parent_node (parent_block);
        while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
            WebKitDOMNode *node;

            parent_block = parent;
            node = webkit_dom_node_clone_node (parent_block, FALSE);
            webkit_dom_node_append_child (node, block_clone, NULL);
            block_clone = node;
            parent = webkit_dom_node_get_parent_node (parent_block);
        }

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

        webkit_dom_html_element_set_inner_text (
            WEBKIT_DOM_HTML_ELEMENT (paragraph),
            UNICODE_ZERO_WIDTH_SPACE,
            NULL);

        /* Insert the selection markers to right place */
        webkit_dom_node_insert_before (
            WEBKIT_DOM_NODE (paragraph),
            webkit_dom_node_get_next_sibling (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_start_marker),
            webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (paragraph)),
            NULL);

        /* Insert the cloned nodes before the BODY parent node */
        webkit_dom_node_insert_before (
            webkit_dom_node_get_parent_node (parent_block),
            block_clone,
            parent_block,
            NULL);

        /* Insert the new empty paragraph before the BODY parent node */
        webkit_dom_node_insert_before (
            webkit_dom_node_get_parent_node (parent_block),
            WEBKIT_DOM_NODE (paragraph),
            parent_block,
            NULL);

        /* Remove the old block (its copy was moved to the right place) */
        remove_node (current_block);

        e_html_editor_selection_restore (selection);

        return NULL;
    } else {
        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;

        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)) {
            gint citation_level, length;
            gint word_wrap_length =
                e_html_editor_selection_get_word_wrap_length (selection);
            WebKitDOMNode *node;

            node = webkit_dom_node_get_first_child (next_sibling);
            while (node && is_citation_node (node))
                node = webkit_dom_node_get_first_child (node);

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

            /* Rewrap and requote first block after the newly inserted line */
            if (node && WEBKIT_DOM_IS_ELEMENT (node)) {
                remove_quoting_from_element (WEBKIT_DOM_ELEMENT (node));
                remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (node));
                node = WEBKIT_DOM_NODE (e_html_editor_selection_wrap_paragraph_length (
                    selection, WEBKIT_DOM_ELEMENT (node), length));
                quote_plain_text_element_after_wrapping (
                    document, WEBKIT_DOM_ELEMENT (node), citation_level);
            }

            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) {
        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_block_element (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) {
            gchar *content;
            gint text_length;
            WebKitDOMElement *block;
            gboolean remove_quoting = FALSE;

            block = WEBKIT_DOM_ELEMENT (parent);
            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");
            }

            content = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (block));
            text_length = g_utf8_strlen (content, -1);
            g_free (content);

            /* Wrap and quote the line */
            if (!remove_quoting && text_length >= word_wrap_length) {
                remove_quoting_from_element (block);

                block = e_html_editor_selection_wrap_paragraph_length (
                    selection, block, length);
                webkit_dom_node_normalize (WEBKIT_DOM_NODE (block));
                quote_plain_text_element_after_wrapping (
                    document, WEBKIT_DOM_ELEMENT (block), citation_level);
                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_selection_restore (selection);
                e_html_editor_view_force_spell_check_for_current_paragraph (view);
                return;
            }
        }
        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");
    }
    g_object_unref (list);

    /* 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_object_unref (list);
            g_free (attribute_ns);
            g_free (selector);
        }
        g_free (name);
    }
    g_object_unref (attributes);

    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_object_unref (list);
    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);
    }
    g_object_unref (list);
}

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);
    }
    g_object_unref (list);
}

static void
remove_input_event_listener_from_body (EHTMLEditorView *view)
{
    if (!view->priv->body_input_event_removed) {
        WebKitDOMDocument *document;

        document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

        webkit_dom_event_target_remove_event_listener (
            WEBKIT_DOM_EVENT_TARGET (
                webkit_dom_document_get_body (document)),
            "input",
            G_CALLBACK (body_input_event_cb),
            FALSE);

        view->priv->body_input_event_removed = TRUE;
    }
}

static void
register_input_event_listener_on_body (EHTMLEditorView *view)
{
    if (view->priv->body_input_event_removed) {
        WebKitDOMDocument *document;

        document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));

        webkit_dom_event_target_add_event_listener (
            WEBKIT_DOM_EVENT_TARGET (
                webkit_dom_document_get_body (document)),
            "input",
            G_CALLBACK (body_input_event_cb),
            FALSE,
            view);

        view->priv->body_input_event_removed = FALSE;
    }
}

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

    if (webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (body), "data-evo-draft")) {
        /* Restore the selection how it was when the draft was saved */
        e_html_editor_selection_move_caret_into_element (
            document, WEBKIT_DOM_ELEMENT (body));
        e_html_editor_selection_restore (
            e_html_editor_view_get_selection (view));
    }

    /* Register on input event that is called when the content (body) is modified */
    register_input_event_listener_on_body (view);

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

    if (view->priv->inline_spelling)
        e_html_editor_view_force_spell_check (view);
    else
        e_html_editor_view_turn_spell_check_off (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 = get_parent_block_node_from_child (
        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_for_paste_as_text (GtkClipboard *clipboard,
                                           const gchar *text,
                                           EHTMLEditorView *view)
{
    EHTMLEditorSelection *selection;

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

    selection = e_html_editor_view_get_selection (view);

    e_html_editor_selection_insert_as_text (selection, text);
}

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_by_data (priv->aliasing_settings, object);
        g_object_unref (priv->aliasing_settings);
        priv->aliasing_settings = NULL;
    }

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

    if (priv->mail_settings != NULL) {
        g_signal_handlers_disconnect_by_data (priv->mail_settings, object);
        g_object_unref (priv->mail_settings);
        priv->mail_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;
    }
    g_object_unref (list);

    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_ELEMENT (get_parent_block_node_from_child (
        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 (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_start_marker));

            if (WEBKIT_DOM_IS_ELEMENT (prev_sibling))
                success = element_has_class (
                    WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted");
            /* We really have to be in the beginning of paragraph and
             * not on the beginning of some line in the paragraph */
            if (success && webkit_dom_node_get_previous_sibling (prev_sibling))
                success = FALSE;
        }

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

    if (success && citation_level == 1) {
        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 (paragraph) {
            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));

        if (paragraph)
            remove_node_if_empty (
                webkit_dom_node_get_next_sibling (
                    WEBKIT_DOM_NODE (paragraph)));
    }

    if (success && citation_level > 1) {
        gint length, word_wrap_length;
        EHTMLEditorSelection *selection;
        WebKitDOMNode *parent;

        selection = e_html_editor_view_get_selection (view);
        word_wrap_length = e_html_editor_selection_get_word_wrap_length (selection);
        length =  word_wrap_length - 2 * (citation_level - 1);

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

        }

        remove_quoting_from_element (block);
        remove_wrapping_from_element (block);

        block = e_html_editor_selection_wrap_paragraph_length (
            selection, block, length);
        webkit_dom_node_normalize (WEBKIT_DOM_NODE (block));
        quote_plain_text_element_after_wrapping (
            document, block, citation_level - 1);

        parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (block));

        if (!webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (block))) {
            /* Currect block is in the beginning of citation, just move it
             * before the citation where already is */
            webkit_dom_node_insert_before (
                webkit_dom_node_get_parent_node (parent),
                WEBKIT_DOM_NODE (block),
                parent,
                NULL);
        } else if (!webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (block))) {
            /* Currect block is at the end of the citation, just move it
             * after the citation where already is */
            webkit_dom_node_insert_before (
                webkit_dom_node_get_parent_node (parent),
                WEBKIT_DOM_NODE (block),
                webkit_dom_node_get_next_sibling (parent),
                NULL);
        } else {
            /* Current block is somewhere in the middle of the citation
             * so we need to split the citation and insert the block into
             * the citation that is one level lower */
            WebKitDOMNode *clone, *child;

            clone = webkit_dom_node_clone_node (parent, FALSE);

            /* Move nodes that are after the currect block into the
             * new blockquote */
            child = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (block));
            while (child) {
                WebKitDOMNode *next = webkit_dom_node_get_next_sibling (child);
                webkit_dom_node_append_child (clone, child, NULL);
                child = next;
            }

            clone = webkit_dom_node_insert_before (
                webkit_dom_node_get_parent_node (parent),
                clone,
                webkit_dom_node_get_next_sibling (parent),
                NULL);

            webkit_dom_node_insert_before (
                webkit_dom_node_get_parent_node (parent),
                WEBKIT_DOM_NODE (block),
                clone,
                NULL);
        }
    }

    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;
        EHTMLEditorSelectionBlockFormat format;

        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)) {
            remove_input_event_listener_from_body (view);
            return (insert_new_line_into_citation (view, "")) ? TRUE : FALSE;
        }

        /* When the return is pressed in a H1-6 element, WebKit doesn't
         * continue with the same element, but creates normal paragraph,
         * so we have to unset the bold font. */
        format = e_html_editor_selection_get_block_format (selection);
        if (format >= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1 &&
            format <= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6)
            e_html_editor_selection_set_bold (selection, 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 (e_html_editor_selection_is_collapsed (selection)) {
            e_html_editor_selection_save (selection);
            if (change_quoted_block_to_normal (view)) {
                e_html_editor_selection_restore (selection);
                e_html_editor_view_force_spell_check_for_current_paragraph (view);
                return TRUE;
            }
            e_html_editor_selection_restore (selection);
        } else
            remove_input_event_listener_from_body (view);

        /* BackSpace in indented block decrease indent level by one */
        if (e_html_editor_selection_is_indented (selection)) {
            WebKitDOMElement *caret;
            WebKitDOMNode *prev_sibling;

            caret = e_html_editor_selection_save_caret_position (selection);

            /* Empty text node before caret */
            prev_sibling = webkit_dom_node_get_previous_sibling (
                WEBKIT_DOM_NODE (caret));
            if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling)) {
                gchar *content;

                content = webkit_dom_node_get_text_content (prev_sibling);
                if (g_strcmp0 (content, "") == 0)
                    prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
                g_free (content);
            }

            if (!prev_sibling) {
                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, "");
    }
    g_object_unref (list);
}

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

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

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

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

        return TRUE;
    }

    return FALSE;
}

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

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

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (widget));

    register_input_event_listener_on_body (view);

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

    /* This will fix the structure after the situations where some text
     * inside the quoted content is selected and afterwards deleted with
     * BackSpace or Delete. */
    if ((event->keyval == GDK_KEY_BackSpace) ||
        (event->keyval == GDK_KEY_Delete)) {
        gint level;
        WebKitDOMNode *node, *parent;
        WebKitDOMElement *selection_start_marker, *selection_end_marker;
        WebKitDOMElement *element;

        e_html_editor_selection_save (selection);
        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");

        level = get_citation_level (
            WEBKIT_DOM_NODE (selection_start_marker), FALSE);
        if (level == 0)
            goto restore;

        node = webkit_dom_node_get_previous_sibling (
            WEBKIT_DOM_NODE (selection_start_marker));

        if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (node))
            node = webkit_dom_node_get_previous_sibling (node);

        if (node)
            goto restore;

        parent = get_parent_block_node_from_child (
            WEBKIT_DOM_NODE (selection_start_marker));

        node = webkit_dom_node_get_previous_sibling (parent);
        if (!node) {
            /* Situation where the start of the selection was in the
             * multiple quoted content and that start on the beginning
             * of the citation.
             *
             * >
             * >> |
             * >> xx|x
             * */
            node = webkit_dom_node_get_parent_node (parent);
            if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))
                goto restore;
            node = webkit_dom_node_get_previous_sibling (node);
            if (!node)
                goto restore;
            if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (webkit_dom_node_get_parent_node (node)))
                goto restore;
        }

        element = webkit_dom_element_query_selector (
            WEBKIT_DOM_ELEMENT (node), "span.-x-evo-quote-character > br", NULL);
        if (element) {
            WebKitDOMNode *tmp;

            if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node)) {
                /* We have to select the right block when the selection
                 * started on the end of the citation that is
                 * inside another citation.
                 *
                 * >>|
                 * > xx|x
                 */
                /* <span class="-x-evo-quote-character"> */
                node = webkit_dom_node_get_parent_node (
                    WEBKIT_DOM_NODE (element));
                /* <span class="-x-evo-quoted"> */
                node = webkit_dom_node_get_parent_node (node);
                /* right block */
                node = webkit_dom_node_get_parent_node (node);
            }

            webkit_dom_node_append_child (
                node, WEBKIT_DOM_NODE (selection_start_marker), NULL);

            while ((tmp = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker))))
                webkit_dom_node_append_child (node, tmp, NULL);

            webkit_dom_node_insert_before (
                node,
                WEBKIT_DOM_NODE (selection_end_marker),
                webkit_dom_node_get_next_sibling (
                    WEBKIT_DOM_NODE (selection_start_marker)),
                NULL);

            webkit_dom_node_append_child (
                node, WEBKIT_DOM_NODE (element), NULL);
            remove_node (parent);
        }
 restore:
        e_html_editor_selection_restore (selection);
    }

    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_as_text (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_for_paste_as_text,
        view);
}

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

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

    /* Don't insert temporary BR before BR that is used for wrapping */
    skip = WEBKIT_DOM_IS_HTMLBR_ELEMENT (node);
    skip = skip && element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br");

    if (is_html_node && !skip) {
        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_br_node (WebKitDOMNode *node,
               gint quote_level)
{
    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);
}

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_IS_HTML_ANCHOR_ELEMENT (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)) {
            if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) {
                move_next = TRUE;
                goto next_node;
            }
            goto not_br;
        } else if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-first-br") ||
                   element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-last-br")) {
            quote_br_node (node, quote_level);
            node = next_sibling;
            skip_node = TRUE;
            goto next_node;
        }

        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)) {
            quote_br_node (prev_sibling, quote_level);
            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));

    webkit_dom_node_normalize (element_clone);
    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);

    g_object_unref (list);
    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);
        }
    }
    g_object_unref (list);

    webkit_dom_node_normalize (body_clone);
    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);
    }
    g_object_unref (attributes);

    /* 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);
        }
    }
    g_object_unref (paragraphs);
}

static gboolean
create_anchor_for_link (const GMatchInfo *info,
                        GString *res,
                        gpointer data)
{
    gint offset = 0;
    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)
        offset += 4;

    if (g_str_has_prefix (match, "&nbsp;"))
        offset += 6;

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

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

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

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

    g_free (match);

    return FALSE;
}

static gboolean
replace_to_nbsp (const GMatchInfo *info,
                 GString *res,
                 gboolean use_nbsp)
{
    gchar *match, *previous_tab;
    const gchar *string;
    gint ii, length = 0, start = 0;

    match = g_match_info_fetch (info, 0);
    g_match_info_fetch_pos (info, 0, &start, NULL);
    string = g_match_info_get_string (info);

    if (start > 0) {
        previous_tab = g_strrstr_len (string, start, "\x9");
        if (previous_tab && *previous_tab) {
            char *act_tab = NULL;
            act_tab = strstr (previous_tab + 1, "\x9");

            if (act_tab && *act_tab) {
                length = act_tab - previous_tab - 1;
                length = TAB_LENGTH - length;
            }
        }
    }

    if (length == 0) {
        if (strstr (match, "\x9"))
            length = TAB_LENGTH - (start %  TAB_LENGTH);
        else
            length = strlen (match);
    }

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

    g_free (match);

    return FALSE;
}

static gboolean
surround_links_with_anchor (const gchar *text)
{
    return (strstr (text, "http") || strstr (text, "ftp") ||
        strstr (text, "www") || strstr (text, "@"));
}

/* 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;
    gboolean first_element = TRUE;

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

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

    /* Replace single spaces on the beginning of line, 2+ spaces and
     * tabulators with non breaking spaces */
    regex_nbsp = g_regex_new ("^\\s{1}|\\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),
                "<br>",
                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,
                (GRegexEvalCallback) replace_to_nbsp,
                NULL,
                NULL);
            g_free (truncated);

            if (surround_links_with_anchor (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 ? rest_to_insert : "<br>",
                NULL);

            if (g_strcmp0 (rest_to_insert, UNICODE_ZERO_WIDTH_SPACE) == 0)
                webkit_dom_html_element_set_inner_html (
                    WEBKIT_DOM_HTML_ELEMENT (paragraph), "<br>", NULL);

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

            g_free (rest_to_insert);
        } else if (rest && !*rest && first_element) {
            webkit_dom_html_element_set_inner_html (
                WEBKIT_DOM_HTML_ELEMENT (paragraph),
                "<br class=\"-x-evo-first-br\">",
                NULL);

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

        if (citation_end)
            ignore_next_br = TRUE;

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

    if (g_utf8_strlen (prev_br, -1) > 0) {
        gchar *truncated = g_strdup (
            g_str_has_prefix (prev_br, "<br>") ? prev_br + 4 : prev_br);
        gchar *rest_to_insert;
        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);

        g_strchomp (truncated);

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

        if (surround_links_with_anchor (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 ?
                rest_to_insert : "<br class=\"-x-evo-last-br\">",
            NULL);

        if (g_strcmp0 (rest_to_insert, UNICODE_ZERO_WIDTH_SPACE) == 0)
            webkit_dom_html_element_set_inner_html (
                WEBKIT_DOM_HTML_ELEMENT (paragraph),
                "<br class=\"-x-evo-last-br\">",
                NULL);

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

        g_free (rest_to_insert);
    }

    /* 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);
    }
    g_object_unref (list);

    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));
        if (!first_child) {
            paragraph = e_html_editor_selection_get_paragraph_element (
                selection, document, -1, 0);
            first_child = webkit_dom_node_append_child (
                WEBKIT_DOM_NODE (body),
                WEBKIT_DOM_NODE (paragraph),
                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);
    }

    paragraph = webkit_dom_document_query_selector (document, "br.-x-evo-last-br", NULL);
    if (paragraph)
        webkit_dom_element_remove_attribute (paragraph, "class");
    paragraph = webkit_dom_document_query_selector (document, "br.-x-evo-first-br", NULL);
    if (paragraph)
        webkit_dom_element_remove_attribute (paragraph, "class");

    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
fix_structure_after_pasting_multiline_content (WebKitDOMNode *node)
{
    WebKitDOMNode *first_child, *parent;

    /* When pasting content that does not contain just the
     * one line text WebKit inserts all the content after the
     * first line into one element. So we have to take it out
     * of this element and insert it after that element. */
    parent = webkit_dom_node_get_parent_node (node);
    first_child = webkit_dom_node_get_first_child (parent);
    while (first_child) {
        WebKitDOMNode *next_child =
            webkit_dom_node_get_next_sibling  (first_child);
        if (webkit_dom_node_has_child_nodes (first_child))
            webkit_dom_node_insert_before (
                webkit_dom_node_get_parent_node (parent),
                first_child,
                parent,
                NULL);
        first_child = next_child;
    }
    remove_node (parent);
}

static void
html_editor_view_insert_converted_html_into_selection (EHTMLEditorView *view,
                                                       WebKitDOMDocument *document_convertor)
{
    EHTMLEditorSelection *selection = e_html_editor_view_get_selection (view);
    gboolean has_selection;
    gchar *inner_text, *inner_html;
    gint citation_level;
    WebKitDOMDocument *document;
    WebKitDOMElement *element;
    WebKitDOMHTMLElement *convertor_body;
    WebKitDOMNode *node;
    WebKitDOMRange *range;

    remove_input_event_listener_from_body (view);

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

    has_selection = !e_html_editor_selection_is_collapsed (selection);

    range = html_editor_view_get_dom_range (view);
    node = webkit_dom_range_get_end_container (range, NULL);
    citation_level = get_citation_level (node, FALSE);
    /* Pasting into the citation */
    if (citation_level > 0) {
        gint length;
        gint word_wrap_length = e_html_editor_selection_get_word_wrap_length (selection);
        WebKitDOMElement *selection_start_marker, *selection_end_marker;
        WebKitDOMElement *br;
        WebKitDOMNode *first_paragraph, *last_paragraph;
        WebKitDOMNode *child, *parent;

        e_html_editor_selection_save (selection);
        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");

        first_paragraph = webkit_dom_node_get_first_child (
            WEBKIT_DOM_NODE (element));
        last_paragraph = webkit_dom_node_get_last_child (
            WEBKIT_DOM_NODE (element));

        length = word_wrap_length - 2 * citation_level;

        /* Pasting text that was parsed just into one paragraph */
        if (webkit_dom_node_is_same_node (first_paragraph, last_paragraph)) {
            WebKitDOMNode *child, *parent;

            parent = get_parent_block_node_from_child (
                WEBKIT_DOM_NODE (selection_start_marker));

            remove_quoting_from_element (WEBKIT_DOM_ELEMENT (parent));
            remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (parent));

            while ((child = webkit_dom_node_get_first_child (first_paragraph)))
                webkit_dom_node_insert_before (
                    parent,
                    child,
                    WEBKIT_DOM_NODE (selection_start_marker),
                    NULL);

            parent = WEBKIT_DOM_NODE (
                e_html_editor_selection_wrap_paragraph_length (
                    selection, WEBKIT_DOM_ELEMENT (parent), length));
            webkit_dom_node_normalize (parent);
            quote_plain_text_element_after_wrapping (
                document, WEBKIT_DOM_ELEMENT (parent), citation_level);

            goto delete;
        }

        /* Pasting content parsed into the multiple paragraphs */
        parent = get_parent_block_node_from_child (
            WEBKIT_DOM_NODE (selection_start_marker));

        remove_quoting_from_element (WEBKIT_DOM_ELEMENT (parent));
        remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (parent));

        /* Move the elements from the first paragraph before the selection start element */
        while ((child = webkit_dom_node_get_first_child (first_paragraph)))
            webkit_dom_node_insert_before (
                parent,
                child,
                WEBKIT_DOM_NODE (selection_start_marker),
                NULL);

        remove_node (first_paragraph);

        /* If the BR element is on the last position, remove it as we don't need it */
        child = webkit_dom_node_get_last_child (parent);
        if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child))
            remove_node (child);

        parent = get_parent_block_node_from_child (
            WEBKIT_DOM_NODE (selection_end_marker)),

        child = webkit_dom_node_get_next_sibling (
            WEBKIT_DOM_NODE (selection_end_marker));
        /* Move the elements that are in the same paragraph as the selection end
         * on the end of pasted text, but avoid BR on the end of paragraph */
        while (child) {
            WebKitDOMNode *next_child =
                webkit_dom_node_get_next_sibling  (child);
            if (!(!next_child && WEBKIT_DOM_IS_HTMLBR_ELEMENT (child)))
                webkit_dom_node_append_child (last_paragraph, child, NULL);
            child = next_child;
        }

        /* Caret will be restored on the end of pasted text */
        webkit_dom_node_append_child (
            last_paragraph,
            e_html_editor_selection_get_caret_position_node (document),
            NULL);

        /* Insert the paragraph with the end of the pasted text after
         * the paragraph that contains the selection end */
        webkit_dom_node_insert_before (
            webkit_dom_node_get_parent_node (parent),
            last_paragraph,
            webkit_dom_node_get_next_sibling (parent),
            NULL);

        /* Wrap, quote and move all paragraphs from pasted text into the body */
        while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)))) {
            child = WEBKIT_DOM_NODE (e_html_editor_selection_wrap_paragraph_length (
                selection, WEBKIT_DOM_ELEMENT (child), length));
            quote_plain_text_element_after_wrapping (
                document, WEBKIT_DOM_ELEMENT (child), citation_level);
            webkit_dom_node_insert_before (
                webkit_dom_node_get_parent_node (last_paragraph),
                child,
                last_paragraph,
                NULL);
        }

        webkit_dom_node_normalize (last_paragraph);

        last_paragraph = WEBKIT_DOM_NODE (
            e_html_editor_selection_wrap_paragraph_length (
                selection, WEBKIT_DOM_ELEMENT (last_paragraph), length));
        quote_plain_text_element_after_wrapping (
            document, WEBKIT_DOM_ELEMENT (last_paragraph), citation_level);

        remove_quoting_from_element (WEBKIT_DOM_ELEMENT (parent));
        remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (parent));

        parent = get_parent_block_node_from_child (
            WEBKIT_DOM_NODE (selection_start_marker));
        parent = WEBKIT_DOM_NODE (e_html_editor_selection_wrap_paragraph_length (
            selection, WEBKIT_DOM_ELEMENT (parent), length));
        quote_plain_text_element_after_wrapping (
            document, WEBKIT_DOM_ELEMENT (parent), citation_level);

        /* If the pasted text begun or ended with a new line we have to
         * quote these paragraphs as well */
        br = webkit_dom_element_query_selector (
            WEBKIT_DOM_ELEMENT (last_paragraph), "br.-x-evo-last-br", NULL);
        if (br) {
            WebKitDOMNode *parent;

            parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (br));
            quote_plain_text_recursive (document, parent, parent, citation_level);
            webkit_dom_element_remove_attribute (br, "class");
        }

        br = webkit_dom_document_query_selector (
            document, "* > br.-x-evo-first-br", NULL);
        if (br) {
            WebKitDOMNode *parent;

            parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (br));
            quote_plain_text_recursive (document, parent, parent, citation_level);
            webkit_dom_element_remove_attribute (br, "class");
        }
 delete:
        e_html_editor_selection_restore (selection);
        /* Remove the text that was meant to be replaced by the pasted text */
        if (has_selection)
            e_html_editor_view_exec_command (
                view, E_HTML_EDITOR_VIEW_COMMAND_DELETE, NULL);

        e_html_editor_selection_restore_caret_position (selection);
        goto out;
    }

    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);
    g_free (inner_html);

    e_html_editor_selection_save (selection);

    element = webkit_dom_document_query_selector (
        document, "* > br.-x-evo-first-br", NULL);
    if (element) {
        WebKitDOMNode *next_sibling;
        WebKitDOMNode *parent;

        parent = webkit_dom_node_get_parent_node (
            WEBKIT_DOM_NODE (element));

        next_sibling = webkit_dom_node_get_next_sibling (parent);
        if (next_sibling)
            remove_node (WEBKIT_DOM_NODE (parent));
        else
            webkit_dom_element_remove_attribute (element, "class");
    }

    element = webkit_dom_document_query_selector (
        document, "* > br.-x-evo-last-br", NULL);
    if (element) {
        WebKitDOMNode *parent;
        WebKitDOMNode *child;
        WebKitDOMElement *selection_marker;

        parent = webkit_dom_node_get_parent_node (
            WEBKIT_DOM_NODE (element));

        node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent));
        if (node) {
            node = webkit_dom_node_get_first_child (node);
            if (node) {
                inner_html = webkit_dom_node_get_text_content (node);
                if (g_str_has_prefix (inner_html, UNICODE_NBSP))
                    webkit_dom_character_data_replace_data (
                        WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL);
                g_free (inner_html);
            }
        }

        selection_marker = webkit_dom_document_get_element_by_id (
            document, "-x-evo-selection-end-marker");

        if (has_selection) {
            /* Everything after the selection end marker have to be in separate
             * paragraph */
            child = webkit_dom_node_get_next_sibling (
                WEBKIT_DOM_NODE (selection_marker));
            /* Move the elements that are in the same paragraph as the selection end
             * on the end of pasted text, but avoid BR on the end of paragraph */
            while (child) {
                WebKitDOMNode *next_child =
                    webkit_dom_node_get_next_sibling  (child);
                if (!(!next_child && WEBKIT_DOM_IS_HTMLBR_ELEMENT (child)))
                    webkit_dom_node_append_child (parent, child, NULL);
                child = next_child;
            }

            remove_node (WEBKIT_DOM_NODE (element));

            webkit_dom_node_insert_before (
                webkit_dom_node_get_parent_node (
                    webkit_dom_node_get_parent_node (
                        WEBKIT_DOM_NODE (selection_marker))),
                parent,
                webkit_dom_node_get_next_sibling (
                    webkit_dom_node_get_parent_node (
                        WEBKIT_DOM_NODE (selection_marker))),
                NULL);
            node = parent;
        } else {
            node = webkit_dom_node_get_next_sibling (parent);
            if (!node)
                fix_structure_after_pasting_multiline_content (parent);
        }

        if (node) {
            /* Restore caret on the end of pasted text */
            webkit_dom_node_insert_before (
                node,
                WEBKIT_DOM_NODE (selection_marker),
                webkit_dom_node_get_first_child (node),
                NULL);

            selection_marker = webkit_dom_document_get_element_by_id (
                document, "-x-evo-selection-start-marker");
            webkit_dom_node_insert_before (
                node,
                WEBKIT_DOM_NODE (selection_marker),
                webkit_dom_node_get_first_child (node),
                NULL);
        }

        if (element)
            webkit_dom_element_remove_attribute (element, "class");

        if (webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent)) && !has_selection)
            remove_node (parent);
    } else {
        /* When pasting the content that was copied from the composer, WebKit
         * restores the selection wrongly, thus is saved wrongly and we have
         * to fix it */
        WebKitDOMElement *selection_start_marker, *selection_end_marker;
        WebKitDOMNode *paragraph, *parent;

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

        paragraph = get_parent_block_node_from_child (
            WEBKIT_DOM_NODE (selection_start_marker));
        parent = webkit_dom_node_get_parent_node (paragraph);
        if (element_has_class (WEBKIT_DOM_ELEMENT (paragraph), "-x-evo-paragraph") &&
            element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph"))
            fix_structure_after_pasting_multiline_content (paragraph);

        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_get_next_sibling (
                WEBKIT_DOM_NODE (selection_start_marker)),
            NULL);
    }

    e_html_editor_selection_restore (selection);
 out:
    e_html_editor_view_force_spell_check (view);
    e_html_editor_selection_scroll_to_caret (selection);

    register_input_event_listener_on_body (view);

    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;

    g_settings = g_settings_new ("org.gnome.evolution.mail");
    view->priv->mail_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->body_input_event_removed = TRUE;
    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");
}

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

        quoted_node = webkit_dom_node_list_item (list, jj);
        text_content = webkit_dom_node_get_text_content (quoted_node);
        webkit_dom_html_element_set_outer_html (
            WEBKIT_DOM_HTML_ELEMENT (quoted_node), text_content, NULL);

        g_free (text_content);
    }
    g_object_unref (list);

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

        quoted_node = webkit_dom_node_list_item (list, jj);
        text_content = webkit_dom_node_get_text_content (quoted_node);
        webkit_dom_html_element_set_outer_html (
            WEBKIT_DOM_HTML_ELEMENT (quoted_node), text_content, NULL);

        g_free (text_content);
    }
    g_object_unref (list);

    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")) {
                        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-evo-draft");
    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 gboolean
replace_to_whitespaces (const GMatchInfo *info,
                        GString *res,
                        gpointer data)
{
    gint ii, length = 0;
    gint chars_count = GPOINTER_TO_INT (data);

    length = TAB_LENGTH - (chars_count %  TAB_LENGTH);

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

    return FALSE;
}

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, ">");
            g_object_unref (attributes);
        }
        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;
            gint char_count = 0;

            content = webkit_dom_node_get_text_content (child);
            if (!changing_mode && to_plain_text) {
                /* Replace tabs with 8 whitespaces, otherwise they got
                 * replaced by single whitespace */
                if (strstr (content, "\x9")) {
                    if (buffer->str && *buffer->str) {
                        gchar *start_of_line = g_strrstr_len (
                            buffer->str, -1, "\n") + 1;

                        if (start_of_line && *start_of_line)
                                char_count = strlen (start_of_line);
                    } else
                        char_count = 0;

                    regex = g_regex_new ("\x9", 0, 0, NULL);
                    tmp = g_regex_replace_eval (
                        regex,
                        content,
                        -1,
                        0,
                        0,
                        (GRegexEvalCallback) replace_to_whitespaces,
                        GINT_TO_POINTER (char_count),
                        NULL);

                    g_string_append (buffer, tmp);
                    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 *class;
                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);
                }

                class = webkit_dom_element_get_class_name (WEBKIT_DOM_ELEMENT (node));
                if ((css_align = strstr (class, "-x-evo-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 + 13, "left")) {
                        if (g_str_has_prefix (css_align + 13, "center"))
                            length = (word_wrap_length - g_utf8_strlen (content, -1)) / 2;
                        else
                            length = word_wrap_length - g_utf8_strlen (content, -1);

                        if (length < 0)
                            length = 0;

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

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

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

                content = webkit_dom_node_get_text_content (child);
                /* Replace tabs with 8 whitespaces, otherwise they got
                 * replaced by single whitespace */
                if (strstr (content, "\x9")) {
                    if (buffer->str && *buffer->str) {
                        gchar *start_of_line = g_strrstr_len (
                            buffer->str, -1, "\n") + 1;

                        if (start_of_line && *start_of_line)
                            char_count = strlen (start_of_line);
                    } else
                        char_count = 0;

                    regex = g_regex_new ("\x9", 0, 0, NULL);
                    tmp = g_regex_replace_eval (
                        regex,
                        content,
                        -1,
                        0,
                        0,
                        (GRegexEvalCallback) replace_to_whitespaces,
                        GINT_TO_POINTER (char_count),
                        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;
            goto next;
        }

        /* 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;
                goto next;
            } else {
                if (!changing_mode && to_plain_text) {
                    if (get_citation_level (child, FALSE) == 0) {
                        gchar *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 (to_html)
                    remove_base_attributes (WEBKIT_DOM_ELEMENT (child));
            }
        }

        if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (child) &&
            element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-indented"))
            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;
            goto next;
        }

        /* 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;
                goto next;
            }
            if (to_html) {
                remove_base_attributes (WEBKIT_DOM_ELEMENT (child));
                remove_evolution_attributes (WEBKIT_DOM_ELEMENT (child));
            }
            if (!changing_mode && to_plain_text)
                if (!webkit_dom_node_has_child_nodes (child))
                    g_string_append (buffer, "\n");
        }

        /* Signature */
        if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (child) &&
            element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-signature-wrapper")) {
            WebKitDOMNode *first_child;

            first_child = webkit_dom_node_get_first_child (child);

            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;
            }
            if (to_plain_text && changing_mode) {
                content = webkit_dom_html_element_get_outer_html (
                    WEBKIT_DOM_HTML_ELEMENT (child));
                g_string_append (buffer, content);
                g_free (content);
                skip_node = TRUE;
            }
            skip_node = TRUE;
            goto next;
        }

        /* 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;
                goto next;
            }
            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;
                goto next;
            }
        }

        /* 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) {
                if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-wrap-br")) {
                    g_string_append (buffer, changing_mode ? "<br>" : "\n");
                    goto next;
                }

                /* Insert new line when we hit the BR element that is
                 * not the last element in the block */
                if (!webkit_dom_node_is_same_node (
                    child, webkit_dom_node_get_last_child (node))) {
                    g_string_append (buffer, changing_mode ? "<br>" : "\n");
                } else {
                    /* In citations in the empty lines the BR element
                     * is on the end and we have to put NL there */
                    WebKitDOMNode *parent;

                    parent = webkit_dom_node_get_parent_node (child);
                    if (webkit_dom_node_get_next_sibling (parent)) {
                        parent = webkit_dom_node_get_parent_node (parent);

                        if (is_citation_node (parent))
                            g_string_append (buffer, changing_mode ? "<br>" : "\n");
                    }
                }
            }
        }

        if (WEBKIT_DOM_IS_HTML_ANCHOR_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 (!changing_mode && to_plain_text) {
                content = webkit_dom_html_element_get_inner_text (
                    WEBKIT_DOM_HTML_ELEMENT (child));
                g_string_append (buffer, content);
                g_free (content);
                skip_node = TRUE;
            }
        }
 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);
        WebKitDOMNode *last_child = webkit_dom_node_get_last_child (node);

        if (last_child && WEBKIT_DOM_IS_HTMLBR_ELEMENT (last_child))
            if (webkit_dom_node_get_previous_sibling (last_child))
                add_br = FALSE;

        /* 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 && !skip_nl)
            g_string_append (buffer, changing_mode ? "<br>" : "\n");

        g_free (content);
    }

    g_object_unref (nodes);
}

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

    g_object_unref (list);
}

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

    g_object_unref (images);
}

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

    g_object_unref (smileys);
}

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);
            }
        }
    }
    g_object_unref (paragraphs);
}

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 *document_element;
    gchar *content;

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

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

    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 (first_child) {
            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)
{
    EHTMLEditorSelection *selection;
    gboolean wrap = FALSE, quote = FALSE, clean = FALSE;
    gboolean converted, is_from_new_message;
    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");
    is_from_new_message = webkit_dom_element_has_attribute (
        WEBKIT_DOM_ELEMENT (body), "data-new-message");
    source = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (body), TRUE);

    selection = e_html_editor_view_get_selection (view);

    /* If composer is in HTML mode we have to move the content to plain version */
    if (view->priv->html_mode) {
        if (converted || is_from_new_message) {
            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_remove_attribute (
                    WEBKIT_DOM_ELEMENT (paragraph), "id");
            }
            g_object_unref (paragraphs);

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

            g_object_unref (source);

            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 (
                        selection, WEBKIT_DOM_ELEMENT (item));
                }
                item = next_item;
            }
        } else {
            e_html_editor_selection_wrap_paragraph (
                selection, WEBKIT_DOM_ELEMENT (paragraph));
        }
    }
    g_object_unref (paragraphs);

    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);
    }
    g_object_unref (paragraphs);

    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);
    else
        g_object_unref (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, *document_clone;
    gchar *html_content;

    document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
    document_clone = webkit_dom_node_clone_node (
        WEBKIT_DOM_NODE (webkit_dom_document_get_document_element (document)), TRUE);
    body = WEBKIT_DOM_NODE (webkit_dom_element_query_selector (
        WEBKIT_DOM_ELEMENT (document_clone), "body", NULL));
    process_elements (view, body, TRUE, FALSE, FALSE, NULL);

    html_content = webkit_dom_html_element_get_outer_html (
        WEBKIT_DOM_HTML_ELEMENT (document_clone));

    g_object_unref (document_clone);

    return html_content;
}

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);
    }
    g_object_unref (attributes);

    /* Remove everything from HEAD element */
    while (webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (head)))
        remove_node (webkit_dom_node_get_first_child (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);
    }
    g_object_unref (attributes);
}

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;

        e_html_editor_selection_save (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_dom_html_element_set_outer_html (
                WEBKIT_DOM_HTML_ELEMENT (
                    webkit_dom_document_get_document_element (document)),
                plain,
                NULL);
            e_html_editor_selection_restore (selection);
            e_html_editor_view_force_spell_check (view);
        }

        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;

    if (inline_spelling)
        e_html_editor_view_force_spell_check (view);
    else
        e_html_editor_view_turn_spell_check_off (view);

    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://");
}

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

    webkit_web_view_load_string (
        view->priv->convertor_web_view, text, "text/plain", 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 {
        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;

    convert_and_load_plain_text (view, text);
}

/**
 * e_html_editor_view_paste_as_text:
 * @view: an #EHTMLEditorView
 *
 * Pastes current content of clipboard into the editor without formatting
 */
void
e_html_editor_view_paste_as_text (EHTMLEditorView *view)
{
    g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));

    html_editor_view_paste_as_text (view);
}

/**
 * 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)
{
    gboolean mark_citations, use_custom_font;
    GdkColor *link = NULL;
    GdkColor *visited = NULL;
    gchar *base64, *font, *aa = NULL, *citation_color;
    const gchar *styles[] = { "normal", "oblique", "italic" };
    const gchar *smoothing = NULL;
    GString *stylesheet;
    GtkStyleContext *context;
    PangoFontDescription *ms, *vw;
    WebKitWebSettings *settings;

    g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));

    use_custom_font = g_settings_get_boolean (
        view->priv->mail_settings, "use-custom-font");

    if (use_custom_font) {
        font = g_settings_get_string (
            view->priv->mail_settings, "monospace-font");
        ms = pango_font_description_from_string (font ? font : "monospace 10");
        g_free (font);
    } else {
        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) {
        if (use_custom_font) {
            font = g_settings_get_string (
                view->priv->mail_settings, "variable-width-font");
            vw = pango_font_description_from_string (font ? font : "serif 10");
            g_free (font);
        } else {
            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-align-left "
        "{\n"
        "  text-align: left; \n"
        "}\n");

    g_string_append (
        stylesheet,
        ".-x-evo-align-center "
        "{\n"
        "  text-align: center; \n"
        "}\n");

    g_string_append (
        stylesheet,
        ".-x-evo-align-right "
        "{\n"
        "  text-align: right; \n"
        "}\n");

    g_string_append (
        stylesheet,
        ".-x-evo-list-item-align-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");

    citation_color = g_settings_get_string (
        view->priv->mail_settings, "citation-color");
    mark_citations = g_settings_get_boolean (
        view->priv->mail_settings, "mark-citations");

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

    if (mark_citations && citation_color)
        g_string_append_printf (
            stylesheet,
            "  color: %s !important; \n",
            citation_color);

    g_string_append (stylesheet, "}\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);
    }
    g_object_unref (list);

    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_object_unref (list);
    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);
}