aboutsummaryrefslogblamecommitdiffstats
path: root/e-util/e-table-item.c
blob: c552922a3454ebb22a5d9d7e9c22b1b7e798e043 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                                           
  
                 
  



                                                                
  

                                                                  
                                                                    
                                                  
  
                                                                   
                                                                             






                                                        
   
  

                                                                
   
                    
                   
      
 

                         
                 
                  



                    
                       
                           
 

                           
                   
                      
                           

                                          
 



                               
 

                        

            





                                                                                                                           


                                                                  
                                                                  

                                                                     
 
      
                      
                         
                     
                    
              
                  
                   
                  

                                


                   
                                               

      











                                          
                        
 


                           

  


                                  




                                                                                                   
                                                                               

                                                                                             
 


                                                                                                                                                             
 
                                                                               


                                                                                      






































                                             
                  

                                   
 

                          
                                     








                                                                           



                           
                  

                                   

                                     





                                                                
                                             

                                 



                           
                  
                                   
                                  
 
               
                            
                          

                                                                             
                                                       





                                 
                                   
                                 
 


                                                                            

 
           


                                      





                                   
                  
                          
                            
                       

                                                        
                                                           
                                      

                                          
                                         
                                            















                                                      
                                                                         



                                                                 
                             


                  

                            

                                                        
                                                           
                             
                                      



                                                    
                                                                                    


                                                                            
                                                              

                                              
                 


         
                      

                             
                                                                                          
 





                                   
                        




                                                    
 
                                                     
                          
                                 
                        

                                   
 


                                                              
                       
                                                  
                                                                    
                    
                                                                  
                
                                                            

         








                                                           

                                          
 
                        



                                                                         
                                                                     
                 
         

                                        




                          




                                                   
 
                          
                             



                                                              



                                   
                       
                                                  
                                                                    
                    
                                                                  
                
                                                            




                          






                                      


                                                                                       




                               







                                                                       
           
                                        
 
                              
               
 

                                       


                                     
                                                        
                       
 
                                          
                                                    


                                     


                                       
               
 

                                            
 







                                                                         


                                         
                                 

                                                            
                                            
                                                                             
 
                                                                                          
         
 
                                      
                                                               
                              
                                                                   

 

                                                                              
                                              
   


                                          
               
 

                                          
 

                                  
                                          
                                                      





                                       
               
 

                                  
                                            

                                                      
         
 
                                 
                               
                         


           




                                  
 
                           



                                                              



                          
 
                                                                     
                                                                  
 
 
           

                                  

                                              

                                        
                                                       

                                                
                                                 


                                                                                      


                                              
                                       
                                                                          







                                                                                      
 




                                 


                                      
 
                                              
                               
 

                                                                                                 
 



                      
 
                                                                      

                             

                             
                                                                           
                                      
         

                                


                                                         

                                      

 




                                                                     





                                        




















                                                   
                                          
                              
                                                   
 
                                           
                                          
                                       
                                           
                                            

                                              
                                

                                   

 
  




                                                                     
                                            



                            











                                              
                                        

                                     
                                         
                                     



                              



                                                                      





                                         








                                                 
 
                              


                                               
                                     


                                            
                                         


                           
  
                       



                                                                            
           

                                     
 


                                                             
 
                                                               
 
                  
 
                                          
                                                                                                 






                                  


                                      
               
 
                                                         
                       
                                                   




                                          
               
                                   
 

                         
                                   

                                                                    
                                                
                                  








                                                 
                     

 


                                   




                                                       






                                                                                
                                                                    

                                                      
 
                                                                                 
                                                                                                                                 
         




                                        

                                   

 





                                                                            
           

                                
 







                                                                              
                                                                                

                                                          
                                                                
                                                                                
                                                              
                                                                                       
                         
                 
                                              
         

 






                                                                        
                                                                      



                                                                         
           

                                

                                                              
 

                         
 
                                      
                                                           

                                                                           

                            

                                                           
                                                                          








                                                                                                             
                                         
                                      
                                                                                                 
 




                                                             
                 
 

                                                
                                                                           
 

                              

 
           




                                        

                                                        
                                   
                           
 



                 
 

                                                                          
 
                                                                                                     

 
  

                                                                  
    


                                       
 
                                                              
 



                                    
 
                                      
                                                                                           
                
                                

                                                           
                                                                          
 

                             

 

                                







                               
 
                            
 













                                                                                









                                                                                          
           
                                           




                                          
 
                            

                            
 




                                             
 





                                         
         

 







                                                                                   




                                        
 
                            
 




                                     
 
                  

                                                                        
            

                                                                 

 
           

                                 
 
                              
                        
 


                                                                                      
                       




                                              
 
     



                                          


                                                                   
 
                                                                       
 

                                                                 



                                                                 



           

                                         
                              

                            
 
                                       
 
                                                                                      




                                    
 



                                          





                                    
                                             


                       
                                                                             
 

                                                         
                                                                       
 



                                                         



                            
                                                                                                   
 
                                                                                                           


           

                                       
 
                                                                                                           




                                             
               
                                       

                               

                             

                                              
         
 
                                
                             



                     
                                            
 
                                                                                                           
                                    
                                   

                                                                                        
         

 





                                                     
                                                                                                                                           





                                     


                            
                            
                                                                  




                              


                                   
                            
                                                                  






                                                               
  
                                                                    

           

                                                     
 
                                                  

                                      
                                              

                             






                                                                           

                                                    

                           




                                                                       
 
           

                                                  
 


                                                          
                                   
                       
         


                                                               
                                
 

                           




                                                                   
                                         


           


                                                      
 


                                                          
                                   
                       
         
 
                                                                                                                                                          


                                                           
 

                           
                                           
 
 
           



                                                       
 


                                                          
                                   
                       
         
 
                                                                                                                                                          


                                                           
 

                           
                                           

 
           



                                                        
 


                                                          
                                   
                       
         


                                                               
                       

                                                                                                                              

                                                   

         

                           
                                         
 



                                                                   


           



                                                       
 


                                                          
                                   
                       
         
 

                                                               
                                                     
                                                                                                                      
         
 

                           
                                         
 



                                                                   

 
   









                                                                      

                                           



                                          
 

                                    
 

                                                 
 




                                          


                                        

                                                                 



                           
                                                                                        


           
                                         
                                  
 

                                                                            


           

                                              
 
                                                    
 
                                       
                                        
 
                                                           
                                                
                                                              
 
                                                          
                                               
                                                             
 
                                                       
                                             
                                                           
 
                                                           
                                                 
                                                               
 
                                                            
                                                  
                                                                
 
                                                              
                                                   
                                                                 
 
                                                             
                                                  
                                                                
 
                          


                                            
 
                                              
                                           

                                                                     
                                      
                                                         
         

                         
 
                                                   


           

                                                    
 
                                                  
 
                                   
                                      
 


                                                        
 


                                                            
 


                                                     
 


                                                        
 
                                              
                                                                             


           


                                          
 

                                                               

                                                                   


           

                                                
 
                                                       
 

                                              
                                                                                  
           
 
                              
                                               

                                            
                                             
                
                                       
                                                    
                                                     
                 
         

                                                               

                                                                   

 
           


                                            
 
                       
 
                                                          
                                                                                                   




                     
           

                                           
 
                                               
 
                             
                              
 
                                                   
 
                                                      
                                           
                                                          
 
                                                            
                                           
                                                                
 


                                                            

 
  
                          
   
           
                             




                                                
                                         
 
                                        
                                                            

                                              
                                         
 
                                  
                                                      


                                        


                                           
 
                                                    
                                                                     


           



                                      
 

                                                           
                        
 
                              
                               
                                              
                                                                                        

                      
                              
                                             
                                                                                      
                      
 
                                  

                                                                        
                                                 
                                               
                                                                                                      
                      
 

                                                                

                      

                                                                          

                      

                                                                        

                      

                                                                      

                      

                                                              
                      
 

                                                           
                      
 

                                

                                                                                                    
                                                     
                                                            
                 
                                                                
                      
                             



                                                  
 
                                                                                                                                 
                      


                                                                              
                                                                       
                                                        
                                                              
                                                                    
                                                      
                                                                        

                         
                      
         
                              
                                                


           



                                    
 
                        
                 
 
                                    
 
                              

                                                       
                      

                                                        
                      

                                                               
                      
                             



                                           
                                                                      
                      

                                                                     
                      


                                                                           
                
                                                                               
                      



           
                                   
 

                                    





                                            



                                              

                                           


                                              
 
                                            
                                               
 

                                              
 
                                            
                                                         
 
                                           
                                           
                                           
                                           

                                              

                                            

                                           
 

                                           

                                              

                                           
 



                                           


                                            
                                               
                                            



                                            
                                            
                                            
 


                                               
                                                                                

 

                       
                                    

                      
               


                                     
 

                   
                                                                             

                     


           

                                   
                                              
 

                                                                                      
 

                                                               


                                                     
 

                                            
 
                                     
 
                                
 
                                                                   
                         
 
                                                                                        
                                                   






                                                               
                                      
                                     
                                                               
                              
                                                                   




                                     
                                              
 
                                     
                                                               
                                     

         
                              
                                              
 
                                        
                                                            







                                              
                                       

                        
 

                                                                                        


           






                                    
 
                        
 

                                                                        
 


                                               
 
                           


           





                                
 
                                              






                                               
                         
                           
                                                                     
                                                      
                                                        
                                                              
 


                                                    


                                                  
                                                                      
 

                                    
                                                                            
 


                                                    
                       
                     
                                
                                                   


                                                                               
 



                                     
                                      
                                          



                                        
 
          


                            
                       
 
          
                            
           
                                      

                                                                                                                             
                                         
                               
                                                                                                                         




                                             
                            
 

                               
 
                                                            
                                                           
 






                                                                       
 
                                              


                                                  
                 
                               
 
                                    
                               
         
 
                            
                       
 



                      

                                       
 
                                                        
                                                                                                         

                           
 
                                                      
                        
                                  
                                            
 
                                                   
 
                              
 
                                                                                                                               
 




                                                  
 
                                                              
                                                                                       
                                                                     
                                                         
                                                
                                         
                                                 
                                             
                                            
                                             
 
                                                   

                                                  
                                                                                                                        
                                                                     

                                                      
                                      
                                           


                                             
 
                                
                                    
                                              
                                         
 
                                                                                                                   
 

                                                                         



                                                                                 
                                     








                                                                                  
                                                                              








                                                       



                                                               




                                                       



                                                               


                                                   

                                           


                                                            
                                                                   
                                                                                    

                                                            














                                                               
                                     




                                                               
 
                                                    
                                                           
                                                   
                                                                                         

                                                                                 
                                                              



                                                                   

                                                          
                                                                                                                                       







                                                                        
                         
 
                                          
                 
                             
 
                                                
                                                                                                                 

                             
         
 
                                      
                                   
 
                                                               

                                                                                       
                                                                                      
 




                                                                            

                 
 


                     
                                         

                                                          



                                                      
 

                                                                              
 


                                                                              
         

 
                        




                                 
 
                    

 
               






                              
 

                                    
                               
                      
 
                                                              
 
                                                           

                                                             

                                                 

                                                                                         

                            
 

                                   
 
               
                                                       



                                                                               
 

                                      

                              

         

                                      
                                     

                                                                                     



                                       
                                     
                                     
                                                           
                                                                       
 


                                      


                                     
         
                            

                                 
                            

                                 


                    
           


                                 
 
                                      
                                                                                                   




                                      
                                    




                                          
 
                                                                                                            




                                       
                                    




                                          
 
                                                                                                            

 
           


                                        



                                   



                                 

                                                                              

                                    


                                        
                                                             




                                                            




                                             

                                   
                                                               



                                                        






                                       
                   
           
                                 
                           
 
                                              
                              








                                        
                                   


                               
 


                             









                                                                             
                                
                               
                              
                                            
                                                    
                                     
 
                                                                                                       
 
                                       

                                           
                                                                                 
 

                                                   
 



                                                                                                 
                                                      
                                                                      
                                            
                         
 
                                                          




                                                                     
 




                                                          
 
                                                                                                                       




                                                      





                                                             
                                            
                         
 

                                                           



                                                                  


                                                     
                                            
                         
 




                                                          
 
                                                  




                                                                      




                                                              
 


                                                                                           
                                                   

                                                     
                                                                                                                                 


                                                                                


                                                             
 
                                                        
                                                                       




                                                                             


                                                            
                         
 


                                                        

                                                  

                                                                            


                                                                    


                                                                  
                                                          
                                                                             
                                                                         

                         

                              
                                                                                 







                                                                                               
                                            
 








                                                                     
 

                                                                 




                                                                  
                                        
                                                                                                      



                                     
 
                 

                      
 
                                  
                               
                              
                                            
 
                                                                                                         
 
                                             
                                                                       
                                                     

                 
                                        

                                                           
                                                              
                                                                                                                                                           



                                                     

                 
                                       

                                           
 




                                                                    

                         


                                                                        
                                         

                                                                                       


                                                                                     
 
                                                                                               
                                            
 




                                                          
 
                                                                                                                                            
 
                                                                                                                                            

 
                                                                  
 



                                                                             
 
                                                               






                                                                     


                              
                                                                                              

                                          


                                     
 


                      
 
                                 
                                          
     
                               
      
 
                                                                                                        
 






                                                                      

                                                   
 

                                                                    
 




                                                         
 











                                                                            


                                                                      
                                                                                                                                                                   



                                                             


                                                                       

                         
                                                     
                                                                               
                                                             

                         
                                                                 

                                                                          
                                                                          
                         

                                                    
                 

                      
                                 
                                     
                               
                                            
 



                                                                           
 
                                         

                                                                    
                                                      

                                                       






                                                                    

                                                                        




                                                                     






                                                         
                                                                                       

                                    
                                                                     
                                                                         
                                                                           
                                                          

                                                                      
                                      

                                                                         




                                      




                                                  
 
                          
                                                                                                               

                                                               
 
                                                  
 



                                                             
 
                                               
                                                    
                                                                       


                                            

                      
 

                                            
                                    
 
                                                                                                             
 




                                                  
 
                                                         

                                     

                                         
                                       

                                     

                                                
                                      
                         
 

                                                               

                                                                    
                                            

                                                                                    
                                                           
                                       
                              
 

                                      

                                                
                                      

                         

                                                               

                                                                    
                                            

                                                                                    
                                                            
                                       
                              
 



                                     

                                                                                                       
                                                                                    
 
                                                                              
                                                                                                                                                                                
                                                            
                              
                                                                                                                             
                              

                                     




                                                
                                                                
                                                                                              
                                                  
                              
                                                                                                                                     
                              

                                    




                                                
                                                                
                                                                                                          
                                                  
                              
                                                                                                                                     
                              


                                          
                                                                    


                                                   
                                                                       
                                                                          
                                               
                                                                                     
                                                                           

                                                                                                                              




                                                                                                 

                                                                                                                  
                                            

                                                                   




                                                                  

                                                                                       

                                                                                                                                                                 


                                      


                                                                  
                         
 



                                        
                                                
                                                                               
                                                               
                                                               

                                                                                                                             

                                                
                         

                                                               

                                                                    
                                        
                                                                                                                                     
                              
 
                        


                                        
 
                               
                                               
















                                                 


                                      
                                                         
                                                      


                                                                                                                                           

                                                                                        
                                 
                                                         

                                                                               

                                                                                    
                                                        
                                                                                                                                        
                                        
                                                                                       
                                                                       
                                                                       

                                                                                                                    
                                                        
                                                                                                                                        

                                      
                         
                 
                                          
                      
         
 


                                            
                                                                                                              
 




                                                  

                                     
                                     
 
                                        
                                                                       
                                                       
                                                       

                                                                                                    
                 
                      
         
 
                              
                                 
                              
                                                                                                                 
                                                                   

                                                                      
                                      

                                                                         


                                     

                      
                              
                                                                                                                    
                                             


                                                  
                                                                                                                                       
                                                                                            


                                                                                                                  







                                                                   


                                                                                                                     
                                                              
                         

                 
                
                                   
         
                                                                                        
 
                          
 
 
           

                                        
 


                                                        

                       
                                       

                                            




                                                                              







                                                                   
                                         


           
                                                
 

                                                                           
 


                                                       






                                                    
 









                                                
 













































































































































                                                  









                                                        












































































































                                                                           
 

                                      

 
   






                                                               
    


                                         
 
                                                                       


           



                                          
 

                                                 
 
                        
                                                             


                        
                                    
         
 
                        


                                                           
         
 
 
   






                                                               

                                                 
 
                        
 

                                                         
 



                                          
 
                          

 
           



                                              
 
                                                        
                      
 
                                                        
                       
 
                                                
 


                                                                         
                             
                                              
                                         


                       
                                                                   
                                         
                                                                            
                        
                                                       
                 

         

                                                                  
                                              

                                                                     


                                                
                                       
 
 
           



                                                 
 
                                                        

                      
 
                                                        

                       

                                                
 
                                               
                                                                           
                                                 
                                                                              
                                
                                                         
                         
                                                      


                 
                              
                                              

                           

                                                                         


           

                                                 
 


                                                        

                       
                                 
                                                                   

 
           


                                                     
 


                                                        


                                 
                                                                            


         
   







                                                                      
    


                                         


                                                 
 
                                                                                                                   
 
                              
                                              



                               
                                                                                                         

                                                       

 
   
                           



                                                         


                                         

                          
 


                                                 
                                                                                                 
 

                               
 



                                 


                              
 



                                             

                                                       

 
   
                                











                                                                      
    




                                               
 
                                                                      
                                                    
                                            

                              
                                                             
                                  
         

                                       

 





                                                                    




                                                 
 
                             
                                                                      
                                                    
                                            






                                                                        
                                                                                 






                                       
    






                                                
















                                                                                       

                         
                          


                         

                                                       
 
               

                          
                                 
                             
                                                                      




                                                            
                                                                                       
                                           
                                                                                               


                                                               
                                                                                                                     





                      



                                                 
 

                              

                                          
                                                             


                                                                                    








                                             




                                       
 
                    
                                                           
                        
                                                  

                                       
                           
                 


           





                                                             
 
                                            

                                    
                           

                                                      
                            
                    
                        
 
                                                           
                                                            




                                                                                        
 
                                        
                                                        
         

             
                                                     
                                           
                                                                                
 
                               

                                                                                      


                                      
                                              
                                              
                                      


                         
                                                  
                                                                     
 

                                                     
                                                                                
                                        
 






                                                             
 
                                           
 

                                          
 
                         
                                                
                                                                
                 
                     
         

                                        
                                      
                               
                                                  
                                                                           

                                          
                                                                   

         
                      
                                     
 



                        

                                                            

                                             
                                                      
 
                                                         



                                         

                                                        




                                      





                                                         

                                             

                                                      
                        
                 
                       
 





                                                                                        
 
                                                     
                                   
 
                                                                                 







                                                                                                          







                                 
 

                                                               
 
                                                      


                  
               





                                                           

                                             

                                                      
                        
                 

                                
 





                                                                                        
 
                                                     
                                   
 
                                                                                 


















                                                                                                          
                                                        


                       
           
                                              
                                                          
 

                                                   

                                           

 
   





                                                                      
                            
   


                                             
                                                   

                                            
                                                        
                                 
                            

                                      


















                                                                   


                         













                                                                     
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * e-table-item.c
 *
 * 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:
 *      Chris Lahey <clahey@ximian.com>
 *      Miguel de Icaza <miguel@gnu.org>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 */
