aboutsummaryrefslogblamecommitdiffstats
path: root/mail/message-list.c
blob: 3350f97e411c501194cbe391a2d25d9eeb2def6a (plain) (tree)
1
2
3
4
5
6
7
8
9
10
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
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
  



                                                                
  



                                                                    
  
                                                                   
                                                                             
  
  





                                                        
  
   
 
                    
                   
      
 

                         



                      


                   
                       
                        
 

                                         
                                    


                                       


                                    
 
                   





                     
                 



                  
                  
      
 

                                                                       


                                                                           
            
            
 



                                                       





                                                                         


                            

  


                                                 
                              
 


                                         
                                            
                          
                              
                               
 
                                      
                           
 
                                  
                                  
                              
                               
                                
                                                                                             


                                         
 





                                                                   
                                                       



                                       

  







                                  
                                  






















                                                                         

               
                              
                    
                              
                               
                     
                          
                           
                           

  


                                                                          

                                                                         





                               

                                        
                                  



                                              
 
               
                            


                        
                                                                  

                                                    




                                                    
                                                   



                                         

                                                            



                                         


                                                              

  





                                       
                                   
                                   












                                     

                                       
 



                           
                       

  

                                                             
                                                             




                                                                            
                                                             


                                                                            






                                                                            
                                                             
                                                                            
 

                         
                           


                   
                                          
 








                                       








                                      









                                   
























                                           
  
 






                                          











                                                                     
                                                                    

                                                                 

                                                               













































































                                                                               

























































































                                                                              
           


                                        
 
                    
 

                                                    
 
                                                                             
 


                      
              
                       
 
                     
 












                                                                 
                                                               
                    
                                           
                             
 
                                                  
                                                        
 
                                                   

 


                                                                   
                         
                                            
                              
 
                                                  
                                                        
 
                          

 
                    


                                                 
 

                                  
                      
                   
 















                                                           
                                                      
         
 


                                                
 


                                                                                                   
                                                                                                                    




                                                 
 
                                      
                              
                                     
                                         
 
                                 
                                  

                                                                    

                                                    
 
                                                  
                                                                     

                                          
 
                                          
                                                             
                                  
 
                                 



                                                                         
         
 
                                                     
 


                                          
           
                                           
                                                
 

                                                    

                                       

                                            
 
 
              
                                             



                                 
 
                    
                 
                               

                                
                                                                
 
                                              


                                                                     
                                                                         
                                    




                    
              
                                              



                                  
 
                    
                 
                               

                                
                                                                
 
                                              


                                                                     
                                                                         
                                    




                    
              
                                          


                                                     
 
                    
                        

                                
                                                                
 

                                                                                                          

                            
                                                            

                            
                                                               

                                                                                    

                                                                       
            

                                                               


                                                                                            

                                                                   
                    

                                                                           





                    

                                       
 



                                  
                                     

                                                                         
 

                                          
 


                                                               

 
   
                       
                               
                                         


                                             

                                                                          

                                                                  



                                                                           
             

                                                                        
    
        
                                               


                                                          
 
                    
 


                                                                     


                                                                          

                                                                          
 


                             

 

                           
                 



              
                                                                             

                

        
                                                   


                                                              
 
                                                                             
 








                                          


                                                   
 

                                 
                           
                                     




                                                          
 
                                               

                       



                                                              
                                                                
 




                                                                          

                                                                  
           








                                                                
                                   
                                                                    
                                                    

                                                                      
                                                    
                                                                    
         
 
                   
                            
                               


                                                   
 
                                                                        
                                               

                                    

                                             
                                                          
                                                             


                                                  

                                     
                                                  
                                 


         
    
                                                           
 
                    
                                
                           
 

                                                          
                                                                
 

                                                                                                          
                       
 
                                                            

                       
                                                               

                                                                             
                                               
                                                                  

                                                                    

                               


         
    
                                                           
 
                    



                                

                                                          
                                                                
 

                                                                                                          






                                                             
                                                    



                                                                                 
                                                                    




                                                   
                                                         




                               








                                                   
                                     
 

                                                          

                                                                
                                                                 











                                                                    

 
                                   
                                  



                         


                                       

                                                                       
 

                                           
                                       


           
                                         
                                         
 
                    


                                  
                                        

                                       

                                                                         
 
                                                            






                                                              

                                          

                                                                       

                                  
 
                                                      
 

                            

                                         
 
                                           
 


                                                         










                                                                

                                                            
 
           

                                             

                                                                       
                               
 




                                                         
 
 









                                                                   

 









                                                         
 
                                                                  
 



                                                  
                                             
                                
 
                                                   

                        
                                                      
 
                                                        


                            
                                            
                               
 
                                                                        


                                                     
                                                       
                                                                
                                                               



                                                               
 


                                                   

                 
                                                           
                                                                             
 



                                                       
                


                                                       
         

                                 

 
    
                                              
 
                               

                                              

                                                      

 
           
                                                             
                                               


                                                        
 



                                                 
 



                                          
 
                                          
                                                                            
                                                                          

 
               
                                
                               
                              
 
                                    
                               
 
                 
                                                 
            
                                              
                                                   
 

                                                                    
 

                     
 



                      
 
               
                                
                               
                              




                                     
                 
                                                 
            
                                              






                                                                  
 
                     

 






                                                

                          

                                
                                    
 








                                                                    
                                                             











                                                            
                                             






                                                 
                   
                                   

                                    
 
           

                                          

                               
 
                                                                           

                                            


                                                                                 
         
 
 
               
                                        
                                       
                                      





                                     
 
                 
                                                     
            
                                                  
                                                       
 

                                                                                      
 




                                                                            
 
                                                   

         
                     

 
                    

                                            


                             








                                                    
                                       








                                                      
                              

                                         

                                    


                                         

                                                                    

                                                    

                                                  
                                                                     




                                          
                                                                                     
                                                    



                                                                                       
                                                                     




                                          
                                                     




                          
               
                                     
                                 


                                                
 
                              
                         
                      
 

                                                          
                                                      
 
                      
                                
                                                            
                                                   
                                                   

                                                         
                                                    
                                                   
                    
                                                   
                         
                                                                                                            
                         

                                 
 
                                                                      

                                           
 

                                               
                                        
                                       
 
                                                                                       
                                                                  

                                                                             

                                          
                                                           
                            
                                                           
                      
                                                   

                                   
                                 
                              
 
                                                                       
                                  
                                                                      




                                                        
                               
                                                                          
                                                  
                            

                                                                        
                                                                                                                
                      
                                                         
                                                  
                           
                                                                                      
                         
                                                            
                                                  

                                                     
                                                  
                              
                                                                                      
                        


                                     
 
                                                                                                      
 
                                                   

                            


                                     
 
                                                                                                      

                                                   
         
                    
                                                       
                                                  
                         
                                                                                      
                      
                                                                            
                         
                                                                                                            
                          
                                            
 
                                                                                                              
 
                                                    
         
                          
                                                                    
 
                                                                                 


                                                             
                                                                                                
                                                                                                   
 
                              


                                                                                   
                                     


                                                        

                                                                      
                                                                                                                                          
                                                                                                                      
















                                                                                                      
                                                                                                 
                                                                                    
                                                   
                                                                                                                    
                                                         
 
                                                                                                              
                                                           
                         
 
                                                                  
                 
 


                                                                                 
                                         
         
                            
                                                              
                                  

                                      

                                         
 
                                                    
 



                                                                        
 

                                                                  
 

                                                                      
 
                                                                            
         
                                  
                             
                                                       
 
                                                 
         
                              
                         
                                           
                                                         
                                            
                                   


                                                             
                                               

                    
                                              
         
                         

                                                    
 

                                                              
                                                                                                                                  
                                                                                                              
 






                                                                                                
 







                                                                                                     

                                                          
                         


                                                          
                                                                
         
                
                                                                                     
                            
         

 
              
                         
 
                                     

                                       
                      
                              
 
                      
                                         
 




                                          
                                                                                


                            




                                                        
                                                                                            



                                    
                       





                                                                
                                                                                             

                                            


                         

                                                  
                                                                                        
                        
                                                                                  

                 



                              
                                               

                                                                                                  
                            
                                 
                            
                                                                       
                                   
 








                                                                                     
 


                                       
 
                                       
                                                                                            
 

                                                                           




                                           
 
                                                             




                                           
 


                                                                                                


                                     
 
                                                                                     



                                           
                                                     

                                                                             


                                   
 


                                                                         


                            


                         
           

                                           
 

                                                                                                        
 
 

                                 
 

                             
 
                                       




                                                                                          
 
                                                                                
 


                                                                        
                              
 


                                                                    
                              
 


                                                                 
                              
 


                                                                     
                              
 


                                                               
                              
 
                       
                                                        
                                                                      




                                           
                                                              
                              
 

                                                        




                                           
                                                              
                              
 


                                                              
 
                       
                                                         




                                           
                                                              
                              
 


                                                                        
                              
 
                                              
                                                                      
                              
 



                                                                            
                      

 
           
                                           
                                     
 
                        
 
                           
                       
 
                                                                          


                                                                            
                                                                     
                          
 
                                                    

 
           
                                           
                                     
                                      
 
                           

                       
                           


                                                                     
                                             
                                
 

                                                            
                                                                             

                                  
 
                                                    

 
    
                                                   
 

                            
                                                        

                             
                                                       

                                        

 
           
                                                    
 







                                                            
                              
                                 
 


                                                                              


                                                                      
 
                                               


                                                             


         
           



                                         
                                            
 
                                       
 
                                                   
 
                                    


                       
                                
                                                                       
                                                                                          
                
                                
                                                                       
                                                                                          


         
               

                                                   
                                                    
 
                                                   
 
                                                      

                    


           


                                                        
                                                 
 
                              
                            




                                                                
                                                                            


                       
                                                        
                                                          
 
                                                              
                                        


                                                                    

 
           

                                   
                                   




                                               
                                                 
 
                            

                        

                                                        


                               
                                    
                                                                            
                              
                                       
                                                                            



                              
                                 
                                 

 


                                                                     
                     
 





                                                                  
                                  


                       
 


                          

  

                                        
 



                                                           
                    
                                                                                        
            
                                                                                         


           


                                              
 

                              
                                                             
 

                            
                                                
                                                         
                                                     
                                            

                                
                                                                         

                               
                                                                         




                      
                                        
 
                                 
 




                                
                                                  


                                                         
                                                                        


           
                                        
 
                                    
                                   
                                         
                                               

 





                                             


           
                                    

                                               
                                    

 
           

                                        
                                        






                                                             
                                                      
 
                            

                            



                                                                 

                       
                                                        


                           
                                               
                                            
                                          
                                                      
                                                                   


                                                  
                                                                
 
                           

                                

 





                                  

                                   






                                                                                      

                                              










                                                                               
               




                                             
                                               


                                          
                                 
 

                                                            
                                                 



                                                   

                                                             
                                                                             
                                                            



                                                   
                                                
                                          
                                                    

















                                                                             
                                                                       




                                                                          
 
                                                                    




                                                           

                                                          
                       
 
                                                                               
                                                                 
                                                                             
                                                                   

                                                
         
                          
 

                                                                 

                                                                     
 
                                                

                           

 
           

                                         
                                                
 
                                                   

 
               
                                                    
                                                   
 
                                  
 





                                                                            
                                                                                


                                                            

                 
                                                                            
 













                                                               

 
           





                                               





                                                            





                                                             

                                                  


                                                            
 





                                                             





                                                             




                                                             











                                                                       

                                           

                                                                   


                                                        






                                                         






                                                                   

                                            

                                                                    

                                                        


                                            




                                                          






                                                               






                                                                



                                                                 

                                                        







                                                                       
                                                          

                                 
                                  
 






                                                         
                                             
                                                               



                                              
                                                                


                                               

                               
                                               

                                                 
                                        


                                                           


                                                                 
 


                                          
 
                                               
 
                                        



                                                        
                                        



                                                        
                                                    
                                                                     



                                       

                                                          
 
                                                             
 
                                      
                                                                               
 
                                      

                                             
 
                                                        
 
                                                                       
 


                                                                     
                                                     
                                                                      

 
           








                                                                         





                                                                     
                           

                                                                       
                                                                 
                                                                          
                                                     







                                                            
























































                                                              




































































































































































































































































































































                                                                                   
           
                                                 
 
                                   
               
 
                                                         
                                                                                       
 






                                                                      
                                                             
 
                                         
 





                                                 










                                                 











                                                                   





                                                 








                                            




                                                 











                                                            











                                                         








                                                             
 
                                                  









                                                                     
                                                    







                                                                       

 






                                                                           
           









                                                                  




































































                                                                                                       
                                                  
 
                        
                             
                          
 
          
                    
           
                                                             

                                                                                         
                                                       

                                            
                                                        
                            
 

                                                                            
 
                                      
                                                                             
                                                          

         



                                                                 
 


                                                                    
 


                                                     
 


                                                                     
 


                                                            
                                                   
 

                                                   
                                                                  
 


                                                            
                                                   
 


                                                                       
 







                                                                    

 






                                     
           
                                        
 
                                
 
                                                                 
 

                                         
                                          

                                                             
 
                            

 

                                                    


                                                                    
                                           

 
           
                       
                        
                                      


                               
                          
                                                                          
                          


           
                                      
                           
 
                               
                            
 

                                  
                    
 
                                   
                                    

      

                                                 
                                                                                    
                                                        
                           




                                                                               
                                 
 



                                                     
 
                                                          
                                                 

                                                                           
         
 



                                                                     
                  
                                                  
             
                                  

                                                           
                                                                                    
      

 







                                                        












































                                                         

                             







                                                                      

 


                                                                     
                                              
                                           
 
                            




                                 

                              
                                 
 

                                                   
                                                        


                                                                      
 
                               




                                                                       

                                                                            
 
                                


                                                
                                                                         

                                                     
                                                      
                             
                                                                   
                                                                   

                                                                 







                                                                   



                                                                




                                    
 


                                                                       
              
                                                
 
                    


                       
                                          
                               
 

                                                                     

                            
 

                                                            
                            
 
                                     

                                                                             
                                                  

                                           
                             

                             
                                                     

                                                                    
                                                                        
                       

         
                                                                                  
                                              



                                                     

                                                                    
                                                                        
                       

         


                    
              

                                                 
                                     

                                
                            
                    



                         


                                                        

                                                             
 

                                                 




                                                       
                                                     




                                                                              


                                                                    

                 





                                                                           


                 

                                






                                                 
                            

                         


                                                        

                                            


                                                           

         


                                                             


                                                             


                                                      

 

                                       
 
                                                                           
                                                               

                                                                          
 
                                                                           

                                                               

                                                                          
 
           
                                      
                                      
                                    
 
                     
                                                                         
                              
                            

                                  
                    
 
                                   
                                    
      

             
                                  

                                                           
                                                                                         

      



                                                                             
 

                                                              
 
                                                                    
 
                                                              
 
                                                 
 

                                     
                                                            
                                            
 


                                                                   

                                                              
                                                            

                                                                          
                                                              
 
                                                                   

                                             
 


                                                                   

                                                              
                                                            
                                                                                                                                
                                                                        
                                                                        
                  
                                                              

         
                      
                            


                                                            
                                   




                                                                    
                        
                                                            
                                             
 
                                                                   



                                                                            
                                                                      
 
                                                                        
 


                                                                           

                                                                      
                                                                    
                 
                                 





                                                                                                                            
         
 
             
                                  

                                                           
                                                                                    
      

 
                                                    

                                                        
                                                                                  
           
                                         
                             

                                        
 
                    

                   
                                                     
                                  
                                                                     


                                    
 
                                              

                                                                     
 
                               
                                                                          




                            
                                                                                
                     
           
                            
                      
                                      
 
                                                                                                                 
                         
 




                                                                                     
                                         

                             


                                        
 
                               
                        
 
                                              
 



                                               
                                                   
                                                                             
                 
 
                       

                                                                     




                                                                
                                            
                              
                             
 
                         
                               
 
                                                                 

                                                       
                                       
                    
                                              
                                                               



                                                                              
                          
 

                                                                                                    
                                                                    
 
                                
                                                   


                                                                                
                       
           
                                              

                                  

                                             
 

                                  
                                            
                             
 


                                                 


                          
                                                                                  
                                 
                                                          
                                                                              

                                                                          


                                        
                                                          
                                                                                
                                                       
                                                               
                                 
                                                             

                                        
                                                      

                                                                        

                                                                               
                         
                                                      

                                      
                                                               

                                                                   
                                                      
                                                                    
                                                                    

                                              

                                                                                    

                                              
                                    



                                                                                     
                                                                                   

                                                                                                    




                                                              
                                                                           
                                                                                      

                                                                                            






                                                                                       
                                                                        
                                                                                     
                                                                               
                                                                                       



                                                         
                                                                           
                                                                                               

                                                                                            

                                                      





                                 
           
                                      
                               
 

                              
                            

                                  
                    
 
                                   
                                    

      

                                                              
 
                                                            
 
                                                      
 
                                         
 

                                                           
 
                                                                     
         
 
                                                    
 
                                                           

                                     
 
                      
                            


                                                            
                                   




                                                                    
                        
                                                                        
                 
                                 

         
             
                                  

                                                           
                                                                                    



      
           
                                                                    
                                                      
 

                                    
 


                                                               
                                                                           

                                             
 
                                    

                                                                           


         

                                              
                                                    

                                                         
 
                                          
                               
               

                                                     
 
                                                         
                            

                              
                                            

                                                        

                                                                
                         
                                                                

                                                                        

                                                                            
                                                                             

                                                                            
                    

                                                                            

                                                                      
         
 
                                                                                 





                                                                            
                

                                                                   
         

                          


           

                                                            
                                                       
 
                                                      
                               
                                        

                              
               
 
                                          
                       
 

                                                 

                                                                            
 
                                                                     
                              

                                                               
                                                              
                                                                
 


                                                               
                                                                    
                                                              

                                                                            



                                                                                
 

                                                                                                                                                
                                            



                                                                                
                                           

                                                                                          
 
                                                                                                      
                                 
                         
 


                                                                

                                                

                 
 
                            
                                                                           


                                                                

 














                                                                    



                                     
  
                                                           
    
    
                                                   
                                             
 

                                                           
                                                          
 
                                                 
                       
 




                                                            





                                             




                                                        
                                                        
                                                                
 
                                         
 

                                                                           
 
                                                      
                                        
                                                    
 





                                                        




                                                                       
 



                                                                   
 
                                                             
         
 
                                                
                                                                               

                                                 
 

                                          
 


                                                                             
                                                                         
 
                             


                                          
                            
                                  
 
                                                    
                                                     
 




                                                                              
 
                                                                      
                                                                    
 

                                                                                     
 

                                                                                     
 

                                                                                     
 
                                                                                               

                                                                    
                                                                                             
                                                                    
 
                                                              
                                                        
 
                                               
                                          


                                                                           
 
                                              
                                                                                    
         

 















                                                                    








                                                                
        

























                                                                            

























                                                                            





















                                                                     




















                                                                     
               
                                        

                                         




                                                                 
 
                                                        
                                                                                                      



                                                     
                



                                                     
         
 

                                  
 
 
           

                                     
                                     
                                            
 
                                                             
                             
 
                         
                               
            
                                                               
 





                                                                             
                                                                 
                                                                                                      
                       
 
                                          
                                                      
 

                                       


                                                                         
         
 
 
           
                                      
                                                    
 
                        
                            
                      
 
                                                                                      
                                                                                       
                                                        
 
                           
                                                     
                                                     
                                                                         
            
                              
 
                                                                                          

                                                                                           

                                                                                                                                                                    

                          






                                                                 

         
                                                       
 
                                 

 
           

                      
                      


                            
 
                            
                               

                                 
                      

                      


                                          
                                             
                                                 
                             
 
                                                    
                             
 


                                                     

























                                                                                                        
                                                
 
                         
                                                                      
 
                                                                      





                                                                       
                                                                           
                                                      
 
                                                                              
                                                      
         
 
                                            
                                                                   
 

                                                                             
                                                                        

                                         
                                                            
                                                                           

         
                                                          
                                                

                                  
 

                                


                    
                          
                                  



                        
                               
                                      

                                                   
                         
 
                                  
                       
 
                                                         

                                                     


           
                                                     
 

                            
                                         
                             
                                  
          
 

                                                                       
                                      

                                                             
 
                                                        




                                                           
 


                         
    
                                                     
                                           
 
               
                                  
                    
                                              
 

                                                                   
                                         

                                                                   
                                 
                                                      
         
 

                                                          

 






                                 
                                 








                                                     
                                        


                                       
                    

















                                                                   


                                                                                     
















                                                     
                      
                                  



                    
                            
                                   


                                               
                                   







                                                        

                                                                 


                                                          



                          

                                  
                                      










                                                        

                                                                 

                                                                 



                          
    
                                               
 
                               


    
                                             
 
                                                     
 

                                        

                                    

                                                             
                    
                                                      
 
                                                              
 

                                                     


         
                                                      
    
                                                                
 
                                                          
 

                                                               
 


                                                                           

         
 
    
                                                                  
 



                                                               
 


                                                                           



         
                                                   
                                             

                                                
                                                                                    
                               
 
                                                                                                         
                       
 


                                                                               
         
 

                                                              
              

                                                                
         

 










                                                                                              
                                  
                            

                                                                                  
                           
                                  


           


                                   
















                                                                   
                                                                

                         




                                                                     



                                                                                               
                                                                                                                        





                                                                
                                                                                                                        





                                                                
                                                                                   








                                                      
                                                                            




                   


                                                            

                           
                                                                                         
                                                           


                                                      
                                                                     



                      
                                                
                                      
                                                



                                   
                            


                                         


                                                     

                                        
                                                        

                                          
                                                                   





                                                                                                                   

                                                      




                                                              
                                              
                                  

                                                                             
                                                                        
                                            
 

                    

                                                          












                                                                                                                    
                                                                       
 

                          

                                                          
                           

                                                  

                                                  





                                                                          




                                                               




                                                                       






                                                      
 
                                                                                         
 




                                                                 



                                                                           

                                                                   
 
                                

 





















                                                                       



                                                                     
 
                                       
 





                                                                 
 
                                                           
 


                                                        
 


                                                                
 












                                                               

 
           


                                                      
 

                                  
                                            
                               
                      
                    
                      

                              
                                   
 



                                                                        
                       
 




                                                      
                                     
                                          








                                                                 
                                        



                                                        
                                  


                                                       
                               







                                                                   
                        



                                                                   
                 
         
 



                                                                  
                


                                                          
 


                                                                   
 




                                                                 

                                              
         
 
                                   
 
                                                  
 


                                                      
 
                                  

                                                                       
         
 




                                                                           
 
                                     
                                           










                                                                             
 

                                                                  
 

                                                           
 





                                                                            
         
 




                                                                          

 
           


                                                   
 



                                   
                    
                                   
                           
                                   
 

                                                    
                                                                        
 

                                                                         
                                                       



                                                                  
                                                         
 
                                        
 
                                                                     
 

                                                                     
                       
 



                                                                      
                       
         
 
                                                              
 

                                                  
 



                                                           
 

                                                                
 






                                                             
                                           












                                                                            


                         












                                                                       
 




                                                             
 
                                                        




                                                                 
                                                                                        
                                                              

                                         


                                                          
                 
 






                                               
 

                                                       
 







                                                                                    
 
                                                                              
 

                                                         
 
                                                
                                    
 
                                                                 
                                                               

                                                                 
                 

         























                                                                               
 
                                                             
         
 

                                                                
 

                             
                                                
 
                                                    

 
               
                                                  
 


                                   
 
















                                                                        


                     
 
           
                                             
 
                                     
 
                                                       
 

                                                                             
 


                                                                       
         
 





                                                                     



           
                                           
                                     
                                         
 



                                   
                           
 
                                                                       


                                                                                
                                                                     
                                                 

                                                         
                       
         
 
                                           
 


                                                                    
 

                                                
                                      
 
                                                        

                                                              
 
                                                                       






                                                                         
 
                                                                 
 


                                                                         
 


                                                                             

                                     
                                                         
                                                      




                                                               
                                                      

























































                                                                               
         





                                          
 
