aboutsummaryrefslogblamecommitdiffstats
path: root/calendar/gui/e-week-view.c
blob: b2bc509dfa27c4d0cd30660a269555ac38be6cbf (plain) (tree)
1
2
3
4
5
6
7
8
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
  
                                                                


                                                               


                                                                  



                                                                    
                                                                             






                                                        
  





                                                               
                    
                   
      


                        
                 
                           
                       
                                          
 
                                
                                 
                              
                                
                               
                                
 
                            
                            
                      
                                 
                                   
                               

                                    

                        
                 
                  

            
                       
 



                                                    
                                       
 





                                          

                                           
                                                                            
                             

                                           
                          



















                                                                          




                                                                      

  




                                      
                                                                             
                                                                 





                                                                           
                                                            

                                                               
                                                                    

                                                                   
                                                                      
                                                                     


                                                              








                                                                      
                                                           
                                                           

                                                        
                                                        
                                                        
                                                            
                                                                    



                                                                 

                                                                



                                                                   
                                                                             
                                                                                
                                                                       
                                                                       
                                                                                     


                                                                         



                                                                                       


                                                                      
                                                                                                  
                                                                                          
                                                                                                                      


                                                                        

                                                              




                                                                                               
 


                                                                  
                                                                                    
 



                                                              
                                                            
 


                              

                                  

  



                                                









                                                                 
                                                              

























                                                                                                                 

                                           



































                                                                                                               


                                                     
                       
         










                                                       


                                                     
                       
         




























                                                                                                                       


                                                     
                       
         











                                                       


                                                     
                       
         
























                                                                          


                                            
                      





                                                                                                                

                                                                          

                                                                    

                                                        

                                                                   
                                


                                                                     
                                              




                                                      
                                                              




                                                           


                                                                                
                                                     
                                                         



                                                                                    



                                                                           

                                                                          







                                                              


                                                     
                                                  




                                                       

                                                               

                                                   
                                                       

                                                                             


           
                                          



                                            
                                                              
                               





                                                       

                                                            


                                                    
                       
         

                                                                 
                                            


                                                                           
                                                       


                                                      







                                                           



                                                         
                                                            











                                                                             








                                                      


                                                                                             












































                                                                               
                                   








                                    
                                 
 
                                                                        


                                                        
                                                     
                          



                                                                                                                              
                          





































                                                                                                                 
                          


                                                                     
                          




                                                                                   
                          







                                                                            
                          

         

                                                                 
 






                                 


           
















                                                              
























                                                                       



                                                                                                   





                                                                       







































                                                                         


                                       
                                     
 


                                                                        

                                                          
 

                                                                    

                                                




















                                                                         


           
                                     
 
                             
 

                                                                                
 
                                         
 













                                                                                


           
                                       
 
                             
 
                                         
 









                                                    
 

                                                                                  


           

                                              
 










                                                                       
 

                                                                                                  
 
















































































































































































































































































































































                                                                                                                                                    


                                                                                                  
                                    
                 


























































                                                                                              
                       

















                                                                                                                               
                                                                     






























                                                                           
                                                              

                             

                                                               



















































                                                                                                           
 




                                                                      
           
                                                
 

                                                 
 
                                         
 


                                                                            
         



























                                                                                   


           

                                              
                                   
                                     
                                       
 

                                                                    


                                                            
                                                  
                                                          

                                                








                                                              

                                                   




                                                                                
 




                                                             









                                                          

                                                 








                                               

                                                 
 




                                          

                                                            

 
           


                                       
                                  


                          
                                                              



                                                     
                                                           
 
                                                                
 
                                           


                                         







                                               

                                                

                               

                                                
                                                            











                                                              




                                                                                                       


                                                             















                                                                              


                                                                   



                                                                                          




                                                                    




                                                 



                                                                    




                                                                                        




                                                                  
 











                                                                       










                                                                          


                                                                                  







                                                                          
                                                                               
 





                                                             
 






                                                                                

 







                                  
 

                                                            
 
                                                                     
 


                                                        
 
                    

 
               

                       


















                                                 
           

                                             
 




                                                                                              











                                                                                                                                             

 



                                                  
 



                                 
 


                                                                            
 





                                                                                                                                                                           
 
                                              
 



                                                                        

 


                                                    
                                                   
                      
                                 
                          
                        
                                       


                                        
 




                                                                             




                                       

                                                                        
                                                                          



                                                                            
                                            
                                           


                                                                   
                                       








                                                                            
                                              
                                         


                                                                   
                                        






                                                                             
 

                                                          


                                              

                                     
                       
 
                                                              


                                                            
 
                                                                             
                                                     

                                                                   

                                                                                      


                                                                   

                                                                                      




                                                         





                                                                      

                                             




                                                                      

                                                                               
                                                                        

                                         

                                                                   
                                                       
                                                                      

                                                                                



                                                                                  

                                                                                



                                                                        

                                                

 








                                                                     

                                                     




                                                           























                                                             




                                                                       









                                                                      

 

                                        
                                               
 
                     
 


                                                     
                       
         
 
                                                       

                                             
 
                                                                                                                 


                                              
                                                                                                                      



                                                          
                                                                   
         

 
    


                                                                  
 
                               




                                                      

                                                            
                                                                                                                
 
                                                  


                                                    

                                                                 
                                                                                                               

                                                                              
                                                                                                                               


                                                            


                                               
                                                                     
                                                
                                                             



                                               



                                                       

                                                                             

                                                      
 
                                                 

 
                                                                            
                   
    

                                                      

                        



                                       
                                                 
                                                                             
                                                             






                                                      
                                                                 

                                                         
                                                                 


                                                       

                                                                          

                                                                    
                                                                             

                                                                     
                                              










                                                                    


                                                                                
 


                                                               
 


                                                                                    
 
                                                                      
                                                     


                                                                           
                                        

                                                                           
                                                         
                                                                       
                                                         

                                                       
                                                                             





                                                                     


                                                                           

                                                                          







                                                              
 
                                             


                                                       







                                                                              


                                                                     
                                            



                           
                                                               



                                               


                                                                                    



                                                      
        
                                                      


                                                                 
                                                

 
    

                                                          
 
                        




                                                      
                                                                

                       
                                                           
 
                              
                                                           

                                                                                              

                                                                       







                                                      


                                                           

                                                         

                                                                            

                                                               

         



                                                                       
 
                                                         
                                                  
 
                                                             

                                                 
                                                           

 




                                                                 
                                                 

 
    

                                                            


                                                      
                                                             
 

    
                                                  


                                                             




                                                          

 
    

                                                  
 
                        






                                                               
                                                        

                       
                                                   
 
                                                          






                                                      
 



                                                                               
 
                                                          
 



                                                                     
 
                                                     
         

 
        
                                                       


                                                                 
                                                 

 
    

                                                            
 

                                     

                                                      
                                                                  

                       
                                                             

                                                     
                                                         

                       
                                                  



                                                                          

                                                                            

                                                                         



                                                                     



                                                      
 
                                                         
                                                       

                                                                   
 


                                         
                                                           


                                                                
                                                     

 
    

                                                                    


                                                      
                                                                          

                       
                                                                     



                                                  


                                                         
                                                                       

 
               
                                                           
 
                         


                                       
 


                                                                        
                                                                        

                                                                          
                                           
 
                                                 
                                                                 
                                                            

                                                                 
                                                            
         
 
                                                                            
 


                                                               

 
                                                                                                 
           
                                        

                                            
 
                            

                                     

                                         


                                        

                                                                                                       

                                                          
                                                             


                                                          
                                                                                
                                                                                                        
                                      
                              
                 

         
                                                                                        

                                                                                       
                                                            
                                                                            



                                                       
 

                                                                             

                                                                           

                                                         


                                                                           


                              
 


                                                    
                               
 

                                                                          
 


                                                
                                                                       
                                            





                                                                      

                                                  

                                            




                                 


                                                                     
                                                                              

                            
 
                                                                        
                                                                 
                                                        
                                                  

                                                                     
 


                                                    

                                                  

                                

                                                                                   
                                       
                                                                             


                                                                                                        



                                                                                    
                                                                                  


                                                       
                                                                                        

                                                             
                 






                                                                                                                 
                                                       
 





                                                                                       
                         

                 
 

                                                            


                                             

 
    





                                                   
 
                                    
 

                                             

                                                            
                                                              
                                                             
                                           
 

                                                
 

                                                

                            
                                                             

                         
         

 
                                                                           






                                                                              
        





                                                    


                                 
                      









                                                                              


                                                                                        


                                                                    



                                                    
                                                              


                                                             
                             

         


                                                        




                                                              
                                      
                



                                                        
                                  
                                                      




                    
               

                                                 







                                                                                      


                                                                                                
                                                                                                         



                                                                                                                   











                                                                                                                               
                                               
                                                    
                                                  
 


                                

                       


                                                                        
                                                         

                               



                                                                    
                                                                 

                            



                                                 
                                                                           



                                                                                                         



                                                                 
                 
                                                                                                                                        


                            

                                          
                                  

                                        
 
                                               
                                                                                                                                   

                                                                       

                                                                         



















                                                                         


                                                                             
                                                                                   



                                                                       
                                       
                                                                   
                                                                       
 



                                                                                                 
 


                                                                       
 
                                                                          

         
                    

 

                                                 
                                                      
                                                    
 





                                                           

                                                                      
                                                             
                
                                                                     




                     

                                         

                                              
 







                                  
                                                                                            
                      
 
                                                                                                 



                                                                                  
 

                                                 
                                                                                 
         
 








                                                                        
                                    





                                                           

                                                                                  
                                                                                     


                                      

                                     
         
 

                                                                
 


                    

                                         

                                              


                       











                                                                     
                                                                    
 


                     
                                                                          
                                                                    

                                                          

                                            
 
                                       
                                                           
                         
 

                                                                          



















                                                              
                                                          
                                  

                             
                                                                   

                                                                    



                                                                          

                         
 
                                               
                                                            

                                                             
                                                                              
                                                                             
                                                       






                                                                                        




                              

                                                   
                                       



                                     




























                                                                               


                                              
                              
                                 
                                                
                                                                  
 





                                          
 


                                                                              


                                                          
         
 








                                                                             
                                                                                        
                                            
                                                                                  



                                                      

                                                    
                                                               


                                                 




                                                                      


                                                                     

 
                                                                          
               
                                           



                                        

 
                                     

                             
                                             
 
                              

                                                   
                                                                               
 
                                                  

                                                                                             
 




                                                                                            
                                                                                                                            
 
                                        
                                                                           
                
                                                                                  
 
                                                                                                                                                   
                                                      
                                                                                                               
         

                            


                             

                              

                                                
 

                                                                  


                                                  
                                         


                                                    
                                                                                                

                                                



                                                                               

                                                             
 


                    




                                                               




                                                     




                                                                  





                                                                     
                                                                      

                                                 







                                                                            
           


                                                       




                                                     



                                                
    
                                                
                                                


                                        

                                         













                                          



                                                 
                                       
                                 

                                                                 



                                                                              


                                                
                                                                             

                                                   

                                                                

                                                                       
                                                                                                          

                                                                                                        





                                                                                                                  

                                                                                                                            
                                                                            

                                                                          
                                                     

                 

                                                                         
                                                               
                                                                          
                                              








                                                                      
 


                                                                      



                                                                       



                                                                              

                                              



                                                      
 



                                                                                                                
                                                                                             








                                                                                      

 
                       


                                             


                               


                                                                     
                                                                               
 



                      

                                       
 
                                                                                            
                               
                      
 
                                                                                                 



                                                                                  
 

                                                                   
                                                                                                    


                                                             
 
                                                                                 

         
 

                                        

                                  
 
                                                                                            
                               
 
                                                              
 

                                      
                                                    
                                                     
 
                                                                          
 

                                                                         
                                               
 
                                                                

                                                    
                                                                                                                             

                                                              
                                                                           
                                                               
                                                                                                                    
 


                                     
                 
                                       

                                                                       
                                                                                                              
 
                                              
                                                                                                           


                                    



                                                     


                                     

 
                    


                                          






                                                                           
                                                                                                 















                                                                            

                                                     

                                               
 


                                  

                                 



                                                                        
                            


                                               



                                        
 




                                                     


                                                                     
                                                                              






                                                                                        

                                                                    

                                                                                                       



                                                                            
                     


                                                                           
                                                                                
                                    
                                                                          

                                             

                                      


                       


                                                                              


                                                            

                                                  
                                                                               
                                                    





                                                                       
                                                       

                         
                                                      
                                    
                                                                                                 
                                    

                                                           
                                                         
                                    
                                              
                                    
                                                                    




                                                             



                                                                                                 
         
 
                                                                                                        


                                                          
 




                                       


                                                
                                     
                                  
                               
                                           
 
                                                 

                                                                              
                                                                                                              
 
                                 










                                                                                                 
 
                              
                                                   
 
                                                                                                                                 
                                                                                                    
                                                                 
                 
                                                                                                         


                                                                                


                                              
 


                                                   




                                                                               

          
                                                                            
                                                                   


                                                                       
                                                                              



                                                                          



                                                                             

                            
                                                                          


                                                                           
                                                          



                                                     
                                                                               
                                                                        


                                                                


                                                      
                                                                        
                                                    
                                                                            



                                           




                                                                               
                                                    
                                                                            


                                    
                                                                       
                                              

                                                                 
                                                               

                                                                          
                                                                        
                        
                                    
                                                                            


                                                                            

                                       
                                                                              
                                                                         




                                                                      


                                                                                        



                                                                             
                                                                       



                                                                               
                                                                   

                                                                     

                                                                             
                                                           

                                                                                  
                                                                       
 
                                                                               
                                       


                                                                           

                                                                        

                                                                              
                                                                        


                                                                                
                                                                       








                                                                   
                                                             
                                 
 




                                                
                                                                      
 
                              
                                
                                                

 
        
                                                      


                                                     




                                                    
                                      
 


                                                               
                            
 


                                                                     
                                                                              






                                                                                        


                                                                    
                                                                       

                             

                                                              
                             
 
                                                





                                                                                                           





                                                                                                        



                                                                                                             
 

                                                                           
 
                                                         
 



                                                                                      
 
                                                                                   


                                                                                                   


                                                                
                                                                



                                                                                              

                                                             



                                                                                        
                                                                                                     

                                                            
                                                                                  


                                                       


                                             
         
                    

 
                                  














                                                                    



                                                                                           
                                 

                                 
                             



                                                 
                                           
 


                                                                     
                                                                              






                                                                                        



                                                                                                    
                                                                         
                                                                             




                                                   

                                                      
                                                    
                                                     
 
                              
                                 
                                                                                         
                               



                                 
 
                                                                
 
                                  
                           
                                                  


                                                                                    
                                                                            
                                                                     


                                                                        
                                                                     
                                                                       
                                    
                                                            
                                                   
                                                                       

                                                                            
                                    

                      




                                                                              


                                                                             


                                                                          


                                                
                                                                                                                     
                                                                                                             


                                                                                                      



                                                                           
 
                                                               
                            
                              
                                                  



                                                                              

                                                                
                                          
 
                                                     
                                                                           


                                                                               
 


                                                                                     

                                                                                          
                                                                           
                                                                               
 
                                                                                                  
 
                                                     
                                                                 
 

                                                                    


                                    
                                        


                                                                 
 
                                                                        
                                              


                                                
                                                                       
 




                                                                       

                                                                           
                                                 

                                    



                                              
                                                         








                                                                          




                                                                 
                                                                  
                         

                                                                        
                                                                     
                                                                       
                                    

                                                  
                      


                                             
                           
 

                                                                                            

                                     
                                                                                            
 

                                                                        
                                                                  
 




                                                                 

                                       
                                                             
                               
                                         
                                                                                                                     

                                                      
                                                                   
                                                       
                                                                                                                 
 
                            
         

                                                  
 

                               




                                                                 
                                                                                                           
 
                                      
                                                                                                   
                 
                            
                              
                                                 
                                                                         
                        
                                                                         
                 








                             


                                                        

                              
                                                                        


                                                      
                                    

                                                 



                                                      
                             
 


                                                                     
                                                                              
 


                                        
                            





                                                                            
                                                                                 












                                                                                                     
                      


                      
 

                                                         

                                                
                                                                            

                                              
                                                                            


                                                                                     

                                  
 



                                                                           
                            
 
                                                                                
 
                    


           

                                                          
 
                                                    
 



                                                                 

 
    

                                                           
 
















                                                                        
 
                            













                                                           
         


                                                                


           



                                                    





                                   
                           








                                                      


                                                                     
                                                                              



                                        


                                                                            

                                                                           



                                                                                                       
                            
                                                                                                       
 
                                                                          
                                                                                                                 
                                                                
                                                                        
                                                                                                                 
                                                              

                                               
 



                                                            
 

                                                                                  


                                                                                
                                 
                 
 


                                                                            





                                                                     

                                                      
 


                                                                                 



                                                   
 
    

                              


                                                     
                                                      










                                                                          
                      
                                                                   

                                                        
         
 
                                                               

                                                             

 

                                                     
                                                      




                                 

                                  
                           
                         
                           
 
                                                                            

                                                                           






                                                      


                                                                     
                                                                              






                                                                                        




                                                                    

                                                  
                                                                 

                                                                     
                       
         
 
                    
                                                                    
                                                            
 

                                                                                                       
 

                                                         
 
                                                   
                                                     
                                                                                                       

                                                                                       
                                                  


                                                               

         
                                                   
                                                     
                                                             



                                                                                                              
                                                                                     



                                                   
                                                                         
                                                                                        
                                                              
                                                                                   
 

                                      
                                                             
                                                       
 
                                 


                                             






                                                                                     




                                                                              
                                                                   
                                                                             





                                                     


                                                                                                           


                                                           
 
                                                                     
                                                                                                
                                                 
                                 
 

                                                                                            


                                                                 
                                                               
                                                    


                                                                                
                                                                                    



                                                                                         
                                                                                    



                                                                                                                    



                                                                            
                                                                                
                                                      


                                                                              
                                                                                    



                                                                                       
                                                                                    



                                                                                                                    



                                                                            
                                                                              
                                                      







                                                                                     

                                                                      
 

                                                                                                 



                                                                    
                 

         

     
                      
                              
 
                                                               

                                                             

 
        



                                                         









                                                                             


                                                                                                        













                                                                              
                                                   




                                                                            
               




                                                        



                                   



                               

                                                                  

                                
 

                                                                          
 


                                                


                                                       
                                                                       
                                            
                                          
                                                                                                                            

                                                 

                                                           
                                                 

                                           

                         







                                                      
        

                                                   



                                 


                                                                     



                                                                              


                                                                             














                                                                        
           
                                                
 
                                        
 

                                                                  
 
                                                   


           
                                                  
 
                                        
 

                                                                    
 
                                                     


           
                                                  
 
                                        
 

                                                                    
 
                                                     


           
                                                   
 
                                        
 

                                                                     
 
                                                      

 
               

                                             



                             
                               
                         
 




                                                              
                               


                                                       
                                                                     
                                                      





                                                                          
                                                             




                                                     
                                     
                                                                         
                                                                                          
                            

                                                                                               
                                       
                                                                         
                                                                                            
                            
                                                                                                 
                              
                                
                                                              
                              
                                  
                                                                
                              
                                  
                                                                
                              
                                   
                                                                 







                                              
 
                                                   


                                                                         
                                                                    
                                                                                                        
                                                                             
                                                                                                          
                                                                             
                                                                                                          
                                                                               
                                                                                                           
         
 



                                                                      
                        
                                              
                                    

                                                                           
                                          
                                                       
                             

                                                                                                
 
                                                                                        
 


                                      
                       

 
           

                                                              
 
                                                                    

 
           

                                                                
 
                                                                   

 
           

                                                                
 

                  
 
           

                                                                 

                 

 
    
                                                  
                                                    
                                            
 
                                               
 
                                                                                

 
    

                                                       





                                                               
                                                                                              









                                                                     


                                                        

                                                       



                                              




                                                                  
                                                         




                                                                               
                                                            






                                                                          
                                                                     

                                         
                                                                       

                                                                 

                 

                                                                              

                                                             
                                                                                                  



                                                             


                                                                                          
                                                                                          



                                                             

                           
                                                



                     
 
                                                                           
                                                           
    




                                                          
 



                                                                        
                                                                   
                                                        
                             
                                                         

















                                                                   
    
                                                        
 
                         

                        

                                                                        
                                                                    





                                                           
                                                        




                                                               
 








                                                                                                                                    




                                                
                                                               



                                                 










                                                       
 







                                                                
 
        

                                                         

                                                                 
 
                                                            
                                                                                       

                     
 






                                                                 
/*
 * 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@ximian.com>
 *      Rodrigo Moya <rodrigo@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

/*
 * EWeekView - displays the Week & Month views of the calendar.
 */

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

#include "e-week-view.h"

#include <math.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>
#include <libgnomecanvas/libgnomecanvas.h>

#include "dialogs/delete-comp.h"
#include "dialogs/delete-error.h"
#include "dialogs/send-comp.h"
#include "dialogs/cancel-comp.h"
#include "dialogs/recur-comp.h"
#include "dialogs/goto-dialog.h"

#include "calendar-config.h"
#include "calendar-config.h"
#include "comp-util.h"
#include "e-cal-model-calendar.h"
#include "e-week-view-event-item.h"
#include "e-week-view-layout.h"
#include "e-week-view-main-item.h"
#include "e-week-view-titles-item.h"
#include "ea-calendar.h"
#include "itip-utils.h"
#include "misc.h"
#include "print.h"

/* Images */
#include "art/jump.xpm"

#define E_WEEK_VIEW_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), E_TYPE_WEEK_VIEW, EWeekViewPrivate))