/*
 * TODO:
 *   Add a border to the thing, so that focusing works properly.
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "e-table-item.h"

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <gdk/gdkkeysyms.h>

#include "e-canvas-utils.h"
#include "e-canvas.h"
#include "e-cell.h"
#include "e-marshal.h"
#include "e-table-subset.h"
#include "gal-a11y-e-table-item-factory.h"
#include "gal-a11y-e-table-item.h"

G_DEFINE_TYPE (
    ETableItem,
    e_table_item,
    GNOME_TYPE_CANVAS_ITEM)

#define FOCUSED_BORDER 2

#define d(x)

#if d(!)0
#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)), g_print ("%s: e_table_item_leave_edit\n", __FUNCTION__))
#else
#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)))
#endif

static void eti_check_cursor_bounds (ETableItem *eti);
static void eti_cancel_drag_due_to_model_change (ETableItem *eti);

/* FIXME: Do an analysis of which cell functions are needed before
 * realize and make sure that all of them are doable by all the cells
 * and that all of the others are only done after realization. */

enum {
    CURSOR_CHANGE,
    CURSOR_ACTIVATED,
    DOUBLE_CLICK,
    RIGHT_CLICK,
    CLICK,
    KEY_PRESS,
    START_DRAG,
    STYLE_SET,
    SELECTION_MODEL_REMOVED,
    SELECTION_MODEL_ADDED,
    LAST_SIGNAL
};

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

enum {
    PROP_0,
    PROP_TABLE_HEADER,
    PROP_TABLE_MODEL,
    PROP_SELECTION_MODEL,
    PROP_TABLE_ALTERNATING_ROW_COLORS,
    PROP_TABLE_HORIZONTAL_DRAW_GRID,
    PROP_TABLE_VERTICAL_DRAW_GRID,
    PROP_TABLE_DRAW_FOCUS,
    PROP_CURSOR_MODE,
    PROP_LENGTH_THRESHOLD,
    PROP_CURSOR_ROW,
    PROP_UNIFORM_ROW_HEIGHT,
    PROP_IS_EDITING,

    PROP_MINIMUM_WIDTH,
    PROP_WIDTH,
    PROP_HEIGHT
};

#define DOUBLE_CLICK_TIME      250
#define TRIPLE_CLICK_TIME      500

static gint eti_get_height (ETableItem *eti);
static gint eti_row_height (ETableItem *eti, gint row);
static void e_table_item_focus (ETableItem *eti, gint col, gint row, GdkModifierType state);
static void eti_cursor_change (ESelectionModel *selection, gint row, gint col, ETableItem *eti);
static void eti_cursor_activated (ESelectionModel *selection, gint row, gint col, ETableItem *eti);
static void eti_selection_change (ESelectionModel *selection, ETableItem *eti);
static void eti_selection_row_change (ESelectionModel *selection, gint row, ETableItem *eti);
static void e_table_item_redraw_row (ETableItem *eti, gint row);

#define ETI_SINGLE_ROW_HEIGHT(eti) ((eti)->uniform_row_height_cache != -1 ? (eti)->uniform_row_height_cache : eti_row_height((eti), -1))
#define ETI_MULTIPLE_ROW_HEIGHT(eti,row) ((eti)->height_cache && (eti)->height_cache[(row)] != -1 ? (eti)->height_cache[(row)] : eti_row_height((eti),(row)))
#define ETI_ROW_HEIGHT(eti,row) ((eti)->uniform_row_height ? ETI_SINGLE_ROW_HEIGHT ((eti)) : ETI_MULTIPLE_ROW_HEIGHT((eti),(row)))

/* tweak_hsv is a really tweaky function. it modifies its first argument, which
 * should be the color you want tweaked. delta_h, delta_s and delta_v specify
 * how much you want their respective channels modified (and in what direction).
 * if it can't do the specified modification, it does it in the oppositon direction */
static void
e_hsv_tweak (GdkColor *color,
             gdouble delta_h,
             gdouble delta_s,
             gdouble delta_v)
{
    gdouble h, s, v, r, g, b;

    r = color->red   / 65535.0f;
    g = color->green / 65535.0f;
    b = color->blue  / 65535.0f;

    gtk_rgb_to_hsv (r, g, b, &h, &s, &v);

    if (h + delta_h < 0) {
        h -= delta_h;
    } else {
        h += delta_h;
    }

    if (s + delta_s < 0) {
        s -= delta_s;
    } else {
        s += delta_s;
    }

    if (v + delta_v < 0) {
        v -= delta_v;
    } else {
        v += delta_v;
    }

    gtk_hsv_to_rgb (h, s, v, &r, &g, &b);

    color->red   = r * 65535.0f;
    color->green = g * 65535.0f;
    color->blue  = b * 65535.0f;
}

inline static gint
model_to_view_row (ETableItem *eti,
                   gint row)
{
    if (row == -1)
        return -1;
    if (eti->uses_source_model) {
        gint model_row;

        model_row = e_table_subset_view_to_model_row (
            E_TABLE_SUBSET (eti->table_model), eti->row_guess);
        if (model_row >= 0 && model_row == row)
            return eti->row_guess;

        return e_table_subset_model_to_view_row (
            E_TABLE_SUBSET (eti->table_model), row);
    } else
        return row;
}

inline static gint
view_to_model_row (ETableItem *eti,
                   gint row)
{
    if (eti->uses_source_model) {
        gint model_row;

        model_row = e_table_subset_view_to_model_row (
            E_TABLE_SUBSET (eti->table_model), row);

        if (model_row >= 0)
            eti->row_guess = row;

        return model_row;
    } else
        return row;
}

inline static gint
model_to_view_col (ETableItem *eti,
                   gint model_col)
{
    gint i;
    if (model_col == -1)
        return -1;
    for (i = 0; i < eti->cols; i++) {
        ETableCol *ecol = e_table_header_get_column (eti->header, i);
        if (ecol->spec->model_col == model_col)
            return i;
    }
    return -1;
}

inline static gint
view_to_model_col (ETableItem *eti,
                   gint view_col)
{
    ETableCol *ecol = e_table_header_get_column (eti->header, view_col);

    return (ecol != NULL) ? ecol->spec->model_col : -1;
}

static void
grab_cancelled (ECanvas *canvas,
                GnomeCanvasItem *item,
                gpointer data)
{
    ETableItem *eti = data;

    eti->grab_cancelled = TRUE;
}

inline static void
eti_grab (ETableItem *eti,
          GdkDevice *device,
          guint32 time)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
    d (g_print ("%s: time: %d\n", __FUNCTION__, time));
    if (eti->grabbed_count == 0) {
        GdkGrabStatus grab_status;

        eti->gtk_grabbed = FALSE;
        eti->grab_cancelled = FALSE;

        grab_status = e_canvas_item_grab (
            E_CANVAS (item->canvas),
            item,
            GDK_BUTTON1_MOTION_MASK |
            GDK_BUTTON2_MOTION_MASK |
            GDK_BUTTON3_MOTION_MASK |
            GDK_POINTER_MOTION_MASK |
            GDK_BUTTON_PRESS_MASK |
            GDK_BUTTON_RELEASE_MASK,
            NULL,
            device, time,
            grab_cancelled,
            eti);

        if (grab_status != GDK_GRAB_SUCCESS) {
            d (g_print ("%s: gtk_grab_add\n", __FUNCTION__));
            gtk_grab_add (GTK_WIDGET (item->canvas));
            eti->gtk_grabbed = TRUE;
        }
    }
    eti->grabbed_count++;
}

inline static void
eti_ungrab (ETableItem *eti,
            guint32 time)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
    d (g_print ("%s: time: %d\n", __FUNCTION__, time));
    eti->grabbed_count--;
    if (eti->grabbed_count == 0) {
        if (eti->grab_cancelled) {
            eti->grab_cancelled = FALSE;
        } else {
            if (eti->gtk_grabbed) {
                d (g_print ("%s: gtk_grab_remove\n", __FUNCTION__));
                gtk_grab_remove (GTK_WIDGET (item->canvas));
                eti->gtk_grabbed = FALSE;
            }
            gnome_canvas_item_ungrab (item, time);
            eti->grabbed_col = -1;
            eti->grabbed_row = -1;
        }
    }
}

inline static gboolean
eti_editing (ETableItem *eti)
{
    d (g_print ("%s: %s\n", __FUNCTION__, (eti->editing_col == -1) ? "false":"true"));

    if (eti->editing_col == -1)
        return FALSE;
    else
        return TRUE;
}

inline static GdkColor *
eti_get_cell_background_color (ETableItem *eti,
                               gint row,
                               gint col,
                               gboolean selected,
                               gboolean *allocatedp)
{
    ECellView *ecell_view = eti->cell_views[col];
    GtkWidget *canvas;
    GdkColor *background, bg;
    GtkStyle *style;
    gchar *color_spec = NULL;
    gboolean allocated = FALSE;

    canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas);
    style = gtk_widget_get_style (canvas);

    if (selected) {
        if (gtk_widget_has_focus (canvas))
            background = &style->bg[GTK_STATE_SELECTED];
        else
            background = &style->bg[GTK_STATE_ACTIVE];
    } else {
        background = &style->base[GTK_STATE_NORMAL];
    }

    color_spec = e_cell_get_bg_color (ecell_view, row);

    if (color_spec != NULL) {
        if (gdk_color_parse (color_spec, &bg)) {
            background = gdk_color_copy (&bg);
            allocated = TRUE;
        }
    }

    if (eti->alternating_row_colors) {
        if (row % 2) {

        } else {
            if (!allocated) {
                background = gdk_color_copy (background);
                allocated = TRUE;
            }
            e_hsv_tweak (background, 0.0f, 0.0f, -0.07f);
        }
    }
    if (allocatedp)
        *allocatedp = allocated;

    return background;
}

inline static GdkColor *
eti_get_cell_foreground_color (ETableItem *eti,
                               gint row,
                               gint col,
                               gboolean selected,
                               gboolean *allocated)
{
    GtkWidget *canvas;
    GdkColor *foreground;
    GtkStyle *style;

    canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas);
    style = gtk_widget_get_style (canvas);

    if (allocated)
        *allocated = FALSE;

    if (selected) {
        if (gtk_widget_has_focus (canvas))
            foreground = &style->fg[GTK_STATE_SELECTED];
        else
            foreground = &style->fg[GTK_STATE_ACTIVE];
    } else {
        foreground = &style->text[GTK_STATE_NORMAL];
    }

    return foreground;
}

static void
eti_free_save_state (ETableItem *eti)
{
    if (eti->save_row == -1 ||
        !eti->cell_views_realized)
        return;

    e_cell_free_state (
        eti->cell_views[eti->save_col], view_to_model_col (eti, eti->save_col),
        eti->save_col, eti->save_row, eti->save_state);
    eti->save_row = -1;
    eti->save_col = -1;
    eti->save_state = NULL;
}

/*
 * During realization, we have to invoke the per-ecell realize routine
 * (On our current setup, we have one e-cell per column.
 *
 * We might want to optimize this to only realize the unique e-cells:
 * ie, a strings-only table, uses the same e-cell for every column, and
 * we might want to avoid realizing each e-cell.
 */
static void
eti_realize_cell_views (ETableItem *eti)
{
    GnomeCanvasItem *item;
    gint i;

    item = GNOME_CANVAS_ITEM (eti);

    if (eti->cell_views_realized)
        return;

    if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
        return;

    for (i = 0; i < eti->n_cells; i++)
        e_cell_realize (eti->cell_views[i]);
    eti->cell_views_realized = 1;
}

