aboutsummaryrefslogblamecommitdiffstats
path: root/calendar/gui/e-meeting-time-sel.c
blob: ca5bb2c15b84460c121964fc4d1aed0a1101339b (plain) (tree)
1
2
3
4
5
6
7
8
  
                                                                


                                                               


                                                                  



                                                                    
                                                                             






                                                        
  











                               
                       

                                     
                                          
 

                                
                            
 
                          
                                     
 
                            
                                

                                    





                                     
                                                                   
                                              

                                                                             


                                                                              

                                                                        

                                                                              



                                                                             
                                                    
                                                          
                                                         

                                                          

                                                                        
                                  




                                                          






                                




                   
                                         
 
                                                                                  
                                                                                       



                                                                              

                                                                     






                                                                              

                                                                          

                                                                                         



                                                                                    


                                                                                   




                                                                                          
                                                                                      
                                                                                        
                                                                                    
                                                                                      
                                                                                         









                                                                                                
                                                                                       








                                                                                           

                                                                                   



                                                                                     

                                                                                    




                                                                                                      
                                                                       
                                                                                      
                                                                                                   



                                                                                                   


                                                                                   
                                                                          
                                                                                
                                                                             













                                                                                                 
                                                                                                                                     
 


                                                                                                       
 
                                                                      
 


                                                                      

           




























































                                                                                
                                               






                                                     




















                                                            

                                                                                


           
                                                                     
 
                                   

                                     
                                                                               
 


                                                                        
                                                              
 



                                                                    
                                                          








































                                                                     

 
           
                                                        
 


                                                  
 












                                                                          
                              
 
                                
                                      

                                                          

 
    

                                                             
 
                                                                
                                                             

                                  
                                  
                      
                        

                                    
                                               

                                                                            
                                       

                                                        
                                                                              











                                                                                     
                             

                                     







                                                                                   


                                                                                         
 



                                                                                
                              
                         
 
                       
                                          
 



                                                                 


                                                                                         
 
                                                              



                                                                                                   
 
                                                      
 


                                                                                                          


                                                    


                                                                            

                                            
                                                                                                              
      
 
                                                                                           
 

                                               
                                                                                







                                                                                            

                                                                                        







                                                                              



                                                                                             

                                                                                            
 
                                                        
 
                                                                 


                                                      
                                                                 




                                                                       

                                          
                                                                 




                                                                        


                                                
                                                                                                          




                                                                                      
                                                                                                            

















                                                                                                                                    
                                                         







                                                                     
                                   
                                                                  
                              
                                                       

                                                                  

                                                                                      
                                                               
                                                                                                     

                                                
                                                                                

                                              

                                                                                               




                                                                            
                                                            
                                                                      
                                                                        



                                                                                     
                                                                
                                                                          





                                                                                     
 
                                                           

                                                                                            
                                                                             

                                                                       
 

                                                                                              


                                                           

                                                                                    
                                                                             

                                                                       
 

                                                                                           


                                        
                                                                             



                                                     

                                                                                     
                                                                             
 

                                                                                         







                                                         
                                                                   


                                                                        
                                                                                           
                                                                                  


                                                                                      
                                                                                            

                                                 
                                                                                 






                                                                             

                                                                             
                                                                        


                                                                                      

                                                                                                
 
                                                                
                                                                          

                                
                                                                 


                                                                       
                                                                                         
                                                                                  


                                                                                    
                                                                                            




                                                                                       
 

                                                                 
                                                       
                                                                               
                                                                                             
                                                                              

                                                                                                



                                                                  
                                                       
                                                                               
                                                                                                
                                                                              

                                                                                                



                                                                  
                                                       
                                                                               
                                                                                    
                                                                              

                                                                                                



                                                                   

                                                                                                     
                                                                              

                                                                                                


                                                        
                                                       








                                                             
                                                  







                                                                                
                                                                             
 
                                
                                         

                                                          
 
                                
                                          

                                                           
 
                                
                                      

                                                       
 


                                                                  

                                                                                           
 


                                                                                  

                                                            
                                                         


                                                







                                                                              
                                                                           
 
                                
                                         

                                                        
 
                                
                                          

                                                         
 
                                
                                      

                                                     



                                                                

                                                                                         
 







                                                                                



                                                           

                                                                                               

                                                                                          
                                                                                           
                                                                                               
 
                                                                                              
                                                                                                             
                                                                                                                     
                                                                                                             
                                                                                                                         
 
                                                                          

                                                                 


                                                                               
                                                                 





                                                                               
 


                                                                
                                                           
                                                              
 
                                                 

 
                                                                           
                                                    
           



                                                                 








                                                                          
                                                          
                                                    



                                                            
                                                                        

                                

                                                                              
                                 


           


                                                         

                                   
                                 
                        

                                             
                                                       
 
                                                           
 
                          

                                                
                                                     

                    
                                                       
                
                                                            
         



                                                                      
 


                    
           


                                                                     
 


                                        

                                                                

 

                                                                 
                                                             
 
                                  
 
                                          
                                                               
 
                                               
                                                                   
 
                                 

 

                                                                  
                                                              
 
                                  
 
                                          
                                                               
 
                                               
                                                                    
 
                                  

 
           
                                                


                       
                                                                
 
                                                                               
 


                   

                                                                         
 
                                                                       
 

                                            
 




                                                                          
 
                                                         
 

                                                              
 



                                                                          
 

                                             
 




                                                                            
 
                                                           
 



















                                                                      

 


                                                                          



                                 
 


                                                                                              

                                          

                                                
 






                                                          
 
                           
 

                                                                
 
                                        
 
                       

 




                                                   

                                                                                            


                                               
                                                                                    

 






                                                     

                                                     
 

                                                                                              

 
           


                                   
                         




                                                           
 


                      
               
                                                  
 
                                

                                  
                                  
                                



                                        
 
                                        
                                  




                                                                                              
 


                                                                           
                                                                         
                                                                                            
                    
                                                                                              
 
                                                                                    

                                                                              
 

                                                                               

                                            


                                                                    
 
                                                                                    
 



                                                                             
  


                                                                                               
                                                                                                     
  
          
 
                                                                                             
 

                                   
                                                                              

                                                                       
                                                                              
                                                                        

                                
                                                







                                                     
                                                            






                                                                                                              

                                                                      

 

                                                                  

                                                




                                               
                                                      
 

                                                                                             



                     
           

                                                               
 
                                 
                        

                        

                        
                                                            




                                                                  
 
                                                        

                          



                                                       

 


                                                                       
                                                                       
 
                                   
                                  
                      
 

                                                                  


                                                      

 

                                                                       
                                                                       
 


                                   
 




                                                                

 

                                                                    









                                                             
 


                                                                        


                                                       


                                                                    



                                                   

                                                                    









                                                            
 
                                                                       























                                                                              
                                                     


                                                             
                                                 
 


                    

                                                               
                                                      



                                
 













                                                                      

                                                                          
                                                                            


                                
                                                            














                                                                    

                                                                     



                                                                 


                                
                                                            








                                                        
 







                                                                                               
 








                                                                    

                                                                  
                                                            


                                
                                                            













                                                                    

                                                  

                                         
 
                                                                




                                                                  

                                                               
                                        
 
                                                    
 

                                                                                                               
         
 



                                                          
 
                                        

                     

 
    


                                                                     

                                
 



                                                                     






                                                                                   
                       
 





                                                                           
 




                                                                     
                                        


                                                 
 


                                                                      


                                                                 
                       
 
                                                                                         
                                                      
                
                                              
         
 
                
                                                                                   

                                                                                                   
                                                                                    

                                                                                               



                                                                       



                                                                          
                                                                        


                                                                          
                                                                           


                                                                          
                                                               
 


                                                                        

                                                                       
                                                                                                
 
                                                            















                                                                                                                             
 
    

                                                                 
 
                                                            
 
                                                                           






                                                                         

 














                                                                 
                                                            


































                                                                                       
                                                         
                                        
       
                                                         














                                                                  
                                                   
                                        
       










                                                                         
           
                                                                           
                                                                                   
 
                                                                  

 

                                                                     
                                                                             





                                                                               

                                                                      



                                                                           

                                  
                                        


                                 
                          



                                                  





                                                        

                                                 
                                                                                   



                                                                        


           
                                                               
                                                                       
 
                                                                           
                                             
                                                       

                                                                
                                                                 

 

                                                                      
                                                                              





                                                                                

                                                                       



                                                                            

                                  
                                        


                                 
                          



                                                  





                                                        

                                                 
                                                                                    



                                                                        

 

                                                                      
                                                                              

                                                                           
                                             
                                                        


                                                                 

                                                                  
                                                                          



                                                      

                                                                  
                                                                          



                                                     
                                                                     
                                  

                                                            
                                                   










                                                                                                                                                                 
 







                                                                                                                                                      
                                                    








                                                                                           
                   





                                                                           
                                                                   

                                                                                                  









                                                                                                                           


                                                                               
                                                      
                                                                                                                                     

                                                                                     
                                                                                                                                       



















                                                                                                                                              


                                                                            






                                                                                                                                      
                         












                                                                                
                                                                     

                                                                             
 
                                                                 
 










                                                                                                                                                              
           




                                                                       
 
                                                                                  











                                               
                                                                             
                                                                          

                                                                         




                                                                        



                                                     







                                                                      
                

                                                       
                                       

                                                                
 



                                                                           
                                                               







                                                                             

                                                                             


























                                                                        
 






                                                                                  
                                                                              
                                                                          

                                                                                  




                                                                                 



                                                   




                                                    
                                               














                                                                    
                                                            
                                     
                                       
         




                                                                           
                                                               







                                                                             


                                                                          
























                                                                                       
 




                                                                                  
                                                                              


                                                                            
           



                                                           



                                                                             
                                                     


















                                                             
                                                                          
                                                      

                                                                   


                                                                    











                                                                                             
                                   



                                                                                            

                                                                            
                                                                                 


                                                                           

                                                                             
                                                                                








                                      
                                                                          
                                                                         
 

                        
                                                                           
                                             
                                                       

                                                                

                                                             
                                                                

 
           
                                                                             
                                                                            
 

                        
                                                                           
                                             
                                                       

                                                                

                                                                     
                                                                

 





































                                                                                            


                                                                              
                                                             







                                                                          


                                                                    
                                                                



                                                                            
                                                                                 





                                                                          
                                                                           


                                                                         

                                                                              

                                                                  


























                                                                                                                          
                                                                           
                                                                       



                                                                            
                             
                                                              



                                                                               
                                
                          
                                    
                            
                          

                                        
                                                                        
                                                  




                                                                           
                                               




                                                                          

                                                         
                                                                   



                                                      





                                                                         

                                                         
                                                                   





                                                    


                                                        
                                                                

                                                         
                                                                   



                                                      



                                                                         














                                                                                             
 
     
                                                                    
                                                      

      
                                                

                                                           


                                                                                

                                
                     

 
                                                                      
                                                                  

                                                              
                                                                      
 


                                                                 
                                                         

 
                                                                       


                                                                             

                                                                 
                                                                         


                                                             
                                  
                       
 
                  
                                                                            
                                        
                                                   

                  


                                                                                         

                                                      
                                                                                   
















                                                                                                                                                                 
 
                                                 

 
                                                                     


                                                                             

                                                               
                                                                       

                             
                                  
                       
 
                  
                                                                          
                                        
                                                   
                         


                                                   


                                                                                       

                                                      
                                                                                 





                                                                               
                                                                                     
                                                                             
                                               
                                                  

                                                                                






                                                                     
 
                                                 

 
                                                                            
                                




















                                                                           

                                                                   

                                                                           





                                                                       
               


                                                                          








                                                                                                                        
                                                                               

                                                                            


                                                                                    

                                 
 
                                                                   
                                                                                             
                                                 






                                                                                            
                                                                            



                                                                              

                                                                     
                                                  



                                                                    
                                 



                                                                          

                                                                   




















                                                                                                                       
                                                           


                                                                                 














                                                                    
                                      
                                   
                                     







                                                                        
                                
                            







                                                                  
                


                                                             
                                     



                                                                  
                                                                       
                                       
                                                                      








                                                                        
                                                                        

                       


                                                                          
                                                                                      

                             

                                                                                             
                       
 


                                                                       
                                                                   
                                                                        










                                                                                   
 














                                                                           



                                                                           
                                                         

 
                                                                             

                                                                           







                                                            
                                 













                                                                          

                                                                   




















                                                                                               
                                                  

                                                            

                                                                                    





                                                                          
                        

                                             



                                                                      








                                                                          
                        



                                                                       










                                                                        
                                                                          
                                     
                            

         


                                                                          
                                                                                      

                             

                                                                                             

                            


                                                                       
                                                                                                  























                                                                                   


                                                                           
                                                         
 
        
                                                                           


                                                                            



                                                                 




                             









                                                                              



                                                                              
                                                                                 


                                                                                               

                                                                              

 



                                                                            
                   
 




                                                                               


                                                                       

                                                                            

 
                                                                          
                                                                             


                                                                             
                                                
                          
                                 
                          











                                                                  






                                                                                       
 

                                                                          

                                                                       

                       
                               



                                                                 
                                                                             
                                                      



                                                                      

                                                                         

                                                     

                                                                 

 
                                                                            

                                                                            
    
                                                                  





















                                                                              




                                                                              

                                                                              


                                                                       

















                                                                             
                                                                          
                                                                  
    



                                                                                          











                                                                         
                                                                              



                                                                            

                                                                  

                                                           















                                                                                                                       
                                                                  

                                                                          

                                                                           
                                                                       



                                                                         
                                          
                                                                                                      




                                                                            
                                         












                                                                               



                                     

                                                                   
                                                       







                                                                       
                                                                    


           



                                    

                                                                   
                                                       





                                                                    


                                    










                                                                       
                       



                                         

                                                                   



                                                                       
 



                     
                                                         
 
                                                                              
                                     
                                                      
 

                                                               
 
/*
 * 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:
 *      Damon Chaplin <damon@gtk.org>
 *      Rodrigo Moya <rodrigo@novell.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

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

#include "e-meeting-time-sel.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <glib/gi18n.h>
#include <gdk/gdkkeysyms.h>
#include <libebackend/e-extensible.h>
#include <libgnomecanvas/libgnomecanvas.h>

#include "misc/e-canvas.h"
#include "misc/e-canvas-utils.h"
#include "misc/e-dateedit.h"

#include "e-util/e-util.h"
#include "e-util/e-datetime-format.h"

#include "e-meeting-utils.h"
#include "e-meeting-list-view.h"
#include "e-meeting-time-sel-item.h"

struct _EMeetingTimeSelectorPrivate {
    gint week_start_day;
    guint show_week_numbers  : 1;
    guint use_24_hour_format : 1;
};

/* An array of hour strings for 24 hour time, "0:00" .. "23:00". */
const gchar *EMeetingTimeSelectorHours[24] = {
    "0:00", "1:00", "2:00", "3:00", "4:00", "5:00", "6:00", "7:00",
    "8:00", "9:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00",
    "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00"
};

/* An array of hour strings for 12 hour time, "12:00am" .. "11:00pm". */
const gchar *EMeetingTimeSelectorHours12[24] = {
    "12:00am", "1:00am", "2:00am", "3:00am", "4:00am", "5:00am", "6:00am",
    "7:00am", "8:00am", "9:00am", "10:00am", "11:00am", "12:00pm",
    "1:00pm", "2:00pm", "3:00pm", "4:00pm", "5:00pm", "6:00pm", "7:00pm",
    "8:00pm", "9:00pm", "10:00pm", "11:00pm"
};

/* The number of days shown in the entire canvas. */
#define E_MEETING_TIME_SELECTOR_DAYS_SHOWN      35
#define E_MEETING_TIME_SELECTOR_DAYS_START_BEFORE   7
#define E_MEETING_TIME_SELECTOR_FB_DAYS_BEFORE          7
#define E_MEETING_TIME_SELECTOR_FB_DAYS_AFTER           28

/* This is the number of pixels between the mouse has to move before the
 * scroll speed is incremented. */
#define E_MEETING_TIME_SELECTOR_SCROLL_INCREMENT_WIDTH  10

/* This is the maximum scrolling speed. */
#define E_MEETING_TIME_SELECTOR_MAX_SCROLL_SPEED    4

enum {
    PROP_0,
    PROP_SHOW_WEEK_NUMBERS,
    PROP_USE_24_HOUR_FORMAT,
    PROP_WEEK_START_DAY
};