#define E_WEEK_VIEW_SMALL_FONT_PTSIZE 7

#define E_WEEK_VIEW_JUMP_BUTTON_WIDTH   16
#define E_WEEK_VIEW_JUMP_BUTTON_HEIGHT  8

#define E_WEEK_VIEW_JUMP_BUTTON_X_PAD   3
#define E_WEEK_VIEW_JUMP_BUTTON_Y_PAD   3

#define E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS -1

/* The timeout before we do a layout, so we don't do a layout for each event
 * we get from the server. */
#define E_WEEK_VIEW_LAYOUT_TIMEOUT  100

struct _EWeekViewPrivate {
    /* The first day shown in the view. */
    GDate first_day_shown;

    /* If we are displaying multiple weeks in rows.  If this is
     * FALSE only one week is shown, with a different layout. */
    gboolean multi_week_view;

    /* How many weeks we are showing.  This is only relevant if
     * multi_week_view is TRUE. */
    gint weeks_shown;

    /* If Sat & Sun are compressed.  Only applicable in month view,
     * since they are always compressed into 1 cell in week view. */
    gboolean compress_weekend;

    /* Whether we show event end times. */
    gboolean show_event_end_times;

    /* Whether to update the base date when the time range changes. */
    gboolean update_base_date;

    /* The first day of the week we display.  This will usually be
     * week_start_day, but if the Sat & Sun are compressed and the
     * week starts on Sunday then we have to use Saturday. */
    GDateWeekday display_start_day;
};

typedef struct {
    EWeekView *week_view;
    ECalModelComponent *comp_data;
} AddEventData;

static void e_week_view_set_colors (EWeekView *week_view, GtkWidget *widget);
static void e_week_view_recalc_cell_sizes (EWeekView *week_view);
static gboolean e_week_view_get_next_tab_event (EWeekView *week_view,
                        GtkDirectionType direction,
                        gint current_event_num,
                        gint current_span_num,
                        gint *next_event_num,
                        gint *next_span_num);
static void e_week_view_update_query (EWeekView *week_view);

static gboolean e_week_view_on_button_press (GtkWidget *widget,
                         GdkEvent *button_event,
                         EWeekView *week_view);
static gboolean e_week_view_on_button_release (GtkWidget *widget,
                           GdkEvent *button_event,
                           EWeekView *week_view);
static gboolean e_week_view_on_scroll (GtkWidget *widget,
                       GdkEventScroll *scroll,
                       EWeekView *week_view);
static gboolean e_week_view_on_motion (GtkWidget *widget,
                       GdkEventMotion *event,
                       EWeekView *week_view);
static gint e_week_view_convert_position_to_day (EWeekView *week_view,
                         gint x,
                         gint y);
static void e_week_view_update_selection (EWeekView *week_view,
                      gint day);

static void e_week_view_free_events (EWeekView *week_view);
static gboolean e_week_view_add_event (ECalComponent *comp,
                       time_t     start,
                       time_t     end,
                       gboolean prepend,
                       gpointer   data);
static void e_week_view_check_layout (EWeekView *week_view);
static void e_week_view_ensure_events_sorted (EWeekView *week_view);
static void e_week_view_reshape_events (EWeekView *week_view);
static void e_week_view_reshape_event_span (EWeekView *week_view,
                        gint event_num,
                        gint span_num);
static void e_week_view_recalc_day_starts (EWeekView *week_view,
                       time_t lower);
static void e_week_view_on_editing_started (EWeekView *week_view,
                        GnomeCanvasItem *item);
static void e_week_view_on_editing_stopped (EWeekView *week_view,
                        GnomeCanvasItem *item);
static gboolean e_week_view_find_event_from_uid (EWeekView    *week_view,
                         ECalClient             *client,
                         const gchar      *uid,
                         const gchar      *rid,
                         gint         *event_num_return);
typedef gboolean (* EWeekViewForeachEventCallback) (EWeekView *week_view,
                            gint event_num,
                            gpointer data);
static void e_week_view_foreach_event_with_uid (EWeekView *week_view,
                        const gchar *uid,
                        EWeekViewForeachEventCallback callback,
                        gpointer data);
static gboolean e_week_view_on_text_item_event (GnomeCanvasItem *item,
                        GdkEvent *event,
                        EWeekView *week_view);
static gboolean e_week_view_event_move (ECalendarView *cal_view, ECalViewMoveDirection direction);
static gint e_week_view_get_day_offset_of_event (EWeekView *week_view, time_t event_time);
static void e_week_view_change_event_time (EWeekView *week_view, time_t start_dt, time_t end_dt, gboolean is_all_day);
static gboolean e_week_view_on_jump_button_event (GnomeCanvasItem *item,
                          GdkEvent *event,
                          EWeekView *week_view);
static gboolean e_week_view_do_key_press (GtkWidget *widget,
                      GdkEventKey *event);
static gint e_week_view_get_adjust_days_for_move_up (EWeekView *week_view, gint
current_day);
static gint e_week_view_get_adjust_days_for_move_down (EWeekView *week_view,gint current_day);
static gint e_week_view_get_adjust_days_for_move_left (EWeekView *week_view,gint current_day);
static gint e_week_view_get_adjust_days_for_move_right (EWeekView *week_view,gint current_day);

static gboolean e_week_view_remove_event_cb (EWeekView *week_view,
                         gint event_num,
                         gpointer data);
static gboolean e_week_view_recalc_display_start_day    (EWeekView  *week_view);

static void e_week_view_queue_layout (EWeekView *week_view);
static void e_week_view_cancel_layout (EWeekView *week_view);
static gboolean e_week_view_layout_timeout_cb (gpointer data);

G_DEFINE_TYPE (EWeekView, e_week_view, E_TYPE_CALENDAR_VIEW)

enum {
    PROP_0,
    PROP_COMPRESS_WEEKEND,
    PROP_SHOW_EVENT_END_TIMES,
    PROP_IS_EDITING
};

static gint map_left[] = {0, 1, 2, 0, 1, 2, 2};
static gint map_right[] = {3, 4, 5, 3, 4, 5, 6};

static void
week_view_process_component (EWeekView *week_view,
                             ECalModelComponent *comp_data)
{
    ECalComponent *comp = NULL;
    AddEventData add_event_data;
    /* rid is never used in this function? */
    const gchar *uid;
    gchar *rid = NULL;

    /* If we don't have a valid date set yet, just return. */
    if (!g_date_valid (&week_view->priv->first_day_shown))
        return;

    comp = e_cal_component_new ();
    if (!e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (comp_data->icalcomp))) {
        g_object_unref (comp);

        g_message (G_STRLOC ": Could not set icalcomponent on ECalComponent");
        return;
    }

    e_cal_component_get_uid (comp, &uid);
    if (e_cal_component_is_instance (comp))
        rid = e_cal_component_get_recurid_as_string (comp);
    else
        rid = NULL;

    /* Add the object */
    add_event_data.week_view = week_view;
    add_event_data.comp_data = comp_data;
    e_week_view_add_event (comp, comp_data->instance_start, comp_data->instance_end, FALSE, &add_event_data);

    g_object_unref (comp);
    g_free (rid);
}

static void
week_view_update_row (EWeekView *week_view,
                      gint row)
{
    ECalModelComponent *comp_data;
    ECalModel *model;
    gint event_num;
    const gchar *uid;
    gchar *rid = NULL;

    model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view));
    comp_data = e_cal_model_get_component_at (model, row);
    g_return_if_fail (comp_data != NULL);

    uid = icalcomponent_get_uid (comp_data->icalcomp);
    if (e_cal_util_component_is_instance (comp_data->icalcomp)) {
        icalproperty *prop;

        prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_RECURRENCEID_PROPERTY);
        if (prop)
            rid = icaltime_as_ical_string_r (icalcomponent_get_recurrenceid (comp_data->icalcomp));
    }

    if (e_week_view_find_event_from_uid (week_view, comp_data->client, uid, rid, &event_num))
        e_week_view_remove_event_cb (week_view, event_num, NULL);

    g_free (rid);

    week_view_process_component (week_view, comp_data);

    gtk_widget_queue_draw (week_view->main_canvas);
    e_week_view_queue_layout (week_view);
}

static void
week_view_model_cell_changed_cb (EWeekView *week_view,
                                 gint col,
                                 gint row)
{
    if (!E_CALENDAR_VIEW (week_view)->in_focus) {
        e_week_view_free_events (week_view);
        week_view->requires_update = TRUE;
        return;
    }

    week_view_update_row (week_view, row);
}

static void
week_view_model_comps_deleted_cb (EWeekView *week_view,
                                  gpointer data)
{
    GSList *l, *list = data;

    /* FIXME Stop editing? */
    if (!E_CALENDAR_VIEW (week_view)->in_focus) {
        e_week_view_free_events (week_view);
        week_view->requires_update = TRUE;
        return;
    }

    for (l = list; l != NULL; l = g_slist_next (l)) {
        gint event_num;
        const gchar *uid;
        gchar *rid = NULL;
        ECalModelComponent *comp_data = l->data;

        uid = icalcomponent_get_uid (comp_data->icalcomp);
        if (e_cal_util_component_is_instance (comp_data->icalcomp)) {
            icalproperty *prop;

            prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_RECURRENCEID_PROPERTY);
            if (prop)
                rid = icaltime_as_ical_string_r (icalcomponent_get_recurrenceid (comp_data->icalcomp));
        }

        if (e_week_view_find_event_from_uid (week_view, comp_data->client, uid, rid, &event_num))
            e_week_view_remove_event_cb (week_view, event_num, NULL);
        g_free (rid);
    }

    gtk_widget_queue_draw (week_view->main_canvas);
    e_week_view_queue_layout (week_view);
}

static void
week_view_model_row_changed_cb (EWeekView *week_view,
                                gint row)
{
    if (!E_CALENDAR_VIEW (week_view)->in_focus) {
        e_week_view_free_events (week_view);
        week_view->requires_update = TRUE;
        return;
    }

    week_view_update_row (week_view, row);
}

static void
week_view_model_rows_inserted_cb (EWeekView *week_view,
                                  gint row,
                                  gint count)
{
    ECalModel *model;
    gint i;

    if (!E_CALENDAR_VIEW (week_view)->in_focus) {
        e_week_view_free_events (week_view);
        week_view->requires_update = TRUE;
        return;
    }

    model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view));

    for (i = 0; i < count; i++) {
        ECalModelComponent *comp_data;

        comp_data = e_cal_model_get_component_at (model, row + i);
        if (comp_data == NULL) {
            g_warning ("comp_data is NULL\n");
            continue;
        }
        week_view_process_component (week_view, comp_data);
    }

    gtk_widget_queue_draw (week_view->main_canvas);
    e_week_view_queue_layout (week_view);
}

static void
week_view_time_range_changed_cb (EWeekView *week_view,
                                 time_t start_time,
                                 time_t end_time,
                                 ECalModel *model)
{
    GDate date, base_date;
    GDateWeekday weekday;
    GDateWeekday display_start_day;
    guint day_offset, week_start_offset;
    gint num_days;
    gboolean update_adjustment_value = FALSE;

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    time_to_gdate_with_zone (&date, start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));

    weekday = g_date_get_weekday (&date);
    display_start_day = e_week_view_get_display_start_day (week_view);

    /* Convert it to an offset from the start of the display. */
    week_start_offset = e_weekday_get_days_between (
        display_start_day, weekday);

    /* Set the day_offset to the result, so we move back to the
     * start of the week. */
    day_offset = week_start_offset;

    /* Calculate the base date, i.e. the first day shown when the
     * scrollbar adjustment value is 0. */
    base_date = date;
    g_date_subtract_days (&base_date, day_offset);

    /* See if we need to update the base date. */
    if (!g_date_valid (&week_view->base_date)
        || e_week_view_get_update_base_date (week_view)) {
        week_view->base_date = base_date;
        update_adjustment_value = TRUE;
    }

    /* See if we need to update the first day shown. */
    if (!g_date_valid (&week_view->priv->first_day_shown)
        || g_date_compare (&week_view->priv->first_day_shown, &base_date)) {
        week_view->priv->first_day_shown = base_date;
        start_time = time_add_day_with_zone (
            start_time, -((gint) day_offset),
            e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
        start_time = time_day_begin_with_zone (
            start_time,
            e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
        e_week_view_recalc_day_starts (week_view, start_time);
    }

    /* Reset the adjustment value to 0 if the base address has changed.
     * Note that we do this after updating first_day_shown so that our
     * signal handler will not try to reload the events. */
    if (update_adjustment_value) {
        GtkRange *range;
        GtkAdjustment *adjustment;

        range = GTK_RANGE (week_view->vscrollbar);
        adjustment = gtk_range_get_adjustment (range);
        gtk_adjustment_set_value (adjustment, 0);
    }

    if (!E_CALENDAR_VIEW (week_view)->in_focus) {
        e_week_view_free_events (week_view);
        week_view->requires_update = TRUE;
        return;
    }

    gtk_widget_queue_draw (week_view->main_canvas);

    num_days = e_week_view_get_weeks_shown (week_view) * 7;

    /* FIXME Preserve selection if possible */
    if (week_view->selection_start_day == -1 ||
        num_days <= week_view->selection_start_day)
        e_calendar_view_set_selected_time_range (
            E_CALENDAR_VIEW (week_view), start_time, start_time);
}

static void
timezone_changed_cb (ECalModel *cal_model,
                     icaltimezone *old_zone,
                     icaltimezone *new_zone,
                     gpointer user_data)
{
    ECalendarView *cal_view = (ECalendarView *) user_data;
    GDate *first_day_shown;
    struct icaltimetype tt = icaltime_null_time ();
    time_t lower;
    EWeekView *week_view = (EWeekView *) cal_view;

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    first_day_shown = &week_view->priv->first_day_shown;

    if (!cal_view->in_focus) {
        e_week_view_free_events (week_view);
        week_view->requires_update = TRUE;
        return;
    }

    /* If we don't have a valid date set yet, just return. */
    if (!g_date_valid (first_day_shown))
        return;

    /* Recalculate the new start of the first week. We just use exactly
     * the same time, but with the new timezone. */
    tt.year = g_date_get_year (first_day_shown);
    tt.month = g_date_get_month (first_day_shown);
    tt.day = g_date_get_day (first_day_shown);

    lower = icaltime_as_timet_with_zone (tt, new_zone);

    e_week_view_recalc_day_starts (week_view, lower);
    e_week_view_update_query (week_view);
}

static void
week_view_notify_week_start_day_cb (EWeekView *week_view)
{
    GDate *first_day_shown;

    first_day_shown = &week_view->priv->first_day_shown;

    e_week_view_recalc_display_start_day (week_view);

    /* Recalculate the days shown and reload if necessary. */
    if (g_date_valid (first_day_shown))
        e_week_view_set_first_day_shown (week_view, first_day_shown);

    gtk_widget_queue_draw (week_view->titles_canvas);
    gtk_widget_queue_draw (week_view->main_canvas);
}

static void
month_scroll_by_week_changed_cb (GSettings *settings,
                                 const gchar *key,
                                 gpointer user_data)
{
    EWeekView *week_view = user_data;

    g_return_if_fail (week_view != NULL);
    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    if (e_week_view_get_multi_week_view (week_view) &&
        week_view->month_scroll_by_week != calendar_config_get_month_scroll_by_week ()) {
        week_view->priv->multi_week_view = FALSE;
        e_week_view_set_multi_week_view (week_view, TRUE);
    }
}

/* FIXME: This is also needed in e-day-view-time-item.c. We should probably use
 * pango's approximation function, but it needs a language tag. Find out how to
 * get one of those properly. */
static gint
get_digit_width (PangoLayout *layout)
{
    gint digit;
    gint max_digit_width = 1;

    for (digit = '0'; digit <= '9'; digit++) {
        gchar digit_char;
        gint  digit_width;

        digit_char = digit;

        pango_layout_set_text (layout, &digit_char, 1);
        pango_layout_get_pixel_size (layout, &digit_width, NULL);

        max_digit_width = MAX (max_digit_width, digit_width);
    }

    return max_digit_width;
}

static gint
get_string_width (PangoLayout *layout,
                  const gchar *string)
{
    gint width;

    pango_layout_set_text (layout, string, -1);
    pango_layout_get_pixel_size (layout, &width, NULL);
    return width;
}

static gboolean
e_week_view_add_new_event_in_selected_range (EWeekView *week_view,
                                             const gchar *initial_text)
{
    ECalClient *client;
    ECalModel *model;
    ECalComponent *comp = NULL;
    icalcomponent *icalcomp;
    gint event_num;
    ECalComponentDateTime date;
    struct icaltimetype itt;
    time_t dtstart, dtend;
    const gchar *uid;
    AddEventData add_event_data;
    EWeekViewEvent *wvevent;
    EWeekViewEventSpan *span;
    gboolean success = FALSE;

    model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view));
    client = e_cal_model_ref_default_client (model);

    /* Check if the client is read only */
    if (e_client_is_readonly (E_CLIENT (client)))
        goto exit;

    /* Add a new event covering the selected range. */
    icalcomp = e_cal_model_create_component_with_defaults (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), TRUE);
    if (!icalcomp)
        goto exit;
    uid = icalcomponent_get_uid (icalcomp);

    comp = e_cal_component_new ();
    e_cal_component_set_icalcomponent (comp, icalcomp);

    dtstart = week_view->day_starts[week_view->selection_start_day];
    dtend = week_view->day_starts[week_view->selection_end_day + 1];

    date.value = &itt;
    date.tzid = NULL;

    /* We use DATE values now, so we don't need the timezone. */
    /*date.tzid = icaltimezone_get_tzid (e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));*/

    *date.value = icaltime_from_timet_with_zone (dtstart, TRUE,
                             e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
    e_cal_component_set_dtstart (comp, &date);

    *date.value = icaltime_from_timet_with_zone (dtend, TRUE,
                             e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
    e_cal_component_set_dtend (comp, &date);

    /* Editor default in week/month view */
    e_cal_component_set_transparency (comp, E_CAL_COMPONENT_TRANSP_TRANSPARENT);

    e_cal_component_set_categories (
        comp, e_calendar_view_get_default_category (E_CALENDAR_VIEW (week_view)));

    /* We add the event locally and start editing it. We don't send it
     * to the server until the user finishes editing it. */
    add_event_data.week_view = week_view;
    add_event_data.comp_data = NULL;
    e_week_view_add_event (comp, dtstart, dtend, TRUE, &add_event_data);
    e_week_view_check_layout (week_view);
    gtk_widget_queue_draw (week_view->main_canvas);

    if (!e_week_view_find_event_from_uid (week_view, client, uid, NULL, &event_num)) {
        g_warning ("Couldn't find event to start editing.\n");
        goto exit;
    }

    if (!is_array_index_in_bounds (week_view->events, event_num))
        goto exit;

    wvevent = &g_array_index (week_view->events, EWeekViewEvent,
                  event_num);

    if (!is_array_index_in_bounds (week_view->spans, wvevent->spans_index + 0))
        goto exit;

    span = &g_array_index (week_view->spans, EWeekViewEventSpan,
                   wvevent->spans_index + 0);

    /* If the event can't be fit on the screen, don't try to edit it. */
    if (!span->text_item) {
        e_week_view_foreach_event_with_uid (week_view, uid,
                e_week_view_remove_event_cb, NULL);
        goto exit;
    }

    e_week_view_start_editing_event (
        week_view, event_num, 0, (gchar *) initial_text);

    success = TRUE;

exit:
    g_clear_object (&comp);
    g_clear_object (&client);

    return success;
}

static void
week_view_set_property (GObject *object,
                        guint property_id,
                        const GValue *value,
                        GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_COMPRESS_WEEKEND:
            e_week_view_set_compress_weekend (
                E_WEEK_VIEW (object),
                g_value_get_boolean (value));
            return;

        case PROP_SHOW_EVENT_END_TIMES:
            e_week_view_set_show_event_end_times (
                E_WEEK_VIEW (object),
                g_value_get_boolean (value));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
week_view_get_property (GObject *object,
                        guint property_id,
                        GValue *value,
                        GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_COMPRESS_WEEKEND:
            g_value_set_boolean (
                value,
                e_week_view_get_compress_weekend (
                E_WEEK_VIEW (object)));
            return;

        case PROP_SHOW_EVENT_END_TIMES:
            g_value_set_boolean (
                value,
                e_week_view_get_show_event_end_times (
                E_WEEK_VIEW (object)));
            return;

        case PROP_IS_EDITING:
            g_value_set_boolean (value, e_week_view_is_editing (E_WEEK_VIEW (object)));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
week_view_dispose (GObject *object)
{
    EWeekView *week_view;

    week_view = E_WEEK_VIEW (object);

    e_week_view_cancel_layout (week_view);

    if (week_view->events) {
        e_week_view_free_events (week_view);
        g_array_free (week_view->events, TRUE);
        week_view->events = NULL;
    }

    if (week_view->small_font_desc) {
        pango_font_description_free (week_view->small_font_desc);
        week_view->small_font_desc = NULL;
    }

    if (week_view->normal_cursor) {
        g_object_unref (week_view->normal_cursor);
        week_view->normal_cursor = NULL;
    }
    if (week_view->move_cursor) {
        g_object_unref (week_view->move_cursor);
        week_view->move_cursor = NULL;
    }
    if (week_view->resize_width_cursor) {
        g_object_unref (week_view->resize_width_cursor);
        week_view->resize_width_cursor = NULL;
    }

    calendar_config_remove_notification (
        month_scroll_by_week_changed_cb, week_view);

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

static void
week_view_constructed (GObject *object)
{
    ECalModel *model;
    ECalendarView *calendar_view;

    /* Chain up to parent's constructed() method. */
    G_OBJECT_CLASS (e_week_view_parent_class)->constructed (object);

    calendar_view = E_CALENDAR_VIEW (object);
    model = e_calendar_view_get_model (calendar_view);

    e_week_view_recalc_display_start_day (E_WEEK_VIEW (object));

    g_signal_connect_swapped (
        model, "notify::week-start-day",
        G_CALLBACK (week_view_notify_week_start_day_cb), object);

    g_signal_connect_swapped (
        model, "comps-deleted",
        G_CALLBACK (week_view_model_comps_deleted_cb), object);

    g_signal_connect_swapped (
        model, "model-cell-changed",
        G_CALLBACK (week_view_model_cell_changed_cb), object);

    g_signal_connect_swapped (
        model, "model-row-changed",
        G_CALLBACK (week_view_model_row_changed_cb), object);

    g_signal_connect_swapped (
        model, "model-rows-inserted",
        G_CALLBACK (week_view_model_rows_inserted_cb), object);

    g_signal_connect_swapped (
        model, "time-range-changed",
        G_CALLBACK (week_view_time_range_changed_cb), object);
}

static void
week_view_realize (GtkWidget *widget)
{
    EWeekView *week_view;

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

    week_view = E_WEEK_VIEW (widget);

    /* Allocate the colors. */
    e_week_view_set_colors (week_view, widget);

    /* Create the pixmaps. */
    week_view->reminder_icon =
        e_icon_factory_get_icon ("stock_bell", GTK_ICON_SIZE_MENU);
    week_view->recurrence_icon =
        e_icon_factory_get_icon ("view-refresh", GTK_ICON_SIZE_MENU);
    week_view->timezone_icon =
        e_icon_factory_get_icon ("stock_timezone", GTK_ICON_SIZE_MENU);
    week_view->attach_icon =
        e_icon_factory_get_icon ("mail-attachment", GTK_ICON_SIZE_MENU);
    week_view->meeting_icon =
        e_icon_factory_get_icon ("stock_people", GTK_ICON_SIZE_MENU);
}

static void
week_view_unrealize (GtkWidget *widget)
{
    EWeekView *week_view;

    week_view = E_WEEK_VIEW (widget);

    g_object_unref (week_view->reminder_icon);
    week_view->reminder_icon = NULL;
    g_object_unref (week_view->recurrence_icon);
    week_view->recurrence_icon = NULL;
    g_object_unref (week_view->timezone_icon);
    week_view->timezone_icon = NULL;
    g_object_unref (week_view->attach_icon);
    week_view->attach_icon = NULL;
    g_object_unref (week_view->meeting_icon);
    week_view->meeting_icon = NULL;

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

static void
week_view_style_set (GtkWidget *widget,
                     GtkStyle *previous_style)
{
    EWeekView *week_view;
    GtkStyle *style;
    gint day, day_width, max_day_width, max_abbr_day_width;
    gint month, month_width, max_month_width, max_abbr_month_width;
    gint span_num;
    const gchar *name;
    PangoFontDescription *font_desc;
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;
    PangoLayout *layout;
    EWeekViewEventSpan *span;

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

    week_view = E_WEEK_VIEW (widget);
    style = gtk_widget_get_style (widget);

    e_week_view_set_colors (week_view, widget);
    if (week_view->spans) {
        for (span_num = 0; span_num < week_view->spans->len;
                span_num++) {
            span = &g_array_index (week_view->spans,
                    EWeekViewEventSpan, span_num);
            if (span->text_item) {
                gnome_canvas_item_set (
                    span->text_item,
                    "fill_color_gdk", &style->text[GTK_STATE_NORMAL],
                    NULL);
            }
        }
    }

    /* Set up Pango prerequisites */
    font_desc = style->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);

    /* Recalculate the height of each row based on the font size. */
    week_view->row_height = PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
        PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)) +
        E_WEEK_VIEW_EVENT_BORDER_HEIGHT * 2 + E_WEEK_VIEW_EVENT_TEXT_Y_PAD * 2;
    week_view->row_height = MAX (week_view->row_height, E_WEEK_VIEW_ICON_HEIGHT + E_WEEK_VIEW_ICON_Y_PAD + E_WEEK_VIEW_EVENT_BORDER_HEIGHT * 2);

    /* Check that the small font is smaller than the default font.
     * If it isn't, we won't use it. */
    if (week_view->small_font_desc) {
        if (PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
            PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
            <= E_WEEK_VIEW_SMALL_FONT_PTSIZE)
            week_view->use_small_font = FALSE;
    }

    /* Set the height of the top canvas. */
    gtk_widget_set_size_request (
        week_view->titles_canvas, -1,
        PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
        PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)) + 5);

    /* Save the sizes of various strings in the font, so we can quickly
     * decide which date formats to use. */

    max_day_width = 0;
    max_abbr_day_width = 0;
    for (day = 0; day < 7; day++) {
        name = e_get_weekday_name (day + 1, FALSE);
        day_width = get_string_width (layout, name);
        week_view->day_widths[day] = day_width;
        max_day_width = MAX (max_day_width, day_width);

        name = e_get_weekday_name (day + 1, TRUE);
        day_width = get_string_width (layout, name);
        week_view->abbr_day_widths[day] = day_width;
        max_abbr_day_width = MAX (max_abbr_day_width, day_width);
    }

    max_month_width = 0;
    max_abbr_month_width = 0;
    for (month = 0; month < 12; month++) {
        name = e_get_month_name (month + 1, FALSE);
        month_width = get_string_width (layout, name);
        week_view->month_widths[month] = month_width;
        max_month_width = MAX (max_month_width, month_width);

        name = e_get_month_name (month + 1, TRUE);
        month_width = get_string_width (layout, name);
        week_view->abbr_month_widths[month] = month_width;
        max_abbr_month_width = MAX (max_abbr_month_width, month_width);
    }

    week_view->space_width = get_string_width (layout, " ");
    week_view->colon_width = get_string_width (layout, ":");
    week_view->slash_width = get_string_width (layout, "/");
    week_view->digit_width = get_digit_width (layout);
    if (week_view->small_font_desc) {
        pango_layout_set_font_description (layout, week_view->small_font_desc);
        week_view->small_digit_width = get_digit_width (layout);
        pango_layout_set_font_description (layout, style->font_desc);
    }
    week_view->max_day_width = max_day_width;
    week_view->max_abbr_day_width = max_abbr_day_width;
    week_view->max_month_width = max_month_width;
    week_view->max_abbr_month_width = max_abbr_month_width;

    week_view->am_string_width = get_string_width (
        layout,
        week_view->am_string);
    week_view->pm_string_width = get_string_width (
        layout,
        week_view->pm_string);

    g_object_unref (layout);
    pango_font_metrics_unref (font_metrics);
}