static void
eti_attach_cell_views (ETableItem *eti)
{
    gint i;

    g_return_if_fail (eti->header);
    g_return_if_fail (eti->table_model);

    /* this is just c&p from model pre change, but it fixes things */
    eti_cancel_drag_due_to_model_change (eti);
    eti_check_cursor_bounds (eti);
    if (eti_editing (eti))
        e_table_item_leave_edit_(eti);
    eti->motion_row = -1;
    eti->motion_col = -1;

    /*
     * Now realize the various ECells
     */
    eti->n_cells = eti->cols;
    eti->cell_views = g_new (ECellView *, eti->n_cells);

    for (i = 0; i < eti->n_cells; i++) {
        ETableCol *ecol = e_table_header_get_column (eti->header, i);

        eti->cell_views[i] = e_cell_new_view (ecol->ecell, eti->table_model, eti);
    }

    eti->needs_compute_height = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

/*
 * During unrealization: we invoke every e-cell (one per column in the current
 * setup) to dispose all X resources allocated
 */
static void
eti_unrealize_cell_views (ETableItem *eti)
{
    gint i;

    if (eti->cell_views_realized == 0)
        return;

    eti_free_save_state (eti);

    for (i = 0; i < eti->n_cells; i++)
        e_cell_unrealize (eti->cell_views[i]);
    eti->cell_views_realized = 0;
}

static void
eti_detach_cell_views (ETableItem *eti)
{
    gint i;

    eti_free_save_state (eti);

    for (i = 0; i < eti->n_cells; i++) {
        e_cell_kill_view (eti->cell_views[i]);
        eti->cell_views[i] = NULL;
    }

    g_free (eti->cell_views);
    eti->cell_views = NULL;
    eti->n_cells = 0;
}

static void
eti_bounds (GnomeCanvasItem *item,
            gdouble *x1,
            gdouble *y1,
            gdouble *x2,
            gdouble *y2)
{
    cairo_matrix_t i2c;
    ETableItem *eti = E_TABLE_ITEM (item);

    /* Wrong BBox's are the source of redraw nightmares */

    *x1 = 0;
    *y1 = 0;
    *x2 = eti->width;
    *y2 = eti->height;

    gnome_canvas_item_i2c_matrix (GNOME_CANVAS_ITEM (eti), &i2c);
    gnome_canvas_matrix_transform_rect (&i2c, x1, y1, x2, y2);
}

static void
eti_reflow (GnomeCanvasItem *item,
            gint flags)
{
    ETableItem *eti = E_TABLE_ITEM (item);

    if (eti->needs_compute_height) {
        gint new_height = eti_get_height (eti);

        if (new_height != eti->height) {
            eti->height = new_height;
            e_canvas_item_request_parent_reflow (GNOME_CANVAS_ITEM (eti));
            eti->needs_redraw = 1;
            gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
        }
        eti->needs_compute_height = 0;
    }
    if (eti->needs_compute_width) {
        gint new_width = e_table_header_total_width (eti->header);
        if (new_width != eti->width) {
            eti->width = new_width;
            e_canvas_item_request_parent_reflow (GNOME_CANVAS_ITEM (eti));
            eti->needs_redraw = 1;
            gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
        }
        eti->needs_compute_width = 0;
    }
}

/*
 * GnomeCanvasItem::update method
 */
static void
eti_update (GnomeCanvasItem *item,
            const cairo_matrix_t *i2c,
            gint flags)
{
    ETableItem *eti = E_TABLE_ITEM (item);
    gdouble x1, x2, y1, y2;

    if (GNOME_CANVAS_ITEM_CLASS (e_table_item_parent_class)->update)
        (*GNOME_CANVAS_ITEM_CLASS (e_table_item_parent_class)->update)(item, i2c, flags);

    x1 = item->x1;
    y1 = item->y1;
    x2 = item->x2;
    y2 = item->y2;

    eti_bounds (item, &item->x1, &item->y1, &item->x2, &item->y2);
    if (item->x1 != x1 ||
        item->y1 != y1 ||
        item->x2 != x2 ||
        item->y2 != y2) {
        gnome_canvas_request_redraw (item->canvas, x1, y1, x2, y2);
        eti->needs_redraw = 1;
    }

    if (eti->needs_redraw) {
        gnome_canvas_request_redraw (
            item->canvas, item->x1, item->y1,
            item->x2, item->y2);
        eti->needs_redraw = 0;
    }
}

/*
 * eti_remove_table_model:
 *
 * Invoked to release the table model associated with this ETableItem
 */
static void
eti_remove_table_model (ETableItem *eti)
{
    if (!eti->table_model)
        return;

    g_signal_handler_disconnect (
        eti->table_model,
        eti->table_model_pre_change_id);
    g_signal_handler_disconnect (
        eti->table_model,
        eti->table_model_no_change_id);
    g_signal_handler_disconnect (
        eti->table_model,
        eti->table_model_change_id);
    g_signal_handler_disconnect (
        eti->table_model,
        eti->table_model_row_change_id);
    g_signal_handler_disconnect (
        eti->table_model,
        eti->table_model_cell_change_id);
    g_signal_handler_disconnect (
        eti->table_model,
        eti->table_model_rows_inserted_id);
    g_signal_handler_disconnect (
        eti->table_model,
        eti->table_model_rows_deleted_id);
    g_object_unref (eti->table_model);
    if (eti->source_model)
        g_object_unref (eti->source_model);

    eti->table_model_pre_change_id = 0;
    eti->table_model_no_change_id = 0;
    eti->table_model_change_id = 0;
    eti->table_model_row_change_id = 0;
    eti->table_model_cell_change_id = 0;
    eti->table_model_rows_inserted_id = 0;
    eti->table_model_rows_deleted_id = 0;
    eti->table_model = NULL;
    eti->source_model = NULL;
    eti->uses_source_model = 0;
}

/*
 * eti_remove_table_model:
 *
 * Invoked to release the table model associated with this ETableItem
 */
static void
eti_remove_selection_model (ETableItem *eti)
{
    if (!eti->selection)
        return;

    g_signal_handler_disconnect (
        eti->selection,
        eti->selection_change_id);
    g_signal_handler_disconnect (
        eti->selection,
        eti->selection_row_change_id);
    g_signal_handler_disconnect (
        eti->selection,
        eti->cursor_change_id);
    g_signal_handler_disconnect (
        eti->selection,
        eti->cursor_activated_id);
    g_object_unref (eti->selection);

    eti->selection_change_id = 0;
    eti->selection_row_change_id = 0;
    eti->cursor_activated_id = 0;
    eti->selection = NULL;
}

/*
 * eti_remove_header_model:
 *
 * Invoked to release the header model associated with this ETableItem
 */
static void
eti_remove_header_model (ETableItem *eti)
{
    if (!eti->header)
        return;

    g_signal_handler_disconnect (
        eti->header,
        eti->header_structure_change_id);
    g_signal_handler_disconnect (
        eti->header,
        eti->header_dim_change_id);
    g_signal_handler_disconnect (
        eti->header,
        eti->header_request_width_id);

    if (eti->cell_views) {
        eti_unrealize_cell_views (eti);
        eti_detach_cell_views (eti);
    }
    g_object_unref (eti->header);

    eti->header_structure_change_id = 0;
    eti->header_dim_change_id = 0;
    eti->header_request_width_id = 0;
    eti->header = NULL;
}

/*
 * eti_row_height_real:
 *
 * Returns the height used by row @row.  This does not include the one-pixel
 * used as a separator between rows
 */
static gint
eti_row_height_real (ETableItem *eti,
                     gint row)
{
    const gint cols = e_table_header_count (eti->header);
    gint col;
    gint h, max_h;

    g_return_val_if_fail (cols == 0 || eti->cell_views, 0);

    max_h = 0;

    for (col = 0; col < cols; col++) {
        h = e_cell_height (eti->cell_views[col], view_to_model_col (eti, col), col, row);

        if (h > max_h)
            max_h = h;
    }
    return max_h;
}

static void
confirm_height_cache (ETableItem *eti)
{
    gint i;

    if (eti->uniform_row_height || eti->height_cache)
        return;
    eti->height_cache = g_new (int, eti->rows);
    for (i = 0; i < eti->rows; i++) {
        eti->height_cache[i] = -1;
    }
}

static gboolean
height_cache_idle (ETableItem *eti)
{
    gint changed = 0;
    gint i;
    confirm_height_cache (eti);
    for (i = eti->height_cache_idle_count; i < eti->rows; i++) {
        if (eti->height_cache[i] == -1) {
            eti_row_height (eti, i);
            changed++;
            if (changed >= 20)
                break;
        }
    }
    if (changed >= 20) {
        eti->height_cache_idle_count = i;
        return TRUE;
    }
    eti->height_cache_idle_id = 0;
    return FALSE;
}

static void
free_height_cache (ETableItem *eti)
{
    GnomeCanvasItem *item;

    item = GNOME_CANVAS_ITEM (eti);

    if (item->flags & GNOME_CANVAS_ITEM_REALIZED) {
        if (eti->height_cache)
            g_free (eti->height_cache);
        eti->height_cache = NULL;
        eti->height_cache_idle_count = 0;
        eti->uniform_row_height_cache = -1;

        if (eti->uniform_row_height && eti->height_cache_idle_id != 0) {
            g_source_remove (eti->height_cache_idle_id);
            eti->height_cache_idle_id = 0;
        }

        if ((!eti->uniform_row_height) && eti->height_cache_idle_id == 0)
            eti->height_cache_idle_id = g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) height_cache_idle, eti, NULL);
    }
}

static void
calculate_height_cache (ETableItem *eti)
{
    free_height_cache (eti);
    confirm_height_cache (eti);
}

/*
 * eti_row_height:
 *
 * Returns the height used by row @row.  This does not include the one-pixel
 * used as a separator between rows
 */
static gint
eti_row_height (ETableItem *eti,
                gint row)
{
    if (eti->uniform_row_height) {
        eti->uniform_row_height_cache = eti_row_height_real (eti, -1);
        return eti->uniform_row_height_cache;
    } else {
        if (!eti->height_cache) {
            calculate_height_cache (eti);
        }
        if (eti->height_cache[row] == -1) {
            eti->height_cache[row] = eti_row_height_real (eti, row);
            if (row > 0 &&
                eti->length_threshold != -1 &&
                eti->rows > eti->length_threshold &&
                eti->height_cache[row] != eti_row_height (eti, 0)) {
                eti->needs_compute_height = 1;
                e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
            }
        }
        return eti->height_cache[row];
    }
}

/*
 * eti_get_height:
 *
 * Returns the height of the ETableItem.
 *
 * The ETableItem might compute the whole height by asking every row its
 * size.  There is a special mode (designed to work when there are too
 * many rows in the table that performing the previous step could take
 * too long) set by the ETableItem->length_threshold that would determine
 * when the height is computed by using the first row as the size for
 * every other row in the ETableItem.
 */
static gint
eti_get_height (ETableItem *eti)
{
    const gint rows = eti->rows;
    gint height_extra = eti->horizontal_draw_grid ? 1 : 0;

    if (rows == 0)
        return 0;

    if (eti->uniform_row_height) {
        gint row_height = ETI_ROW_HEIGHT (eti, -1);
        return ((row_height + height_extra) * rows + height_extra);
    } else {
        gint height;
        gint row;
        if (eti->length_threshold != -1) {
            if (rows > eti->length_threshold) {
                gint row_height = ETI_ROW_HEIGHT (eti, 0);
                if (eti->height_cache) {
                    height = 0;
                    for (row = 0; row < rows; row++) {
                        if (eti->height_cache[row] == -1) {
                            height += (row_height + height_extra) * (rows - row);
                            break;
                        }
                        else
                            height += eti->height_cache[row] + height_extra;
                    }
                } else
                    height = (ETI_ROW_HEIGHT (eti, 0) + height_extra) * rows;

                /*
                 * 1 pixel at the top
                 */
                return height + height_extra;
            }
        }

        height = height_extra;
        for (row = 0; row < rows; row++)
            height += ETI_ROW_HEIGHT (eti, row) + height_extra;

        return height;
    }
}

static void
eti_item_region_redraw (ETableItem *eti,
                        gint x0,
                        gint y0,
                        gint x1,
                        gint y1)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
    gdouble dx1, dy1, dx2, dy2;
    cairo_matrix_t i2c;

    dx1 = x0;
    dy1 = y0;
    dx2 = x1;
    dy2 = y1;

    gnome_canvas_item_i2c_matrix (item, &i2c);
    gnome_canvas_matrix_transform_rect (&i2c, &dx1, &dy1, &dx2, &dy2);

    gnome_canvas_request_redraw (item->canvas, floor (dx1), floor (dy1), ceil (dx2), ceil (dy2));
}

/*
 * Computes the distance between @start_row and @end_row in pixels
 */
gint
e_table_item_row_diff (ETableItem *eti,
                       gint start_row,
                       gint end_row)
{
    gint height_extra = eti->horizontal_draw_grid ? 1 : 0;

    if (start_row < 0)
        start_row = 0;
    if (end_row > eti->rows)
        end_row = eti->rows;

    if (eti->uniform_row_height) {
        return ((end_row - start_row) * (ETI_ROW_HEIGHT (eti, -1) + height_extra));
    } else {
        gint row, total;
        total = 0;
        for (row = start_row; row < end_row; row++)
            total += ETI_ROW_HEIGHT (eti, row) + height_extra;

        return total;
    }
}

static void
eti_get_region (ETableItem *eti,
                gint start_col,
                gint start_row,
                gint end_col,
                gint end_row,
                gint *x1p,
                gint *y1p,
                gint *x2p,
                gint *y2p)
{
    gint x1, y1, x2, y2;

    x1 = e_table_header_col_diff (eti->header, 0, start_col);
    y1 = e_table_item_row_diff (eti, 0, start_row);
    x2 = x1 + e_table_header_col_diff (eti->header, start_col, end_col + 1);
    y2 = y1 + e_table_item_row_diff (eti, start_row, end_row + 1);
    if (x1p)
        *x1p = x1;
    if (y1p)
        *y1p = y1;
    if (x2p)
        *x2p = x2;
    if (y2p)
        *y2p = y2;
}

/*
 * eti_request_region_redraw:
 *
 * Request a canvas redraw on the range (start_col, start_row) to (end_col, end_row).
 * This is inclusive (ie, you can use: 0,0-0,0 to redraw the first cell).
 *
 * The @border argument is a number of pixels around the region that should also be queued
 * for redraw.   This is typically used by the focus routines to queue a redraw for the
 * border as well.
 */
static void
eti_request_region_redraw (ETableItem *eti,
                           gint start_col,
                           gint start_row,
                           gint end_col,
                           gint end_row,
                           gint border)
{
    gint x1, y1, x2, y2;

    if (eti->rows > 0) {

        eti_get_region (
            eti,
            start_col, start_row,
            end_col, end_row,
            &x1, &y1, &x2, &y2);

        eti_item_region_redraw (
            eti,
            x1 - border,
            y1 - border,
            x2 + 1 + border,
            y2 + 1 + border);
    }
}

/*
 * eti_request_region_show
 *
 * Request a canvas show on the range (start_col, start_row) to (end_col, end_row).
 * This is inclusive (ie, you can use: 0,0-0,0 to show the first cell).
 */
static void
eti_request_region_show (ETableItem *eti,
                         gint start_col,
                         gint start_row,
                         gint end_col,
                         gint end_row,
                         gint delay)
{
    gint x1, y1, x2, y2;

    eti_get_region (
        eti,
        start_col, start_row,
        end_col, end_row,
        &x1, &y1, &x2, &y2);

    if (delay)
        e_canvas_item_show_area_delayed (
            GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2, delay);
    else
        e_canvas_item_show_area (
            GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2);
}

static void
eti_show_cursor (ETableItem *eti,
                 gint delay)
{
    GnomeCanvasItem *item;
    gint cursor_row;

    item = GNOME_CANVAS_ITEM (eti);

    if (!((item->flags & GNOME_CANVAS_ITEM_REALIZED) && eti->cell_views_realized))
        return;

    if (eti->frozen_count > 0) {
        eti->queue_show_cursor = TRUE;
        return;
    }

#if 0
    g_object_get (
        eti->selection,
        "cursor_row", &cursor_row,
        NULL);
#else
    cursor_row = e_selection_model_cursor_row (eti->selection);
#endif

    d (g_print ("%s: cursor row: %d\n", __FUNCTION__, cursor_row));

    if (cursor_row != -1) {
        cursor_row = model_to_view_row (eti, cursor_row);
        eti_request_region_show (
            eti,
            0, cursor_row, eti->cols - 1, cursor_row,
            delay);
    }
}

static void
eti_check_cursor_bounds (ETableItem *eti)
{
    GnomeCanvasItem *item;
    gint x1, y1, x2, y2;
    gint cursor_row;

    item = GNOME_CANVAS_ITEM (eti);

    if (!((item->flags & GNOME_CANVAS_ITEM_REALIZED) && eti->cell_views_realized))
        return;

    if (eti->frozen_count > 0) {
        return;
    }

    g_object_get (
        eti->selection,
        "cursor_row", &cursor_row,
        NULL);

    if (cursor_row == -1) {
        eti->cursor_x1 = -1;
        eti->cursor_y1 = -1;
        eti->cursor_x2 = -1;
        eti->cursor_y2 = -1;
        eti->cursor_on_screen = TRUE;
        return;
    }

    d (g_print ("%s: model cursor row: %d\n", __FUNCTION__, cursor_row));

    cursor_row = model_to_view_row (eti, cursor_row);

    d (g_print ("%s: cursor row: %d\n", __FUNCTION__, cursor_row));

    eti_get_region (
        eti,
        0, cursor_row, eti->cols - 1, cursor_row,
        &x1, &y1, &x2, &y2);
    eti->cursor_x1 = x1;
    eti->cursor_y1 = y1;
    eti->cursor_x2 = x2;
    eti->cursor_y2 = y2;
    eti->cursor_on_screen = e_canvas_item_area_shown (GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2);

    d (g_print ("%s: cursor on screen: %s\n", __FUNCTION__, eti->cursor_on_screen ? "TRUE" : "FALSE"));
}