enum {
    CHANGED,
    LAST_SIGNAL
};

static gint signals[LAST_SIGNAL] = { 0 };

static void e_meeting_time_selector_alloc_named_color (EMeetingTimeSelector * mts,
                               const gchar *name, GdkColor *c);
static void e_meeting_time_selector_add_key_color (EMeetingTimeSelector * mts,
                           GtkWidget *hbox,
                           gchar *label_text,
                           GdkColor *color);
static gint e_meeting_time_selector_draw_key_color (GtkWidget *darea,
                              cairo_t *cr,
                              GdkColor *color);
static void e_meeting_time_selector_options_menu_detacher (GtkWidget *widget,
                               GtkMenu   *menu);
static void e_meeting_time_selector_autopick_menu_detacher (GtkWidget *widget,
                                GtkMenu   *menu);
static void e_meeting_time_selector_realize (GtkWidget *widget);
static void e_meeting_time_selector_unrealize (GtkWidget *widget);
static void e_meeting_time_selector_style_set (GtkWidget *widget,
                           GtkStyle  *previous_style);
static gint e_meeting_time_selector_draw (GtkWidget *widget, cairo_t *cr);
static void e_meeting_time_selector_draw_shadow (EMeetingTimeSelector *mts, cairo_t *cr);
static void e_meeting_time_selector_hadjustment_changed (GtkAdjustment *adjustment,
                             EMeetingTimeSelector *mts);
static void e_meeting_time_selector_vadjustment_changed (GtkAdjustment *adjustment,
                             EMeetingTimeSelector *mts);
static void e_meeting_time_selector_on_canvas_realized (GtkWidget *widget,
                            EMeetingTimeSelector *mts);

static void e_meeting_time_selector_on_options_button_clicked (GtkWidget *button,
                                   EMeetingTimeSelector *mts);
static void e_meeting_time_selector_options_menu_position_callback (GtkMenu *menu,
                                    gint *x,
                                    gint *y,
                                    gboolean *push_in,
                                    gpointer user_data);
static void e_meeting_time_selector_on_zoomed_out_toggled (GtkCheckMenuItem *button,
                               EMeetingTimeSelector *mts);
static void e_meeting_time_selector_on_working_hours_toggled (GtkCheckMenuItem *menuitem,
                                  EMeetingTimeSelector *mts);
static void e_meeting_time_selector_on_invite_others_button_clicked (GtkWidget *button,
                                     EMeetingTimeSelector *mts);
static void e_meeting_time_selector_on_update_free_busy (GtkWidget *button,
                             EMeetingTimeSelector *mts);
static void e_meeting_time_selector_on_autopick_button_clicked (GtkWidget *button,
                                EMeetingTimeSelector *mts);
static void e_meeting_time_selector_autopick_menu_position_callback (GtkMenu *menu,
                                     gint *x,
                                     gint *y,
                                     gboolean *push_in,
                                     gpointer user_data);
static void e_meeting_time_selector_on_autopick_option_toggled (GtkWidget *button,
                                EMeetingTimeSelector *mts);
static void e_meeting_time_selector_on_prev_button_clicked (GtkWidget *button,
                                EMeetingTimeSelector *mts);
static void e_meeting_time_selector_on_next_button_clicked (GtkWidget *button,
                                EMeetingTimeSelector *mts);
static void e_meeting_time_selector_autopick (EMeetingTimeSelector *mts,
                          gboolean forward);
static void e_meeting_time_selector_calculate_time_difference (EMeetingTime *start,
                                   EMeetingTime *end,
                                   gint *days,
                                   gint *hours,
                                   gint *minutes);
static void e_meeting_time_selector_find_nearest_interval (EMeetingTimeSelector *mts,
                               EMeetingTime *start_time,
                               EMeetingTime *end_time,
                               gint days, gint hours, gint mins);
static void e_meeting_time_selector_find_nearest_interval_backward (EMeetingTimeSelector *mts,
                                    EMeetingTime *start_time,
                                    EMeetingTime *end_time,
                                    gint days, gint hours, gint mins);
static void e_meeting_time_selector_adjust_time (EMeetingTime *mtstime,
                         gint days, gint hours, gint minutes);
static EMeetingFreeBusyPeriod * e_meeting_time_selector_find_time_clash (EMeetingTimeSelector *mts,
                                    EMeetingAttendee *attendee,
                                    EMeetingTime *start_time,
                                    EMeetingTime *end_time);

static void e_meeting_time_selector_recalc_grid (EMeetingTimeSelector *mts);
static void e_meeting_time_selector_recalc_date_format (EMeetingTimeSelector *mts);
static void e_meeting_time_selector_save_position (EMeetingTimeSelector *mts,
                           EMeetingTime *mtstime);
static void e_meeting_time_selector_restore_position (EMeetingTimeSelector *mts,
                              EMeetingTime *mtstime);
static void e_meeting_time_selector_on_start_time_changed (GtkWidget *widget,
                               EMeetingTimeSelector *mts);
static void e_meeting_time_selector_on_end_time_changed (GtkWidget *widget,
                             EMeetingTimeSelector *mts);
static void e_meeting_time_selector_update_date_popup_menus (EMeetingTimeSelector *mts);
static void e_meeting_time_selector_on_canvas_size_allocate (GtkWidget *widget,
                                 GtkAllocation *allocation,
                                 EMeetingTimeSelector *mts);
static void e_meeting_time_selector_update_main_canvas_scroll_region (EMeetingTimeSelector *mts);
static gboolean e_meeting_time_selector_timeout_handler (gpointer data);
static void e_meeting_time_selector_update_start_date_edit (EMeetingTimeSelector *mts);
static void e_meeting_time_selector_update_end_date_edit (EMeetingTimeSelector *mts);
static void e_meeting_time_selector_ensure_meeting_time_shown (EMeetingTimeSelector *mts);
static void e_meeting_time_selector_update_dates_shown (EMeetingTimeSelector *mts);
static gboolean e_meeting_time_selector_on_canvas_scroll_event (GtkWidget *widget, GdkEventScroll *event, EMeetingTimeSelector *mts);

static void row_inserted_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data);
static void row_changed_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data);
static void row_deleted_cb (GtkTreeModel *model, GtkTreePath *path, gpointer data);

static void free_busy_template_changed_cb (EMeetingTimeSelector *mts);

G_DEFINE_TYPE_WITH_CODE (
    EMeetingTimeSelector, e_meeting_time_selector, GTK_TYPE_TABLE,
    G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))