static void
week_view_size_allocate (GtkWidget *widget,
                         GtkAllocation *allocation)
{
    EWeekView *week_view;
    GtkAllocation canvas_allocation;
    gdouble old_x2, old_y2, new_x2, new_y2;

    week_view = E_WEEK_VIEW (widget);

    (*GTK_WIDGET_CLASS (e_week_view_parent_class)->size_allocate) (widget, allocation);

    e_week_view_recalc_cell_sizes (week_view);

    /* Set the scroll region of the top canvas to its allocated size. */
    gnome_canvas_get_scroll_region (
        GNOME_CANVAS (week_view->titles_canvas),
        NULL, NULL, &old_x2, &old_y2);
    gtk_widget_get_allocation (
        week_view->titles_canvas, &canvas_allocation);
    new_x2 = canvas_allocation.width - 1;
    new_y2 = canvas_allocation.height - 1;
    if (old_x2 != new_x2 || old_y2 != new_y2)
        gnome_canvas_set_scroll_region (
            GNOME_CANVAS (week_view->titles_canvas),
            0, 0, new_x2, new_y2);

    /* Set the scroll region of the main canvas to its allocated width,
     * but with the height depending on the number of rows needed. */
    gnome_canvas_get_scroll_region (
        GNOME_CANVAS (week_view->main_canvas),
        NULL, NULL, &old_x2, &old_y2);
    gtk_widget_get_allocation (
        week_view->main_canvas, &canvas_allocation);
    new_x2 = canvas_allocation.width - 1;
    new_y2 = canvas_allocation.height - 1;
    if (old_x2 != new_x2 || old_y2 != new_y2)
        gnome_canvas_set_scroll_region (
            GNOME_CANVAS (week_view->main_canvas),
            0, 0, new_x2, new_y2);

    /* Flag that we need to reshape the events. */
    if (old_x2 != new_x2 || old_y2 != new_y2) {
        week_view->events_need_reshape = TRUE;
        e_week_view_check_layout (week_view);
    }
}

static gint
week_view_focus_in (GtkWidget *widget,
                    GdkEventFocus *event)
{
    EWeekView *week_view;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    week_view = E_WEEK_VIEW (widget);

    /* XXX Can't access flags directly anymore, but is it really needed?
     *     If so, could we call gtk_widget_send_focus_change() instead? */
#if 0
    GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
#endif

    if (E_CALENDAR_VIEW (week_view)->in_focus && week_view->requires_update) {
        time_t my_start = 0, my_end = 0, model_start = 0, model_end = 0;

        week_view->requires_update = FALSE;

        e_cal_model_get_time_range (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), &model_start, &model_end);

        if (e_calendar_view_get_visible_time_range (E_CALENDAR_VIEW (week_view), &my_start, &my_end) &&
            model_start == my_start && model_end == my_end) {
            /* update only when the same time range is set in a view and in a model;
             * otherwise time range change invokes also query update */
            e_week_view_update_query (week_view);
        }
    }

    gtk_widget_queue_draw (week_view->main_canvas);

    return FALSE;
}

static gint
week_view_focus_out (GtkWidget *widget,
                     GdkEventFocus *event)
{
    EWeekView *week_view;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    week_view = E_WEEK_VIEW (widget);

    /* XXX Can't access flags directly anymore, but is it really needed?
     *     If so, could we call gtk_widget_send_focus_change() instead? */
#if 0
    GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
#endif

    gtk_widget_queue_draw (week_view->main_canvas);

    return FALSE;
}

static gboolean
week_view_key_press (GtkWidget *widget,
                     GdkEventKey *event)
{
    gboolean handled = FALSE;
    handled = e_week_view_do_key_press (widget, event);

    /* if not handled, try key bindings */
    if (!handled)
        handled = GTK_WIDGET_CLASS (e_week_view_parent_class)->key_press_event (widget, event);
    return handled;
}

static gboolean
week_view_focus (GtkWidget *widget,
                 GtkDirectionType direction)
{
    EWeekView *week_view;
    gint new_event_num;
    gint new_span_num;
    gint event_loop;
    gboolean editable = FALSE;
    static gint last_focus_event_num = -1, last_focus_span_num = -1;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE);

    week_view = E_WEEK_VIEW (widget);

    e_week_view_check_layout (week_view);

    if (week_view->focused_jump_button == E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS) {
        last_focus_event_num = week_view->editing_event_num;
        last_focus_span_num = week_view->editing_span_num;
    }

    /* if there is not event, just grab week_view */
    if (week_view->events->len == 0) {
        gtk_widget_grab_focus (widget);
        return TRUE;
    }

    for (event_loop = 0; event_loop < week_view->events->len;
         ++event_loop) {
        if (!e_week_view_get_next_tab_event (week_view, direction,
                             last_focus_event_num,
                             last_focus_span_num,
                             &new_event_num,
                             &new_span_num))
            return FALSE;

        if (new_event_num == -1) {
            /* focus should go to week_view widget
             */
            gtk_widget_grab_focus (widget);
            return TRUE;
        }

        editable = e_week_view_start_editing_event (
            week_view,
            new_event_num,
            new_span_num,
            NULL);
        last_focus_event_num = new_event_num;
        last_focus_span_num = new_span_num;

        if (editable)
            break;
        else {
            /* check if we should go to the jump button */

            EWeekViewEvent *event;
            EWeekViewEventSpan *span;
            gint current_day;

            if (!is_array_index_in_bounds (week_view->events, new_event_num))
                break;

            event = &g_array_index (week_view->events,
                        EWeekViewEvent,
                        new_event_num);

            if (!is_array_index_in_bounds (week_view->spans, event->spans_index + new_span_num))
                break;

            span = &g_array_index (week_view->spans,
                           EWeekViewEventSpan,
                           event->spans_index + new_span_num);
            current_day = span->start_day;

            if ((week_view->focused_jump_button != current_day) &&
                e_week_view_is_jump_button_visible (week_view, current_day)) {

                /* focus go to the jump button */
                e_week_view_stop_editing_event (week_view);
                gnome_canvas_item_grab_focus (week_view->jump_buttons[current_day]);
                return TRUE;
            }
        }
    }
    return editable;
}

static gboolean
week_view_popup_menu (GtkWidget *widget)
{
    EWeekView *week_view = E_WEEK_VIEW (widget);

    e_week_view_show_popup_menu (
        week_view, NULL,
        week_view->editing_event_num);

    return TRUE;
}

static GList *
week_view_get_selected_events (ECalendarView *cal_view)
{
    EWeekViewEvent *event = NULL;
    GList *list = NULL;
    EWeekView *week_view = (EWeekView *) cal_view;

    g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), NULL);

    if (week_view->editing_event_num != -1) {
        if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num)) {
            week_view->editing_event_num = -1;
            g_object_notify (G_OBJECT (week_view), "is-editing");
            return NULL;
        }

        event = &g_array_index (week_view->events, EWeekViewEvent,
                    week_view->editing_event_num);
    } else if (week_view->popup_event_num != -1) {
        if (!is_array_index_in_bounds (week_view->events, week_view->popup_event_num))
            return NULL;

        event = &g_array_index (week_view->events, EWeekViewEvent,
                    week_view->popup_event_num);
    }

    if (event)
        list = g_list_prepend (list, event);

    return list;
}

static gboolean
week_view_get_selected_time_range (ECalendarView *cal_view,
                                   time_t *start_time,
                                   time_t *end_time)
{
    gint start_day, end_day;
    EWeekView *week_view = E_WEEK_VIEW (cal_view);

    start_day = week_view->selection_start_day;
    end_day = week_view->selection_end_day;

    if (start_day == -1) {
        start_day = 0;
        end_day = 0;
    }

    if (start_time)
        *start_time = week_view->day_starts[start_day];

    if (end_time)
        *end_time = week_view->day_starts[end_day + 1];

    return  TRUE;
}

/* This sets the selected time range. The EWeekView will show the corresponding
 * month and the days between start_time and end_time will be selected.
 * To select a single day, use the same value for start_time & end_time. */
static void
week_view_set_selected_time_range (ECalendarView *cal_view,
                                   time_t start_time,
                                   time_t end_time)
{
    GDate date, end_date;
    gint num_days;
    gboolean update_adjustment_value = FALSE;
    EWeekView *week_view = E_WEEK_VIEW (cal_view);

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    if (!g_date_valid (&week_view->base_date)) {
        /* This view has not been initialized/shown yet, thus skip this. */
        return;
    }

    time_to_gdate_with_zone (&date, start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));

    /* Set the selection to the given days. */
    week_view->selection_start_day = g_date_get_julian (&date)
        - g_date_get_julian (&week_view->base_date);
    if (end_time == start_time
        || end_time <= time_add_day_with_zone (start_time, 1,
                           e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))))
        week_view->selection_end_day = week_view->selection_start_day;
    else {
        time_to_gdate_with_zone (&end_date, end_time - 60, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
        week_view->selection_end_day = g_date_get_julian (&end_date)
            - g_date_get_julian (&week_view->base_date);
    }

    /* Make sure the selection is valid. */
    num_days = (e_week_view_get_weeks_shown (week_view) * 7) - 1;
    week_view->selection_start_day = CLAMP (
        week_view->selection_start_day, 0, num_days);
    week_view->selection_end_day = CLAMP (
        week_view->selection_end_day,
        week_view->selection_start_day,
        num_days);

    /* Reset the adjustment value to 0 if the base address has changed.
     * Note that we do this after updating first_day_shown so that our
     * signal handler will not try to reload the events. */
    if (update_adjustment_value) {
        GtkRange *range;
        GtkAdjustment *adjustment;

        range = GTK_RANGE (week_view->vscrollbar);
        adjustment = gtk_range_get_adjustment (range);
        gtk_adjustment_set_value (adjustment, 0);
    }

    gtk_widget_queue_draw (week_view->main_canvas);
}

static gboolean
week_view_get_visible_time_range (ECalendarView *cal_view,
                                  time_t *start_time,
                                  time_t *end_time)
{
    gint num_days;
    EWeekView *week_view = E_WEEK_VIEW (cal_view);

    /* If we don't have a valid date set yet, return FALSE. */
    if (!g_date_valid (&week_view->priv->first_day_shown))
        return FALSE;

    num_days = e_week_view_get_weeks_shown (week_view) * 7;

    *start_time = week_view->day_starts[0];
    *end_time = week_view->day_starts[num_days];

    return TRUE;
}

static void
week_view_paste_text (ECalendarView *cal_view)
{
    EWeekViewEvent *event;
    EWeekViewEventSpan *span;
    EWeekView *week_view;

    g_return_if_fail (E_IS_WEEK_VIEW (cal_view));

    week_view = E_WEEK_VIEW (cal_view);

    if (week_view->editing_event_num == -1 &&
        !e_week_view_add_new_event_in_selected_range (week_view, NULL))
        return;

    if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num))
        return;

    event = &g_array_index (week_view->events, EWeekViewEvent,
                week_view->editing_event_num);

    if (!is_array_index_in_bounds (week_view->spans, event->spans_index + week_view->editing_span_num))
        return;

    span = &g_array_index (week_view->spans, EWeekViewEventSpan,
                   event->spans_index + week_view->editing_span_num);

    if (span->text_item &&
        E_IS_TEXT (span->text_item) &&
        E_TEXT (span->text_item)->editing) {
        e_text_paste_clipboard (E_TEXT (span->text_item));
    }
}

static void
week_view_cursor_key_up (EWeekView *week_view)
{
    if (week_view->selection_start_day == -1)
        return;

    week_view->selection_start_day--;

    if (week_view->selection_start_day < 0) {
        e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_UP);
        week_view->selection_start_day = 6;
    }

    week_view->selection_end_day = week_view->selection_start_day;
    g_signal_emit_by_name (week_view, "selected_time_changed");
    gtk_widget_queue_draw (week_view->main_canvas);
}

static void
week_view_cursor_key_down (EWeekView *week_view)
{
    if (week_view->selection_start_day == -1)
        return;

    week_view->selection_start_day++;

    if (week_view->selection_start_day > 6) {
        e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_DOWN);
        week_view->selection_start_day = 0;
    }

    week_view->selection_end_day = week_view->selection_start_day;
    g_signal_emit_by_name (week_view, "selected_time_changed");
    gtk_widget_queue_draw (week_view->main_canvas);
}

static void
week_view_cursor_key_left (EWeekView *week_view)
{
    if (week_view->selection_start_day == -1)
        return;

    week_view->selection_start_day = map_left[week_view->selection_start_day];
    week_view->selection_end_day = week_view->selection_start_day;
    g_signal_emit_by_name (week_view, "selected_time_changed");
    gtk_widget_queue_draw (week_view->main_canvas);
}

static void
week_view_cursor_key_right (EWeekView *week_view)
{
    if (week_view->selection_start_day == -1)
        return;

    week_view->selection_start_day = map_right[week_view->selection_start_day];
    week_view->selection_end_day = week_view->selection_start_day;
    g_signal_emit_by_name (week_view, "selected_time_changed");
    gtk_widget_queue_draw (week_view->main_canvas);
}