/*
 * 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/>
 *
 *
 * Authors:
 *      Miguel de Icaza (miguel@ximian.com)
 *      Bertrand Guiheneuf (bg@aful.org)
 *      And just about everyone else in evolution ...
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

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

#include "message-list.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <string.h>
#include <ctype.h>

#include <glib/gi18n.h>
#include <glib/gstdio.h>

#include "libemail-engine/e-mail-utils.h"
#include "libemail-engine/mail-config.h"
#include "libemail-engine/mail-mt.h"
#include "libemail-engine/mail-ops.h"
#include "libemail-engine/mail-tools.h"

#include "e-mail-label-list-store.h"
#include "e-mail-ui-session.h"
#include "em-utils.h"

/*#define TIMEIT */

#ifdef TIMEIT
#include <sys/time.h>
#include <unistd.h>
#endif

#ifdef G_OS_WIN32
#ifdef gmtime_r
#undef gmtime_r
#endif
#ifdef localtime_r
#undef localtime_r
#endif

/* The gmtime() and localtime() in Microsoft's C library are MT-safe */
#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
#define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
#endif

#define d(x)
#define t(x)

#define MESSAGE_LIST_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), MESSAGE_LIST_TYPE, MessageListPrivate))

/* Common search expression segments. */
#define EXCLUDE_DELETED_MESSAGES_EXPR   "(not (system-flag \"deleted\"))"
#define EXCLUDE_JUNK_MESSAGES_EXPR  "(not (system-flag \"junk\"))"

typedef struct _RegenData RegenData;

struct _MLSelection {
    GPtrArray *uids;
    CamelFolder *folder;
};

struct _MessageListPrivate {
    GtkWidget *invisible;   /* 4 selection */

    EMailSession *session;

    CamelFolder *folder;
    gulong folder_changed_handler_id;

    /* For message list regeneration. */
    GMutex regen_lock;
    RegenData *regen_data;
    guint regen_timeout_id;

    struct _MLSelection clipboard;
    gboolean destroyed;

    gboolean expanded_default;
    gboolean group_by_threads;
    gboolean show_deleted;
    gboolean thread_latest;
    gboolean thread_subject;
    gboolean any_row_changed; /* save state before regen list when this is set to true */

    GtkTargetList *copy_target_list;
    GtkTargetList *paste_target_list;

    /* XXX Not sure if we really need a separate frozen counter
     *     for the tree model but the old ETreeMemory class had
     *     its own frozen counter so we preserve it here. */
    GNode *tree_model_root;
    gint tree_model_frozen;

    /* This aids in automatic message selection. */
    time_t newest_read_date;
    const gchar *newest_read_uid;
    time_t oldest_unread_date;
    const gchar *oldest_unread_uid;
};

struct _RegenData {
    volatile gint ref_count;

    EActivity *activity;
    MessageList *message_list;

    gchar *search;

    gboolean group_by_threads;
    gboolean thread_subject;
    CamelFolderThread *tree;

    /* This indicates we're regenerating the message list because
     * we received a "folder-changed" signal from our CamelFolder. */
    gboolean folder_changed;

    CamelFolder *folder;
    GPtrArray *summary;

    gint last_row; /* last selected (cursor) row */

    xmlDoc *expand_state; /* expanded state to be restored */

    /* These may be set during a regen operation.  Use the
     * select_lock to ensure consistency and thread-safety.
     * These are applied after the operation is finished. */
    GMutex select_lock;
    gchar *select_uid;
    gboolean select_all;
    gboolean select_use_fallback;
};

enum {
    PROP_0,
    PROP_COPY_TARGET_LIST,
    PROP_FOLDER,
    PROP_GROUP_BY_THREADS,
    PROP_PASTE_TARGET_LIST,
    PROP_SESSION,
    PROP_SHOW_DELETED,
    PROP_THREAD_LATEST,
    PROP_THREAD_SUBJECT
};

/* Forward Declarations */
static void message_list_selectable_init
                    (ESelectableInterface *interface);
static void message_list_tree_model_init
                    (ETreeModelInterface *interface);

G_DEFINE_TYPE_WITH_CODE (
    MessageList,
    message_list,
    E_TYPE_TREE,
    G_IMPLEMENT_INTERFACE (
        E_TYPE_EXTENSIBLE, NULL)
    G_IMPLEMENT_INTERFACE (
        E_TYPE_SELECTABLE,
        message_list_selectable_init)
    G_IMPLEMENT_INTERFACE (
        E_TYPE_TREE_MODEL,
        message_list_tree_model_init))

static struct {
    const gchar *target;
    GdkAtom atom;
    guint32 actions;
} ml_drag_info[] = {
    { "x-uid-list", NULL, GDK_ACTION_MOVE | GDK_ACTION_COPY },
    { "message/rfc822", NULL, GDK_ACTION_COPY },
    { "text/uri-list", NULL, GDK_ACTION_COPY },
};

enum {
    DND_X_UID_LIST,     /* x-uid-list */
    DND_MESSAGE_RFC822, /* message/rfc822 */
    DND_TEXT_URI_LIST   /* text/uri-list */
};

/* What we send */
static GtkTargetEntry ml_drag_types[] = {
    { (gchar *) "x-uid-list", 0, DND_X_UID_LIST },
    { (gchar *) "text/uri-list", 0, DND_TEXT_URI_LIST },
};

/* What we accept */
static GtkTargetEntry ml_drop_types[] = {
    { (gchar *) "x-uid-list", 0, DND_X_UID_LIST },
    { (gchar *) "message/rfc822", 0, DND_MESSAGE_RFC822 },
    { (gchar *) "text/uri-list", 0, DND_TEXT_URI_LIST },
};

/*
 * Default sizes for the ETable display
 *
 */
#define N_CHARS(x) (CHAR_WIDTH * (x))

#define COL_ICON_WIDTH         (16)
#define COL_ATTACH_WIDTH       (16)
#define COL_CHECK_BOX_WIDTH    (16)
#define COL_FROM_EXPANSION     (24.0)
#define COL_FROM_WIDTH_MIN     (32)
#define COL_SUBJECT_EXPANSION  (30.0)
#define COL_SUBJECT_WIDTH_MIN  (32)
#define COL_SENT_EXPANSION     (24.0)
#define COL_SENT_WIDTH_MIN     (32)
#define COL_RECEIVED_EXPANSION (20.0)
#define COL_RECEIVED_WIDTH_MIN (32)
#define COL_TO_EXPANSION       (24.0)
#define COL_TO_WIDTH_MIN       (32)
#define COL_SIZE_EXPANSION     (6.0)
#define COL_SIZE_WIDTH_MIN     (32)
#define COL_SENDER_EXPANSION     (24.0)
#define COL_SENDER_WIDTH_MIN     (32)

enum {
    NORMALISED_SUBJECT,
    NORMALISED_FROM,
    NORMALISED_TO,
    NORMALISED_LAST
};

static void on_cursor_activated_cmd     (ETree *tree,
                         gint row,
                         GNode *node,
                         gpointer user_data);
static void on_selection_changed_cmd    (ETree *tree,
                         MessageList *message_list);
static gint on_click            (ETree *tree,
                         gint row,
                         GNode *node,
                         gint col,
                         GdkEvent *event,
                         MessageList *message_list);

static void mail_regen_list         (MessageList *message_list,
                         const gchar *search,
                         gboolean folder_changed);
static void mail_regen_cancel       (MessageList *message_list);

static void clear_info          (gchar *key,
                         GNode *node,
                         MessageList *message_list);

enum {
    MESSAGE_SELECTED,
    MESSAGE_LIST_BUILT,
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = {0, };

static const gchar *status_map[] = {
    N_("Unseen"),
    N_("Seen"),
    N_("Answered"),
    N_("Forwarded"),
    N_("Multiple Unseen Messages"),
    N_("Multiple Messages"),
};

static const gchar *status_icons[] = {
    "mail-unread",
    "mail-read",
    "mail-replied",
    "mail-forward",
    "stock_mail-unread-multiple",
    "stock_mail-open-multiple"
};

static const gchar *score_map[] = {
    N_("Lowest"),
    N_("Lower"),
    N_("Low"),
    N_("Normal"),
    N_("High"),
    N_("Higher"),
    N_("Highest"),
};

static const gchar *score_icons[] = {
    "stock_score-lowest",
    "stock_score-lower",
    "stock_score-low",
    "stock_score-normal",
    "stock_score-high",
    "stock_score-higher",
    "stock_score-highest"
};

static const gchar *attachment_icons[] = {
    NULL,  /* empty icon */
    "mail-attachment",
    "stock_new-meeting"
};

static const gchar *flagged_icons[] = {
    NULL,  /* empty icon */
    "emblem-important"
};

static const gchar *followup_icons[] = {
    NULL,  /* empty icon */
    "stock_mail-flag-for-followup",
    "stock_mail-flag-for-followup-done"
};

static RegenData *
regen_data_new (MessageList *message_list,
                GCancellable *cancellable)
{
    RegenData *regen_data;
    EActivity *activity;
    EMailSession *session;

    activity = e_activity_new ();
    e_activity_set_cancellable (activity, cancellable);
    e_activity_set_text (activity, _("Generating message list"));

    regen_data = g_slice_new0 (RegenData);
    regen_data->ref_count = 1;
    regen_data->activity = g_object_ref (activity);
    regen_data->message_list = g_object_ref (message_list);
    regen_data->last_row = -1;

    /* Capture MessageList state to use for this regen. */
    regen_data->folder = message_list_ref_folder (message_list);
    regen_data->group_by_threads =
        message_list_get_group_by_threads (message_list);
    regen_data->thread_subject =
        message_list_get_thread_subject (message_list);

    g_mutex_init (&regen_data->select_lock);

    session = message_list_get_session (message_list);
    e_mail_ui_session_add_activity (E_MAIL_UI_SESSION (session), activity);

    g_object_unref (activity);

    return regen_data;
}

static RegenData *
regen_data_ref (RegenData *regen_data)
{
    g_return_val_if_fail (regen_data != NULL, NULL);
    g_return_val_if_fail (regen_data->ref_count > 0, NULL);

    g_atomic_int_inc (&regen_data->ref_count);

    return regen_data;
}

static void
regen_data_unref (RegenData *regen_data)
{
    g_return_if_fail (regen_data != NULL);
    g_return_if_fail (regen_data->ref_count > 0);

    if (g_atomic_int_dec_and_test (&regen_data->ref_count)) {

        g_clear_object (&regen_data->activity);
        g_clear_object (&regen_data->message_list);

        g_free (regen_data->search);

        if (regen_data->tree != NULL)
            camel_folder_thread_messages_unref (regen_data->tree);

        if (regen_data->summary != NULL) {
            guint ii, length;

            length = regen_data->summary->len;

            for (ii = 0; ii < length; ii++)
                camel_folder_free_message_info (
                    regen_data->folder,
                    regen_data->summary->pdata[ii]);

            g_ptr_array_free (regen_data->summary, TRUE);
        }

        g_clear_object (&regen_data->folder);

        if (regen_data->expand_state != NULL)
            xmlFreeDoc (regen_data->expand_state);

        g_mutex_clear (&regen_data->select_lock);
        g_free (regen_data->select_uid);

        g_slice_free (RegenData, regen_data);
    }
}

static RegenData *
message_list_ref_regen_data (MessageList *message_list)
{
    RegenData *regen_data = NULL;

    g_mutex_lock (&message_list->priv->regen_lock);

    if (message_list->priv->regen_data != NULL)
        regen_data = regen_data_ref (message_list->priv->regen_data);

    g_mutex_unlock (&message_list->priv->regen_lock);

    return regen_data;
}

static void
message_list_tree_model_freeze (MessageList *message_list)
{
    if (message_list->priv->tree_model_frozen == 0)
        e_tree_model_pre_change (E_TREE_MODEL (message_list));

    message_list->priv->tree_model_frozen++;
}

static void
message_list_tree_model_thaw (MessageList *message_list)
{
    if (message_list->priv->tree_model_frozen > 0)
        message_list->priv->tree_model_frozen--;

    if (message_list->priv->tree_model_frozen == 0)
        e_tree_model_node_changed (
            E_TREE_MODEL (message_list),
            message_list->priv->tree_model_root);
}

static GNode *
message_list_tree_model_insert (MessageList *message_list,
                                GNode *parent,
                                gint position,
                                gpointer data)
{
    ETreeModel *tree_model;
    GNode *node;
    gboolean tree_model_frozen;

    if (parent == NULL)
        g_return_val_if_fail (
            message_list->priv->tree_model_root == NULL, NULL);

    tree_model = E_TREE_MODEL (message_list);
    tree_model_frozen = (message_list->priv->tree_model_frozen > 0);

    if (!tree_model_frozen)
        e_tree_model_pre_change (tree_model);

    node = g_node_new (data);

    if (parent != NULL) {
        g_node_insert (parent, position, node);
        if (!tree_model_frozen)
            e_tree_model_node_inserted (tree_model, parent, node);
    } else {
        message_list->priv->tree_model_root = node;
        if (!tree_model_frozen)
            e_tree_model_node_changed (tree_model, node);
    }

    return node;
}

static void
message_list_tree_model_remove (MessageList *message_list,
                                GNode *node)
{
    ETreeModel *tree_model;
    GNode *parent = node->parent;
    gboolean tree_model_frozen;
    gint old_position = 0;

    g_return_if_fail (node != NULL);

    tree_model = E_TREE_MODEL (message_list);
    tree_model_frozen = (message_list->priv->tree_model_frozen > 0);

    if (!tree_model_frozen) {
        e_tree_model_pre_change (tree_model);
        old_position = g_node_child_position (parent, node);
    }

    g_node_unlink (node);

    if (!tree_model_frozen)
        e_tree_model_node_removed (
            tree_model, parent, node, old_position);

    g_node_destroy (node);

    if (node == message_list->priv->tree_model_root)
        message_list->priv->tree_model_root = NULL;

    if (!tree_model_frozen)
        e_tree_model_node_deleted (tree_model, node);
}

static gint
address_compare (gconstpointer address1,
                 gconstpointer address2,
                 gpointer cmp_cache)
{
    gint retval;

    g_return_val_if_fail (address1 != NULL, 1);
    g_return_val_if_fail (address2 != NULL, -1);

    retval = g_ascii_strcasecmp ((gchar *) address1, (gchar *) address2);

    return retval;
}

static gchar *
filter_size (gint size)
{
    gfloat fsize;

    if (size < 1024) {
        return g_strdup_printf ("%d", size);
    } else {
        fsize = ((gfloat) size) / 1024.0;
        if (fsize < 1024.0) {
            return g_strdup_printf ("%.2f K", fsize);
        } else {
            fsize /= 1024.0;
            return g_strdup_printf ("%.2f M", fsize);
        }
    }
}

/* Gets the uid of the message displayed at a given view row */
static const gchar *
get_message_uid (MessageList *message_list,
                 GNode *node)
{
    g_return_val_if_fail (node != NULL, NULL);
    g_return_val_if_fail (node->data != NULL, NULL);

    return camel_message_info_uid (node->data);
}

/* Gets the CamelMessageInfo for the message displayed at the given
 * view row.
 */
static CamelMessageInfo *
get_message_info (MessageList *message_list,
                  GNode *node)
{
    g_return_val_if_fail (node != NULL, NULL);
    g_return_val_if_fail (node->data != NULL, NULL);

    return node->data;
}

static const gchar *
get_normalised_string (MessageList *message_list,
                       CamelMessageInfo *info,
                       gint col)
{
    const gchar *string, *str;
    gchar *normalised;
    EPoolv *poolv;
    gint index;

    switch (col) {
    case COL_SUBJECT_NORM:
        string = camel_message_info_subject (info);
        index = NORMALISED_SUBJECT;
        break;
    case COL_FROM_NORM:
        string = camel_message_info_from (info);
        index = NORMALISED_FROM;
        break;
    case COL_TO_NORM:
        string = camel_message_info_to (info);
        index = NORMALISED_TO;
        break;
    default:
        string = NULL;
        index = NORMALISED_LAST;
        g_warning ("Should not be reached\n");
    }

    /* slight optimisation */
    if (string == NULL || string[0] == '\0')
        return "";

    poolv = g_hash_table_lookup (message_list->normalised_hash, camel_message_info_uid (info));
    if (poolv == NULL) {
        poolv = e_poolv_new (NORMALISED_LAST);
        g_hash_table_insert (message_list->normalised_hash, (gchar *) camel_message_info_uid (info), poolv);
    } else {
        str = e_poolv_get (poolv, index);
        if (*str)
            return str;
    }

    if (col == COL_SUBJECT_NORM) {
        gint skip_len;
        const gchar *subject;
        gboolean found_re = TRUE;

        subject = string;
        while (found_re) {
            found_re = em_utils_is_re_in_subject (
                subject, &skip_len) && skip_len > 0;
            if (found_re)
                subject += skip_len;

            /* jump over any spaces */
            while (*subject && isspace ((gint) *subject))
                subject++;
        }

        /* jump over any spaces */
        while (*subject && isspace ((gint) *subject))
            subject++;

        string = subject;
        normalised = g_utf8_collate_key (string, -1);
    } else {
        /* because addresses require strings, not collate keys */
        normalised = g_strdup (string);
    }

    e_poolv_set (poolv, index, normalised, TRUE);

    return e_poolv_get (poolv, index);
}

static void
clear_selection (MessageList *message_list,
                 struct _MLSelection *selection)
{
    if (selection->uids != NULL) {
        g_ptr_array_unref (selection->uids);
        selection->uids = NULL;
    }