static void
meeting_time_selector_set_property (GObject *object,
                                    guint property_id,
                                    const GValue *value,
                                    GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_SHOW_WEEK_NUMBERS:
            e_meeting_time_selector_set_show_week_numbers (
                E_MEETING_TIME_SELECTOR (object),
                g_value_get_boolean (value));
            return;

        case PROP_USE_24_HOUR_FORMAT:
            e_meeting_time_selector_set_use_24_hour_format (
                E_MEETING_TIME_SELECTOR (object),
                g_value_get_boolean (value));
            return;

        case PROP_WEEK_START_DAY:
            e_meeting_time_selector_set_week_start_day (
                E_MEETING_TIME_SELECTOR (object),
                g_value_get_int (value));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
meeting_time_selector_get_property (GObject *object,
                                    guint property_id,
                                    GValue *value,
                                    GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_SHOW_WEEK_NUMBERS:
            g_value_set_boolean (
                value,
                e_meeting_time_selector_get_show_week_numbers (
                E_MEETING_TIME_SELECTOR (object)));
            return;

        case PROP_USE_24_HOUR_FORMAT:
            g_value_set_boolean (
                value,
                e_meeting_time_selector_get_use_24_hour_format (
                E_MEETING_TIME_SELECTOR (object)));
            return;

        case PROP_WEEK_START_DAY:
            g_value_set_int (
                value,
                e_meeting_time_selector_get_week_start_day (
                E_MEETING_TIME_SELECTOR (object)));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
meeting_time_selector_dispose (GObject *object)
{
    EMeetingTimeSelector *mts;

    mts = E_MEETING_TIME_SELECTOR (object);

    e_meeting_time_selector_remove_timeout (mts);

    if (mts->model) {
        g_signal_handlers_disconnect_matched (
            mts->model, G_SIGNAL_MATCH_DATA,
            0, 0, NULL, NULL, mts);
        g_object_unref (mts->model);
        mts->model = NULL;
    }

    mts->display_top = NULL;
    mts->display_main = NULL;

    if (mts->fb_refresh_not != 0) {
        g_source_remove (mts->fb_refresh_not);
        mts->fb_refresh_not = 0;
    }

    if (mts->style_change_idle_id != 0) {
        g_source_remove (mts->style_change_idle_id);
        mts->style_change_idle_id = 0;
    }

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

static void
e_meeting_time_selector_class_init (EMeetingTimeSelectorClass *class)
{
    GObjectClass *object_class;
    GtkWidgetClass *widget_class;

    g_type_class_add_private (class, sizeof (EMeetingTimeSelectorPrivate));

    object_class = G_OBJECT_CLASS (class);
    object_class->set_property = meeting_time_selector_set_property;
    object_class->get_property = meeting_time_selector_get_property;
    object_class->dispose = meeting_time_selector_dispose;

    widget_class = GTK_WIDGET_CLASS (class);
    widget_class->realize = e_meeting_time_selector_realize;
    widget_class->unrealize = e_meeting_time_selector_unrealize;
    widget_class->style_set = e_meeting_time_selector_style_set;
    widget_class->draw = e_meeting_time_selector_draw;

    g_object_class_install_property (
        object_class,
        PROP_SHOW_WEEK_NUMBERS,
        g_param_spec_boolean (
            "show-week-numbers",
            "Show Week Numbers",
            NULL,
            TRUE,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_USE_24_HOUR_FORMAT,
        g_param_spec_boolean (
            "use-24-hour-format",
            "Use 24-Hour Format",
            NULL,
            TRUE,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_WEEK_START_DAY,
        g_param_spec_int (
            "week-start-day",
            "Week Start Day",
            NULL,
            0,  /* Monday */
            6,  /* Sunday */
            0,
            G_PARAM_READWRITE));

    signals[CHANGED] = g_signal_new (
        "changed",
        G_TYPE_FROM_CLASS (object_class),
        G_SIGNAL_RUN_FIRST,
        G_STRUCT_OFFSET (EMeetingTimeSelectorClass, changed),
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);
}

static void
e_meeting_time_selector_init (EMeetingTimeSelector *mts)
{
    mts->priv = G_TYPE_INSTANCE_GET_PRIVATE (
        mts, E_TYPE_MEETING_TIME_SELECTOR,
        EMeetingTimeSelectorPrivate);

    /* The shadow is drawn in the border so it must be >= 2 pixels. */
    gtk_container_set_border_width (GTK_CONTAINER (mts), 2);

    mts->accel_group = gtk_accel_group_new ();

    mts->working_hours_only = TRUE;
    mts->day_start_hour = 9;
    mts->day_start_minute = 0;
    mts->day_end_hour = 18;
    mts->day_end_minute = 0;
    mts->zoomed_out = TRUE;
    mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_NONE;

    mts->list_view = NULL;

    mts->fb_refresh_not = 0;
    mts->style_change_idle_id = 0;

    e_extensible_load_extensions (E_EXTENSIBLE (mts));
}

void
e_meeting_time_selector_construct (EMeetingTimeSelector *mts,
                                   EMeetingStore *ems)
{
    GtkWidget *hbox, *vbox, *separator, *label, *table, *sw;
    GtkWidget *alignment, *child_hbox, *arrow, *menuitem;
    GtkWidget *child;
    GtkAdjustment *adjustment;
    GtkScrollable *scrollable;
    GSList *group;
    guint accel_key;
    time_t meeting_start_time;
    struct tm *meeting_start_tm;
    AtkObject *a11y_label, *a11y_date_edit;

    /* The default meeting time is the nearest half-hour interval in the
     * future, in working hours. */
    meeting_start_time = time (NULL);
    g_date_clear (&mts->meeting_start_time.date, 1);
    g_date_set_time_t (&mts->meeting_start_time.date, meeting_start_time);
    meeting_start_tm = localtime (&meeting_start_time);
    mts->meeting_start_time.hour = meeting_start_tm->tm_hour;
    mts->meeting_start_time.minute = meeting_start_tm->tm_min;

    e_meeting_time_selector_find_nearest_interval (mts, &mts->meeting_start_time,
                               &mts->meeting_end_time,
                               0, 0, 30);

    e_meeting_time_selector_update_dates_shown (mts);

    mts->meeting_positions_valid = FALSE;

    mts->row_height = 17;
    mts->col_width = 55;
    mts->day_width = 55 * 24 + 1;

    mts->auto_scroll_timeout_id = 0;

    vbox = gtk_vbox_new (FALSE, 0);
    gtk_table_attach (GTK_TABLE (mts),
              vbox, 0, 1, 0, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
    gtk_widget_show (vbox);

    mts->attendees_vbox_spacer = gtk_vbox_new (FALSE, 0);
    gtk_box_pack_start (GTK_BOX (vbox), mts->attendees_vbox_spacer, FALSE, FALSE, 0);
    gtk_widget_show (mts->attendees_vbox_spacer);

    mts->attendees_vbox = gtk_vbox_new (FALSE, 0);
    gtk_box_pack_start (GTK_BOX (vbox), mts->attendees_vbox, TRUE, TRUE, 0);
    gtk_widget_show (mts->attendees_vbox);

    /* build the etable */
    mts->model = ems;

    if (mts->model)
        g_object_ref (mts->model);

    g_signal_connect_swapped (
        mts->model, "notify::free-busy-template",
        G_CALLBACK (free_busy_template_changed_cb), mts);

    g_signal_connect (mts->model, "row_inserted", G_CALLBACK (row_inserted_cb), mts);
    g_signal_connect (mts->model, "row_changed", G_CALLBACK (row_changed_cb), mts);
    g_signal_connect (mts->model, "row_deleted", G_CALLBACK (row_deleted_cb), mts);

    mts->list_view = e_meeting_list_view_new (mts->model);
    e_meeting_list_view_column_set_visible (mts->list_view, E_MEETING_STORE_ROLE_COL, FALSE);
    e_meeting_list_view_column_set_visible (mts->list_view, E_MEETING_STORE_RSVP_COL, FALSE);
    e_meeting_list_view_column_set_visible (mts->list_view, E_MEETING_STORE_STATUS_COL, FALSE);
    e_meeting_list_view_column_set_visible (mts->list_view, E_MEETING_STORE_TYPE_COL, FALSE);

    gtk_widget_show (GTK_WIDGET (mts->list_view));

    sw = gtk_scrolled_window_new (NULL, NULL);
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
    gtk_widget_set_child_visible (
        gtk_scrolled_window_get_vscrollbar (
        GTK_SCROLLED_WINDOW (sw)), FALSE);
    gtk_widget_show (sw);
    gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (mts->list_view));

#if 0
    /* FIXME: do we need sorting here */
    g_signal_connect (real_table->sort_info, "sort_info_changed", G_CALLBACK (sort_info_changed_cb), mts);
#endif

    gtk_box_pack_start (GTK_BOX (mts->attendees_vbox), GTK_WIDGET (sw), TRUE, TRUE, 6);

    /* The free/busy information */
    mts->display_top = gnome_canvas_new ();
    gtk_widget_set_size_request (mts->display_top, -1, mts->row_height * 3);
    gnome_canvas_set_scroll_region (GNOME_CANVAS (mts->display_top),
                    0, 0,
                    mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN,
                    mts->row_height * 3);
    /* Add some horizontal padding for the shadow around the display. */
    gtk_table_attach (GTK_TABLE (mts), mts->display_top,
              1, 4, 0, 1, GTK_EXPAND | GTK_FILL, 0, 0, 0);
    gtk_widget_show (mts->display_top);
    g_signal_connect (mts->display_top, "realize",
              G_CALLBACK (e_meeting_time_selector_on_canvas_realized), mts);

    mts->display_main = gnome_canvas_new ();
    e_meeting_time_selector_update_main_canvas_scroll_region (mts);
    /* Add some horizontal padding for the shadow around the display. */
    gtk_table_attach (GTK_TABLE (mts), mts->display_main,
              1, 4, 1, 2,
              GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
    gtk_widget_show (mts->display_main);
    g_signal_connect (mts->display_main, "realize",
              G_CALLBACK (e_meeting_time_selector_on_canvas_realized), mts);
    g_signal_connect (mts->display_main, "size_allocate",
              G_CALLBACK (e_meeting_time_selector_on_canvas_size_allocate), mts);
    g_signal_connect (mts->display_main, "scroll-event",
              G_CALLBACK (e_meeting_time_selector_on_canvas_scroll_event), mts);

    scrollable = GTK_SCROLLABLE (mts->display_main);

    adjustment = gtk_scrollable_get_vadjustment (scrollable);
    gtk_scrolled_window_set_vadjustment (
        GTK_SCROLLED_WINDOW (sw), adjustment);

    adjustment = gtk_scrollable_get_hadjustment (scrollable);
    mts->hscrollbar = gtk_hscrollbar_new (adjustment);
    gtk_adjustment_set_step_increment (adjustment, mts->day_width);
    gtk_table_attach (
        GTK_TABLE (mts), mts->hscrollbar,
        1, 4, 2, 3, GTK_EXPAND | GTK_FILL, 0, 0, 0);
    gtk_widget_show (mts->hscrollbar);

    adjustment = gtk_scrollable_get_vadjustment (scrollable);
    mts->vscrollbar = gtk_vscrollbar_new (adjustment);
    gtk_adjustment_set_step_increment (adjustment, mts->row_height);
    gtk_table_attach (
        GTK_TABLE (mts), mts->vscrollbar,
        4, 5, 1, 2, 0, GTK_EXPAND | GTK_FILL, 0, 0);
    gtk_widget_show (mts->vscrollbar);

    /* Create the item in the top canvas. */
    mts->item_top = gnome_canvas_item_new (GNOME_CANVAS_GROUP (GNOME_CANVAS (mts->display_top)->root),
                   e_meeting_time_selector_item_get_type (),
                   "EMeetingTimeSelectorItem::meeting_time_selector", mts,
                   NULL);

    /* Create the item in the main canvas. */
    mts->item_main = gnome_canvas_item_new (GNOME_CANVAS_GROUP (GNOME_CANVAS (mts->display_main)->root),
                   e_meeting_time_selector_item_get_type (),
                   "EMeetingTimeSelectorItem::meeting_time_selector", mts,
                   NULL);

    /* Create the hbox containing the color key. */
    hbox = gtk_hbox_new (FALSE, 2);
    gtk_table_attach (GTK_TABLE (mts), hbox,
              1, 4, 3, 4, GTK_FILL, 0, 0, 8);
    gtk_widget_show (hbox);

    e_meeting_time_selector_add_key_color (mts, hbox, _("Tentative"), &mts->busy_colors[E_MEETING_FREE_BUSY_TENTATIVE]);
    e_meeting_time_selector_add_key_color (mts, hbox, _("Busy"), &mts->busy_colors[E_MEETING_FREE_BUSY_BUSY]);
    e_meeting_time_selector_add_key_color (mts, hbox, _("Out of Office"), &mts->busy_colors[E_MEETING_FREE_BUSY_OUT_OF_OFFICE]);
    e_meeting_time_selector_add_key_color (mts, hbox, _("No Information"),
                           NULL);

    separator = gtk_hseparator_new ();
    gtk_table_attach (GTK_TABLE (mts), separator,
              0, 5, 4, 5, GTK_FILL, 0, 6, 6);
    gtk_widget_show (separator);

    /* Create the Invite Others & Options buttons on the left. */
    hbox = gtk_hbox_new (FALSE, 4);
    gtk_table_attach (GTK_TABLE (mts), hbox,
              0, 1, 3, 4, GTK_FILL, 0, 0, 0);
    gtk_widget_show (hbox);

    mts->add_attendees_button =
        gtk_button_new_with_mnemonic (_("Atte_ndees..."));
    gtk_button_set_image (
        GTK_BUTTON (mts->add_attendees_button),
        gtk_image_new_from_stock (
            GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_BUTTON));
    gtk_box_pack_start (GTK_BOX (hbox), mts->add_attendees_button, TRUE, TRUE, 6);
    gtk_widget_show (mts->add_attendees_button);
    g_signal_connect (mts->add_attendees_button, "clicked",
              G_CALLBACK (e_meeting_time_selector_on_invite_others_button_clicked), mts);

    mts->options_button = gtk_button_new ();
    gtk_box_pack_start (GTK_BOX (hbox), mts->options_button, TRUE, TRUE, 6);
    gtk_widget_show (mts->options_button);

    g_signal_connect (mts->options_button, "clicked",
              G_CALLBACK (e_meeting_time_selector_on_options_button_clicked), mts);

    child_hbox = gtk_hbox_new (FALSE, 2);
    gtk_container_add (GTK_CONTAINER (mts->options_button), child_hbox);
    gtk_widget_show (child_hbox);

    label = gtk_label_new_with_mnemonic (_("O_ptions"));
    accel_key = gtk_label_get_mnemonic_keyval (GTK_LABEL (label));
    gtk_box_pack_start (GTK_BOX (child_hbox), label, TRUE, TRUE, 6);
    gtk_widget_show (label);
    gtk_widget_add_accelerator (mts->options_button, "clicked", mts->accel_group,
                    accel_key, GDK_MOD1_MASK, 0);

    arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
    gtk_box_pack_start (GTK_BOX (child_hbox), arrow, FALSE, FALSE, 6);
    gtk_widget_show (arrow);

    /* Create the Options menu. */
    mts->options_menu = gtk_menu_new ();
    gtk_menu_attach_to_widget (GTK_MENU (mts->options_menu), mts->options_button,
                   e_meeting_time_selector_options_menu_detacher);

    menuitem = gtk_check_menu_item_new_with_label ("");
    child = gtk_bin_get_child (GTK_BIN (menuitem));
    gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("Show _only working hours"));
    gtk_menu_shell_append (GTK_MENU_SHELL (mts->options_menu), menuitem);
    gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menuitem),
                    mts->working_hours_only);

    g_signal_connect (menuitem, "toggled",
              G_CALLBACK (e_meeting_time_selector_on_working_hours_toggled), mts);
    gtk_widget_show (menuitem);

    menuitem = gtk_check_menu_item_new_with_label ("");
    child = gtk_bin_get_child (GTK_BIN (menuitem));
    gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("Show _zoomed out"));
    gtk_menu_shell_append (GTK_MENU_SHELL (mts->options_menu), menuitem);
    gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menuitem),
                    mts->zoomed_out);

    g_signal_connect (menuitem, "toggled",
              G_CALLBACK (e_meeting_time_selector_on_zoomed_out_toggled), mts);
    gtk_widget_show (menuitem);

    menuitem = gtk_menu_item_new ();
    gtk_menu_shell_append (GTK_MENU_SHELL (mts->options_menu), menuitem);
    gtk_widget_set_sensitive (menuitem, FALSE);
    gtk_widget_show (menuitem);

    menuitem = gtk_menu_item_new_with_label ("");
    child = gtk_bin_get_child (GTK_BIN (menuitem));
    gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("_Update free/busy"));
    gtk_menu_shell_append (GTK_MENU_SHELL (mts->options_menu), menuitem);

    g_signal_connect (menuitem, "activate",
              G_CALLBACK (e_meeting_time_selector_on_update_free_busy), mts);
    gtk_widget_show (menuitem);

    /* Create the 3 AutoPick buttons on the left. */
    hbox = gtk_hbox_new (FALSE, 0);
    gtk_table_attach (GTK_TABLE (mts), hbox,
              0, 1, 5, 6, GTK_FILL, 0, 0, 0);
    gtk_widget_show (hbox);

    mts->autopick_down_button = gtk_button_new_with_label ("");
    child = gtk_bin_get_child (GTK_BIN (mts->autopick_down_button));
    gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("_<<"));
    accel_key = gtk_label_get_mnemonic_keyval (GTK_LABEL (child));
    gtk_widget_add_accelerator (mts->autopick_down_button, "clicked", mts->accel_group,
                    accel_key, GDK_MOD1_MASK | GDK_SHIFT_MASK, 0);
    gtk_box_pack_start (GTK_BOX (hbox), mts->autopick_down_button, TRUE, TRUE, 6);
    gtk_widget_show (mts->autopick_down_button);
    g_signal_connect (mts->autopick_down_button, "clicked",
              G_CALLBACK (e_meeting_time_selector_on_prev_button_clicked), mts);

    mts->autopick_button = gtk_button_new ();
    gtk_box_pack_start (GTK_BOX (hbox), mts->autopick_button, TRUE, TRUE, 6);
    gtk_widget_show (mts->autopick_button);

    child_hbox = gtk_hbox_new (FALSE, 2);
    gtk_container_add (GTK_CONTAINER (mts->autopick_button), child_hbox);
    gtk_widget_show (child_hbox);

    label = gtk_label_new ("");
    gtk_label_set_text_with_mnemonic (GTK_LABEL (label), _("_Autopick"));
    accel_key = gtk_label_get_mnemonic_keyval (GTK_LABEL (label));
    gtk_box_pack_start (GTK_BOX (child_hbox), label, TRUE, TRUE, 6);
    gtk_widget_show (label);
    gtk_widget_add_accelerator (mts->autopick_button, "clicked", mts->accel_group,
                    accel_key, GDK_MOD1_MASK, 0);
    g_signal_connect (mts->autopick_button, "clicked",
              G_CALLBACK (e_meeting_time_selector_on_autopick_button_clicked), mts);

    arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
    gtk_box_pack_start (GTK_BOX (child_hbox), arrow, FALSE, FALSE, 6);
    gtk_widget_show (arrow);

    mts->autopick_up_button = gtk_button_new_with_label ("");
    child = gtk_bin_get_child (GTK_BIN (mts->autopick_up_button));
    gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _(">_>"));
    accel_key = gtk_label_get_mnemonic_keyval (GTK_LABEL (child));
    gtk_widget_add_accelerator (mts->autopick_up_button, "clicked", mts->accel_group,
                    accel_key, GDK_MOD1_MASK | GDK_SHIFT_MASK, 0);
    gtk_box_pack_start (GTK_BOX (hbox), mts->autopick_up_button, TRUE, TRUE, 6);
    gtk_widget_show (mts->autopick_up_button);
    g_signal_connect (mts->autopick_up_button, "clicked",
              G_CALLBACK (e_meeting_time_selector_on_next_button_clicked), mts);

    /* Create the Autopick menu. */
    mts->autopick_menu = gtk_menu_new ();
    gtk_menu_attach_to_widget (GTK_MENU (mts->autopick_menu), mts->autopick_button,
                   e_meeting_time_selector_autopick_menu_detacher);

    menuitem = gtk_radio_menu_item_new_with_label (NULL, "");
    mts->autopick_all_item = menuitem;
    child = gtk_bin_get_child (GTK_BIN (menuitem));
    group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem));
    gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("_All people and resources"));
    gtk_menu_shell_append (GTK_MENU_SHELL (mts->autopick_menu), menuitem);
    g_signal_connect (menuitem, "toggled",
              G_CALLBACK (e_meeting_time_selector_on_autopick_option_toggled), mts);
    gtk_widget_show (menuitem);

    menuitem = gtk_radio_menu_item_new_with_label (group, "");
    mts->autopick_all_people_one_resource_item = menuitem;
    child = gtk_bin_get_child (GTK_BIN (menuitem));
    group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem));
    gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("All _people and one resource"));
    gtk_menu_shell_append (GTK_MENU_SHELL (mts->autopick_menu), menuitem);
    g_signal_connect (menuitem, "toggled",
              G_CALLBACK (e_meeting_time_selector_on_autopick_option_toggled), mts);
    gtk_widget_show (menuitem);

    menuitem = gtk_radio_menu_item_new_with_label (group, "");
    mts->autopick_required_people_item = menuitem;
    child = gtk_bin_get_child (GTK_BIN (menuitem));
    group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menuitem));
    gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("_Required people"));
    gtk_menu_shell_append (GTK_MENU_SHELL (mts->autopick_menu), menuitem);
    g_signal_connect (menuitem, "activate",
              G_CALLBACK (e_meeting_time_selector_on_autopick_option_toggled), mts);
    gtk_widget_show (menuitem);

    menuitem = gtk_radio_menu_item_new_with_label (group, "");
    mts->autopick_required_people_one_resource_item = menuitem;
    child = gtk_bin_get_child (GTK_BIN (menuitem));
    gtk_label_set_text_with_mnemonic (GTK_LABEL (child), _("Required people and _one resource"));
    gtk_menu_shell_append (GTK_MENU_SHELL (mts->autopick_menu), menuitem);
    g_signal_connect (menuitem, "activate",
              G_CALLBACK (e_meeting_time_selector_on_autopick_option_toggled), mts);
    gtk_widget_show (menuitem);

    /* Create the date entry fields on the right. */
    alignment = gtk_alignment_new (0.0, 0.5, 0, 0);
    gtk_table_attach (GTK_TABLE (mts), alignment,
              1, 4, 5, 6, GTK_FILL, 0, 0, 0);
    gtk_widget_show (alignment);

    table = gtk_table_new (2, 2, FALSE);
    gtk_table_set_row_spacings (GTK_TABLE (table), 4);
    gtk_container_add (GTK_CONTAINER (alignment), table);
    gtk_widget_show (table);

    mts->start_date_edit = e_date_edit_new ();
    gtk_label_set_mnemonic_widget (GTK_LABEL (label), mts->start_date_edit);
    a11y_label = gtk_widget_get_accessible (label);
    a11y_date_edit = gtk_widget_get_accessible (mts->start_date_edit);
    if (a11y_label != NULL && a11y_date_edit != NULL) {
        atk_object_add_relationship (a11y_date_edit,
                    ATK_RELATION_LABELLED_BY,
                    a11y_label);
    }
    e_date_edit_set_show_time (E_DATE_EDIT (mts->start_date_edit), TRUE);

    g_object_bind_property (
        mts, "show-week-numbers",
        mts->start_date_edit, "show-week-numbers",
        G_BINDING_SYNC_CREATE);

    g_object_bind_property (
        mts, "use-24-hour-format",
        mts->start_date_edit, "use-24-hour-format",
        G_BINDING_SYNC_CREATE);

    g_object_bind_property (
        mts, "week-start-day",
        mts->start_date_edit, "week-start-day",
        G_BINDING_SYNC_CREATE);

    gtk_table_attach (GTK_TABLE (table), mts->start_date_edit,
              1, 2, 0, 1, GTK_FILL, 0, 0, 0);
    gtk_widget_show (mts->start_date_edit);
    g_signal_connect (mts->start_date_edit, "changed",
              G_CALLBACK (e_meeting_time_selector_on_start_time_changed), mts);

    label = gtk_label_new_with_mnemonic (_("_Start time:"));
    gtk_label_set_mnemonic_widget (GTK_LABEL (label), (mts->start_date_edit));

    gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
    gtk_table_attach (GTK_TABLE (table), label,
              0, 1, 0, 1, GTK_FILL, 0, 4, 0);
    gtk_widget_show (label);

    mts->end_date_edit = e_date_edit_new ();
    gtk_label_set_mnemonic_widget (GTK_LABEL (label), mts->end_date_edit);
    a11y_label = gtk_widget_get_accessible (label);
    a11y_date_edit = gtk_widget_get_accessible (mts->end_date_edit);
    if (a11y_label != NULL && a11y_date_edit != NULL) {
        atk_object_add_relationship (a11y_date_edit,
                    ATK_RELATION_LABELLED_BY,
                    a11y_label);
    }
    e_date_edit_set_show_time (E_DATE_EDIT (mts->end_date_edit), TRUE);

    g_object_bind_property (
        mts, "show-week-numbers",
        mts->end_date_edit, "show-week-numbers",
        G_BINDING_SYNC_CREATE);

    g_object_bind_property (
        mts, "use-24-hour-format",
        mts->end_date_edit, "use-24-hour-format",
        G_BINDING_SYNC_CREATE);

    g_object_bind_property (
        mts, "week-start-day",
        mts->end_date_edit, "week-start-day",
        G_BINDING_SYNC_CREATE);

    gtk_table_attach (GTK_TABLE (table), mts->end_date_edit,
              1, 2, 1, 2, GTK_FILL, 0, 0, 0);
    gtk_widget_show (mts->end_date_edit);
    g_signal_connect (mts->end_date_edit, "changed",
              G_CALLBACK (e_meeting_time_selector_on_end_time_changed), mts);

    label = gtk_label_new_with_mnemonic (_("_End time:"));
    gtk_label_set_mnemonic_widget (GTK_LABEL (label), (mts->end_date_edit));

    gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
    gtk_table_attach (GTK_TABLE (table), label,
              0, 1, 1, 2, GTK_FILL, 0, 4, 0);
    gtk_widget_show (label);

    gtk_table_set_col_spacing (GTK_TABLE (mts), 0, 4);
    gtk_table_set_row_spacing (GTK_TABLE (mts), 4, 12);

    /* Allocate the colors. */
    e_meeting_time_selector_alloc_named_color (mts, "snow", &mts->bg_color);
    e_meeting_time_selector_alloc_named_color (mts, "snow3", &mts->all_attendees_bg_color);
    e_meeting_time_selector_alloc_named_color (mts, "black", &mts->grid_color);
    e_meeting_time_selector_alloc_named_color (mts, "white", &mts->grid_shadow_color);
    e_meeting_time_selector_alloc_named_color (mts, "gray50", &mts->grid_unused_color);
    e_meeting_time_selector_alloc_named_color (mts, "white", &mts->attendee_list_bg_color);

    e_meeting_time_selector_alloc_named_color (mts, "snow4", &mts->meeting_time_bg_color);
    e_meeting_time_selector_alloc_named_color (mts, "snow", &mts->busy_colors[E_MEETING_FREE_BUSY_FREE]);
    e_meeting_time_selector_alloc_named_color (mts, "#a5d3ef", &mts->busy_colors[E_MEETING_FREE_BUSY_TENTATIVE]);
    e_meeting_time_selector_alloc_named_color (mts, "blue", &mts->busy_colors[E_MEETING_FREE_BUSY_BUSY]);
    e_meeting_time_selector_alloc_named_color (mts, "#ce6194", &mts->busy_colors[E_MEETING_FREE_BUSY_OUT_OF_OFFICE]);

    /* Connect handlers to the adjustments  scroll the other items. */
    scrollable = GTK_SCROLLABLE (mts->display_main);
    adjustment = gtk_scrollable_get_hadjustment (scrollable);
    g_signal_connect (
        adjustment, "value_changed",
        G_CALLBACK (e_meeting_time_selector_hadjustment_changed), mts);
    adjustment = gtk_scrollable_get_vadjustment (scrollable);
    g_signal_connect (
        adjustment, "value_changed",
        G_CALLBACK (e_meeting_time_selector_vadjustment_changed), mts);
    g_signal_connect (
        adjustment, "changed",
        G_CALLBACK (e_meeting_time_selector_vadjustment_changed), mts);

    e_meeting_time_selector_recalc_grid (mts);
    e_meeting_time_selector_ensure_meeting_time_shown (mts);
    e_meeting_time_selector_update_start_date_edit (mts);
    e_meeting_time_selector_update_end_date_edit (mts);
    e_meeting_time_selector_update_date_popup_menus (mts);

    g_signal_emit (mts, signals[CHANGED], 0);
}