static void
e_week_view_class_init (EWeekViewClass *class)
{
    GObjectClass *object_class;
    GtkWidgetClass *widget_class;
    ECalendarViewClass *view_class;

    g_type_class_add_private (class, sizeof (EWeekViewPrivate));

    object_class = G_OBJECT_CLASS (class);
    object_class->set_property = week_view_set_property;
    object_class->get_property = week_view_get_property;
    object_class->dispose = week_view_dispose;
    object_class->constructed = week_view_constructed;

    widget_class = GTK_WIDGET_CLASS (class);
    widget_class->realize = week_view_realize;
    widget_class->unrealize = week_view_unrealize;
    widget_class->style_set = week_view_style_set;
    widget_class->size_allocate = week_view_size_allocate;
    widget_class->focus_in_event = week_view_focus_in;
    widget_class->focus_out_event = week_view_focus_out;
    widget_class->key_press_event = week_view_key_press;
    widget_class->focus = week_view_focus;
    widget_class->popup_menu = week_view_popup_menu;

    view_class = E_CALENDAR_VIEW_CLASS (class);
    view_class->get_selected_events = week_view_get_selected_events;
    view_class->get_selected_time_range = week_view_get_selected_time_range;
    view_class->set_selected_time_range = week_view_set_selected_time_range;
    view_class->get_visible_time_range = week_view_get_visible_time_range;
    view_class->paste_text = week_view_paste_text;

    class->cursor_key_up = week_view_cursor_key_up;
    class->cursor_key_down = week_view_cursor_key_down;
    class->cursor_key_left = week_view_cursor_key_left;
    class->cursor_key_right = week_view_cursor_key_right;

    /* XXX This property really belongs in EMonthView,
     *     but too much drawing code is tied to it. */
    g_object_class_install_property (
        object_class,
        PROP_COMPRESS_WEEKEND,
        g_param_spec_boolean (
            "compress-weekend",
            "Compress Weekend",
            NULL,
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_SHOW_EVENT_END_TIMES,
        g_param_spec_boolean (
            "show-event-end-times",
            "Show Event End Times",
            NULL,
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_override_property (
        object_class,
        PROP_IS_EDITING,
        "is-editing");

    /* init the accessibility support for e_week_view */
    e_week_view_a11y_init ();
}

static void
e_week_view_init (EWeekView *week_view)
{
    GnomeCanvasGroup *canvas_group;
    GtkAdjustment *adjustment;
    GdkPixbuf *pixbuf;
    gint i;

    week_view->priv = E_WEEK_VIEW_GET_PRIVATE (week_view);
    week_view->priv->weeks_shown = 6;
    week_view->priv->compress_weekend = TRUE;
    week_view->priv->show_event_end_times = TRUE;
    week_view->priv->update_base_date = TRUE;
    week_view->priv->display_start_day = G_DATE_MONDAY;

    gtk_widget_set_can_focus (GTK_WIDGET (week_view), TRUE);

    week_view->event_destroyed = FALSE;
    week_view->events = g_array_new (
        FALSE, FALSE,
        sizeof (EWeekViewEvent));
    week_view->events_sorted = TRUE;
    week_view->events_need_layout = FALSE;
    week_view->events_need_reshape = FALSE;

    week_view->layout_timeout_id = 0;

    week_view->spans = NULL;

    week_view->month_scroll_by_week = FALSE;
    week_view->scroll_by_week_notif_id = 0;
    week_view->rows = 6;
    week_view->columns = 2;

    g_date_clear (&week_view->base_date, 1);
    g_date_clear (&week_view->priv->first_day_shown, 1);

    week_view->row_height = 10;
    week_view->rows_per_cell = 1;

    week_view->selection_start_day = -1;
    week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_NONE;

    week_view->pressed_event_num = -1;
    week_view->editing_event_num = -1;

    week_view->last_edited_comp_string = NULL;

    /* Create the small font. */
    week_view->use_small_font = TRUE;

    week_view->small_font_desc =
        pango_font_description_copy (gtk_widget_get_style (GTK_WIDGET (week_view))->font_desc);
    pango_font_description_set_size (
        week_view->small_font_desc,
        E_WEEK_VIEW_SMALL_FONT_PTSIZE * PANGO_SCALE);

    /* String to use in 12-hour time format for times in the morning. */
    week_view->am_string = _("am");

    /* String to use in 12-hour time format for times in the afternoon. */
    week_view->pm_string = _("pm");

    week_view->bc_event_time = 0;
    week_view->before_click_dtstart = 0;
    week_view->before_click_dtend = 0;

    /*
     * Titles Canvas. Note that we don't show it is only shown in the
     * Month view.
     */
    week_view->titles_canvas = e_canvas_new ();
    gtk_table_attach (
        GTK_TABLE (week_view), week_view->titles_canvas,
        1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);

    canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->titles_canvas)->root);

    week_view->titles_canvas_item =
        gnome_canvas_item_new (
            canvas_group,
            e_week_view_titles_item_get_type (),
            "EWeekViewTitlesItem::week_view", week_view,
            NULL);

    /*
     * Main Canvas
     */
    week_view->main_canvas = e_canvas_new ();
    gtk_table_attach (
        GTK_TABLE (week_view), week_view->main_canvas,
        1, 2, 1, 2,
        GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
    gtk_widget_show (week_view->main_canvas);

    canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->main_canvas)->root);

    week_view->main_canvas_item =
        gnome_canvas_item_new (
            canvas_group,
            e_week_view_main_item_get_type (),
            "EWeekViewMainItem::week_view", week_view,
            NULL);

    g_signal_connect_after (
        week_view->main_canvas, "button_press_event",
        G_CALLBACK (e_week_view_on_button_press), week_view);
    g_signal_connect (
        week_view->main_canvas, "button_release_event",
        G_CALLBACK (e_week_view_on_button_release), week_view);
    g_signal_connect (
        week_view->main_canvas, "scroll_event",
        G_CALLBACK (e_week_view_on_scroll), week_view);
    g_signal_connect (
        week_view->main_canvas, "motion_notify_event",
        G_CALLBACK (e_week_view_on_motion), week_view);

    /* Create the buttons to jump to each days. */
    pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) jump_xpm);

    for (i = 0; i < E_WEEK_VIEW_MAX_WEEKS * 7; i++) {
        week_view->jump_buttons[i] = gnome_canvas_item_new
            (canvas_group,
             gnome_canvas_pixbuf_get_type (),
             "GnomeCanvasPixbuf::pixbuf", pixbuf,
             NULL);

        g_signal_connect (
            week_view->jump_buttons[i], "event",
            G_CALLBACK (e_week_view_on_jump_button_event), week_view);
    }
    week_view->focused_jump_button = E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS;

    g_object_unref (pixbuf);

    /*
     * Scrollbar.
     */
    adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, -52, 52, 1, 1, 1));

    week_view->vscrollbar = gtk_scrollbar_new (
        GTK_ORIENTATION_VERTICAL, adjustment);
    gtk_table_attach (
        GTK_TABLE (week_view), week_view->vscrollbar,
        2, 3, 1, 2, 0, GTK_EXPAND | GTK_FILL, 0, 0);
    gtk_widget_show (week_view->vscrollbar);

    /* Create the cursors. */
    week_view->normal_cursor = gdk_cursor_new (GDK_LEFT_PTR);
    week_view->move_cursor = gdk_cursor_new (GDK_FLEUR);
    week_view->resize_width_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
    week_view->last_cursor_set = NULL;

    week_view->requires_update = FALSE;
}

/**
 * e_week_view_new:
 * @Returns: a new #EWeekView.
 *
 * Creates a new #EWeekView.
 **/
ECalendarView *
e_week_view_new (ECalModel *model)
{
    ECalendarView *view;
    g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);

    view = g_object_new (E_TYPE_WEEK_VIEW, "model", model, NULL);

    g_signal_connect (
        model, "timezone_changed",
        G_CALLBACK (timezone_changed_cb), view);

    return view;
}

static GdkColor
color_inc (GdkColor c,
           gint amount)
{
    #define dec(x)              \
        if (x + amount >= 0     \
            && x + amount <= 0xFFFF)    \
            x += amount;        \
        else if (amount <= 0)       \
            x = 0;          \
        else                \
            x = 0xFFFF;

    dec (c.red);
    dec (c.green);
    dec (c.blue);

    #undef dec

    return c;
}

static void
e_week_view_set_colors (EWeekView *week_view,
                        GtkWidget *widget)
{
    GtkStyle *style;

    style = gtk_widget_get_style (widget);

    week_view->colors[E_WEEK_VIEW_COLOR_EVEN_MONTHS] = style->base[GTK_STATE_INSENSITIVE];
    week_view->colors[E_WEEK_VIEW_COLOR_ODD_MONTHS] = style->base[GTK_STATE_NORMAL];
    week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND] = style->base[GTK_STATE_NORMAL];
    week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BORDER] = style->dark[GTK_STATE_NORMAL];
    week_view->colors[E_WEEK_VIEW_COLOR_EVENT_TEXT] = style->text[GTK_STATE_NORMAL];
    week_view->colors[E_WEEK_VIEW_COLOR_GRID] = style->dark[GTK_STATE_NORMAL];
    week_view->colors[E_WEEK_VIEW_COLOR_SELECTED] = style->base[GTK_STATE_SELECTED];
    week_view->colors[E_WEEK_VIEW_COLOR_SELECTED_UNFOCUSSED] = style->bg[GTK_STATE_SELECTED];
    week_view->colors[E_WEEK_VIEW_COLOR_DATES] = style->text[GTK_STATE_NORMAL];
    week_view->colors[E_WEEK_VIEW_COLOR_DATES_SELECTED] = style->text[GTK_STATE_SELECTED];
    week_view->colors[E_WEEK_VIEW_COLOR_TODAY] = style->base[GTK_STATE_SELECTED];
    week_view->colors[E_WEEK_VIEW_COLOR_TODAY_BACKGROUND] = get_today_background (week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND]);
    week_view->colors[E_WEEK_VIEW_COLOR_MONTH_NONWORKING_DAY] = color_inc (week_view->colors[E_WEEK_VIEW_COLOR_EVEN_MONTHS], -0x0A0A);
}

static GdkColor
e_week_view_get_text_color (EWeekView *week_view,
                            EWeekViewEvent *event,
                            GtkWidget *widget)
{
    GtkStyle *style;
    GdkColor bg_color;
    guint16 red, green, blue;
    gdouble cc = 65535.0;

    red = week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND].red;
    green = week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND].green;
    blue = week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND].blue;

    if (is_comp_data_valid (event) && gdk_color_parse (e_cal_model_get_color_for_component (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), event->comp_data),
         &bg_color)) {
        red = bg_color.red;
        green = bg_color.green;
        blue = bg_color.blue;
    }

    style = gtk_widget_get_style (widget);

    if ((red / cc > 0.7) || (green / cc > 0.7) || (blue / cc > 0.7))
        return style->black;
    else
        return style->white;
}

static void
e_week_view_recalc_cell_sizes (EWeekView *week_view)
{
    gfloat canvas_width, canvas_height, offset;
    gint row, col;
    GtkAllocation allocation;
    GtkWidget *widget;
    GtkStyle *style;
    gint width, height, time_width;
    PangoFontDescription *font_desc;
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;

    if (e_week_view_get_multi_week_view (week_view)) {
        week_view->rows =
            e_week_view_get_weeks_shown (week_view) * 2;
        week_view->columns =
            e_week_view_get_compress_weekend (week_view) ? 6 : 7;
    } else {
        week_view->rows = 6;
        week_view->columns = 2;
    }

    gtk_widget_get_allocation (week_view->main_canvas, &allocation);

    /* Calculate the column sizes, using floating point so that pixels
     * get divided evenly. Note that we use one more element than the
     * number of columns, to make it easy to get the column widths.
     * We also add one to the width so that the right border of the last
     * column is off the edge of the displayed area. */
    canvas_width = allocation.width + 1;
    canvas_width /= week_view->columns;
    offset = 0;
    for (col = 0; col <= week_view->columns; col++) {
        week_view->col_offsets[col] = floor (offset + 0.5);
        offset += canvas_width;
    }

    /* Calculate the cell widths based on the offsets. */
    for (col = 0; col < week_view->columns; col++) {
        week_view->col_widths[col] = week_view->col_offsets[col + 1]
            - week_view->col_offsets[col];
    }

    /* Now do the same for the row heights. */
    canvas_height = allocation.height + 1;
    canvas_height /= week_view->rows;
    offset = 0;
    for (row = 0; row <= week_view->rows; row++) {
        week_view->row_offsets[row] = floor (offset + 0.5);
        offset += canvas_height;
    }

    /* Calculate the cell heights based on the offsets. */
    for (row = 0; row < week_view->rows; row++) {
        week_view->row_heights[row] = week_view->row_offsets[row + 1]
            - week_view->row_offsets[row];
    }

    /* If the font hasn't been set yet just return. */
    widget = GTK_WIDGET (week_view);
    style = gtk_widget_get_style (widget);
    if (!style)
        return;
    font_desc = style->font_desc;
    if (!font_desc)
        return;

    pango_context = gtk_widget_get_pango_context (widget);
    font_metrics = pango_context_get_metrics (
        pango_context, font_desc,
        pango_context_get_language (pango_context));

    /* Calculate the number of rows of events in each cell, for the large
     * cells and the compressed weekend cells. */
    if (e_week_view_get_multi_week_view (week_view)) {
        week_view->events_y_offset = E_WEEK_VIEW_DATE_T_PAD
            + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics))
            + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
            + E_WEEK_VIEW_DATE_B_PAD;
    } else {
        week_view->events_y_offset = E_WEEK_VIEW_DATE_T_PAD
            + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics))
            + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
            + E_WEEK_VIEW_DATE_LINE_T_PAD + 1
            + E_WEEK_VIEW_DATE_LINE_B_PAD;
    }

    height = week_view->row_heights[0];
    week_view->rows_per_cell =
        (height * 2 - week_view->events_y_offset) /
        (week_view->row_height + E_WEEK_VIEW_EVENT_Y_SPACING);
    week_view->rows_per_cell = MIN (
        week_view->rows_per_cell,
        E_WEEK_VIEW_MAX_ROWS_PER_CELL);

    week_view->rows_per_compressed_cell =
        (height - week_view->events_y_offset) /
        (week_view->row_height + E_WEEK_VIEW_EVENT_Y_SPACING);
    week_view->rows_per_compressed_cell = MIN (
        week_view->rows_per_compressed_cell,
        E_WEEK_VIEW_MAX_ROWS_PER_CELL);

    /* Determine which time format to use, based on the width of the cells.
     * We only allow the time to take up about half of the width. */
    width = week_view->col_widths[0];

    time_width = e_week_view_get_time_string_width (week_view);

    week_view->time_format = E_WEEK_VIEW_TIME_NONE;
    if (week_view->use_small_font && week_view->small_font_desc) {
        if (e_week_view_get_show_event_end_times (week_view) &&
            width / 2 > time_width * 2 + E_WEEK_VIEW_EVENT_TIME_SPACING)
            week_view->time_format = E_WEEK_VIEW_TIME_BOTH_SMALL_MIN;
        else if (width / 2 > time_width)
            week_view->time_format = E_WEEK_VIEW_TIME_START_SMALL_MIN;
    } else {
        if (e_week_view_get_show_event_end_times (week_view) &&
            width / 2 > time_width * 2 + E_WEEK_VIEW_EVENT_TIME_SPACING)
            week_view->time_format = E_WEEK_VIEW_TIME_BOTH;
        else if (width / 2 > time_width)
            week_view->time_format = E_WEEK_VIEW_TIME_START;
    }

    pango_font_metrics_unref (font_metrics);
}

/**
 * e_week_view_get_next_tab_event
 * @week_view: the week_view widget operate on
 * @direction: GTK_DIR_TAB_BACKWARD or GTK_DIR_TAB_FORWARD.
 * @current_event_num and @current_span_num: current status.
 * @next_event_num: the event number focus should go next.
 *                  -1 indicates focus should go to week_view widget.
 * @next_span_num: always return 0.
 **/
static gboolean
e_week_view_get_next_tab_event (EWeekView *week_view,
                                GtkDirectionType direction,
                                gint current_event_num,
                                gint current_span_num,
                                gint *next_event_num,
                                gint *next_span_num)
{
    gint event_num;

    g_return_val_if_fail (week_view != NULL, FALSE);
    g_return_val_if_fail (next_event_num != NULL, FALSE);
    g_return_val_if_fail (next_span_num != NULL, FALSE);

    if (week_view->events->len <= 0)
        return FALSE;

    /* we only tab through events not spans */
    *next_span_num = 0;

    switch (direction) {
    case GTK_DIR_TAB_BACKWARD:
        event_num = current_event_num - 1;
        break;
    case GTK_DIR_TAB_FORWARD:
        event_num = current_event_num + 1;
        break;
    default:
        return FALSE;
    }

    if (event_num == -1)
        /* backward, out of event range, go to week view widget
         */
        *next_event_num = -1;
    else if (event_num < -1)
        /* backward from week_view, go to the last event
         */
        *next_event_num = week_view->events->len - 1;
    else if (event_num >= week_view->events->len)
        /* forward, out of event range, go to week view widget
         */
        *next_event_num = -1;
    else
        *next_event_num = event_num;
    return TRUE;
}

/* Restarts a query for the week view */
static void
e_week_view_update_query (EWeekView *week_view)
{
    gint rows, r;

    if (!E_CALENDAR_VIEW (week_view)->in_focus) {
        e_week_view_free_events (week_view);
        week_view->requires_update = TRUE;
        return;
    }

    gtk_widget_queue_draw (week_view->main_canvas);
    e_week_view_free_events (week_view);
    e_week_view_queue_layout (week_view);

    rows = e_table_model_row_count (E_TABLE_MODEL (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view))));
    for (r = 0; r < rows; r++) {
        ECalModelComponent *comp_data;

        comp_data = e_cal_model_get_component_at (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), r);
        if (comp_data == NULL) {
            g_warning ("comp_data is NULL\n");
            continue;
        }
        week_view_process_component (week_view, comp_data);
    }
}

void
e_week_view_set_selected_time_range_visible (EWeekView *week_view,
                                             time_t start_time,
                                             time_t end_time)
{
    GDate *first_day_shown;
    GDate date, end_date;
    gint num_days;

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    first_day_shown = &week_view->priv->first_day_shown;

    time_to_gdate_with_zone (&date, start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));

    /* Set the selection to the given days. */
    week_view->selection_start_day =
        g_date_get_julian (&date) -
        g_date_get_julian (first_day_shown);
    if (end_time == start_time
        || end_time <= time_add_day_with_zone (start_time, 1,
                           e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))))
        week_view->selection_end_day = week_view->selection_start_day;
    else {
        time_to_gdate_with_zone (&end_date, end_time - 60, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
        week_view->selection_end_day =
            g_date_get_julian (&end_date) -
            g_date_get_julian (first_day_shown);
    }

    /* Make sure the selection is valid. */
    num_days = (e_week_view_get_weeks_shown (week_view) * 7) - 1;
    week_view->selection_start_day = CLAMP (
        week_view->selection_start_day, 0, num_days);
    week_view->selection_end_day = CLAMP (
        week_view->selection_end_day,
        week_view->selection_start_day,
        num_days);

    gtk_widget_queue_draw (week_view->main_canvas);
}

/* Note that the returned date may be invalid if no date has been set yet. */
void
e_week_view_get_first_day_shown (EWeekView *week_view,
                                 GDate *date)
{
    *date = week_view->priv->first_day_shown;
}

/* This sets the first day shown in the view. It will be rounded down to the
 * nearest week. */
void
e_week_view_set_first_day_shown (EWeekView *week_view,
                                 GDate *date)
{
    GDate base_date;
    GDateWeekday weekday;
    GDateWeekday display_start_day;
    guint day_offset;
    gint num_days;
    gboolean update_adjustment_value = FALSE;
    guint32 old_selection_start_julian = 0, old_selection_end_julian = 0;
    struct icaltimetype start_tt = icaltime_null_time ();
    time_t start_time;

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    /* Calculate the old selection range. */
    if (week_view->selection_start_day != -1) {
        old_selection_start_julian =
            g_date_get_julian (&week_view->base_date)
            + week_view->selection_start_day;
        old_selection_end_julian =
            g_date_get_julian (&week_view->base_date)
            + week_view->selection_end_day;
    }

    weekday = g_date_get_weekday (date);
    display_start_day = e_week_view_get_display_start_day (week_view);

    /* Convert it to an offset from the start of the display. */
    day_offset = e_weekday_get_days_between (display_start_day, weekday);

    /* Calculate the base date, i.e. the first day shown when the
     * scrollbar adjustment value is 0. */
    base_date = *date;
    g_date_subtract_days (&base_date, day_offset);

    /* See if we need to update the base date. */
    if (!g_date_valid (&week_view->base_date)
        || g_date_compare (&week_view->base_date, &base_date)) {
        week_view->base_date = base_date;
        update_adjustment_value = TRUE;
    }

    /* See if we need to update the first day shown. */
    if (!g_date_valid (&week_view->priv->first_day_shown)
        || g_date_compare (&week_view->priv->first_day_shown, &base_date)) {
        week_view->priv->first_day_shown = base_date;

        start_tt.year = g_date_get_year (&base_date);
        start_tt.month = g_date_get_month (&base_date);
        start_tt.day = g_date_get_day (&base_date);

        start_time = icaltime_as_timet_with_zone (
            start_tt,
            e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));

        e_week_view_recalc_day_starts (week_view, start_time);
        e_week_view_update_query (week_view);
    }

    /* Try to keep the previous selection, but if it is no longer shown
     * just select the first day. */
    if (week_view->selection_start_day != -1) {
        week_view->selection_start_day = old_selection_start_julian
            - g_date_get_julian (&base_date);
        week_view->selection_end_day = old_selection_end_julian
            - g_date_get_julian (&base_date);

        /* Make sure the selection is valid. */
        num_days = (e_week_view_get_weeks_shown (week_view) * 7) - 1;
        week_view->selection_start_day = CLAMP (
            week_view->selection_start_day, 0, num_days);
        week_view->selection_end_day = CLAMP (
            week_view->selection_end_day,
            week_view->selection_start_day,
            num_days);
    }

    /* Reset the adjustment value to 0 if the base address has changed.
     * Note that we do this after updating first_day_shown so that our
     * signal handler will not try to reload the events. */
    if (update_adjustment_value) {
        GtkRange *range;
        GtkAdjustment *adjustment;

        range = GTK_RANGE (week_view->vscrollbar);
        adjustment = gtk_range_get_adjustment (range);
        gtk_adjustment_set_value (adjustment, 0);
    }

    e_week_view_update_query (week_view);
    gtk_widget_queue_draw (week_view->main_canvas);
}

GDateWeekday
e_week_view_get_display_start_day (EWeekView *week_view)
{
    g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), G_DATE_BAD_WEEKDAY);

    return week_view->priv->display_start_day;
}

/* Recalculates the time_t corresponding to the start of each day. */
static void
e_week_view_recalc_day_starts (EWeekView *week_view,
                               time_t lower)
{
    gint num_days, day;
    time_t tmp_time;

    num_days = e_week_view_get_weeks_shown (week_view) * 7;

    tmp_time = lower;
    week_view->day_starts[0] = tmp_time;
    for (day = 1; day <= num_days; day++) {
        tmp_time = time_add_day_with_zone (
            tmp_time, 1,
            e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
        week_view->day_starts[day] = tmp_time;
    }
}

gboolean
e_week_view_get_multi_week_view (EWeekView *week_view)
{
    g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);

    return week_view->priv->multi_week_view;
}