static void
eti_maybe_show_cursor (ETableItem *eti,
                       gint delay)
{
    d (g_print ("%s: cursor on screen: %s\n", __FUNCTION__, eti->cursor_on_screen ? "TRUE" : "FALSE"));
    if (eti->cursor_on_screen)
        eti_show_cursor (eti, delay);
    eti_check_cursor_bounds (eti);
}

static gboolean
eti_idle_show_cursor_cb (gpointer data)
{
    ETableItem *eti = data;

    if (eti->selection) {
        eti_show_cursor (eti, 0);
        eti_check_cursor_bounds (eti);
    }

    eti->cursor_idle_id = 0;
    g_object_unref (eti);
    return FALSE;
}

static void
eti_idle_maybe_show_cursor (ETableItem *eti)
{
    d (g_print ("%s: cursor on screen: %s\n", __FUNCTION__, eti->cursor_on_screen ? "TRUE" : "FALSE"));
    if (eti->cursor_on_screen) {
        g_object_ref (eti);
        if (!eti->cursor_idle_id)
            eti->cursor_idle_id = g_idle_add (eti_idle_show_cursor_cb, eti);
    }
}

static void
eti_cancel_drag_due_to_model_change (ETableItem *eti)
{
    if (eti->maybe_in_drag) {
        eti->maybe_in_drag = FALSE;
        if (!eti->maybe_did_something)
            e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state);
    }
    if (eti->in_drag) {
        eti->in_drag = FALSE;
    }
}

static void
eti_freeze (ETableItem *eti)
{
    eti->frozen_count++;
    d (g_print ("%s: %d\n", __FUNCTION__, eti->frozen_count));
}

static void
eti_unfreeze (ETableItem *eti)
{
    if (eti->frozen_count <= 0)
        return;

    eti->frozen_count--;
    d (g_print ("%s: %d\n", __FUNCTION__, eti->frozen_count));
    if (eti->frozen_count == 0 && eti->queue_show_cursor) {
        eti_show_cursor (eti, 0);
        eti_check_cursor_bounds (eti);
        eti->queue_show_cursor = FALSE;
    }
}

/*
 * Callback routine: invoked before the ETableModel suffers a change
 */
static void
eti_table_model_pre_change (ETableModel *table_model,
                            ETableItem *eti)
{
    eti_cancel_drag_due_to_model_change (eti);
    eti_check_cursor_bounds (eti);
    if (eti_editing (eti))
        e_table_item_leave_edit_(eti);
    eti->motion_row = -1;
    eti->motion_col = -1;
    eti_freeze (eti);
}

/*
 * Callback routine: invoked when the ETableModel has not suffered a change
 */
static void
eti_table_model_no_change (ETableModel *table_model,
                           ETableItem *eti)
{
    eti_unfreeze (eti);
}

/*
 * Callback routine: invoked when the ETableModel has suffered a change
 */

static void
eti_table_model_changed (ETableModel *table_model,
                         ETableItem *eti)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

    if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
        eti_unfreeze (eti);
        return;
    }

    eti->rows = e_table_model_row_count (eti->table_model);

    free_height_cache (eti);

    eti_unfreeze (eti);

    eti->needs_compute_height = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));

    eti_idle_maybe_show_cursor (eti);
}

static void
eti_table_model_row_changed (ETableModel *table_model,
                             gint row,
                             ETableItem *eti)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

    if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
        eti_unfreeze (eti);
        return;
    }

    if ((!eti->uniform_row_height) && eti->height_cache && eti->height_cache[row] != -1 && eti_row_height_real (eti, row) != eti->height_cache[row]) {
        eti_table_model_changed (table_model, eti);
        return;
    }

    eti_unfreeze (eti);

    e_table_item_redraw_row (eti, row);
}

static void
eti_table_model_cell_changed (ETableModel *table_model,
                              gint col,
                              gint row,
                              ETableItem *eti)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

    if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
        eti_unfreeze (eti);
        return;
    }

    if ((!eti->uniform_row_height) && eti->height_cache && eti->height_cache[row] != -1 && eti_row_height_real (eti, row) != eti->height_cache[row]) {
        eti_table_model_changed (table_model, eti);
        return;
    }

    eti_unfreeze (eti);

    e_table_item_redraw_row (eti, row);
}

static void
eti_table_model_rows_inserted (ETableModel *table_model,
                               gint row,
                               gint count,
                               ETableItem *eti)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

    if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
        eti_unfreeze (eti);
        return;
    }
    eti->rows = e_table_model_row_count (eti->table_model);

    if (eti->height_cache) {
        gint i;
        eti->height_cache = g_renew (int, eti->height_cache, eti->rows);
        memmove (eti->height_cache + row + count, eti->height_cache + row, (eti->rows - count - row) * sizeof (gint));
        for (i = row; i < row + count; i++)
            eti->height_cache[i] = -1;
    }

    eti_unfreeze (eti);

    eti_idle_maybe_show_cursor (eti);

    eti->needs_compute_height = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

static void
eti_table_model_rows_deleted (ETableModel *table_model,
                              gint row,
                              gint count,
                              ETableItem *eti)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

    if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
        eti_unfreeze (eti);
        return;
    }

    eti->rows = e_table_model_row_count (eti->table_model);

    if (eti->height_cache && (eti->rows > row)) {
        memmove (eti->height_cache + row, eti->height_cache + row + count, (eti->rows - row) * sizeof (gint));
    }

    eti_unfreeze (eti);

    eti_idle_maybe_show_cursor (eti);

    eti->needs_compute_height = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

/**
 * e_table_item_redraw_range
 * @eti: %ETableItem which will be redrawn
 * @start_col: The first col to redraw.
 * @start_row: The first row to redraw.
 * @end_col: The last col to redraw.
 * @end_row: The last row to redraw.
 *
 * This routine redraws the given %ETableItem in the range given.  The
 * range is inclusive at both ends.
 */
void
e_table_item_redraw_range (ETableItem *eti,
                           gint start_col,
                           gint start_row,
                           gint end_col,
                           gint end_row)
{
    gint border;
    gint cursor_col, cursor_row;

    g_return_if_fail (eti != NULL);
    g_return_if_fail (E_IS_TABLE_ITEM (eti));

    g_object_get (
        eti->selection,
        "cursor_col", &cursor_col,
        "cursor_row", &cursor_row,
        NULL);

    if ((start_col == cursor_col) ||
        (end_col   == cursor_col) ||
        (view_to_model_row (eti, start_row) == cursor_row) ||
        (view_to_model_row (eti, end_row)   == cursor_row))
        border = 2;
    else
        border = 0;

    eti_request_region_redraw (eti, start_col, start_row, end_col, end_row, border);
}

static void
e_table_item_redraw_row (ETableItem *eti,
                         gint row)
{
    if (row != -1)
        e_table_item_redraw_range (eti, 0, row, eti->cols - 1, row);
}

static void
eti_add_table_model (ETableItem *eti,
                     ETableModel *table_model)
{
    g_return_if_fail (eti->table_model == NULL);

    eti->table_model = table_model;
    g_object_ref (eti->table_model);

    eti->table_model_pre_change_id = g_signal_connect (
        table_model, "model_pre_change",
        G_CALLBACK (eti_table_model_pre_change), eti);

    eti->table_model_no_change_id = g_signal_connect (
        table_model, "model_no_change",
        G_CALLBACK (eti_table_model_no_change), eti);

    eti->table_model_change_id = g_signal_connect (
        table_model, "model_changed",
        G_CALLBACK (eti_table_model_changed), eti);

    eti->table_model_row_change_id = g_signal_connect (
        table_model, "model_row_changed",
        G_CALLBACK (eti_table_model_row_changed), eti);

    eti->table_model_cell_change_id = g_signal_connect (
        table_model, "model_cell_changed",
        G_CALLBACK (eti_table_model_cell_changed), eti);

    eti->table_model_rows_inserted_id = g_signal_connect (
        table_model, "model_rows_inserted",
        G_CALLBACK (eti_table_model_rows_inserted), eti);

    eti->table_model_rows_deleted_id = g_signal_connect (
        table_model, "model_rows_deleted",
        G_CALLBACK (eti_table_model_rows_deleted), eti);

    if (eti->header) {
        eti_detach_cell_views (eti);
        eti_attach_cell_views (eti);
    }

    if (E_IS_TABLE_SUBSET (table_model)) {
        eti->uses_source_model = 1;
        eti->source_model = e_table_subset_get_source_model (
            E_TABLE_SUBSET (table_model));
        if (eti->source_model)
            g_object_ref (eti->source_model);
    }

    eti_freeze (eti);

    eti_table_model_changed (table_model, eti);
}

static void
eti_add_selection_model (ETableItem *eti,
                         ESelectionModel *selection)
{
    g_return_if_fail (eti->selection == NULL);

    eti->selection = selection;
    g_object_ref (eti->selection);

    eti->selection_change_id = g_signal_connect (
        selection, "selection_changed",
        G_CALLBACK (eti_selection_change), eti);

    eti->selection_row_change_id = g_signal_connect (
        selection, "selection_row_changed",
        G_CALLBACK (eti_selection_row_change), eti);

    eti->cursor_change_id = g_signal_connect (
        selection, "cursor_changed",
        G_CALLBACK (eti_cursor_change), eti);

    eti->cursor_activated_id = g_signal_connect (
        selection, "cursor_activated",
        G_CALLBACK (eti_cursor_activated), eti);

    eti_selection_change (selection, eti);
    g_signal_emit_by_name (eti, "selection_model_added", eti->selection);
}