/* This adds a color to the color key beneath the main display. If color is
 * NULL, it displays the No Info pattern instead. */
static void
e_meeting_time_selector_add_key_color (EMeetingTimeSelector *mts,
                                       GtkWidget *hbox,
                                       gchar *label_text,
                                       GdkColor *color)
{
    GtkWidget *child_hbox, *darea, *label;

    child_hbox = gtk_hbox_new (FALSE, 4);
    gtk_box_pack_start (GTK_BOX (hbox), child_hbox, TRUE, TRUE, 0);
    gtk_widget_show (child_hbox);

    darea = gtk_drawing_area_new ();
    gtk_box_pack_start (GTK_BOX (child_hbox), darea, FALSE, FALSE, 0);
    g_object_set_data (G_OBJECT (darea), "data", mts);
    gtk_widget_set_size_request (darea, 14, 14);
    gtk_widget_show (darea);

    label = gtk_label_new (label_text);
    gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
    gtk_box_pack_start (GTK_BOX (child_hbox), label, TRUE, TRUE, 6);
    gtk_widget_show (label);

    g_signal_connect (darea, "draw",
              G_CALLBACK (e_meeting_time_selector_draw_key_color),
              color);
}

static gint
e_meeting_time_selector_draw_key_color (GtkWidget *darea,
                                        cairo_t *cr,
                                        GdkColor *color)
{
    EMeetingTimeSelector * mts;
    GtkAllocation allocation;
    GtkStyle *style;

    style = gtk_widget_get_style (darea);
    gtk_widget_get_allocation (darea, &allocation);

    mts = g_object_get_data (G_OBJECT (darea), "data");

    gtk_paint_shadow (
        style, cr, GTK_STATE_NORMAL,
        GTK_SHADOW_IN, NULL, NULL, 0, 0,
        allocation.width, allocation.height);

    if (color) {
        gdk_cairo_set_source_color (cr, color);
    } else {
        cairo_set_source (cr, mts->no_info_pattern);
    }
    cairo_rectangle (cr,
             1, 1,
             allocation.width - 2, allocation.height - 2);
    cairo_fill (cr);

    return TRUE;
}

static void
e_meeting_time_selector_alloc_named_color (EMeetingTimeSelector *mts,
                                           const gchar *name,
                                           GdkColor *c)
{
    g_return_if_fail (name != NULL);
    g_return_if_fail (c != NULL);

    if ( !gdk_color_parse (name, c))
        g_warning ("Failed to parse color: %s\n", name);
}

static void
e_meeting_time_selector_options_menu_detacher (GtkWidget *widget,
                                               GtkMenu *menu)
{
    EMeetingTimeSelector *mts;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (widget));

    mts = E_MEETING_TIME_SELECTOR (widget);
    g_return_if_fail (mts->options_menu == (GtkWidget *) menu);

    mts->options_menu = NULL;
}

static void
e_meeting_time_selector_autopick_menu_detacher (GtkWidget *widget,
                                                GtkMenu *menu)
{
    EMeetingTimeSelector *mts;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (widget));

    mts = E_MEETING_TIME_SELECTOR (widget);
    g_return_if_fail (mts->autopick_menu == (GtkWidget *) menu);

    mts->autopick_menu = NULL;
}

GtkWidget *
e_meeting_time_selector_new (EMeetingStore *ems)
{
    GtkWidget *mts;

    mts = g_object_new (E_TYPE_MEETING_TIME_SELECTOR, NULL);

    e_meeting_time_selector_construct (E_MEETING_TIME_SELECTOR (mts), ems);

    return mts;
}

gboolean
e_meeting_time_selector_get_show_week_numbers (EMeetingTimeSelector *mts)
{
    g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (mts), FALSE);

    return mts->priv->show_week_numbers;
}

void
e_meeting_time_selector_set_show_week_numbers (EMeetingTimeSelector *mts,
                                               gboolean show_week_numbers)
{
    g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));

    mts->priv->show_week_numbers = show_week_numbers;

    g_object_notify (G_OBJECT (mts), "show-week-numbers");
}

gboolean
e_meeting_time_selector_get_use_24_hour_format (EMeetingTimeSelector *mts)
{
    g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (mts), FALSE);

    return mts->priv->use_24_hour_format;
}

void
e_meeting_time_selector_set_use_24_hour_format (EMeetingTimeSelector *mts,
                                                gboolean use_24_hour_format)
{
    g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));

    mts->priv->use_24_hour_format = use_24_hour_format;

    g_object_notify (G_OBJECT (mts), "use-24-hour-format");
}

gint
e_meeting_time_selector_get_week_start_day (EMeetingTimeSelector *mts)
{
    g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (mts), 0);

    return mts->priv->week_start_day;
}

void
e_meeting_time_selector_set_week_start_day (EMeetingTimeSelector *mts,
                                            gint week_start_day)
{
    g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));

    mts->priv->week_start_day = week_start_day;

    g_object_notify (G_OBJECT (mts), "week-start-day");
}

static cairo_pattern_t *
e_meeting_time_selector_create_no_info_pattern (EMeetingTimeSelector *mts)
{
    cairo_surface_t *surface;
    cairo_pattern_t *pattern;
    GdkColor color;
    cairo_t *cr;

    surface = gdk_window_create_similar_surface (gtk_widget_get_window (GTK_WIDGET (mts)),
                             CAIRO_CONTENT_COLOR, 8, 8);
    cr = cairo_create (surface);

        gdk_color_parse ("white", &color);
    gdk_cairo_set_source_color (cr, &color);
    cairo_paint (cr);

    gdk_cairo_set_source_color (cr, &mts->grid_color);
    cairo_set_line_width (cr, 1.0);
    cairo_move_to (cr, -1,  5);
    cairo_line_to (cr,  9, -5);
    cairo_move_to (cr, -1, 13);
    cairo_line_to (cr,  9,  3);
    cairo_stroke (cr);

    cairo_destroy (cr);

    pattern = cairo_pattern_create_for_surface (surface);
    cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);

    cairo_surface_destroy (surface);

    return pattern;
}

static void
e_meeting_time_selector_realize (GtkWidget *widget)
{
    EMeetingTimeSelector *mts;

    if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->realize)
        (*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->realize)(widget);

    mts = E_MEETING_TIME_SELECTOR (widget);

    mts->no_info_pattern = e_meeting_time_selector_create_no_info_pattern (mts);
}

static void
e_meeting_time_selector_unrealize (GtkWidget *widget)
{
    EMeetingTimeSelector *mts;

    mts = E_MEETING_TIME_SELECTOR (widget);

    cairo_pattern_destroy (mts->no_info_pattern);
    mts->no_info_pattern = NULL;

    if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->unrealize)
        (*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->unrealize)(widget);
}

static gint
get_cell_height (GtkTreeView *tree)
{
    GtkTreeViewColumn *column;
    gint height = -1;

    column = gtk_tree_view_get_column (tree, 0);
    gtk_tree_view_column_cell_get_size (column, NULL,
                        NULL, NULL,
                        NULL, &height);

    return height;
}

static gboolean
style_change_idle_func (EMeetingTimeSelector *mts)
{
    EMeetingTime saved_time;
    GtkAdjustment *adjustment;
    GtkWidget *widget;
    gint hour, max_hour_width;
    /*int maxheight;      */
    PangoFontDescription *font_desc;
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;
    PangoLayout *layout;

    /* Set up Pango prerequisites */
    widget = GTK_WIDGET (mts);
    font_desc = gtk_widget_get_style (widget)->font_desc;
    pango_context = gtk_widget_get_pango_context (widget);
    font_metrics = pango_context_get_metrics (pango_context, font_desc,
                          pango_context_get_language (pango_context));
    layout = pango_layout_new (pango_context);

    /* Calculate the widths of the hour strings in the style's font. */
    max_hour_width = 0;
    for (hour = 0; hour < 24; hour++) {
        if (e_meeting_time_selector_get_use_24_hour_format (mts))
            pango_layout_set_text (layout, EMeetingTimeSelectorHours[hour], -1);
        else
            pango_layout_set_text (layout, EMeetingTimeSelectorHours12[hour], -1);

        pango_layout_get_pixel_size (layout, &mts->hour_widths[hour], NULL);
        max_hour_width = MAX (max_hour_width, mts->hour_widths[hour]);
    }

    /* add also some padding for lines so it fits better */
    mts->row_height = get_cell_height (GTK_TREE_VIEW (mts->list_view)) + 2;
    mts->col_width = max_hour_width + 6;

    e_meeting_time_selector_save_position (mts, &saved_time);
    e_meeting_time_selector_recalc_grid (mts);
    e_meeting_time_selector_restore_position (mts, &saved_time);

    gtk_widget_set_size_request (mts->display_top, -1, mts->row_height * 3 + 4);

    /*
     * FIXME: I can't find a way to get the treeview header heights
     * other than the below but it isn't nice to realize that widget here
     *
 *
    gtk_widget_realize (mts->list_view);
    gdk_window_get_position (gtk_tree_view_get_bin_window (GTK_TREE_VIEW (mts->list_view)),
                 NULL, &maxheight);
    gtk_widget_set_size_request (mts->attendees_vbox_spacer, 1, mts->row_height * 3 - maxheight);
 *
    */

    gtk_widget_set_size_request (mts->attendees_vbox_spacer, 1, mts->row_height * 2 - 6);

    widget = mts->display_main;

    adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
    gtk_adjustment_set_step_increment (adjustment, mts->day_width);

    adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
    gtk_adjustment_set_step_increment (adjustment, mts->row_height);

    g_object_unref (layout);
    pango_font_metrics_unref (font_metrics);

    mts->style_change_idle_id = 0;

    return FALSE;
}

static void
e_meeting_time_selector_style_set (GtkWidget *widget,
                                   GtkStyle *previous_style)
{
    EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (widget);

    if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->style_set)
        (*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->style_set)(widget, previous_style);

    if (!mts->style_change_idle_id)
        mts->style_change_idle_id = g_idle_add (
            (GSourceFunc) style_change_idle_func, widget);
}

/* This draws a shadow around the top display and main display. */
static gint
e_meeting_time_selector_draw (GtkWidget *widget,
                              cairo_t *cr)
{
    EMeetingTimeSelector *mts;

    mts = E_MEETING_TIME_SELECTOR (widget);

    e_meeting_time_selector_draw_shadow (mts, cr);

    if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->draw)
        (*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->draw)(widget, cr);

    return FALSE;
}

static void
e_meeting_time_selector_draw_shadow (EMeetingTimeSelector *mts,
                                     cairo_t *cr)
{
    GtkAllocation allocation;
    GtkStyle *style;
    gint x, y, w, h;

    cairo_save (cr);

    /* Draw the shadow around the graphical displays. */
    gtk_widget_get_allocation (mts->display_top, &allocation);
    x = allocation.x - 2;
    y = allocation.y - 2;
    w = allocation.width + 4;
    h = allocation.height + allocation.height + 4;

    style = gtk_widget_get_style (GTK_WIDGET (mts));

    gtk_paint_shadow (
        style, cr, GTK_STATE_NORMAL,
        GTK_SHADOW_IN, NULL, NULL, x, y, w, h);

    cairo_restore (cr);
}

/* When the main canvas scrolls, we scroll the other canvases. */
static void
e_meeting_time_selector_hadjustment_changed (GtkAdjustment *adjustment,
                                             EMeetingTimeSelector *mts)
{
    GtkAdjustment *hadjustment;
    GtkScrollable *scrollable;
    gdouble value;

    scrollable = GTK_SCROLLABLE (mts->display_top);
    hadjustment = gtk_scrollable_get_hadjustment (scrollable);

    value = gtk_adjustment_get_value (adjustment);
    gtk_adjustment_set_value (hadjustment, value);
}

static void
e_meeting_time_selector_vadjustment_changed (GtkAdjustment *adjustment,
                                             EMeetingTimeSelector *mts)
{
    GtkAdjustment *vadjustment;
    GtkTreeView *tree_view;
    gdouble value;

    tree_view = GTK_TREE_VIEW (mts->list_view);
    vadjustment = gtk_tree_view_get_vadjustment (tree_view);

    value = gtk_adjustment_get_value (adjustment);
    gtk_adjustment_set_value (vadjustment, value);
}

void
e_meeting_time_selector_get_meeting_time (EMeetingTimeSelector *mts,
                                          gint *start_year,
                                          gint *start_month,
                                          gint *start_day,
                                          gint *start_hour,
                                          gint *start_minute,
                                          gint *end_year,
                                          gint *end_month,
                                          gint *end_day,
                                          gint *end_hour,
                                          gint *end_minute)
{
    *start_year = g_date_get_year (&mts->meeting_start_time.date);
    *start_month = g_date_get_month (&mts->meeting_start_time.date);
    *start_day = g_date_get_day (&mts->meeting_start_time.date);
    *start_hour = mts->meeting_start_time.hour;
    *start_minute = mts->meeting_start_time.minute;

    *end_year = g_date_get_year (&mts->meeting_end_time.date);
    *end_month = g_date_get_month (&mts->meeting_end_time.date);
    *end_day = g_date_get_day (&mts->meeting_end_time.date);
    *end_hour = mts->meeting_end_time.hour;
    *end_minute = mts->meeting_end_time.minute;
}