void
e_week_view_set_multi_week_view (EWeekView *week_view,
                                 gboolean multi_week_view)
{
    GtkRange *range;
    GtkAdjustment *adjustment;
    gint page_increment, page_size;

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    if (multi_week_view == week_view->priv->multi_week_view)
        return;

    week_view->priv->multi_week_view = multi_week_view;

    if (multi_week_view) {
        gtk_widget_show (week_view->titles_canvas);
        week_view->month_scroll_by_week = calendar_config_get_month_scroll_by_week ();

        calendar_config_add_notification_month_scroll_by_week (
            month_scroll_by_week_changed_cb, week_view);

        if (week_view->month_scroll_by_week) {
            page_increment = 1;
            page_size = 1;
        } else {
            page_increment = 4;
            page_size = 5;
        }
    } else {
        gtk_widget_hide (week_view->titles_canvas);
        page_increment = page_size = 1;

        if (week_view->scroll_by_week_notif_id) {
            calendar_config_remove_notification (
                month_scroll_by_week_changed_cb, week_view);
            week_view->scroll_by_week_notif_id = 0;
        }
    }

    range = GTK_RANGE (week_view->vscrollbar);
    adjustment = gtk_range_get_adjustment (range);
    gtk_adjustment_set_page_increment (adjustment, page_increment);
    gtk_adjustment_set_page_size (adjustment, page_size);

    e_week_view_recalc_display_start_day (week_view);
    e_week_view_recalc_cell_sizes (week_view);

    if (g_date_valid (&week_view->priv->first_day_shown))
        e_week_view_set_first_day_shown (
            week_view,
            &week_view->priv->first_day_shown);
}

gboolean
e_week_view_get_update_base_date (EWeekView *week_view)
{
    g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);

    return week_view->priv->update_base_date;
}

void
e_week_view_set_update_base_date (EWeekView *week_view,
                                  gboolean update_base_date)
{
    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    week_view->priv->update_base_date = update_base_date;
}

gint
e_week_view_get_weeks_shown (EWeekView *week_view)
{
    g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), 1);

    /* Give a sensible answer for single-week view. */
    if (!e_week_view_get_multi_week_view (week_view))
        return 1;

    return week_view->priv->weeks_shown;
}

void
e_week_view_set_weeks_shown (EWeekView *week_view,
                             gint weeks_shown)
{
    GtkRange *range;
    GtkAdjustment *adjustment;
    gint page_increment, page_size;

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    weeks_shown = MIN (weeks_shown, E_WEEK_VIEW_MAX_WEEKS);

    if (weeks_shown == week_view->priv->weeks_shown)
        return;

    week_view->priv->weeks_shown = weeks_shown;

    if (e_week_view_get_multi_week_view (week_view)) {
        if (week_view->month_scroll_by_week) {
            page_increment = 1;
            page_size = 1;
        } else {
            page_increment = 4;
            page_size = 5;
        }

        range = GTK_RANGE (week_view->vscrollbar);
        adjustment = gtk_range_get_adjustment (range);
        gtk_adjustment_set_page_increment (adjustment, page_increment);
        gtk_adjustment_set_page_size (adjustment, page_size);

        e_week_view_recalc_cell_sizes (week_view);

        if (g_date_valid (&week_view->priv->first_day_shown))
            e_week_view_set_first_day_shown (
                week_view,
                &week_view->priv->first_day_shown);

        e_week_view_update_query (week_view);
    }
}

gboolean
e_week_view_get_compress_weekend (EWeekView *week_view)
{
    g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);

    return week_view->priv->compress_weekend;
}

void
e_week_view_set_compress_weekend (EWeekView *week_view,
                                  gboolean compress_weekend)
{
    gboolean need_reload = FALSE;

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    if (compress_weekend == week_view->priv->compress_weekend)
        return;

    week_view->priv->compress_weekend = compress_weekend;

    /* The option only affects the month view. */
    if (!e_week_view_get_multi_week_view (week_view))
        return;

    e_week_view_recalc_cell_sizes (week_view);

    need_reload = e_week_view_recalc_display_start_day (week_view);

    /* If the display_start_day has changed we need to recalculate the
     * date range shown and reload all events, otherwise we only need to
     * do a reshape. */
    if (need_reload) {
        /* Recalculate the days shown and reload if necessary. */
        if (g_date_valid (&week_view->priv->first_day_shown))
            e_week_view_set_first_day_shown (
                week_view,
                &week_view->priv->first_day_shown);
    } else {
        week_view->events_need_reshape = TRUE;
        e_week_view_check_layout (week_view);
    }

    gtk_widget_queue_draw (week_view->titles_canvas);
    gtk_widget_queue_draw (week_view->main_canvas);

    g_object_notify (G_OBJECT (week_view), "compress-weekend");
}

/* Whether we display event end times. */
gboolean
e_week_view_get_show_event_end_times (EWeekView *week_view)
{
    g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), TRUE);

    return week_view->priv->show_event_end_times;
}

void
e_week_view_set_show_event_end_times (EWeekView *week_view,
                                      gboolean show_event_end_times)
{
    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    if (show_event_end_times == week_view->priv->show_event_end_times)
        return;

    week_view->priv->show_event_end_times = show_event_end_times;
    e_week_view_recalc_cell_sizes (week_view);
    week_view->events_need_reshape = TRUE;
    e_week_view_check_layout (week_view);

    gtk_widget_queue_draw (week_view->titles_canvas);
    gtk_widget_queue_draw (week_view->main_canvas);

    g_object_notify (G_OBJECT (week_view), "show-event-end-times");
}

static gboolean
e_week_view_recalc_display_start_day (EWeekView *week_view)
{
    ECalModel *model;
    GDateWeekday week_start_day;
    GDateWeekday display_start_day;
    gboolean changed;

    model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view));
    week_start_day = e_cal_model_get_week_start_day (model);

    /* The display start day defaults to week_start_day, but we have
     * to use Saturday if the weekend is compressed and week_start_day
     * is Sunday. */
    display_start_day = week_start_day;

    if (display_start_day == G_DATE_SUNDAY) {
        if (!e_week_view_get_multi_week_view (week_view))
            display_start_day = G_DATE_SATURDAY;

        if (e_week_view_get_compress_weekend (week_view))
            display_start_day = G_DATE_SATURDAY;
    }

    changed = (display_start_day != week_view->priv->display_start_day);

    week_view->priv->display_start_day = display_start_day;

    return changed;
}

/* Checks if the users participation status is NEEDS-ACTION and shows the summary as bold text */
static void
set_text_as_bold (EWeekViewEvent *event,
                  EWeekViewEventSpan *span,
                  ESourceRegistry *registry)
{
    ECalComponent *comp;
    GSList *attendees = NULL, *l;
    gchar *address;
    ECalComponentAttendee *at = NULL;

    if (!is_comp_data_valid (event))
        return;

    comp = e_cal_component_new ();
    e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp));
    address = itip_get_comp_attendee (
        registry, comp, event->comp_data->client);
    e_cal_component_get_attendee_list (comp, &attendees);
    for (l = attendees; l; l = l->next) {
        ECalComponentAttendee *attendee = l->data;

        if ((g_str_equal (itip_strip_mailto (attendee->value), address))
         || (attendee->sentby && g_str_equal (itip_strip_mailto (attendee->sentby), address))) {
            at = attendee;
            break;
        }
    }

    /* The attendee has not yet accepted the meeting, display the summary as bolded.
     * If the attendee is not present, it might have come through a mailing list.
     * In that case, we never show the meeting as bold even if it is unaccepted. */
    if (at && (at->status == ICAL_PARTSTAT_NEEDSACTION))
        gnome_canvas_item_set (span->text_item, "bold", TRUE, NULL);

    e_cal_component_free_attendee_list (attendees);
    g_free (address);
    g_object_unref (comp);
}

/* This calls a given function for each event instance that matches the given
 * uid. Note that it is safe for the callback to remove the event (since we
 * step backwards through the arrays). */
static void
e_week_view_foreach_event_with_uid (EWeekView *week_view,
                                    const gchar *uid,
                                    EWeekViewForeachEventCallback callback,
                                    gpointer data)
{
    EWeekViewEvent *event;
    gint event_num;

    for (event_num = week_view->events->len - 1;
         event_num >= 0;
         event_num--) {
        const gchar *u;

        event = &g_array_index (week_view->events, EWeekViewEvent,
                    event_num);

        if (!is_comp_data_valid (event))
            continue;

        u = icalcomponent_get_uid (event->comp_data->icalcomp);
        if (u && !strcmp (uid, u)) {
            if (!(*callback) (week_view, event_num, data))
                return;
        }
    }
}

static gboolean
e_week_view_remove_event_cb (EWeekView *week_view,
                             gint event_num,
                             gpointer data)
{
    EWeekViewEvent *event;
    EWeekViewEventSpan *span;
    gint span_num;

    if (!is_array_index_in_bounds (week_view->events, event_num))
        return TRUE;

    event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
    if (!event)
        return TRUE;

    /* If we were editing this event, set editing_event_num to -1 so
     * on_editing_stopped doesn't try to update the event. */
    if (week_view->editing_event_num == event_num) {
        week_view->editing_event_num = -1;
        g_object_notify (G_OBJECT (week_view), "is-editing");
    }

    if (week_view->popup_event_num == event_num)
        week_view->popup_event_num = -1;

    if (is_comp_data_valid (event))
        g_object_unref (event->comp_data);
    event->comp_data = NULL;

    if (week_view->spans) {
        /* We leave the span elements in the array, but set the canvas item
         * pointers to NULL. */
        for (span_num = 0; span_num < event->num_spans; span_num++) {
            if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
                break;

            span = &g_array_index (week_view->spans, EWeekViewEventSpan,
                           event->spans_index + span_num);

            if (span->text_item) {
                g_object_run_dispose (G_OBJECT (span->text_item));
                span->text_item = NULL;
            }
            if (span->background_item) {
                g_object_run_dispose (G_OBJECT (span->background_item));
                span->background_item = NULL;
            }
        }

        /* Update event_num numbers for already created spans with event_num higher than our event_num */
        for (span_num = 0; span_num < week_view->spans->len; span_num++) {
            span = &g_array_index (week_view->spans, EWeekViewEventSpan, span_num);

            if (span && span->background_item && E_IS_WEEK_VIEW_EVENT_ITEM (span->background_item)) {
                EWeekViewEventItem *wveitem = E_WEEK_VIEW_EVENT_ITEM (span->background_item);
                gint wveitem_event_num;

                wveitem_event_num =
                    e_week_view_event_item_get_event_num (wveitem);

                if (wveitem_event_num > event_num)
                    e_week_view_event_item_set_event_num (
                        wveitem, wveitem_event_num - 1);
            }
        }
    }

    g_array_remove_index (week_view->events, event_num);

    week_view->events_need_layout = TRUE;

    return TRUE;
}

void
e_week_view_get_day_position (EWeekView *week_view,
                              gint day,
                              gint *day_x,
                              gint *day_y,
                              gint *day_w,
                              gint *day_h)
{
    gint cell_x, cell_y, cell_h;

    e_week_view_layout_get_day_position (
        day,
        e_week_view_get_multi_week_view (week_view),
        e_week_view_get_weeks_shown (week_view),
        e_week_view_get_display_start_day (week_view),
        e_week_view_get_compress_weekend (week_view),
        &cell_x, &cell_y, &cell_h);

    *day_x = week_view->col_offsets[cell_x];
    *day_y = week_view->row_offsets[cell_y];

    *day_w = week_view->col_widths[cell_x];
    *day_h = week_view->row_heights[cell_y];

    while (cell_h > 1) {
        *day_h += week_view->row_heights[cell_y + 1];
        cell_h--;
        cell_y++;
    }
}

/* Returns the bounding box for a span of an event. Usually this can easily
 * be determined by the start & end days and row of the span, which are set in
 * e_week_view_layout_event (). Though we need a special case for the weekends
 * when they are compressed, since the span may not fit.
 * The bounding box includes the entire width of the days in the view (but
 * not the vertical line down the right of the last day), though the displayed
 * event doesn't normally extend to the edges of the day.
 * It returns FALSE if the span isn't visible. */
gboolean
e_week_view_get_span_position (EWeekView *week_view,
                               gint event_num,
                               gint span_num,
                               gint *span_x,
                               gint *span_y,
                               gint *span_w)
{
    EWeekViewEvent *event;
    EWeekViewEventSpan *span;
    gint num_days;
    gint start_x, start_y, start_w, start_h;
    gint end_x, end_y, end_w, end_h;

    g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);
    g_return_val_if_fail (event_num < week_view->events->len, FALSE);

    event = &g_array_index (week_view->events, EWeekViewEvent, event_num);

    g_return_val_if_fail (span_num < event->num_spans, FALSE);

    if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
        return FALSE;

    span = &g_array_index (week_view->spans, EWeekViewEventSpan,
                   event->spans_index + span_num);

    if (!e_week_view_layout_get_span_position (
        event, span,
        week_view->rows_per_cell,
        week_view->rows_per_compressed_cell,
        e_week_view_get_display_start_day (week_view),
        e_week_view_get_multi_week_view (week_view),
        e_week_view_get_compress_weekend (week_view),
        &num_days)) {
        return FALSE;
    }

    e_week_view_get_day_position (
        week_view, span->start_day,
        &start_x, &start_y, &start_w, &start_h);
    *span_y = start_y + week_view->events_y_offset
        + span->row * (week_view->row_height
                   + E_WEEK_VIEW_EVENT_Y_SPACING);
    if (num_days == 1) {
        *span_x = start_x;
        *span_w = start_w - 1;
    } else {
        e_week_view_get_day_position (
            week_view,
            span->start_day + num_days - 1,
            &end_x, &end_y, &end_w, &end_h);
        *span_x = start_x;
        *span_w = end_x + end_w - start_x - 1;
    }

    return TRUE;
}

static gboolean
ewv_pass_gdkevent_to_etext (EWeekView *week_view,
                            GdkEvent *gevent)
{
    g_return_val_if_fail (week_view != NULL, FALSE);
    g_return_val_if_fail (gevent != NULL, FALSE);

    if (week_view->editing_event_num != -1 && week_view->editing_span_num != -1) {
        EWeekViewEvent *event;
        EWeekViewEventSpan *span;

        if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num))
            return FALSE;

        event = &g_array_index (week_view->events, EWeekViewEvent, week_view->editing_event_num);

        if (!is_array_index_in_bounds (week_view->spans, event->spans_index + week_view->editing_span_num))
            return FALSE;

        span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + week_view->editing_span_num);

        if (span->text_item && E_IS_TEXT (span->text_item)) {
            GNOME_CANVAS_ITEM_GET_CLASS (span->text_item)->event (span->text_item, gevent);
            return TRUE;
        }
    }

    return FALSE;
}

static gboolean
e_week_view_on_button_press (GtkWidget *widget,
                             GdkEvent *button_event,
                             EWeekView *week_view)
{
    guint event_button = 0;
    gdouble event_x_win = 0;
    gdouble event_y_win = 0;
    gint x, y, day;

    gdk_event_get_button (button_event, &event_button);
    gdk_event_get_coords (button_event, &event_x_win, &event_y_win);

    /* Convert the mouse position to a week & day. */
    x = (gint) event_x_win;
    y = (gint) event_y_win;
    day = e_week_view_convert_position_to_day (week_view, x, y);
    if (day == -1)
        return FALSE;

    if (ewv_pass_gdkevent_to_etext (week_view, button_event))
        return TRUE;

    /* If an event is pressed just return. */
    if (week_view->pressed_event_num != -1)
        return FALSE;

    if (event_button == 1 && button_event->type == GDK_2BUTTON_PRESS) {
        time_t dtstart, dtend;

        e_calendar_view_get_selected_time_range ((ECalendarView *) week_view, &dtstart, &dtend);
        if (dtstart < week_view->before_click_dtend && dtend > week_view->before_click_dtstart) {
            e_calendar_view_set_selected_time_range (
                E_CALENDAR_VIEW (week_view),
                week_view->before_click_dtstart,
                week_view->before_click_dtend);
        }
        e_calendar_view_new_appointment_full (E_CALENDAR_VIEW (week_view), FALSE, FALSE, calendar_config_get_prefer_meeting ());
        return TRUE;
    }

    if (event_button == 1) {
        GdkGrabStatus grab_status;
        GdkWindow *window;
        GdkDevice *event_device;
        guint32 event_time;

        /* Start the selection drag. */
        if (!gtk_widget_has_focus (GTK_WIDGET (week_view)) &&  !gtk_widget_has_focus (GTK_WIDGET (week_view->main_canvas)))
            gtk_widget_grab_focus (GTK_WIDGET (week_view));

        window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));

        event_device = gdk_event_get_device (button_event);
        event_time = gdk_event_get_time (button_event);

        grab_status = gdk_device_grab (
            event_device,
            window,
            GDK_OWNERSHIP_NONE,
            FALSE,
            GDK_POINTER_MOTION_MASK |
            GDK_BUTTON_RELEASE_MASK,
            NULL,
            event_time);

        if (grab_status == GDK_GRAB_SUCCESS) {
            if (event_time - week_view->bc_event_time > 250)
                e_calendar_view_get_selected_time_range (
                    E_CALENDAR_VIEW (week_view),
                    &week_view->before_click_dtstart,
                    &week_view->before_click_dtend);
            week_view->bc_event_time = event_time;
            week_view->selection_start_day = day;
            week_view->selection_end_day = day;
            week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_END;
            g_signal_emit_by_name (week_view, "selected_time_changed");

            /* FIXME: Optimise? */
            gtk_widget_queue_draw (week_view->main_canvas);
        }
    } else if (event_button == 3) {
        if (!gtk_widget_has_focus (GTK_WIDGET (week_view)))
            gtk_widget_grab_focus (GTK_WIDGET (week_view));

        if (day < week_view->selection_start_day || day > week_view->selection_end_day) {
            week_view->selection_start_day = day;
            week_view->selection_end_day = day;
            week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_NONE;

            /* FIXME: Optimise? */
            gtk_widget_queue_draw (week_view->main_canvas);
        }

        e_week_view_show_popup_menu (week_view, button_event, -1);
    }

    return TRUE;
}

static gboolean
e_week_view_on_button_release (GtkWidget *widget,
                               GdkEvent *button_event,
                               EWeekView *week_view)
{
    GdkDevice *event_device;
    guint32 event_time;

    event_device = gdk_event_get_device (button_event);
    event_time = gdk_event_get_time (button_event);

    if (week_view->selection_drag_pos != E_WEEK_VIEW_DRAG_NONE) {
        week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_NONE;
        gdk_device_ungrab (event_device, event_time);
    } else {
        ewv_pass_gdkevent_to_etext (week_view, button_event);
    }

    return FALSE;
}

static gboolean
e_week_view_on_scroll (GtkWidget *widget,
                       GdkEventScroll *scroll,
                       EWeekView *week_view)
{
    GtkRange *range;
    GtkAdjustment *adjustment;
    gdouble page_increment;
    gdouble new_value;
    gdouble page_size;
    gdouble lower;
    gdouble upper;
    gdouble value;
    GtkWidget *tool_window = g_object_get_data (G_OBJECT (week_view), "tooltip-window");
    guint timeout;

    timeout = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (week_view), "tooltip-timeout"));
    if (timeout) {
        g_source_remove (timeout);
        g_object_set_data (G_OBJECT (week_view), "tooltip-timeout", NULL);
    }

    if (tool_window) {
        gtk_widget_destroy (tool_window);
        g_object_set_data (G_OBJECT (week_view), "tooltip-window", NULL);
    }

    range = GTK_RANGE (week_view->vscrollbar);
    adjustment = gtk_range_get_adjustment (range);

    page_increment = gtk_adjustment_get_page_increment (adjustment);
    page_size = gtk_adjustment_get_page_size (adjustment);
    lower = gtk_adjustment_get_lower (adjustment);
    upper = gtk_adjustment_get_upper (adjustment);
    value = gtk_adjustment_get_value (adjustment);

    switch (scroll->direction) {
        case GDK_SCROLL_UP:
            new_value = value - page_increment;
            break;
        case GDK_SCROLL_DOWN:
            new_value = value + page_increment;
            break;
        case GDK_SCROLL_SMOOTH:
            if (scroll->delta_y < -0.001 || scroll->delta_y > 0.001) {
                new_value = value + scroll->delta_y * page_increment;
                break;
            }
            return FALSE;
        default:
            return FALSE;
    }

    new_value = CLAMP (new_value, lower, upper - page_size);
    gtk_adjustment_set_value (adjustment, new_value);

    return TRUE;
}

static gboolean
e_week_view_on_motion (GtkWidget *widget,
                       GdkEventMotion *mevent,
                       EWeekView *week_view)
{
    gint x, y, day;

    /* Convert the mouse position to a week & day. */
    x = mevent->x;
    y = mevent->y;
    day = e_week_view_convert_position_to_day (week_view, x, y);
    if (day == -1)
        return FALSE;

    if (week_view->selection_drag_pos != E_WEEK_VIEW_DRAG_NONE) {
        e_week_view_update_selection (week_view, day);
        return TRUE;
    }

    ewv_pass_gdkevent_to_etext (week_view, (GdkEvent *) mevent);

    return FALSE;
}

/* Converts a position in the canvas window to a day offset from the first
 * day displayed. Returns -1 if the position is outside the grid. */