static void
eti_header_dim_changed (ETableHeader *eth,
                        gint col,
                        ETableItem *eti)
{
    eti->needs_compute_width = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

static void
eti_header_structure_changed (ETableHeader *eth,
                              ETableItem *eti)
{
    eti->cols = e_table_header_count (eti->header);

    /*
     * There should be at least one column
     *  BUT: then you can't remove all columns from a header and add new ones.
     */

    if (eti->cell_views) {
        eti_unrealize_cell_views (eti);
        eti_detach_cell_views (eti);
        eti_attach_cell_views (eti);
        eti_realize_cell_views (eti);
    } else {
        if (eti->table_model) {
            eti_attach_cell_views (eti);
            eti_realize_cell_views (eti);
        }
    }
    eti->needs_compute_width = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

static gint
eti_request_column_width (ETableHeader *eth,
                          gint col,
                          ETableItem *eti)
{
    gint width = 0;

    if (eti->cell_views && eti->cell_views_realized) {
        width = e_cell_max_width (eti->cell_views[col], view_to_model_col (eti, col), col);
    }

    return width;
}

static void
eti_add_header_model (ETableItem *eti,
                      ETableHeader *header)
{
    g_return_if_fail (eti->header == NULL);

    eti->header = header;
    g_object_ref (header);

    eti_header_structure_changed (header, eti);

    eti->header_dim_change_id = g_signal_connect (
        header, "dimension_change",
        G_CALLBACK (eti_header_dim_changed), eti);

    eti->header_structure_change_id = g_signal_connect (
        header, "structure_change",
        G_CALLBACK (eti_header_structure_changed), eti);

    eti->header_request_width_id = g_signal_connect (
        header, "request_width",
        G_CALLBACK (eti_request_column_width), eti);
}

/*
 * GObject::dispose method
 */
static void
eti_dispose (GObject *object)
{
    ETableItem *eti = E_TABLE_ITEM (object);

    eti_remove_header_model (eti);
    eti_remove_table_model (eti);
    eti_remove_selection_model (eti);

    if (eti->height_cache_idle_id) {
        g_source_remove (eti->height_cache_idle_id);
        eti->height_cache_idle_id = 0;
    }
    eti->height_cache_idle_count = 0;

    if (eti->cursor_idle_id) {
        g_source_remove (eti->cursor_idle_id);
        eti->cursor_idle_id = 0;
    }

    if (eti->height_cache)
        g_free (eti->height_cache);
    eti->height_cache = NULL;

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

static void
eti_set_property (GObject *object,
                  guint property_id,
                  const GValue *value,
                  GParamSpec *pspec)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (object);
    ETableItem *eti = E_TABLE_ITEM (object);
    gint cursor_col;

    switch (property_id) {
    case PROP_TABLE_HEADER:
        eti_remove_header_model (eti);
        eti_add_header_model (eti, E_TABLE_HEADER (g_value_get_object (value)));
        break;

    case PROP_TABLE_MODEL:
        eti_remove_table_model (eti);
        eti_add_table_model (eti, E_TABLE_MODEL (g_value_get_object (value)));
        break;

    case PROP_SELECTION_MODEL:
        g_signal_emit_by_name (
            eti, "selection_model_removed", eti->selection);
        eti_remove_selection_model (eti);
        if (g_value_get_object (value))
            eti_add_selection_model (eti, E_SELECTION_MODEL (g_value_get_object (value)));
        break;

    case PROP_LENGTH_THRESHOLD:
        eti->length_threshold = g_value_get_int (value);
        break;

    case PROP_TABLE_ALTERNATING_ROW_COLORS:
        eti->alternating_row_colors = g_value_get_boolean (value);
        break;

    case PROP_TABLE_HORIZONTAL_DRAW_GRID:
        eti->horizontal_draw_grid = g_value_get_boolean (value);
        break;

    case PROP_TABLE_VERTICAL_DRAW_GRID:
        eti->vertical_draw_grid = g_value_get_boolean (value);
        break;

    case PROP_TABLE_DRAW_FOCUS:
        eti->draw_focus = g_value_get_boolean (value);
        break;

    case PROP_CURSOR_MODE:
        eti->cursor_mode = g_value_get_int (value);
        break;

    case PROP_MINIMUM_WIDTH:
    case PROP_WIDTH:
        if ((eti->minimum_width == eti->width && g_value_get_double (value) > eti->width) ||
            g_value_get_double (value) < eti->width) {
            eti->needs_compute_width = 1;
            e_canvas_item_request_reflow (item);
        }
        eti->minimum_width = g_value_get_double (value);
        break;
    case PROP_CURSOR_ROW:
        g_object_get (
            eti->selection,
            "cursor_col", &cursor_col,
            NULL);

        e_table_item_focus (eti, cursor_col != -1 ? cursor_col : 0, view_to_model_row (eti, g_value_get_int (value)), 0);
        break;
    case PROP_UNIFORM_ROW_HEIGHT:
        if (eti->uniform_row_height != g_value_get_boolean (value)) {
            eti->uniform_row_height = g_value_get_boolean (value);
            if (item->flags & GNOME_CANVAS_ITEM_REALIZED) {
                free_height_cache (eti);
                eti->needs_compute_height = 1;
                e_canvas_item_request_reflow (item);
                eti->needs_redraw = 1;
                gnome_canvas_item_request_update (item);
            }
        }
        break;
    }
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (item);
}

static void
eti_get_property (GObject *object,
                  guint property_id,
                  GValue *value,
                  GParamSpec *pspec)
{
    ETableItem *eti;
    gint row;

    eti = E_TABLE_ITEM (object);

    switch (property_id) {
    case PROP_WIDTH:
        g_value_set_double (value, eti->width);
        break;
    case PROP_HEIGHT:
        g_value_set_double (value, eti->height);
        break;
    case PROP_MINIMUM_WIDTH:
        g_value_set_double (value, eti->minimum_width);
        break;
    case PROP_CURSOR_ROW:
        g_object_get (
            eti->selection,
            "cursor_row", &row,
            NULL);
        g_value_set_int (value, model_to_view_row (eti, row));
        break;
    case PROP_UNIFORM_ROW_HEIGHT:
        g_value_set_boolean (value, eti->uniform_row_height);
        break;
    case PROP_IS_EDITING:
        g_value_set_boolean (value, e_table_item_is_editing (eti));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
e_table_item_init (ETableItem *eti)
{
    eti->motion_row        = -1;
    eti->motion_col        = -1;
    eti->editing_col               = -1;
    eti->editing_row               = -1;
    eti->height                    = 0;
    eti->width                     = 0;
    eti->minimum_width             = 0;

    eti->save_col                  = -1;
    eti->save_row                  = -1;
    eti->save_state                = NULL;

    eti->click_count               = 0;

    eti->height_cache              = NULL;
    eti->height_cache_idle_id      = 0;
    eti->height_cache_idle_count   = 0;

    eti->length_threshold          = -1;
    eti->uniform_row_height        = FALSE;

    eti->uses_source_model         = 0;
    eti->source_model              = NULL;

    eti->row_guess                 = -1;
    eti->cursor_mode               = E_CURSOR_SIMPLE;

    eti->selection_change_id       = 0;
    eti->selection_row_change_id   = 0;
    eti->cursor_change_id          = 0;
    eti->cursor_activated_id       = 0;
    eti->selection                 = NULL;

    eti->old_cursor_row            = -1;

    eti->needs_redraw              = 0;
    eti->needs_compute_height      = 0;

    eti->in_key_press              = 0;

    eti->maybe_did_something       = TRUE;

    eti->grabbed_count             = 0;
    eti->gtk_grabbed               = 0;

    eti->in_drag                   = 0;
    eti->maybe_in_drag             = 0;
    eti->grabbed                   = 0;

    eti->grabbed_col               = -1;
    eti->grabbed_row               = -1;

    eti->cursor_on_screen          = FALSE;
    eti->cursor_x1                 = -1;
    eti->cursor_y1                 = -1;
    eti->cursor_x2                 = -1;
    eti->cursor_y2                 = -1;

    eti->rows                      = -1;
    eti->cols                      = -1;

    eti->frozen_count              = 0;
    eti->queue_show_cursor         = FALSE;

    e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (eti), eti_reflow);
}

#define gray50_width 2
#define gray50_height 2
static const gchar gray50_bits[] = {
    0x02, 0x01, };

static gboolean
eti_tree_unfreeze (GtkWidget *widget,
                   GdkEvent *event,
                   ETableItem *eti)
{

    if (widget)
        g_object_set_data (G_OBJECT (widget), "freeze-cursor", NULL);

    return FALSE;
}

static void
eti_realize (GnomeCanvasItem *item)
{
    ETableItem *eti = E_TABLE_ITEM (item);

    if (GNOME_CANVAS_ITEM_CLASS (e_table_item_parent_class)->realize)
        (*GNOME_CANVAS_ITEM_CLASS (e_table_item_parent_class)->realize)(item);

    eti->rows = e_table_model_row_count (eti->table_model);

    g_signal_connect (
        item->canvas, "scroll_event",
        G_CALLBACK (eti_tree_unfreeze), eti);

    if (eti->cell_views == NULL)
        eti_attach_cell_views (eti);

    eti_realize_cell_views (eti);

    free_height_cache (eti);

    if (item->canvas->focused_item == NULL && eti->selection) {
        gint row;

        row = e_selection_model_cursor_row (E_SELECTION_MODEL (eti->selection));
        row = model_to_view_row (eti, row);
        if (row != -1) {
            e_canvas_item_grab_focus (item, FALSE);
            eti_show_cursor (eti, 0);
            eti_check_cursor_bounds (eti);
        }
    }

    eti->needs_compute_height = 1;
    eti->needs_compute_width = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

static void
eti_unrealize (GnomeCanvasItem *item)
{
    ETableItem *eti = E_TABLE_ITEM (item);

    if (eti->grabbed_count > 0) {
        d (g_print ("%s: eti_ungrab\n", __FUNCTION__));
        eti_ungrab (eti, -1);
    }

    if (eti_editing (eti))
        e_table_item_leave_edit_(eti);

    if (eti->height_cache_idle_id) {
        g_source_remove (eti->height_cache_idle_id);
        eti->height_cache_idle_id = 0;
    }

    if (eti->height_cache)
        g_free (eti->height_cache);
    eti->height_cache = NULL;
    eti->height_cache_idle_count = 0;

    eti_unrealize_cell_views (eti);

    eti->height = 0;

    if (GNOME_CANVAS_ITEM_CLASS (e_table_item_parent_class)->unrealize)
        (*GNOME_CANVAS_ITEM_CLASS (e_table_item_parent_class)->unrealize)(item);
}

static void
eti_draw_grid_line (ETableItem *eti,
                    cairo_t *cr,
                    GtkStyle *style,
                    gint x1,
                    gint y1,
                    gint x2,
                    gint y2)
{
    cairo_save (cr);

    cairo_set_line_width (cr, 1.0);
    gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_NORMAL]);

    cairo_move_to (cr, x1 + 0.5, y1 + 0.5);
    cairo_line_to (cr, x2 + 0.5, y2 + 0.5);
    cairo_stroke (cr);

    cairo_restore (cr);
}

static void
eti_draw (GnomeCanvasItem *item,
          cairo_t *cr,
          gint x,
          gint y,
          gint width,
          gint height)
{
    ETableItem *eti = E_TABLE_ITEM (item);
    const gint rows = eti->rows;
    const gint cols = eti->cols;
    gint row, col;
    gint first_col, last_col, x_offset;
    gint first_row, last_row, y_offset, yd;
    gint x1, x2;
    gint f_x1, f_x2, f_y1, f_y2;
    gboolean f_found;
    cairo_matrix_t i2c;
    gdouble eti_base_x, eti_base_y, lower_right_y, lower_right_x;
    GtkWidget *canvas = GTK_WIDGET (item->canvas);
    GtkStyle *style = gtk_widget_get_style (canvas);
    gint height_extra = eti->horizontal_draw_grid ? 1 : 0;

    /*
     * Find out our real position after grouping
     */
    gnome_canvas_item_i2c_matrix (item, &i2c);
    eti_base_x = 0;
    eti_base_y = 0;
    cairo_matrix_transform_point (&i2c, &eti_base_x, &eti_base_y);

    lower_right_x = eti->width;
    lower_right_y = eti->height;
    cairo_matrix_transform_point (&i2c, &lower_right_x, &lower_right_y);

    /*
     * First column to draw, last column to draw
     */
    first_col = -1;
    x_offset = 0;
    x1 = floor (eti_base_x);
    for (col = 0; col < cols; col++, x1 = x2) {
        ETableCol *ecol = e_table_header_get_column (eti->header, col);

        x2 = x1 + ecol->width;

        if (x1 > (x + width))
            break;
        if (x2 < x)
            continue;
        if (first_col == -1) {
            x_offset = x1 - x;
            first_col = col;
        }
    }
    last_col = col;

    /*
     * Nothing to paint
     */
    if (first_col == -1)
        return;

    /*
     * Compute row span.
     */
    if (eti->uniform_row_height) {
        first_row = (y          - floor (eti_base_y) - height_extra) / (ETI_ROW_HEIGHT (eti, -1) + height_extra);
        last_row  = (y + height - floor (eti_base_y)               ) / (ETI_ROW_HEIGHT (eti, -1) + height_extra) + 1;
        if (first_row > last_row)
            return;
        y_offset = floor (eti_base_y) - y + height_extra + first_row * (ETI_ROW_HEIGHT (eti, -1) + height_extra);
        if (first_row < 0)
            first_row = 0;
        if (last_row > eti->rows)
            last_row = eti->rows;
    } else {
        gint y1, y2;

        y_offset = 0;
        first_row = -1;

        y1 = y2 = floor (eti_base_y) + height_extra;
        for (row = 0; row < rows; row++, y1 = y2) {

            y2 += ETI_ROW_HEIGHT (eti, row) + height_extra;

            if (y1 > y + height)
                break;

            if (y2 < y)
                continue;

            if (first_row == -1) {
                y_offset = y1 - y;
                first_row = row;
            }
        }
        last_row = row;

        if (first_row == -1)
            return;
    }

    if (first_row == -1)
        return;

    /*
     * Draw cells
     */
    yd = y_offset;
    f_x1 = f_x2 = f_y1 = f_y2 = -1;
    f_found = FALSE;

    if (eti->horizontal_draw_grid && first_row == 0)
        eti_draw_grid_line (eti, cr, style, eti_base_x - x, yd, eti_base_x + eti->width - x, yd);

    yd += height_extra;

    for (row = first_row; row < last_row; row++) {
        gint xd;
        gboolean selected;
        gint cursor_col, cursor_row;

        height = ETI_ROW_HEIGHT (eti, row);

        xd = x_offset;

        selected = e_selection_model_is_row_selected (E_SELECTION_MODEL (eti->selection), view_to_model_row (eti,row));

        g_object_get (
            eti->selection,
            "cursor_col", &cursor_col,
            "cursor_row", &cursor_row,
            NULL);

        for (col = first_col; col < last_col; col++) {
            ETableCol *ecol = e_table_header_get_column (eti->header, col);
            ECellView *ecell_view = eti->cell_views[col];
            gboolean col_selected = selected;
            gboolean cursor = FALSE;
            ECellFlags flags;
            gboolean free_background;
            GdkColor *background;
            gint x1, x2, y1, y2;
            cairo_pattern_t *pat;

            switch (eti->cursor_mode) {
            case E_CURSOR_SIMPLE:
            case E_CURSOR_SPREADSHEET:
                if (cursor_col == ecol->spec->model_col && cursor_row == view_to_model_row (eti, row)) {
                    col_selected = !col_selected;
                    cursor = TRUE;
                }
                break;
            case E_CURSOR_LINE:
                /* Nothing */
                break;
            }

            x1 = xd;
            y1 = yd + 1;
            x2 = x1 + ecol->width;
            y2 = yd + height;

            background = eti_get_cell_background_color (eti, row, col, col_selected, &free_background);

            cairo_save (cr);
            pat = cairo_pattern_create_linear (0, y1, 0, y2);
            cairo_pattern_add_color_stop_rgba (
                pat, 0.0, background->red / 65535.0 ,
                background->green / 65535.0,
                background->blue / 65535.0, selected ? 0.8: 1.0);
            if (selected)
                cairo_pattern_add_color_stop_rgba (
                    pat, 0.5, background->red / 65535.0 ,
                    background->green / 65535.0,
                    background->blue / 65535.0, 0.9);

            cairo_pattern_add_color_stop_rgba (
                pat, 1, background->red / 65535.0 ,
                background->green / 65535.0,
                background->blue / 65535.0, selected ? 0.8 : 1.0);
            cairo_rectangle (cr, x1, y1, ecol->width, height - 1);
            cairo_set_source (cr, pat);
            cairo_fill_preserve (cr);
            cairo_pattern_destroy (pat);
            cairo_set_line_width (cr, 0);
            cairo_stroke (cr);
            cairo_restore (cr);

            cairo_save (cr);
            cairo_set_line_width (cr, 1.0);
            cairo_set_source_rgba (
                cr, background->red / 65535.0 ,
                background->green / 65535.0,
                background->blue / 65535.0, 1);
            cairo_move_to (cr, x1, y1);
            cairo_line_to (cr, x2, y1);
            cairo_stroke (cr);

            cairo_set_line_width (cr, 1.0);
            cairo_set_source_rgba (
                cr, background->red / 65535.0 ,
                background->green / 65535.0,
                background->blue / 65535.0, 1);
            cairo_move_to (cr, x1, y2);
            cairo_line_to (cr, x2, y2);
            cairo_stroke (cr);
            cairo_restore (cr);

            if (free_background)
                gdk_color_free (background);

            flags = col_selected ? E_CELL_SELECTED : 0;
            flags |= gtk_widget_has_focus (canvas) ? E_CELL_FOCUSED : 0;
            flags |= cursor ? E_CELL_CURSOR : 0;

            switch (ecol->justification) {
            case GTK_JUSTIFY_LEFT:
                flags |= E_CELL_JUSTIFY_LEFT;
                break;
            case GTK_JUSTIFY_RIGHT:
                flags |= E_CELL_JUSTIFY_RIGHT;
                break;
            case GTK_JUSTIFY_CENTER:
                flags |= E_CELL_JUSTIFY_CENTER;
                break;
            case GTK_JUSTIFY_FILL:
                flags |= E_CELL_JUSTIFY_FILL;
                break;
            }

            e_cell_draw (
                ecell_view, cr,
                ecol->spec->model_col,
                col, row, flags,
                xd, yd,
                xd + ecol->width, yd + height);

            if (!f_found && !selected) {
                switch (eti->cursor_mode) {
                case E_CURSOR_LINE:
                    if (view_to_model_row (eti, row) == cursor_row) {
                        f_x1 = floor (eti_base_x) - x;
                        f_x2 = floor (lower_right_x) - x;
                        f_y1 = yd + 1;
                        f_y2 = yd + height;
                        f_found = TRUE;
                    }
                    break;
                case E_CURSOR_SIMPLE:
                case E_CURSOR_SPREADSHEET:
                    if (view_to_model_col (eti, col) == cursor_col && view_to_model_row (eti, row) == cursor_row) {
                        f_x1 = xd;
                        f_x2 = xd + ecol->width;
                        f_y1 = yd;
                        f_y2 = yd + height;
                        f_found = TRUE;
                    }
                    break;
                }
            }

            xd += ecol->width;
        }
        yd += height;

        if (eti->horizontal_draw_grid) {
            eti_draw_grid_line (eti, cr, style, eti_base_x - x, yd, eti_base_x + eti->width - x, yd);
            yd++;
        }
    }

    if (eti->vertical_draw_grid) {
        gint xd = x_offset;

        for (col = first_col; col <= last_col; col++) {
            ETableCol *ecol = e_table_header_get_column (eti->header, col);

            eti_draw_grid_line (eti, cr, style, xd, y_offset, xd, yd - 1);

            /*
             * This looks wierd, but it is to draw the last line
             */
            if (ecol)
                xd += ecol->width;
        }
    }

    /*
     * Draw focus
     */
    if (eti->draw_focus && f_found) {
        static const double dash[] = { 1.0, 1.0 };
        cairo_set_line_width (cr, 1.0);
        cairo_rectangle (
            cr,
            f_x1 + 0.5, f_x2 + 0.5,
            f_x2 - f_x1 - 1, f_y2 - f_y1);

        gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]);
        cairo_stroke_preserve (cr);

        cairo_set_dash (cr, dash, G_N_ELEMENTS (dash), 0.0);
        gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]);
        cairo_stroke (cr);
    }
}

static GnomeCanvasItem *
eti_point (GnomeCanvasItem *item,
           gdouble x,
           gdouble y,
           gint cx,
           gint cy)
{
    return item;
}

static gboolean
find_cell (ETableItem *eti,
           gdouble x,
           gdouble y,
           gint *view_col_res,
           gint *view_row_res,
           gdouble *x1_res,
           gdouble *y1_res)
{
    const gint cols = eti->cols;
    const gint rows = eti->rows;
    gdouble x1, y1, x2, y2;
    gint col, row;

    gint height_extra = eti->horizontal_draw_grid ? 1 : 0;

    /* FIXME: this routine is inneficient, fix later */

    if (eti->grabbed_col >= 0 && eti->grabbed_row >= 0) {
        *view_col_res = eti->grabbed_col;
        *view_row_res = eti->grabbed_row;
        *x1_res = x - e_table_header_col_diff (eti->header, 0, eti->grabbed_col);
        *y1_res = y - e_table_item_row_diff (eti, 0, eti->grabbed_row);
        return TRUE;
    }

    if (cols == 0 || rows == 0)
        return FALSE;

    x1 = 0;
    for (col = 0; col < cols - 1; col++, x1 = x2) {
        ETableCol *ecol = e_table_header_get_column (eti->header, col);

        if (x < x1)
            return FALSE;

        x2 = x1 + ecol->width;

        if (x <= x2)
            break;
    }

    if (eti->uniform_row_height) {
        if (y < height_extra)
            return FALSE;
        row = (y - height_extra) / (ETI_ROW_HEIGHT (eti, -1) + height_extra);
        y1 = row * (ETI_ROW_HEIGHT (eti, -1) + height_extra) + height_extra;
        if (row >= eti->rows)
            return FALSE;
    } else {
        y1 = y2 = height_extra;
        if (y < height_extra)
            return FALSE;
        for (row = 0; row < rows; row++, y1 = y2) {
            y2 += ETI_ROW_HEIGHT (eti, row) + height_extra;

            if (y <= y2)
                break;
        }

        if (row == rows)
            return FALSE;
    }
    *view_col_res = col;
    if (x1_res)
        *x1_res = x - x1;
    *view_row_res = row;
    if (y1_res)
        *y1_res = y - y1;
    return TRUE;
}

static void
eti_cursor_move (ETableItem *eti,
                 gint row,
                 gint column)
{
    e_table_item_leave_edit_(eti);
    e_table_item_focus (eti, view_to_model_col (eti, column), view_to_model_row (eti, row), 0);
}