gboolean
e_meeting_time_selector_set_meeting_time (EMeetingTimeSelector *mts,
                                          gint start_year,
                                          gint start_month,
                                          gint start_day,
                                          gint start_hour,
                                          gint start_minute,
                                          gint end_year,
                                          gint end_month,
                                          gint end_day,
                                          gint end_hour,
                                          gint end_minute)
{
    g_return_val_if_fail (E_IS_MEETING_TIME_SELECTOR (mts), FALSE);

    /* Check the dates are valid. */
    if (!g_date_valid_dmy (start_day, start_month, start_year)
        || !g_date_valid_dmy (end_day, end_month, end_year)
        || start_hour < 0 || start_hour > 23
        || end_hour < 0 || end_hour > 23
        || start_minute < 0 || start_minute > 59
        || end_minute < 0 || end_minute > 59)
        return FALSE;

    g_date_set_dmy (&mts->meeting_start_time.date, start_day, start_month,
            start_year);
    mts->meeting_start_time.hour = start_hour;
    mts->meeting_start_time.minute = start_minute;
    g_date_set_dmy (&mts->meeting_end_time.date, end_day, end_month,
            end_year);
    mts->meeting_end_time.hour = end_hour;
    mts->meeting_end_time.minute = end_minute;

    mts->meeting_positions_valid = FALSE;

    gtk_widget_queue_draw (mts->display_top);
    gtk_widget_queue_draw (mts->display_main);

    /* Set the times in the EDateEdit widgets. */
    e_meeting_time_selector_update_start_date_edit (mts);
    e_meeting_time_selector_update_end_date_edit (mts);

    g_signal_emit (mts, signals[CHANGED], 0);

    return TRUE;
}

void
e_meeting_time_selector_set_all_day (EMeetingTimeSelector *mts,
                                     gboolean all_day)
{
    EMeetingTime saved_time;

    mts->all_day = all_day;

    e_date_edit_set_show_time (E_DATE_EDIT (mts->start_date_edit),
                   !all_day);
    e_date_edit_set_show_time (E_DATE_EDIT (mts->end_date_edit),
                   !all_day);

    e_meeting_time_selector_save_position (mts, &saved_time);
    e_meeting_time_selector_recalc_grid (mts);
    e_meeting_time_selector_restore_position (mts, &saved_time);

    gtk_widget_queue_draw (mts->display_top);
    gtk_widget_queue_draw (mts->display_main);
    e_meeting_time_selector_update_date_popup_menus (mts);
}

void
e_meeting_time_selector_set_working_hours_only (EMeetingTimeSelector *mts,
                                                gboolean working_hours_only)
{
    EMeetingTime saved_time;

    g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));

    if (mts->working_hours_only == working_hours_only)
        return;

    mts->working_hours_only = working_hours_only;

    e_meeting_time_selector_save_position (mts, &saved_time);
    e_meeting_time_selector_recalc_grid (mts);
    e_meeting_time_selector_restore_position (mts, &saved_time);

    gtk_widget_queue_draw (mts->display_top);
    gtk_widget_queue_draw (mts->display_main);
    e_meeting_time_selector_update_date_popup_menus (mts);
}

void
e_meeting_time_selector_set_working_hours (EMeetingTimeSelector *mts,
                                           gint day_start_hour,
                                           gint day_start_minute,
                                           gint day_end_hour,
                                           gint day_end_minute)
{
    EMeetingTime saved_time;

    g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));

    if (mts->day_start_hour == day_start_hour
        && mts->day_start_minute == day_start_minute
        && mts->day_end_hour == day_end_hour
        && mts->day_end_minute == day_end_minute)
        return;

    mts->day_start_hour = day_start_hour;
    mts->day_start_minute = day_start_minute;

    /* Make sure we always show atleast an hour */
    if (day_start_hour * 60 + day_start_minute + 60 < day_end_hour * 60 + day_end_minute) {
        mts->day_end_hour = day_end_hour;
        mts->day_end_minute = day_end_minute;
    } else {
        mts->day_end_hour = day_start_hour + 1;
        mts->day_end_minute = day_start_minute;
    }

    e_meeting_time_selector_save_position (mts, &saved_time);
    e_meeting_time_selector_recalc_grid (mts);
    e_meeting_time_selector_restore_position (mts, &saved_time);

    gtk_widget_queue_draw (mts->display_top);
    gtk_widget_queue_draw (mts->display_main);
    e_meeting_time_selector_update_date_popup_menus (mts);
}

void
e_meeting_time_selector_set_zoomed_out (EMeetingTimeSelector *mts,
                                        gboolean zoomed_out)
{
    EMeetingTime saved_time;

    g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));

    if (mts->zoomed_out == zoomed_out)
        return;

    mts->zoomed_out = zoomed_out;

    e_meeting_time_selector_save_position (mts, &saved_time);
    e_meeting_time_selector_recalc_grid (mts);
    e_meeting_time_selector_restore_position (mts, &saved_time);

    gtk_widget_queue_draw (mts->display_top);
    gtk_widget_queue_draw (mts->display_main);
}

static gboolean
e_meeting_time_selector_refresh_cb (gpointer data)
{
    EMeetingTimeSelector *mts = data;

    if (e_meeting_store_get_num_queries (mts->model) == 0) {
        GdkCursor *cursor;
        GdkWindow *window;

        cursor = gdk_cursor_new (GDK_LEFT_PTR);
        window = gtk_widget_get_window (GTK_WIDGET (mts));
        if (window)
            gdk_window_set_cursor (window, cursor);
        g_object_unref (cursor);

        mts->last_cursor_set = GDK_LEFT_PTR;

        e_meeting_time_selector_item_set_normal_cursor (E_MEETING_TIME_SELECTOR_ITEM (mts->item_top));
        e_meeting_time_selector_item_set_normal_cursor (E_MEETING_TIME_SELECTOR_ITEM (mts->item_main));
    }

    if (mts->display_top != NULL)
        gtk_widget_queue_draw (mts->display_top);
    if (mts->display_main != NULL)
        gtk_widget_queue_draw (mts->display_main);

    g_object_unref (G_OBJECT (mts));

    return FALSE;
}

void
e_meeting_time_selector_refresh_free_busy (EMeetingTimeSelector *mts,
                                           gint row,
                                           gboolean all)
{
    EMeetingTime start, end;

    /* nothing to refresh, lets not leak a busy cursor */
    if (e_meeting_store_count_actual_attendees (mts->model) <= 0)
        return;

    start = mts->meeting_start_time;
    g_date_subtract_days (&start.date, E_MEETING_TIME_SELECTOR_FB_DAYS_BEFORE);
    start.hour = 0;
    start.minute = 0;
    end = mts->meeting_end_time;
    g_date_add_days (&end.date, E_MEETING_TIME_SELECTOR_FB_DAYS_AFTER);
    end.hour = 0;
    end.minute = 0;

    /* XXX This function is called during schedule page initialization
     *     before the meeting time selector is realized, meaning it has
     *     no GdkWindow yet.  This avoids a runtime warning. */
    if (gtk_widget_get_realized (GTK_WIDGET (mts))) {
        GdkCursor *cursor;
        GdkWindow *window;

        /* Set the cursor to Busy.  We need to reset it to
         * normal once the free busy queries are complete. */
        cursor = gdk_cursor_new (GDK_WATCH);
        window = gtk_widget_get_window (GTK_WIDGET (mts));
        gdk_window_set_cursor (window, cursor);
        g_object_unref (cursor);

        mts->last_cursor_set = GDK_WATCH;
    }

    /* Ref ourselves in case we are called back after destruction,
     * we can do this because we will get a call back even after
     * an error */
    /* FIXME We should really have a mechanism to unqueue the
     * notification */
    if (all) {
        gint i;

        for (i = 0; i < e_meeting_store_count_actual_attendees (mts->model); i++)
            g_object_ref (G_OBJECT (mts));
    } else {
        g_object_ref (G_OBJECT (mts));
    }

    if (all)
        e_meeting_store_refresh_all_busy_periods (mts->model, &start, &end,
                              e_meeting_time_selector_refresh_cb, mts);
    else
        e_meeting_store_refresh_busy_periods (mts->model, row, &start, &end,
                              e_meeting_time_selector_refresh_cb, mts);
}

EMeetingTimeSelectorAutopickOption
e_meeting_time_selector_get_autopick_option (EMeetingTimeSelector *mts)
{
    GtkWidget *widget;

    widget = mts->autopick_all_item;
    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)))
        return E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_RESOURCES;

    widget = mts->autopick_all_people_one_resource_item;
    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)))
        return E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE;

    widget = mts->autopick_required_people_item;
    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)))
        return E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE;

    return E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE;
}

void
e_meeting_time_selector_set_autopick_option (EMeetingTimeSelector *mts,
                                             EMeetingTimeSelectorAutopickOption autopick_option)
{
    g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));

    switch (autopick_option) {
    case E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_RESOURCES:
        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_all_item), TRUE);
        break;
    case E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE:
        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_all_people_one_resource_item), TRUE);
        break;
    case E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE:
        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_required_people_item), TRUE);
        break;
    case E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE:
        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_required_people_one_resource_item), TRUE);
        break;
    }
}

void
e_meeting_time_selector_set_read_only (EMeetingTimeSelector *mts,
                                       gboolean read_only)
{
    g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));

    gtk_widget_set_sensitive (GTK_WIDGET (mts->list_view), !read_only);
    gtk_widget_set_sensitive (mts->display_main, !read_only);
    gtk_widget_set_sensitive (mts->add_attendees_button, !read_only);
    gtk_widget_set_sensitive (mts->autopick_down_button, !read_only);
    gtk_widget_set_sensitive (mts->autopick_button, !read_only);
    gtk_widget_set_sensitive (mts->autopick_up_button, !read_only);
    gtk_widget_set_sensitive (mts->start_date_edit, !read_only);
    gtk_widget_set_sensitive (mts->end_date_edit, !read_only);
}

/*
 * DEBUGGING ROUTINES - functions to output various bits of data.
 */

#ifdef E_MEETING_TIME_SELECTOR_DEBUG

/* Debugging function to dump information on all attendees. */
void
e_meeting_time_selector_dump (EMeetingTimeSelector *mts)
{
    EMeetingTimeSelectorAttendee *attendee;
    EMeetingTimeSelectorPeriod *period;
    gint row, period_num;
    gchar buffer[128];

    g_return_if_fail (E_IS_MEETING_TIME_SELECTOR (mts));

    g_print ("\n\nAttendee Information:\n");

    for (row = 0; row < mts->attendees->len; row++) {
        attendee = &g_array_index (mts->attendees,
                       EMeetingTimeSelectorAttendee, row);
        g_print ("Attendee: %s\n", attendee->name);
        g_print ("  Longest Busy Period: %i days\n",
             attendee->longest_period_in_days);

        e_meeting_time_selector_attendee_ensure_periods_sorted (mts, attendee);
#if 1
        for (period_num = 0;
             period_num < attendee->busy_periods->len;
             period_num++) {
            period = &g_array_index (attendee->busy_periods,
                         EMeetingTimeSelectorPeriod,
                         period_num);

            /* These are just for debugging so don't need i18n. */
            g_date_strftime (buffer, sizeof (buffer),
                     "%A, %B %d, %Y", &period->start.date);
            g_print ("  Start: %s %i:%02i\n", buffer,
                 period->start.hour, period->start.minute);

            g_date_strftime (buffer, sizeof (buffer),
                     "%A, %B %d, %Y", &period->end.date);
            g_print ("  End  : %s %i:%02i\n", buffer,
                 period->end.hour, period->end.minute);
        }
#endif
    }

}

/* This formats a EMeetingTimein a string and returns it.
 * Note that it uses a static buffer. */
gchar *
e_meeting_time_selector_dump_time (EMeetingTime *mtstime)
{
    static gchar buffer[128];

    gchar buffer2[128];

    /* This is just for debugging so doesn't need i18n. */
    g_date_strftime (buffer, sizeof (buffer), "%A, %B %d, %Y",
             &mtstime->date);
    sprintf (buffer2, " at %i:%02i", (gint) mtstime->hour,
         (gint) mtstime->minute);
    strcat (buffer, buffer2);

    return buffer;
}

/* This formats a GDate in a string and returns it.
 * Note that it uses a static buffer. */
gchar *
e_meeting_time_selector_dump_date (GDate *date)
{
    static gchar buffer[128];

    /* This is just for debugging so doesn't need i18n. */
    g_date_strftime (buffer, sizeof (buffer), "%A, %B %d, %Y", date);
    return buffer;
}

#endif /* E_MEETING_TIME_SELECTOR_DEBUG */

static void
e_meeting_time_selector_on_invite_others_button_clicked (GtkWidget *button,
                                                         EMeetingTimeSelector *mts)
{
    e_meeting_list_view_invite_others_dialog (mts->list_view);
}

static void
e_meeting_time_selector_on_options_button_clicked (GtkWidget *button,
                                                   EMeetingTimeSelector *mts)
{
    gtk_menu_popup (GTK_MENU (mts->options_menu), NULL, NULL,
            e_meeting_time_selector_options_menu_position_callback,
            mts, 1, GDK_CURRENT_TIME);
}

static void
e_meeting_time_selector_options_menu_position_callback (GtkMenu *menu,
                                                        gint *x,
                                                        gint *y,
                                                        gboolean *push_in,
                                                        gpointer user_data)
{
    EMeetingTimeSelector *mts;
    GtkRequisition menu_requisition;
    GtkAllocation allocation;
    GtkWidget *widget;
    GdkWindow *window;
    gint max_x, max_y;

    mts = E_MEETING_TIME_SELECTOR (user_data);

    /* Calculate our preferred position. */
    widget = mts->options_button;
    window = gtk_widget_get_window (widget);
    gdk_window_get_origin (window, x, y);
    gtk_widget_get_allocation (widget, &allocation);
    *x += allocation.x;
    *y += allocation.y + allocation.height - 2;

    /* Now make sure we are on the screen. */
    gtk_widget_get_preferred_size (mts->options_menu, &menu_requisition, NULL);
    max_x = MAX (0, gdk_screen_width () - menu_requisition.width);
    max_y = MAX (0, gdk_screen_height () - menu_requisition.height);
    *x = CLAMP (*x, 0, max_x);
    *y = CLAMP (*y, 0, max_y);
}

static void
e_meeting_time_selector_on_update_free_busy (GtkWidget *button,
                                             EMeetingTimeSelector *mts)
{
    /* Make sure the menu pops down, which doesn't happen by default if
     * keyboard accelerators are used. */
    if (gtk_widget_get_visible (mts->options_menu))
        gtk_menu_popdown (GTK_MENU (mts->options_menu));

    e_meeting_time_selector_refresh_free_busy (mts, 0, TRUE);
}

static void
e_meeting_time_selector_on_autopick_button_clicked (GtkWidget *button,
                                                    EMeetingTimeSelector *mts)
{
    gtk_menu_popup (GTK_MENU (mts->autopick_menu), NULL, NULL,
            e_meeting_time_selector_autopick_menu_position_callback,
            mts, 1, GDK_CURRENT_TIME);
}

static void
e_meeting_time_selector_autopick_menu_position_callback (GtkMenu *menu,
                                                         gint *x,
                                                         gint *y,
                                                         gboolean *push_in,
                                                         gpointer user_data)
{
    EMeetingTimeSelector *mts;
    GtkRequisition menu_requisition;
    GtkAllocation allocation;
    GtkWidget *widget;
    GdkWindow *window;
    gint max_x, max_y;

    mts = E_MEETING_TIME_SELECTOR (user_data);

    /* Calculate our preferred position. */
    widget = mts->autopick_button;
    window = gtk_widget_get_window (widget);
    gdk_window_get_origin (window, x, y);
    gtk_widget_get_allocation (widget, &allocation);
    *x += allocation.x;
    *y += allocation.y + allocation.height - 2;

    /* Now make sure we are on the screen. */
    gtk_widget_get_preferred_size (mts->autopick_menu, &menu_requisition, NULL);
    max_x = MAX (0, gdk_screen_width () - menu_requisition.width);
    max_y = MAX (0, gdk_screen_height () - menu_requisition.height);
    *x = CLAMP (*x, 0, max_x);
    *y = CLAMP (*y, 0, max_y);
}

static void
e_meeting_time_selector_on_autopick_option_toggled (GtkWidget *button,
                                                    EMeetingTimeSelector *mts)
{
    /* Make sure the menu pops down, which doesn't happen by default if
     * keyboard accelerators are used. */
    if (gtk_widget_get_visible (mts->autopick_menu))
        gtk_menu_popdown (GTK_MENU (mts->autopick_menu));
}