static gint
e_week_view_convert_position_to_day (EWeekView *week_view,
                                     gint x,
                                     gint y)
{
    GDateWeekday display_start_day;
    gint col, row, grid_x = -1, grid_y = -1, week, day;
    gint weekend_col;

    display_start_day = e_week_view_get_display_start_day (week_view);

    /* First we convert it to a grid position. */
    for (col = 0; col <= week_view->columns; col++) {
        if (x < week_view->col_offsets[col]) {
            grid_x = col - 1;
            break;
        }
    }

    for (row = 0; row <= week_view->rows; row++) {
        if (y < week_view->row_offsets[row]) {
            grid_y = row - 1;
            break;
        }
    }

    /* If the mouse is outside the grid return FALSE. */
    if (grid_x == -1 || grid_y == -1)
        return -1;

    /* Now convert the grid position to a week and day. */
    if (e_week_view_get_multi_week_view (week_view)) {
        week = grid_y / 2;
        day = grid_x;

        if (e_week_view_get_compress_weekend (week_view)) {
            weekend_col = e_weekday_get_days_between (
                display_start_day, G_DATE_SATURDAY);
            if (grid_x > weekend_col
                || (grid_x == weekend_col && grid_y % 2 == 1))
                day++;
        }
    } else {
        week = 0;

        for (day = 0; day < 7; day++) {
            gint day_x = 0, day_y = 0, rows = 0;
            e_week_view_layout_get_day_position (
                day, FALSE, 1,
                e_week_view_get_display_start_day (week_view),
                e_week_view_get_compress_weekend (week_view),
                &day_x, &day_y, &rows);

            if (grid_x == day_x && grid_y >= day_y && grid_y < day_y + rows)
                break;
        }

        if (day == 7)
            return -1;
    }

    return week * 7 + day;
}

static void
e_week_view_update_selection (EWeekView *week_view,
                              gint day)
{
    gint tmp_day;
    gboolean need_redraw = FALSE;

    if (week_view->selection_drag_pos == E_WEEK_VIEW_DRAG_START) {
        if (day != week_view->selection_start_day) {
            need_redraw = TRUE;
            week_view->selection_start_day = day;
        }
    } else {
        if (day != week_view->selection_end_day) {
            need_redraw = TRUE;
            week_view->selection_end_day = day;
        }
    }

    /* Switch the drag position if necessary. */
    if (week_view->selection_start_day > week_view->selection_end_day) {
        tmp_day = week_view->selection_start_day;
        week_view->selection_start_day = week_view->selection_end_day;
        week_view->selection_end_day = tmp_day;
        if (week_view->selection_drag_pos == E_WEEK_VIEW_DRAG_START)
            week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_END;
        else
            week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_START;
    }

    /* FIXME: Optimise? */
    if (need_redraw) {
        gtk_widget_queue_draw (week_view->main_canvas);
    }
}

static void
e_week_view_free_events (EWeekView *week_view)
{
    EWeekViewEvent *event;
    EWeekViewEventSpan *span;
    gint event_num, span_num, num_days, day;
    gboolean did_editing = week_view->editing_event_num != -1;

    /* Reset all our indices. */
    week_view->pressed_event_num = -1;
    week_view->pressed_span_num = -1;
    week_view->editing_event_num = -1;
    week_view->editing_span_num = -1;
    week_view->popup_event_num = -1;

    for (event_num = 0; event_num < week_view->events->len; event_num++) {
        event = &g_array_index (week_view->events, EWeekViewEvent,
                    event_num);

        if (is_comp_data_valid (event))
            g_object_unref (event->comp_data);
    }

    g_array_set_size (week_view->events, 0);

    /* Destroy all the old canvas items. */
    if (week_view->spans) {
        for (span_num = 0; span_num < week_view->spans->len;
             span_num++) {
            span = &g_array_index (week_view->spans,
                           EWeekViewEventSpan, span_num);
            if (span->background_item)
                g_object_run_dispose (G_OBJECT (span->background_item));
            if (span->text_item)
                g_object_run_dispose (G_OBJECT (span->text_item));
        }
        g_array_free (week_view->spans, TRUE);
        week_view->spans = NULL;
    }

    /* Clear the number of rows used per day. */
    num_days = e_week_view_get_weeks_shown (week_view) * 7;
    for (day = 0; day <= num_days; day++) {
        week_view->rows_per_day[day] = 0;
    }

    /* Hide all the jump buttons. */
    for (day = 0; day < E_WEEK_VIEW_MAX_WEEKS * 7; day++) {
        gnome_canvas_item_hide (week_view->jump_buttons[day]);
    }

    if (did_editing)
        g_object_notify (G_OBJECT (week_view), "is-editing");
}

/* This adds one event to the view, adding it to the appropriate array. */
static gboolean
e_week_view_add_event (ECalComponent *comp,
                       time_t start,
                       time_t end,
                       gboolean prepend,
                       gpointer data)

{
    AddEventData *add_event_data;
    EWeekViewEvent event;
    gint num_days;
    struct icaltimetype start_tt, end_tt;

    add_event_data = data;

    /* Check that the event times are valid. */
    num_days = e_week_view_get_weeks_shown (add_event_data->week_view) * 7;

    g_return_val_if_fail (start <= end, TRUE);
    g_return_val_if_fail (start < add_event_data->week_view->day_starts[num_days], TRUE);
    g_return_val_if_fail (end > add_event_data->week_view->day_starts[0], TRUE);

    start_tt = icaltime_from_timet_with_zone (
        start, FALSE,
        e_calendar_view_get_timezone (E_CALENDAR_VIEW (add_event_data->week_view)));
    end_tt = icaltime_from_timet_with_zone (
        end, FALSE,
                        e_calendar_view_get_timezone (E_CALENDAR_VIEW (add_event_data->week_view)));

    if (add_event_data->comp_data) {
        event.comp_data = g_object_ref (add_event_data->comp_data);
    } else {
        event.comp_data = g_object_new (E_TYPE_CAL_MODEL_COMPONENT, NULL);

        event.comp_data->client = e_cal_model_ref_default_client (e_calendar_view_get_model (E_CALENDAR_VIEW (add_event_data->week_view)));
        e_cal_component_abort_sequence (comp);
        event.comp_data->icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
    }
    event.start = start;
    event.end = end;
    event.tooltip = NULL;
    event.timeout = -1;
    event.color = NULL;
    event.spans_index = 0;
    event.num_spans = 0;
    event.comp_data->instance_start = start;
    event.comp_data->instance_end = end;

    event.start_minute = start_tt.hour * 60 + start_tt.minute;
    event.end_minute = end_tt.hour * 60 + end_tt.minute;
    if (event.end_minute == 0 && start != end)
        event.end_minute = 24 * 60;

    event.different_timezone = FALSE;
    if (!cal_comp_util_compare_event_timezones (
            comp,
            event.comp_data->client,
            e_calendar_view_get_timezone (E_CALENDAR_VIEW (add_event_data->week_view))))
        event.different_timezone = TRUE;

    if (prepend)
        g_array_prepend_val (add_event_data->week_view->events, event);
    else
        g_array_append_val (add_event_data->week_view->events, event);
    add_event_data->week_view->events_sorted = FALSE;
    add_event_data->week_view->events_need_layout = TRUE;

    return TRUE;
}

/* This lays out the events, or reshapes them, as necessary. */
static void
e_week_view_check_layout (EWeekView *week_view)
{
    /* Don't bother if we aren't visible. */
    if (!E_CALENDAR_VIEW (week_view)->in_focus) {
        e_week_view_free_events (week_view);
        week_view->requires_update = TRUE;
        return;
    }

    /* Make sure the events are sorted (by start and size). */
    e_week_view_ensure_events_sorted (week_view);

    if (week_view->events_need_layout)
        week_view->spans = e_week_view_layout_events (
            week_view->events,
            week_view->spans,
            e_week_view_get_multi_week_view (week_view),
            e_week_view_get_weeks_shown (week_view),
            e_week_view_get_compress_weekend (week_view),
            e_week_view_get_display_start_day (week_view),
            week_view->day_starts,
            week_view->rows_per_day);

    if (week_view->events_need_layout || week_view->events_need_reshape)
        e_week_view_reshape_events (week_view);

    week_view->events_need_layout = FALSE;
    week_view->events_need_reshape = FALSE;
}

static void
e_week_view_ensure_events_sorted (EWeekView *week_view)
{
    if (!week_view->events_sorted) {
        qsort (
            week_view->events->data,
            week_view->events->len,
            sizeof (EWeekViewEvent),
            e_week_view_event_sort_func);
        week_view->events_sorted = TRUE;
    }
}

gint
e_week_view_event_sort_func (gconstpointer arg1,
                             gconstpointer arg2)
{
    EWeekViewEvent *event1, *event2;

    event1 = (EWeekViewEvent *) arg1;
    event2 = (EWeekViewEvent *) arg2;

    if (event1->start < event2->start)
        return -1;
    if (event1->start > event2->start)
        return 1;

    if (event1->end > event2->end)
        return -1;
    if (event1->end < event2->end)
        return 1;

    return 0;
}

static void
e_week_view_reshape_events (EWeekView *week_view)
{
    EWeekViewEvent *event;
    GDateWeekday display_start_day;
    gint event_num, span_num;
    gint num_days, day, day_x, day_y, day_w, day_h, max_rows;
    gboolean is_weekend;

    for (event_num = 0; event_num < week_view->events->len; event_num++) {
        event = &g_array_index (week_view->events, EWeekViewEvent,
                    event_num);
        if (!is_comp_data_valid (event))
            continue;

        for (span_num = 0; span_num < event->num_spans; span_num++) {
            gchar *current_comp_string;

            e_week_view_reshape_event_span (
                week_view, event_num, span_num);
            if (week_view->last_edited_comp_string == NULL)
                continue;
            current_comp_string = icalcomponent_as_ical_string_r (event->comp_data->icalcomp);
            if (strncmp (current_comp_string, week_view->last_edited_comp_string,50) == 0) {
                EWeekViewEventSpan *span;

                if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) {
                    g_free (current_comp_string);
                    continue;
                }

                span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num);
                e_canvas_item_grab_focus (span->text_item, TRUE);
                g_free (week_view->last_edited_comp_string);
                week_view->last_edited_comp_string = NULL;
            }
            g_free (current_comp_string);
        }
    }

    /* Reshape the jump buttons and show/hide them as appropriate. */
    num_days = e_week_view_get_weeks_shown (week_view) * 7;
    display_start_day = e_week_view_get_display_start_day (week_view);
    for (day = 0; day < num_days; day++) {
        switch (e_weekday_add_days (display_start_day, day)) {
            case G_DATE_SATURDAY:
            case G_DATE_SUNDAY:
                is_weekend = TRUE;
                break;
            default:
                is_weekend = FALSE;
                break;
        }

        if (!is_weekend || (
            e_week_view_get_multi_week_view (week_view)
            && !e_week_view_get_compress_weekend (week_view)))
            max_rows = week_view->rows_per_cell;
        else
            max_rows = week_view->rows_per_compressed_cell;

        /* Determine whether the jump button should be shown. */
        if (week_view->rows_per_day[day] <= max_rows) {
            gnome_canvas_item_hide (week_view->jump_buttons[day]);
        } else {
            cairo_matrix_t matrix;

            e_week_view_get_day_position (
                week_view, day,
                &day_x, &day_y,
                &day_w, &day_h);

            cairo_matrix_init_translate (
                &matrix,
                day_x + day_w - E_WEEK_VIEW_JUMP_BUTTON_X_PAD - E_WEEK_VIEW_JUMP_BUTTON_WIDTH,
                day_y + day_h - E_WEEK_VIEW_JUMP_BUTTON_Y_PAD - E_WEEK_VIEW_JUMP_BUTTON_HEIGHT);
            gnome_canvas_item_set_matrix (week_view->jump_buttons[day], &matrix);

            gnome_canvas_item_show (week_view->jump_buttons[day]);
            gnome_canvas_item_raise_to_top (week_view->jump_buttons[day]);
        }
    }

    for (day = num_days; day < E_WEEK_VIEW_MAX_WEEKS * 7; day++) {
        gnome_canvas_item_hide (week_view->jump_buttons[day]);
    }
}

static EWeekViewEvent *
tooltip_get_view_event (EWeekView *week_view,
                        gint day,
                        gint event_num)
{
    EWeekViewEvent *pevent;

    if (!is_array_index_in_bounds (week_view->events, event_num))
        return NULL;

    pevent = &g_array_index (week_view->events, EWeekViewEvent, event_num);

    return pevent;
}

static void
tooltip_destroy (EWeekView *week_view,
                 GnomeCanvasItem *item)
{
    gint event_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "event-num"));
    EWeekViewEvent *pevent;
    guint timeout;

    timeout = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (week_view), "tooltip-timeout"));
    if (timeout) {
        g_source_remove (timeout);
        g_object_set_data (G_OBJECT (week_view), "tooltip-timeout", NULL);
    }

    pevent = tooltip_get_view_event (week_view, -1, event_num);
    if (pevent) {
        if (pevent->tooltip && g_object_get_data (G_OBJECT (week_view), "tooltip-window")) {
            gtk_widget_destroy (pevent->tooltip);
            pevent->tooltip = NULL;
        }

        g_object_set_data (G_OBJECT (week_view), "tooltip-window", NULL);
    }
}

static gboolean
tooltip_event_cb (GnomeCanvasItem *item,
                  GdkEvent *event,
                  EWeekView *view)
{
    gint event_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "event-num"));
    EWeekViewEvent *pevent;

    pevent = tooltip_get_view_event (view, -1, event_num);

    switch (event->type) {
        case GDK_ENTER_NOTIFY:
        if (view->editing_event_num == -1) {
            ECalendarViewEventData *data;

            data = g_malloc (sizeof (ECalendarViewEventData));

            pevent->x = ((GdkEventCrossing *) event)->x_root;
            pevent->y = ((GdkEventCrossing *) event)->y_root;
            pevent->tooltip = NULL;

            data->cal_view = (ECalendarView *) view;
            data->day = -1;
            data->event_num = event_num;
            data->get_view_event = (ECalendarViewEvent * (*)(ECalendarView *, int, gint)) tooltip_get_view_event;
            pevent->timeout = g_timeout_add_full (
                G_PRIORITY_DEFAULT, 500,
                (GSourceFunc) e_calendar_view_get_tooltips,
                data, (GDestroyNotify) g_free);
            g_object_set_data ((GObject *) view, "tooltip-timeout", GUINT_TO_POINTER (pevent->timeout));

            return TRUE;
        } else {
            return FALSE;
        }
        case GDK_MOTION_NOTIFY:
            pevent->x = ((GdkEventMotion *) event)->x_root;
            pevent->y = ((GdkEventMotion *) event)->y_root;
            pevent->tooltip = (GtkWidget *) g_object_get_data (G_OBJECT (view), "tooltip-window");

            if (pevent->tooltip) {
                e_calendar_view_move_tip (pevent->tooltip, pevent->x + 16, pevent->y + 16);
            }

            return TRUE;
        case GDK_LEAVE_NOTIFY:
        case GDK_KEY_PRESS:
        case GDK_BUTTON_PRESS:
            tooltip_destroy (view, item);
        default:
            return FALSE;
    }
}

static const gchar *
get_comp_summary (ECalClient *client,
                  icalcomponent *icalcomp,
                  gboolean *free_text)
{
    const gchar *my_summary, *location;
    const gchar *summary;
    gboolean my_free_text = FALSE;

    g_return_val_if_fail (icalcomp != NULL && free_text != NULL, NULL);

    my_summary = e_calendar_view_get_icalcomponent_summary (client, icalcomp, &my_free_text);

    location = icalcomponent_get_location (icalcomp);
    if (location && *location) {
        *free_text = TRUE;
        summary = g_strdup_printf ("%s (%s)", my_summary, location);

        if (my_free_text)
            g_free ((gchar *) my_summary);
    } else {
        *free_text = my_free_text;
        summary = my_summary;
    }

    return summary;
}

static void
e_week_view_reshape_event_span (EWeekView *week_view,
                                gint event_num,
                                gint span_num)
{
    ECalendarView *cal_view;
    ECalModel *model;
    ESourceRegistry *registry;
    EWeekViewEvent *event;
    EWeekViewEventSpan *span;
    gint span_x, span_y, span_w, num_icons, icons_width, time_width;
    gint min_text_x, max_text_w, width;
    gboolean show_icons = TRUE, use_max_width = FALSE;
    gboolean one_day_event;
    ECalComponent *comp;
    gdouble text_x, text_y, text_w, text_h;
    gchar *text, *end_of_line;
    gint line_len, text_width;
    PangoFontDescription *font_desc;
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;
    PangoLayout *layout;

    cal_view = E_CALENDAR_VIEW (week_view);
    model = e_calendar_view_get_model (cal_view);

    registry = e_cal_model_get_registry (model);

    if (!is_array_index_in_bounds (week_view->events, event_num))
        return;

    event = &g_array_index (week_view->events, EWeekViewEvent, event_num);

    if (!is_comp_data_valid (event))
        return;

    if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
        return;

    span = &g_array_index (week_view->spans, EWeekViewEventSpan,
                   event->spans_index + span_num);
    comp = e_cal_component_new ();
    e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp));

    one_day_event = e_week_view_is_one_day_event (week_view, event_num);

    /* If the span will not be visible destroy the canvas items and
     * return. */
    if (!e_week_view_get_span_position (week_view, event_num, span_num,
                        &span_x, &span_y, &span_w)) {
        if (span->background_item)
            g_object_run_dispose (G_OBJECT (span->background_item));
        if (span->text_item)
            g_object_run_dispose (G_OBJECT (span->text_item));
        span->background_item = NULL;
        span->text_item = NULL;

        g_object_unref (comp);
        return;
    }

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

    /* If we are editing a long event we don't show the icons and the EText
     * item uses the maximum width available. */
    if (!one_day_event && week_view->editing_event_num == event_num
        && week_view->editing_span_num == span_num) {
        show_icons = FALSE;
        use_max_width = TRUE;
    }

    /* Calculate how many icons we need to show. */
    num_icons = 0;
    if (show_icons) {
        if (e_cal_component_has_alarms (comp))
            num_icons++;
        if (e_cal_component_has_recurrences (comp) || e_cal_component_is_instance (comp))
            num_icons++;
        if (e_cal_component_has_attachments (comp))
            num_icons++;
        if (e_cal_component_has_attendees (comp))
            num_icons++;
        if (event->different_timezone)
            num_icons++;
        num_icons += cal_comp_util_get_n_icons (comp, NULL);
    }

    /* Create the background canvas item if necessary. */
    if (!span->background_item) {
        span->background_item =
            gnome_canvas_item_new (
                GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->main_canvas)->root),
                e_week_view_event_item_get_type (),
                NULL);
    }

    g_object_set_data ((GObject *) span->background_item, "event-num", GINT_TO_POINTER (event_num));
    g_signal_connect (
        span->background_item, "event",
        G_CALLBACK (tooltip_event_cb), week_view);

    gnome_canvas_item_set (
        span->background_item,
        "event_num", event_num,
        "span_num", span_num,
        NULL);

    /* Create the text item if necessary. */
    if (!span->text_item) {
        const gchar *summary;
        GtkWidget *widget;
        GdkColor color;
        gboolean free_text = FALSE;

        widget = (GtkWidget *) week_view;

        color = e_week_view_get_text_color (week_view, event, widget);
        summary = get_comp_summary (event->comp_data->client, event->comp_data->icalcomp, &free_text);

        span->text_item =
            gnome_canvas_item_new (
                GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->main_canvas)->root),
                e_text_get_type (),
                "clip", TRUE,
                "max_lines", 1,
                "editable", TRUE,
                "text", summary ? summary : "",
                "use_ellipsis", TRUE,
                "fill_color_gdk", &color,
                "im_context", E_CANVAS (week_view->main_canvas)->im_context,
                NULL);

        if (free_text)
            g_free ((gchar *) summary);

        if (e_client_check_capability (E_CLIENT (event->comp_data->client), CAL_STATIC_CAPABILITY_HAS_UNACCEPTED_MEETING)
                && e_cal_util_component_has_attendee (event->comp_data->icalcomp)) {
            set_text_as_bold (event, span, registry);
        }
        g_object_set_data (G_OBJECT (span->text_item), "event-num", GINT_TO_POINTER (event_num));
        g_signal_connect (
            span->text_item, "event",
            G_CALLBACK (e_week_view_on_text_item_event), week_view);
        g_signal_emit_by_name (
            G_OBJECT (week_view),
            "event_added", event);

    }

    /* Calculate the position of the text item.
     * For events < 1 day it starts after the times & icons and ends at the
     * right edge of the span.
     * For events >= 1 day we need to determine whether times are shown at
     * the start and end of the span, then try to center the text item with
     * the icons in the middle, but making sure we don't go over the times.
    */

    /* Calculate the space necessary to display a time, e.g. "13:00". */
    time_width = e_week_view_get_time_string_width (week_view);

    /* Calculate the space needed for the icons. */
    icons_width = (E_WEEK_VIEW_ICON_WIDTH + E_WEEK_VIEW_ICON_X_PAD)
        * num_icons - E_WEEK_VIEW_ICON_X_PAD + E_WEEK_VIEW_ICON_R_PAD;

    /* The y position and height are the same for both event types. */
    text_y = span_y + E_WEEK_VIEW_EVENT_BORDER_HEIGHT
        + E_WEEK_VIEW_EVENT_TEXT_Y_PAD;

    text_h =
        PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
        PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));

    if (one_day_event) {
        /* Note that 1-day events don't have a border. Although we
         * still use the border height to position the events
         * vertically so they still line up neatly (see above),
         * we don't use the border width or edge padding at all. */
        text_x = span_x + E_WEEK_VIEW_EVENT_L_PAD;

        switch (week_view->time_format) {
        case E_WEEK_VIEW_TIME_BOTH_SMALL_MIN:
        case E_WEEK_VIEW_TIME_BOTH:
            /* These have 2 time strings with a small space between
             * them and some space before the EText item. */
            text_x += time_width * 2
                + E_WEEK_VIEW_EVENT_TIME_SPACING
                + E_WEEK_VIEW_EVENT_TIME_X_PAD;
            break;
        case E_WEEK_VIEW_TIME_START_SMALL_MIN:
        case E_WEEK_VIEW_TIME_START:
            /* These have just 1 time string with some space
             * before the EText item. */
            text_x += time_width + E_WEEK_VIEW_EVENT_TIME_X_PAD;
            break;
        case E_WEEK_VIEW_TIME_NONE:
            break;
        }

        /* The icons_width includes space on the right of the icons. */
        text_x += icons_width;

        /* The width of the EText item extends right to the edge of the
         * event, just inside the border. */
        text_w = span_x + span_w - E_WEEK_VIEW_EVENT_R_PAD - text_x;

    } else {
        if (use_max_width) {
            /* When we are editing the event we use all the
             * available width. */
            text_x = span_x + E_WEEK_VIEW_EVENT_L_PAD
                + E_WEEK_VIEW_EVENT_BORDER_WIDTH
                + E_WEEK_VIEW_EVENT_EDGE_X_PAD;
            text_w = span_x + span_w - E_WEEK_VIEW_EVENT_R_PAD
                - E_WEEK_VIEW_EVENT_BORDER_WIDTH
                - E_WEEK_VIEW_EVENT_EDGE_X_PAD - text_x;
        } else {
            text = NULL;
            /* Get the width of the text of the event. This is a
             * bit of a hack. It would be better if EText could
             * tell us this. */
            g_object_get (span->text_item, "text", &text, NULL);
            text_width = 0;
            if (text) {
                /* It should only have one line of text in it.
                 * I'm not sure we need this any more. */
                end_of_line = strchr (text, '\n');
                if (end_of_line)
                    line_len = end_of_line - text;
                else
                    line_len = strlen (text);

                pango_layout_set_text (layout, text, line_len);
                pango_layout_get_pixel_size (layout, &text_width, NULL);
                g_free (text);
            }

            /* Add on the width of the icons and find the default
             * position, which centers the icons + text. */
            width = text_width + icons_width;
            text_x = span_x + (span_w - width) / 2;

            /* Now calculate the left-most valid position, and make
             * sure we don't go to the left of that. */
            min_text_x = span_x + E_WEEK_VIEW_EVENT_L_PAD
                + E_WEEK_VIEW_EVENT_BORDER_WIDTH
                + E_WEEK_VIEW_EVENT_EDGE_X_PAD;
            /* See if we will want to display the start time, and
             * if so take that into account. */
            if (event->start > week_view->day_starts[span->start_day])
                min_text_x += time_width
                    + E_WEEK_VIEW_EVENT_TIME_X_PAD;

            /* Now make sure we don't go to the left of the minimum
             * position. */
            text_x = MAX (text_x, min_text_x);

            /* Now calculate the largest valid width, using the
             * calculated x position, and make sure we don't
             * exceed that. */
            max_text_w = span_x + span_w - E_WEEK_VIEW_EVENT_R_PAD
                - E_WEEK_VIEW_EVENT_BORDER_WIDTH
                - E_WEEK_VIEW_EVENT_EDGE_X_PAD - text_x;
            if (event->end < week_view->day_starts[span->start_day
                                  + span->num_days])
                max_text_w -= time_width
                    + E_WEEK_VIEW_EVENT_TIME_X_PAD;

            text_w = MIN (width, max_text_w);

            /* Now take out the space for the icons. */
            text_x += icons_width;
            text_w -= icons_width;
        }
    }

    /* Make sure we don't try to use a negative width. */
    text_w = MAX (text_w, 0);

    gnome_canvas_item_set (
        span->text_item,
        "clip_width", (gdouble) text_w,
        "clip_height", (gdouble) text_h,
        NULL);
    e_canvas_item_move_absolute (span->text_item, text_x, text_y);

    g_object_unref (comp);
    g_object_unref (layout);
    pango_font_metrics_unref (font_metrics);
}