static void
eti_cursor_move_left (ETableItem *eti)
{
    gint cursor_col, cursor_row;
    g_object_get (
        eti->selection,
        "cursor_col", &cursor_col,
        "cursor_row", &cursor_row,
        NULL);

    eti_cursor_move (eti, model_to_view_row (eti, cursor_row), model_to_view_col (eti, cursor_col) - 1);
}

static void
eti_cursor_move_right (ETableItem *eti)
{
    gint cursor_col, cursor_row;
    g_object_get (
        eti->selection,
        "cursor_col", &cursor_col,
        "cursor_row", &cursor_row,
        NULL);

    eti_cursor_move (eti, model_to_view_row (eti, cursor_row), model_to_view_col (eti, cursor_col) + 1);
}

static gint
eti_e_cell_event (ETableItem *item,
                  ECellView *ecell_view,
                  GdkEvent *event,
                  gint model_col,
                  gint view_col,
                  gint row,
                  ECellFlags flags)
{
    ECellActions actions = 0;
    gint ret_val;

    ret_val = e_cell_event (
        ecell_view, event, model_col, view_col, row, flags, &actions);

    if (actions & E_CELL_GRAB) {
        GdkDevice *event_device;
        guint32 event_time;

        d (g_print ("%s: eti_grab\n", __FUNCTION__));

        event_device = gdk_event_get_device (event);
        event_time = gdk_event_get_time (event);
        eti_grab (item, event_device, event_time);

        item->grabbed_col = view_col;
        item->grabbed_row = row;
    }

    if (actions & E_CELL_UNGRAB) {
        guint32 event_time;

        d (g_print ("%s: eti_ungrab\n", __FUNCTION__));

        event_time = gdk_event_get_time (event);
        eti_ungrab (item, event_time);

        item->grabbed_col = -1;
        item->grabbed_row = -1;
    }

    return ret_val;
}

/* FIXME: cursor */
static gint
eti_event (GnomeCanvasItem *item,
           GdkEvent *event)
{
    ETableItem *eti = E_TABLE_ITEM (item);
    ECellView *ecell_view;
    GdkModifierType event_state = 0;
    GdkEvent *event_copy;
    guint event_button = 0;
    guint event_keyval = 0;
    gdouble event_x_item = 0;
    gdouble event_y_item = 0;
    gdouble event_x_win = 0;
    gdouble event_y_win = 0;
    guint32 event_time;
    gboolean return_val = TRUE;
#if d(!)0
    gboolean leave = FALSE;
#endif

    if (!eti->header)
        return FALSE;

    /* Don't fetch the device here.  GnomeCanvas frequently emits
     * synthesized events, and calling gdk_event_get_device() on them
     * will trigger a runtime warning.  Fetch the device where needed. */
    gdk_event_get_button (event, &event_button);
    gdk_event_get_coords (event, &event_x_win, &event_y_win);
    gdk_event_get_keyval (event, &event_keyval);
    gdk_event_get_state (event, &event_state);
    event_time = gdk_event_get_time (event);

    switch (event->type) {
    case GDK_BUTTON_PRESS: {
        gdouble x1, y1;
        gint col, row;
        gint cursor_row, cursor_col;
        gint new_cursor_row, new_cursor_col;
        ECellFlags flags = 0;

        d (g_print ("%s: GDK_BUTTON_PRESS received, button %d\n", __FUNCTION__, event_button));

        switch (event_button) {
        case 1: /* Fall through. */
        case 2:
            e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), TRUE);

            event_x_item = event_x_win;
            event_y_item = event_y_win;

            gnome_canvas_item_w2i (
                item, &event_x_item, &event_y_item);

            if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1)) {
                if (eti_editing (eti))
                    e_table_item_leave_edit_(eti);
                return TRUE;
            }

            ecell_view = eti->cell_views[col];

            /* Clone the event and alter its position. */
            event_copy = gdk_event_copy (event);
            event_copy->button.x = x1;
            event_copy->button.y = y1;

            g_object_get (
                eti->selection,
                "cursor_row", &cursor_row,
                "cursor_col", &cursor_col,
                NULL);

            if (cursor_col == view_to_model_col (eti, col) && cursor_row == view_to_model_row (eti, row)) {
                flags = E_CELL_CURSOR;
            } else {
                flags = 0;
            }

            return_val = eti_e_cell_event (
                eti, ecell_view, event_copy,
                view_to_model_col (eti, col),
                col, row, flags);
            if (return_val) {
                gdk_event_free (event_copy);
                return TRUE;
            }

            g_signal_emit (
                eti, eti_signals[CLICK], 0,
                row, view_to_model_col (eti, col),
                event_copy, &return_val);

            gdk_event_free (event_copy);

            if (return_val) {
                eti->click_count = 0;
                return TRUE;
            }

            g_object_get (
                eti->selection,
                "cursor_row", &cursor_row,
                "cursor_col", &cursor_col,
                NULL);

            eti->maybe_did_something =
                e_selection_model_maybe_do_something (
                E_SELECTION_MODEL (eti->selection),
                view_to_model_row (eti, row),
                view_to_model_col (eti, col),
                event_state);
            g_object_get (
                eti->selection,
                "cursor_row", &new_cursor_row,
                "cursor_col", &new_cursor_col,
                NULL);

            if (cursor_row != new_cursor_row || cursor_col != new_cursor_col) {
                eti->click_count = 1;
            } else {
                eti->click_count++;
                eti->row_guess = row;

                if ((!eti_editing (eti)) && e_table_model_is_cell_editable (eti->table_model, cursor_col, row)) {
                    e_table_item_enter_edit (eti, col, row);
                }

                /*
                 * Adjust the event positions
                 */

                if (eti_editing (eti)) {
                    return_val = eti_e_cell_event (
                        eti, ecell_view, event,
                        view_to_model_col (eti, col),
                        col, row,
                        E_CELL_EDITING |
                        E_CELL_CURSOR);
                    if (return_val)
                        return TRUE;
                }
            }

            if (event_button == 1) {
                GdkDevice *event_device;

                return_val = TRUE;

                event_device = gdk_event_get_device (event);

                eti->maybe_in_drag = TRUE;
                eti->drag_row      = new_cursor_row;
                eti->drag_col      = new_cursor_col;
                eti->drag_x        = event_x_item;
                eti->drag_y        = event_y_item;
                eti->drag_state    = event_state;
                eti->grabbed       = TRUE;
                d (g_print ("%s: eti_grab\n", __FUNCTION__));
                eti_grab (eti, event_device, event_time);
            }

            break;
        case 3:
            e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), TRUE);

            event_x_item = event_x_win;
            event_y_item = event_y_win;

            gnome_canvas_item_w2i (
                item, &event_x_item, &event_y_item);

            if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1))
                return TRUE;

            e_selection_model_right_click_down (
                E_SELECTION_MODEL (eti->selection),
                view_to_model_row (eti, row),
                view_to_model_col (eti, col), 0);

            /* Clone the event and alter its position. */
            event_copy = gdk_event_copy (event);
            event_copy->button.x = event_x_item;
            event_copy->button.y = event_y_item;

            g_signal_emit (
                eti, eti_signals[RIGHT_CLICK], 0,
                row, view_to_model_col (eti, col),
                event, &return_val);

            gdk_event_free (event_copy);

            if (!return_val)
                e_selection_model_right_click_up (E_SELECTION_MODEL (eti->selection));
            break;
        case 4:
        case 5:
            return FALSE;

        }
        break;
    }

    case GDK_BUTTON_RELEASE: {
        gdouble x1, y1;
        gint col, row;
        gint cursor_row, cursor_col;

        d (g_print ("%s: GDK_BUTTON_RELEASE received, button %d\n", __FUNCTION__, event_button));

        if (eti->grabbed_count > 0) {
            d (g_print ("%s: eti_ungrab\n", __FUNCTION__));
            eti_ungrab (eti, event_time);
        }

        if (event_button == 1) {
            if (eti->maybe_in_drag) {
                eti->maybe_in_drag = FALSE;
                if (!eti->maybe_did_something)
                    e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state);
            }
            if (eti->in_drag) {
                eti->in_drag = FALSE;
            }
        }

        switch (event_button) {
        case 1: /* Fall through. */
        case 2:

            event_x_item = event_x_win;
            event_y_item = event_y_win;

            gnome_canvas_item_w2i (
                item, &event_x_item, &event_y_item);
#if d(!)0
            {
                gboolean cell_found = find_cell (
                    eti, event_x_item, event_y_item,
                    &col, &row, &x1, &y1);
                g_print (
                    "%s: find_cell(%f, %f) = %s(%d, %d, %f, %f)\n",
                    __FUNCTION__, event_x_item, event_y_item,
                    cell_found?"true":"false", col, row, x1, y1);
            }
#endif

            if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1))
                return TRUE;

            g_object_get (
                eti->selection,
                "cursor_row", &cursor_row,
                "cursor_col", &cursor_col,
                NULL);

            if (eti_editing (eti) && cursor_row == view_to_model_row (eti, row) && cursor_col == view_to_model_col (eti, col)) {

                d (g_print ("%s: GDK_BUTTON_RELEASE received, button %d, line: %d\n", __FUNCTION__, event_button, __LINE__))
;

                ecell_view = eti->cell_views[col];

                /* Clone the event and alter its position. */
                event_copy = gdk_event_copy (event);
                event_copy->button.x = x1;
                event_copy->button.y = y1;

                return_val = eti_e_cell_event (
                    eti, ecell_view, event_copy,
                    view_to_model_col (eti, col),
                    col, row,
                    E_CELL_EDITING |
                    E_CELL_CURSOR);

                gdk_event_free (event_copy);
            }
            break;
        case 3:
            e_selection_model_right_click_up (E_SELECTION_MODEL (eti->selection));
            return_val = TRUE;
            break;
        case 4:
        case 5:
            return FALSE;

        }
        break;
    }

    case GDK_2BUTTON_PRESS: {
        gint model_col, model_row;
#if 0
        gdouble x1, y1;
#endif

        d (g_print ("%s: GDK_2BUTTON_PRESS received, button %d\n", __FUNCTION__, event_button));

        /*
         * click_count is so that if you click on two
         * different rows we don't send a double click signal.
         */

        if (eti->click_count >= 2) {

            event_x_item = event_x_win;
            event_y_item = event_y_win;

            gnome_canvas_item_w2i (
                item, &event_x_item, &event_y_item);

            g_object_get (
                eti->selection,
                "cursor_row", &model_row,
                "cursor_col", &model_col,
                NULL);

            /* Clone the event and alter its position. */
            event_copy = gdk_event_copy (event);
            event_copy->button.x = event_x_item -
                e_table_header_col_diff (
                    eti->header, 0,
                    model_to_view_col (eti, model_col));
            event_copy->button.y = event_y_item -
                e_table_item_row_diff (
                    eti, 0,
                    model_to_view_row (eti, model_row));

            if (event_button == 1) {
                if (eti->maybe_in_drag) {
                    eti->maybe_in_drag = FALSE;
                    if (!eti->maybe_did_something)
                        e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state);
                }
                if (eti->in_drag) {
                    eti->in_drag = FALSE;
                }
                if (eti_editing (eti))
                    e_table_item_leave_edit_ (eti);

            }

            if (eti->grabbed_count > 0) {
                d (g_print ("%s: eti_ungrab\n", __FUNCTION__));
                eti_ungrab (eti, event_time);
            }

            if (model_row != -1 && model_col != -1) {
                g_signal_emit (
                    eti, eti_signals[DOUBLE_CLICK], 0,
                    model_row, model_col, event_copy);
            }

            gdk_event_free (event_copy);
        }
        break;
    }
    case GDK_MOTION_NOTIFY: {
        gint col, row, flags;
        gdouble x1, y1;
        gint cursor_col, cursor_row;

        event_x_item = event_x_win;
        event_y_item = event_y_win;

        gnome_canvas_item_w2i (item, &event_x_item, &event_y_item);

        if (eti->maybe_in_drag) {
            if (abs (event_x_item - eti->drag_x) >= 3 ||
                abs (event_y_item - eti->drag_y) >= 3) {
                gboolean drag_handled;

                eti->maybe_in_drag = 0;

                /* Clone the event and
                 * alter its position. */
                event_copy = gdk_event_copy (event);
                event_copy->motion.x = event_x_item;
                event_copy->motion.y = event_y_item;

                g_signal_emit (
                    eti, eti_signals[START_DRAG], 0,
                    eti->drag_row, eti->drag_col,
                    event_copy, &drag_handled);

                gdk_event_free (event_copy);

                if (drag_handled)
                    eti->in_drag = 1;
                else
                    eti->in_drag = 0;
            }
        }

        if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1))
            return TRUE;

        if (eti->motion_row != -1 && eti->motion_col != -1 &&
            (row != eti->motion_row || col != eti->motion_col)) {
            GdkEvent *cross = gdk_event_new (GDK_LEAVE_NOTIFY);
            cross->crossing.time = event_time;
            return_val = eti_e_cell_event (
                eti, eti->cell_views[eti->motion_col],
                cross,
                view_to_model_col (eti, eti->motion_col),
                eti->motion_col, eti->motion_row, 0);
        }

        eti->motion_row = row;
        eti->motion_col = col;

        g_object_get (
            eti->selection,
            "cursor_row", &cursor_row,
            "cursor_col", &cursor_col,
            NULL);

        flags = 0;
        if (cursor_row == view_to_model_row (eti, row) && cursor_col == view_to_model_col (eti, col)) {
            flags = E_CELL_EDITING | E_CELL_CURSOR;
        }

        ecell_view = eti->cell_views[col];

        /* Clone the event and alter its position. */
        event_copy = gdk_event_copy (event);
        event_copy->motion.x = x1;
        event_copy->motion.y = y1;

        return_val = eti_e_cell_event (
            eti, ecell_view, event_copy,
            view_to_model_col (eti, col), col, row, flags);

        gdk_event_free (event_copy);

        break;
    }

    case GDK_KEY_PRESS: {
        gint cursor_row, cursor_col;
        gint handled = TRUE;

        d (g_print ("%s: GDK_KEY_PRESS received, keyval: %d\n", __FUNCTION__, (gint) e->key.keyval));

        g_object_get (
            eti->selection,
            "cursor_row", &cursor_row,
            "cursor_col", &cursor_col,
            NULL);

        if (cursor_row == -1 && cursor_col == -1)
            return FALSE;

        eti->in_key_press = TRUE;

        switch (event_keyval) {
        case GDK_KEY_Left:
        case GDK_KEY_KP_Left:
            if (eti_editing (eti)) {
                handled = FALSE;
                break;
            }

            g_signal_emit (
                eti, eti_signals[KEY_PRESS], 0,
                model_to_view_row (eti, cursor_row),
                cursor_col, event, &return_val);
            if ((!return_val) &&
               (atk_get_root () || eti->cursor_mode != E_CURSOR_LINE) &&
               cursor_col != view_to_model_col (eti, 0))
                eti_cursor_move_left (eti);
            return_val = 1;
            break;

        case GDK_KEY_Right:
        case GDK_KEY_KP_Right:
            if (eti_editing (eti)) {
                handled = FALSE;
                break;
            }

            g_signal_emit (
                eti, eti_signals[KEY_PRESS], 0,
                model_to_view_row (eti, cursor_row),
                cursor_col, event, &return_val);
            if ((!return_val) &&
               (atk_get_root () || eti->cursor_mode != E_CURSOR_LINE) &&
               cursor_col != view_to_model_col (eti, eti->cols - 1))
                eti_cursor_move_right (eti);
            return_val = 1;
            break;

        case GDK_KEY_Up:
        case GDK_KEY_KP_Up:
        case GDK_KEY_Down:
        case GDK_KEY_KP_Down:
            if ((event_state & GDK_MOD1_MASK)
                && ((event_keyval == GDK_KEY_Down) || (event_keyval == GDK_KEY_KP_Down))) {
                gint view_col = model_to_view_col (eti, cursor_col);

                if ((view_col >= 0) && (view_col < eti->cols))
                    if (eti_e_cell_event (eti, eti->cell_views[view_col], event, cursor_col, view_col, model_to_view_row (eti, cursor_row),  E_CELL_CURSOR))
                        return TRUE;
            } else
            return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
            break;
        case GDK_KEY_Home:
        case GDK_KEY_KP_Home:
            if (eti_editing (eti)) {
                handled = FALSE;
                break;
            }

            if (eti->cursor_mode != E_CURSOR_LINE) {
                eti_cursor_move (eti, model_to_view_row (eti, cursor_row), 0);
                return_val = TRUE;
            } else
                return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
            break;
        case GDK_KEY_End:
        case GDK_KEY_KP_End:
            if (eti_editing (eti)) {
                handled = FALSE;
                break;
            }

            if (eti->cursor_mode != E_CURSOR_LINE) {
                eti_cursor_move (eti, model_to_view_row (eti, cursor_row), eti->cols - 1);
                return_val = TRUE;
            } else
                return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
            break;
        case GDK_KEY_Tab:
        case GDK_KEY_KP_Tab:
        case GDK_KEY_ISO_Left_Tab:
            if ((event_state & GDK_CONTROL_MASK) != 0) {
                return_val = FALSE;
                break;
            }
            if (eti->cursor_mode == E_CURSOR_SPREADSHEET) {
                if ((event_state & GDK_SHIFT_MASK) != 0) {
                /* shift tab */
                    if (cursor_col != view_to_model_col (eti, 0))
                        eti_cursor_move_left (eti);
                    else if (cursor_row != view_to_model_row (eti, 0))
                        eti_cursor_move (eti, model_to_view_row (eti, cursor_row) - 1, eti->cols - 1);
                    else
                        return_val = FALSE;
                } else {
                    if (cursor_col != view_to_model_col (eti, eti->cols - 1))
                        eti_cursor_move_right (eti);
                    else if (cursor_row != view_to_model_row (eti, eti->rows - 1))
                        eti_cursor_move (eti, model_to_view_row (eti, cursor_row) + 1, 0);
                    else
                        return_val = FALSE;
                }
                g_object_get (
                    eti->selection,
                    "cursor_row", &cursor_row,
                    "cursor_col", &cursor_col,
                    NULL);

                if (cursor_col >= 0 && cursor_row >= 0 && return_val &&
                    (!eti_editing (eti)) && e_table_model_is_cell_editable (eti->table_model, cursor_col, model_to_view_row (eti, cursor_row))) {
                    e_table_item_enter_edit (eti, model_to_view_col (eti, cursor_col), model_to_view_row (eti, cursor_row));
                }
                break;
            } else {
            /* Let tab send you to the next widget. */
            return_val = FALSE;
            break;
            }

        case GDK_KEY_Return:
        case GDK_KEY_KP_Enter:
        case GDK_KEY_ISO_Enter:
        case GDK_KEY_3270_Enter:
            if (eti_editing (eti)) {
                ecell_view = eti->cell_views[eti->editing_col];
                return_val = eti_e_cell_event (
                    eti, ecell_view, event,
                    view_to_model_col (eti, eti->editing_col),
                    eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR | E_CELL_PREEDIT);
                if (!return_val)
                    break;
            }
            g_signal_emit (
                eti, eti_signals[KEY_PRESS], 0,
                model_to_view_row (eti, cursor_row),
                cursor_col, event, &return_val);
            if (!return_val)
                return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
            break;

        default:
            handled = FALSE;
            break;
        }

        if (!handled) {
            switch (event_keyval) {
            case GDK_KEY_Scroll_Lock:
            case GDK_KEY_Sys_Req:
            case GDK_KEY_Shift_L:
            case GDK_KEY_Shift_R:
            case GDK_KEY_Control_L:
            case GDK_KEY_Control_R:
            case GDK_KEY_Caps_Lock:
            case GDK_KEY_Shift_Lock:
            case GDK_KEY_Meta_L:
            case GDK_KEY_Meta_R:
            case GDK_KEY_Alt_L:
            case GDK_KEY_Alt_R:
            case GDK_KEY_Super_L:
            case GDK_KEY_Super_R:
            case GDK_KEY_Hyper_L:
            case GDK_KEY_Hyper_R:
            case GDK_KEY_ISO_Lock:
                break;

            default:
                if (!eti_editing (eti)) {
                    gint col, row;
                    row = model_to_view_row (eti, cursor_row);
                    col = model_to_view_col (eti, cursor_col);
                    if (col != -1 && row != -1 && e_table_model_is_cell_editable (eti->table_model, cursor_col, row)) {
                        e_table_item_enter_edit (eti, col, row);
                    }
                }
                if (!eti_editing (eti)) {
                    g_signal_emit (
                        eti, eti_signals[KEY_PRESS], 0,
                        model_to_view_row (eti, cursor_row),
                        cursor_col, event, &return_val);
                    if (!return_val)
                        e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
                } else {
                    ecell_view = eti->cell_views[eti->editing_col];
                    return_val = eti_e_cell_event (
                        eti, ecell_view, event,
                        view_to_model_col (eti, eti->editing_col),
                        eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR);
                    if (!return_val)
                        e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
                }
                break;
            }
        }
        eti->in_key_press = FALSE;
        break;
    }

    case GDK_KEY_RELEASE: {
        gint cursor_row, cursor_col;

        d (g_print ("%s: GDK_KEY_RELEASE received, keyval: %d\n", __FUNCTION__, (gint) event_keyval));

        g_object_get (
            eti->selection,
            "cursor_row", &cursor_row,
            "cursor_col", &cursor_col,
            NULL);

        if (cursor_col == -1)
            return FALSE;

        if (eti_editing (eti)) {
            ecell_view = eti->cell_views[eti->editing_col];
            return_val = eti_e_cell_event (
                eti, ecell_view, event,
                view_to_model_col (eti, eti->editing_col),
                eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR);
        }
        break;
    }

    case GDK_LEAVE_NOTIFY:
        d (leave = TRUE);
    case GDK_ENTER_NOTIFY:
        d (g_print ("%s: %s received\n", __FUNCTION__, leave ? "GDK_LEAVE_NOTIFY" : "GDK_ENTER_NOTIFY"));
        if (eti->motion_row != -1 && eti->motion_col != -1)
            return_val = eti_e_cell_event (
                eti, eti->cell_views[eti->motion_col],
                event,
                view_to_model_col (eti, eti->motion_col),
                eti->motion_col, eti->motion_row, 0);
        eti->motion_row = -1;
        eti->motion_col = -1;

        break;

    case GDK_FOCUS_CHANGE:
        d (g_print ("%s: GDK_FOCUS_CHANGE received, %s\n", __FUNCTION__, e->focus_change.in ? "in": "out"));
        if (event->focus_change.in) {
            if (eti->save_row != -1 &&
                eti->save_col != -1 &&
                !eti_editing (eti) &&
                e_table_model_is_cell_editable (eti->table_model, view_to_model_col (eti, eti->save_col), eti->save_row)) {
                e_table_item_enter_edit (eti, eti->save_col, eti->save_row);
                e_cell_load_state (
                    eti->cell_views[eti->editing_col], view_to_model_col (eti, eti->save_col),
                    eti->save_col, eti->save_row, eti->edit_ctx, eti->save_state);
                eti_free_save_state (eti);
            }
        } else {
            if (eti_editing (eti)) {
                eti_free_save_state (eti);

                eti->save_row   = eti->editing_row;
                eti->save_col   = eti->editing_col;
                eti->save_state = e_cell_save_state (
                    eti->cell_views[eti->editing_col], view_to_model_col (eti, eti->editing_col),
                    eti->editing_col, eti->editing_row, eti->edit_ctx);
                e_table_item_leave_edit_(eti);
            }
        }

    default:
        return_val = FALSE;
    }
    /* d(g_print("%s: returning: %s\n", __FUNCTION__, return_val?"true":"false"));*/

    return return_val;
}