    g_clear_object (&selection->folder);
}

static GNode *
ml_search_forward (MessageList *message_list,
                   gint start,
                   gint end,
                   guint32 flags,
                   guint32 mask)
{
    GNode *node;
    gint row;
    CamelMessageInfo *info;
    ETreeTableAdapter *etta;

    etta = e_tree_get_table_adapter (E_TREE (message_list));

    for (row = start; row <= end; row++) {
        node = e_tree_table_adapter_node_at_row (etta, row);
        if (node != NULL
            && (info = get_message_info (message_list, node))
            && (camel_message_info_flags (info) & mask) == flags)
            return node;
    }

    return NULL;
}

static GNode *
ml_search_backward (MessageList *message_list,
                    gint start,
                    gint end,
                    guint32 flags,
                    guint32 mask)
{
    GNode *node;
    gint row;
    CamelMessageInfo *info;
    ETreeTableAdapter *etta;

    etta = e_tree_get_table_adapter (E_TREE (message_list));

    for (row = start; row >= end; row--) {
        node = e_tree_table_adapter_node_at_row (etta, row);
        if (node != NULL
            && (info = get_message_info (message_list, node))
            && (camel_message_info_flags (info) & mask) == flags)
            return node;
    }

    return NULL;
}

static GNode *
ml_search_path (MessageList *message_list,
                MessageListSelectDirection direction,
                guint32 flags,
                guint32 mask)
{
    GNode *node;
    gint row, count;
    ETreeTableAdapter *etta;

    etta = e_tree_get_table_adapter (E_TREE (message_list));

    if (message_list->cursor_uid == NULL
        || (node = g_hash_table_lookup (message_list->uid_nodemap, message_list->cursor_uid)) == NULL)
        return NULL;

    row = e_tree_table_adapter_row_of_node (etta, node);
    if (row == -1)
        return NULL;
    count = e_table_model_row_count ((ETableModel *) etta);

    if ((direction & MESSAGE_LIST_SELECT_DIRECTION) == MESSAGE_LIST_SELECT_NEXT)
        node = ml_search_forward (
            message_list, row + 1, count - 1, flags, mask);
    else
        node = ml_search_backward (
            message_list, row - 1, 0, flags, mask);

    if (node == NULL && (direction & MESSAGE_LIST_SELECT_WRAP)) {
        if ((direction & MESSAGE_LIST_SELECT_DIRECTION) == MESSAGE_LIST_SELECT_NEXT)
            node = ml_search_forward (
                message_list, 0, row, flags, mask);
        else
            node = ml_search_backward (
                message_list, count - 1, row, flags, mask);
    }

    return node;
}

static void
select_node (MessageList *message_list,
             GNode *node)
{
    ETree *tree;
    ETreeTableAdapter *etta;
    ETreeSelectionModel *etsm;

    tree = E_TREE (message_list);
    etta = e_tree_get_table_adapter (tree);
    etsm = (ETreeSelectionModel *) e_tree_get_selection_model (tree);

    g_free (message_list->cursor_uid);
    message_list->cursor_uid = NULL;

    e_tree_table_adapter_show_node (etta, node);
    e_tree_set_cursor (tree, node);
    e_tree_selection_model_select_single_path (etsm, node);
}

/**
 * message_list_select:
 * @message_list: a MessageList
 * @direction: the direction to search in
 * @flags: a set of flag values
 * @mask: a mask for comparing against @flags
 *
 * This moves the message list selection to a suitable row. @flags and
 * @mask combine to specify what constitutes a suitable row. @direction is
 * %MESSAGE_LIST_SELECT_NEXT if it should find the next matching
 * message, or %MESSAGE_LIST_SELECT_PREVIOUS if it should find the
 * previous. %MESSAGE_LIST_SELECT_WRAP is an option bit which specifies the
 * search should wrap.
 *
 * If no suitable row is found, the selection will be
 * unchanged.
 *
 * Returns %TRUE if a new message has been selected or %FALSE otherwise.
 **/
gboolean
message_list_select (MessageList *message_list,
                     MessageListSelectDirection direction,
                     guint32 flags,
                     guint32 mask)
{
    GNode *node;

    node = ml_search_path (message_list, direction, flags, mask);
    if (node != NULL) {
        select_node (message_list, node);

        /* This function is usually called in response to a key
         * press, so grab focus if the message list is visible. */
        if (gtk_widget_get_visible (GTK_WIDGET (message_list)))
            gtk_widget_grab_focus (GTK_WIDGET (message_list));

        return TRUE;
    } else
        return FALSE;
}

/**
 * message_list_can_select:
 * @message_list:
 * @direction:
 * @flags:
 * @mask:
 *
 * Returns true if the selection specified is possible with the current view.
 *
 * Return value:
 **/
gboolean
message_list_can_select (MessageList *message_list,
                         MessageListSelectDirection direction,
                         guint32 flags,
                         guint32 mask)
{
    return ml_search_path (message_list, direction, flags, mask) != NULL;
}

/**
 * message_list_select_uid:
 * @message_list:
 * @uid:
 *
 * Selects the message with the given UID.
 **/
void
message_list_select_uid (MessageList *message_list,
                         const gchar *uid,
                         gboolean with_fallback)
{
    MessageListPrivate *priv;
    GHashTable *uid_nodemap;
    GNode *node = NULL;
    RegenData *regen_data = NULL;

    g_return_if_fail (IS_MESSAGE_LIST (message_list));

    priv = message_list->priv;
    uid_nodemap = message_list->uid_nodemap;

    if (message_list->priv->folder == NULL)
        return;

    /* Try to find the requested message UID. */
    if (uid != NULL)
        node = g_hash_table_lookup (uid_nodemap, uid);

    regen_data = message_list_ref_regen_data (message_list);

    /* If we're busy or waiting to regenerate the message list, cache
     * the UID so we can try again when we're done.  Otherwise if the
     * requested message UID was not found and 'with_fallback' is set,
     * try a couple fallbacks:
     *
     * 1) Oldest unread message in the list, by date received.
     * 2) Newest read message in the list, by date received.
     */
    if (regen_data != NULL) {
        g_mutex_lock (&regen_data->select_lock);
        g_free (regen_data->select_uid);
        regen_data->select_uid = g_strdup (uid);
        regen_data->select_use_fallback = with_fallback;
        g_mutex_unlock (&regen_data->select_lock);

        regen_data_unref (regen_data);

    } else if (with_fallback) {
        if (node == NULL && priv->oldest_unread_uid != NULL)
            node = g_hash_table_lookup (
                uid_nodemap, priv->oldest_unread_uid);
        if (node == NULL && priv->newest_read_uid != NULL)
            node = g_hash_table_lookup (
                uid_nodemap, priv->newest_read_uid);
    }

    if (node) {
        ETree *tree;
        GNode *old_cur;

        tree = E_TREE (message_list);
        old_cur = e_tree_get_cursor (tree);

        /* This will emit a changed signal that we'll pick up */
        e_tree_set_cursor (tree, node);

        if (old_cur == node)
            g_signal_emit (
                message_list,
                signals[MESSAGE_SELECTED],
                0, message_list->cursor_uid);
    } else {
        g_free (message_list->cursor_uid);
        message_list->cursor_uid = NULL;
        g_signal_emit (
            message_list,
            signals[MESSAGE_SELECTED],
            0, NULL);
    }
}

void
message_list_select_next_thread (MessageList *message_list)
{
    GNode *node;
    ETreeTableAdapter *etta;
    gint i, count, row;

    g_return_if_fail (IS_MESSAGE_LIST (message_list));

    etta = e_tree_get_table_adapter (E_TREE (message_list));

    if (!message_list->cursor_uid
        || (node = g_hash_table_lookup (message_list->uid_nodemap, message_list->cursor_uid)) == NULL)
        return;

    row = e_tree_table_adapter_row_of_node (etta, node);
    if (row == -1)
        return;
    count = e_table_model_row_count ((ETableModel *) etta);

    /* find the next node which has a root parent (i.e. toplevel node) */
    for (i = row + 1; i < count - 1; i++) {
        node = e_tree_table_adapter_node_at_row (etta, i);
        if (node != NULL && G_NODE_IS_ROOT (node->parent)) {
            select_node (message_list, node);
            return;
        }
    }
}

void
message_list_select_prev_thread (MessageList *message_list)
{
    GNode *node;
    ETreeTableAdapter *etta;
    gint i, row;
    gboolean skip_first;

    g_return_if_fail (IS_MESSAGE_LIST (message_list));

    etta = e_tree_get_table_adapter (E_TREE (message_list));

    if (!message_list->cursor_uid
        || (node = g_hash_table_lookup (message_list->uid_nodemap, message_list->cursor_uid)) == NULL)
        return;

    row = e_tree_table_adapter_row_of_node (etta, node);
    if (row == -1)
        return;

    /* skip first found if in the middle of the thread */
    skip_first = !G_NODE_IS_ROOT (node->parent);

    /* find the previous node which has a root parent (i.e. toplevel node) */
    for (i = row - 1; i >= 0; i--) {
        node = e_tree_table_adapter_node_at_row (etta, i);
        if (node != NULL && G_NODE_IS_ROOT (node->parent)) {
            if (skip_first) {
                skip_first = FALSE;
                continue;
            }

            select_node (message_list, node);
            return;
        }
    }
}

/**
 * message_list_select_all:
 * @message_list: Message List widget
 *
 * Selects all messages in the message list.
 **/
void
message_list_select_all (MessageList *message_list)
{
    RegenData *regen_data = NULL;

    g_return_if_fail (IS_MESSAGE_LIST (message_list));

    regen_data = message_list_ref_regen_data (message_list);

    if (regen_data != NULL && regen_data->group_by_threads) {
        regen_data->select_all = TRUE;
    } else {
        ETree *tree;
        ESelectionModel *selection_model;

        tree = E_TREE (message_list);
        selection_model = e_tree_get_selection_model (tree);
        e_selection_model_select_all (selection_model);
    }

    if (regen_data != NULL)
        regen_data_unref (regen_data);
}

typedef struct thread_select_info {
    MessageList *message_list;
    GPtrArray *paths;
} thread_select_info_t;

static gboolean
select_thread_node (ETreeModel *model,
                    GNode *node,
                    gpointer user_data)
{
    thread_select_info_t *tsi = (thread_select_info_t *) user_data;

    g_ptr_array_add (tsi->paths, node);

    return FALSE; /*not done yet */
}

static void
select_thread (MessageList *message_list,
               ETreeForeachFunc selector)
{
    ETree *tree;
    ETreeSelectionModel *etsm;
    thread_select_info_t tsi;

    tsi.message_list = message_list;
    tsi.paths = g_ptr_array_new ();

    tree = E_TREE (message_list);
    etsm = (ETreeSelectionModel *) e_tree_get_selection_model (tree);

    e_tree_selected_path_foreach (tree, selector, &tsi);

    e_tree_selection_model_select_paths (etsm, tsi.paths);

    g_ptr_array_free (tsi.paths, TRUE);
}

static void
thread_select_foreach (ETreePath path,
                       gpointer user_data)
{
    thread_select_info_t *tsi = (thread_select_info_t *) user_data;
    ETreeModel *tree_model;
    GNode *last, *node = path;

    tree_model = E_TREE_MODEL (tsi->message_list);

    do {
        last = node;
        node = node->parent;
    } while (!G_NODE_IS_ROOT (node));

    g_ptr_array_add (tsi->paths, last);

    e_tree_model_node_traverse (
        tree_model, last,
        (ETreePathFunc) select_thread_node, tsi);
}

/**
 * message_list_select_thread:
 * @message_list: Message List widget
 *
 * Selects all messages in the current thread (based on cursor).
 **/
void
message_list_select_thread (MessageList *message_list)
{
    select_thread (message_list, thread_select_foreach);
}

static void
subthread_select_foreach (ETreePath path,
                          gpointer user_data)
{
    thread_select_info_t *tsi = (thread_select_info_t *) user_data;
    ETreeModel *tree_model;

    tree_model = E_TREE_MODEL (tsi->message_list);

    e_tree_model_node_traverse (
        tree_model, path,
        (ETreePathFunc) select_thread_node, tsi);
}

/**
 * message_list_select_subthread:
 * @message_list: Message List widget
 *
 * Selects all messages in the current subthread (based on cursor).
 **/
void
message_list_select_subthread (MessageList *message_list)
{
    select_thread (message_list, subthread_select_foreach);
}

/**
 * message_list_invert_selection:
 * @message_list: Message List widget
 *
 * Invert the current selection in the message-list.
 **/
void
message_list_invert_selection (MessageList *message_list)
{
    ESelectionModel *etsm;

    etsm = e_tree_get_selection_model (E_TREE (message_list));

    e_selection_model_invert_selection (etsm);
}

void
message_list_copy (MessageList *message_list,
                   gboolean cut)
{
    MessageListPrivate *p = message_list->priv;
    GPtrArray *uids;

    clear_selection (message_list, &p->clipboard);

    uids = message_list_get_selected (message_list);

    if (uids->len > 0) {
        if (cut) {
            CamelFolder *folder;
            gint i;

            folder = message_list_ref_folder (message_list);

            camel_folder_freeze (folder);

            for (i = 0; i < uids->len; i++)
                camel_folder_set_message_flags (
                    folder, uids->pdata[i],
                    CAMEL_MESSAGE_SEEN |
                    CAMEL_MESSAGE_DELETED,
                    CAMEL_MESSAGE_SEEN |
                    CAMEL_MESSAGE_DELETED);

            camel_folder_thaw (folder);

            g_object_unref (folder);
        }

        p->clipboard.uids = g_ptr_array_ref (uids);
        p->clipboard.folder = message_list_ref_folder (message_list);

        gtk_selection_owner_set (
            p->invisible,
            GDK_SELECTION_CLIPBOARD,
            gtk_get_current_event_time ());
    } else {
        gtk_selection_owner_set (
            NULL, GDK_SELECTION_CLIPBOARD,
            gtk_get_current_event_time ());
    }

    g_ptr_array_unref (uids);
}

void
message_list_paste (MessageList *message_list)
{
    gtk_selection_convert (
        message_list->priv->invisible,
        GDK_SELECTION_CLIPBOARD,
        gdk_atom_intern ("x-uid-list", FALSE),
        GDK_CURRENT_TIME);
}

static void
for_node_and_subtree_if_collapsed (MessageList *message_list,
                                   GNode *node,
                                   CamelMessageInfo *mi,
                                   ETreePathFunc func,
                                   gpointer data)
{
    ETreeModel *tree_model;
    GNode *child;

    tree_model = E_TREE_MODEL (message_list);

    func (NULL, (ETreePath) mi, data);

    if (!node)
        return;

    child = g_node_first_child (node);
    if (child && !e_tree_node_is_expanded (E_TREE (message_list), node))
        e_tree_model_node_traverse (tree_model, node, func, data);
}

static gboolean
unread_foreach (ETreeModel *etm,
                ETreePath path,
                gpointer data)
{
    gboolean *saw_unread = data;
    CamelMessageInfo *info;

    if (!etm)
        info = (CamelMessageInfo *) path;
    else
        info = ((GNode *) path)->data;
    g_return_val_if_fail (info != NULL, FALSE);

    if (!(camel_message_info_flags (info) & CAMEL_MESSAGE_SEEN))
        *saw_unread = TRUE;

    return FALSE;
}

struct LatestData {
    gboolean sent;
    time_t latest;
};

static gboolean
latest_foreach (ETreeModel *etm,
                ETreePath path,
                gpointer data)
{
    struct LatestData *ld = data;
    CamelMessageInfo *info;
    time_t date;

    if (!etm)
        info = (CamelMessageInfo *) path;
    else
        info = ((GNode *) path)->data;
    g_return_val_if_fail (info != NULL, FALSE);

    date = ld->sent ? camel_message_info_date_sent (info)
            : camel_message_info_date_received (info);

    if (ld->latest == 0 || date > ld->latest)
        ld->latest = date;

    return FALSE;
}

static gchar *
sanitize_recipients (const gchar *string)
{
    GString     *gstring;
    gboolean     quoted = FALSE;
    const gchar *p;
    GString *recipients = g_string_new ("");
    gchar *single_add;
    gchar **name;

    if (!string || !*string)
        return (gchar *) "";

    gstring = g_string_new ("");

    for (p = string; *p; p = g_utf8_next_char (p)) {
        gunichar c = g_utf8_get_char (p);

        if (c == '"')
            quoted = ~quoted;
        else if (c == ',' && !quoted) {
            single_add = g_string_free (gstring, FALSE);
            name = g_strsplit (single_add,"<",2);
            g_string_append (recipients, *name);
            g_string_append (recipients, ",");
            g_free (single_add);
            g_strfreev (name);
            gstring = g_string_new ("");
            continue;
        }

        g_string_append_unichar (gstring, c);
    }

    single_add = g_string_free (gstring, FALSE);
    name = g_strsplit (single_add,"<",2);
    g_string_append (recipients, *name);
    g_free (single_add);
    g_strfreev (name);

    return g_string_free (recipients, FALSE);
}

struct LabelsData {
    EMailLabelListStore *store;
    GHashTable *labels_tag2iter;
};

static void
add_label_if_known (struct LabelsData *ld,
                    const gchar *tag)
{
    GtkTreeIter label_defn;

    if (e_mail_label_list_store_lookup (ld->store, tag, &label_defn)) {
        g_hash_table_insert (
            ld->labels_tag2iter,
            /* Should be the same as the "tag" arg */
            e_mail_label_list_store_get_tag (ld->store, &label_defn),
            gtk_tree_iter_copy (&label_defn));
    }
}

static gboolean
add_all_labels_foreach (ETreeModel *etm,
                        ETreePath path,
                        gpointer data)
{
    struct LabelsData *ld = data;
    CamelMessageInfo *msg_info;
    const gchar *old_label;
    gchar *new_label;
    const CamelFlag *flag;

    if (!etm)
        msg_info = (CamelMessageInfo *) path;
    else
        msg_info = ((GNode *) path)->data;
    g_return_val_if_fail (msg_info != NULL, FALSE);

    for (flag = camel_message_info_user_flags (msg_info); flag; flag = flag->next)
        add_label_if_known (ld, flag->name);

    old_label = camel_message_info_user_tag (msg_info, "label");
    if (old_label != NULL) {
        /* Convert old-style labels ("<name>") to "$Label<name>". */
        new_label = g_alloca (strlen (old_label) + 10);
        g_stpcpy (g_stpcpy (new_label, "$Label"), old_label);

        add_label_if_known (ld, new_label);
    }

    return FALSE;
}

static const gchar *
get_trimmed_subject (CamelMessageInfo *info)
{
    const gchar *subject;
    const gchar *mlist;
    gint mlist_len = 0;
    gboolean found_mlist;

    subject = camel_message_info_subject (info);
    if (!subject || !*subject)
        return subject;

    mlist = camel_message_info_mlist (info);

    if (mlist && *mlist) {
        const gchar *mlist_end;

        mlist_end = strchr (mlist, '@');
        if (mlist_end)
            mlist_len = mlist_end - mlist;
        else
            mlist_len = strlen (mlist);
    }

    do {
        gint skip_len;
        gboolean found_re = TRUE;

        found_mlist = FALSE;

        while (found_re) {
            found_re = FALSE;

            found_re = em_utils_is_re_in_subject (
                subject, &skip_len) && skip_len > 0;
            if (found_re)
                subject += skip_len;

            /* jump over any spaces */
            while (*subject && isspace ((gint) *subject))
                subject++;
        }

        if (mlist_len &&
            *subject == '[' &&
            !g_ascii_strncasecmp ((gchar *) subject + 1, mlist, mlist_len) &&
            subject[1 + mlist_len] == ']') {
            subject += 1 + mlist_len + 1;  /* jump over "[mailing-list]" */
            found_mlist = TRUE;

            /* jump over any spaces */
            while (*subject && isspace ((gint) *subject))
                subject++;
        }
    } while (found_mlist);

    /* jump over any spaces */
    while (*subject && isspace ((gint) *subject))
        subject++;

    return subject;
}

static gpointer
ml_tree_value_at_ex (ETreeModel *etm,
                     GNode *node,
                     gint col,
                     CamelMessageInfo *msg_info,
                     MessageList *message_list)
{
    EMailSession *session;
    const gchar *str;
    guint32 flags;

    session = message_list_get_session (message_list);

    g_return_val_if_fail (msg_info != NULL, NULL);

    switch (col) {
    case COL_MESSAGE_STATUS:
        flags = camel_message_info_flags (msg_info);
        if (flags & CAMEL_MESSAGE_ANSWERED)
            return GINT_TO_POINTER (2);
        else if (flags & CAMEL_MESSAGE_FORWARDED)
            return GINT_TO_POINTER (3);
        else if (flags & CAMEL_MESSAGE_SEEN)
            return GINT_TO_POINTER (1);
        else
            return GINT_TO_POINTER (0);
    case COL_FLAGGED:
        return GINT_TO_POINTER ((camel_message_info_flags (msg_info) & CAMEL_MESSAGE_FLAGGED) != 0);
    case COL_SCORE: {
        const gchar *tag;
        gint score = 0;

        tag = camel_message_info_user_tag (msg_info, "score");
        if (tag)
            score = atoi (tag);

        return GINT_TO_POINTER (score);
    }
    case COL_FOLLOWUP_FLAG_STATUS: {
        const gchar *tag, *cmp;

        /* FIXME: this all should be methods off of message-tag-followup class,
         * FIXME: the tag names should be namespaced :( */
        tag = camel_message_info_user_tag (msg_info, "follow-up");
        cmp = camel_message_info_user_tag (msg_info, "completed-on");
        if (tag && tag[0]) {
            if (cmp && cmp[0])
                return GINT_TO_POINTER (2);
            else
                return GINT_TO_POINTER (1);
        } else
            return GINT_TO_POINTER (0);
    }
    case COL_FOLLOWUP_DUE_BY: {
        const gchar *tag;
        time_t due_by;

        tag = camel_message_info_user_tag (msg_info, "due-by");
        if (tag && *tag) {
            due_by = camel_header_decode_date (tag, NULL);
            return GINT_TO_POINTER (due_by);
        } else {
            return GINT_TO_POINTER (0);
        }
    }
    case COL_FOLLOWUP_FLAG:
        str = camel_message_info_user_tag (msg_info, "follow-up");
        return (gpointer)(str ? str : "");
    case COL_ATTACHMENT:
        if (camel_message_info_user_flag (msg_info, "$has_cal"))
            return GINT_TO_POINTER (2);
        return GINT_TO_POINTER ((camel_message_info_flags (msg_info) & CAMEL_MESSAGE_ATTACHMENTS) != 0);
    case COL_FROM:
        str = camel_message_info_from (msg_info);
        return (gpointer)(str ? str : "");
    case COL_FROM_NORM:
        return (gpointer) get_normalised_string (message_list, msg_info, col);
    case COL_SUBJECT:
        str = camel_message_info_subject (msg_info);
        return (gpointer)(str ? str : "");
    case COL_SUBJECT_TRIMMED:
        str = get_trimmed_subject (msg_info);
        return (gpointer)(str ? str : "");
    case COL_SUBJECT_NORM:
        return (gpointer) get_normalised_string (message_list, msg_info, col);
    case COL_SENT: {
        struct LatestData ld;
        ld.sent = TRUE;
        ld.latest = 0;

        for_node_and_subtree_if_collapsed (message_list, node, msg_info, latest_foreach, &ld);

        return GINT_TO_POINTER (ld.latest);
    }
    case COL_RECEIVED: {
        struct LatestData ld;
        ld.sent = FALSE;
        ld.latest = 0;

        for_node_and_subtree_if_collapsed (message_list, node, msg_info, latest_foreach, &ld);

        return GINT_TO_POINTER (ld.latest);
    }
    case COL_TO:
        str = camel_message_info_to (msg_info);
        return (gpointer)(str ? str : "");
    case COL_TO_NORM:
        return (gpointer) get_normalised_string (message_list, msg_info, col);
    case COL_SIZE:
        return GINT_TO_POINTER (camel_message_info_size (msg_info));
    case COL_DELETED:
        return GINT_TO_POINTER ((camel_message_info_flags (msg_info) & CAMEL_MESSAGE_DELETED) != 0);
    case COL_UNREAD: {
        gboolean saw_unread = FALSE;

        for_node_and_subtree_if_collapsed (message_list, node, msg_info, unread_foreach, &saw_unread);

        return GINT_TO_POINTER (saw_unread);
    }
    case COL_COLOUR: {
        const gchar *colour, *due_by, *completed, *followup;

        /* Priority: colour tag; label tag; important flag; due-by tag */

        /* This is astonisngly poorly written code */

        /* To add to the woes, what color to show when the user choose multiple labels ?
        Don't say that I need to have the new labels[with subject] column visible always */

        colour = NULL;
        due_by = camel_message_info_user_tag (msg_info, "due-by");
        completed = camel_message_info_user_tag (msg_info, "completed-on");
        followup = camel_message_info_user_tag (msg_info, "follow-up");
        if (colour == NULL) {
            /* Get all applicable labels. */
            struct LabelsData ld;

            ld.store = e_mail_ui_session_get_label_store (
                E_MAIL_UI_SESSION (session));
            ld.labels_tag2iter = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
            for_node_and_subtree_if_collapsed (message_list, node, msg_info, add_all_labels_foreach, &ld);

            if (g_hash_table_size (ld.labels_tag2iter) == 1) {
                GHashTableIter iter;
                GtkTreeIter *label_defn;
                GdkColor colour_val;
                gchar *colour_alloced;

                /* Extract the single label from the hashtable. */
                g_hash_table_iter_init (&iter, ld.labels_tag2iter);
                g_hash_table_iter_next (&iter, NULL, (gpointer *) &label_defn);

                e_mail_label_list_store_get_color (ld.store, label_defn, &colour_val);

                /* XXX Hack to avoid returning an allocated string. */
                colour_alloced = gdk_color_to_string (&colour_val);
                colour = g_intern_string (colour_alloced);
                g_free (colour_alloced);
            } else if (camel_message_info_flags (msg_info) & CAMEL_MESSAGE_FLAGGED) {
                /* FIXME: extract from the important.xpm somehow. */
                colour = "#A7453E";
            } else if (((followup && *followup) || (due_by && *due_by)) && !(completed && *completed)) {
                time_t now = time (NULL);

                if ((followup && *followup) || now >= camel_header_decode_date (due_by, NULL))
                    colour = "#A7453E";
            }

            g_hash_table_destroy (ld.labels_tag2iter);
        }

        if (!colour)
            colour = camel_message_info_user_tag (msg_info, "color");

        return (gpointer) colour;
    }
    case COL_LOCATION: {
        /* Fixme : freeing memory stuff (mem leaks) */
        CamelStore *store;
        CamelFolder *folder;
        CamelService *service;
        const gchar *store_name;
        const gchar *folder_name;

        folder = message_list->priv->folder;

        if (CAMEL_IS_VEE_FOLDER (folder))
            folder = camel_vee_folder_get_location (
                CAMEL_VEE_FOLDER (folder),
                (CamelVeeMessageInfo *) msg_info, NULL);

        store = camel_folder_get_parent_store (folder);
        folder_name = camel_folder_get_full_name (folder);

        service = CAMEL_SERVICE (store);
        store_name = camel_service_get_display_name (service);

        return g_strdup_printf ("%s : %s", store_name, folder_name);
    }
    case COL_MIXED_RECIPIENTS:
    case COL_RECIPIENTS:{
        str = camel_message_info_to (msg_info);

        return sanitize_recipients (str);
    }
    case COL_MIXED_SENDER:
    case COL_SENDER:{
        gchar **sender_name = NULL;
        str = camel_message_info_from (msg_info);
        if (str && str[0] != '\0') {
            gchar *res;
            sender_name = g_strsplit (str,"<",2);
            res = g_strdup (*sender_name);
            g_strfreev (sender_name);
            return (gpointer)(res);
        }
        else
            return (gpointer)("");
    }
    case COL_LABELS:{
        struct LabelsData ld;
        GString *result = g_string_new ("");

        ld.store = e_mail_ui_session_get_label_store (
            E_MAIL_UI_SESSION (session));
        ld.labels_tag2iter = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
        for_node_and_subtree_if_collapsed (message_list, node, msg_info, add_all_labels_foreach, &ld);

        if (g_hash_table_size (ld.labels_tag2iter) > 0) {
            GHashTableIter iter;
            GtkTreeIter *label_defn;

            g_hash_table_iter_init (&iter, ld.labels_tag2iter);
            while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &label_defn)) {
                gchar *label_name, *label_name_clean;

                if (result->len > 0)
                    g_string_append (result, ", ");

                label_name = e_mail_label_list_store_get_name (ld.store, label_defn);
                label_name_clean = e_str_without_underscores (label_name);

                g_string_append (result, label_name_clean);

                g_free (label_name_clean);
                g_free (label_name);
            }
        }

        g_hash_table_destroy (ld.labels_tag2iter);
        return (gpointer) g_string_free (result, FALSE);
    }
    default:
        g_warning ("%s: This shouldn't be reached (col:%d)", G_STRFUNC, col);
        return NULL;
    }
}