gboolean
e_week_view_start_editing_event (EWeekView *week_view,
                                 gint event_num,
                                 gint span_num,
                                 gchar *initial_text)
{
    EWeekViewEvent *event;
    EWeekViewEventSpan *span;
    ETextEventProcessor *event_processor = NULL;
    ETextEventProcessorCommand command;
    ECalModelComponent *comp_data;

    /* If we are already editing the event, just return. */
    if (event_num == week_view->editing_event_num
        && span_num == week_view->editing_span_num)
        return TRUE;

    if (!is_array_index_in_bounds (week_view->events, event_num))
        return FALSE;

    event = &g_array_index (week_view->events, EWeekViewEvent, event_num);

    if (!is_comp_data_valid (event))
        return FALSE;

    if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
        return FALSE;

    span = &g_array_index (week_view->spans, EWeekViewEventSpan,
                   event->spans_index + span_num);

    if (e_client_is_readonly (E_CLIENT (event->comp_data->client)))
        return FALSE;

    /* If the event is not shown, don't try to edit it. */
    if (!span->text_item)
        return FALSE;

    if (week_view->editing_event_num >= 0) {
        EWeekViewEvent *editing;

        if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num))
            return FALSE;

        editing = &g_array_index (week_view->events, EWeekViewEvent, week_view->editing_event_num);

        /* do not change to other part of same component - the event is spread into more days */
        if (editing && event && editing->comp_data == event->comp_data)
            return FALSE;
    }

    gnome_canvas_item_set (
        span->text_item,
        "text", initial_text ? initial_text : icalcomponent_get_summary (event->comp_data->icalcomp),
        NULL);

    /* Save the comp_data value because we use that as our invariant */
    comp_data = event->comp_data;

    e_canvas_item_grab_focus (span->text_item, TRUE);

    /* If the above focus caused things to redraw, then find the
     * the event and the span again */
    if (event_num < week_view->events->len)
        event = &g_array_index (week_view->events, EWeekViewEvent, event_num);

    if (event_num >= week_view->events->len || event->comp_data != comp_data) {
        /* When got in because of other comp_data, then be sure we go through all events */
        event_num = week_view->events->len;

        /* Unfocussing can cause a removal but not a new
         * addition so just run backwards through the
         * events */
        for (event_num--; event_num >= 0; event_num--) {
            event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
            if (event->comp_data == comp_data)
                break;
        }
        g_return_val_if_fail (event_num >= 0, FALSE);
    }

    if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
        return FALSE;

    span = &g_array_index (week_view->spans, EWeekViewEventSpan,  event->spans_index + span_num);

    /* Try to move the cursor to the end of the text. */
    g_object_get (span->text_item, "event_processor", &event_processor, NULL);
    if (event_processor) {
        command.action = E_TEP_MOVE;
        command.position = E_TEP_END_OF_BUFFER;
        g_signal_emit_by_name (
            event_processor,
            "command", &command);
    }
    return TRUE;
}

/* This stops any current edit. */
void
e_week_view_stop_editing_event (EWeekView *week_view)
{
    GtkWidget *toplevel;

    /* Check we are editing an event. */
    if (week_view->editing_event_num == -1)
        return;

    /* Set focus to the toplevel so the item loses focus. */
    toplevel = gtk_widget_get_toplevel (GTK_WIDGET (week_view));
    if (toplevel && GTK_IS_WINDOW (toplevel))
        gtk_window_set_focus (GTK_WINDOW (toplevel), NULL);
}

/* Cancels the current edition by resetting the appointment's text to its original value */
static void
cancel_editing (EWeekView *week_view)
{
    gint event_num, span_num;
    EWeekViewEvent *event;
    EWeekViewEventSpan *span;
    const gchar *summary;

    event_num = week_view->editing_event_num;
    span_num = week_view->editing_span_num;

    g_return_if_fail (event_num != -1);

    if (!is_array_index_in_bounds (week_view->events, event_num))
        return;

    event = &g_array_index (week_view->events, EWeekViewEvent, event_num);

    if (!is_comp_data_valid (event))
        return;

    if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
        return;

    span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num);

    /* Reset the text to what was in the component */

    summary = icalcomponent_get_summary (event->comp_data->icalcomp);
    g_object_set (span->text_item, "text", summary ? summary : "", NULL);

    /* Stop editing */
    e_week_view_stop_editing_event (week_view);
}

static gboolean
e_week_view_on_text_item_event (GnomeCanvasItem *item,
                                GdkEvent *gdk_event,
                                EWeekView *week_view)
{
    EWeekViewEvent *event;
    gint event_num, span_num;
    gint nevent = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "event-num"));
    EWeekViewEvent *pevent;
    guint event_button = 0;
    guint event_keyval = 0;
    gdouble event_x_root = 0;
    gdouble event_y_root = 0;

    pevent = tooltip_get_view_event (week_view, -1, nevent);

    switch (gdk_event->type) {
    case GDK_KEY_PRESS:
        tooltip_destroy (week_view, item);
        gdk_event_get_keyval (gdk_event, &event_keyval);

        if (!E_TEXT (item)->preedit_len && event_keyval == GDK_KEY_Return) {
            /* We set the keyboard focus to the EDayView, so the
             * EText item loses it and stops the edit. */
            gtk_widget_grab_focus (GTK_WIDGET (week_view));

            /* Stop the signal last or we will also stop any
             * other events getting to the EText item. */
            g_signal_stop_emission_by_name (item, "event");
            return TRUE;
        } else if (event_keyval == GDK_KEY_Escape) {
            cancel_editing (week_view);
            g_signal_stop_emission_by_name (item, "event");
            /* focus should go to week view when stop editing */
            gtk_widget_grab_focus (GTK_WIDGET (week_view));
            return TRUE;
        }
        break;
    case GDK_2BUTTON_PRESS:
        if (!e_week_view_find_event_from_item (week_view, item,
                               &event_num, &span_num))
            return FALSE;

        if (!is_array_index_in_bounds (week_view->events, event_num))
            return FALSE;

        event = &g_array_index (week_view->events, EWeekViewEvent,
                    event_num);

        if (!is_comp_data_valid (event))
            return FALSE;

        /* if we started to editing new item on the canvas, then do not open editing dialog until it's saved,
         * because the save of the event recalculates event numbers and you can edit different one */
        if (!is_icalcomp_on_the_server (event->comp_data->icalcomp, event->comp_data->client))
            return TRUE;

        e_calendar_view_edit_appointment (
            E_CALENDAR_VIEW (week_view),
            event->comp_data->client,
            event->comp_data->icalcomp, EDIT_EVENT_AUTODETECT);

        g_signal_stop_emission_by_name (item, "event");
        return TRUE;
    case GDK_BUTTON_PRESS:
        tooltip_destroy (week_view, item);
        if (!e_week_view_find_event_from_item (week_view, item,
                               &event_num, &span_num))
            return FALSE;

        gdk_event_get_button (gdk_event, &event_button);
        if (event_button == 3) {
            EWeekViewEvent *e;

            if (E_TEXT (item)->editing) {
                e_week_view_stop_editing_event (week_view);
                gtk_widget_grab_focus (GTK_WIDGET (week_view));
                return FALSE;
            }

            if (!is_array_index_in_bounds (week_view->events, event_num))
                return FALSE;

            e = &g_array_index (week_view->events, EWeekViewEvent, event_num);

            if (!gtk_widget_has_focus (GTK_WIDGET (week_view)))
                gtk_widget_grab_focus (GTK_WIDGET (week_view));

            e_week_view_set_selected_time_range_visible (week_view, e->start, e->end);

            e_week_view_show_popup_menu (
                week_view, gdk_event, event_num);

            g_signal_stop_emission_by_name (
                item->canvas, "button_press_event");
            return TRUE;
        }

        if (event_button != 3) {
            week_view->pressed_event_num = event_num;
            week_view->pressed_span_num = span_num;
        }

        /* Only let the EText handle the event while editing. */
        if (!E_TEXT (item)->editing) {
            gdouble event_x_win = 0;
            gdouble event_y_win = 0;

            g_signal_stop_emission_by_name (item, "event");

            gdk_event_get_coords (
                gdk_event, &event_x_win, &event_y_win);

            week_view->drag_event_x = (gint) event_x_win;
            week_view->drag_event_y = (gint) event_y_win;

            /* FIXME: Remember the day offset from the start of
             * the event, for DnD. */

            return TRUE;
        }
        break;
    case GDK_BUTTON_RELEASE:
        if (!E_TEXT (item)->editing) {
            /* This shouldn't ever happen. */
            if (!e_week_view_find_event_from_item (week_view,
                                   item,
                                   &event_num,
                                   &span_num))
                return FALSE;

            if (week_view->pressed_event_num != -1
                && week_view->pressed_event_num == event_num
                && week_view->pressed_span_num == span_num) {
                e_week_view_start_editing_event (
                    week_view,
                    event_num,
                    span_num,
                    NULL);
                week_view->pressed_event_num = -1;
            }

            /* Stop the signal last or we will also stop any
             * other events getting to the EText item. */
            g_signal_stop_emission_by_name (item, "event");
            return TRUE;
        }
        week_view->pressed_event_num = -1;
        break;
    case GDK_ENTER_NOTIFY:
    {
        ECalendarViewEventData *data;
        gint nspan;

        if (week_view->editing_event_num != -1
            || !e_week_view_find_event_from_item (week_view, item, &nevent, &nspan))
            return FALSE;

        g_object_set_data ((GObject *) item, "event-num", GINT_TO_POINTER (nevent));

        pevent = tooltip_get_view_event (week_view, -1, nevent);

        data = g_malloc (sizeof (ECalendarViewEventData));

        gdk_event_get_root_coords (
            gdk_event, &event_x_root, &event_y_root);

        pevent->x = (gint) event_x_root;
        pevent->y = (gint) event_y_root;
        pevent->tooltip = NULL;

        data->cal_view = (ECalendarView *) week_view;
        data->day = -1;
        data->event_num = nevent;
        data->get_view_event = (ECalendarViewEvent * (*)(ECalendarView *, int, gint)) tooltip_get_view_event;
        pevent->timeout = g_timeout_add_full (
            G_PRIORITY_DEFAULT, 500,
            (GSourceFunc) e_calendar_view_get_tooltips,
            data, (GDestroyNotify) g_free);
        g_object_set_data ((GObject *) week_view, "tooltip-timeout", GUINT_TO_POINTER (pevent->timeout));

        return TRUE;
    }
    case GDK_LEAVE_NOTIFY:
        tooltip_destroy (week_view, item);

        return FALSE;
    case GDK_MOTION_NOTIFY:
        gdk_event_get_root_coords (
            gdk_event, &event_x_root, &event_y_root);

        pevent->x = (gint) event_x_root;
        pevent->y = (gint) event_y_root;
        pevent->tooltip = (GtkWidget *) g_object_get_data (G_OBJECT (week_view), "tooltip-window");

        if (pevent->tooltip) {
            e_calendar_view_move_tip (pevent->tooltip, pevent->x + 16, pevent->y + 16);
        }
        return TRUE;
    case GDK_FOCUS_CHANGE:
        if (gdk_event->focus_change.in) {
            e_week_view_on_editing_started (week_view, item);
        } else {
            e_week_view_on_editing_stopped (week_view, item);
        }

        return FALSE;
    default:
        break;
    }

    return FALSE;
}

static gboolean
e_week_view_event_move (ECalendarView *cal_view,
                        ECalViewMoveDirection direction)
{
    EWeekViewEvent *event;
    gint event_num, adjust_days, current_start_day, current_end_day;
    time_t start_dt, end_dt;
    struct icaltimetype start_time,end_time;
    EWeekView *week_view = E_WEEK_VIEW (cal_view);
    gboolean is_all_day = FALSE;

    event_num = week_view->editing_event_num;
    adjust_days = 0;

    /* If no item is being edited, just return. */
    if (event_num == -1)
        return FALSE;

    if (!is_array_index_in_bounds (week_view->events, event_num))
        return FALSE;

    event = &g_array_index (week_view->events, EWeekViewEvent, event_num);

    if (!is_comp_data_valid (event))
        return FALSE;

    end_dt = event->end;
    start_time = icalcomponent_get_dtstart (event->comp_data->icalcomp);
    end_time = icalcomponent_get_dtend (event->comp_data->icalcomp);

    if (start_time.is_date && end_time.is_date)
        is_all_day = TRUE;

    current_end_day = e_week_view_get_day_offset_of_event (week_view,end_dt);

    switch (direction) {
    case E_CAL_VIEW_MOVE_UP:
        adjust_days = e_week_view_get_adjust_days_for_move_up (week_view,current_end_day);
        break;
    case E_CAL_VIEW_MOVE_DOWN:
        adjust_days = e_week_view_get_adjust_days_for_move_down (week_view,current_end_day);
        break;
    case E_CAL_VIEW_MOVE_LEFT:
        adjust_days = e_week_view_get_adjust_days_for_move_left (week_view,current_end_day);
        break;
    case E_CAL_VIEW_MOVE_RIGHT:
        adjust_days = e_week_view_get_adjust_days_for_move_right (week_view,current_end_day);
        break;
    default:
        break;
    }

    icaltime_adjust (&start_time ,adjust_days,0,0,0);
    icaltime_adjust (&end_time ,adjust_days,0,0,0);
    start_dt = icaltime_as_timet_with_zone (
        start_time,
        e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
    end_dt = icaltime_as_timet_with_zone (
        end_time,
        e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));

    current_start_day = e_week_view_get_day_offset_of_event (week_view,start_dt);
    current_end_day = e_week_view_get_day_offset_of_event (week_view,end_dt);
    if (is_all_day)
        current_end_day--;

    if (current_start_day < 0)
        return TRUE;

    if (current_end_day >= e_week_view_get_weeks_shown (week_view) * 7)
        return TRUE;

    e_week_view_change_event_time (week_view, start_dt, end_dt, is_all_day);

    return TRUE;
}

static gint
e_week_view_get_day_offset_of_event (EWeekView *week_view,
                                     time_t event_time)
{
    time_t first_day = week_view->day_starts[0];

    if (event_time - first_day < 0)
        return -1;
    else
        return (event_time - first_day) / (24 * 60 * 60);
}

void
e_week_view_scroll_a_step (EWeekView *week_view,
                           ECalViewMoveDirection direction)
{
    GtkAdjustment *adjustment;
    GtkRange *range;
    gdouble step_increment;
    gdouble page_size;
    gdouble new_value;
    gdouble lower;
    gdouble upper;
    gdouble value;

    range = GTK_RANGE (week_view->vscrollbar);
    adjustment = gtk_range_get_adjustment (range);

    step_increment = gtk_adjustment_get_step_increment (adjustment);
    page_size = gtk_adjustment_get_page_size (adjustment);
    lower = gtk_adjustment_get_lower (adjustment);
    upper = gtk_adjustment_get_upper (adjustment);
    value = gtk_adjustment_get_value (adjustment);

    switch (direction) {
        case E_CAL_VIEW_MOVE_UP:
            new_value = value - step_increment;
            break;
        case E_CAL_VIEW_MOVE_DOWN:
            new_value = value + step_increment;
            break;
        case E_CAL_VIEW_MOVE_PAGE_UP:
            new_value = value - page_size;
            break;
        case E_CAL_VIEW_MOVE_PAGE_DOWN:
            new_value = value + page_size;
            break;
        default:
            return;
    }

    new_value = CLAMP (new_value, lower, upper - page_size);
    gtk_adjustment_set_value (adjustment, new_value);
}

static void
e_week_view_change_event_time (EWeekView *week_view,
                               time_t start_dt,
                               time_t end_dt,
                               gboolean is_all_day)
{
    EWeekViewEvent *event;
    gint event_num;
    ECalComponent *comp;
    ECalComponentDateTime date;
    struct icaltimetype itt;
    ECalClient *client;
    CalObjModType mod = CALOBJ_MOD_ALL;
    GtkWindow *toplevel;

    event_num = week_view->editing_event_num;

    /* If no item is being edited, just return. */
    if (event_num == -1)
        return;

    if (!is_array_index_in_bounds (week_view->events, event_num))
        return;

    event = &g_array_index (week_view->events, EWeekViewEvent, event_num);

    if (!is_comp_data_valid (event))
        return;

    client = event->comp_data->client;

    /* We use a temporary shallow copy of the ico since we don't want to
     * change the original ico here. Otherwise we would not detect that
     * the event's time had changed in the "update_event" callback. */
    comp = e_cal_component_new ();
    e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp));
    date.value = &itt;
    /* FIXME: Should probably keep the timezone of the original start
     * and end times. */
    date.tzid = icaltimezone_get_tzid (e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));

    *date.value = icaltime_from_timet_with_zone (start_dt, is_all_day,
                             e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
    cal_comp_set_dtstart_with_oldzone (client, comp, &date);
    *date.value = icaltime_from_timet_with_zone (end_dt, is_all_day,
                             e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
    cal_comp_set_dtend_with_oldzone (client, comp, &date);

    e_cal_component_commit_sequence (comp);

    if (week_view->last_edited_comp_string != NULL) {
        g_free (week_view->last_edited_comp_string);
        week_view->last_edited_comp_string = NULL;
    }

    week_view->last_edited_comp_string = e_cal_component_get_as_string (comp);

    if (e_cal_component_has_recurrences (comp)) {
        if (!recur_component_dialog (client, comp, &mod, NULL, FALSE)) {
            gtk_widget_queue_draw (week_view->main_canvas);
            goto out;
        }

        if (mod == CALOBJ_MOD_ALL)
            comp_util_sanitize_recurrence_master (comp, client);

        if (mod == CALOBJ_MOD_THIS) {
            e_cal_component_set_rdate_list (comp, NULL);
            e_cal_component_set_rrule_list (comp, NULL);
            e_cal_component_set_exdate_list (comp, NULL);
            e_cal_component_set_exrule_list (comp, NULL);
        }
    } else if (e_cal_component_is_instance (comp))
        mod = CALOBJ_MOD_THIS;

    toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (week_view)));

    e_cal_component_commit_sequence (comp);

    e_calendar_view_modify_and_send (
        E_CALENDAR_VIEW (week_view),
        comp, client, mod, toplevel, TRUE);