static void
eti_style_set (ETableItem *eti,
               GtkStyle *previous_style)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

    if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
        return;

    if (eti->cell_views_realized) {
        gint i;
        gint n_cells = eti->n_cells;

        for (i = 0; i < n_cells; i++) {
            e_cell_style_set (eti->cell_views[i], previous_style);
        }
    }

    eti->needs_compute_height = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));

    free_height_cache (eti);

    eti_idle_maybe_show_cursor (eti);
}

static void
e_table_item_class_init (ETableItemClass *class)
{
    GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class);
    GObjectClass *object_class = G_OBJECT_CLASS (class);

    object_class->dispose       = eti_dispose;
    object_class->set_property  = eti_set_property;
    object_class->get_property  = eti_get_property;

    item_class->update          = eti_update;
    item_class->realize         = eti_realize;
    item_class->unrealize       = eti_unrealize;
    item_class->draw            = eti_draw;
    item_class->point           = eti_point;
    item_class->event           = eti_event;

    class->cursor_change    = NULL;
    class->cursor_activated = NULL;
    class->double_click     = NULL;
    class->right_click      = NULL;
    class->click            = NULL;
    class->key_press        = NULL;
    class->start_drag       = NULL;
    class->style_set        = eti_style_set;
    class->selection_model_removed = NULL;
    class->selection_model_added = NULL;

    g_object_class_install_property (
        object_class,
        PROP_TABLE_HEADER,
        g_param_spec_object (
            "ETableHeader",
            "Table header",
            "Table header",
            E_TYPE_TABLE_HEADER,
            G_PARAM_WRITABLE));

    g_object_class_install_property (
        object_class,
        PROP_TABLE_MODEL,
        g_param_spec_object (
            "ETableModel",
            "Table model",
            "Table model",
            E_TYPE_TABLE_MODEL,
            G_PARAM_WRITABLE));

    g_object_class_install_property (
        object_class,
        PROP_SELECTION_MODEL,
        g_param_spec_object (
            "selection_model",
            "Selection model",
            "Selection model",
            E_TYPE_SELECTION_MODEL,
            G_PARAM_WRITABLE));

    g_object_class_install_property (
        object_class,
        PROP_TABLE_ALTERNATING_ROW_COLORS,
        g_param_spec_boolean (
            "alternating_row_colors",
            "Alternating Row Colors",
            "Alternating Row Colors",
            FALSE,
            G_PARAM_WRITABLE));

    g_object_class_install_property (
        object_class,
        PROP_TABLE_HORIZONTAL_DRAW_GRID,
        g_param_spec_boolean (
            "horizontal_draw_grid",
            "Horizontal Draw Grid",
            "Horizontal Draw Grid",
            FALSE,
            G_PARAM_WRITABLE));

    g_object_class_install_property (
        object_class,
        PROP_TABLE_VERTICAL_DRAW_GRID,
        g_param_spec_boolean (
            "vertical_draw_grid",
            "Vertical Draw Grid",
            "Vertical Draw Grid",
            FALSE,
            G_PARAM_WRITABLE));

    g_object_class_install_property (
        object_class,
        PROP_TABLE_DRAW_FOCUS,
        g_param_spec_boolean (
            "drawfocus",
            "Draw focus",
            "Draw focus",
            FALSE,
            G_PARAM_WRITABLE));

    g_object_class_install_property (
        object_class,
        PROP_CURSOR_MODE,
        g_param_spec_int (
            "cursor_mode",
            "Cursor mode",
            "Cursor mode",
            E_CURSOR_LINE,
            E_CURSOR_SPREADSHEET,
            E_CURSOR_LINE,
            G_PARAM_WRITABLE));

    g_object_class_install_property (
        object_class,
        PROP_LENGTH_THRESHOLD,
        g_param_spec_int (
            "length_threshold",
            "Length Threshold",
            "Length Threshold",
            -1, G_MAXINT, 0,
            G_PARAM_WRITABLE));

    g_object_class_install_property (
        object_class,
        PROP_MINIMUM_WIDTH,
        g_param_spec_double (
            "minimum_width",
            "Minimum width",
            "Minimum Width",
            0.0, G_MAXDOUBLE, 0.0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_WIDTH,
        g_param_spec_double (
            "width",
            "Width",
            "Width",
            0.0, G_MAXDOUBLE, 0.0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_HEIGHT,
        g_param_spec_double (
            "height",
            "Height",
            "Height",
            0.0, G_MAXDOUBLE, 0.0,
            G_PARAM_READABLE));

    g_object_class_install_property (
        object_class,
        PROP_CURSOR_ROW,
        g_param_spec_int (
            "cursor_row",
            "Cursor row",
            "Cursor row",
            0, G_MAXINT, 0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_UNIFORM_ROW_HEIGHT,
        g_param_spec_boolean (
            "uniform_row_height",
            "Uniform row height",
            "Uniform row height",
            FALSE,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_IS_EDITING,
        g_param_spec_boolean (
            "is-editing",
            "Whether is in an editing mode",
            "Whether is in an editing mode",
            FALSE,
            G_PARAM_READABLE));

    eti_signals[CURSOR_CHANGE] = g_signal_new (
        "cursor_change",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ETableItemClass, cursor_change),
        NULL, NULL,
        g_cclosure_marshal_VOID__INT,
        G_TYPE_NONE, 1,
        G_TYPE_INT);

    eti_signals[CURSOR_ACTIVATED] = g_signal_new (
        "cursor_activated",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ETableItemClass, cursor_activated),
        NULL, NULL,
        g_cclosure_marshal_VOID__INT,
        G_TYPE_NONE, 1,
        G_TYPE_INT);

    eti_signals[DOUBLE_CLICK] = g_signal_new (
        "double_click",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ETableItemClass, double_click),
        NULL, NULL,
        e_marshal_NONE__INT_INT_BOXED,
        G_TYPE_NONE, 3,
        G_TYPE_INT,
        G_TYPE_INT,
        GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

    eti_signals[START_DRAG] = g_signal_new (
        "start_drag",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ETableItemClass, start_drag),
        g_signal_accumulator_true_handled, NULL,
        e_marshal_BOOLEAN__INT_INT_BOXED,
        G_TYPE_BOOLEAN, 3,
        G_TYPE_INT,
        G_TYPE_INT,
        GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

    eti_signals[RIGHT_CLICK] = g_signal_new (
        "right_click",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ETableItemClass, right_click),
        g_signal_accumulator_true_handled, NULL,
        e_marshal_BOOLEAN__INT_INT_BOXED,
        G_TYPE_BOOLEAN, 3,
        G_TYPE_INT,
        G_TYPE_INT,
        GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

    eti_signals[CLICK] = g_signal_new (
        "click",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ETableItemClass, click),
        g_signal_accumulator_true_handled, NULL,
        e_marshal_BOOLEAN__INT_INT_BOXED,
        G_TYPE_BOOLEAN, 3,
        G_TYPE_INT,
        G_TYPE_INT,
        GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

    eti_signals[KEY_PRESS] = g_signal_new (
        "key_press",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ETableItemClass, key_press),
        g_signal_accumulator_true_handled, NULL,
        e_marshal_BOOLEAN__INT_INT_BOXED,
        G_TYPE_BOOLEAN, 3,
        G_TYPE_INT,
        G_TYPE_INT,
        GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

    eti_signals[STYLE_SET] = g_signal_new (
        "style_set",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ETableItemClass, style_set),
        NULL, NULL,
        g_cclosure_marshal_VOID__OBJECT,
        G_TYPE_NONE, 1,
        GTK_TYPE_STYLE);

    eti_signals[SELECTION_MODEL_REMOVED] = g_signal_new (
        "selection_model_removed",
        G_TYPE_FROM_CLASS (object_class),
        G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
        G_STRUCT_OFFSET (ETableItemClass, selection_model_removed),
        NULL, NULL,
        g_cclosure_marshal_VOID__POINTER,
        G_TYPE_NONE, 1,
        G_TYPE_POINTER);

    eti_signals[SELECTION_MODEL_ADDED] = g_signal_new (
        "selection_model_added",
        G_TYPE_FROM_CLASS (object_class),
        G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
        G_STRUCT_OFFSET (ETableItemClass, selection_model_added),
        NULL, NULL,
        g_cclosure_marshal_VOID__POINTER,
        G_TYPE_NONE, 1,
        G_TYPE_POINTER);

    /* A11y Init */
    gal_a11y_e_table_item_init ();
}

/**
 * e_table_item_set_cursor:
 * @eti: %ETableItem which will have the cursor set.
 * @col: Column to select.  -1 means the last column.
 * @row: Row to select.  -1 means the last row.
 *
 * This routine sets the cursor of the %ETableItem canvas item.
 */
void
e_table_item_set_cursor (ETableItem *eti,
                         gint col,
                         gint row)
{
    e_table_item_focus (eti, col, view_to_model_row (eti, row), 0);
}

static void
e_table_item_focus (ETableItem *eti,
                    gint col,
                    gint row,
                    GdkModifierType state)
{
    g_return_if_fail (eti != NULL);
    g_return_if_fail (E_IS_TABLE_ITEM (eti));

    if (row == -1) {
        row = view_to_model_row (eti, eti->rows - 1);
    }

    if (col == -1) {
        col = eti->cols - 1;
    }

    if (row != -1) {
        e_selection_model_do_something (
            E_SELECTION_MODEL (eti->selection),
            row, col, state);
    }
}