static void
e_meeting_time_selector_on_prev_button_clicked (GtkWidget *button,
                                                EMeetingTimeSelector *mts)
{
    e_meeting_time_selector_autopick (mts, FALSE);
}

static void
e_meeting_time_selector_on_next_button_clicked (GtkWidget *button,
                                                EMeetingTimeSelector *mts)
{
    e_meeting_time_selector_autopick (mts, TRUE);
}

/* This tries to find the previous or next meeting time for which all
 * attendees will be available. */
static void
e_meeting_time_selector_autopick (EMeetingTimeSelector *mts,
                                  gboolean forward)
{
    EMeetingTime start_time, end_time, *resource_free;
    EMeetingAttendee *attendee;
    EMeetingFreeBusyPeriod *period;
    EMeetingTimeSelectorAutopickOption autopick_option;
    gint duration_days, duration_hours, duration_minutes, row;
    gboolean meeting_time_ok, skip_optional = FALSE;
    gboolean need_one_resource = FALSE, found_resource;

    /* Get the current meeting duration in days + hours + minutes. */
    e_meeting_time_selector_calculate_time_difference (&mts->meeting_start_time, &mts->meeting_end_time, &duration_days, &duration_hours, &duration_minutes);

    /* Find the first appropriate start time. */
    start_time = mts->meeting_start_time;
    if (forward)
        e_meeting_time_selector_find_nearest_interval (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes);
    else
        e_meeting_time_selector_find_nearest_interval_backward (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes);

    /* Determine if we can skip optional people and if we only need one
     * resource based on the autopick option. */
    autopick_option = e_meeting_time_selector_get_autopick_option (mts);
    if (autopick_option == E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE
        || autopick_option == E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE)
        skip_optional = TRUE;
    if (autopick_option == E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE
        || autopick_option == E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE)
        need_one_resource = TRUE;

    /* Keep moving forward or backward until we find a possible meeting
     * time. */
    for (;;) {
        meeting_time_ok = TRUE;
        found_resource = FALSE;
        resource_free = NULL;

        /* Step through each attendee, checking if the meeting time
         * intersects one of the attendees busy periods. */
        for (row = 0; row <  e_meeting_store_count_actual_attendees (mts->model); row++) {
            attendee = e_meeting_store_find_attendee_at_row (mts->model, row);

            /* Skip optional people if they don't matter. */
            if (skip_optional && e_meeting_attendee_get_atype (attendee) == E_MEETING_ATTENDEE_OPTIONAL_PERSON)
                continue;

            period = e_meeting_time_selector_find_time_clash (mts, attendee, &start_time, &end_time);

            if (need_one_resource && e_meeting_attendee_get_atype (attendee) == E_MEETING_ATTENDEE_RESOURCE) {
                if (period) {
                    /* We want to remember the closest
                     * prev/next time that one resource is
                     * available, in case we don't find any
                     * free resources. */
                    if (forward) {
                        if (!resource_free || e_meeting_time_compare_times (resource_free, &period->end) > 0)
                            resource_free = &period->end;
                    } else {
                        if (!resource_free || e_meeting_time_compare_times (resource_free, &period->start) < 0)
                            resource_free = &period->start;
                    }

                } else {
                    found_resource = TRUE;
                }
            } else if (period) {
                /* Skip the period which clashed. */
                if (forward) {
                    start_time = period->end;
                } else {
                    start_time = period->start;
                    e_meeting_time_selector_adjust_time (&start_time, -duration_days, -duration_hours, -duration_minutes);
                }
                meeting_time_ok = FALSE;
                break;
            }
        }

        /* Check that we found one resource if necessary. If not, skip
         * to the closest time that a resource is free. Note that if
         * there are no resources, resource_free will never get set,
         * so we assume the meeting time is OK. */
        if (meeting_time_ok && need_one_resource && !found_resource
            && resource_free) {
            if (forward) {
                start_time = *resource_free;
            } else {
                start_time = *resource_free;
                e_meeting_time_selector_adjust_time (&start_time, -duration_days, -duration_hours, -duration_minutes);
            }
            meeting_time_ok = FALSE;
        }

        if (meeting_time_ok) {
            mts->meeting_start_time = start_time;
            mts->meeting_end_time = end_time;
            mts->meeting_positions_valid = FALSE;
            gtk_widget_queue_draw (mts->display_top);
            gtk_widget_queue_draw (mts->display_main);

            /* Make sure the time is shown. */
            e_meeting_time_selector_ensure_meeting_time_shown (mts);

            /* Set the times in the EDateEdit widgets. */
            e_meeting_time_selector_update_start_date_edit (mts);
            e_meeting_time_selector_update_end_date_edit (mts);

            g_signal_emit (mts, signals[CHANGED], 0);

            return;
        }

        /* Move forward to the next possible interval. */
        if (forward)
            e_meeting_time_selector_find_nearest_interval (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes);
        else
            e_meeting_time_selector_find_nearest_interval_backward (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes);
    }
}

static void
e_meeting_time_selector_calculate_time_difference (EMeetingTime *start,
                                                   EMeetingTime *end,
                                                   gint *days,
                                                   gint *hours,
                                                   gint *minutes)
{
    *days = g_date_get_julian (&end->date) - g_date_get_julian (&start->date);
    *hours = end->hour - start->hour;
    *minutes = end->minute - start->minute;
    if (*minutes < 0) {
        *minutes += 60;
        *hours = *hours - 1;
    }
    if (*hours < 0) {
        *hours += 24;
        *days = *days - 1;
    }
}

/* This moves the given time forward to the next suitable start of a meeting.
 * If zoomed_out is set, this means every hour. If not every half-hour. */
static void
e_meeting_time_selector_find_nearest_interval (EMeetingTimeSelector *mts,
                                               EMeetingTime *start_time,
                                               EMeetingTime *end_time,
                                               gint days,
                                               gint hours,
                                               gint mins)
{
    gint minutes_shown;
    gboolean set_to_start_of_working_day = FALSE;

    if (!mts->all_day) {
        if (mts->zoomed_out) {
            start_time->hour++;
            start_time->minute = 0;
        } else {
            start_time->minute += 30;
            start_time->minute -= start_time->minute % 30;
        }
    } else {
        g_date_add_days (&start_time->date, 1);
        start_time->hour = 0;
        start_time->minute = 0;
    }
    e_meeting_time_selector_fix_time_overflows (start_time);

    *end_time = *start_time;
    e_meeting_time_selector_adjust_time (end_time, days, hours, mins);

    /* Check if the interval is less than a day as seen in the display.
     * If it isn't we don't worry about the working day. */
    if (!mts->working_hours_only || days > 0)
        return;
    minutes_shown = (mts->day_end_hour - mts->day_start_hour) * 60;
    minutes_shown += mts->day_end_minute - mts->day_start_minute;
    if (hours * 60 + mins > minutes_shown)
        return;

    /* If the meeting time finishes past the end of the working day, move
     * onto the start of the next working day. If the meeting time starts
     * before the working day, move it on as well. */
    if (start_time->hour > mts->day_end_hour
        || (start_time->hour == mts->day_end_hour
        && start_time->minute > mts->day_end_minute)
        || end_time->hour > mts->day_end_hour
        || (end_time->hour == mts->day_end_hour
        && end_time->minute > mts->day_end_minute)) {
        g_date_add_days (&start_time->date, 1);
        set_to_start_of_working_day = TRUE;
    } else if (start_time->hour < mts->day_start_hour
           || (start_time->hour == mts->day_start_hour
               && start_time->minute < mts->day_start_minute)) {
        set_to_start_of_working_day = TRUE;
    }

    if (set_to_start_of_working_day) {
        start_time->hour = mts->day_start_hour;
        start_time->minute = mts->day_start_minute;

        if (mts->zoomed_out) {
            if (start_time->minute > 0) {
                start_time->hour++;
                start_time->minute = 0;
            }
        } else {
            start_time->minute += 29;
            start_time->minute -= start_time->minute % 30;
        }

        e_meeting_time_selector_fix_time_overflows (start_time);

        *end_time = *start_time;
        e_meeting_time_selector_adjust_time (end_time, days, hours, mins);
    }
}

/* This moves the given time backward to the next suitable start of a meeting.
 * If zoomed_out is set, this means every hour. If not every half-hour. */
static void
e_meeting_time_selector_find_nearest_interval_backward (EMeetingTimeSelector *mts,
                                                        EMeetingTime *start_time,
                                                        EMeetingTime *end_time,
                                                        gint days,
                                                        gint hours,
                                                        gint mins)
{
    gint new_hour, minutes_shown;
    gboolean set_to_end_of_working_day = FALSE;

    if (!mts->all_day) {
        new_hour = start_time->hour;
        if (mts->zoomed_out) {
            if (start_time->minute == 0)
                new_hour--;
            start_time->minute = 0;
        } else {
            if (start_time->minute == 0) {
                start_time->minute = 30;
                new_hour--;
            } else if (start_time->minute <= 30)
                start_time->minute = 0;
            else
                start_time->minute = 30;
        }
        if (new_hour < 0) {
            new_hour += 24;
            g_date_subtract_days (&start_time->date, 1);
        }
        start_time->hour = new_hour;
    } else {
        g_date_subtract_days (&start_time->date, 1);
        start_time->hour = 0;
        start_time->minute = 0;
    }

    *end_time = *start_time;
    e_meeting_time_selector_adjust_time (end_time, days, hours, mins);

    /* Check if the interval is less than a day as seen in the display.
     * If it isn't we don't worry about the working day. */
    if (!mts->working_hours_only || days > 0)
        return;
    minutes_shown = (mts->day_end_hour - mts->day_start_hour) * 60;
    minutes_shown += mts->day_end_minute - mts->day_start_minute;
    if (hours * 60 + mins > minutes_shown)
        return;

    /* If the meeting time finishes past the end of the working day, move
     * back to the end of the working day. If the meeting time starts
     * before the working day, move it back to the end of the previous
     * working day. */
    if (start_time->hour > mts->day_end_hour
        || (start_time->hour == mts->day_end_hour
        && start_time->minute > mts->day_end_minute)
        || end_time->hour > mts->day_end_hour
        || (end_time->hour == mts->day_end_hour
        && end_time->minute > mts->day_end_minute)) {
        set_to_end_of_working_day = TRUE;
    } else if (start_time->hour < mts->day_start_hour
           || (start_time->hour == mts->day_start_hour
               && start_time->minute < mts->day_start_minute)) {
        g_date_subtract_days (&end_time->date, 1);
        set_to_end_of_working_day = TRUE;
    }

    if (set_to_end_of_working_day) {
        end_time->hour = mts->day_end_hour;
        end_time->minute = mts->day_end_minute;
        *start_time = *end_time;
        e_meeting_time_selector_adjust_time (start_time, -days, -hours, -mins);

        if (mts->zoomed_out) {
            start_time->minute = 0;
        } else {
            start_time->minute -= start_time->minute % 30;
        }

        *end_time = *start_time;
        e_meeting_time_selector_adjust_time (end_time, days, hours, mins);
    }
}

/* This adds on the given days, hours & minutes to a EMeetingTimeSelectorTime.
 * It is used to calculate the end of a period given a start & duration.
 * Days, hours & minutes can be negative, to move backwards, but they should
 * be within normal ranges, e.g. hours should be between -23 and 23. */
static void
e_meeting_time_selector_adjust_time (EMeetingTime *mtstime,
                                     gint days,
                                     gint hours,
                                     gint minutes)
{
    gint new_hours, new_minutes;

    /* We have to handle negative values for hous and minutes here, since
     * EMeetingTimeuses guint8s to store them. */
    new_minutes = mtstime->minute + minutes;
    if (new_minutes < 0) {
        new_minutes += 60;
        hours -= 1;
    }

    new_hours = mtstime->hour + hours;
    if (new_hours < 0) {
        new_hours += 24;
        days -= 1;
    }

    g_date_add_days (&mtstime->date, days);
    mtstime->hour = new_hours;
    mtstime->minute = new_minutes;

    e_meeting_time_selector_fix_time_overflows (mtstime);
}

/* This looks for any busy period of the given attendee which clashes with
 * the start and end time. It uses a binary search. */
static EMeetingFreeBusyPeriod *
e_meeting_time_selector_find_time_clash (EMeetingTimeSelector *mts,
                                         EMeetingAttendee *attendee,
                                         EMeetingTime *start_time,
                                         EMeetingTime *end_time)
{
    EMeetingFreeBusyPeriod *period;
    const GArray *busy_periods;
    gint period_num;

    busy_periods = e_meeting_attendee_get_busy_periods (attendee);
    period_num = e_meeting_attendee_find_first_busy_period (attendee, &start_time->date);

    if (period_num == -1)
        return NULL;

    /* Step forward through the busy periods until we find a clash or we
     * go past the end_time. */
    while (period_num < busy_periods->len) {
        period = &g_array_index (busy_periods,  EMeetingFreeBusyPeriod, period_num);

        /* If the period starts at or after the end time, there is no
         * clash and we are finished. The busy periods are sorted by
         * their start times, so all the rest will be later. */
        if (e_meeting_time_compare_times (&period->start, end_time) >= 0)
            return NULL;

        /* If the period ends after the start time, we have found a
         * clash. From the above test we already know the busy period
         * isn't completely after the meeting time. */
        if (e_meeting_time_compare_times (&period->end, start_time) > 0)
            return period;

        period_num++;
    }

    return NULL;
}

static void
e_meeting_time_selector_on_zoomed_out_toggled (GtkCheckMenuItem *menuitem,
                                               EMeetingTimeSelector *mts)
{
    gboolean active;

    /* Make sure the menu pops down, which doesn't happen by default if
     * keyboard accelerators are used. */
    if (gtk_widget_get_visible (mts->options_menu))
        gtk_menu_popdown (GTK_MENU (mts->options_menu));

    active = gtk_check_menu_item_get_active (menuitem);
    e_meeting_time_selector_set_zoomed_out (mts, active);
    e_meeting_time_selector_ensure_meeting_time_shown (mts);
}

static void
e_meeting_time_selector_on_working_hours_toggled (GtkCheckMenuItem *menuitem,
                                                  EMeetingTimeSelector *mts)
{
    gboolean active;

    /* Make sure the menu pops down, which doesn't happen by default if
     * keyboard accelerators are used. */
    if (gtk_widget_get_visible (mts->options_menu))
        gtk_menu_popdown (GTK_MENU (mts->options_menu));

    active = gtk_check_menu_item_get_active (menuitem);
    e_meeting_time_selector_set_working_hours_only (mts, active);
    e_meeting_time_selector_ensure_meeting_time_shown (mts);
}

/* This recalculates day_width, first_hour_shown and last_hour_shown. */
static void
e_meeting_time_selector_recalc_grid (EMeetingTimeSelector *mts)
{
    if (mts->working_hours_only) {
        mts->first_hour_shown = mts->day_start_hour;
        mts->last_hour_shown = mts->day_end_hour;
        if (mts->day_end_minute != 0)
            mts->last_hour_shown += 1;
    } else {
        mts->first_hour_shown = 0;
        mts->last_hour_shown = 24;
    }

    /* In the brief view we use the nearest hours divisible by 3. */
    if (mts->zoomed_out) {
        mts->first_hour_shown -= mts->first_hour_shown % 3;
        mts->last_hour_shown += 2;
        mts->last_hour_shown -= mts->last_hour_shown % 3;
    }

    mts->day_width = mts->col_width * (mts->last_hour_shown - mts->first_hour_shown);
    if (mts->zoomed_out)
        mts->day_width /= 3;

    /* Add one pixel for the extra vertical grid line. */
    mts->day_width++;

    gnome_canvas_set_scroll_region (GNOME_CANVAS (mts->display_top),
                    0, 0,
                    mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN,
                    mts->row_height * 3);
    e_meeting_time_selector_update_main_canvas_scroll_region (mts);

    e_meeting_time_selector_recalc_date_format (mts);
    mts->meeting_positions_valid = FALSE;
}

/* This saves the first visible time in the given EMeetingTimeSelectorTime. */
static void
e_meeting_time_selector_save_position (EMeetingTimeSelector *mts,
                                       EMeetingTime *mtstime)
{
    gint scroll_x, scroll_y;

    gnome_canvas_get_scroll_offsets (GNOME_CANVAS (mts->display_main),
                     &scroll_x, &scroll_y);
    e_meeting_time_selector_calculate_time (mts, scroll_x, mtstime);
}