static gchar *
filter_date (time_t date)
{
    time_t nowdate = time (NULL);
    time_t yesdate;
    struct tm then, now, yesterday;
    gchar buf[26];
    gboolean done = FALSE;

    if (date == 0)
        return g_strdup (_("?"));

    localtime_r (&date, &then);
    localtime_r (&nowdate, &now);
    if (then.tm_mday == now.tm_mday &&
        then.tm_mon == now.tm_mon &&
        then.tm_year == now.tm_year) {
        e_utf8_strftime_fix_am_pm (buf, 26, _("Today %l:%M %p"), &then);
        done = TRUE;
    }
    if (!done) {
        yesdate = nowdate - 60 * 60 * 24;
        localtime_r (&yesdate, &yesterday);
        if (then.tm_mday == yesterday.tm_mday &&
            then.tm_mon == yesterday.tm_mon &&
            then.tm_year == yesterday.tm_year) {
            e_utf8_strftime_fix_am_pm (buf, 26, _("Yesterday %l:%M %p"), &then);
            done = TRUE;
        }
    }
    if (!done) {
        gint i;
        for (i = 2; i < 7; i++) {
            yesdate = nowdate - 60 * 60 * 24 * i;
            localtime_r (&yesdate, &yesterday);
            if (then.tm_mday == yesterday.tm_mday &&
                then.tm_mon == yesterday.tm_mon &&
                then.tm_year == yesterday.tm_year) {
                e_utf8_strftime_fix_am_pm (buf, 26, _("%a %l:%M %p"), &then);
                done = TRUE;
                break;
            }
        }
    }
    if (!done) {
        if (then.tm_year == now.tm_year) {
            e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %l:%M %p"), &then);
        } else {
            e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %Y"), &then);
        }
    }

    return g_strdup (buf);
}

static ECell * create_composite_cell (gint col)
{
    ECell *cell_vbox, *cell_hbox, *cell_sub, *cell_date, *cell_from, *cell_tree, *cell_attach;
    GSettings *settings;
    gchar *fixed_name = NULL;
    gboolean show_email;
    gint alt_col = (col == COL_FROM) ? COL_SENDER : COL_RECIPIENTS;
    gboolean same_font = FALSE;

    settings = g_settings_new ("org.gnome.evolution.mail");
    show_email = g_settings_get_boolean (settings, "show-email");
    same_font = g_settings_get_boolean (settings, "vertical-view-fonts");
    g_object_unref (settings);
    if (!same_font) {
        settings = g_settings_new ("org.gnome.desktop.interface");
        fixed_name = g_settings_get_string (settings, "monospace-font-name");
        g_object_unref (settings);
    }

    cell_vbox = e_cell_vbox_new ();

    cell_hbox = e_cell_hbox_new ();

    /* Exclude the meeting icon. */
    cell_attach = e_cell_toggle_new (attachment_icons, G_N_ELEMENTS (attachment_icons));

    cell_date = e_cell_date_new (NULL, GTK_JUSTIFY_RIGHT);
    e_cell_date_set_format_component (E_CELL_DATE (cell_date), "mail");
    g_object_set (
        cell_date,
        "bold_column", COL_UNREAD,
        "color_column", COL_COLOUR,
        NULL);

    cell_from = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
    g_object_set (
        cell_from,
        "bold_column", COL_UNREAD,
        "color_column", COL_COLOUR,
        NULL);

    e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_from, show_email ? col : alt_col, 68);
    e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_attach, COL_ATTACHMENT, 5);
    e_cell_hbox_append (E_CELL_HBOX (cell_hbox), cell_date, COL_SENT, 27);
    g_object_unref (cell_from);
    g_object_unref (cell_attach);
    g_object_unref (cell_date);

    cell_sub = e_cell_text_new (fixed_name? fixed_name : NULL, GTK_JUSTIFY_LEFT);
    g_object_set (
        cell_sub,
        "color_column", COL_COLOUR,
        NULL);
    cell_tree = e_cell_tree_new (TRUE, cell_sub);
    e_cell_vbox_append (E_CELL_VBOX (cell_vbox), cell_hbox, COL_FROM);
    e_cell_vbox_append (E_CELL_VBOX (cell_vbox), cell_tree, COL_SUBJECT);
    g_object_unref (cell_sub);
    g_object_unref (cell_hbox);
    g_object_unref (cell_tree);

    g_object_set_data (G_OBJECT (cell_vbox), "cell_date", cell_date);
    g_object_set_data (G_OBJECT (cell_vbox), "cell_sub", cell_sub);
    g_object_set_data (G_OBJECT (cell_vbox), "cell_from", cell_from);

    g_free (fixed_name);

    return cell_vbox;
}

static void
composite_cell_set_strike_col (ECell *cell,
                               gint col)
{
    g_object_set (g_object_get_data (G_OBJECT (cell), "cell_date"),  "strikeout_column", col, NULL);
    g_object_set (g_object_get_data (G_OBJECT (cell), "cell_from"),  "strikeout_column", col, NULL);
}

static ETableExtras *
message_list_create_extras (void)
{
    ETableExtras *extras;
    ECell *cell;

    extras = e_table_extras_new ();
    e_table_extras_add_icon_name (extras, "status", "mail-unread");
    e_table_extras_add_icon_name (extras, "score", "stock_score-higher");
    e_table_extras_add_icon_name (extras, "attachment", "mail-attachment");
    e_table_extras_add_icon_name (extras, "flagged", "emblem-important");
    e_table_extras_add_icon_name (extras, "followup", "stock_mail-flag-for-followup");

    e_table_extras_add_compare (extras, "address_compare", address_compare);

    cell = e_cell_toggle_new (
        status_icons, G_N_ELEMENTS (status_icons));
    e_table_extras_add_cell (extras, "render_message_status", cell);
    g_object_unref (cell);

    cell = e_cell_toggle_new (
        attachment_icons, G_N_ELEMENTS (attachment_icons));
    e_table_extras_add_cell (extras, "render_attachment", cell);
    g_object_unref (cell);

    cell = e_cell_toggle_new (
        flagged_icons, G_N_ELEMENTS (flagged_icons));
    e_table_extras_add_cell (extras, "render_flagged", cell);
    g_object_unref (cell);

    cell = e_cell_toggle_new (
        followup_icons, G_N_ELEMENTS (followup_icons));
    e_table_extras_add_cell (extras, "render_flag_status", cell);
    g_object_unref (cell);

    cell = e_cell_toggle_new (
        score_icons, G_N_ELEMENTS (score_icons));
    e_table_extras_add_cell (extras, "render_score", cell);
    g_object_unref (cell);

    /* date cell */
    cell = e_cell_date_new (NULL, GTK_JUSTIFY_LEFT);
    e_cell_date_set_format_component (E_CELL_DATE (cell), "mail");
    g_object_set (
        cell,
        "bold_column", COL_UNREAD,
        "color_column", COL_COLOUR,
        NULL);
    e_table_extras_add_cell (extras, "render_date", cell);
    g_object_unref (cell);

    /* text cell */
    cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
    g_object_set (
        cell,
        "bold_column", COL_UNREAD,
        "color_column", COL_COLOUR,
        NULL);
    e_table_extras_add_cell (extras, "render_text", cell);
    g_object_unref (cell);

    cell = e_cell_tree_new (TRUE, cell);
    e_table_extras_add_cell (extras, "render_tree", cell);
    g_object_unref (cell);

    /* size cell */
    cell = e_cell_size_new (NULL, GTK_JUSTIFY_RIGHT);
    g_object_set (
        cell,
        "bold_column", COL_UNREAD,
        "color_column", COL_COLOUR,
        NULL);
    e_table_extras_add_cell (extras, "render_size", cell);
    g_object_unref (cell);

    /* Composite cell for wide view */
    cell = create_composite_cell (COL_FROM);
    e_table_extras_add_cell (extras, "render_composite_from", cell);
    g_object_unref (cell);

    cell = create_composite_cell (COL_TO);
    e_table_extras_add_cell (extras, "render_composite_to", cell);
    g_object_unref (cell);

    /* set proper format component for a default 'date' cell renderer */
    cell = e_table_extras_get_cell (extras, "date");
    e_cell_date_set_format_component (E_CELL_DATE (cell), "mail");

    return extras;
}

static void
save_tree_state (MessageList *message_list,
                 CamelFolder *folder)
{
    gchar *filename;

    if (folder == NULL)
        return;

    if (message_list->search != NULL && *message_list->search != '\0')
        return;

    filename = mail_config_folder_to_cachename (folder, "et-expanded-");
    e_tree_save_expanded_state (E_TREE (message_list), filename);
    g_free (filename);

    message_list->priv->any_row_changed = FALSE;
}

static void
load_tree_state (MessageList *message_list,
                 CamelFolder *folder,
                 xmlDoc *expand_state)
{
    if (folder == NULL)
        return;

    if (expand_state) {
        e_tree_load_expanded_state_xml (
            E_TREE (message_list), expand_state);
    } else if (!message_list->search || !*message_list->search) {
        /* only when not searching */
        gchar *filename;

        filename = mail_config_folder_to_cachename (
            folder, "et-expanded-");
        e_tree_load_expanded_state (E_TREE (message_list), filename);
        g_free (filename);
    }

    message_list->priv->any_row_changed = FALSE;
}

void
message_list_save_state (MessageList *message_list)
{
    CamelFolder *folder;

    folder = message_list_ref_folder (message_list);

    if (folder != NULL) {
        save_tree_state (message_list, folder);
        g_object_unref (folder);
    }
}

static void
message_list_setup_etree (MessageList *message_list)
{
    CamelFolder *folder;

    /* Build the spec based on the folder, and possibly
     * from a saved file.   Otherwise, leave default. */

    folder = message_list_ref_folder (message_list);

    if (folder != NULL) {
        gint data = 1;
        ETableItem *item;

        item = e_tree_get_item (E_TREE (message_list));

        g_object_set (message_list, "uniform_row_height", TRUE, NULL);
        g_object_set_data (
            G_OBJECT (((GnomeCanvasItem *) item)->canvas),
            "freeze-cursor", &data);

        /* build based on saved file */
        load_tree_state (message_list, folder, NULL);

        g_object_unref (folder);
    }
}

static void
ml_selection_get (GtkWidget *widget,
                  GtkSelectionData *data,
                  guint info,
                  guint time_stamp,
                  MessageList *message_list)
{
    struct _MLSelection *selection;

    selection = &message_list->priv->clipboard;

    if (selection->uids == NULL)
        return;

    if (info & 2) {
        /* text/plain */
        d (printf ("setting text/plain selection for uids\n"));
        em_utils_selection_set_mailbox (data, selection->folder, selection->uids);
    } else {
        /* x-uid-list */
        d (printf ("setting x-uid-list selection for uids\n"));
        em_utils_selection_set_uidlist (data, selection->folder, selection->uids);
    }
}

static gboolean
ml_selection_clear_event (GtkWidget *widget,
                          GdkEventSelection *event,
                          MessageList *message_list)
{
    MessageListPrivate *p = message_list->priv;

    clear_selection (message_list, &p->clipboard);

    return TRUE;
}

static void
ml_selection_received (GtkWidget *widget,
                       GtkSelectionData *selection_data,
                       guint time,
                       MessageList *message_list)
{
    EMailSession *session;
    CamelFolder *folder;
    GdkAtom target;

    target = gtk_selection_data_get_target (selection_data);

    if (target != gdk_atom_intern ("x-uid-list", FALSE)) {
        d (printf ("Unknown selection received by message-list\n"));
        return;
    }

    folder = message_list_ref_folder (message_list);
    session = message_list_get_session (message_list);

    /* FIXME Not passing a GCancellable or GError here. */
    em_utils_selection_get_uidlist (
        selection_data, session, folder, FALSE, NULL, NULL);

    g_clear_object (&folder);
}

static void
ml_tree_drag_data_get (ETree *tree,
                       gint row,
                       GNode *node,
                       gint col,
                       GdkDragContext *context,
                       GtkSelectionData *data,
                       guint info,
                       guint time,
                       MessageList *message_list)
{
    CamelFolder *folder;
    GPtrArray *uids;

    folder = message_list_ref_folder (message_list);
    uids = message_list_get_selected (message_list);

    if (uids->len > 0) {
        switch (info) {
        case DND_X_UID_LIST:
            em_utils_selection_set_uidlist (data, folder, uids);
            break;
        case DND_TEXT_URI_LIST:
            em_utils_selection_set_urilist (data, folder, uids);
            break;
        }
    }

    g_clear_object (&folder);
    g_ptr_array_unref (uids);
}

/* TODO: merge this with the folder tree stuff via empopup targets */
/* Drop handling */
struct _drop_msg {
    MailMsg base;

    GdkDragContext *context;

    /* Only selection->data and selection->length are valid */
    GtkSelectionData *selection;

    CamelFolder *folder;
    MessageList *message_list;

    guint32 action;
    guint info;

    guint move : 1;
    guint moved : 1;
    guint aborted : 1;
};

static gchar *
ml_drop_async_desc (struct _drop_msg *m)
{
    const gchar *full_name;

    full_name = camel_folder_get_full_name (m->folder);

    if (m->move)
        return g_strdup_printf (_("Moving messages into folder %s"), full_name);
    else
        return g_strdup_printf (_("Copying messages into folder %s"), full_name);
}

static void
ml_drop_async_exec (struct _drop_msg *m,
                    GCancellable *cancellable,
                    GError **error)
{
    EMailSession *session;

    session = message_list_get_session (m->message_list);

    switch (m->info) {
    case DND_X_UID_LIST:
        em_utils_selection_get_uidlist (
            m->selection, session, m->folder,
            m->action == GDK_ACTION_MOVE,
            cancellable, error);
        break;
    case DND_MESSAGE_RFC822:
        em_utils_selection_get_message (m->selection, m->folder);
        break;
    case DND_TEXT_URI_LIST:
        em_utils_selection_get_urilist (m->selection, m->folder);
        break;
    }
}

static void
ml_drop_async_done (struct _drop_msg *m)
{
    gboolean success, delete;

    /* ?? */
    if (m->aborted) {
        success = FALSE;
        delete = FALSE;
    } else {
        success = (m->base.error == NULL);
        delete = success && m->move && !m->moved;
    }

    gtk_drag_finish (m->context, success, delete, GDK_CURRENT_TIME);
}

static void
ml_drop_async_free (struct _drop_msg *m)
{
    g_object_unref (m->context);
    g_object_unref (m->folder);
    g_object_unref (m->message_list);
    gtk_selection_data_free (m->selection);
}

static MailMsgInfo ml_drop_async_info = {
    sizeof (struct _drop_msg),
    (MailMsgDescFunc) ml_drop_async_desc,
    (MailMsgExecFunc) ml_drop_async_exec,
    (MailMsgDoneFunc) ml_drop_async_done,
    (MailMsgFreeFunc) ml_drop_async_free
};

static void
ml_drop_action (struct _drop_msg *m)
{
    m->move = m->action == GDK_ACTION_MOVE;
    mail_msg_unordered_push (m);
}

static void
ml_tree_drag_data_received (ETree *tree,
                            gint row,
                            GNode *node,
                            gint col,
                            GdkDragContext *context,
                            gint x,
                            gint y,
                            GtkSelectionData *selection_data,
                            guint info,
                            guint time,
                            MessageList *message_list)
{
    CamelFolder *folder;
    struct _drop_msg *m;

    if (gtk_selection_data_get_data (selection_data) == NULL)
        return;

    if (gtk_selection_data_get_length (selection_data) == -1)
        return;

    folder = message_list_ref_folder (message_list);
    if (folder == NULL)
        return;

    m = mail_msg_new (&ml_drop_async_info);
    m->context = g_object_ref (context);
    m->folder = g_object_ref (folder);
    m->message_list = g_object_ref (message_list);
    m->action = gdk_drag_context_get_selected_action (context);
    m->info = info;

    /* need to copy, goes away once we exit */
    m->selection = gtk_selection_data_copy (selection_data);

    ml_drop_action (m);

    g_object_unref (folder);
}

struct search_child_struct {
    gboolean found;
    gconstpointer looking_for;
};

static void
search_child_cb (GtkWidget *widget,
                 gpointer data)
{
    struct search_child_struct *search = (struct search_child_struct *) data;

    search->found = search->found || g_direct_equal (widget, search->looking_for);
}

static gboolean
is_tree_widget_children (ETree *tree,
                         gconstpointer widget)
{
    struct search_child_struct search;

    search.found = FALSE;
    search.looking_for = widget;

    gtk_container_foreach (GTK_CONTAINER (tree), search_child_cb, &search);

    return search.found;
}

static gboolean
ml_tree_drag_motion (ETree *tree,
                     GdkDragContext *context,
                     gint x,
                     gint y,
                     guint time,
                     MessageList *message_list)
{
    GList *targets;
    GdkDragAction action, actions = 0;
    GtkWidget *source_widget;

    /* If drop target is name of the account/store
     * and not actual folder, don't allow any action. */
    if (message_list->priv->folder == NULL) {
        gdk_drag_status (context, 0, time);
        return TRUE;
    }

    source_widget = gtk_drag_get_source_widget (context);

    /* If source widget is packed under 'tree', don't allow any action */
    if (is_tree_widget_children (tree, source_widget)) {
        gdk_drag_status (context, 0, time);
        return TRUE;
    }

    if (EM_IS_FOLDER_TREE (source_widget)) {
        EMFolderTree *folder_tree;
        CamelFolder *selected_folder = NULL;
        CamelStore *selected_store;
        gchar *selected_folder_name;
        gboolean has_selection;

        folder_tree = EM_FOLDER_TREE (source_widget);

        has_selection = em_folder_tree_get_selected (
            folder_tree, &selected_store, &selected_folder_name);

        /* Sanity checks */
        g_warn_if_fail (
            (has_selection && selected_store != NULL) ||
            (!has_selection && selected_store == NULL));
        g_warn_if_fail (
            (has_selection && selected_folder_name != NULL) ||
            (!has_selection && selected_folder_name == NULL));

        if (has_selection) {
            selected_folder = camel_store_get_folder_sync (
                selected_store, selected_folder_name,
                CAMEL_STORE_FOLDER_INFO_FAST, NULL, NULL);
            g_object_unref (selected_store);
            g_free (selected_folder_name);
        }

        if (selected_folder == message_list->priv->folder) {
            gdk_drag_status (context, 0, time);
            return TRUE;
        }
    }

    targets = gdk_drag_context_list_targets (context);
    while (targets != NULL) {
        gint i;

        d (printf ("atom drop '%s'\n", gdk_atom_name (targets->data)));
        for (i = 0; i < G_N_ELEMENTS (ml_drag_info); i++)
            if (targets->data == (gpointer) ml_drag_info[i].atom)
                actions |= ml_drag_info[i].actions;

        targets = g_list_next (targets);
    }
    d (printf ("\n"));

    actions &= gdk_drag_context_get_actions (context);
    action = gdk_drag_context_get_suggested_action (context);
    if (action == GDK_ACTION_COPY && (actions & GDK_ACTION_MOVE))
        action = GDK_ACTION_MOVE;

    gdk_drag_status (context, action, time);

    return action != 0;
}

static void
on_model_row_changed (ETableModel *model,
                      gint row,
                      MessageList *message_list)
{
    message_list->priv->any_row_changed = TRUE;
}

static gboolean
ml_tree_sorting_changed (ETreeTableAdapter *adapter,
                         MessageList *message_list)
{
    gboolean group_by_threads;

    g_return_val_if_fail (message_list != NULL, FALSE);

    group_by_threads = message_list_get_group_by_threads (message_list);

    if (group_by_threads && message_list->frozen == 0) {
        if (message_list->thread_tree != NULL) {
            /* free the previous thread_tree to recreate it fully */
            camel_folder_thread_messages_unref (
                message_list->thread_tree);
            message_list->thread_tree = NULL;
        }

        mail_regen_list (message_list, message_list->search, FALSE);

        return TRUE;
    }

    return FALSE;
}

static void
message_list_set_session (MessageList *message_list,
                          EMailSession *session)
{
    g_return_if_fail (E_IS_MAIL_SESSION (session));
    g_return_if_fail (message_list->priv->session == NULL);

    message_list->priv->session = g_object_ref (session);
}