/**
 * e_table_item_get_focused_column:
 * @eti: %ETableItem which will have the cursor retrieved.
 *
 * This routine gets the cursor of the %ETableItem canvas item.
 *
 * Returns: The current cursor column.
 */
gint
e_table_item_get_focused_column (ETableItem *eti)
{
    gint cursor_col;

    g_return_val_if_fail (eti != NULL, -1);
    g_return_val_if_fail (E_IS_TABLE_ITEM (eti), -1);

    g_object_get (
        eti->selection,
        "cursor_col", &cursor_col,
        NULL);

    return cursor_col;
}

static void
eti_cursor_change (ESelectionModel *selection,
                   gint row,
                   gint col,
                   ETableItem *eti)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
    gint view_row;

    if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
        return;

    view_row = model_to_view_row (eti, row);

    if (eti->old_cursor_row != -1 && view_row != eti->old_cursor_row)
        e_table_item_redraw_row (eti, eti->old_cursor_row);

    if (view_row == -1) {
        e_table_item_leave_edit_(eti);
        eti->old_cursor_row = -1;
        return;
    }

    if (!e_table_model_has_change_pending (eti->table_model)) {
        if (!eti->in_key_press) {
            eti_maybe_show_cursor (eti, DOUBLE_CLICK_TIME + 10);
        } else {
            eti_maybe_show_cursor (eti, 0);
        }
    }

    e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), FALSE);
    if (eti_editing (eti))
        e_table_item_leave_edit_(eti);

    g_signal_emit (eti, eti_signals[CURSOR_CHANGE], 0, view_row);

    e_table_item_redraw_row (eti, view_row);

    eti->old_cursor_row = view_row;
}

static void
eti_cursor_activated (ESelectionModel *selection,
                      gint row,
                      gint col,
                      ETableItem *eti)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
    gint view_row;
    gint view_col;

    if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
        return;

    view_row = model_to_view_row (eti, row);
    view_col = model_to_view_col (eti, col);

    if (view_row != -1 && view_col != -1) {
        if (!e_table_model_has_change_pending (eti->table_model)) {
            if (!eti->in_key_press) {
                eti_show_cursor (eti, DOUBLE_CLICK_TIME + 10);
            } else {
                eti_show_cursor (eti, 0);
            }
            eti_check_cursor_bounds (eti);
        }
    }

    if (eti_editing (eti))
        e_table_item_leave_edit_(eti);

    if (view_row != -1)
        g_signal_emit (
            eti, eti_signals[CURSOR_ACTIVATED], 0, view_row);
}

static void
eti_selection_change (ESelectionModel *selection,
                      ETableItem *eti)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

    if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
        return;

    eti->needs_redraw = TRUE;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

static void
eti_selection_row_change (ESelectionModel *selection,
                          gint row,
                          ETableItem *eti)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

    if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
        return;

    if (!eti->needs_redraw) {
        e_table_item_redraw_row (eti, model_to_view_row (eti, row));
    }
}

/**
 * e_table_item_enter_edit
 * @eti: %ETableItem which will start being edited
 * @col: The view col to edit.
 * @row: The view row to edit.
 *
 * This routine starts the given %ETableItem editing at the given view
 * column and row.
 */
void
e_table_item_enter_edit (ETableItem *eti,
                         gint col,
                         gint row)
{
    g_return_if_fail (eti != NULL);
    g_return_if_fail (E_IS_TABLE_ITEM (eti));

    d (g_print ("%s: %d, %d, eti_editing() = %s\n", __FUNCTION__, col, row, eti_editing (eti)?"true":"false"));

    if (eti_editing (eti))
        e_table_item_leave_edit_(eti);

    eti->editing_col = col;
    eti->editing_row = row;

    eti->edit_ctx = e_cell_enter_edit (eti->cell_views[col], view_to_model_col (eti, col), col, row);

    g_object_notify (G_OBJECT (eti), "is-editing");
}

/**
 * e_table_item_leave_edit_
 * @eti: %ETableItem which will stop being edited
 *
 * This routine stops the given %ETableItem from editing.
 */
void
e_table_item_leave_edit (ETableItem *eti)
{
    gint col, row;
    gpointer edit_ctx;

    g_return_if_fail (eti != NULL);
    g_return_if_fail (E_IS_TABLE_ITEM (eti));

    d (g_print ("%s: eti_editing() = %s\n", __FUNCTION__, eti_editing (eti)?"true":"false"));

    if (!eti_editing (eti))
        return;

    col = eti->editing_col;
    row = eti->editing_row;
    edit_ctx = eti->edit_ctx;

    eti->editing_col = -1;
    eti->editing_row = -1;
    eti->edit_ctx = NULL;

    e_cell_leave_edit (
        eti->cell_views[col],
        view_to_model_col (eti, col),
        col, row, edit_ctx);

    g_object_notify (G_OBJECT (eti), "is-editing");
}

/**
 * e_table_item_compute_location
 * @eti: %ETableItem to look in.
 * @x: A pointer to the x location to find in the %ETableItem.
 * @y: A pointer to the y location to find in the %ETableItem.
 * @row: A pointer to the location to store the found row in.
 * @col: A pointer to the location to store the found col in.
 *
 * This routine locates the pixel location (*x, *y) in the
 * %ETableItem.  If that location is in the %ETableItem, *row and *col
 * are set to the view row and column where it was found.  If that
 * location is not in the %ETableItem, the height of the %ETableItem
 * is removed from the value y points to.
 */
void
e_table_item_compute_location (ETableItem *eti,
                               gint *x,
                               gint *y,
                               gint *row,
                               gint *col)
{
    /* Save the grabbed row but make sure that we don't get flawed
     * results because the cursor is grabbed. */
    gint grabbed_row = eti->grabbed_row;
    eti->grabbed_row = -1;

    if (!find_cell (eti, *x, *y, col, row, NULL, NULL)) {
        *y -= eti->height;
    }

    eti->grabbed_row = grabbed_row;
}

/**
 * e_table_item_compute_mouse_over:
 * Similar to e_table_item_compute_location, only here recalculating
 * the position inside the item too.
 **/
void
e_table_item_compute_mouse_over (ETableItem *eti,
                                 gint x,
                                 gint y,
                                 gint *row,
                                 gint *col)
{
    gdouble realx, realy;
    /* Save the grabbed row but make sure that we don't get flawed
     * results because the cursor is grabbed. */
    gint grabbed_row = eti->grabbed_row;
    eti->grabbed_row = -1;

    realx = x;
    realy = y;

    gnome_canvas_item_w2i (GNOME_CANVAS_ITEM (eti), &realx, &realy);

    if (!find_cell (eti, (gint) realx, (gint) realy, col, row, NULL, NULL)) {
        *row = -1;
        *col = -1;
    }

    eti->grabbed_row = grabbed_row;
}

void
e_table_item_get_cell_geometry (ETableItem *eti,
                                gint *row,
                                gint *col,
                                gint *x,
                                gint *y,
                                gint *width,
                                gint *height)
{
    if (eti->rows > *row) {
        if (x)
            *x = e_table_header_col_diff (eti->header, 0, *col);
        if (y)
            *y = e_table_item_row_diff (eti, 0, *row);
        if (width)
            *width = e_table_header_col_diff (eti->header, *col, *col + 1);
        if (height)
            *height = ETI_ROW_HEIGHT (eti, *row);
        *row = -1;
        *col = -1;
    } else {
        *row -= eti->rows;
    }
}

typedef struct {
    ETableItem *item;
    gint rows_printed;
} ETableItemPrintContext;

static gdouble *
e_table_item_calculate_print_widths (ETableHeader *eth,
                                     gdouble width)
{
    gint i;
    gdouble extra;
    gdouble expansion;
    gint last_resizable = -1;
    gdouble scale = 1.0L;
    gdouble *widths = g_new (gdouble, e_table_header_count (eth));
    /* - 1 to account for the last pixel border. */
    extra = width - 1;
    expansion = 0;
    for (i = 0; i < eth->col_count; i++) {
        extra -= eth->columns[i]->min_width * scale;
        if (eth->columns[i]->spec->resizable && eth->columns[i]->expansion > 0)
            last_resizable = i;
        expansion += eth->columns[i]->spec->resizable ? eth->columns[i]->expansion : 0;
        widths[i] = eth->columns[i]->min_width * scale;
    }
    for (i = 0; i <= last_resizable; i++) {
        widths[i] += extra * (eth->columns[i]->spec->resizable ? eth->columns[i]->expansion : 0) / expansion;
    }

    return widths;
}

static gdouble
eti_printed_row_height (ETableItem *eti,
                        gdouble *widths,
                        GtkPrintContext *context,
                        gint row)
{
    gint col;
    gint cols = eti->cols;
    gdouble height = 0;
    for (col = 0; col < cols; col++) {
        ECellView *ecell_view = eti->cell_views[col];
        gdouble this_height = e_cell_print_height (
            ecell_view, context, view_to_model_col (eti, col), col, row,
            widths[col] - 1);
        if (this_height > height)
            height = this_height;
    }
    return height;
}

#define CHECK(x) if((x) == -1) return -1;

static gint
gp_draw_rect (GtkPrintContext *context,
              gdouble x,
              gdouble y,
              gdouble width,
              gdouble height)
{
    cairo_t *cr;
    cr = gtk_print_context_get_cairo_context (context);
    cairo_save (cr);
    cairo_rectangle (cr, x, y, width, height);
    cairo_set_line_width (cr, 0.5);
    cairo_stroke (cr);
    cairo_restore (cr);
    return 0;
}

static void
e_table_item_print_page (EPrintable *ep,
                         GtkPrintContext *context,
                         gdouble width,
                         gdouble height,
                         gboolean quantize,
                         ETableItemPrintContext *itemcontext)
{
    ETableItem *eti = itemcontext->item;
    const gint rows = eti->rows;
    const gint cols = eti->cols;
    gdouble max_height;
    gint rows_printed = itemcontext->rows_printed;
    gint row, col, next_page = 0;
    gdouble yd = height;
    cairo_t *cr;
    gdouble *widths;

    cr = gtk_print_context_get_cairo_context (context);
    max_height = gtk_print_context_get_height (context);
    widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);

    /*
     * Draw cells
     */

    if (eti->horizontal_draw_grid) {
        gp_draw_rect (context, 0, yd, width, 1);
    }
    yd++;

    for (row = rows_printed; row < rows; row++) {
        gdouble xd = 1, row_height;
        row_height = eti_printed_row_height (eti, widths, context, row);

        if (quantize) {
            if (yd + row_height + 1 > max_height && row != rows_printed) {
                next_page = 1;
                break;
            }
        } else {
            if (yd > max_height) {
                next_page = 1;
                break;
            }
        }

        for (col = 0; col < cols; col++) {
            ECellView *ecell_view = eti->cell_views[col];

            cairo_save (cr);
            cairo_translate (cr, xd, yd);
            cairo_rectangle (cr, 0, 0, widths[col] - 1, row_height);
            cairo_clip (cr);

            e_cell_print (
                ecell_view, context,
                view_to_model_col (eti, col),
                col,
                row,
                widths[col] - 1,
                row_height + 2);

            cairo_restore (cr);

            xd += widths[col];
        }

    yd += row_height;
        if (eti->horizontal_draw_grid) {
            gp_draw_rect (context, 0, yd, width, 1);
        }
        yd++;
    }

    itemcontext->rows_printed = row;
    if (eti->vertical_draw_grid) {
        gdouble xd = 0;
        for (col = 0; col < cols; col++) {
            gp_draw_rect (context, xd, height, 1, yd - height);
            xd += widths[col];
        }
        gp_draw_rect (context, xd, height, 1, yd - height);
    }

    if (next_page)
        cairo_show_page (cr);

    g_free (widths);
}

static gboolean
e_table_item_data_left (EPrintable *ep,
                        ETableItemPrintContext *itemcontext)
{
    ETableItem *item = itemcontext->item;
    gint rows_printed = itemcontext->rows_printed;

    g_signal_stop_emission_by_name (ep, "data_left");
    return rows_printed < item->rows;
}

static void
e_table_item_reset (EPrintable *ep,
                    ETableItemPrintContext *itemcontext)
{
    itemcontext->rows_printed = 0;
}

static gdouble
e_table_item_height (EPrintable *ep,
                     GtkPrintContext *context,
                     gdouble width,
                     gdouble max_height,
                     gboolean quantize,
                     ETableItemPrintContext *itemcontext)
{
    ETableItem *item = itemcontext->item;
    const gint rows = item->rows;
    gint rows_printed = itemcontext->rows_printed;
    gdouble *widths;
    gint row;
    gdouble yd = 0;

    widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);

    /*
     * Draw cells
     */
    yd++;

    for (row = rows_printed; row < rows; row++) {
        gdouble row_height;

        row_height = eti_printed_row_height (item, widths, context, row);
        if (quantize) {
            if (max_height != -1 && yd + row_height + 1 > max_height && row != rows_printed) {
                break;
            }
        } else {
            if (max_height != -1 && yd > max_height) {
                break;
            }
        }

        yd += row_height;

        yd++;
    }

    g_free (widths);

    if (max_height != -1 && (!quantize) && yd > max_height)
        yd = max_height;

    g_signal_stop_emission_by_name (ep, "height");
    return yd;
}

static gboolean
e_table_item_will_fit (EPrintable *ep,
                       GtkPrintContext *context,
                       gdouble width,
                       gdouble max_height,
                       gboolean quantize,
                       ETableItemPrintContext *itemcontext)
{
    ETableItem *item = itemcontext->item;
    const gint rows = item->rows;
    gint rows_printed = itemcontext->rows_printed;
    gdouble *widths;
    gint row;
    gdouble yd = 0;
    gboolean ret_val = TRUE;

    widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);

    /*
     * Draw cells
     */
    yd++;

    for (row = rows_printed; row < rows; row++) {
        gdouble row_height;

        row_height = eti_printed_row_height (item, widths, context, row);
        if (quantize) {
            if (max_height != -1 && yd + row_height + 1 > max_height && row != rows_printed) {
                ret_val = FALSE;
                break;
            }
        } else {
            if (max_height != -1 && yd > max_height) {
                ret_val = FALSE;
                break;
            }
        }

        yd += row_height;

        yd++;
    }

    g_free (widths);

    g_signal_stop_emission_by_name (ep, "will_fit");
    return ret_val;
}

static void
e_table_item_printable_destroy (gpointer data,
                                GObject *where_object_was)
{
    ETableItemPrintContext *itemcontext = data;

    g_object_unref (itemcontext->item);
    g_free (itemcontext);
}

/**
 * e_table_item_get_printable
 * @eti: %ETableItem which will be printed
 *
 * This routine creates and returns an %EPrintable that can be used to
 * print the given %ETableItem.
 *
 * Returns: The %EPrintable.
 */
EPrintable *
e_table_item_get_printable (ETableItem *item)
{
    EPrintable *printable = e_printable_new ();
    ETableItemPrintContext *itemcontext;

    itemcontext = g_new (ETableItemPrintContext, 1);
    itemcontext->item = item;
    g_object_ref (item);
    itemcontext->rows_printed = 0;

    g_signal_connect (
        printable, "print_page",
        G_CALLBACK (e_table_item_print_page), itemcontext);
    g_signal_connect (
        printable, "data_left",
        G_CALLBACK (e_table_item_data_left), itemcontext);
    g_signal_connect (
        printable, "reset",
        G_CALLBACK (e_table_item_reset), itemcontext);
    g_signal_connect (
        printable, "height",
        G_CALLBACK (e_table_item_height), itemcontext);
    g_signal_connect (
        printable, "will_fit",
        G_CALLBACK (e_table_item_will_fit), itemcontext);

    g_object_weak_ref (
        G_OBJECT (printable),
        e_table_item_printable_destroy, itemcontext);

    return printable;
}

/**
 * e_table_item_is_editing:
 * @eti: an %ETableItem 
 *
 * Returns: Whether the table item is currently editing cell content.
 **/
gboolean
e_table_item_is_editing (ETableItem *eti)
{
    g_return_val_if_fail (E_IS_TABLE_ITEM (eti), FALSE);

    return eti_editing (eti);
}