/* This restores a saved position. */
static void
e_meeting_time_selector_restore_position (EMeetingTimeSelector *mts,
                                          EMeetingTime *mtstime)
{
    gint scroll_x, scroll_y, new_scroll_x;

    new_scroll_x = e_meeting_time_selector_calculate_time_position (mts,
                                    mtstime);
    gnome_canvas_get_scroll_offsets (GNOME_CANVAS (mts->display_main),
                     &scroll_x, &scroll_y);
    gnome_canvas_scroll_to (GNOME_CANVAS (mts->display_main),
                new_scroll_x, scroll_y);
}

/* This returns the x pixel coords of the meeting time in the entire scroll
 * region. It recalculates them if they have been marked as invalid.
 * If it returns FALSE then no meeting time is set or the meeting time is
 * not visible in the current scroll area. */
gboolean
e_meeting_time_selector_get_meeting_time_positions (EMeetingTimeSelector *mts,
                                                    gint *start_x,
                                                    gint *end_x)
{
    if (mts->meeting_positions_valid) {
        if (mts->meeting_positions_in_scroll_area) {
            *start_x = mts->meeting_start_x;
            *end_x = mts->meeting_end_x;
            return TRUE;
        } else {
            return FALSE;
        }
    }

    mts->meeting_positions_valid = TRUE;

    /* Check if the days aren't in our current range. */
    if (g_date_compare (&mts->meeting_start_time.date, &mts->last_date_shown) > 0
        || g_date_compare (&mts->meeting_end_time.date, &mts->first_date_shown) < 0) {
        mts->meeting_positions_in_scroll_area = FALSE;
        return FALSE;
    }

    mts->meeting_positions_in_scroll_area = TRUE;
    *start_x = mts->meeting_start_x = e_meeting_time_selector_calculate_time_position (mts, &mts->meeting_start_time);
    *end_x = mts->meeting_end_x = e_meeting_time_selector_calculate_time_position (mts, &mts->meeting_end_time);

    return TRUE;
}

/* This recalculates the date format to used, by computing the width of the
 * longest date strings in the widget's font and seeing if they fit. */
static void
e_meeting_time_selector_recalc_date_format (EMeetingTimeSelector *mts)
{
    /* An array of dates, one for each month in the year 2000. They must
     * all be Sundays. */
    static const gint days[12] = { 23, 20, 19, 23, 21, 18,
                      23, 20, 17, 22, 19, 24 };
    GDate date;
    gint max_date_width, longest_weekday_width, longest_month_width, width;
    gint day, longest_weekday, month, longest_month;
    gchar buffer[128], *str;
    const gchar *name;
    PangoContext *pango_context;
    PangoLayout *layout;
    struct tm tm_time;

    /* Set up Pango prerequisites */
    pango_context = gtk_widget_get_pango_context (GTK_WIDGET (mts));
    layout = pango_layout_new (pango_context);

    /* Calculate the maximum date width we can fit into the display. */
    max_date_width = mts->day_width - 2;

    /* Find the biggest full weekday name. We start on a particular
     * Monday and go through seven days. */
    g_date_clear (&date, 1);
    g_date_set_dmy (&date, 3, 1, 2000); /* Monday 3rd Jan 2000. */
    longest_weekday_width = 0;
    longest_weekday = G_DATE_MONDAY;
    for (day = G_DATE_MONDAY; day <= G_DATE_SUNDAY; day++) {
        name = e_get_weekday_name (day, FALSE);
        pango_layout_set_text (layout, name, -1);
        pango_layout_get_pixel_size (layout, &width, NULL);
        if (width > longest_weekday_width) {
            longest_weekday = day;
            longest_weekday_width = width;
        }
    }

    /* Now find the biggest month name. */
    longest_month_width = 0;
    longest_month = G_DATE_JANUARY;
    for (month = G_DATE_JANUARY; month <= G_DATE_DECEMBER; month++) {
        name = e_get_month_name (month, FALSE);
        pango_layout_set_text (layout, name, -1);
        pango_layout_get_pixel_size (layout, &width, NULL);
        if (width > longest_month_width) {
            longest_month = month;
            longest_month_width = width;
        }
    }

    /* Now try it with abbreviated weekday names. */
    longest_weekday_width = 0;
    longest_weekday = G_DATE_MONDAY;
    for (day = G_DATE_MONDAY; day <= G_DATE_SUNDAY; day++) {
        name = e_get_weekday_name (day, TRUE);
        pango_layout_set_text (layout, name, -1);
        pango_layout_get_pixel_size (layout, &width, NULL);
        if (width > longest_weekday_width) {
            longest_weekday = day;
            longest_weekday_width = width;
        }
    }

    g_date_set_dmy (&date, days[longest_month - 1] + longest_weekday,
            longest_month, 2000);

    g_date_to_struct_tm (&date, &tm_time);
    str = e_datetime_format_format_tm ("calendar", "table",  DTFormatKindDate, &tm_time);

    g_return_if_fail (str != NULL);

    if (!e_datetime_format_includes_day_name ("calendar", "table",  DTFormatKindDate)) {
        gchar *tmp;

        g_date_strftime (buffer, sizeof (buffer), "%a", &date);

        tmp = str;
        str = g_strconcat (buffer, " ", str, NULL);
        g_free (tmp);
    }

#if 0
    g_print ("longest_month: %i longest_weekday: %i date: %s\n",
         longest_month, longest_weekday, str);
#endif

    pango_layout_set_text (layout, str, -1);
    pango_layout_get_pixel_size (layout, &width, NULL);
    if (width < max_date_width)
        mts->date_format = E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY;
    else
        mts->date_format = E_MEETING_TIME_SELECTOR_DATE_SHORT;

    g_object_unref (layout);
    g_free (str);
}

/* Turn off the background of the canvas windows. This reduces flicker
 * considerably when scrolling. (Why isn't it in GnomeCanvas?). */
static void
e_meeting_time_selector_on_canvas_realized (GtkWidget *widget,
                                            EMeetingTimeSelector *mts)
{
    GdkWindow *window;

    window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
    gdk_window_set_background_pattern (window, NULL);
}

/* This is called when the meeting start time GnomeDateEdit is changed,
 * either via the "date_changed". "time_changed" or "activate" signals on one
 * of the GtkEntry widgets. So don't use the widget parameter since it may be
 * one of the child GtkEntry widgets. */
static void
e_meeting_time_selector_on_start_time_changed (GtkWidget *widget,
                                               EMeetingTimeSelector *mts)
{
    gint duration_days, duration_hours, duration_minutes;
    EMeetingTime mtstime;
    gint hour = 0, minute = 0;
    time_t newtime;

    /* Date */
    newtime = e_date_edit_get_time (E_DATE_EDIT (mts->start_date_edit));
    g_date_clear (&mtstime.date, 1);
    g_date_set_time_t (&mtstime.date, newtime);

    /* Time */
    e_date_edit_get_time_of_day (E_DATE_EDIT (mts->start_date_edit), &hour, &minute);
    mtstime.hour = hour;
    mtstime.minute = minute;

    /* If the time hasn't changed, just return. */
    if (e_meeting_time_compare_times (&mtstime, &mts->meeting_start_time) == 0)
        return;

    /* Calculate the current meeting duration. */
    e_meeting_time_selector_calculate_time_difference (&mts->meeting_start_time, &mts->meeting_end_time, &duration_days, &duration_hours, &duration_minutes);

    /* Set the new start time. */
    mts->meeting_start_time = mtstime;

    /* Update the end time so the meeting duration stays the same. */
    mts->meeting_end_time = mts->meeting_start_time;
    e_meeting_time_selector_adjust_time (&mts->meeting_end_time, duration_days, duration_hours, duration_minutes);
    e_meeting_time_selector_update_end_date_edit (mts);

    mts->meeting_positions_valid = FALSE;
    e_meeting_time_selector_ensure_meeting_time_shown (mts);
    gtk_widget_queue_draw (mts->display_top);
    gtk_widget_queue_draw (mts->display_main);

    g_signal_emit (mts, signals[CHANGED], 0);
}

/* This is called when the meeting end time GnomeDateEdit is changed,
 * either via the "date_changed", "time_changed" or "activate" signals on one
 * of the GtkEntry widgets. So don't use the widget parameter since it may be
 * one of the child GtkEntry widgets. */
static void
e_meeting_time_selector_on_end_time_changed (GtkWidget *widget,
                                             EMeetingTimeSelector *mts)
{
    EMeetingTime mtstime;
    gint hour = 0, minute = 0;
    time_t newtime;

    /* Date */
    newtime = e_date_edit_get_time (E_DATE_EDIT (mts->end_date_edit));
    g_date_clear (&mtstime.date, 1);
    g_date_set_time_t (&mtstime.date, newtime);
    if (mts->all_day)
        g_date_add_days (&mtstime.date, 1);

    /* Time */
    e_date_edit_get_time_of_day (E_DATE_EDIT (mts->end_date_edit), &hour, &minute);
    mtstime.hour = hour;
    mtstime.minute = minute;

    /* If the time hasn't changed, just return. */
    if (e_meeting_time_compare_times (&mtstime, &mts->meeting_end_time) == 0)
        return;

    /* Set the new end time. */
    mts->meeting_end_time = mtstime;

    /* If the start time is after the end time, set it to the same time. */
    if (e_meeting_time_compare_times (&mtstime, &mts->meeting_start_time) <= 0) {
        /* We set it first, before updating the widget, so the signal
         * handler will just return. */
        mts->meeting_start_time = mtstime;
        if (mts->all_day)
            g_date_subtract_days (&mts->meeting_start_time.date, 1);
        e_meeting_time_selector_update_start_date_edit (mts);
    }

    mts->meeting_positions_valid = FALSE;
    e_meeting_time_selector_ensure_meeting_time_shown (mts);
    gtk_widget_queue_draw (mts->display_top);
    gtk_widget_queue_draw (mts->display_main);

    g_signal_emit (mts, signals[CHANGED], 0);
}

/* This updates the ranges shown in the GnomeDateEdit popup menus, according
 * to working_hours_only etc. */
static void
e_meeting_time_selector_update_date_popup_menus (EMeetingTimeSelector *mts)
{
    EDateEdit *start_edit, *end_edit;
    gint low_hour, high_hour;

    start_edit = E_DATE_EDIT (mts->start_date_edit);
    end_edit = E_DATE_EDIT (mts->end_date_edit);

    if (mts->working_hours_only) {
        low_hour = mts->day_start_hour;
        high_hour = mts->day_end_hour;
    } else {
        low_hour = 0;
        high_hour = 23;
    }

    e_date_edit_set_time_popup_range (start_edit, low_hour, high_hour);
    e_date_edit_set_time_popup_range (end_edit, low_hour, high_hour);
}

static void
e_meeting_time_selector_on_canvas_size_allocate (GtkWidget *widget,
                                                 GtkAllocation *allocation,
                                                 EMeetingTimeSelector *mts)
{
    e_meeting_time_selector_update_main_canvas_scroll_region (mts);

    e_meeting_time_selector_ensure_meeting_time_shown (mts);
}

static gboolean
e_meeting_time_selector_on_canvas_scroll_event (GtkWidget *widget,
                                                GdkEventScroll *event,
                                                EMeetingTimeSelector *mts)
{
    gboolean return_val = FALSE;

    /* escalate to the list view's parent, which is a scrolled window */
    g_signal_emit_by_name (gtk_widget_get_parent (GTK_WIDGET (mts->list_view)), "scroll-event", event, &return_val);

    return return_val;
}

/* This updates the canvas scroll regions according to the number of attendees.
 * If the total height needed is less than the height of the canvas, we must
 * use the height of the canvas, or it causes problems. */
static void
e_meeting_time_selector_update_main_canvas_scroll_region (EMeetingTimeSelector *mts)
{
    GtkAllocation allocation;
    gint height;

    gtk_widget_get_allocation (mts->display_main, &allocation);
    height = mts->row_height * (e_meeting_store_count_actual_attendees (mts->model) + 2);
    height = MAX (height, allocation.height);

    gnome_canvas_set_scroll_region (GNOME_CANVAS (mts->display_main),
                    0, 0,
                    mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN,
                    height);
}

/* This changes the meeting time based on the given x coordinate and whether
 * we are dragging the start or end bar. It returns the new position, which
 * will be swapped if the start bar is dragged past the end bar or vice versa.
 * It make sure the meeting time is never dragged outside the visible canvas
 * area. */