out:
    g_object_unref (comp);
}

static void
e_week_view_on_editing_started (EWeekView *week_view,
                                GnomeCanvasItem *item)
{
    gint event_num, span_num;

    if (!e_week_view_find_event_from_item (week_view, item,
                           &event_num, &span_num))
        return;

    week_view->editing_event_num = event_num;
    week_view->editing_span_num = span_num;

    /* We need to reshape long events so the whole width is used while
     * editing. */
    if (!e_week_view_is_one_day_event (week_view, event_num)) {
        e_week_view_reshape_event_span (
            week_view, event_num, span_num);
    }

    g_signal_emit_by_name (week_view, "selection_changed");

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

static void
e_week_view_on_editing_stopped (EWeekView *week_view,
                                GnomeCanvasItem *item)
{
    gint event_num, span_num;
    EWeekViewEvent *event;
    EWeekViewEventSpan *span;
    gchar *text = NULL;
    ECalComponent *comp;
    ECalComponentText summary;
    ECalClient *client;
    const gchar *uid;
    gboolean on_server;

    /* Note: the item we are passed here isn't reliable, so we just stop
     * the edit of whatever item was being edited. We also receive this
     * event twice for some reason. */
    event_num = week_view->editing_event_num;
    span_num = week_view->editing_span_num;

    /* If no item is being edited, just return. */
    if (event_num == -1)
        return;

    if (!is_array_index_in_bounds (week_view->events, event_num))
        return;

    event = &g_array_index (week_view->events, EWeekViewEvent, event_num);

    if (!is_comp_data_valid (event))
        return;

    if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
        return;

    span = &g_array_index (week_view->spans, EWeekViewEventSpan,
                   event->spans_index + span_num);

    /* Reset the edit fields. */
    week_view->editing_event_num = -1;

    /* Check that the event is still valid. */
    uid = icalcomponent_get_uid (event->comp_data->icalcomp);
    if (!uid) {
        g_object_notify (G_OBJECT (week_view), "is-editing");
        return;
    }

    text = NULL;
    g_object_set (span->text_item, "handle_popup", FALSE, NULL);
    g_object_get (span->text_item, "text", &text, NULL);

    comp = e_cal_component_new ();
    e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp));

    client = event->comp_data->client;
    on_server = cal_comp_is_on_server (comp, client);

    if (string_is_empty (text) && !on_server) {
        e_cal_component_get_uid (comp, &uid);
        g_signal_handlers_disconnect_by_func (item, e_week_view_on_text_item_event, week_view);
        e_week_view_foreach_event_with_uid (week_view, uid,
                            e_week_view_remove_event_cb, NULL);
        week_view->event_destroyed = TRUE;
        gtk_widget_queue_draw (week_view->main_canvas);
        e_week_view_check_layout (week_view);
        goto out;
    }

    /* Only update the summary if necessary. */
    e_cal_component_get_summary (comp, &summary);
    if (summary.value && !strcmp (text, summary.value)) {
        gboolean free_text = FALSE;
        const gchar *summary;

        summary = get_comp_summary (event->comp_data->client, event->comp_data->icalcomp, &free_text);
        g_object_set (span->text_item, "text", summary ? summary : "", NULL);

        if (free_text)
            g_free ((gchar *) summary);

        if (!e_week_view_is_one_day_event (week_view, event_num))
            e_week_view_reshape_event_span (week_view, event_num, span_num);
    } else if (summary.value || !string_is_empty (text)) {
        icalcomponent *icalcomp = e_cal_component_get_icalcomponent (comp);

        summary.value = text;
        summary.altrep = NULL;
        e_cal_component_set_summary (comp, &summary);
        e_cal_component_commit_sequence (comp);

        if (!on_server) {
            gchar *uid = NULL;
            GError *error = NULL;

            e_cal_client_create_object_sync (
                client, icalcomp, &uid, NULL, &error);

            if (error != NULL) {
                g_warning (
                    G_STRLOC ": Could not create the object! %s",
                    error->message);
                uid = NULL;
            } else {
                if (uid)
                    icalcomponent_set_uid (icalcomp, uid);

                e_calendar_view_emit_user_created (
                    E_CALENDAR_VIEW (week_view), client);
            }

            if (uid)
                g_free (uid);
            if (error)
                g_error_free (error);

            /* we remove the object since we either got the update from the server or failed */
            e_week_view_remove_event_cb (week_view, event_num, NULL);
        } else {
            CalObjModType mod = CALOBJ_MOD_ALL;
            GtkWindow *toplevel;

            if (e_cal_component_has_recurrences (comp)) {
                if (!recur_component_dialog (client, comp, &mod, NULL, FALSE)) {
                    goto out;
                }

                if (mod == CALOBJ_MOD_ALL)
                    comp_util_sanitize_recurrence_master (comp, client);

                if (mod == CALOBJ_MOD_THIS) {
                    ECalComponentDateTime dt;
                    struct icaltimetype tt;
                    gchar *tzid;

                    e_cal_component_get_dtstart (comp, &dt);
                    if (dt.value->zone) {
                        tt = icaltime_from_timet_with_zone (
                            event->comp_data->instance_start,
                            dt.value->is_date,
                            dt.value->zone);
                    } else {
                        tt = icaltime_from_timet_with_zone (
                            event->comp_data->instance_start,
                            dt.value->is_date,
                            e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
                    }
                    tzid = g_strdup (dt.tzid);
                    e_cal_component_free_datetime (&dt);
                    dt.value = &tt;
                    dt.tzid = tzid;
                    e_cal_component_set_dtstart (comp, &dt);
                    g_free (tzid);

                    e_cal_component_get_dtend (comp, &dt);
                    if (dt.value->zone) {
                        tt = icaltime_from_timet_with_zone (
                            event->comp_data->instance_end,
                            dt.value->is_date,
                            dt.value->zone);
                    } else {
                        tt = icaltime_from_timet_with_zone (
                            event->comp_data->instance_end,
                            dt.value->is_date,
                            e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));
                    }
                    tzid = g_strdup (dt.tzid);
                    e_cal_component_free_datetime (&dt);
                    dt.value = &tt;
                    dt.tzid = tzid;
                    e_cal_component_set_dtend (comp, &dt);
                    g_free (tzid);

                    e_cal_component_set_rdate_list (comp, NULL);
                    e_cal_component_set_rrule_list (comp, NULL);
                    e_cal_component_set_exdate_list (comp, NULL);
                    e_cal_component_set_exrule_list (comp, NULL);

                    e_cal_component_commit_sequence (comp);
                }
            } else if (e_cal_component_is_instance (comp))
                mod = CALOBJ_MOD_THIS;

            /* FIXME When sending here, what exactly should we send? */
            toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (week_view)));

            e_calendar_view_modify_and_send (
                E_CALENDAR_VIEW (week_view),
                comp, client, mod, toplevel, FALSE);
        }
    }

 out:

    g_free (text);
    g_object_unref (comp);

    g_signal_emit_by_name (week_view, "selection_changed");

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

gboolean
e_week_view_find_event_from_item (EWeekView *week_view,
                                  GnomeCanvasItem *item,
                                  gint *event_num_return,
                                  gint *span_num_return)
{
    EWeekViewEvent *event;
    EWeekViewEventSpan *span;
    gint event_num, span_num, num_events;

    num_events = week_view->events->len;
    for (event_num = 0; event_num < num_events; event_num++) {
        event = &g_array_index (week_view->events, EWeekViewEvent,
                    event_num);
        for (span_num = 0; span_num < event->num_spans; span_num++) {
            if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num))
                continue;

            span = &g_array_index (week_view->spans,
                           EWeekViewEventSpan,
                           event->spans_index + span_num);
            if (span->text_item == item) {
                *event_num_return = event_num;
                *span_num_return = span_num;
                return TRUE;
            }
        }
    }

    return FALSE;
}

/* Finds the index of the event with the given uid.
 * Returns TRUE if an event with the uid was found.
 * Note that for recurring events there may be several EWeekViewEvents, one
 * for each instance, all with the same iCalObject and uid. So only use this
 * function if you know the event doesn't recur or you are just checking to
 * see if any events with the uid exist. */
static gboolean
e_week_view_find_event_from_uid (EWeekView *week_view,
                                 ECalClient *client,
                                 const gchar *uid,
                                 const gchar *rid,
                                 gint *event_num_return)
{
    EWeekViewEvent *event;
    gint event_num, num_events;

    *event_num_return = -1;
    if (!uid)
        return FALSE;

    num_events = week_view->events->len;
    for (event_num = 0; event_num < num_events; event_num++) {
        const gchar *u;
        gchar *r = NULL;

        event = &g_array_index (week_view->events, EWeekViewEvent,
                    event_num);

        if (!is_comp_data_valid (event))
            continue;

        if (event->comp_data->client != client)
            continue;

        u = icalcomponent_get_uid (event->comp_data->icalcomp);
        if (u && !strcmp (uid, u)) {
            if (rid && *rid) {
                r = icaltime_as_ical_string_r (icalcomponent_get_recurrenceid (event->comp_data->icalcomp));
                if (!r || !*r)
                    continue;
                if (strcmp (rid, r) != 0) {
                    g_free (r);
                    continue;
                }
                g_free (r);
            }

            *event_num_return = event_num;
            return TRUE;
        }
    }

    return FALSE;
}

gboolean
e_week_view_is_one_day_event (EWeekView *week_view,
                              gint event_num)
{
    EWeekViewEvent *event;
    EWeekViewEventSpan *span;

    if (!is_array_index_in_bounds (week_view->events, event_num))
        return FALSE;

    event = &g_array_index (week_view->events, EWeekViewEvent, event_num);
    if (event->num_spans != 1)
        return FALSE;

    if (!is_array_index_in_bounds (week_view->spans, event->spans_index))
        return FALSE;

    span = &g_array_index (week_view->spans, EWeekViewEventSpan,
                   event->spans_index);

    if (event->start == week_view->day_starts[span->start_day]
        && event->end == week_view->day_starts[span->start_day + 1])
        return FALSE;

    if (span->num_days == 1
        && event->start >= week_view->day_starts[span->start_day]
        && event->end <= week_view->day_starts[span->start_day + 1])
        return TRUE;

    return FALSE;
}

static void
e_week_view_cursor_key_up (EWeekView *week_view)
{
    EWeekViewClass *week_view_class;

    week_view_class = E_WEEK_VIEW_GET_CLASS (week_view);
    g_return_if_fail (week_view_class->cursor_key_up != NULL);

    week_view_class->cursor_key_up (week_view);
}

static void
e_week_view_cursor_key_down (EWeekView *week_view)
{
    EWeekViewClass *week_view_class;

    week_view_class = E_WEEK_VIEW_GET_CLASS (week_view);
    g_return_if_fail (week_view_class->cursor_key_down != NULL);

    week_view_class->cursor_key_down (week_view);
}

static void
e_week_view_cursor_key_left (EWeekView *week_view)
{
    EWeekViewClass *week_view_class;

    week_view_class = E_WEEK_VIEW_GET_CLASS (week_view);
    g_return_if_fail (week_view_class->cursor_key_left != NULL);

    week_view_class->cursor_key_left (week_view);
}

static void
e_week_view_cursor_key_right (EWeekView *week_view)
{
    EWeekViewClass *week_view_class;

    week_view_class = E_WEEK_VIEW_GET_CLASS (week_view);
    g_return_if_fail (week_view_class->cursor_key_right != NULL);

    week_view_class->cursor_key_right (week_view);
}

static gboolean
e_week_view_do_key_press (GtkWidget *widget,
                          GdkEventKey *event)
{
    EWeekView *week_view;
    gchar *initial_text;
    guint keyval;
    gboolean stop_emission;
    gboolean ret_val;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    week_view = E_WEEK_VIEW (widget);
    keyval = event->keyval;

    /* The Escape key aborts a resize operation. */
#if 0
    if (week_view->resize_drag_pos != E_CALENDAR_VIEW_POS_NONE) {
        if (event->keyval == GDK_KEY_Escape) {
            e_week_view_abort_resize (week_view, event->time);
        }
        return FALSE;
    }
#endif

    /* Handle the cursor keys for moving the selection */
    stop_emission = FALSE;
    if (!(event->state & GDK_SHIFT_MASK)
        && !(event->state & GDK_MOD1_MASK)) {
        stop_emission = TRUE;
        switch (keyval) {
        case GDK_KEY_Page_Up:
            if (!e_week_view_get_multi_week_view (week_view))
                e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_UP);
            else
                e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_PAGE_UP);
            break;
        case GDK_KEY_Page_Down:
            if (!e_week_view_get_multi_week_view (week_view))
                e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_DOWN);
            else
                e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_PAGE_DOWN);
            break;
        case GDK_KEY_Up:
            e_week_view_cursor_key_up (week_view);
            break;
        case GDK_KEY_Down:
            e_week_view_cursor_key_down (week_view);
            break;
        case GDK_KEY_Left:
            e_week_view_cursor_key_left (week_view);
            break;
        case GDK_KEY_Right:
            e_week_view_cursor_key_right (week_view);
            break;
        default:
            stop_emission = FALSE;
            break;
        }
    }
    if (stop_emission)
        return TRUE;

    /*Navigation through days with arrow keys*/
    if (((event->state & GDK_SHIFT_MASK) != GDK_SHIFT_MASK)
        &&((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK)
        &&((event->state & GDK_MOD1_MASK) == GDK_MOD1_MASK)) {
        if (keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up)
            return e_week_view_event_move ((ECalendarView *) week_view, E_CAL_VIEW_MOVE_UP);
        else if (keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down)
            return e_week_view_event_move ((ECalendarView *) week_view, E_CAL_VIEW_MOVE_DOWN);
        else if (keyval == GDK_KEY_Left || keyval == GDK_KEY_KP_Left)
            return e_week_view_event_move ((ECalendarView *) week_view, E_CAL_VIEW_MOVE_LEFT);
        else if (keyval == GDK_KEY_Right || keyval == GDK_KEY_KP_Right)
            return e_week_view_event_move ((ECalendarView *) week_view, E_CAL_VIEW_MOVE_RIGHT);
    }

    if (week_view->selection_start_day == -1)
        return FALSE;

    /* We only want to start an edit with a return key or a simple
     * character. */
    if (event->keyval == GDK_KEY_Return) {
        initial_text = NULL;
    } else if (((event->keyval >= 0x20) && (event->keyval <= 0xFF)
            && (event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)))
           || (event->length == 0)
           || (event->keyval == GDK_KEY_Tab)) {
        return FALSE;
    } else
        initial_text = e_utf8_from_gtk_event_key (widget, event->keyval, event->string);

    ret_val = e_week_view_add_new_event_in_selected_range (week_view, initial_text);

    if (initial_text)
        g_free (initial_text);

    return ret_val;
}

static gint
e_week_view_get_adjust_days_for_move_up (EWeekView *week_view,
                                         gint current_day)
{
    return e_week_view_get_multi_week_view (week_view) ? -7 : 0;
}

static gint
e_week_view_get_adjust_days_for_move_down (EWeekView *week_view,
                                           gint current_day)
{
    return e_week_view_get_multi_week_view (week_view) ? 7 : 0;
}

static gint
e_week_view_get_adjust_days_for_move_left (EWeekView *week_view,
                                           gint current_day)
{
    return -1;
}

static gint
e_week_view_get_adjust_days_for_move_right (EWeekView *week_view,
                                            gint current_day)
{
    return 1;
}

void
e_week_view_show_popup_menu (EWeekView *week_view,
                             GdkEvent *button_event,
                             gint event_num)
{
    week_view->popup_event_num = event_num;

    e_calendar_view_popup_event (E_CALENDAR_VIEW (week_view), button_event);
}

void
e_week_view_jump_to_button_item (EWeekView *week_view,
                                 GnomeCanvasItem *item)
{
    gint day;
    GnomeCalendar *calendar;

    for (day = 0; day < E_WEEK_VIEW_MAX_WEEKS * 7; ++day) {
        if (item == week_view->jump_buttons[day]) {
            calendar = e_calendar_view_get_calendar (E_CALENDAR_VIEW (week_view));
            if (calendar)
                gnome_calendar_dayjump
                    (calendar,
                     week_view->day_starts[day]);
            else
                g_warning ("Calendar not set");
            return;
        }
    }
}

static gboolean
e_week_view_on_jump_button_event (GnomeCanvasItem *item,
                                  GdkEvent *event,
                                  EWeekView *week_view)
{
    gint day;

    if (event->type == GDK_BUTTON_PRESS) {
        e_week_view_jump_to_button_item (week_view, item);
        return TRUE;
    }
    else if (event->type == GDK_KEY_PRESS) {
        /* return, if Tab, Control or Alt is pressed */
        if ((event->key.keyval == GDK_KEY_Tab) ||
            (event->key.state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)))
            return FALSE;
        /* with a return key or a simple character (from 0x20 to 0xff),
         * jump to the day
         */
        if ((event->key.keyval == GDK_KEY_Return) ||
            ((event->key.keyval >= 0x20) &&
             (event->key.keyval <= 0xFF))) {
            e_week_view_jump_to_button_item (week_view, item);
            return TRUE;
        }
    }
    else if (event->type == GDK_FOCUS_CHANGE) {
        GdkEventFocus *focus_event = (GdkEventFocus *) event;
        GdkPixbuf *pixbuf = NULL;

        for (day = 0; day < E_WEEK_VIEW_MAX_WEEKS * 7; day++) {
            if (item == week_view->jump_buttons[day])
                break;
        }

        g_return_val_if_fail (day < E_WEEK_VIEW_MAX_WEEKS * 7, FALSE);

        if (focus_event->in) {
            week_view->focused_jump_button = day;
            pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) jump_xpm_focused);
            gnome_canvas_item_set (
                week_view->jump_buttons[day],
                "GnomeCanvasPixbuf::pixbuf",
                pixbuf, NULL);
        }
        else {
            week_view->focused_jump_button = E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS;
            pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) jump_xpm);
            gnome_canvas_item_set (
                week_view->jump_buttons[day],
                "GnomeCanvasPixbuf::pixbuf",
                pixbuf, NULL);
        }
        if (pixbuf)
            g_object_unref (pixbuf);
    }

    return FALSE;
}

/* Converts an hour from 0-23 to the preferred time format, and returns the
 * suffix to add and the width of it in the normal font. */
void
e_week_view_convert_time_to_display (EWeekView *week_view,
                                     gint hour,
                                     gint *display_hour,
                                     const gchar **suffix,
                                     gint *suffix_width)
{
    ECalModel *model;

    model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view));

    /* Calculate the actual hour number to display. For 12-hour
     * format we convert 0-23 to 12-11am/12-11pm. */
    *display_hour = hour;
    if (e_cal_model_get_use_24_hour_format (model)) {
        *suffix = "";
        *suffix_width = 0;
    } else {
        if (hour < 12) {
            *suffix = week_view->am_string;
            *suffix_width = week_view->am_string_width;
        } else {
            *display_hour -= 12;
            *suffix = week_view->pm_string;
            *suffix_width = week_view->pm_string_width;
        }

        /* 12-hour uses 12:00 rather than 0:00. */
        if (*display_hour == 0)
            *display_hour = 12;
    }
}

gint
e_week_view_get_time_string_width (EWeekView *week_view)
{
    ECalModel *model;
    gint time_width;

    model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view));

    if (week_view->use_small_font && week_view->small_font_desc)
        time_width = week_view->digit_width * 2
            + week_view->small_digit_width * 2;
    else
        time_width = week_view->digit_width * 4
            + week_view->colon_width;

    if (!e_cal_model_get_use_24_hour_format (model))
        time_width += MAX (week_view->am_string_width,
                   week_view->pm_string_width);

    return time_width;
}

/* Queues a layout, unless one is already queued. */
static void
e_week_view_queue_layout (EWeekView *week_view)
{
    if (week_view->layout_timeout_id == 0) {
        week_view->layout_timeout_id = g_timeout_add (E_WEEK_VIEW_LAYOUT_TIMEOUT, e_week_view_layout_timeout_cb, week_view);
    }
}

/* Removes any queued layout. */
static void
e_week_view_cancel_layout (EWeekView *week_view)
{
    if (week_view->layout_timeout_id != 0) {
        g_source_remove (week_view->layout_timeout_id);
        week_view->layout_timeout_id = 0;
    }
}

static gboolean
e_week_view_layout_timeout_cb (gpointer data)
{
    EWeekView *week_view = E_WEEK_VIEW (data);

    gtk_widget_queue_draw (week_view->main_canvas);
    e_week_view_check_layout (week_view);

    week_view->layout_timeout_id = 0;
    return FALSE;
}

/* Returns the number of selected events (0 or 1 at present). */
gint
e_week_view_get_num_events_selected (EWeekView *week_view)
{
    g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), 0);

    return (week_view->editing_event_num != -1) ? 1 : 0;
}

gboolean
e_week_view_is_jump_button_visible (EWeekView *week_view,
                                    gint day)
{
    g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);

    if ((day >= 0) && (day < E_WEEK_VIEW_MAX_WEEKS * 7))
        return week_view->jump_buttons[day]->flags & GNOME_CANVAS_ITEM_VISIBLE;
    return FALSE;
}

gboolean
e_week_view_is_editing (EWeekView *week_view)
{
    g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE);

    return week_view->editing_event_num != -1;
}