static void
message_list_set_property (GObject *object,
                           guint property_id,
                           const GValue *value,
                           GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_FOLDER:
            message_list_set_folder (
                MESSAGE_LIST (object),
                g_value_get_object (value));
            return;

        case PROP_GROUP_BY_THREADS:
            message_list_set_group_by_threads (
                MESSAGE_LIST (object),
                g_value_get_boolean (value));
            return;

        case PROP_SESSION:
            message_list_set_session (
                MESSAGE_LIST (object),
                g_value_get_object (value));
            return;

        case PROP_SHOW_DELETED:
            message_list_set_show_deleted (
                MESSAGE_LIST (object),
                g_value_get_boolean (value));
            return;

        case PROP_THREAD_LATEST:
            message_list_set_thread_latest (
                MESSAGE_LIST (object),
                g_value_get_boolean (value));
            return;

        case PROP_THREAD_SUBJECT:
            message_list_set_thread_subject (
                MESSAGE_LIST (object),
                g_value_get_boolean (value));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
message_list_get_property (GObject *object,
                           guint property_id,
                           GValue *value,
                           GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_COPY_TARGET_LIST:
            g_value_set_boxed (
                value,
                message_list_get_copy_target_list (
                MESSAGE_LIST (object)));
            return;

        case PROP_FOLDER:
            g_value_take_object (
                value,
                message_list_ref_folder (
                MESSAGE_LIST (object)));
            return;

        case PROP_GROUP_BY_THREADS:
            g_value_set_boolean (
                value,
                message_list_get_group_by_threads (
                MESSAGE_LIST (object)));
            return;

        case PROP_PASTE_TARGET_LIST:
            g_value_set_boxed (
                value,
                message_list_get_paste_target_list (
                MESSAGE_LIST (object)));
            return;

        case PROP_SESSION:
            g_value_set_object (
                value,
                message_list_get_session (
                MESSAGE_LIST (object)));
            return;

        case PROP_SHOW_DELETED:
            g_value_set_boolean (
                value,
                message_list_get_show_deleted (
                MESSAGE_LIST (object)));
            return;

        case PROP_THREAD_LATEST:
            g_value_set_boolean (
                value,
                message_list_get_thread_latest (
                MESSAGE_LIST (object)));
            return;

        case PROP_THREAD_SUBJECT:
            g_value_set_boolean (
                value,
                message_list_get_thread_subject (
                MESSAGE_LIST (object)));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
message_list_dispose (GObject *object)
{
    MessageList *message_list = MESSAGE_LIST (object);
    MessageListPrivate *priv;

    priv = message_list->priv;

    if (priv->folder_changed_handler_id > 0) {
        g_signal_handler_disconnect (
            priv->folder,
            priv->folder_changed_handler_id);
        priv->folder_changed_handler_id = 0;
    }

    if (priv->copy_target_list != NULL) {
        gtk_target_list_unref (priv->copy_target_list);
        priv->copy_target_list = NULL;
    }

    if (priv->paste_target_list != NULL) {
        gtk_target_list_unref (priv->paste_target_list);
        priv->paste_target_list = NULL;
    }

    priv->destroyed = TRUE;

    if (message_list->priv->folder != NULL)
        mail_regen_cancel (message_list);

    if (message_list->uid_nodemap) {
        g_hash_table_foreach (
            message_list->uid_nodemap,
            (GHFunc) clear_info, message_list);
        g_hash_table_destroy (message_list->uid_nodemap);
        message_list->uid_nodemap = NULL;
    }

    g_clear_object (&priv->session);
    g_clear_object (&priv->folder);
    g_clear_object (&priv->invisible);

    g_clear_object (&message_list->extras);

    if (message_list->idle_id > 0) {
        g_source_remove (message_list->idle_id);
        message_list->idle_id = 0;
    }

    if (message_list->seen_id > 0) {
        g_source_remove (message_list->seen_id);
        message_list->seen_id = 0;
    }

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

static void
message_list_finalize (GObject *object)
{
    MessageList *message_list = MESSAGE_LIST (object);

    g_hash_table_destroy (message_list->normalised_hash);

    if (message_list->thread_tree)
        camel_folder_thread_messages_unref (message_list->thread_tree);

    g_free (message_list->search);
    g_free (message_list->frozen_search);
    g_free (message_list->cursor_uid);

    g_mutex_clear (&message_list->priv->regen_lock);

    clear_selection (message_list, &message_list->priv->clipboard);

    if (message_list->priv->tree_model_root != NULL)
        g_node_destroy (message_list->priv->tree_model_root);

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

static void
message_list_constructed (GObject *object)
{
    /* Chain up to parent's constructed() method. */
    G_OBJECT_CLASS (message_list_parent_class)->constructed (object);

    e_extensible_load_extensions (E_EXTENSIBLE (object));
}

static void
message_list_selectable_update_actions (ESelectable *selectable,
                                        EFocusTracker *focus_tracker,
                                        GdkAtom *clipboard_targets,
                                        gint n_clipboard_targets)
{
    GtkAction *action;
    gboolean sensitive;

    action = e_focus_tracker_get_select_all_action (focus_tracker);
    sensitive = (e_tree_row_count (E_TREE (selectable)) > 0);
    gtk_action_set_tooltip (action, _("Select all visible messages"));
    gtk_action_set_sensitive (action, sensitive);
}

static void
message_list_selectable_select_all (ESelectable *selectable)
{
    message_list_select_all (MESSAGE_LIST (selectable));
}

static ETreePath
message_list_get_root (ETreeModel *tree_model)
{
    MessageList *message_list = MESSAGE_LIST (tree_model);

    return message_list->priv->tree_model_root;
}

static ETreePath
message_list_get_parent (ETreeModel *tree_model,
                         ETreePath path)
{
    return ((GNode *) path)->parent;
}

static ETreePath
message_list_get_first_child (ETreeModel *tree_model,
                              ETreePath path)
{
    return g_node_first_child ((GNode *) path);
}

static ETreePath
message_list_get_next (ETreeModel *tree_model,
                       ETreePath path)
{
    return g_node_next_sibling ((GNode *) path);
}

static gboolean
message_list_is_root (ETreeModel *tree_model,
                      ETreePath path)
{
    return G_NODE_IS_ROOT ((GNode *) path);
}

static gboolean
message_list_is_expandable (ETreeModel *tree_model,
                            ETreePath path)
{
    return (g_node_first_child ((GNode *) path) != NULL);
}

static guint
message_list_get_n_children (ETreeModel *tree_model,
                             ETreePath path)
{
    return g_node_n_children ((GNode *) path);
}

static guint
message_list_depth (ETreeModel *tree_model,
                    ETreePath path)
{
    return g_node_depth ((GNode *) path);
}

static gboolean
message_list_get_expanded_default (ETreeModel *tree_model)
{
    MessageList *message_list = MESSAGE_LIST (tree_model);

    return message_list->priv->expanded_default;
}

static gint
message_list_column_count (ETreeModel *tree_model)
{
    return COL_LAST;
}

static gboolean
message_list_has_save_id (ETreeModel *tree_model)
{
    return TRUE;
}

static gchar *
message_list_get_save_id (ETreeModel *tree_model,
                          ETreePath path)
{
    CamelMessageInfo *info;

    if (G_NODE_IS_ROOT ((GNode *) path))
        return g_strdup ("root");

    /* Note: ETable can ask for the save_id while we're clearing
     *       it, which is the only time info should be NULL. */
    info = ((GNode *) path)->data;
    if (info == NULL)
        return NULL;

    return g_strdup (camel_message_info_uid (info));
}

static gboolean
message_list_has_get_node_by_id (ETreeModel *tree_model)
{
    return TRUE;
}

static ETreePath
message_list_get_node_by_id (ETreeModel *tree_model,
                             const gchar *save_id)
{
    MessageList *message_list;

    message_list = MESSAGE_LIST (tree_model);

    if (!strcmp (save_id, "root"))
        return e_tree_model_get_root (tree_model);

    return g_hash_table_lookup (message_list->uid_nodemap, save_id);
}

static gpointer
message_list_sort_value_at (ETreeModel *tree_model,
                            ETreePath path,
                            gint col)
{
    MessageList *message_list;
    struct LatestData ld;

    message_list = MESSAGE_LIST (tree_model);

    if (!(col == COL_SENT || col == COL_RECEIVED))
        return e_tree_model_value_at (tree_model, path, col);

    if (G_NODE_IS_ROOT ((GNode *) path))
        return NULL;

    ld.sent = (col == COL_SENT);
    ld.latest = 0;

    latest_foreach (tree_model, path, &ld);
    if (message_list->priv->thread_latest)
        e_tree_model_node_traverse (
            tree_model, path, latest_foreach, &ld);

    return GINT_TO_POINTER (ld.latest);
}

static gpointer
message_list_value_at (ETreeModel *tree_model,
                       ETreePath path,
                       gint col)
{
    MessageList *message_list;
    CamelMessageInfo *msg_info;

    message_list = MESSAGE_LIST (tree_model);

    if (G_NODE_IS_ROOT ((GNode *) path))
        return NULL;

    /* retrieve the message information array */
    msg_info = ((GNode *) path)->data;
    g_return_val_if_fail (msg_info != NULL, NULL);

    return ml_tree_value_at_ex (tree_model, path, col, msg_info, message_list);
}

static void
message_list_set_value_at (ETreeModel *tree_model,
                           ETreePath path,
                           gint col,
                           gconstpointer val)
{
    g_warn_if_reached ();
}

static gboolean
message_list_is_editable (ETreeModel *tree_model,
                          ETreePath path,
                          gint col)
{
    return FALSE;
}

static gpointer
message_list_duplicate_value (ETreeModel *tree_model,
                              gint col,
                              gconstpointer value)
{
    switch (col) {
        case COL_MESSAGE_STATUS:
        case COL_FLAGGED:
        case COL_SCORE:
        case COL_ATTACHMENT:
        case COL_DELETED:
        case COL_UNREAD:
        case COL_SENT:
        case COL_RECEIVED:
        case COL_SIZE:
        case COL_FOLLOWUP_FLAG_STATUS:
        case COL_FOLLOWUP_DUE_BY:
            return (gpointer) value;

        case COL_FROM:
        case COL_SUBJECT:
        case COL_TO:
        case COL_SENDER:
        case COL_RECIPIENTS:
        case COL_MIXED_SENDER:
        case COL_MIXED_RECIPIENTS:
        case COL_FOLLOWUP_FLAG:
        case COL_LOCATION:
        case COL_LABELS:
            return g_strdup (value);

        default:
            g_return_val_if_reached (NULL);
    }
}

static void
message_list_free_value (ETreeModel *tree_model,
                         gint col,
                         gpointer value)
{
    switch (col) {
        case COL_MESSAGE_STATUS:
        case COL_FLAGGED:
        case COL_SCORE:
        case COL_ATTACHMENT:
        case COL_DELETED:
        case COL_UNREAD:
        case COL_SENT:
        case COL_RECEIVED:
        case COL_SIZE:
        case COL_FOLLOWUP_FLAG_STATUS:
        case COL_FOLLOWUP_DUE_BY:
        case COL_FROM_NORM:
        case COL_SUBJECT_NORM:
        case COL_TO_NORM:
        case COL_SUBJECT_TRIMMED:
        case COL_COLOUR:
            break;

        case COL_FROM:
        case COL_SUBJECT:
        case COL_TO:
        case COL_FOLLOWUP_FLAG:
        case COL_LOCATION:
        case COL_SENDER:
        case COL_RECIPIENTS:
        case COL_MIXED_SENDER:
        case COL_MIXED_RECIPIENTS:
        case COL_LABELS:
            g_free (value);
            break;

        default:
            g_warn_if_reached ();
    }
}

static gpointer
message_list_initialize_value (ETreeModel *tree_model,
                               gint col)
{
    switch (col) {
        case COL_MESSAGE_STATUS:
        case COL_FLAGGED:
        case COL_SCORE:
        case COL_ATTACHMENT:
        case COL_DELETED:
        case COL_UNREAD:
        case COL_SENT:
        case COL_RECEIVED:
        case COL_SIZE:
        case COL_FOLLOWUP_FLAG_STATUS:
        case COL_FOLLOWUP_DUE_BY:
            return NULL;

        case COL_FROM:
        case COL_SUBJECT:
        case COL_TO:
        case COL_FOLLOWUP_FLAG:
        case COL_LOCATION:
        case COL_SENDER:
        case COL_RECIPIENTS:
        case COL_MIXED_SENDER:
        case COL_MIXED_RECIPIENTS:
        case COL_LABELS:
            return g_strdup ("");

        default:
            g_return_val_if_reached (NULL);
    }
}

static gboolean
message_list_value_is_empty (ETreeModel *tree_model,
                             gint col,
                             gconstpointer value)
{
    switch (col) {
        case COL_MESSAGE_STATUS:
        case COL_FLAGGED:
        case COL_SCORE:
        case COL_ATTACHMENT:
        case COL_DELETED:
        case COL_UNREAD:
        case COL_SENT:
        case COL_RECEIVED:
        case COL_SIZE:
        case COL_FOLLOWUP_FLAG_STATUS:
        case COL_FOLLOWUP_DUE_BY:
            return value == NULL;

        case COL_FROM:
        case COL_SUBJECT:
        case COL_TO:
        case COL_FOLLOWUP_FLAG:
        case COL_LOCATION:
        case COL_SENDER:
        case COL_RECIPIENTS:
        case COL_MIXED_SENDER:
        case COL_MIXED_RECIPIENTS:
        case COL_LABELS:
            return !(value && *(gchar *) value);

        default:
            g_return_val_if_reached (FALSE);
    }
}

static gchar *
message_list_value_to_string (ETreeModel *tree_model,
                              gint col,
                              gconstpointer value)
{
    guint ii;

    switch (col) {
        case COL_MESSAGE_STATUS:
            ii = GPOINTER_TO_UINT (value);
            if (ii > 5)
                return g_strdup ("");
            return g_strdup (_(status_map[ii]));

        case COL_SCORE:
            ii = GPOINTER_TO_UINT (value) + 3;
            if (ii > 6)
                ii = 3;
            return g_strdup (_(score_map[ii]));

        case COL_ATTACHMENT:
        case COL_FLAGGED:
        case COL_DELETED:
        case COL_UNREAD:
        case COL_FOLLOWUP_FLAG_STATUS:
            ii = GPOINTER_TO_UINT (value);
            return g_strdup_printf ("%u", ii);

        case COL_SENT:
        case COL_RECEIVED:
        case COL_FOLLOWUP_DUE_BY:
            return filter_date (GPOINTER_TO_INT (value));

        case COL_SIZE:
            return filter_size (GPOINTER_TO_INT (value));

        case COL_FROM:
        case COL_SUBJECT:
        case COL_TO:
        case COL_FOLLOWUP_FLAG:
        case COL_LOCATION:
        case COL_SENDER:
        case COL_RECIPIENTS:
        case COL_MIXED_SENDER:
        case COL_MIXED_RECIPIENTS:
        case COL_LABELS:
            return g_strdup (value);

        default:
            g_return_val_if_reached (NULL);
    }

}

static void
message_list_class_init (MessageListClass *class)
{
    GObjectClass *object_class;
    gint i;

    for (i = 0; i < G_N_ELEMENTS (ml_drag_info); i++)
        ml_drag_info[i].atom = gdk_atom_intern (ml_drag_info[i].target, FALSE);

    g_type_class_add_private (class, sizeof (MessageListPrivate));

    object_class = G_OBJECT_CLASS (class);
    object_class->set_property = message_list_set_property;
    object_class->get_property = message_list_get_property;
    object_class->dispose = message_list_dispose;
    object_class->finalize = message_list_finalize;
    object_class->constructed = message_list_constructed;

    class->message_list_built = NULL;

    /* Inherited from ESelectableInterface */
    g_object_class_override_property (
        object_class,
        PROP_COPY_TARGET_LIST,
        "copy-target-list");

    g_object_class_install_property (
        object_class,
        PROP_FOLDER,
        g_param_spec_object (
            "folder",
            "Folder",
            "The source folder",
            CAMEL_TYPE_FOLDER,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_GROUP_BY_THREADS,
        g_param_spec_boolean (
            "group-by-threads",
            "Group By Threads",
            "Group messages into conversation threads",
            FALSE,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT |
            G_PARAM_STATIC_STRINGS));

    /* Inherited from ESelectableInterface */
    g_object_class_override_property (
        object_class,
        PROP_PASTE_TARGET_LIST,
        "paste-target-list");

    g_object_class_install_property (
        object_class,
        PROP_SESSION,
        g_param_spec_object (
            "session",
            "Mail Session",
            "The mail session",
            E_TYPE_MAIL_SESSION,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT_ONLY |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_SHOW_DELETED,
        g_param_spec_boolean (
            "show-deleted",
            "Show Deleted",
            "Show messages marked for deletion",
            FALSE,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_THREAD_LATEST,
        g_param_spec_boolean (
            "thread-latest",
            "Thread Latest",
            "Sort threads by latest message",
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_THREAD_SUBJECT,
        g_param_spec_boolean (
            "thread-subject",
            "Thread Subject",
            "Thread messages by Subject headers",
            FALSE,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT |
            G_PARAM_STATIC_STRINGS));

    signals[MESSAGE_SELECTED] = g_signal_new (
        "message_selected",
        MESSAGE_LIST_TYPE,
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (MessageListClass, message_selected),
        NULL,
        NULL,
        g_cclosure_marshal_VOID__STRING,
        G_TYPE_NONE, 1,
        G_TYPE_STRING);

    signals[MESSAGE_LIST_BUILT] = g_signal_new (
        "message_list_built",
        MESSAGE_LIST_TYPE,
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (MessageListClass, message_list_built),
        NULL,
        NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);
}

static void
message_list_selectable_init (ESelectableInterface *interface)
{
    interface->update_actions = message_list_selectable_update_actions;
    interface->select_all = message_list_selectable_select_all;
}

static void
message_list_tree_model_init (ETreeModelInterface *interface)
{
    interface->get_root = message_list_get_root;
    interface->get_parent = message_list_get_parent;
    interface->get_first_child = message_list_get_first_child;
    interface->get_next = message_list_get_next;
    interface->is_root = message_list_is_root;
    interface->is_expandable = message_list_is_expandable;
    interface->get_n_children = message_list_get_n_children;
    interface->depth = message_list_depth;
    interface->get_expanded_default = message_list_get_expanded_default;
    interface->column_count = message_list_column_count;
    interface->has_save_id = message_list_has_save_id;
    interface->get_save_id = message_list_get_save_id;
    interface->has_get_node_by_id = message_list_has_get_node_by_id;
    interface->get_node_by_id = message_list_get_node_by_id;
    interface->sort_value_at = message_list_sort_value_at;
    interface->value_at = message_list_value_at;
    interface->set_value_at = message_list_set_value_at;
    interface->is_editable = message_list_is_editable;
    interface->duplicate_value = message_list_duplicate_value;
    interface->free_value = message_list_free_value;
    interface->initialize_value = message_list_initialize_value;
    interface->value_is_empty = message_list_value_is_empty;
    interface->value_to_string = message_list_value_to_string;
}

static void
message_list_init (MessageList *message_list)
{
    MessageListPrivate *p;
    GtkTargetList *target_list;
    GdkAtom matom;

    message_list->priv = MESSAGE_LIST_GET_PRIVATE (message_list);

    message_list->normalised_hash = g_hash_table_new_full (
        g_str_hash, g_str_equal,
        (GDestroyNotify) NULL,
        (GDestroyNotify) e_poolv_destroy);

    message_list->uid_nodemap = g_hash_table_new (g_str_hash, g_str_equal);

    message_list->cursor_uid = NULL;
    message_list->last_sel_single = FALSE;

    g_mutex_init (&message_list->priv->regen_lock);

    /* TODO: Should this only get the selection if we're realised? */
    p = message_list->priv;
    p->invisible = gtk_invisible_new ();
    p->destroyed = FALSE;
    g_object_ref_sink (p->invisible);
    p->any_row_changed = FALSE;

    matom = gdk_atom_intern ("x-uid-list", FALSE);
    gtk_selection_add_target (p->invisible, GDK_SELECTION_CLIPBOARD, matom, 0);
    gtk_selection_add_target (p->invisible, GDK_SELECTION_CLIPBOARD, GDK_SELECTION_TYPE_STRING, 2);

    g_signal_connect (
        p->invisible, "selection_get",
        G_CALLBACK (ml_selection_get), message_list);
    g_signal_connect (
        p->invisible, "selection_clear_event",
        G_CALLBACK (ml_selection_clear_event), message_list);
    g_signal_connect (
        p->invisible, "selection_received",
        G_CALLBACK (ml_selection_received), message_list);

    /* FIXME This is currently unused. */
    target_list = gtk_target_list_new (NULL, 0);
    message_list->priv->copy_target_list = target_list;

    /* FIXME This is currently unused. */
    target_list = gtk_target_list_new (NULL, 0);
    message_list->priv->paste_target_list = target_list;
}

static void
message_list_construct (MessageList *message_list)
{
    AtkObject *a11y;
    gboolean constructed;
    gchar *etspecfile;

    /*
     * The etree
     */
    message_list->extras = message_list_create_extras ();

    etspecfile = g_build_filename (EVOLUTION_ETSPECDIR, "message-list.etspec", NULL);
    constructed = e_tree_construct_from_spec_file (
        E_TREE (message_list),
        E_TREE_MODEL (message_list),
        message_list->extras, etspecfile, NULL);
    g_free (etspecfile);

    if (constructed)
        e_tree_root_node_set_visible (E_TREE (message_list), FALSE);

    if (atk_get_root () != NULL) {
        a11y = gtk_widget_get_accessible (GTK_WIDGET (message_list));
        atk_object_set_name (a11y, _("Messages"));
    }

    g_signal_connect (
        e_tree_get_table_adapter (E_TREE (message_list)),
        "model_row_changed",
        G_CALLBACK (on_model_row_changed), message_list);

    g_signal_connect (
        message_list, "cursor_activated",
        G_CALLBACK (on_cursor_activated_cmd), message_list);

    g_signal_connect (
        message_list, "click",
        G_CALLBACK (on_click), message_list);

    g_signal_connect (
        message_list, "selection_change",
        G_CALLBACK (on_selection_changed_cmd), message_list);

    e_tree_drag_source_set (
        E_TREE (message_list), GDK_BUTTON1_MASK,
        ml_drag_types, G_N_ELEMENTS (ml_drag_types),
        GDK_ACTION_MOVE | GDK_ACTION_COPY);

    g_signal_connect (
        message_list, "tree_drag_data_get",
        G_CALLBACK (ml_tree_drag_data_get), message_list);

    e_tree_drag_dest_set (
        E_TREE (message_list), GTK_DEST_DEFAULT_ALL,
        ml_drop_types, G_N_ELEMENTS (ml_drop_types),
        GDK_ACTION_MOVE | GDK_ACTION_COPY);

    g_signal_connect (
        message_list, "tree_drag_data_received",
        G_CALLBACK (ml_tree_drag_data_received), message_list);

    g_signal_connect (
        message_list, "drag-motion",
        G_CALLBACK (ml_tree_drag_motion), message_list);

    g_signal_connect (
        e_tree_get_table_adapter (E_TREE (message_list)),
        "sorting_changed",
        G_CALLBACK (ml_tree_sorting_changed), message_list);
}

/**
 * message_list_new:
 *
 * Creates a new message-list widget.
 *
 * Returns a new message-list widget.
 **/
GtkWidget *
message_list_new (EMailSession *session)
{
    GtkWidget *message_list;

    g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);

    message_list = g_object_new (
        message_list_get_type (),
        "session", session, NULL);

    message_list_construct (MESSAGE_LIST (message_list));

    return message_list;
}

EMailSession *
message_list_get_session (MessageList *message_list)
{
    g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);

    return message_list->priv->session;
}

static void
clear_info (gchar *key,
            GNode *node,
            MessageList *message_list)
{
    CamelMessageInfo *info;

    info = node->data;
    camel_folder_free_message_info (message_list->priv->folder, info);
    node->data = NULL;
}

static void
clear_tree (MessageList *message_list,
            gboolean tfree)
{
    ETreeModel *tree_model;
    CamelFolder *folder;

#ifdef TIMEIT
    struct timeval start, end;
    gulong diff;

    printf ("Clearing tree\n");
    gettimeofday (&start, NULL);
#endif

    tree_model = E_TREE_MODEL (message_list);

    /* we also reset the uid_rowmap since it is no longer useful/valid anyway */
    folder = message_list_ref_folder (message_list);
    if (folder != NULL)
        g_hash_table_foreach (
            message_list->uid_nodemap,
            (GHFunc) clear_info, message_list);
    g_hash_table_destroy (message_list->uid_nodemap);
    message_list->uid_nodemap = g_hash_table_new (g_str_hash, g_str_equal);
    g_clear_object (&folder);

    message_list->priv->newest_read_date = 0;
    message_list->priv->newest_read_uid = NULL;
    message_list->priv->oldest_unread_date = 0;
    message_list->priv->oldest_unread_uid = NULL;

    if (message_list->priv->tree_model_root != NULL) {
        /* we should be frozen already */
        message_list_tree_model_remove (
            message_list, message_list->priv->tree_model_root);
    }

    /* Create a new placeholder root node. */
    message_list_tree_model_insert (message_list, NULL, 0, NULL);
    g_warn_if_fail (message_list->priv->tree_model_root != NULL);

    if (tfree)
        e_tree_model_rebuilt (tree_model);
#ifdef TIMEIT
    gettimeofday (&end, NULL);
    diff = end.tv_sec * 1000 + end.tv_usec / 1000;
    diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
    printf ("Clearing tree took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
#endif
}

static gboolean
folder_store_supports_vjunk_folder (CamelFolder *folder)
{
    CamelStore *store;

    g_return_val_if_fail (folder != NULL, FALSE);

    store = camel_folder_get_parent_store (folder);
    if (store == NULL)
        return FALSE;

    if (CAMEL_IS_VEE_FOLDER (folder))
        return TRUE;

    if (store->flags & CAMEL_STORE_VJUNK)
        return TRUE;

    if (store->flags & CAMEL_STORE_REAL_JUNK_FOLDER)
        return TRUE;

    return FALSE;
}

static gboolean
message_list_get_hide_junk (MessageList *message_list,
                            CamelFolder *folder)
{
    if (folder == NULL)
        return FALSE;

    if (!folder_store_supports_vjunk_folder (folder))
        return FALSE;

    if (folder->folder_flags & CAMEL_FOLDER_IS_JUNK)
        return FALSE;

    if (folder->folder_flags & CAMEL_FOLDER_IS_TRASH)
        return FALSE;

    return TRUE;
}

static gboolean
message_list_get_hide_deleted (MessageList *message_list,
                               CamelFolder *folder)
{
    CamelStore *store;
    gboolean non_trash_folder;

    if (folder == NULL)
        return FALSE;

    if (message_list_get_show_deleted (message_list))
        return FALSE;

    store = camel_folder_get_parent_store (folder);
    g_return_val_if_fail (store != NULL, FALSE);

    non_trash_folder =
        ((store->flags & CAMEL_STORE_VTRASH) == 0) ||
        ((folder->folder_flags & CAMEL_FOLDER_IS_TRASH) == 0);

    return non_trash_folder;
}

/* Check if the given node is selectable in the current message list,
 * which depends on the type of the folder (normal, junk, trash). */
static gboolean
is_node_selectable (MessageList *message_list,
                    CamelMessageInfo *info)
{
    CamelFolder *folder;
    gboolean is_junk_folder;
    gboolean is_trash_folder;
    guint32 flags;
    gboolean flag_junk;
    gboolean flag_deleted;
    gboolean hide_junk;
    gboolean hide_deleted;
    gboolean store_has_vjunk;

    g_return_val_if_fail (info != NULL, FALSE);

    folder = message_list_ref_folder (message_list);
    g_return_val_if_fail (folder != NULL, FALSE);

    store_has_vjunk = folder_store_supports_vjunk_folder (folder);

    /* check folder type */
    is_junk_folder =
        store_has_vjunk &&
        (folder->folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
    is_trash_folder = folder->folder_flags & CAMEL_FOLDER_IS_TRASH;

    hide_junk = message_list_get_hide_junk (message_list, folder);
    hide_deleted = message_list_get_hide_deleted (message_list, folder);

    g_object_unref (folder);

    /* check flags set on current message */
    flags = camel_message_info_flags (info);
    flag_junk = store_has_vjunk && (flags & CAMEL_MESSAGE_JUNK) != 0;
    flag_deleted = flags & CAMEL_MESSAGE_DELETED;

    /* perform actions depending on folder type */
    if (is_junk_folder) {
        /* messages in a junk folder are selectable only if
         * the message is marked as junk and if not deleted
         * when hide_deleted is set */
        if (flag_junk && !(flag_deleted && hide_deleted))
            return TRUE;

    } else if (is_trash_folder) {
        /* messages in a trash folder are selectable unless
         * not deleted any more */
        if (flag_deleted)
            return TRUE;
    } else {
        /* in normal folders it depends on hide_deleted,
         * hide_junk and the message flags */
        if (!(flag_junk && hide_junk)
            && !(flag_deleted && hide_deleted))
            return TRUE;
    }

    return FALSE;
}

/* We try and find something that is selectable in our tree.  There is
 * actually no assurance that we'll find something that will still be
 * there next time, but its probably going to work most of the time. */
static gchar *
find_next_selectable (MessageList *message_list)
{
    GNode *node;
    gint last;
    gint vrow_orig;
    gint vrow;
    ETree *et = E_TREE (message_list);
    CamelMessageInfo *info;

    node = g_hash_table_lookup (
        message_list->uid_nodemap, message_list->cursor_uid);
    if (node == NULL)
        return NULL;

    info = get_message_info (message_list, node);
    if (info && is_node_selectable (message_list, info))
        return NULL;

    last = e_tree_row_count (et);

    /* model_to_view_row etc simply dont work for sorted views.  Sigh. */
    vrow_orig = e_tree_row_of_node (et, node);

    /* We already checked this node. */
    vrow = vrow_orig + 1;

    while (vrow < last) {
        node = e_tree_node_at_row (et, vrow);
        info = get_message_info (message_list, node);
        if (info && is_node_selectable (message_list, info))
            return g_strdup (camel_message_info_uid (info));
        vrow++;
    }

    /* We didn't find any undeleted entries _below_ the currently selected one
 *       * so let's try to find one _above_ */
    vrow = vrow_orig - 1;

    while (vrow >= 0) {
        node = e_tree_node_at_row (et, vrow);
        info = get_message_info (message_list, node);
        if (info && is_node_selectable (message_list, info))
            return g_strdup (camel_message_info_uid (info));
        vrow--;
    }

    return NULL;
}

static GNode *
ml_uid_nodemap_insert (MessageList *message_list,
                       CamelMessageInfo *info,
                       GNode *parent,
                       gint row)
{
    CamelFolder *folder;
    GNode *node;
    const gchar *uid;
    time_t date;
    guint flags;

    folder = message_list_ref_folder (message_list);
    g_return_val_if_fail (folder != NULL, NULL);

    if (parent == NULL)
        parent = message_list->priv->tree_model_root;

    node = message_list_tree_model_insert (
        message_list, parent, row, info);

    uid = camel_message_info_uid (info);
    flags = camel_message_info_flags (info);
    date = camel_message_info_date_received (info);

    camel_folder_ref_message_info (folder, info);
    g_hash_table_insert (message_list->uid_nodemap, (gpointer) uid, node);

    /* Track the latest seen and unseen messages shown, used in
     * fallback heuristics for automatic message selection. */
    if (flags & CAMEL_MESSAGE_SEEN) {
        if (date > message_list->priv->newest_read_date) {
            message_list->priv->newest_read_date = date;
            message_list->priv->newest_read_uid = uid;
        }
    } else {
        if (message_list->priv->oldest_unread_date == 0) {
            message_list->priv->oldest_unread_date = date;
            message_list->priv->oldest_unread_uid = uid;
        } else if (date < message_list->priv->oldest_unread_date) {
            message_list->priv->oldest_unread_date = date;
            message_list->priv->oldest_unread_uid = uid;
        }
    }

    g_object_unref (folder);

    return node;
}

static void
ml_uid_nodemap_remove (MessageList *message_list,
                       CamelMessageInfo *info)
{
    CamelFolder *folder;
    const gchar *uid;

    folder = message_list_ref_folder (message_list);
    g_return_if_fail (folder != NULL);

    uid = camel_message_info_uid (info);

    if (uid == message_list->priv->newest_read_uid) {
        message_list->priv->newest_read_date = 0;
        message_list->priv->newest_read_uid = NULL;
    }

    if (uid == message_list->priv->oldest_unread_uid) {
        message_list->priv->oldest_unread_date = 0;
        message_list->priv->oldest_unread_uid = NULL;
    }

    g_hash_table_remove (message_list->uid_nodemap, uid);
    camel_folder_free_message_info (folder, info);

    g_object_unref (folder);
}

/* only call if we have a tree model */
/* builds the tree structure */

static void build_subtree           (MessageList *message_list,
                         GNode *parent,
                         CamelFolderThreadNode *c,
                         gint *row);

static void build_subtree_diff      (MessageList *message_list,
                         GNode *parent,
                         GNode *node,
                         CamelFolderThreadNode *c,
                         gint *row);

static void
build_tree (MessageList *message_list,
            CamelFolderThread *thread,
            gboolean folder_changed)
{
    gint row = 0;
    ETableItem *table_item = e_tree_get_item (E_TREE (message_list));
    gchar *saveuid = NULL;
    GPtrArray *selected;
#ifdef TIMEIT
    struct timeval start, end;
    gulong diff;

    printf ("Building tree\n");
    gettimeofday (&start, NULL);
#endif

#ifdef TIMEIT
    gettimeofday (&end, NULL);
    diff = end.tv_sec * 1000 + end.tv_usec / 1000;
    diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
    printf ("Loading tree state took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
#endif

    if (message_list->priv->tree_model_root == NULL) {
        message_list_tree_model_insert (message_list, NULL, 0, NULL);
        g_warn_if_fail (message_list->priv->tree_model_root != NULL);
    }

    if (message_list->cursor_uid != NULL)
        saveuid = find_next_selectable (message_list);

        selected = message_list_get_selected (message_list);

        message_list_tree_model_freeze (message_list);

        clear_tree (message_list, FALSE);

        build_subtree (
            message_list,
            message_list->priv->tree_model_root,
            thread->tree, &row);

        /* Show the cursor unless we're responding to a
         * "folder-changed" signal from our CamelFolder. */
        if (folder_changed && table_item != NULL)
            table_item->queue_show_cursor = FALSE;

        message_list_tree_model_thaw (message_list);

        /* it's required to thaw & freeze, to propagate changes */
        message_list_tree_model_freeze (message_list);

        message_list_set_selected (message_list, selected);

        g_ptr_array_unref (selected);

        /* Show the cursor unless we're responding to a
         * "folder-changed" signal from our CamelFolder. */
        if (folder_changed && table_item != NULL)
            table_item->queue_show_cursor = FALSE;

        message_list_tree_model_thaw (message_list);
    if (!saveuid && message_list->cursor_uid && g_hash_table_lookup (message_list->uid_nodemap, message_list->cursor_uid)) {
        /* this makes sure a visible node is selected, like when
         * collapsing all nodes and a children had been selected
        */
        saveuid = g_strdup (message_list->cursor_uid);
    }

    if (saveuid) {
        GNode *node;

        node = g_hash_table_lookup (
            message_list->uid_nodemap, saveuid);
        if (node == NULL) {
            g_free (message_list->cursor_uid);
            message_list->cursor_uid = NULL;
            g_signal_emit (
                message_list,
                signals[MESSAGE_SELECTED], 0, NULL);
        } else {
            ETree *tree = E_TREE (message_list);
            GNode *parent = node;

            while ((parent = parent->parent) != NULL) {
                if (!e_tree_node_is_expanded (tree, parent))
                    node = parent;
            }

            message_list_tree_model_freeze (message_list);

            e_tree_set_cursor (E_TREE (message_list), node);

            /* Show the cursor unless we're responding to a
             * "folder-changed" signal from our CamelFolder. */
            if (folder_changed && table_item != NULL)
                table_item->queue_show_cursor = FALSE;

            message_list_tree_model_thaw (message_list);
        }
        g_free (saveuid);
    } else if (message_list->cursor_uid && !g_hash_table_lookup (message_list->uid_nodemap, message_list->cursor_uid)) {
        g_free (message_list->cursor_uid);
        message_list->cursor_uid = NULL;
        g_signal_emit (
            message_list,
            signals[MESSAGE_SELECTED], 0, NULL);
    }

#ifdef TIMEIT
    gettimeofday (&end, NULL);
    diff = end.tv_sec * 1000 + end.tv_usec / 1000;
    diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
    printf ("Building tree took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
#endif
}

/* this is about 20% faster than build_subtree_diff,
 * entirely because e_tree_model_node_insert (xx, -1 xx)
 * is faster than inserting to the right row :( */
/* Otherwise, this code would probably go as it does the same thing essentially */
static void
build_subtree (MessageList *message_list,
               GNode *parent,
               CamelFolderThreadNode *c,
               gint *row)
{
    GNode *node;

    while (c) {
        /* phantom nodes no longer allowed */
        if (!c->message) {
            g_warning ("c->message shouldn't be NULL\n");
            c = c->next;
            continue;
        }

        node = ml_uid_nodemap_insert (
            message_list,
            (CamelMessageInfo *) c->message, parent, -1);

        if (c->child) {
            build_subtree (message_list, node, c->child, row);
        }
        c = c->next;
    }
}

/* compares a thread tree node with the etable tree node to see if they point to
 * the same object */
static gint
node_equal (ETreeModel *etm,
            GNode *ap,
            CamelFolderThreadNode *bp)
{
    if (bp->message && strcmp (camel_message_info_uid (ap->data), camel_message_info_uid (bp->message)) == 0)
        return 1;

    return 0;
}

/* adds a single node, retains save state, and handles adding children if required */
static void
add_node_diff (MessageList *message_list,
               GNode *parent,
               GNode *node,
               CamelFolderThreadNode *c,
               gint *row,
               gint myrow)
{
    CamelMessageInfo *info;
    GNode *new_node;

    g_return_if_fail (c->message != NULL);

    /* XXX Casting away constness. */
    info = (CamelMessageInfo *) c->message;

    /* we just update the hashtable key */
    ml_uid_nodemap_remove (message_list, info);
    new_node = ml_uid_nodemap_insert (message_list, info, parent, myrow);
    (*row)++;

    if (c->child) {
        build_subtree_diff (
            message_list, new_node, NULL, c->child, row);
    }
}

/* removes node, children recursively and all associated data */
static void
remove_node_diff (MessageList *message_list,
                  GNode *node,
                  gint depth)
{
    ETreePath cp, cn;
    CamelMessageInfo *info;

    t (printf ("Removing node: %s\n", (gchar *) node->data));

    /* we depth-first remove all node data's ... */
    cp = g_node_first_child (node);
    while (cp) {
        cn = g_node_next_sibling (cp);
        remove_node_diff (message_list, cp, depth + 1);
        cp = cn;
    }

    /* and the rowid entry - if and only if it is referencing this node */
    info = node->data;

    /* and only at the toplevel, remove the node (etree should optimise this remove somewhat) */
    if (depth == 0)
        message_list_tree_model_remove (message_list, node);

    g_return_if_fail (info);
    ml_uid_nodemap_remove (message_list, info);
}

/* applies a new tree structure to an existing tree, but only by changing things
 * that have changed */
static void
build_subtree_diff (MessageList *message_list,
                    GNode *parent,
                    GNode *node,
                    CamelFolderThreadNode *c,
                    gint *row)
{
    ETreeModel *tree_model;
    GNode *ap, *ai, *at, *tmp;
    CamelFolderThreadNode *bp, *bi, *bt;
    gint i, j, myrow = 0;

    tree_model = E_TREE_MODEL (message_list);

    ap = node;
    bp = c;

    while (ap || bp) {
        t (printf ("Processing row: %d (subtree row %d)\n", *row, myrow));
        if (ap == NULL) {
            t (printf ("out of old nodes\n"));
            /* ran out of old nodes - remaining nodes are added */
            add_node_diff (
                message_list, parent, ap, bp, row, myrow);
            myrow++;
            bp = bp->next;
        } else if (bp == NULL) {
            t (printf ("out of new nodes\n"));
            /* ran out of new nodes - remaining nodes are removed */
            tmp = g_node_next_sibling (ap);
            remove_node_diff (message_list, ap, 0);
            ap = tmp;
        } else if (node_equal (tree_model, ap, bp)) {
            *row = (*row)+1;
            myrow++;
            tmp = g_node_first_child (ap);
            /* make child lists match (if either has one) */
            if (bp->child || tmp) {
                build_subtree_diff (
                    message_list, ap, tmp, bp->child, row);
            }
            ap = g_node_next_sibling (ap);
            bp = bp->next;
        } else {
            t (printf ("searching for matches\n"));
            /* we have to scan each side for a match */
            bi = bp->next;
            ai = g_node_next_sibling (ap);
            for (i = 1; bi != NULL; i++,bi = bi->next) {
                if (node_equal (tree_model, ap, bi))
                    break;
            }
            for (j = 1; ai != NULL; j++,ai = g_node_next_sibling (ai)) {
                if (node_equal (tree_model, ai, bp))
                    break;
            }
            if (i < j) {
                /* smaller run of new nodes - must be nodes to add */
                if (bi) {
                    bt = bp;
                    while (bt != bi) {
                        t (printf ("adding new node 0\n"));
                        add_node_diff (
                            message_list, parent, NULL, bt, row, myrow);
                        myrow++;
                        bt = bt->next;
                    }
                    bp = bi;
                } else {
                    t (printf ("adding new node 1\n"));
                    /* no match in new nodes, add one, try next */
                    add_node_diff (
                        message_list, parent, NULL, bp, row, myrow);
                    myrow++;
                    bp = bp->next;
                }
            } else {
                /* bigger run of old nodes - must be nodes to remove */
                if (ai) {
                    at = ap;
                    while (at != NULL && at != ai) {
                        t (printf ("removing old node 0\n"));
                        tmp = g_node_next_sibling (at);
                        remove_node_diff (message_list, at, 0);
                        at = tmp;
                    }
                    ap = ai;
                } else {
                    t (printf ("adding new node 2\n"));
                    /* didn't find match in old nodes, must be new node? */
                    add_node_diff (
                        message_list, parent, NULL, bp, row, myrow);
                    myrow++;
                    bp = bp->next;
                }
            }
        }
    }
}

static void
build_flat (MessageList *message_list,
            GPtrArray *summary)
{
    gchar *saveuid = NULL;
    gint i;
    GPtrArray *selected;
#ifdef TIMEIT
    struct timeval start, end;
    gulong diff;

    printf ("Building flat\n");
    gettimeofday (&start, NULL);
#endif

    if (message_list->cursor_uid != NULL)
        saveuid = find_next_selectable (message_list);

    selected = message_list_get_selected (message_list);

    message_list_tree_model_freeze (message_list);

    clear_tree (message_list, FALSE);

    for (i = 0; i < summary->len; i++) {
        CamelMessageInfo *info = summary->pdata[i];

        ml_uid_nodemap_insert (message_list, info, NULL, -1);
    }

    message_list_tree_model_thaw (message_list);

    message_list_set_selected (message_list, selected);

    g_ptr_array_unref (selected);

    if (saveuid) {
        GNode *node;

        node = g_hash_table_lookup (
            message_list->uid_nodemap, saveuid);
        if (node == NULL) {
            g_free (message_list->cursor_uid);
            message_list->cursor_uid = NULL;
            g_signal_emit (
                message_list,
                signals[MESSAGE_SELECTED], 0, NULL);
        } else {
            e_tree_set_cursor (E_TREE (message_list), node);
        }
        g_free (saveuid);
    }

#ifdef TIMEIT
    gettimeofday (&end, NULL);
    diff = end.tv_sec * 1000 + end.tv_usec / 1000;
    diff -= start.tv_sec * 1000 + start.tv_usec / 1000;
    printf ("Building flat took %ld.%03ld seconds\n", diff / 1000, diff % 1000);
#endif

}

static void
message_list_change_first_visible_parent (MessageList *message_list,
                                          GNode *node)
{
    ETreeModel *tree_model;
    GNode *first_visible = NULL;

    tree_model = E_TREE_MODEL (message_list);

    while (node != NULL && (node = node->parent) != NULL) {
        if (!e_tree_node_is_expanded (E_TREE (message_list), node))
            first_visible = node;
    }

    if (first_visible != NULL) {
        e_tree_model_pre_change (tree_model);
        e_tree_model_node_data_changed (tree_model, first_visible);
    }
}

static CamelFolderChangeInfo *
mail_folder_hide_by_flag (CamelFolder *folder,
                          MessageList *message_list,
                          CamelFolderChangeInfo *changes,
                          gint flag)
{
    CamelFolderChangeInfo *newchanges;
    CamelMessageInfo *info;
    gint i;

    newchanges = camel_folder_change_info_new ();

    for (i = 0; i < changes->uid_changed->len; i++) {
        GNode *node;
        guint32 flags;

        node = g_hash_table_lookup (
            message_list->uid_nodemap,
            changes->uid_changed->pdata[i]);
        info = camel_folder_get_message_info (
            folder, changes->uid_changed->pdata[i]);
        if (info)
            flags = camel_message_info_flags (info);

        if (node != NULL && info != NULL && (flags & flag) != 0)
            camel_folder_change_info_remove_uid (
                newchanges, changes->uid_changed->pdata[i]);
        else if (node == NULL && info != NULL && (flags & flag) == 0)
            camel_folder_change_info_add_uid (
                newchanges, changes->uid_changed->pdata[i]);
        else
            camel_folder_change_info_change_uid (
                newchanges, changes->uid_changed->pdata[i]);
        if (info)
            camel_folder_free_message_info (folder, info);
    }

    if (newchanges->uid_added->len > 0 || newchanges->uid_removed->len > 0) {
        for (i = 0; i < changes->uid_added->len; i++)
            camel_folder_change_info_add_uid (
                newchanges, changes->uid_added->pdata[i]);
        for (i = 0; i < changes->uid_removed->len; i++)
            camel_folder_change_info_remove_uid (
                newchanges, changes->uid_removed->pdata[i]);
    } else {
        camel_folder_change_info_clear (newchanges);
        camel_folder_change_info_cat (newchanges, changes);
    }

    return newchanges;
}

static void
message_list_folder_changed (CamelFolder *folder,
                             CamelFolderChangeInfo *changes,
                             MessageList *message_list)
{
    CamelFolderChangeInfo *altered_changes = NULL;
    ETreeModel *tree_model;
    gboolean need_list_regen = TRUE;
    gboolean hide_junk;
    gboolean hide_deleted;
    gint i;

    if (message_list->priv->destroyed)
        return;

    tree_model = E_TREE_MODEL (message_list);

    hide_junk = message_list_get_hide_junk (message_list, folder);
    hide_deleted = message_list_get_hide_deleted (message_list, folder);

    d (printf ("folder changed event, changes = %p\n", changes));
    if (changes != NULL) {
        for (i = 0; i < changes->uid_removed->len; i++)
            g_hash_table_remove (
                message_list->normalised_hash,
                changes->uid_removed->pdata[i]);

        /* Check if the hidden state has changed.
         * If so, modify accordingly and regenerate. */
        if (hide_junk || hide_deleted)
            altered_changes = mail_folder_hide_by_flag (
                folder, message_list, changes,
                (hide_junk ? CAMEL_MESSAGE_JUNK : 0) |
                (hide_deleted ? CAMEL_MESSAGE_DELETED : 0));
        else {
            altered_changes = camel_folder_change_info_new ();
            camel_folder_change_info_cat (altered_changes, changes);
        }

        if (altered_changes->uid_added->len == 0 && altered_changes->uid_removed->len == 0 && altered_changes->uid_changed->len < 100) {
            for (i = 0; i < altered_changes->uid_changed->len; i++) {
                GNode *node;

                node = g_hash_table_lookup (
                    message_list->uid_nodemap,
                    altered_changes->uid_changed->pdata[i]);
                if (node) {
                    e_tree_model_pre_change (tree_model);
                    e_tree_model_node_data_changed (tree_model, node);

                    message_list_change_first_visible_parent (message_list, node);
                }
            }

            g_signal_emit (
                message_list,
                signals[MESSAGE_LIST_BUILT], 0);

            need_list_regen = FALSE;
        }
    }

    if (need_list_regen)
        mail_regen_list (message_list, message_list->search, TRUE);

    if (altered_changes != NULL)
        camel_folder_change_info_free (altered_changes);
}

CamelFolder *
message_list_ref_folder (MessageList *message_list)
{
    CamelFolder *folder = NULL;

    /* XXX Do we need a property lock to guard this? */

    g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);

    if (message_list->priv->folder != NULL)
        folder = g_object_ref (message_list->priv->folder);

    return folder;
}

/**
 * message_list_set_folder:
 * @message_list: Message List widget
 * @folder: folder backend to be set
 *
 * Sets @folder to be the backend folder for @message_list.
 **/
void
message_list_set_folder (MessageList *message_list,
                         CamelFolder *folder)
{
    /* XXX Do we need a property lock to guard this? */

    g_return_if_fail (IS_MESSAGE_LIST (message_list));

    if (folder == message_list->priv->folder)
        return;

    if (folder != NULL) {
        g_return_if_fail (CAMEL_IS_FOLDER (folder));
        g_object_ref (folder);
    }

    g_free (message_list->search);
    message_list->search = NULL;

    g_free (message_list->frozen_search);
    message_list->frozen_search = NULL;

    if (message_list->seen_id) {
        g_source_remove (message_list->seen_id);
        message_list->seen_id = 0;
    }

    /* reset the normalised sort performance hack */
    g_hash_table_remove_all (message_list->normalised_hash);

    mail_regen_cancel (message_list);

    if (message_list->priv->folder != NULL)
        save_tree_state (message_list, message_list->priv->folder);

    message_list_tree_model_freeze (message_list);
    clear_tree (message_list, TRUE);
    message_list_tree_model_thaw (message_list);

    /* remove the cursor activate idle handler */
    if (message_list->idle_id != 0) {
        g_source_remove (message_list->idle_id);
        message_list->idle_id = 0;
    }

    if (message_list->priv->folder != NULL) {
        g_signal_handler_disconnect (
            message_list->priv->folder,
            message_list->priv->folder_changed_handler_id);
        message_list->priv->folder_changed_handler_id = 0;

        if (message_list->uid_nodemap != NULL)
            g_hash_table_foreach (
                message_list->uid_nodemap,
                (GHFunc) clear_info, message_list);

        g_clear_object (&message_list->priv->folder);
    }

    if (message_list->thread_tree != NULL) {
        camel_folder_thread_messages_unref (message_list->thread_tree);
        message_list->thread_tree = NULL;
    }

    g_free (message_list->cursor_uid);
    message_list->cursor_uid = NULL;

    /* Always emit message-selected, event when an account node
     * (folder == NULL) is selected, so that views know what happened and
     * can stop all running operations etc. */
    g_signal_emit (message_list, signals[MESSAGE_SELECTED], 0, NULL);

    if (folder != NULL) {
        CamelStore *store;
        gboolean non_trash_folder;
        gint strikeout_col;
        ECell *cell;
        gulong handler_id;

        message_list->priv->folder = folder;
        message_list->just_set_folder = TRUE;

        store = camel_folder_get_parent_store (folder);

        non_trash_folder =
            ((store->flags & CAMEL_STORE_VTRASH) == 0) ||
            ((folder->folder_flags & CAMEL_FOLDER_IS_TRASH) == 0);

        /* Setup the strikeout effect for non-trash folders */
        strikeout_col = non_trash_folder ? COL_DELETED : -1;

        cell = e_table_extras_get_cell (message_list->extras, "render_date");
        g_object_set (cell, "strikeout_column", strikeout_col, NULL);

        cell = e_table_extras_get_cell (message_list->extras, "render_text");
        g_object_set (cell, "strikeout_column", strikeout_col, NULL);

        cell = e_table_extras_get_cell (message_list->extras, "render_size");
        g_object_set (cell, "strikeout_column", strikeout_col, NULL);

        cell = e_table_extras_get_cell (message_list->extras, "render_composite_from");
        composite_cell_set_strike_col (cell, strikeout_col);

        cell = e_table_extras_get_cell (message_list->extras, "render_composite_to");
        composite_cell_set_strike_col (cell, strikeout_col);

        /* Build the etree suitable for this folder */
        message_list_setup_etree (message_list);

        handler_id = g_signal_connect (
            folder, "changed",
            G_CALLBACK (message_list_folder_changed),
            message_list);
        message_list->priv->folder_changed_handler_id = handler_id;

        if (message_list->frozen == 0)
            mail_regen_list (message_list, message_list->search, FALSE);
    }
}

GtkTargetList *
message_list_get_copy_target_list (MessageList *message_list)
{
    g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);

    return message_list->priv->copy_target_list;
}

GtkTargetList *
message_list_get_paste_target_list (MessageList *message_list)
{
    g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);

    return message_list->priv->paste_target_list;
}

void
message_list_set_expanded_default (MessageList *message_list,
                                   gboolean expanded_default)
{
    g_return_if_fail (IS_MESSAGE_LIST (message_list));

    message_list->priv->expanded_default = expanded_default;
}

gboolean
message_list_get_group_by_threads (MessageList *message_list)
{
    g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);

    return message_list->priv->group_by_threads;
}

void
message_list_set_group_by_threads (MessageList *message_list,
                                   gboolean group_by_threads)
{
    g_return_if_fail (IS_MESSAGE_LIST (message_list));

    if (group_by_threads == message_list->priv->group_by_threads)
        return;

    message_list->priv->group_by_threads = group_by_threads;

    g_object_notify (G_OBJECT (message_list), "group-by-threads");

    /* Changing this property triggers a message list regen. */
    if (message_list->frozen == 0)
        mail_regen_list (message_list, message_list->search, FALSE);
}

gboolean
message_list_get_show_deleted (MessageList *message_list)
{
    g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);

    return message_list->priv->show_deleted;
}

void
message_list_set_show_deleted (MessageList *message_list,
                               gboolean show_deleted)
{
    g_return_if_fail (IS_MESSAGE_LIST (message_list));

    if (show_deleted == message_list->priv->show_deleted)
        return;

    message_list->priv->show_deleted = show_deleted;

    g_object_notify (G_OBJECT (message_list), "show-deleted");

    /* Changing this property triggers a message list regen. */
    if (message_list->frozen == 0)
        mail_regen_list (message_list, message_list->search, FALSE);
}

gboolean
message_list_get_thread_latest (MessageList *message_list)
{
    g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);

    return message_list->priv->thread_latest;
}

void
message_list_set_thread_latest (MessageList *message_list,
                                gboolean thread_latest)
{
    g_return_if_fail (IS_MESSAGE_LIST (message_list));

    if (thread_latest == message_list->priv->thread_latest)
        return;

    message_list->priv->thread_latest = thread_latest;

    g_object_notify (G_OBJECT (message_list), "thread-latest");
}

gboolean
message_list_get_thread_subject (MessageList *message_list)
{
    g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);

    return message_list->priv->thread_subject;
}

void
message_list_set_thread_subject (MessageList *message_list,
                                 gboolean thread_subject)
{
    g_return_if_fail (IS_MESSAGE_LIST (message_list));

    if (thread_subject == message_list->priv->thread_subject)
        return;

    message_list->priv->thread_subject = thread_subject;

    g_object_notify (G_OBJECT (message_list), "thread-subject");
}

static gboolean
on_cursor_activated_idle (gpointer data)
{
    MessageList *message_list = data;
    ESelectionModel *esm;
    gint selected;

    esm = e_tree_get_selection_model (E_TREE (message_list));
    selected = e_selection_model_selected_count (esm);

    if (selected == 1 && message_list->cursor_uid) {
        d (printf ("emitting cursor changed signal, for uid %s\n", message_list->cursor_uid));
        g_signal_emit (
            message_list,
            signals[MESSAGE_SELECTED], 0,
            message_list->cursor_uid);
    } else {
        g_signal_emit (
            message_list,
            signals[MESSAGE_SELECTED], 0,
            NULL);
    }

    message_list->idle_id = 0;
    return FALSE;
}

static void
on_cursor_activated_cmd (ETree *tree,
                         gint row,
                         GNode *node,
                         gpointer user_data)
{
    MessageList *message_list = MESSAGE_LIST (user_data);
    const gchar *new_uid;

    if (node == NULL)
        new_uid = NULL;
    else
        new_uid = get_message_uid (message_list, node);

    /* Do not check the cursor_uid and the new_uid values, because the
    * selected item (set in on_selection_changed_cmd) can be different
    * from the one with a cursor (when selecting with Ctrl, for example).
    * This has a little side-effect, when keeping list it that state,
    * then changing folders forth and back will select and move cursor
    * to that selected item. Does anybody consider it as a bug? */
    if ((message_list->cursor_uid == NULL && new_uid == NULL)
        || (message_list->last_sel_single && message_list->cursor_uid != NULL && new_uid != NULL))
        return;

    g_free (message_list->cursor_uid);
    message_list->cursor_uid = g_strdup (new_uid);

    if (!message_list->idle_id) {
        message_list->idle_id =
            g_idle_add_full (
                G_PRIORITY_LOW, on_cursor_activated_idle,
                message_list, NULL);
    }
}

static void
on_selection_changed_cmd (ETree *tree,
                          MessageList *message_list)
{
    GPtrArray *uids;
    const gchar *newuid;
    GNode *cursor;

    /* not sure if we could just ignore this for the cursor, i think sometimes you
     * only get a selection changed when you should also get a cursor activated? */
    uids = message_list_get_selected (message_list);

    if (uids->len == 1)
        newuid = g_ptr_array_index (uids, 0);
    else if ((cursor = e_tree_get_cursor (tree)))
        newuid = (gchar *) camel_message_info_uid (cursor->data);
    else
        newuid = NULL;

    /* If the selection isn't empty, then we ignore the no-uid check, since this event
     * is also used for other updating.  If it is empty, it might just be a setup event
     * from etree which we do need to ignore */
    if ((newuid == NULL && message_list->cursor_uid == NULL && uids->len == 0) ||
        (message_list->last_sel_single && uids->len == 1 && newuid != NULL && message_list->cursor_uid != NULL && !strcmp (message_list->cursor_uid, newuid))) {
        /* noop */
    } else {
        g_free (message_list->cursor_uid);
        message_list->cursor_uid = g_strdup (newuid);
        if (message_list->idle_id == 0)
            message_list->idle_id = g_idle_add_full (
                G_PRIORITY_LOW,
                on_cursor_activated_idle,
                message_list, NULL);
    }

    message_list->last_sel_single = uids->len == 1;

    g_ptr_array_unref (uids);
}

static gint
on_click (ETree *tree,
          gint row,
          GNode *node,
          gint col,
          GdkEvent *event,
          MessageList *list)
{
    CamelFolder *folder;
    CamelMessageInfo *info;
    gboolean folder_is_trash;
    const gchar *uid;
    gint flag = 0;
    guint32 flags;

    if (col == COL_MESSAGE_STATUS)
        flag = CAMEL_MESSAGE_SEEN;
    else if (col == COL_FLAGGED)
        flag = CAMEL_MESSAGE_FLAGGED;
    else if (col != COL_FOLLOWUP_FLAG_STATUS)
        return FALSE;

    if (!(info = get_message_info (list, node)))
        return FALSE;

    folder = message_list_ref_folder (list);
    g_return_val_if_fail (folder != NULL, FALSE);

    if (col == COL_FOLLOWUP_FLAG_STATUS) {
        const gchar *tag, *cmp;

        tag = camel_message_info_user_tag (info, "follow-up");
        cmp = camel_message_info_user_tag (info, "completed-on");
        if (tag && tag[0]) {
            if (cmp && cmp[0]) {
                camel_message_info_set_user_tag (info, "follow-up", NULL);
                camel_message_info_set_user_tag (info, "due-by", NULL);
                camel_message_info_set_user_tag (info, "completed-on", NULL);
            } else {
                gchar *text;

                text = camel_header_format_date (time (NULL), 0);
                camel_message_info_set_user_tag (info, "completed-on", text);
                g_free (text);
            }
        } else {
            /* default follow-up flag name to use when clicked in the message list column */
            camel_message_info_set_user_tag (info, "follow-up", _("Follow-up"));
            camel_message_info_set_user_tag (info, "completed-on", NULL);
        }

        return TRUE;
    }

    flags = camel_message_info_flags (info);

    folder_is_trash =
        ((folder->folder_flags & CAMEL_FOLDER_IS_TRASH) != 0);

    /* If a message was marked as deleted and the user flags it as
     * important or unread in a non-Trash folder, then undelete the
     * message.  We avoid automatically undeleting messages while
     * viewing a Trash folder because it would cause the message to
     * suddenly disappear from the message list, which is confusing
     * and alarming to the user. */
    if (!folder_is_trash && flags & CAMEL_MESSAGE_DELETED) {
        if (col == COL_FLAGGED && !(flags & CAMEL_MESSAGE_FLAGGED))
            flag |= CAMEL_MESSAGE_DELETED;

        if (col == COL_MESSAGE_STATUS && (flags & CAMEL_MESSAGE_SEEN))
            flag |= CAMEL_MESSAGE_DELETED;
    }

    uid = camel_message_info_uid (info);
    camel_folder_set_message_flags (folder, uid, flag, ~flags);

    /* Notify the folder tree model that the user has marked a message
     * as unread so it doesn't mistake the event as new mail arriving. */
    if (col == COL_MESSAGE_STATUS && (flags & CAMEL_MESSAGE_SEEN)) {
        EMFolderTreeModel *model;

        model = em_folder_tree_model_get_default ();
        em_folder_tree_model_user_marked_unread (model, folder, 1);
    }

    if (flag == CAMEL_MESSAGE_SEEN && list->seen_id) {
        g_source_remove (list->seen_id);
        list->seen_id = 0;
    }

    g_object_unref (folder);

    return TRUE;
}

struct _ml_selected_data {
    MessageList *message_list;
    GPtrArray *uids;
};

static void
ml_getselected_cb (GNode *node,
                   gpointer user_data)
{
    struct _ml_selected_data *data = user_data;
    const gchar *uid;

    if (G_NODE_IS_ROOT (node))
        return;

    uid = get_message_uid (data->message_list, node);
    g_return_if_fail (uid != NULL);
    g_ptr_array_add (data->uids, g_strdup (uid));
}

GPtrArray *
message_list_get_selected (MessageList *message_list)
{
    CamelFolder *folder;

    struct _ml_selected_data data = {
        message_list,
        g_ptr_array_new ()
    };

    g_ptr_array_set_free_func (data.uids, (GDestroyNotify) g_free);

    e_tree_selected_path_foreach (
        E_TREE (message_list),
        (ETreeForeachFunc) ml_getselected_cb, &data);

    folder = message_list_ref_folder (message_list);

    if (folder != NULL && data.uids->len > 0)
        camel_folder_sort_uids (folder, data.uids);

    g_clear_object (&folder);

    return data.uids;
}

void
message_list_set_selected (MessageList *message_list,
                           GPtrArray *uids)
{
    gint i;
    ETreeSelectionModel *etsm;
    GNode *node;
    GPtrArray *paths = g_ptr_array_new ();

    etsm = (ETreeSelectionModel *)
        e_tree_get_selection_model (E_TREE (message_list));
    for (i = 0; i < uids->len; i++) {
        node = g_hash_table_lookup (
            message_list->uid_nodemap, uids->pdata[i]);
        if (node != NULL)
            g_ptr_array_add (paths, node);
    }

    e_tree_selection_model_select_paths (etsm, paths);
    g_ptr_array_free (paths, TRUE);
}

struct ml_sort_uids_data {
    gchar *uid;
    gint row;
};

static gint
ml_sort_uids_cb (gconstpointer a,
                 gconstpointer b)
{
    struct ml_sort_uids_data * const *pdataA = a;
    struct ml_sort_uids_data * const *pdataB = b;

    return (* pdataA)->row - (* pdataB)->row;
}

void
message_list_sort_uids (MessageList *message_list,
                        GPtrArray *uids)
{
    struct ml_sort_uids_data *data;
    GPtrArray *array;
    GNode *node;
    ETreeTableAdapter *adapter;
    gint ii;

    g_return_if_fail (message_list != NULL);
    g_return_if_fail (IS_MESSAGE_LIST (message_list));
    g_return_if_fail (uids != NULL);

    if (uids->len <= 1)
        return;

    adapter = e_tree_get_table_adapter (E_TREE (message_list));

    array = g_ptr_array_new_full (uids->len, g_free);

    for (ii = 0; ii < uids->len; ii++) {
        data = g_new0 (struct ml_sort_uids_data, 1);
        data->uid = g_ptr_array_index (uids, ii);

        node = g_hash_table_lookup (message_list->uid_nodemap, data->uid);
        if (node != NULL)
            data->row = e_tree_table_adapter_row_of_node (adapter, node);
        else
            data->row = ii;

        g_ptr_array_add (array, data);
    }

    g_ptr_array_sort (array, ml_sort_uids_cb);

    for (ii = 0; ii < uids->len; ii++) {
        data = g_ptr_array_index (array, ii);

        uids->pdata[ii] = data->uid;
    }

    g_ptr_array_free (array, TRUE);
}

struct ml_count_data {
    MessageList *message_list;
    guint count;
};

static void
ml_getcount_cb (GNode *node,
                gpointer user_data)
{
    struct ml_count_data *data = user_data;

    if (!G_NODE_IS_ROOT (node))
        data->count++;
}

guint
message_list_count (MessageList *message_list)
{
    struct ml_count_data data = { message_list, 0 };

    g_return_val_if_fail (IS_MESSAGE_LIST (message_list), 0);

    e_tree_path_foreach (
        E_TREE (message_list),
        (ETreeForeachFunc) ml_getcount_cb, &data);

    return data.count;
}

static void
ml_getselcount_cb (gint model_row,
                   gpointer user_data)
{
    struct ml_count_data *data = user_data;

    data->count++;
}

guint
message_list_selected_count (MessageList *message_list)
{
    struct ml_count_data data = { message_list, 0 };

    g_return_val_if_fail (IS_MESSAGE_LIST (message_list), 0);

    e_tree_selected_row_foreach (
        E_TREE (message_list), ml_getselcount_cb, &data);

    return data.count;
}

void
message_list_freeze (MessageList *message_list)
{
    message_list->frozen++;
}

void
message_list_thaw (MessageList *message_list)
{
    g_return_if_fail (message_list->frozen != 0);

    message_list->frozen--;
    if (message_list->frozen == 0) {
        const gchar *search;

        if (message_list->frozen_search != NULL)
            search = message_list->frozen_search;
        else
            search = message_list->search;

        mail_regen_list (message_list, search, FALSE);

        g_free (message_list->frozen_search);
        message_list->frozen_search = NULL;
    }
}

/* set whether we are in threaded view or flat view */
void
message_list_set_threaded_expand_all (MessageList *message_list)
{
    g_return_if_fail (IS_MESSAGE_LIST (message_list));

    if (message_list_get_group_by_threads (message_list)) {
        message_list->expand_all = 1;

        if (message_list->frozen == 0)
            mail_regen_list (
                message_list, message_list->search, FALSE);
    }
}

void
message_list_set_threaded_collapse_all (MessageList *message_list)
{
    g_return_if_fail (IS_MESSAGE_LIST (message_list));

    if (message_list_get_group_by_threads (message_list)) {
        message_list->collapse_all = 1;

        if (message_list->frozen == 0)
            mail_regen_list (
                message_list, message_list->search, FALSE);
    }
}

void
message_list_set_search (MessageList *message_list,
                         const gchar *search)
{
    if (search == NULL || search[0] == '\0')
        if (message_list->search == NULL || message_list->search[0] == '\0')
            return;

    if (search != NULL && message_list->search != NULL && strcmp (search, message_list->search) == 0)
        return;

    if (message_list->thread_tree) {
        camel_folder_thread_messages_unref (message_list->thread_tree);
        message_list->thread_tree = NULL;
    }

    if (message_list->frozen == 0)
        mail_regen_list (message_list, search, FALSE);
    else {
        g_free (message_list->frozen_search);
        message_list->frozen_search = g_strdup (search);
    }
}

struct sort_column_data {
    ETableCol *col;
    gboolean ascending;
};

struct sort_message_info_data {
    CamelMessageInfo *mi;
    GPtrArray *values; /* read values so far, in order of sort_array_data::sort_columns */
};

struct sort_array_data {
    MessageList *message_list;
    CamelFolder *folder;
    GPtrArray *sort_columns; /* struct sort_column_data in order of sorting */
    GHashTable *message_infos; /* uid -> struct sort_message_info_data */
    gpointer cmp_cache;
    GCancellable *cancellable;
};

static gint
cmp_array_uids (gconstpointer a,
                gconstpointer b,
                gpointer user_data)
{
    const gchar *uid1 = *(const gchar **) a;
    const gchar *uid2 = *(const gchar **) b;
    struct sort_array_data *sort_data = user_data;
    gint i, res = 0;
    struct sort_message_info_data *md1, *md2;

    g_return_val_if_fail (sort_data != NULL, 0);

    md1 = g_hash_table_lookup (sort_data->message_infos, uid1);
    md2 = g_hash_table_lookup (sort_data->message_infos, uid2);

    g_return_val_if_fail (md1 != NULL, 0);
    g_return_val_if_fail (md1->mi != NULL, 0);
    g_return_val_if_fail (md2 != NULL, 0);
    g_return_val_if_fail (md2->mi != NULL, 0);

    if (g_cancellable_is_cancelled (sort_data->cancellable))
        return 0;

    for (i = 0;
         res == 0
         && i < sort_data->sort_columns->len
         && !g_cancellable_is_cancelled (sort_data->cancellable);
         i++) {
        gpointer v1, v2;
        struct sort_column_data *scol = g_ptr_array_index (sort_data->sort_columns, i);

        if (md1->values->len <= i) {
            v1 = ml_tree_value_at_ex (NULL, NULL, scol->col->compare_col, md1->mi, sort_data->message_list);
            g_ptr_array_add (md1->values, v1);
        } else {
            v1 = g_ptr_array_index (md1->values, i);
        }

        if (md2->values->len <= i) {
            v2 = ml_tree_value_at_ex (NULL, NULL, scol->col->compare_col, md2->mi, sort_data->message_list);
            g_ptr_array_add (md2->values, v2);
        } else {
            v2 = g_ptr_array_index (md2->values, i);
        }

        if (v1 != NULL && v2 != NULL) {
            res = (*scol->col->compare) (v1, v2, sort_data->cmp_cache);
        } else if (v1 != NULL || v2 != NULL) {
            res = v1 == NULL ? -1 : 1;
        }

        if (!scol->ascending)
            res = res * (-1);
    }

    if (res == 0)
        res = camel_folder_cmp_uids (sort_data->folder, uid1, uid2);

    return res;
}

static void
free_message_info_data (gpointer uid,
                        struct sort_message_info_data *data,
                        struct sort_array_data *sort_data)
{
    if (data->values) {
        /* values in this array are not newly allocated, even ml_tree_value_at_ex
         * returns gpointer, not a gconstpointer */
        g_ptr_array_free (data->values, TRUE);
    }

    camel_folder_free_message_info (sort_data->folder, data->mi);
    g_free (data);
}

static void
ml_sort_uids_by_tree (MessageList *message_list,
                      GPtrArray *uids,
                      GCancellable *cancellable)
{
    ETreeTableAdapter *adapter;
    ETableSortInfo *sort_info;
    ETableHeader *full_header;
    CamelFolder *folder;
    struct sort_array_data sort_data;
    guint i, len;

    if (g_cancellable_is_cancelled (cancellable))
        return;

    g_return_if_fail (uids != NULL);

    folder = message_list_ref_folder (message_list);
    g_return_if_fail (folder != NULL);

    adapter = e_tree_get_table_adapter (E_TREE (message_list));
    g_return_if_fail (adapter != NULL);

    sort_info = e_tree_table_adapter_get_sort_info (adapter);
    full_header = e_tree_table_adapter_get_header (adapter);

    if (!sort_info || uids->len == 0 || !full_header || e_table_sort_info_sorting_get_count (sort_info) == 0) {
        camel_folder_sort_uids (folder, uids);
        g_object_unref (folder);
        return;
    }

    len = e_table_sort_info_sorting_get_count (sort_info);

    sort_data.message_list = message_list;
    sort_data.folder = folder;
    sort_data.sort_columns = g_ptr_array_sized_new (len);
    sort_data.message_infos = g_hash_table_new (g_str_hash, g_str_equal);
    sort_data.cmp_cache = e_table_sorting_utils_create_cmp_cache ();
    sort_data.cancellable = cancellable;

    for (i = 0;
         i < len
         && !g_cancellable_is_cancelled (cancellable);
         i++) {
        ETableSortColumn scol;
        struct sort_column_data *data = g_new0 (struct sort_column_data, 1);

        scol = e_table_sort_info_sorting_get_nth (sort_info, i);

        data->ascending = scol.ascending;
        data->col = e_table_header_get_column_by_col_idx (full_header, scol.column);
        if (data->col == NULL)
            data->col = e_table_header_get_column (full_header, e_table_header_count (full_header) - 1);

        g_ptr_array_add (sort_data.sort_columns, data);
    }

    camel_folder_summary_prepare_fetch_all (folder->summary, NULL);

    for (i = 0;
         i < uids->len
         && !g_cancellable_is_cancelled (cancellable);
         i++) {
        gchar *uid;
        CamelMessageInfo *mi;
        struct sort_message_info_data *md;

        uid = g_ptr_array_index (uids, i);
        mi = camel_folder_get_message_info (folder, uid);
        if (mi == NULL) {
            g_warning (
                "%s: Cannot find uid '%s' in folder '%s'",
                G_STRFUNC, uid,
                camel_folder_get_full_name (folder));
            continue;
        }

        md = g_new0 (struct sort_message_info_data, 1);
        md->mi = mi;
        md->values = g_ptr_array_sized_new (len);

        g_hash_table_insert (sort_data.message_infos, uid, md);
    }

    if (!g_cancellable_is_cancelled (cancellable))
        g_qsort_with_data (
            uids->pdata,
            uids->len,
            sizeof (gpointer),
            cmp_array_uids,
            &sort_data);

    camel_folder_summary_unlock (folder->summary, CAMEL_FOLDER_SUMMARY_SUMMARY_LOCK);

    /* FIXME Teach the hash table to destroy its own data. */
    g_hash_table_foreach (
        sort_data.message_infos,
        (GHFunc) free_message_info_data,
        &sort_data);
    g_hash_table_destroy (sort_data.message_infos);

    g_ptr_array_foreach (sort_data.sort_columns, (GFunc) g_free, NULL);
    g_ptr_array_free (sort_data.sort_columns, TRUE);

    e_table_sorting_utils_free_cmp_cache (sort_data.cmp_cache);

    g_object_unref (folder);
}

static void
message_list_regen_tweak_search_results (MessageList *message_list,
                                         GPtrArray *search_results,
                                         CamelFolder *folder,
                                         gboolean folder_changed,
                                         gboolean show_deleted,
                                         gboolean show_junk)
{
    CamelMessageInfo *info;
    CamelMessageFlags flags;
    const gchar *uid;
    gboolean needs_tweaking;
    gboolean uid_is_deleted;
    gboolean uid_is_junk;
    gboolean add_uid;
    guint ii;

    /* If we're responding to a "folder-changed" signal, then the
     * displayed message may not be included in the search results.
     * Include the displayed message anyway so it doesn't suddenly
     * disappear while the user is reading it. */
    needs_tweaking =
        (folder_changed && message_list->cursor_uid != NULL);

    if (!needs_tweaking)
        return;

    uid = message_list->cursor_uid;

    /* Scan the search results for a particular UID.
     * If found then the results don't need tweaked. */
    for (ii = 0; ii < search_results->len; ii++) {
        if (g_str_equal (uid, search_results->pdata[ii]))
            return;
    }

    info = camel_folder_get_message_info (folder, uid);

    /* XXX Should we emit a runtime warning here? */
    if (info == NULL)
        return;

    flags = camel_message_info_flags (info);
    uid_is_deleted = ((flags & CAMEL_MESSAGE_DELETED) != 0);
    uid_is_junk = ((flags & CAMEL_MESSAGE_JUNK) != 0);

    if (!folder_store_supports_vjunk_folder (folder))
        uid_is_junk = FALSE;

    add_uid =
        (!uid_is_junk || show_junk) &&
        (!uid_is_deleted || show_deleted);

    if (add_uid)
        g_ptr_array_add (
            search_results,
            (gpointer) camel_pstring_strdup (uid));

    camel_folder_free_message_info (folder, info);
}

static void
message_list_regen_thread (GSimpleAsyncResult *simple,
                           GObject *source_object,
                           GCancellable *cancellable)
{
    MessageList *message_list;
    RegenData *regen_data;
    GPtrArray *uids, *searchuids = NULL;
    CamelMessageInfo *info;
    GNode *cursor;
    ETree *tree;
    GString *expr;
    gboolean hide_deleted;
    gboolean hide_junk;
    GError *local_error = NULL;

    message_list = MESSAGE_LIST (source_object);
    regen_data = g_simple_async_result_get_op_res_gpointer (simple);

    if (g_cancellable_is_cancelled (cancellable))
        return;

    hide_junk = message_list_get_hide_junk (
        message_list, regen_data->folder);
    hide_deleted = message_list_get_hide_deleted (
        message_list, regen_data->folder);

    tree = E_TREE (message_list);
    cursor = e_tree_get_cursor (tree);
    if (cursor != NULL)
        regen_data->last_row =
            e_tree_table_adapter_row_of_node (
            e_tree_get_table_adapter (tree), cursor);

    /* Construct the search expression. */

    expr = g_string_new ("");

    if (hide_deleted && hide_junk) {
        g_string_append_printf (
            expr, "(match-all (and %s %s))",
            EXCLUDE_DELETED_MESSAGES_EXPR,
            EXCLUDE_JUNK_MESSAGES_EXPR);
    } else if (hide_deleted) {
        g_string_append_printf (
            expr, "(match-all %s)",
            EXCLUDE_DELETED_MESSAGES_EXPR);
    } else if (hide_junk) {
        g_string_append_printf (
            expr, "(match-all %s)",
            EXCLUDE_JUNK_MESSAGES_EXPR);
    }

    if (regen_data->search != NULL) {
        if (expr->len == 0) {
            g_string_assign (expr, regen_data->search);
        } else {
            g_string_prepend (expr, "(and ");
            g_string_append_c (expr, ' ');
            g_string_append (expr, regen_data->search);
            g_string_append_c (expr, ')');
        }
    }

    /* Execute the search. */

    if (expr->len == 0) {
        uids = camel_folder_get_uids (regen_data->folder);
    } else {
        uids = camel_folder_search_by_expression (
            regen_data->folder, expr->str,
            cancellable, &local_error);

        /* XXX This indicates we need to use a different
         *     "free UID" function for some dumb reason. */
        searchuids = uids;

        if (uids != NULL)
            message_list_regen_tweak_search_results (
                message_list, uids,
                regen_data->folder,
                regen_data->folder_changed,
                !hide_deleted,
                !hide_junk);
    }

    g_string_free (expr, TRUE);

    /* Handle search error or cancellation. */

    if (local_error == NULL)
        g_cancellable_set_error_if_cancelled (
            cancellable, &local_error);

    if (local_error != NULL) {
        g_simple_async_result_take_error (simple, local_error);
        goto exit;
    }

    /* XXX This check might not be necessary.  A successfully completed
     *     search with no results should return an empty UID array, but
     *     still need to verify that. */
    if (uids == NULL)
        goto exit;

    /* update/build a new tree */
    if (regen_data->group_by_threads) {
        ml_sort_uids_by_tree (message_list, uids, cancellable);

        if (regen_data->tree != NULL)
            camel_folder_thread_messages_apply (
                regen_data->tree, uids);
        else
            regen_data->tree = camel_folder_thread_messages_new (
                regen_data->folder, uids,
                regen_data->thread_subject);
    } else {
        guint ii;

        camel_folder_sort_uids (regen_data->folder, uids);
        regen_data->summary = g_ptr_array_new ();

        camel_folder_summary_prepare_fetch_all (
            regen_data->folder->summary, NULL);

        for (ii = 0; ii < uids->len; ii++) {
            info = camel_folder_get_message_info (
                regen_data->folder, uids->pdata[ii]);
            if (info != NULL)
                g_ptr_array_add (regen_data->summary, info);
        }
    }

exit:
    if (searchuids != NULL)
        camel_folder_search_free (regen_data->folder, searchuids);
    else if (uids != NULL)
        camel_folder_free_uids (regen_data->folder, uids);
}

static void
message_list_regen_done_cb (GObject *source_object,
                            GAsyncResult *result,
                            gpointer user_data)
{
    MessageList *message_list;
    GSimpleAsyncResult *simple;
    RegenData *regen_data;
    EActivity *activity;
    ETree *tree;
    ETreeTableAdapter *adapter;
    gboolean searching;
    GError *local_error = NULL;

    message_list = MESSAGE_LIST (source_object);
    simple = G_SIMPLE_ASYNC_RESULT (result);
    regen_data = g_simple_async_result_get_op_res_gpointer (simple);

    /* Withdraw our RegenData from the private struct, if it hasn't
     * already been replaced.  We have exclusive access to it now. */
    g_mutex_lock (&message_list->priv->regen_lock);
    if (message_list->priv->regen_data == regen_data) {
        regen_data_unref (message_list->priv->regen_data);
        message_list->priv->regen_data = NULL;
    }
    g_mutex_unlock (&message_list->priv->regen_lock);

    activity = regen_data->activity;

    g_simple_async_result_propagate_error (simple, &local_error);

    if (e_activity_handle_cancellation (activity, local_error)) {
        g_error_free (local_error);
        return;

    /* FIXME This should be handed off to an EAlertSink. */
    } else if (local_error != NULL) {
        g_warning ("%s: %s", G_STRFUNC, local_error->message);
        g_error_free (local_error);
        return;
    }

    e_activity_set_state (activity, E_ACTIVITY_COMPLETED);

    tree = E_TREE (message_list);
    adapter = e_tree_get_table_adapter (tree);

    /* Show the cursor unless we're responding to a
     * "folder-changed" signal from our CamelFolder. */
    if (!regen_data->folder_changed)
        e_tree_show_cursor_after_reflow (tree);

    g_signal_handlers_block_by_func (
        adapter, ml_tree_sorting_changed, message_list);

    g_free (message_list->search);
    message_list->search = g_strdup (regen_data->search);

    searching =
        (message_list->search != NULL) &&
        (*message_list->search != '\0');

    if (regen_data->group_by_threads) {
        gboolean forcing_expand_state;

        forcing_expand_state =
            message_list->expand_all ||
            message_list->collapse_all;

        if (message_list->just_set_folder) {
            message_list->just_set_folder = FALSE;
            if (regen_data->expand_state != NULL) {
                /* Load state from disk rather than use
                 * the memory data when changing folders. */
                xmlFreeDoc (regen_data->expand_state);
                regen_data->expand_state = NULL;
            }
        }

        if (forcing_expand_state || searching) {
            if (message_list->expand_all || searching)
                e_tree_force_expanded_state (tree, 1);
            else
                e_tree_force_expanded_state (tree, -1);
        }

        /* Show the cursor unless we're responding to a
         * "folder-changed" signal from our CamelFolder. */
        build_tree (
            message_list,
            regen_data->tree,
            regen_data->folder_changed);

        if (message_list->thread_tree != NULL)
            camel_folder_thread_messages_unref (
                message_list->thread_tree);
        message_list->thread_tree = regen_data->tree;
        regen_data->tree = NULL;

        if (forcing_expand_state || searching) {
            if (message_list->priv->folder != NULL &&
                tree != NULL && !searching)
                save_tree_state (
                    message_list,
                    regen_data->folder);
            /* do not forget to set this back to use the default value... */
            e_tree_force_expanded_state (tree, 0);
        } else {
            load_tree_state (
                message_list,
                regen_data->folder,
                regen_data->expand_state);
        }

        message_list->expand_all = 0;
        message_list->collapse_all = 0;
    } else {
        build_flat (
            message_list,
            regen_data->summary);
    }

    if (regen_data->select_all) {
        message_list_select_all (message_list);

    } else if (regen_data->select_uid != NULL) {
        message_list_select_uid (
            message_list,
            regen_data->select_uid,
            regen_data->select_use_fallback);

    } else if (message_list->cursor_uid == NULL && regen_data->last_row != -1) {
        gint row_count;

        row_count = e_table_model_row_count (E_TABLE_MODEL (adapter));

        if (regen_data->last_row >= row_count)
            regen_data->last_row = row_count;

        if (regen_data->last_row >= 0) {
            GNode *node;

            node = e_tree_table_adapter_node_at_row (
                adapter, regen_data->last_row);
            if (node != NULL)
                select_node (message_list, node);
        }
    }

    if (gtk_widget_get_visible (GTK_WIDGET (message_list))) {
        const gchar *info_message;
        gboolean have_search_expr;

        /* space is used to indicate no search too */
        have_search_expr =
            (message_list->search != NULL) &&
            (*message_list->search != '\0') &&
            (strcmp (message_list->search, " ") != 0);

        if (e_tree_row_count (E_TREE (tree)) > 0) {
            info_message = NULL;
        } else if (have_search_expr) {
            info_message =
                _("No message satisfies your search criteria. "
                "Change search criteria by selecting a new "
                "Show message filter from the drop down list "
                "above or by running a new search either by "
                "clearing it with Search->Clear menu item or "
                "by changing the query above.");
        } else {
            info_message =
                _("There are no messages in this folder.");
        }

        e_tree_set_info_message (tree, info_message);
    }

    g_signal_handlers_unblock_by_func (
        adapter, ml_tree_sorting_changed, message_list);

    g_signal_emit (
        message_list,
        signals[MESSAGE_LIST_BUILT], 0);

    message_list->priv->any_row_changed = FALSE;
}

static gboolean
message_list_regen_timeout_cb (gpointer user_data)
{
    GSimpleAsyncResult *simple;
    RegenData *regen_data;
    GCancellable *cancellable;

    simple = G_SIMPLE_ASYNC_RESULT (user_data);
    regen_data = g_simple_async_result_get_op_res_gpointer (simple);
    cancellable = e_activity_get_cancellable (regen_data->activity);

    g_mutex_lock (&regen_data->message_list->priv->regen_lock);
    regen_data->message_list->priv->regen_timeout_id = 0;
    g_mutex_unlock (&regen_data->message_list->priv->regen_lock);

    if (g_cancellable_is_cancelled (cancellable)) {
        g_simple_async_result_complete (simple);
    } else {
        g_simple_async_result_run_in_thread (
            simple,
            message_list_regen_thread,
            G_PRIORITY_DEFAULT,
            cancellable);
    }

    return FALSE;
}

static void
mail_regen_cancel (MessageList *message_list)
{
    RegenData *regen_data = NULL;

    g_mutex_lock (&message_list->priv->regen_lock);

    if (message_list->priv->regen_data != NULL)
        regen_data = regen_data_ref (message_list->priv->regen_data);

    if (message_list->priv->regen_timeout_id > 0) {
        g_source_remove (message_list->priv->regen_timeout_id);
        message_list->priv->regen_timeout_id = 0;
    }

    g_mutex_unlock (&message_list->priv->regen_lock);

    /* Cancel outside the lock, since this will emit a signal. */
    if (regen_data != NULL) {
        e_activity_cancel (regen_data->activity);
        regen_data_unref (regen_data);
    }
}

static void
mail_regen_list (MessageList *message_list,
                 const gchar *search,
                 gboolean folder_changed)
{
    GSimpleAsyncResult *simple;
    GCancellable *cancellable;
    RegenData *new_regen_data;
    RegenData *old_regen_data;
    gboolean searching;

    /* Report empty search as NULL, not as one/two-space string. */
    if (search && (strcmp (search, " ") == 0 || strcmp (search, "  ") == 0))
        search = NULL;

    /* Can't list messages in a folder until we have a folder. */
    if (message_list->priv->folder == NULL) {
        g_free (message_list->search);
        message_list->search = g_strdup (search);
        return;
    }

    cancellable = g_cancellable_new ();

    new_regen_data = regen_data_new (message_list, cancellable);
    new_regen_data->search = g_strdup (search);
    new_regen_data->folder_changed = folder_changed;

    if (message_list->thread_tree != NULL) {
        CamelFolderThread *thread_tree;
        gboolean hide_deleted;

        thread_tree = message_list->thread_tree;
        hide_deleted = message_list_get_hide_deleted (
            message_list, new_regen_data->folder);

        if (new_regen_data->group_by_threads && hide_deleted) {
            new_regen_data->tree = thread_tree;
            camel_folder_thread_messages_ref (thread_tree);
        } else {
            camel_folder_thread_messages_unref (thread_tree);
            message_list->thread_tree = NULL;
        }
    }

    searching = (g_strcmp0 (message_list->search, " ") != 0);

    if (e_tree_row_count (E_TREE (message_list)) <= 0) {
        if (gtk_widget_get_visible (GTK_WIDGET (message_list))) {
            gchar *txt;

            txt = g_strdup_printf (
                "%s...", _("Generating message list"));
            e_tree_set_info_message (E_TREE (message_list), txt);
            g_free (txt);
        }
    } else if (message_list->priv->any_row_changed &&
           new_regen_data->group_by_threads &&
           !message_list->just_set_folder &&
           !searching) {
        /* Something changed.  If it was an expand
         * state change, then save the expand state. */
        message_list_save_state (message_list);
    } else if (new_regen_data->group_by_threads &&
           !message_list->just_set_folder &&
           !searching) {
        /* Remember the expand state and restore it after regen. */
        new_regen_data->expand_state =
            e_tree_save_expanded_state_xml (E_TREE (message_list));
    }

    /* We generate the message list content in a worker thread, and
     * then supply our own GAsyncReadyCallback to redraw the widget. */

    simple = g_simple_async_result_new (
        G_OBJECT (message_list),
        message_list_regen_done_cb,
        NULL, mail_regen_list);

    g_simple_async_result_set_check_cancellable (simple, cancellable);

    g_simple_async_result_set_op_res_gpointer (
        simple,
        regen_data_ref (new_regen_data),
        (GDestroyNotify) regen_data_unref);

    /* Swap the old regen data (if present) for the new regen data. */

    g_mutex_lock (&message_list->priv->regen_lock);

    old_regen_data = message_list->priv->regen_data;
    message_list->priv->regen_data = regen_data_ref (new_regen_data);

    if (message_list->priv->regen_timeout_id > 0) {
        g_source_remove (message_list->priv->regen_timeout_id);
        message_list->priv->regen_timeout_id = 0;
    }

    /* Start the regen after a short timeout, so normal updates
     * are immediate.  XXX Do we still need to do this timeout?
     * What are "normal" updates? */
    if (old_regen_data != NULL) {
        message_list->priv->regen_timeout_id =
            g_timeout_add_full (
                G_PRIORITY_DEFAULT, 50,
                message_list_regen_timeout_cb,
                g_object_ref (simple),
                (GDestroyNotify) g_object_unref);
    } else {
        g_simple_async_result_run_in_thread (
            simple,
            message_list_regen_thread,
            G_PRIORITY_DEFAULT,
            cancellable);
    }

    g_mutex_unlock (&message_list->priv->regen_lock);

    /* Cancel outside the lock, since this will emit a signal. */
    if (old_regen_data != NULL) {
        e_activity_cancel (old_regen_data->activity);
        regen_data_unref (old_regen_data);
    }

    g_object_unref (simple);

    regen_data_unref (new_regen_data);

    g_object_unref (cancellable);
}