void
e_meeting_time_selector_drag_meeting_time (EMeetingTimeSelector *mts,
                                           gint x)
{
    EMeetingTime first_time, last_time, drag_time, *time_to_set;
    gint scroll_x, scroll_y, canvas_width;
    gboolean set_both_times = FALSE;
    GtkAllocation allocation;

    /* Get the x coords of visible part of the canvas. */
    gnome_canvas_get_scroll_offsets (GNOME_CANVAS (mts->display_main),
                     &scroll_x, &scroll_y);
    gtk_widget_get_allocation (mts->display_main, &allocation);
    canvas_width = allocation.width;

    /* Save the x coordinate for the timeout handler. */
    mts->last_drag_x = (x < scroll_x) ? x - scroll_x
        : x - scroll_x - canvas_width + 1;

    /* Check if the mouse is off the edge of the canvas. */
    if (x < scroll_x || x > scroll_x + canvas_width) {
        /* If we haven't added a timeout function, add one. */
        if (mts->auto_scroll_timeout_id == 0) {
            mts->auto_scroll_timeout_id = g_timeout_add (60, e_meeting_time_selector_timeout_handler, mts);
            mts->scroll_count = 0;

            /* Call the handler to start scrolling now. */
            e_meeting_time_selector_timeout_handler (mts);
            return;
        }
    } else {
        e_meeting_time_selector_remove_timeout (mts);
    }

    /* Calculate the minimum & maximum times we can use, based on the
     * scroll offsets and whether zoomed_out is set. */
    e_meeting_time_selector_calculate_time (mts, scroll_x, &first_time);
    e_meeting_time_selector_calculate_time (mts, scroll_x + canvas_width - 1,
                        &last_time);
    if (!mts->all_day) {
        if (mts->zoomed_out) {
            if (first_time.minute > 30)
                first_time.hour++;
            first_time.minute = 0;
            last_time.minute = 0;
        } else {
            first_time.minute += 15;
            first_time.minute -= first_time.minute % 30;
            last_time.minute -= last_time.minute % 30;
        }
    } else {
        if (first_time.hour > 0 || first_time.minute > 0)
            g_date_add_days (&first_time.date, 1);
        first_time.hour = 0;
        first_time.minute = 0;
        last_time.hour = 0;
        last_time.minute = 0;
    }
    e_meeting_time_selector_fix_time_overflows (&first_time);
    e_meeting_time_selector_fix_time_overflows (&last_time);

    /* Calculate the time from x coordinate. */
    e_meeting_time_selector_calculate_time (mts, x, &drag_time);

    /* Calculate the nearest half-hour or hour, depending on whether
     * zoomed_out is set. */
    if (!mts->all_day) {
        if (mts->zoomed_out) {
            if (drag_time.minute > 30)
                drag_time.hour++;
            drag_time.minute = 0;
        } else {
            drag_time.minute += 15;
            drag_time.minute -= drag_time.minute % 30;
        }
    } else {
        if (drag_time.hour > 12)
            g_date_add_days (&drag_time.date, 1);
        drag_time.hour = 0;
        drag_time.minute = 0;
    }
    e_meeting_time_selector_fix_time_overflows (&drag_time);

    /* Now make sure we are between first_time & last_time. */
    if (e_meeting_time_compare_times (&drag_time, &first_time) < 0)
        drag_time = first_time;
    if (e_meeting_time_compare_times (&drag_time, &last_time) > 0)
        drag_time = last_time;

    /* Set the meeting start or end time to drag_time. */
    if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
        time_to_set = &mts->meeting_start_time;
    else
        time_to_set = &mts->meeting_end_time;

    /* If the time is unchanged, just return. */
    if (e_meeting_time_compare_times (time_to_set, &drag_time) == 0)
        return;

    /* Don't let an empty occur for all day events */
    if (mts->all_day
        && mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START
        && e_meeting_time_compare_times (&mts->meeting_end_time, &drag_time) == 0)
        return;
    else if (mts->all_day
         && mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END
         && e_meeting_time_compare_times (&mts->meeting_start_time, &drag_time) == 0)
        return;

    *time_to_set = drag_time;

    /* Check if the start time and end time need to be switched. */
    if (e_meeting_time_compare_times (&mts->meeting_start_time,
                      &mts->meeting_end_time) > 0) {
        drag_time = mts->meeting_start_time;
        mts->meeting_start_time = mts->meeting_end_time;
        mts->meeting_end_time = drag_time;

        if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
            mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_END;
        else
            mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_START;

        set_both_times = TRUE;
    }

    /* Mark the calculated positions as invalid. */
    mts->meeting_positions_valid = FALSE;

    /* Redraw the canvases. */
    gtk_widget_queue_draw (mts->display_top);
    gtk_widget_queue_draw (mts->display_main);

    /* Set the times in the GnomeDateEdit widgets. */
    if (set_both_times
        || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
        e_meeting_time_selector_update_start_date_edit (mts);

    if (set_both_times
        || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END)
        e_meeting_time_selector_update_end_date_edit (mts);

    if (set_both_times
        || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END
        || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
        g_signal_emit (mts, signals[CHANGED], 0);
}

/* This is the timeout function which handles auto-scrolling when the user is
 * dragging one of the meeting time vertical bars outside the left or right
 * edge of the canvas. */
static gboolean
e_meeting_time_selector_timeout_handler (gpointer data)
{
    EMeetingTimeSelector *mts;
    EMeetingTime drag_time, *time_to_set;
    gint scroll_x, max_scroll_x, scroll_y, canvas_width;
    gint scroll_speed, scroll_offset;
    gboolean set_both_times = FALSE;
    GtkAllocation allocation;

    mts = E_MEETING_TIME_SELECTOR (data);

    GDK_THREADS_ENTER ();

    /* Return if we don't need to scroll yet. */
    if (mts->scroll_count-- > 0) {
        GDK_THREADS_LEAVE ();
        return TRUE;
    }

    /* Get the x coords of visible part of the canvas. */
    gnome_canvas_get_scroll_offsets (GNOME_CANVAS (mts->display_main),
                     &scroll_x, &scroll_y);
    gtk_widget_get_allocation (mts->display_main, &allocation);
    canvas_width = allocation.width;

    /* Calculate the scroll delay, between 0 and MAX_SCROLL_SPEED. */
    scroll_speed = abs (mts->last_drag_x / E_MEETING_TIME_SELECTOR_SCROLL_INCREMENT_WIDTH);
    scroll_speed = MIN (scroll_speed,
                E_MEETING_TIME_SELECTOR_MAX_SCROLL_SPEED);

    /* Reset the scroll count. */
    mts->scroll_count = E_MEETING_TIME_SELECTOR_MAX_SCROLL_SPEED - scroll_speed;

    /* Calculate how much we need to scroll. */
    if (mts->last_drag_x >= 0)
        scroll_offset = mts->col_width;
    else
        scroll_offset = -mts->col_width;

    scroll_x += scroll_offset;
    max_scroll_x = (mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN)
        - canvas_width;
    scroll_x = CLAMP (scroll_x, 0, max_scroll_x);

    /* Calculate the minimum or maximum visible time in the canvas, which
     * we will now set the dragged time to. */
    if (scroll_offset > 0) {
        e_meeting_time_selector_calculate_time (mts,
                            scroll_x + canvas_width - 1,
                            &drag_time);
        if (!mts->all_day) {
            if (mts->zoomed_out) {
                drag_time.minute = 0;
            } else {
                drag_time.minute -= drag_time.minute % 30;
            }
        } else {
            drag_time.hour = 0;
            drag_time.minute = 0;
        }
    } else {
        e_meeting_time_selector_calculate_time (mts, scroll_x,
                            &drag_time);
        if (!mts->all_day) {
            if (mts->zoomed_out) {
                if (drag_time.minute > 30)
                    drag_time.hour++;
                drag_time.minute = 0;
            } else {
                drag_time.minute += 15;
                drag_time.minute -= drag_time.minute % 30;
            }
        } else {
            if (drag_time.hour > 0 || drag_time.minute > 0)
                g_date_add_days (&drag_time.date, 1);
            drag_time.hour = 0;
            drag_time.minute = 0;
        }
    }
    e_meeting_time_selector_fix_time_overflows (&drag_time);

    /* Set the meeting start or end time to drag_time. */
    if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
        time_to_set = &mts->meeting_start_time;
    else
        time_to_set = &mts->meeting_end_time;

    /* If the time is unchanged, just return. */
    if (e_meeting_time_compare_times (time_to_set, &drag_time) == 0) {
        GDK_THREADS_LEAVE ();
        goto scroll;
    }

    /* Don't let an empty occur for all day events */
    if (mts->all_day
        && mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START
        && e_meeting_time_compare_times (&mts->meeting_end_time, &drag_time) == 0)
        goto scroll;
    else if (mts->all_day
         && mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END
         && e_meeting_time_compare_times (&mts->meeting_start_time, &drag_time) == 0)
        goto scroll;

    *time_to_set = drag_time;

    /* Check if the start time and end time need to be switched. */
    if (e_meeting_time_compare_times (&mts->meeting_start_time, &mts->meeting_end_time) > 0) {
        drag_time = mts->meeting_start_time;
        mts->meeting_start_time = mts->meeting_end_time;
        mts->meeting_end_time = drag_time;

        if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
            mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_END;
        else
            mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_START;

        set_both_times = TRUE;
    }

    /* Mark the calculated positions as invalid. */
    mts->meeting_positions_valid = FALSE;

    /* Set the times in the GnomeDateEdit widgets. */
    if (set_both_times
        || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
        e_meeting_time_selector_update_start_date_edit (mts);

    if (set_both_times
        || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END)
        e_meeting_time_selector_update_end_date_edit (mts);

    if (set_both_times
        || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END
        || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START)
        g_signal_emit (mts, signals[CHANGED], 0);

 scroll:
    /* Redraw the canvases. We freeze and thaw the layouts so that they
     * get redrawn completely. Otherwise the pixels get scrolled left or
     * right which is not good for us (since our vertical bars have been
     * moved) and causes flicker. */
    gnome_canvas_scroll_to (GNOME_CANVAS (mts->display_main),
                scroll_x, scroll_y);
    gnome_canvas_scroll_to (GNOME_CANVAS (mts->display_top),
                scroll_x, scroll_y);

    GDK_THREADS_LEAVE ();
    return TRUE;
}

/* This removes our auto-scroll timeout function, if we have one installed. */
void
e_meeting_time_selector_remove_timeout (EMeetingTimeSelector *mts)
{
    if (mts->auto_scroll_timeout_id) {
        g_source_remove (mts->auto_scroll_timeout_id);
        mts->auto_scroll_timeout_id = 0;
    }
}

/* This updates the GnomeDateEdit widget displaying the meeting start time. */
static void
e_meeting_time_selector_update_start_date_edit (EMeetingTimeSelector *mts)
{
    e_date_edit_set_date_and_time_of_day (E_DATE_EDIT (mts->start_date_edit),
                          g_date_get_year (&mts->meeting_start_time.date),
                          g_date_get_month (&mts->meeting_start_time.date),
                          g_date_get_day (&mts->meeting_start_time.date),
                          mts->meeting_start_time.hour,
                          mts->meeting_start_time.minute);
}

/* This updates the GnomeDateEdit widget displaying the meeting end time. */
static void
e_meeting_time_selector_update_end_date_edit (EMeetingTimeSelector *mts)
{
    GDate date;

    date = mts->meeting_end_time.date;
    if (mts->all_day)
        g_date_subtract_days (&date, 1);

    e_date_edit_set_date_and_time_of_day (E_DATE_EDIT (mts->end_date_edit),
                          g_date_get_year (&date),
                          g_date_get_month (&date),
                          g_date_get_day (&date),
                          mts->meeting_end_time.hour,
                          mts->meeting_end_time.minute);
}

/* This ensures that the meeting time is shown on screen, by scrolling the
 * canvas and possibly by changing the range of dates shown in the canvas. */
static void
e_meeting_time_selector_ensure_meeting_time_shown (EMeetingTimeSelector *mts)
{
    gint start_x, end_x, scroll_x, scroll_y;
    gint new_scroll_x;
    GtkAllocation allocation;
    EMeetingTime time;

    /* Check if we need to change the range of dates shown. */
    if (g_date_compare (&mts->meeting_start_time.date,
                &mts->first_date_shown) < 0
        || g_date_compare (&mts->meeting_end_time.date,
                   &mts->last_date_shown) > 0) {
        e_meeting_time_selector_update_dates_shown (mts);
        gtk_widget_queue_draw (mts->display_top);
        gtk_widget_queue_draw (mts->display_main);
    }

    /* If all of the meeting time is visible, just return. */
    if (e_meeting_time_selector_get_meeting_time_positions (mts, &start_x,
                                &end_x)) {
        time.date = mts->meeting_start_time.date;
        time.hour = 0;
        time.minute = 0;
        start_x = e_meeting_time_selector_calculate_time_position (mts, &time);
    }

    gnome_canvas_get_scroll_offsets (GNOME_CANVAS (mts->display_main),
                     &scroll_x, &scroll_y);
    gtk_widget_get_allocation (mts->display_main, &allocation);
    if (start_x > scroll_x && end_x <= scroll_x + allocation.width)
        return;

    new_scroll_x = start_x;
    gnome_canvas_scroll_to (GNOME_CANVAS (mts->display_main),
                new_scroll_x, scroll_y);
}

/* This updates the range of dates shown in the canvas, to make sure that the
 * currently selected meeting time is in the range. */
static void
e_meeting_time_selector_update_dates_shown (EMeetingTimeSelector *mts)
{
    mts->first_date_shown = mts->meeting_start_time.date;
    g_date_subtract_days (&mts->first_date_shown,
                  E_MEETING_TIME_SELECTOR_DAYS_START_BEFORE);

    mts->last_date_shown = mts->first_date_shown;
    g_date_add_days (&mts->last_date_shown,
             E_MEETING_TIME_SELECTOR_DAYS_SHOWN - 1);
}

/* This checks if the time's hour is over 24 or its minute is over 60 and if
 * so it updates the day/hour appropriately. Note that hours and minutes are
 * stored in guint8's so they can't overflow by much. */
void
e_meeting_time_selector_fix_time_overflows (EMeetingTime *mtstime)
{
    gint hours_to_add, days_to_add;

    hours_to_add = mtstime->minute / 60;
    if (hours_to_add > 0) {
        mtstime->minute -= hours_to_add * 60;
        mtstime->hour += hours_to_add;
    }

    days_to_add = mtstime->hour / 24;
    if (days_to_add > 0) {
        mtstime->hour -= days_to_add * 24;
        g_date_add_days (&mtstime->date, days_to_add);
    }
}

/*
 * CONVERSION ROUTINES - functions to convert between different coordinate
 *           spaces and dates.
 */

/* This takes an x pixel coordinate within the entire canvas scroll region and
 * returns the date in which it falls. If day_position is not NULL it also
 * returns the x coordinate within the date, relative to the visible part of
 * the canvas. It is used when painting the days in the item_draw function.
 * Note that it must handle negative x coordinates in case we are dragging off
 * the edge of the canvas. */
void
e_meeting_time_selector_calculate_day_and_position (EMeetingTimeSelector *mts,
                                                    gint x,
                                                    GDate *date,
                                                    gint *day_position)
{
    gint days_from_first_shown;

    *date = mts->first_date_shown;

    if (x >= 0) {
        days_from_first_shown = x / mts->day_width;
        g_date_add_days (date, days_from_first_shown);
        if (day_position)
            *day_position = - x % mts->day_width;
    } else {
        days_from_first_shown = -x / mts->day_width + 1;
        g_date_subtract_days (date, days_from_first_shown);
        if (day_position)
            *day_position = -mts->day_width - x % mts->day_width;
    }
}

/* This takes an x pixel coordinate within a day, and converts it to hours
 * and minutes, depending on working_hours_only and zoomed_out. */
void
e_meeting_time_selector_convert_day_position_to_hours_and_mins (EMeetingTimeSelector *mts,
                                                                gint day_position,
                                                                guint8 *hours,
                                                                guint8 *minutes)
{
    if (mts->zoomed_out)
        day_position *= 3;

    /* Calculate the hours & minutes from the first displayed. */
    *hours = day_position / mts->col_width;
    *minutes = (day_position % mts->col_width) * 60 / mts->col_width;

    /* Now add on the first hour shown. */
    *hours += mts->first_hour_shown;
}

/* This takes an x pixel coordinate within the entire canvas scroll region and
 * returns the time in which it falls. Note that it won't be extremely
 * accurate since hours may only be a few pixels wide in the display.
 * With zoomed_out set each pixel may represent 5 minutes or more, depending
 * on how small the font is. */
void
e_meeting_time_selector_calculate_time (EMeetingTimeSelector *mts,
                                        gint x,
                                        EMeetingTime *time)
{
    gint day_position;

    /* First get the day and the x position within the day. */
    e_meeting_time_selector_calculate_day_and_position (mts, x, &time->date,
                                NULL);

    /* Now convert the day_position into an hour and minute. */
    if (x >= 0)
        day_position = x % mts->day_width;
    else
        day_position = mts->day_width + x % mts->day_width;

    e_meeting_time_selector_convert_day_position_to_hours_and_mins (mts, day_position, &time->hour, &time->minute);
}

/* This takes a EMeetingTime and calculates the x pixel coordinate
 * within the entire canvas scroll region. It is used to draw the selected
 * meeting time and all the busy periods. */
gint
e_meeting_time_selector_calculate_time_position (EMeetingTimeSelector *mts,
                                                 EMeetingTime *mtstime)
{
    gint x, date_offset, day_offset;

    /* Calculate the number of days since the first date shown in the
     * entire canvas scroll region. */
    date_offset = g_date_get_julian (&mtstime->date) - g_date_get_julian (&mts->first_date_shown);

    /* Calculate the x pixel coordinate of the start of the day. */
    x = date_offset * mts->day_width;

    /* Add on the hours and minutes, depending on whether zoomed_out and
     * working_hours_only are set. */
    day_offset = (mtstime->hour - mts->first_hour_shown) * 60
        + mtstime->minute;
    /* The day width includes an extra vertical grid line so subtract 1. */
    day_offset *= (mts->day_width - 1);
    day_offset /= (mts->last_hour_shown - mts->first_hour_shown) * 60;

    /* Clamp the day_offset in case the time isn't actually visible. */
    x += CLAMP (day_offset, 0, mts->day_width);

    return x;
}

static void
row_inserted_cb (GtkTreeModel *model,
                 GtkTreePath *path,
                 GtkTreeIter *iter,
                 gpointer data)
{
    EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data);
    gint row = gtk_tree_path_get_indices (path)[0];
    /* Update the scroll region. */
    e_meeting_time_selector_update_main_canvas_scroll_region (mts);

    /* Redraw */
    gtk_widget_queue_draw (mts->display_top);
    gtk_widget_queue_draw (mts->display_main);

    /* Get the latest free/busy info */
    e_meeting_time_selector_refresh_free_busy (mts, row, FALSE);
}

static void
row_changed_cb (GtkTreeModel *model,
                GtkTreePath *path,
                GtkTreeIter *iter,
                gpointer data)
{
    EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data);
    gint row = gtk_tree_path_get_indices (path)[0];

    /* Get the latest free/busy info */
    e_meeting_time_selector_refresh_free_busy (mts, row, FALSE);
}

static void
row_deleted_cb (GtkTreeModel *model,
                GtkTreePath *path,
                gpointer data)
{
    EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data);

    /* Update the scroll region. */
    e_meeting_time_selector_update_main_canvas_scroll_region (mts);

    /* Redraw */
    gtk_widget_queue_draw (mts->display_top);
    gtk_widget_queue_draw (mts->display_main);
}

#define REFRESH_PAUSE 5

static gboolean
free_busy_timeout_refresh (gpointer data)
{
    EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data);

    /* Update all free/busy info, so we use the new template uri */
    e_meeting_time_selector_refresh_free_busy (mts, 0, TRUE);

    mts->fb_refresh_not = 0;

    return FALSE;
}

static void
free_busy_template_changed_cb (EMeetingTimeSelector *mts)
{
    /* Wait REFRESH_PAUSE before refreshing, using the latest uri value */
    if (mts->fb_refresh_not != 0)
        g_source_remove (mts->fb_refresh_not);

    mts->fb_refresh_not = g_timeout_add_seconds (
        REFRESH_PAUSE, free_busy_timeout_refresh, mts);
}