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

                                                     
                                                                


                                                               


                                                                  



                                                                    
                                                                             
  




                                                        

   
                    
                   
      
 
                                    
 

                            
                 
                   
                   
                    
                           
                       


                         
 
                                                       






                                                                            







































                                                                           
                                                                       




































































































                                                                                
                                                                        



















                                                                         


                    

















                                   
                                         




                                          




                     
                            
             

  

                                                          
                         




                                         
 


                                                      
                                    

                                         



                                                                  
 

                                                      






































































































































































































                                                    









                                                      


















































                                                       
 

























                                                                                
 
                                     

 











                                                        

                               

                              





                               
                                           
                                                     

                                    

                                                   
                                                   
                                                  
                                      
 




                          


                                       
                                       




                                            
                                            
                                               
 



                                              


                                             
           
                                         


                               
                                           

                                                                       
                                                                          
 



                                         
 
                                                   
                                                                   
                                                      

         


                                                                 
         



                                                                             
         
 

                                                 

                                                                        

 
           



                                                
 

                               
                                           
 

























                                                                          
                                                       


                                                                   
                                                       



















                                                                        


                                                                                  














                                                                                           
         

                                                                       

 
           



                                                  


                               
                                        



                        

                                           
 


                                                 

                                                         


                                                 

                                                        


                                                    

                                             
                                                                
                 


                                                    

                                             
                                                                
                 


                                                    

                                             
                                                                
                 


                                                    

                                             
                                                                
                 


                                                      


                                                                             



                                                        


                                                                                         



                                                        


                                                   
                                                                
                 


                                                 


                                                   
                                                                
                 


                                                 

                                                   
                                                                
                 


                                                 

                                                   
                                                                
                 


                                                 

                                                         
                                                                
                 


                                                     
                                                           
                                                            
                                                                
                 
                       


                                                                                   

                                                 
                                                                   


                                                 
                                                                          


                                                     
                                                             


                                                     
                                                           


                                                     
                                                                    
                       

         
                                                                       

 
           
                                               

                               
 

                                                                                           
 

                                         
                                                                       

                                                              

 
           
                                             









                                                                  

                                                                                         

 
           


                                                  
 
                                         

                               
                                                                              
                                                


                                        
 

                                                                            
                                                      

                                         
                                                                 

                                       





                                                                          


                                                                                 


                                                            
 




                                                                          
                                                           



                                                           
                                     



                                 



                                                          
                                             
                                  
                                                     




                                                        
                                             
                                  






                                                             

                                                                   


                                                                            
 


                                                                             


                                                          
                                                                                          









































                                                                                    


                                                 

                                                

 


                                                         
           





                                                   

                               

                                       
                                                             
                                              

                                       

                         

     


                                                        

                                                


                                                              



                                        



                                                              


                                                            



                                                                             
 




                                                            
 

                                          
                                                  



                                                     



                                                     











                                                         
 




                                                                     


                                                                               
                                                                 

                                
                                                          
                                 



                                                          
                                    


                                   












                                                                 
 



                                                                       














                                                                             

                         


                                                         





                                                

 
           


                                            
 
                               
 


                                                             


           







                                                   




                                        





                                                                
                                                                                             




                                       

     


                                                                             


                                           
                                              
 




                                                              


                                                            





                                                                             





                                                                              
 



                                                                          

                                                                              







                                                                          
                                                                         






                                                                           
                                                          








                                                                           
                                 


















                                                                           
                                
 

                                                                      

                                                     
 
                                                                              
 



                                               
 
                                                                                  
 





















































                                                                                                                                                      
 
                                   















                                                                

                        

                                             

                                                                
                                                                                     




















                                                                                          



                                                                     




                                                              
                                                                  
                                                        
                                                                        
                                       
                                
                                                                 



                                                                
                                                     

                                                                                 


                                      
                                   





                                                                                  

                                                                       




                                                                                            
                                          
                                                     
                                                              
 
                                
                           

 
                    
                      




                                




                                                            
 

                                

                                      
                                           


                                                                     
                             
         
      
 

                    

           










                                                         



                              
                                        









                                                                 
                                                          
                                                               
                        



                                       


                                           
                                              
 



                                             

                                                              


                                                            




                                                                             
                                                                                     





                                                                          
                     

















                                                                              
                                                             






                                                                            

                                                                         













                                                       

                                                              








                                                                         
                                                      




                                                        

                                                                           









                                                                                   


                                                                   



                                                                               


                                                           

                                        

                                                                    

                                                                              
                                                           
                                                               
                                                             




                                                  
                                                 

                                                                                                
 

















                                                                                                       
                                                                                         




                                                                       


                                               
                                                            

                                                                 












                                                             
                                                         

                                                                               

                                                                       













                                                            




                                                                                  



                                                                      







                                                                                   



                                                                          




                                                                        


                                                              







                                                                                          


                                                                           



                                                                      


                                                                   
 

                                                

                                                                    
                                        

                                                                                  


                                           

                                                                              
                                        

                                                                                
                                 

                                             

                                                                               
                                        

                                                                               

                                 

                                                                                      
                                                                 



                                                                       
 




                                                                           
                                                                           

















                                                                                
                                          
                                                                           
                                                                         
 
                                
 
                                                
 
 
    



                                                        
 

                               
                      
 

                                                     
 

                                                              
                                                 
 

                                                                                        
                                                                 



                                                              
         
 
                                                           
                                                     
 
                                                                            
                                                                 
                                                                           
                         
 
                                                
                               
 
                                                                            
                                                                      
                             
                           
 



                                                     
                                                                              

                                                                            
                        




                                             
 
                    

 
           

                                                       








                                                                         

                                                                               
                                                      


                                                 
                                                                                        


                                                 











                                                                       


                                                             




                                                        





                                                                                    







































                                                                                        



                                                                     


           

                                                        









                                                            
                        


                                                    
                      
                          


                                                    
                      
                          


                                                    
                      
                           


                                                    
                      

                            






                                                                          

           

                                             











                                                                       

                                                        
                             

                                                                        






                      
           




                                              



                                                     
 





                                         

                                                                       
                                            
           
                                                     


                                     
                                                                                     
                                                                 

                         



                                                      

                                                  
                                                                        
 


                       





                                                     




                                                            

                                                  



                                                                             
                          
                                       
                                                           



                                                                   
         
                                               



                                              
                                      
                                      
 
                                                                                     

                                                                               








                                                                                        
                                                                                       









                                                                                               

                 

                                                                           
 
                                                                                     
                                                

                                                                      


                                                                           

                                                                                    
                                                                      
         
 
                                                         





                                                                          
 




                                             
                                                                      










                                                                                        

                                       
                                                

 
           














                                                           
 



                                                                  
                                              
 


                          
 



                                                              
 
                  
                                                                               
 
                               
                                                                       

                       





                                                                     






                                                                                        
                 


         
               
                                                     
                                                     
 





                                  
                                             
                                                                          
 





                                                                        


                                                 
                                   


                                                 

                                                              

                                                                  



                                                                          
 
                                          
                                                             
                                                 
                                                             

                            
 
                                           
                             
 


                                           








                                              
                             
 




                                                                       

                                                                  





                                                                                       





                                                               

         
                                      




                                                             


                                                                          






                                                                  
                                                                

                                                         

         




                                                         



                                                                     



                                         
                         


                                                                      

                             


                                                                        



                                                                       

 
               
                                                       
                                                       
 




                                                             


                     
               

                                               
 


                                                                           



                                





















                                                                          
 








                                                                  

                                                         


                                                        

                                                                             


                                                                                 
                 
         
 
                                                                          
                                                             



                                                                              

                                                                             



                                                





                                                                       
                         


                                                    
                             


                                                      
 

                                                                          





                                                     
                





                                                       

         















                                                                       
           




                                                            































                                                                   

 
           




                                                              






























                                                                       
 
                                                        




                                                                              
               






                                                                        





                                                                

                                                                 


                                        


                                           
                                              




                                                                 


                                                            



                                                                             

                                       
 

                                                

                             








                                                     
                                                         
                             
                                                                  
                                              
 

                                                  


                                      




                                                                      
 






                                                                              
                             
                                           
                                                 

                             



















                                                                                     
                                                                          
                                                                          



                                                         
                                                             
 


                                                     

























                                                                           



                    
           





                                                         
 
                                                            




                                                           





                                                                      











                                                                               

                                                            
            

                                                       
 
    


                                                        


                                

 
           



                                                               
































                                                                

                                                                         


                                                        


                                                          
 

                          
                                                                        
 

                                                                     
 



                                                     
 



                                                                                             


                                                                                  
 


                                                   


                                                 





                                                                                    






                                                                                               
 
                                                                
 


                                                                                     

                                                                              

                                                                          
 
                                                                             
                                                                         

                                                   
 





                                                                                
 
                                                                



                                                                               
                         
 





                                                                                    
 
                                                                           


                                                                        
 
                                                                            


                                                                              





                                                          
 


                                                                       
 

                                               
                                                         



                                          
                                               
    

                                                         
 

                                                                       

 
                                                                    
    
                                                                



                                                     
                                                                    
    

                                                                



                                                     
        
                                                          



                                      
    

                                                          



                                         
                                                                           
                                                                    
    


                                                       










                                         
 
                                                                              

                                                                            
           

                                                              

                                        
 


                                                                     
 













                                                                          
 
                                                              


                                                                            
        






                                                       
 

                                                                 


                                                     
                                                                            
                                                      


                                                     







                                                               
                                                                           
                                                  



                                                     




                                                                        

                    
 
 
                                               
                                                   
    
                                                    



                                           
 

                                 
 


                                                 
 
 
                                                                                    
    





                                                 

                          
                   







                                                                                            


                                                             

                                                                       

 
    








                                                  
 
                                                 
 

























                                                                                            

                           


                                                                      







                                                                                      


                                                                     








                                                            

                                                                       

 

                                                     


                                                           



























                                                                

                                                         


                                                             



                                                       


























                                                                

 
           




                                                            
 

















                                                                            
 
                                        
 

                    
 
                                                                          
            
           


                                                         

                                        
 


                                                                
 




                                                    
        


                                                      






                                                
                                    

















                                                                             
           



                                                                   







                                                        

                                                                             
                          









                                                                               
 
                                                                      
 





                                                        
 








                                                           









                                                                        

                                                                              



                                                                          

                                                          
                                                                        
                 
                                              



                                                                               
 


                                                                                    

         



                                                                        
                                                                               
         

 
    

                                                  
 
                        

                        


                                               

                                                                  
                                               

                                                                     
                                               

                                                                             
                                                  

                                                                     
                                                  
                                                                              

                                               

 
    


                                                       


                                                                            


                                                    






                                                                        

                                                                          


                                                                              
               







                                                            

                                                 
                          



                                                                 



                                                                


                                           

                                                        

                                                             

                                                                                   
                                                                          
                                                     


                                                
 




                                                
 
                                                                                      







                                                                      
 


                                                









                                                                                   


                                                


                                                                            
                                                                         





                                                                             
                                                                            

                                                 


                                                
 




                                                






                                                                         
                                    




                                                             















                                                                              
           
                                                        
                                                        
                                                   
 
                                                     
                         
                          
                         

                               











                                                                              





                                                           

                                                      
                                                                   
 
                                                        


                                                                              
                                                     



                                                                            


                                                             
 



                                                                                   


                 



                                                       


                                                           


                                                       
                                          
 
 
           

                                                              
 
                          
                                       

                      

                                                             

                                      

                                                                     
                                              
 

                                                                





                                                                
           




                                                  



                                         
                                                                              
 


                                                                              








                                                               

                                                                              



                                                                  


                                                        
                                                                            






                                                                                  
           
                                                           






                                                        
           
                                                              

                                                    



                                                                    


         
               
                                                       




                                                                



                                             
                                                                         
                                                    
                                 
 
                                          
                                                    
                                                                                        

         
                                         
                                                   
                                                                                       

         
                                   
 

                     
 
                                                                        

                                                                         
    



                                                                       









                                                                                
/*
 * ECalendarItem - canvas item displaying a calendar.
 *
 * 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>
 *      Bolian Yin <bolian.yin@sun.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 */

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

#include <libebackend/libebackend.h>

#include "e-calendar-item.h"

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

#include "ea-widgets.h"
#include "e-misc-utils.h"

static const gint e_calendar_item_days_in_month[12] = {
    31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

#define DAYS_IN_MONTH(year, month) \
  e_calendar_item_days_in_month[month] + (((month) == 1 \
  && ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))) ? 1 : 0)

static void e_calendar_item_dispose     (GObject *object);
static void e_calendar_item_get_property    (GObject *object,
                         guint property_id,
                         GValue *value,
                         GParamSpec *pspec);
static void e_calendar_item_set_property    (GObject *object,
                         guint property_id,
                         const GValue *value,
                         GParamSpec *pspec);
static void e_calendar_item_realize     (GnomeCanvasItem *item);
static void e_calendar_item_unmap       (GnomeCanvasItem *item);
static void e_calendar_item_update      (GnomeCanvasItem *item,
                         const cairo_matrix_t *i2c,
                         gint flags);
static void e_calendar_item_draw        (GnomeCanvasItem *item,
                         cairo_t *cr,
                         gint x,
                         gint y,
                         gint width,
                         gint height);
static void e_calendar_item_draw_month  (ECalendarItem *calitem,
                         cairo_t *cr,
                         gint x,
                         gint y,
                         gint width,
                         gint height,
                         gint row,
                         gint col);
static void e_calendar_item_draw_day_numbers
                        (ECalendarItem *calitem,
                         cairo_t *cr,
                         gint width,
                         gint height,
                         gint row,
                         gint col,
                         gint year,
                         gint month,
                         gint start_weekday,
                         gint cells_x,
                         gint cells_y);
static GnomeCanvasItem *e_calendar_item_point   (GnomeCanvasItem *item,
                         gdouble x,
                         gdouble y,
                         gint cx,
                         gint cy);
static void e_calendar_item_stop_selecting  (ECalendarItem *calitem,
                         guint32 time);
static void e_calendar_item_selection_add_days
                        (ECalendarItem *calitem,
                         gint n_days,
                         gboolean multi_selection);
static gint e_calendar_item_key_press_event (ECalendarItem *item,
                         GdkEvent *event);
static gint e_calendar_item_event       (GnomeCanvasItem *item,
                         GdkEvent *event);
static void e_calendar_item_bounds      (GnomeCanvasItem *item,
                         gdouble *x1,
                         gdouble *y1,
                         gdouble *x2,
                         gdouble *y2);

static gboolean e_calendar_item_button_press    (ECalendarItem *calitem,
                         GdkEvent *event);
static gboolean e_calendar_item_button_release  (ECalendarItem *calitem,
                         GdkEvent *event);
static gboolean e_calendar_item_motion      (ECalendarItem *calitem,
                         GdkEvent *event);

static gboolean e_calendar_item_convert_position_to_day
                        (ECalendarItem *calitem,
                         gint x,
                         gint y,
                         gboolean round_empty_positions,
                         gint *month_offset,
                         gint *day,
                         gboolean *entire_week);
static void e_calendar_item_get_month_info  (ECalendarItem *calitem,
                         gint row,
                         gint col,
                         gint *first_day_offset,
                         gint *days_in_month,
                         gint *days_in_prev_month);
static void e_calendar_item_recalc_sizes    (ECalendarItem *calitem);

static void e_calendar_item_get_day_style   (ECalendarItem *calitem,
                         gint year,
                         gint month,
                         gint day,
                         gint day_style,
                         gboolean today,
                         gboolean prev_or_next_month,
                         gboolean selected,
                         gboolean has_focus,
                         gboolean drop_target,
                         GdkColor **bg_color,
                         GdkColor **fg_color,
                         GdkColor **box_color,
                         gboolean *bold,
                         gboolean *italic);
static void e_calendar_item_check_selection_end
                        (ECalendarItem *calitem,
                         gint start_month,
                         gint start_day,
                         gint *end_month,
                         gint *end_day);
static void e_calendar_item_check_selection_start
                        (ECalendarItem *calitem,
                         gint *start_month,
                         gint *start_day,
                         gint end_month,
                         gint end_day);
static void e_calendar_item_add_days_to_selection
                        (ECalendarItem *calitem,
                         gint days);
static void e_calendar_item_round_up_selection
                        (ECalendarItem *calitem,
                         gint *month_offset,
                         gint *day);
static void e_calendar_item_round_down_selection
                        (ECalendarItem *calitem,
                         gint *month_offset,
                         gint *day);
static gint e_calendar_item_get_inclusive_days
                        (ECalendarItem *calitem,
                         gint start_month_offset,
                         gint start_day,
                         gint end_month_offset,
                         gint end_day);
static void e_calendar_item_ensure_valid_day
                        (ECalendarItem *calitem,
                         gint *month_offset,
                         gint *day);
static gboolean e_calendar_item_ensure_days_visible
                        (ECalendarItem *calitem,
                         gint start_year,
                         gint start_month,
                         gint start_day,
                         gint end_year,
                         gint end_month,
                         gint end_day,
                         gboolean emission);
static void e_calendar_item_show_popup_menu (ECalendarItem *calitem,
                         GdkEvent *button_event,
                         gint month_offset);
static void e_calendar_item_on_menu_item_activate
                        (GtkWidget *menuitem,
                         ECalendarItem *calitem);
static void e_calendar_item_position_menu   (GtkMenu *menu,
                         gint *x,
                         gint *y,
                         gboolean *push_in,
                         gpointer user_data);
static void e_calendar_item_date_range_changed
                        (ECalendarItem *calitem);
static void e_calendar_item_queue_signal_emission
                        (ECalendarItem *calitem);
static gboolean e_calendar_item_signal_emission_idle_cb
                        (gpointer data);
static void e_calendar_item_set_selection_if_emission
                        (ECalendarItem *calitem,
                         const GDate *start_date,
                         const GDate *end_date,
                         gboolean emission);

/* Our arguments. */
enum {
    PROP_0,
    PROP_YEAR,
    PROP_MONTH,
    PROP_X1,
    PROP_Y1,
    PROP_X2,
    PROP_Y2,
    PROP_FONT_DESC,
    PROP_WEEK_NUMBER_FONT,
    PROP_WEEK_NUMBER_FONT_DESC,
    PROP_ROW_HEIGHT,
    PROP_COLUMN_WIDTH,
    PROP_MINIMUM_ROWS,
    PROP_MINIMUM_COLUMNS,
    PROP_MAXIMUM_ROWS,
    PROP_MAXIMUM_COLUMNS,
    PROP_WEEK_START_DAY,
    PROP_SHOW_WEEK_NUMBERS,
    PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK,
    PROP_MAXIMUM_DAYS_SELECTED,
    PROP_DAYS_TO_START_WEEK_SELECTION,
    PROP_MOVE_SELECTION_WHEN_MOVING,
    PROP_PRESERVE_DAY_WHEN_MOVING,
    PROP_DISPLAY_POPUP
};

enum {
  DATE_RANGE_CHANGED,
  SELECTION_CHANGED,
  SELECTION_PREVIEW_CHANGED,
  LAST_SIGNAL
};

static guint e_calendar_item_signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE_WITH_CODE (
    ECalendarItem,
    e_calendar_item,
    GNOME_TYPE_CANVAS_ITEM,
    G_IMPLEMENT_INTERFACE (
        E_TYPE_EXTENSIBLE, NULL))

static void
e_calendar_item_class_init (ECalendarItemClass *class)
{
    GObjectClass  *object_class;
    GnomeCanvasItemClass *item_class;

    object_class = G_OBJECT_CLASS (class);
    object_class->dispose = e_calendar_item_dispose;
    object_class->get_property = e_calendar_item_get_property;
    object_class->set_property = e_calendar_item_set_property;

    item_class = GNOME_CANVAS_ITEM_CLASS (class);
    item_class->realize = e_calendar_item_realize;
    item_class->unmap = e_calendar_item_unmap;
    item_class->update = e_calendar_item_update;
    item_class->draw = e_calendar_item_draw;
    item_class->point = e_calendar_item_point;
    item_class->event = e_calendar_item_event;
    item_class->bounds = e_calendar_item_bounds;

    class->date_range_changed = NULL;
    class->selection_changed = NULL;
    class->selection_preview_changed = NULL;

    g_object_class_install_property (
        object_class,
        PROP_YEAR,
        g_param_spec_int (
            "year",
            NULL,
            NULL,
            G_MININT,
            G_MAXINT,
            0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_MONTH,
        g_param_spec_int (
            "month",
            NULL,
            NULL,
            G_MININT,
            G_MAXINT,
            0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_X1,
        g_param_spec_double (
            "x1",
            NULL,
            NULL,
            -G_MAXDOUBLE,
            G_MAXDOUBLE,
            0.,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_Y1,
        g_param_spec_double (
            "y1",
            NULL,
            NULL,
            -G_MAXDOUBLE,
            G_MAXDOUBLE,
            0.,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_X2,
        g_param_spec_double (
            "x2",
            NULL,
            NULL,
            -G_MAXDOUBLE,
            G_MAXDOUBLE,
            0.,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_Y2,
        g_param_spec_double (
            "y2",
            NULL,
            NULL,
            -G_MAXDOUBLE,
            G_MAXDOUBLE,
            0.,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_FONT_DESC,
        g_param_spec_boxed (
            "font_desc",
            NULL,
            NULL,
            PANGO_TYPE_FONT_DESCRIPTION,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_WEEK_NUMBER_FONT_DESC,
        g_param_spec_boxed (
            "week_number_font_desc",
            NULL,
            NULL,
            PANGO_TYPE_FONT_DESCRIPTION,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_ROW_HEIGHT,
        g_param_spec_int (
            "row_height",
            NULL,
            NULL,
            G_MININT,
            G_MAXINT,
            0,
            G_PARAM_READABLE));

    g_object_class_install_property (
        object_class,
        PROP_COLUMN_WIDTH,
        g_param_spec_int (
            "column_width",
            NULL,
            NULL,
            G_MININT,
            G_MAXINT,
            0,
            G_PARAM_READABLE));

    g_object_class_install_property (
        object_class,
        PROP_MINIMUM_ROWS,
        g_param_spec_int (
            "minimum_rows",
            NULL,
            NULL,
            G_MININT,
            G_MAXINT,
            0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_MINIMUM_COLUMNS,
        g_param_spec_int (
            "minimum_columns",
            NULL,
            NULL,
            G_MININT,
            G_MAXINT,
            0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_MAXIMUM_ROWS,
        g_param_spec_int (
            "maximum_rows",
            NULL,
            NULL,
            G_MININT,
            G_MAXINT,
            0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_MAXIMUM_COLUMNS,
        g_param_spec_int (
            "maximum_columns",
            NULL,
            NULL,
            G_MININT,
            G_MAXINT,
            0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_WEEK_START_DAY,
        g_param_spec_int (
            "week_start_day",
            NULL,
            NULL,
            G_MININT,
            G_MAXINT,
            0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_SHOW_WEEK_NUMBERS,
        g_param_spec_boolean (
            "show_week_numbers",
            NULL,
            NULL,
            TRUE,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK,
        g_param_spec_boolean (
            "keep_wdays_on_weeknum_click",
            NULL,
            NULL,
            FALSE,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_MAXIMUM_DAYS_SELECTED,
        g_param_spec_int (
            "maximum_days_selected",
            NULL,
            NULL,
            G_MININT,
            G_MAXINT,
            0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_DAYS_TO_START_WEEK_SELECTION,
        g_param_spec_int (
            "days_to_start_week_selection",
            NULL,
            NULL,
            G_MININT,
            G_MAXINT,
            0,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_MOVE_SELECTION_WHEN_MOVING,
        g_param_spec_boolean (
            "move_selection_when_moving",
            NULL,
            NULL,
            TRUE,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_PRESERVE_DAY_WHEN_MOVING,
        g_param_spec_boolean (
            "preserve_day_when_moving",
            NULL,
            NULL,
            TRUE,
            G_PARAM_READWRITE));

    g_object_class_install_property (
        object_class,
        PROP_DISPLAY_POPUP,
        g_param_spec_boolean (
            "display_popup",
            NULL,
            NULL,
            TRUE,
            G_PARAM_READWRITE));

    e_calendar_item_signals[DATE_RANGE_CHANGED] = g_signal_new (
        "date_range_changed",
        G_TYPE_FROM_CLASS (object_class),
        G_SIGNAL_RUN_FIRST,
        G_STRUCT_OFFSET (ECalendarItemClass, date_range_changed),
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);

    e_calendar_item_signals[SELECTION_CHANGED] = g_signal_new (
        "selection_changed",
        G_TYPE_FROM_CLASS (object_class),
        G_SIGNAL_RUN_FIRST,
        G_STRUCT_OFFSET (ECalendarItemClass, selection_changed),
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);

    e_calendar_item_signals[SELECTION_PREVIEW_CHANGED] = g_signal_new (
        "selection_preview_changed",
        G_TYPE_FROM_CLASS (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ECalendarItemClass, selection_preview_changed),
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);

    e_calendar_item_a11y_init ();
}

static void
e_calendar_item_init (ECalendarItem *calitem)
{
    struct tm *tmp_tm;
    time_t t;

    /* Set the default time to the current month. */
    t = time (NULL);
    tmp_tm = localtime (&t);
    calitem->year = tmp_tm->tm_year + 1900;
    calitem->month = tmp_tm->tm_mon;

    calitem->styles = NULL;

    calitem->min_cols = 1;
    calitem->min_rows = 1;
    calitem->max_cols = -1;
    calitem->max_rows = -1;

    calitem->rows = 0;
    calitem->cols = 0;

    calitem->show_week_numbers = FALSE;
    calitem->keep_wdays_on_weeknum_click = FALSE;
    calitem->week_start_day = 0;
    calitem->expand = TRUE;
    calitem->max_days_selected = 1;
    calitem->days_to_start_week_selection = -1;
    calitem->move_selection_when_moving = TRUE;
    calitem->preserve_day_when_moving = FALSE;
    calitem->display_popup = TRUE;

    calitem->x1 = 0.0;
    calitem->y1 = 0.0;
    calitem->x2 = 0.0;
    calitem->y2 = 0.0;

    calitem->selecting = FALSE;
    calitem->selecting_axis = NULL;

    calitem->selection_set = FALSE;

    calitem->selection_changed = FALSE;
    calitem->date_range_changed = FALSE;

    calitem->style_callback = NULL;
    calitem->style_callback_data = NULL;
    calitem->style_callback_destroy = NULL;

    calitem->time_callback = NULL;
    calitem->time_callback_data = NULL;
    calitem->time_callback_destroy = NULL;

    calitem->signal_emission_idle_id = 0;
}

static void
e_calendar_item_dispose (GObject *object)
{
    ECalendarItem *calitem;

    calitem = E_CALENDAR_ITEM (object);

    e_calendar_item_set_style_callback (calitem, NULL, NULL, NULL);
    e_calendar_item_set_get_time_callback (calitem, NULL, NULL, NULL);

    if (calitem->styles) {
        g_free (calitem->styles);
        calitem->styles = NULL;
    }

    if (calitem->signal_emission_idle_id > 0) {
        g_source_remove (calitem->signal_emission_idle_id);
        calitem->signal_emission_idle_id = -1;
    }

    if (calitem->font_desc) {
        pango_font_description_free (calitem->font_desc);
        calitem->font_desc = NULL;
    }

    if (calitem->week_number_font_desc) {
        pango_font_description_free (calitem->week_number_font_desc);
        calitem->week_number_font_desc = NULL;
    }

    if (calitem->selecting_axis)
        g_free (calitem->selecting_axis);

    G_OBJECT_CLASS (e_calendar_item_parent_class)->dispose (object);
}

static void
e_calendar_item_get_property (GObject *object,
                              guint property_id,
                              GValue *value,
                              GParamSpec *pspec)
{
    ECalendarItem *calitem;

    calitem = E_CALENDAR_ITEM (object);

    switch (property_id) {
    case PROP_YEAR:
        g_value_set_int (value, calitem->year);
        return;
    case PROP_MONTH:
        g_value_set_int (value, calitem->month);
        return;
    case PROP_X1:
        g_value_set_double (value, calitem->x1);
        return;
    case PROP_Y1:
        g_value_set_double (value, calitem->y1);
        return;
    case PROP_X2:
        g_value_set_double (value, calitem->x2);
        return;
    case PROP_Y2:
        g_value_set_double (value, calitem->y2);
        return;
    case PROP_FONT_DESC:
        g_value_set_boxed (value, calitem->font_desc);
        return;
    case PROP_WEEK_NUMBER_FONT_DESC:
        g_value_set_boxed (value, calitem->week_number_font_desc);
        return;
    case PROP_ROW_HEIGHT:
        e_calendar_item_recalc_sizes (calitem);
        g_value_set_int (value, calitem->min_month_height);
        return;
    case PROP_COLUMN_WIDTH:
        e_calendar_item_recalc_sizes (calitem);
        g_value_set_int (value, calitem->min_month_width);
        return;
    case PROP_MINIMUM_ROWS:
        g_value_set_int (value, calitem->min_rows);
        return;
    case PROP_MINIMUM_COLUMNS:
        g_value_set_int (value, calitem->min_cols);
        return;
    case PROP_MAXIMUM_ROWS:
        g_value_set_int (value, calitem->max_rows);
        return;
    case PROP_MAXIMUM_COLUMNS:
        g_value_set_int (value, calitem->max_cols);
        return;
    case PROP_WEEK_START_DAY:
        g_value_set_int (value, calitem->week_start_day);
        return;
    case PROP_SHOW_WEEK_NUMBERS:
        g_value_set_boolean (value, calitem->show_week_numbers);
        return;
    case PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK:
        g_value_set_boolean (value, calitem->keep_wdays_on_weeknum_click);
        return;
    case PROP_MAXIMUM_DAYS_SELECTED:
        g_value_set_int (value, e_calendar_item_get_max_days_sel (calitem));
        return;
    case PROP_DAYS_TO_START_WEEK_SELECTION:
        g_value_set_int (value, e_calendar_item_get_days_start_week_sel (calitem));
        return;
    case PROP_MOVE_SELECTION_WHEN_MOVING:
        g_value_set_boolean (value, calitem->move_selection_when_moving);
        return;
    case PROP_PRESERVE_DAY_WHEN_MOVING:
        g_value_set_boolean (value, calitem->preserve_day_when_moving);
        return;
    case PROP_DISPLAY_POPUP:
        g_value_set_boolean (value, e_calendar_item_get_display_popup (calitem));
        return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
e_calendar_item_set_property (GObject *object,
                              guint property_id,
                              const GValue *value,
                              GParamSpec *pspec)
{
    GnomeCanvasItem *item;
    ECalendarItem *calitem;
    PangoFontDescription *font_desc;
    gdouble dvalue;
    gint ivalue;
    gboolean bvalue;

    item = GNOME_CANVAS_ITEM (object);
    calitem = E_CALENDAR_ITEM (object);

    switch (property_id) {
    case PROP_YEAR:
        ivalue = g_value_get_int (value);
        e_calendar_item_set_first_month (
            calitem, ivalue, calitem->month);
        return;
    case PROP_MONTH:
        ivalue = g_value_get_int (value);
        e_calendar_item_set_first_month (
            calitem, calitem->year, ivalue);
        return;
    case PROP_X1:
        dvalue = g_value_get_double (value);
        if (calitem->x1 != dvalue) {
            calitem->x1 = dvalue;
            gnome_canvas_item_request_update (item);
        }
        return;
    case PROP_Y1:
        dvalue = g_value_get_double (value);
        if (calitem->y1 != dvalue) {
            calitem->y1 = dvalue;
            gnome_canvas_item_request_update (item);
        }
        return;
    case PROP_X2:
        dvalue = g_value_get_double (value);
        if (calitem->x2 != dvalue) {
            calitem->x2 = dvalue;
            gnome_canvas_item_request_update (item);
        }
        return;
    case PROP_Y2:
        dvalue = g_value_get_double (value);
        if (calitem->y2 != dvalue) {
            calitem->y2 = dvalue;
            gnome_canvas_item_request_update (item);
        }
        return;
    case PROP_FONT_DESC:
        font_desc = g_value_get_boxed (value);
        if (calitem->font_desc)
            pango_font_description_free (calitem->font_desc);
        calitem->font_desc = pango_font_description_copy (font_desc);
        gnome_canvas_item_request_update (item);
        return;
    case PROP_WEEK_NUMBER_FONT_DESC:
        font_desc = g_value_get_boxed (value);
        if (calitem->week_number_font_desc)
            pango_font_description_free (calitem->week_number_font_desc);
        calitem->week_number_font_desc = pango_font_description_copy (font_desc);
        gnome_canvas_item_request_update (item);
        return;
    case PROP_MINIMUM_ROWS:
        ivalue = g_value_get_int (value);
        ivalue = MAX (1, ivalue);
        if (calitem->min_rows != ivalue) {
            calitem->min_rows = ivalue;
            gnome_canvas_item_request_update (item);
        }
        return;
    case PROP_MINIMUM_COLUMNS:
        ivalue = g_value_get_int (value);
        ivalue = MAX (1, ivalue);
        if (calitem->min_cols != ivalue) {
            calitem->min_cols = ivalue;
            gnome_canvas_item_request_update (item);
        }
        return;
    case PROP_MAXIMUM_ROWS:
        ivalue = g_value_get_int (value);
        if (calitem->max_rows != ivalue) {
            calitem->max_rows = ivalue;
            gnome_canvas_item_request_update (item);
        }
        return;
    case PROP_MAXIMUM_COLUMNS:
        ivalue = g_value_get_int (value);
        if (calitem->max_cols != ivalue) {
            calitem->max_cols = ivalue;
            gnome_canvas_item_request_update (item);
        }
        return;
    case PROP_WEEK_START_DAY:
        ivalue = g_value_get_int (value);
        if (calitem->week_start_day != ivalue) {
            calitem->week_start_day = ivalue;
            gnome_canvas_item_request_update (item);
        }
        return;
    case PROP_SHOW_WEEK_NUMBERS:
        bvalue = g_value_get_boolean (value);
        if (calitem->show_week_numbers != bvalue) {
            calitem->show_week_numbers = bvalue;
            gnome_canvas_item_request_update (item);
        }
        return;
    case PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK:
        calitem->keep_wdays_on_weeknum_click = g_value_get_boolean (value);
        return;
    case PROP_MAXIMUM_DAYS_SELECTED:
        ivalue = g_value_get_int (value);
        e_calendar_item_set_max_days_sel (calitem, ivalue);
        return;
    case PROP_DAYS_TO_START_WEEK_SELECTION:
        ivalue = g_value_get_int (value);
        e_calendar_item_set_days_start_week_sel (calitem, ivalue);
        return;
    case PROP_MOVE_SELECTION_WHEN_MOVING:
        bvalue = g_value_get_boolean (value);
        calitem->move_selection_when_moving = bvalue;
        return;
    case PROP_PRESERVE_DAY_WHEN_MOVING:
        bvalue = g_value_get_boolean (value);
        calitem->preserve_day_when_moving = bvalue;
        return;
    case PROP_DISPLAY_POPUP:
        bvalue = g_value_get_boolean (value);
        e_calendar_item_set_display_popup (calitem, bvalue);
        return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
e_calendar_item_realize (GnomeCanvasItem *item)
{
    ECalendarItem *calitem;

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

    calitem = E_CALENDAR_ITEM (item);

    e_calendar_item_style_set (GTK_WIDGET (item->canvas), calitem);

    e_extensible_load_extensions (E_EXTENSIBLE (calitem));
}

static void
e_calendar_item_unmap (GnomeCanvasItem *item)
{
    ECalendarItem *calitem;

    calitem = E_CALENDAR_ITEM (item);

    if (calitem->selecting) {
        gnome_canvas_item_ungrab (item, GDK_CURRENT_TIME);
        calitem->selecting = FALSE;
    }

    if (GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->unmap)
        (* GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->unmap) (item);
}

static void
e_calendar_item_update (GnomeCanvasItem *item,
                        const cairo_matrix_t *i2c,
                        gint flags)
{
    GnomeCanvasItemClass *item_class;
    ECalendarItem *calitem;
    GtkStyle *style;
    gint char_height, width, height, space, space_per_cal, space_per_cell;
    gint rows, cols, xthickness, ythickness;
    PangoFontDescription *font_desc;
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;

    item_class = GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class);
    if (item_class->update != NULL)
        item_class->update (item, i2c, flags);

    calitem = E_CALENDAR_ITEM (item);
    style = gtk_widget_get_style (GTK_WIDGET (item->canvas));
    xthickness = style->xthickness;
    ythickness = style->ythickness;

    item->x1 = calitem->x1;
    item->y1 = calitem->y1;
    item->x2 = calitem->x2 >= calitem->x1 ? calitem->x2 : calitem->x1;
    item->y2 = calitem->y2 >= calitem->y1 ? calitem->y2 : calitem->y1;

    /* Set up Pango prerequisites */
    font_desc = style->font_desc;
    pango_context = gtk_widget_get_pango_context (GTK_WIDGET (item->canvas));
    font_metrics = pango_context_get_metrics (
        pango_context, font_desc,
        pango_context_get_language (pango_context));

    /*
     * Calculate the new layout of the calendar.
     */

    /* Make sure the minimum row width & cell height and the widths of
     * all the digits and characters are up to date. */
    e_calendar_item_recalc_sizes (calitem);

    /* Calculate how many rows & cols we can fit in. */
    width = item->x2 - item->x1;
    height = item->y2 - item->y1;

    width -= xthickness * 2;
    height -= ythickness * 2;

    if (calitem->min_month_height == 0)
        rows = 1;
    else
        rows = height / calitem->min_month_height;
    rows = MAX (rows, calitem->min_rows);
    if (calitem->max_rows > 0)
        rows = MIN (rows, calitem->max_rows);

    if (calitem->min_month_width == 0)
        cols = 1;
    else
        cols = width / calitem->min_month_width;
    cols = MAX (cols, calitem->min_cols);
    if (calitem->max_cols > 0)
        cols = MIN (cols, calitem->max_cols);

    if (rows != calitem->rows || cols != calitem->cols)
        e_calendar_item_date_range_changed (calitem);

    calitem->rows = rows;
    calitem->cols = cols;

    /* Split up the empty space according to the configuration.
     * If the calendar is set to expand, we divide the space between the
     * cells and the spaces around the calendar, otherwise we place the
     * calendars in the center of the available area. */

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

    calitem->month_width = calitem->min_month_width;
    calitem->month_height = calitem->min_month_height;
    calitem->cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
        + E_CALENDAR_ITEM_MIN_CELL_XPAD;
    calitem->cell_height = char_height
        + E_CALENDAR_ITEM_MIN_CELL_YPAD;
    calitem->month_tpad = 0;
    calitem->month_bpad = 0;
    calitem->month_lpad = 0;
    calitem->month_rpad = 0;

    space = height - calitem->rows * calitem->month_height;
    if (space > 0) {
        space_per_cal = space / calitem->rows;
        calitem->month_height += space_per_cal;

        if (calitem->expand) {
            space_per_cell = space_per_cal / E_CALENDAR_ROWS_PER_MONTH;
            calitem->cell_height += space_per_cell;
            space_per_cal -= space_per_cell * E_CALENDAR_ROWS_PER_MONTH;
        }

        calitem->month_tpad = space_per_cal / 2;
        calitem->month_bpad = space_per_cal - calitem->month_tpad;
    }

    space = width - calitem->cols * calitem->month_width;
    if (space > 0) {
        space_per_cal = space / calitem->cols;
        calitem->month_width += space_per_cal;
        space -= space_per_cal * calitem->cols;

        if (calitem->expand) {
            space_per_cell = space_per_cal / E_CALENDAR_COLS_PER_MONTH;
            calitem->cell_width += space_per_cell;
            space_per_cal -= space_per_cell * E_CALENDAR_COLS_PER_MONTH;
        }

        calitem->month_lpad = space_per_cal / 2;
        calitem->month_rpad = space_per_cal - calitem->month_lpad;
    }

    space = MAX (0, space);
    calitem->x_offset = space / 2;

    gnome_canvas_request_redraw (
        item->canvas, item->x1, item->y1,
        item->x2, item->y2);

    pango_font_metrics_unref (font_metrics);
}

/*
 * DRAWING ROUTINES - functions to paint the canvas item.
 */
static void
e_calendar_item_draw (GnomeCanvasItem *canvas_item,
                      cairo_t *cr,
                      gint x,
                      gint y,
                      gint width,
                      gint height)
{
    ECalendarItem *calitem;
    GtkWidget *widget;
    GtkStyleContext *style_context;
    gint char_height, row, col, row_y, bar_height, col_x;
    const PangoFontDescription *font_desc;
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;
    GdkRGBA bg_color;
    GtkBorder border;

#if 0
    g_print (
        "In e_calendar_item_draw %i,%i %ix%i\n",
        x, y, width, height);
#endif
    calitem = E_CALENDAR_ITEM (canvas_item);

    widget = GTK_WIDGET (canvas_item->canvas);
    style_context = gtk_widget_get_style_context (widget);

    /* Set up Pango prerequisites */
    font_desc = calitem->font_desc;
    if (!font_desc)
        font_desc = gtk_style_context_get_font (
            style_context, GTK_STATE_FLAG_NORMAL);
    pango_context = gtk_widget_get_pango_context (
        GTK_WIDGET (canvas_item->canvas));
    font_metrics = pango_context_get_metrics (
        pango_context, font_desc,
        pango_context_get_language (pango_context));

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

    gtk_style_context_get_background_color (
        style_context, GTK_STATE_NORMAL, &bg_color);

    gtk_style_context_get_border (
        style_context, GTK_STATE_NORMAL, &border);

    /* Clear the entire background. */
    cairo_save (cr);
    gdk_cairo_set_source_rgba (cr, &bg_color);
    cairo_rectangle (
        cr, calitem->x1 - x, calitem->y1 - y,
        calitem->x2 - calitem->x1 + 1,
        calitem->y2 - calitem->y1 + 1);
    cairo_fill (cr);
    cairo_restore (cr);

    /* Draw the shadow around the entire item. */
    gtk_style_context_save (style_context);
    gtk_style_context_add_class (
        style_context, GTK_STYLE_CLASS_ENTRY);
    cairo_save (cr);
    gtk_render_frame (
        style_context, cr,
        (gdouble) calitem->x1 - x,
        (gdouble) calitem->y1 - y,
        (gdouble) calitem->x2 - calitem->x1 + 1,
        (gdouble) calitem->y2 - calitem->y1 + 1);
    cairo_restore (cr);
    gtk_style_context_restore (style_context);

    row_y = canvas_item->y1 + border.top;
    bar_height =
        border.top + border.bottom +
        E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + char_height +
        E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME;

    for (row = 0; row < calitem->rows; row++) {
        /* Draw the background for the title bars and the shadow around
         * it, and the vertical lines between columns. */

        cairo_save (cr);
        gdk_cairo_set_source_rgba (cr, &bg_color);
        cairo_rectangle (
            cr, calitem->x1 + border.left - x,
            row_y - y,
            calitem->x2 - calitem->x1 + 1 -
            (border.left + border.right),
            bar_height);
        cairo_fill (cr);
        cairo_restore (cr);

        gtk_style_context_save (style_context);
        gtk_style_context_add_class (
            style_context, GTK_STYLE_CLASS_HEADER);
        cairo_save (cr);
        gtk_render_frame (
            style_context, cr,
            (gdouble) calitem->x1 + border.left - x,
            (gdouble) row_y - y,
            (gdouble) calitem->x2 - calitem->x1 + 1 -
                (border.left + border.right),
            (gdouble) bar_height);
        cairo_restore (cr);
        gtk_style_context_restore (style_context);

        for (col = 0; col < calitem->cols; col++) {
            if (col != 0) {
                col_x = calitem->x1 + calitem->x_offset
                    + calitem->month_width * col;

                gtk_style_context_save (style_context);
                gtk_style_context_add_class (
                    style_context,
                    GTK_STYLE_CLASS_SEPARATOR);
                cairo_save (cr);
                gtk_render_line (
                    style_context, cr,
                    (gdouble) col_x - 1 - x,
                    (gdouble) row_y + border.top + 1 - y,
                    (gdouble) row_y + bar_height -
                        border.bottom - 2 - y,
                    (gdouble) col_x - x);
                cairo_restore (cr);
                gtk_style_context_restore (style_context);
            }

            e_calendar_item_draw_month (
                calitem, cr, x, y,
                width, height, row, col);
        }

        row_y += calitem->month_height;
    }

    pango_font_metrics_unref (font_metrics);
}

static void
layout_set_day_text (ECalendarItem *calitem,
                     PangoLayout *layout,
                     gint day_index)
{
    const gchar *abbr_name;

    /* day_index: 0 = Monday ... 6 = Sunday */
    abbr_name = e_get_weekday_name (day_index + 1, TRUE);
    pango_layout_set_text (layout, abbr_name, -1);
}

static void
e_calendar_item_draw_month (ECalendarItem *calitem,
                            cairo_t *cr,
                            gint x,
                            gint y,
                            gint width,
                            gint height,
                            gint row,
                            gint col)
{
    GnomeCanvasItem *item;
    GtkWidget *widget;
    GtkStyle *style;
    PangoFontDescription *font_desc;
    struct tm tmp_tm;
    GdkRectangle clip_rect;
    gint char_height, xthickness, ythickness, start_weekday;
    gint year, month;
    gint month_x, month_y, month_w, month_h;
    gint min_x, max_x, text_x, text_y;
    gint day, day_index, cells_x, cells_y, min_cell_width, text_width, arrow_button_size;
    gint clip_width, clip_height;
    gchar buffer[64];
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;
    PangoLayout *layout;

#if 0
    g_print (
        "In e_calendar_item_draw_month: %i,%i %ix%i row:%i col:%i\n",
        x, y, width, height, row, col);
#endif
    item = GNOME_CANVAS_ITEM (calitem);
    widget = GTK_WIDGET (item->canvas);
    style = gtk_widget_get_style (widget);

    /* Set up Pango prerequisites */
    font_desc = calitem->font_desc;
    if (!font_desc)
        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));

    char_height =
        PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
        PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
    xthickness = style->xthickness;
    ythickness = style->ythickness;
    arrow_button_size =
        PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics))
        + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
        + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
        + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
        + 2 * xthickness;

    pango_font_metrics_unref (font_metrics);

    /* Calculate the top-left position of the entire month display. */
    month_x = item->x1 + xthickness + calitem->x_offset
        + ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
        ? (calitem->cols - 1 - col) : col) * calitem->month_width - x;
    month_w = item->x2 - item->x1 - xthickness * 2;
    month_w = MIN (month_w, calitem->month_width);
    month_y = item->y1 + ythickness + row * calitem->month_height - y;
    month_h = item->y2 - item->y1 - ythickness * 2;
    month_h = MIN (month_h, calitem->month_height);

    /* Just return if the month is outside the given area. */
    if (month_x >= width || month_x + calitem->month_width <= 0
        || month_y >= height || month_y + calitem->month_height <= 0)
        return;

    month = calitem->month + row * calitem->cols + col;
    year = calitem->year + month / 12;
    month %= 12;

    /* Draw the month name & year, with clipping. Note that the top row
     * needs extra space around it for the buttons. */

    layout = gtk_widget_create_pango_layout (widget, NULL);

    if (row == 0 && col == 0)
        min_x = E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME_WITH_BUTTON;
    else
        min_x = E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME;

    max_x = month_w;
    if (row == 0 && col == 0)
        max_x -= E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME_WITH_BUTTON;
    else
        max_x -= E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME;

    text_y = month_y + style->ythickness
        + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME;
    clip_rect.x = month_x + min_x;
    clip_rect.x = MAX (0, clip_rect.x);
    clip_rect.y = MAX (0, text_y);

    memset (&tmp_tm, 0, sizeof (tmp_tm));
    tmp_tm.tm_year = year - 1900;
    tmp_tm.tm_mon = month;
    tmp_tm.tm_mday = 1;
    tmp_tm.tm_isdst = -1;
    mktime (&tmp_tm);
    start_weekday = (tmp_tm.tm_wday + 6) % 7;

    if (month_x + max_x - clip_rect.x > 0) {
        cairo_save (cr);

        clip_rect.width = month_x + max_x - clip_rect.x;
        clip_rect.height = text_y + char_height - clip_rect.y;
        gdk_cairo_rectangle (cr, &clip_rect);
        cairo_clip (cr);

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

        if (row == 0 && col == 0) {
            PangoLayout *layout_yr;
            gchar buffer_yr[64];
            gdouble max_width;

            layout_yr = gtk_widget_create_pango_layout (widget, NULL);

            /* This is a strftime() format. %B = Month name. */
            e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B"), &tmp_tm);
            /* This is a strftime() format. %Y = Year. */
            e_utf8_strftime (buffer_yr, sizeof (buffer_yr), C_("CalItem", "%Y"), &tmp_tm);

            pango_layout_set_font_description (layout, font_desc);
            pango_layout_set_text (layout, buffer, -1);

            pango_layout_set_font_description (layout_yr, font_desc);
            pango_layout_set_text (layout_yr, buffer_yr, -1);

            if (gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL) {
                max_width = calitem->max_month_name_width;
                pango_layout_get_pixel_size (layout, &text_width, NULL);

                cairo_move_to (cr, month_x + min_x + arrow_button_size + (max_width - text_width) / 2, text_y);
                pango_cairo_show_layout (cr, layout);

                max_width = calitem->max_digit_width * 5;
                pango_layout_get_pixel_size (layout_yr, &text_width, NULL);

                cairo_move_to (cr, month_x + month_w - arrow_button_size - (max_width - text_width) / 2 - text_width - min_x, text_y);
                pango_cairo_show_layout (cr, layout_yr);
            } else {
                max_width = calitem->max_digit_width * 5;
                pango_layout_get_pixel_size (layout_yr, &text_width, NULL);

                cairo_move_to (cr, month_x + min_x + arrow_button_size + (max_width - text_width) / 2, text_y);
                pango_cairo_show_layout (cr, layout_yr);

                max_width = calitem->max_month_name_width;
                pango_layout_get_pixel_size (layout, &text_width, NULL);

                cairo_move_to (cr, month_x + month_w - arrow_button_size - (max_width - text_width) / 2 - text_width - min_x, text_y);
                pango_cairo_show_layout (cr, layout);
            }

            g_object_unref (layout_yr);
        } else {
            /* This is a strftime() format. %B = Month name, %Y = Year. */
            e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B %Y"), &tmp_tm);

            pango_layout_set_font_description (layout, font_desc);
            pango_layout_set_text (layout, buffer, -1);

            /* Ideally we place the text centered in the month, but we
             * won't go to the left of the minimum x position. */
            pango_layout_get_pixel_size (layout, &text_width, NULL);
            text_x = (calitem->month_width - text_width) / 2;
            text_x = MAX (min_x, text_x);

            cairo_move_to (cr, month_x + text_x, text_y);
            pango_cairo_show_layout (cr, layout);
        }

        cairo_restore (cr);
    }

    /* Set the clip rectangle for the main month display. */
    clip_rect.x = MAX (0, month_x);
    clip_rect.y = MAX (0, month_y);
    clip_width = month_x + month_w - clip_rect.x;
    clip_height = month_y + month_h - clip_rect.y;

    if (clip_width <= 0 || clip_height <= 0) {
        g_object_unref (layout);
        return;
    }

    clip_rect.width = clip_width;
    clip_rect.height = clip_height;

    cairo_save (cr);

    gdk_cairo_rectangle (cr, &clip_rect);
    cairo_clip (cr);

    /* Draw the day initials across the top of the month. */
    min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
        + E_CALENDAR_ITEM_MIN_CELL_XPAD;

    cells_x = month_x + E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS + calitem->month_lpad
        + E_CALENDAR_ITEM_XPAD_BEFORE_CELLS;
    if (calitem->show_week_numbers)
        cells_x += calitem->max_week_number_digit_width * 2
            + E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1;
    text_x = cells_x + calitem->cell_width
        - (calitem->cell_width - min_cell_width) / 2;
    text_x -= E_CALENDAR_ITEM_MIN_CELL_XPAD / 2;
    text_y = month_y + ythickness * 2
        + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
        + char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
        + E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + calitem->month_tpad;

    cells_y = text_y + char_height
        + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
        + E_CALENDAR_ITEM_YPAD_ABOVE_CELLS;

    cairo_save (cr);
    gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_SELECTED]);
    cairo_rectangle (
        cr, cells_x ,
        text_y - E_CALENDAR_ITEM_YPAD_ABOVE_CELLS - 1,
            calitem->cell_width * 7  , cells_y - text_y);
    cairo_fill (cr);
    cairo_restore (cr);

    day_index = calitem->week_start_day;
    pango_layout_set_font_description (layout, font_desc);
    if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
        text_x += (7 - 1) * calitem->cell_width;
    gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_ACTIVE]);
    for (day = 0; day < 7; day++) {
        cairo_save (cr);
        layout_set_day_text (calitem, layout, day_index);
        cairo_move_to (
            cr,
            text_x - calitem->day_widths[day_index],
            text_y);
        pango_cairo_show_layout (cr, layout);
        text_x += (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
                ? -calitem->cell_width : calitem->cell_width;
        day_index++;
        if (day_index == 7)
            day_index = 0;
        cairo_restore (cr);
    }

    /* Draw the rectangle around the week number. */
    if (calitem->show_week_numbers) {
        cairo_save (cr);
        gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_SELECTED]);
        cairo_rectangle (
            cr, cells_x, cells_y - (cells_y - text_y + 2) ,
                -20, E_CALENDAR_ROWS_PER_MONTH * calitem->cell_height + 18);
        cairo_fill (cr);
        cairo_restore (cr);
    }

    e_calendar_item_draw_day_numbers (
        calitem, cr, width, height, row, col,
        year, month, start_weekday, cells_x, cells_y);

    g_object_unref (layout);
    cairo_restore (cr);
}

static const gchar *
get_digit_fomat (void)
{

#ifdef HAVE_GNU_GET_LIBC_VERSION
#include <gnu/libc-version.h>

    const gchar *libc_version = gnu_get_libc_version ();
    gchar **split = g_strsplit (libc_version, ".", -1);
    gint major = 0;
    gint minor = 0;
    gint revision = 0;

    major = atoi (split[0]);
    minor = atoi (split[1]);

    if (g_strv_length (split) > 2)
        revision = atoi (split[2]);
    g_strfreev (split);

    if (major > 2 || minor > 2 || (minor == 2 && revision > 2)) {
        return "%Id";
    }
#endif

    return "%d";
}

static void
e_calendar_item_draw_day_numbers (ECalendarItem *calitem,
                                  cairo_t *cr,
                                  gint width,
                                  gint height,
                                  gint row,
                                  gint col,
                                  gint year,
                                  gint month,
                                  gint start_weekday,
                                  gint cells_x,
                                  gint cells_y)
{
    GnomeCanvasItem *item;
    GtkWidget *widget;
    GtkStyle *style;
    PangoFontDescription *font_desc;
    GdkColor *bg_color, *fg_color, *box_color;
    struct tm today_tm;
    time_t t;
    gint char_height, min_cell_width, min_cell_height;
    gint day_num, drow, dcol, day_x, day_y;
    gint text_x, text_y;
    gint num_chars, digit;
    gint week_num, mon, days_from_week_start;
    gint years[3], months[3], days_in_month[3];
    gboolean today, selected, has_focus, drop_target = FALSE;
    gboolean bold, italic, draw_day, finished = FALSE;
    gint today_year, today_month, today_mday, month_offset;
    gchar buffer[9];
    gint day_style = 0;
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;
    PangoLayout *layout;

    item = GNOME_CANVAS_ITEM (calitem);
    widget = GTK_WIDGET (item->canvas);
    style = gtk_widget_get_style (widget);

    /* Set up Pango prerequisites */
    font_desc = calitem->font_desc;
    if (!font_desc)
        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));

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

    min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
        + E_CALENDAR_ITEM_MIN_CELL_XPAD;
    min_cell_height = char_height + E_CALENDAR_ITEM_MIN_CELL_YPAD;

    layout = pango_cairo_create_layout (cr);

    /* Calculate the number of days in the previous, current, and next
     * months. */
    years[0] = years[1] = years[2] = year;
    months[0] = month - 1;
    months[1] = month;
    months[2] = month + 1;
    if (months[0] == -1) {
        months[0] = 11;
        years[0]--;
    }
    if (months[2] == 12) {
        months[2] = 0;
        years[2]++;
    }

    days_in_month[0] = DAYS_IN_MONTH (years[0], months[0]);
    days_in_month[1] = DAYS_IN_MONTH (years[1], months[1]);
    days_in_month[2] = DAYS_IN_MONTH (years[2], months[2]);

    /* Mon 0 is the previous month, which we may show the end of. Mon 1 is
     * the current month, and mon 2 is the next month. */
    mon = 0;

    month_offset = row * calitem->cols + col - 1;
    day_num = days_in_month[0];
    days_from_week_start = (start_weekday + 7 - calitem->week_start_day)
        % 7;
    /* For the top-left month we show the end of the previous month, and
     * if the new month starts on the first day of the week we show a
     * complete week from the previous month. */
    if (days_from_week_start == 0) {
        if (row == 0 && col == 0) {
            day_num -= 6;
        } else {
            mon++;
            month_offset++;
            day_num = 1;
        }
    } else {
        day_num -= days_from_week_start - 1;
    }

    /* Get today's date, so we can highlight it. */
    if (calitem->time_callback) {
        today_tm = calitem->time_callback (
            calitem, calitem->time_callback_data);
    } else {
        t = time (NULL);
        today_tm = *localtime (&t);
    }
    today_year = today_tm.tm_year + 1900;
    today_month = today_tm.tm_mon;
    today_mday = today_tm.tm_mday;

    /* We usually skip the last days of the previous month (mon = 0),
     * except for the top-left month displayed. */
    draw_day = (mon == 1 || (row == 0 && col == 0));

    for (drow = 0; drow < 6; drow++) {
        /* Draw the week number. */
        if (calitem->show_week_numbers) {
            week_num = e_calendar_item_get_week_number (
                calitem, day_num, months[mon], years[mon]);

            text_x = cells_x - E_CALENDAR_ITEM_XPAD_BEFORE_CELLS - 1
                - E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS;
            text_y = cells_y + drow * calitem->cell_height +
                + (calitem->cell_height - min_cell_height + 1) / 2;

            num_chars = 0;
            if (week_num >= 10) {
                digit = week_num / 10;
                text_x -= calitem->week_number_digit_widths[digit];
                num_chars += sprintf (
                    &buffer[num_chars],
                    get_digit_fomat (), digit);
            }

            digit = week_num % 10;
            text_x -= calitem->week_number_digit_widths[digit] + 6;
            num_chars += sprintf (
                &buffer[num_chars],
                get_digit_fomat (), digit);

            cairo_save (cr);
            gdk_cairo_set_source_color (
                cr, &style->text[GTK_STATE_ACTIVE]);
            pango_layout_set_font_description (layout, font_desc);
            pango_layout_set_text (layout, buffer, num_chars);
            cairo_move_to (cr, text_x, text_y);
            pango_cairo_update_layout (cr, layout);
            pango_cairo_show_layout (cr, layout);
            cairo_restore (cr);
        }

        for (dcol = 0; dcol < 7; dcol++) {
            if (draw_day) {
                day_x = cells_x +
                    ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
                    ? 7 - 1 - dcol : dcol) * calitem->cell_width;

                day_y = cells_y + drow * calitem->cell_height;

                today = years[mon] == today_year
                    && months[mon] == today_month
                    && day_num == today_mday;

                selected = calitem->selection_set
                    && (calitem->selection_start_month_offset < month_offset
                        || (calitem->selection_start_month_offset == month_offset
                        && calitem->selection_start_day <= day_num))
                    && (calitem->selection_end_month_offset > month_offset
                        || (calitem->selection_end_month_offset == month_offset
                        && calitem->selection_end_day >= day_num));

                if (calitem->styles)
                    day_style = calitem->styles[(month_offset + 1) * 32 + day_num];

                /* Get the colors & style to use for the day.*/
                if ((gtk_widget_has_focus (GTK_WIDGET (item->canvas))) &&
                    item->canvas->focused_item == item)
                    has_focus = TRUE;
                else
                    has_focus = FALSE;

                bold = FALSE;
                italic = FALSE;

                if (calitem->style_callback)
                    calitem->style_callback (
                        calitem,
                         years[mon],
                         months[mon],
                         day_num,
                         day_style,
                         today,
                         mon != 1,
                         selected,
                         has_focus,
                         drop_target,
                         &bg_color,
                         &fg_color,
                         &box_color,
                         &bold,
                         &italic,
                         calitem->style_callback_data);
                else
                    e_calendar_item_get_day_style (
                        calitem,
                        years[mon],
                        months[mon],
                        day_num,
                        day_style,
                        today,
                        mon != 1,
                        selected,
                        has_focus,
                        drop_target,
                        &bg_color,
                        &fg_color,
                        &box_color,
                        &bold,
                        &italic);

                /* Draw the background, if set. */
                if (bg_color) {
                    cairo_save (cr);
                    gdk_cairo_set_source_color (cr, bg_color);
                    cairo_rectangle (
                        cr, day_x , day_y,
                        calitem->cell_width,
                        calitem->cell_height);
                    cairo_fill (cr);
                    cairo_restore (cr);
                }

                /* Draw the box, if set. */
                if (box_color) {
                    cairo_save (cr);
                    gdk_cairo_set_source_color (cr, box_color);
                    cairo_rectangle (
                        cr, day_x , day_y,
                        calitem->cell_width - 1,
                        calitem->cell_height - 1);
                    cairo_stroke (cr);
                    cairo_restore (cr);
                }

                /* Draw the 1- or 2-digit day number. */
                day_x += calitem->cell_width -
                    (calitem->cell_width -
                    min_cell_width) / 2;
                day_x -= E_CALENDAR_ITEM_MIN_CELL_XPAD / 2;
                day_y += (calitem->cell_height - min_cell_height + 1) / 2;
                day_y += E_CALENDAR_ITEM_MIN_CELL_YPAD / 2;

                num_chars = 0;
                if (day_num >= 10) {
                    digit = day_num / 10;
                    day_x -= calitem->digit_widths[digit];
                    num_chars += sprintf (
                        &buffer[num_chars],
                        get_digit_fomat (), digit);
                }

                digit = day_num % 10;
                day_x -= calitem->digit_widths[digit];
                num_chars += sprintf (
                    &buffer[num_chars],
                    get_digit_fomat (), digit);

                cairo_save (cr);
                if (fg_color) {
                    gdk_cairo_set_source_color (
                        cr, fg_color);
                } else {
                    gdk_cairo_set_source_color (
                        cr, &style->fg[GTK_STATE_NORMAL]);
                }

                if (bold) {
                    pango_font_description_set_weight (
                        font_desc, PANGO_WEIGHT_BOLD);
                } else {
                    pango_font_description_set_weight (
                        font_desc, PANGO_WEIGHT_NORMAL);
                }

                if (italic) {
                    pango_font_description_set_style (
                        font_desc, PANGO_STYLE_ITALIC);
                } else {
                    pango_font_description_set_style (
                        font_desc, PANGO_STYLE_NORMAL);
                }

                pango_layout_set_font_description (layout, font_desc);
                pango_layout_set_text (layout, buffer, num_chars);
                cairo_move_to (cr, day_x, day_y);
                pango_cairo_update_layout (cr, layout);
                pango_cairo_show_layout (cr, layout);
                cairo_restore (cr);
            }

            /* See if we've reached the end of a month. */
            if (day_num == days_in_month[mon]) {
                month_offset++;
                mon++;
                /* We only draw the start of the next month
                 * for the bottom-right month displayed. */
                if (mon == 2 && (row != calitem->rows - 1
                         || col != calitem->cols - 1)) {
                    /* Set a flag so we exit the loop. */
                    finished = TRUE;
                    break;
                }
                day_num = 1;
                draw_day = TRUE;
            } else {
                day_num++;
            }
        }

        /* Exit the loop if the flag is set. */
        if (finished)
            break;
    }

    /* Reset pango weight and style */
    pango_font_description_set_weight (font_desc, PANGO_WEIGHT_NORMAL);
    pango_font_description_set_style (font_desc, PANGO_STYLE_NORMAL);

    g_object_unref (layout);

    pango_font_metrics_unref (font_metrics);
}

gint
e_calendar_item_get_week_number (ECalendarItem *calitem,
                                 gint day,
                                 gint month,
                                 gint year)
{
    GDate date;
    guint weekday, yearday;
    gint week_num;

    g_date_clear (&date, 1);
    g_date_set_dmy (&date, day, month + 1, year);

    /* This results in a value of 0 (Monday) - 6 (Sunday).
     * (or -1 on error - oops!!) */
    weekday = g_date_get_weekday (&date) - 1;

    if (weekday > 0) {
        /* we want always point to nearest Monday, as the first day of the week,
         * regardless of the calendar's week_start_day */
        if (weekday >= 3)
            g_date_add_days (&date, 7 - weekday);
        else
            g_date_subtract_days (&date, weekday);
    }

    /* Calculate the day of the year, from 0 to 365. */
    yearday = g_date_get_day_of_year (&date) - 1;

    /* If the week starts on or after 29th December, it is week 1 of the
     * next year, since there are 4 days in the next year. */
    if (g_date_get_month (&date) == 12 && g_date_get_day (&date) >= 29)
        return 1;

    /* Calculate the week number, from 0. */
    week_num = yearday / 7;

    /* If the first week starts on or after Jan 5th, then we need to add
     * 1 since the previous week will really be the first week. */
    if (yearday % 7 >= 4)
        week_num++;

    /* Add 1 so week numbers are from 1 to 53. */
    return week_num + 1;
}

/* This is supposed to return the nearest item the the point and the distance.
 * Since we are the only item we just return ourself and 0 for the distance.
 * This is needed so that we get button/motion events. */
static GnomeCanvasItem *
e_calendar_item_point (GnomeCanvasItem *item,
                       gdouble x,
                       gdouble y,
                       gint cx,
                       gint cy)
{
    return item;
}

static void
e_calendar_item_stop_selecting (ECalendarItem *calitem,
                                guint32 time)
{
    if (!calitem->selecting)
        return;

    gnome_canvas_item_ungrab (GNOME_CANVAS_ITEM (calitem), time);

    calitem->selecting = FALSE;

    /* If the user selects the grayed dates before the first month or
     * after the last month, we move backwards or forwards one month.
     * The set_month () call should take care of updating the selection. */
    if (calitem->selection_end_month_offset == -1)
        e_calendar_item_set_first_month (
            calitem, calitem->year,
            calitem->month - 1);
    else if (calitem->selection_start_month_offset == calitem->rows * calitem->cols)
        e_calendar_item_set_first_month (
            calitem, calitem->year,
            calitem->month + 1);

    calitem->selection_changed = TRUE;
    if (calitem->selecting_axis) {
        g_free (calitem->selecting_axis);
        calitem->selecting_axis = NULL;
    }

    e_calendar_item_queue_signal_emission (calitem);
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
}

static void
e_calendar_item_selection_add_days (ECalendarItem *calitem,
                                    gint n_days,
                                    gboolean multi_selection)
{
    GDate gdate_start, gdate_end;

    g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));

    if (!e_calendar_item_get_selection (calitem, &gdate_start, &gdate_end)) {
        /* We set the date to the first day of the month */
        g_date_set_dmy (&gdate_start, 1, calitem->month + 1, calitem->year);
        gdate_end = gdate_start;
    }

    if (multi_selection && calitem->max_days_selected > 1) {
        gint days_between;

        days_between = g_date_days_between (&gdate_start, &gdate_end);
        if (!calitem->selecting_axis) {
            calitem->selecting_axis = g_new (GDate, 1);
            *(calitem->selecting_axis) = gdate_start;
        }
        if ((days_between != 0 &&
             g_date_compare (calitem->selecting_axis, &gdate_end) == 0) ||
            (days_between == 0 && n_days < 0)) {
            if (days_between - n_days > calitem->max_days_selected - 1)
                n_days =  days_between + 1 - calitem->max_days_selected;
            g_date_add_days (&gdate_start, n_days);
        }
        else {
            if (days_between + n_days > calitem->max_days_selected - 1)
                n_days = calitem->max_days_selected - 1 - days_between;
            g_date_add_days (&gdate_end, n_days);
        }

        if (g_date_compare (&gdate_end, &gdate_start) < 0) {
            GDate tmp_date;
            tmp_date = gdate_start;
            gdate_start = gdate_end;
            gdate_end = tmp_date;
        }
    }
    else {
        /* clear "selecting_axis", it is only for mulit-selecting */
        if (calitem->selecting_axis) {
            g_free (calitem->selecting_axis);
            calitem->selecting_axis = NULL;
        }
        g_date_add_days (&gdate_start, n_days);
        gdate_end = gdate_start;
    }

    calitem->selecting = TRUE;

    e_calendar_item_set_selection_if_emission (
        calitem, &gdate_start, &gdate_end, FALSE);

    g_signal_emit_by_name (calitem, "selection_preview_changed");
}

static gint
e_calendar_item_key_press_event (ECalendarItem *calitem,
                                 GdkEvent *event)
{
    guint keyval = event->key.keyval;
    gboolean multi_selection = FALSE;

    if (event->key.state & GDK_CONTROL_MASK ||
        event->key.state & GDK_MOD1_MASK)
        return FALSE;

    multi_selection = event->key.state & GDK_SHIFT_MASK;
    switch (keyval) {
    case GDK_KEY_Up:
        e_calendar_item_selection_add_days (
            calitem, -7,
            multi_selection);
        break;
    case GDK_KEY_Down:
        e_calendar_item_selection_add_days (
            calitem, 7,
            multi_selection);
        break;
    case GDK_KEY_Left:
        e_calendar_item_selection_add_days (
            calitem, -1,
            multi_selection);
        break;
    case GDK_KEY_Right:
        e_calendar_item_selection_add_days (
            calitem, 1,
            multi_selection);
        break;
    case GDK_KEY_space:
    case GDK_KEY_Return:
        e_calendar_item_stop_selecting (calitem, event->key.time);
        break;
    default:
        return FALSE;
    }
    return TRUE;
}

static gint
e_calendar_item_event (GnomeCanvasItem *item,
                       GdkEvent *event)
{
    ECalendarItem *calitem;

    calitem = E_CALENDAR_ITEM (item);

    switch (event->type) {
    case GDK_BUTTON_PRESS:
        return e_calendar_item_button_press (calitem, event);
    case GDK_BUTTON_RELEASE:
        return e_calendar_item_button_release (calitem, event);
    case GDK_MOTION_NOTIFY:
        return e_calendar_item_motion (calitem, event);
    case GDK_FOCUS_CHANGE:
        gnome_canvas_item_request_update (item);
        return FALSE;
    case GDK_KEY_PRESS:
        return e_calendar_item_key_press_event (calitem, event);
    default:
        break;
    }

    return FALSE;
}

static void
e_calendar_item_bounds (GnomeCanvasItem *item,
                        gdouble *x1,
                        gdouble *y1,
                        gdouble *x2,
                        gdouble *y2)
{
    ECalendarItem *calitem;

    g_return_if_fail (E_IS_CALENDAR_ITEM (item));

    calitem = E_CALENDAR_ITEM (item);
    *x1 = calitem->x1;
    *y1 = calitem->y1;
    *x2 = calitem->x2;
    *y2 = calitem->y2;
}

/* This checks if any fonts have changed, and if so it recalculates the
 * text sizes and the minimum month size. */
static void
e_calendar_item_recalc_sizes (ECalendarItem *calitem)
{
    GnomeCanvasItem *canvas_item;
    GtkStyle *style;
    gint day, max_day_width, digit, max_digit_width, max_week_number_digit_width;
    gint char_height, width, min_cell_width, min_cell_height;
    gchar buffer[64];
    struct tm tmp_tm;
    PangoFontDescription *font_desc, *wkfont_desc;
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;
    PangoLayout *layout;

    canvas_item = GNOME_CANVAS_ITEM (calitem);
    style = gtk_widget_get_style (GTK_WIDGET (canvas_item->canvas));

    if (!style)
        return;

    /* Set up Pango prerequisites */
    font_desc = calitem->font_desc;
    wkfont_desc = calitem->week_number_font_desc;
    if (!font_desc)
        font_desc = style->font_desc;

    pango_context = gtk_widget_create_pango_context (
        GTK_WIDGET (canvas_item->canvas));
    font_metrics = pango_context_get_metrics (
        pango_context, font_desc,
        pango_context_get_language (pango_context));
    layout = pango_layout_new (pango_context);

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

    max_day_width = 0;
    for (day = 0; day < 7; day++) {
        layout_set_day_text (calitem, layout, day);
        pango_layout_get_pixel_size (layout, &width, NULL);

        calitem->day_widths[day] = width;
        max_day_width = MAX (max_day_width, width);
    }
    calitem->max_day_width = max_day_width;

    max_digit_width = 0;
    max_week_number_digit_width = 0;
    for (digit = 0; digit < 10; digit++) {
        gchar locale_digit[5];
        gint locale_digit_len;

        locale_digit_len = sprintf (locale_digit, get_digit_fomat (), digit);

        pango_layout_set_text (layout, locale_digit, locale_digit_len);
        pango_layout_get_pixel_size (layout, &width, NULL);

        calitem->digit_widths[digit] = width;
        max_digit_width = MAX (max_digit_width, width);

        if (wkfont_desc) {
            pango_context_set_font_description (pango_context, wkfont_desc);
            pango_layout_context_changed (layout);

            pango_layout_set_text (layout, locale_digit, locale_digit_len);
            pango_layout_get_pixel_size (layout, &width, NULL);

            calitem->week_number_digit_widths[digit] = width;
            max_week_number_digit_width = MAX (max_week_number_digit_width, width);

            pango_context_set_font_description (pango_context, font_desc);
            pango_layout_context_changed (layout);
        } else {
            calitem->week_number_digit_widths[digit] = width;
            max_week_number_digit_width = max_digit_width;
        }
    }
    calitem->max_digit_width = max_digit_width;
    calitem->max_week_number_digit_width = max_week_number_digit_width;

    min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
        + E_CALENDAR_ITEM_MIN_CELL_XPAD;
    min_cell_height = char_height + E_CALENDAR_ITEM_MIN_CELL_YPAD;

    calitem->min_month_width = E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS
        + E_CALENDAR_ITEM_XPAD_BEFORE_CELLS + min_cell_width * 7
        + E_CALENDAR_ITEM_XPAD_AFTER_CELLS;
    if (calitem->show_week_numbers) {
        calitem->min_month_width += calitem->max_week_number_digit_width * 2
            + E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1;
    }

    calitem->min_month_height = style->ythickness * 2
        + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + char_height
        + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME + 1
        + E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS
        + char_height + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
        + E_CALENDAR_ITEM_YPAD_ABOVE_CELLS + min_cell_height * 6
        + E_CALENDAR_ITEM_YPAD_BELOW_CELLS;

    calitem->max_month_name_width = 50;
    memset (&tmp_tm, 0, sizeof (tmp_tm));
    tmp_tm.tm_year = 2000 - 100;
    tmp_tm.tm_mday = 1;
    tmp_tm.tm_isdst = -1;
    for (tmp_tm.tm_mon = 0; tmp_tm.tm_mon < 12; tmp_tm.tm_mon++) {
        mktime (&tmp_tm);

        e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B"), &tmp_tm);

        pango_layout_set_text (layout, buffer, -1);
        pango_layout_get_pixel_size (layout, &width, NULL);

        if (width > calitem->max_month_name_width)
            calitem->max_month_name_width = width;
    }

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

static void
e_calendar_item_get_day_style (ECalendarItem *calitem,
                               gint year,
                               gint month,
                               gint day,
                               gint day_style,
                               gboolean today,
                               gboolean prev_or_next_month,
                               gboolean selected,
                               gboolean has_focus,
                               gboolean drop_target,
                               GdkColor **bg_color,
                               GdkColor **fg_color,
                               GdkColor **box_color,
                               gboolean *bold,
                               gboolean *italic)
{
    GtkWidget *widget;
    GtkStyle *style;

    widget = GTK_WIDGET (GNOME_CANVAS_ITEM (calitem)->canvas);
    style = gtk_widget_get_style (widget);

    *bg_color = NULL;
    *fg_color = NULL;
    *box_color = NULL;

    *bold = (day_style & E_CALENDAR_ITEM_MARK_BOLD) ==
        E_CALENDAR_ITEM_MARK_BOLD;
    *italic = (day_style & E_CALENDAR_ITEM_MARK_ITALIC) ==
        E_CALENDAR_ITEM_MARK_ITALIC;

    if (today)
        *box_color = &calitem->colors[E_CALENDAR_ITEM_COLOR_TODAY_BOX];

    if (prev_or_next_month)
        *fg_color = &style->mid[gtk_widget_get_state (widget)];

    if (selected) {
        if (has_focus) {
            *fg_color = &style->text[GTK_STATE_SELECTED];
            *bg_color = &style->base[GTK_STATE_SELECTED];
        } else {
            *fg_color = &style->text[GTK_STATE_ACTIVE];
            *bg_color = &style->base[GTK_STATE_ACTIVE];

            if ((*bg_color)->red == style->base[GTK_STATE_NORMAL].red &&
                (*bg_color)->green == style->base[GTK_STATE_NORMAL].green &&
                (*bg_color)->blue == style->base[GTK_STATE_NORMAL].blue) {
                *fg_color = &style->text[GTK_STATE_SELECTED];
                *bg_color = &style->base[GTK_STATE_SELECTED];
            }
        }
    }
}

static gboolean
e_calendar_item_button_press (ECalendarItem *calitem,
                              GdkEvent *button_event)
{
    GdkGrabStatus grab_status;
    GdkDevice *event_device;
    guint event_button = 0;
    guint32 event_time;
    gdouble event_x_win = 0;
    gdouble event_y_win = 0;
    gint month_offset, day, add_days = 0;
    gboolean all_week, round_up_end = FALSE, round_down_start = FALSE;

    gdk_event_get_button (button_event, &event_button);
    gdk_event_get_coords (button_event, &event_x_win, &event_y_win);
    event_device = gdk_event_get_device (button_event);
    event_time = gdk_event_get_time (button_event);

    if (event_button == 4)
        e_calendar_item_set_first_month (
            calitem, calitem->year,
            calitem->month - 1);
    else if (event_button == 5)
        e_calendar_item_set_first_month (
            calitem, calitem->year,
            calitem->month + 1);

    if (!e_calendar_item_convert_position_to_day (calitem,
                              event_x_win,
                              event_y_win,
                              TRUE,
                              &month_offset, &day,
                              &all_week))
        return FALSE;

    if (event_button == 3 && day == -1
        && e_calendar_item_get_display_popup (calitem)) {
        e_calendar_item_show_popup_menu (
            calitem, button_event, month_offset);
        return TRUE;
    }

    if (event_button != 1 || day == -1)
        return FALSE;

    if (calitem->max_days_selected < 1)
        return TRUE;

    grab_status = gnome_canvas_item_grab (
        GNOME_CANVAS_ITEM (calitem),
        GDK_POINTER_MOTION_MASK |
        GDK_BUTTON_RELEASE_MASK,
        NULL,
        event_device,
        event_time);

    if (grab_status != GDK_GRAB_SUCCESS)
        return FALSE;

    if (all_week && calitem->keep_wdays_on_weeknum_click) {
        gint tmp_start_moff, tmp_start_day;

        tmp_start_moff = calitem->selection_start_month_offset;
        tmp_start_day = calitem->selection_start_day;
        e_calendar_item_round_down_selection (
            calitem, &tmp_start_moff, &tmp_start_day);

        e_calendar_item_round_down_selection (calitem, &month_offset, &day);
        month_offset += calitem->selection_start_month_offset - tmp_start_moff;
        day += calitem->selection_start_day - tmp_start_day;

        /* keep same count of days selected */
        add_days = e_calendar_item_get_inclusive_days (
            calitem,
            calitem->selection_start_month_offset,
            calitem->selection_start_day,
            calitem->selection_end_month_offset,
            calitem->selection_end_day) - 1;
    }

    calitem->selection_set = TRUE;
    calitem->selection_start_month_offset = month_offset;
    calitem->selection_start_day = day;
    calitem->selection_end_month_offset = month_offset;
    calitem->selection_end_day = day;

    if (add_days > 0)
        e_calendar_item_add_days_to_selection (calitem, add_days);

    calitem->selection_real_start_month_offset = month_offset;
    calitem->selection_real_start_day = day;

    calitem->selection_from_full_week = FALSE;
    calitem->selecting = TRUE;
    calitem->selection_dragging_end = TRUE;

    if (all_week && !calitem->keep_wdays_on_weeknum_click) {
        calitem->selection_from_full_week = TRUE;
        round_up_end = TRUE;
    }

    if (calitem->days_to_start_week_selection == 1) {
        round_down_start = TRUE;
        round_up_end = TRUE;
    }

    /* Don't round up or down if we can't select a week or more,
     * or when keeping week days. */
    if (calitem->max_days_selected < 7 ||
        (all_week && calitem->keep_wdays_on_weeknum_click)) {
        round_down_start = FALSE;
        round_up_end = FALSE;
    }

    if (round_up_end)
        e_calendar_item_round_up_selection (
            calitem, &calitem->selection_end_month_offset,
            &calitem->selection_end_day);

    if (round_down_start)
        e_calendar_item_round_down_selection (
            calitem, &calitem->selection_start_month_offset,
            &calitem->selection_start_day);

    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));

    return TRUE;
}

static gboolean
e_calendar_item_button_release (ECalendarItem *calitem,
                                GdkEvent *button_event)
{
    guint32 event_time;

    event_time = gdk_event_get_time (button_event);
    e_calendar_item_stop_selecting (calitem, event_time);

    return FALSE;
}

static gboolean
e_calendar_item_motion (ECalendarItem *calitem,
                        GdkEvent *event)
{
    gint start_month, start_day, end_month, end_day, month_offset, day;
    gint tmp_month, tmp_day, days_in_selection;
    gboolean all_week, round_up_end = FALSE, round_down_start = FALSE;

    if (!calitem->selecting)
        return FALSE;

    if (!e_calendar_item_convert_position_to_day (calitem,
                              event->button.x,
                              event->button.y,
                              TRUE,
                              &month_offset, &day,
                              &all_week))
        return FALSE;

    if (day == -1)
        return FALSE;

    if (calitem->selection_dragging_end) {
        start_month = calitem->selection_real_start_month_offset;
        start_day = calitem->selection_real_start_day;
        end_month = month_offset;
        end_day = day;
    } else {
        start_month = month_offset;
        start_day = day;
        end_month = calitem->selection_real_start_month_offset;
        end_day = calitem->selection_real_start_day;
    }

    if (start_month > end_month || (start_month == end_month
                    && start_day > end_day)) {
        tmp_month = start_month;
        tmp_day = start_day;
        start_month = end_month;
        start_day = end_day;
        end_month = tmp_month;
        end_day = tmp_day;

        calitem->selection_dragging_end =
            !calitem->selection_dragging_end;
    }

    if (calitem->days_to_start_week_selection > 0) {
        days_in_selection = e_calendar_item_get_inclusive_days (
            calitem, start_month, start_day, end_month, end_day);
        if (days_in_selection >= calitem->days_to_start_week_selection) {
            round_down_start = TRUE;
            round_up_end = TRUE;
        }
    }

    /* If we are over a week number and we are dragging the end of the
     * selection, we round up to the end of this week. */
    if (all_week && calitem->selection_dragging_end)
        round_up_end = TRUE;

    /* If the selection was started from a week number and we are dragging
     * the start of the selection, we need to round up the end to include
     * all of the original week selected. */
    if (calitem->selection_from_full_week
        && !calitem->selection_dragging_end)
            round_up_end = TRUE;

    /* Don't round up or down if we can't select a week or more. */
    if (calitem->max_days_selected < 7) {
        round_down_start = FALSE;
        round_up_end = FALSE;
    }

    if (round_up_end)
        e_calendar_item_round_up_selection (
            calitem, &end_month,
            &end_day);
    if (round_down_start)
        e_calendar_item_round_down_selection (
            calitem, &start_month,
            &start_day);

    /* Check we don't go over the maximum number of days to select. */
    if (calitem->selection_dragging_end) {
        e_calendar_item_check_selection_end (
            calitem,
            start_month,
            start_day,
            &end_month,
            &end_day);
    } else {
        e_calendar_item_check_selection_start (
            calitem,
            &start_month,
            &start_day,
            end_month,
            end_day);
    }

    if (start_month == calitem->selection_start_month_offset
        && start_day == calitem->selection_start_day
        && end_month == calitem->selection_end_month_offset
        && end_day == calitem->selection_end_day)
        return FALSE;

    calitem->selection_start_month_offset = start_month;
    calitem->selection_start_day = start_day;
    calitem->selection_end_month_offset = end_month;
    calitem->selection_end_day = end_day;

    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));

    return TRUE;
}

static void
e_calendar_item_check_selection_end (ECalendarItem *calitem,
                                     gint start_month,
                                     gint start_day,
                                     gint *end_month,
                                     gint *end_day)
{
    gint year, month, max_month, max_day, days_in_month;

    if (calitem->max_days_selected <= 0)
        return;

    year = calitem->year;
    month = calitem->month + start_month;
    e_calendar_item_normalize_date (calitem, &year, &month);

    max_month = start_month;
    max_day = start_day + calitem->max_days_selected - 1;

    for (;;) {
        days_in_month = DAYS_IN_MONTH (year, month);
        if (max_day <= days_in_month)
            break;
        max_month++;
        month++;
        if (month == 12) {
            year++;
            month = 0;
        }
        max_day -= days_in_month;
    }

    if (*end_month > max_month) {
        *end_month = max_month;
        *end_day = max_day;
    } else if (*end_month == max_month && *end_day > max_day) {
        *end_day = max_day;
    }
}

static void
e_calendar_item_check_selection_start (ECalendarItem *calitem,
                                       gint *start_month,
                                       gint *start_day,
                                       gint end_month,
                                       gint end_day)
{
    gint year, month, min_month, min_day, days_in_month;

    if (calitem->max_days_selected <= 0)
        return;

    year = calitem->year;
    month = calitem->month + end_month;
    e_calendar_item_normalize_date (calitem, &year, &month);

    min_month = end_month;
    min_day = end_day - calitem->max_days_selected + 1;

    while (min_day <= 0) {
        min_month--;
        month--;
        if (month == -1) {
            year--;
            month = 11;
        }
        days_in_month = DAYS_IN_MONTH (year, month);
        min_day += days_in_month;
    }

    if (*start_month < min_month) {
        *start_month = min_month;
        *start_day = min_day;
    } else if (*start_month == min_month && *start_day < min_day) {
        *start_day = min_day;
    }
}

/* Converts a position within the item to a month & day.
 * The month returned is 0 for the top-left month displayed.
 * If the position is over the month heading -1 is returned for the day.
 * If the position is over a week number the first day of the week is returned
 * and entire_week is set to TRUE.
 * It returns FALSE if the position is completely outside all months. */
static gboolean
e_calendar_item_convert_position_to_day (ECalendarItem *calitem,
                                         gint event_x,
                                         gint event_y,
                                         gboolean round_empty_positions,
                                         gint *month_offset,
                                         gint *day,
                                         gboolean *entire_week)
{
    GnomeCanvasItem *item;
    GtkWidget *widget;
    GtkStyle *style;
    gint xthickness, ythickness, char_height;
    gint x, y, row, col, cells_x, cells_y, day_row, day_col;
    gint first_day_offset, days_in_month, days_in_prev_month;
    gint week_num_x1, week_num_x2;
    PangoFontDescription *font_desc;
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;

    item = GNOME_CANVAS_ITEM (calitem);
    widget = GTK_WIDGET (item->canvas);
    style = gtk_widget_get_style (widget);

    font_desc = calitem->font_desc;
    if (!font_desc)
        font_desc = style->font_desc;
    pango_context = gtk_widget_create_pango_context (widget);
    font_metrics = pango_context_get_metrics (
        pango_context, font_desc,
        pango_context_get_language (pango_context));

    char_height =
        PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
        PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
    xthickness = style->xthickness;
    ythickness = style->ythickness;

    pango_font_metrics_unref (font_metrics);

    *entire_week = FALSE;

    x = event_x - xthickness - calitem->x_offset;
    y = event_y - ythickness;

    if (x < 0 || y < 0)
        return FALSE;

    row = y / calitem->month_height;
    col = x / calitem->month_width;

    if (row >= calitem->rows || col >= calitem->cols)
        return FALSE;
    if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
        col = calitem->cols - 1 - col;

    *month_offset = row * calitem->cols + col;

    x = x % calitem->month_width;
    y = y % calitem->month_height;

    if (y < ythickness * 2 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
        + char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME) {
        *day = -1;
        return TRUE;
    }

    cells_y = ythickness * 2 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
        + char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
        + E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + calitem->month_tpad
        + char_height + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
        + E_CALENDAR_ITEM_YPAD_ABOVE_CELLS;
    y -= cells_y;
    if (y < 0)
        return FALSE;
    day_row = y / calitem->cell_height;
    if (day_row >= E_CALENDAR_ROWS_PER_MONTH)
        return FALSE;

    week_num_x1 = E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS + calitem->month_lpad;

    if (calitem->show_week_numbers) {
        week_num_x2 = week_num_x1
            + calitem->max_week_number_digit_width * 2;
        if (x >= week_num_x1 && x < week_num_x2)
            *entire_week = TRUE;
        cells_x = week_num_x2 + E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1;
    } else {
        cells_x = week_num_x1;
    }

    if (*entire_week) {
        day_col = 0;
    } else {
        cells_x += E_CALENDAR_ITEM_XPAD_BEFORE_CELLS;
        x -= cells_x;
        if (x < 0)
            return FALSE;
        day_col = x / calitem->cell_width;
        if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
            day_col = E_CALENDAR_COLS_PER_MONTH - 1 - day_col;
        if (day_col >= E_CALENDAR_COLS_PER_MONTH)
            return FALSE;
    }

    *day = day_row * E_CALENDAR_COLS_PER_MONTH + day_col;

    e_calendar_item_get_month_info (
        calitem, row, col, &first_day_offset,
        &days_in_month, &days_in_prev_month);
    if (*day < first_day_offset) {
        if (*entire_week || (row == 0 && col == 0)) {
            (*month_offset)--;
            *day = days_in_prev_month + 1 - first_day_offset
                + *day;
            return TRUE;
        } else if (round_empty_positions) {
            *day = first_day_offset;
        } else {
            return FALSE;
        }
    }

    *day -= first_day_offset - 1;

    if (*day > days_in_month) {
        if (row == calitem->rows - 1 && col == calitem->cols - 1) {
            (*month_offset)++;
            *day -= days_in_month;
            return TRUE;
        } else if (round_empty_positions) {
            *day = days_in_month;
        } else {
            return FALSE;
        }
    }

    return TRUE;
}

static void
e_calendar_item_get_month_info (ECalendarItem *calitem,
                                gint row,
                                gint col,
                                gint *first_day_offset,
                                gint *days_in_month,
                                gint *days_in_prev_month)
{
    gint year, month, start_weekday, first_day_of_month;
    struct tm tmp_tm = { 0 };

    month = calitem->month + row * calitem->cols + col;
    year = calitem->year + month / 12;
    month = month % 12;

    *days_in_month = DAYS_IN_MONTH (year, month);
    if (month == 0)
        *days_in_prev_month = DAYS_IN_MONTH (year - 1, 11);
    else
        *days_in_prev_month = DAYS_IN_MONTH (year, month - 1);

    tmp_tm.tm_year = year - 1900;
    tmp_tm.tm_mon = month;
    tmp_tm.tm_mday = 1;
    tmp_tm.tm_isdst = -1;
    mktime (&tmp_tm);

    /* Convert to 0 (Monday) to 6 (Sunday). */
    start_weekday = (tmp_tm.tm_wday + 6) % 7;

    first_day_of_month = (start_weekday + 7 - calitem->week_start_day) % 7;

    if (row == 0 && col == 0 && first_day_of_month == 0)
        *first_day_offset = 7;
    else
        *first_day_offset = first_day_of_month;
}

void
e_calendar_item_get_first_month (ECalendarItem *calitem,
                                 gint *year,
                                 gint *month)
{
    *year = calitem->year;
    *month = calitem->month;
}

static void
e_calendar_item_preserve_day_selection (ECalendarItem *calitem,
                                        gint selected_day,
                                        gint *month_offset,
                                        gint *day)
{
    gint year, month, weekday, days, days_in_month;
    struct tm tmp_tm = { 0 };

    year = calitem->year;
    month = calitem->month + *month_offset;
    e_calendar_item_normalize_date (calitem, &year, &month);

    tmp_tm.tm_year = year - 1900;
    tmp_tm.tm_mon = month;
    tmp_tm.tm_mday = *day;
    tmp_tm.tm_isdst = -1;
    mktime (&tmp_tm);

    /* Convert to 0 (Monday) to 6 (Sunday). */
    weekday = (tmp_tm.tm_wday + 6) % 7;

    /* Calculate how many days to the start of the row. */
    days = (weekday + 7 - selected_day) % 7;

    *day -= days;
    if (*day <= 0) {
        month--;
        if (month == -1) {
            year--;
            month = 11;
        }
        days_in_month = DAYS_IN_MONTH (year, month);
        (*month_offset)--;
        *day += days_in_month;
    }
}

/* This also handles values of month < 0 or > 11 by updating the year. */
void
e_calendar_item_set_first_month (ECalendarItem *calitem,
                                 gint year,
                                 gint month)
{
    gint new_year, new_month, months_diff, num_months;
    gint old_days_in_selection, new_days_in_selection;

    new_year = year;
    new_month = month;
    e_calendar_item_normalize_date (calitem, &new_year, &new_month);

    if (calitem->year == new_year && calitem->month == new_month)
        return;

    /* Update the selection. */
    num_months = calitem->rows * calitem->cols;
    months_diff = (new_year - calitem->year) * 12
        + new_month - calitem->month;

    if (calitem->selection_set) {
        if (!calitem->move_selection_when_moving
            || (calitem->selection_start_month_offset - months_diff >= 0
            && calitem->selection_end_month_offset - months_diff < num_months)) {
            calitem->selection_start_month_offset -= months_diff;
            calitem->selection_end_month_offset -= months_diff;
            calitem->selection_real_start_month_offset -= months_diff;

            calitem->year = new_year;
            calitem->month = new_month;
        } else {
            gint selected_day;
            struct tm tmp_tm = { 0 };

            old_days_in_selection = e_calendar_item_get_inclusive_days (
                calitem,
                calitem->selection_start_month_offset,
                calitem->selection_start_day,
                calitem->selection_end_month_offset,
                calitem->selection_end_day);

            /* Calculate the currently selected day */
            tmp_tm.tm_year = calitem->year - 1900;
            tmp_tm.tm_mon = calitem->month + calitem->selection_start_month_offset;
            tmp_tm.tm_mday = calitem->selection_start_day;
            tmp_tm.tm_isdst = -1;
            mktime (&tmp_tm);

            selected_day = (tmp_tm.tm_wday + 6) % 7;

            /* Make sure the selection will be displayed. */
            if (calitem->selection_start_month_offset < 0
                || calitem->selection_start_month_offset >= num_months) {
                calitem->selection_end_month_offset -=
                    calitem->selection_start_month_offset;
                calitem->selection_start_month_offset = 0;
            }

            /* We want to ensure that the same number of days are
             * selected after we have moved the selection. */
            calitem->year = new_year;
            calitem->month = new_month;

            e_calendar_item_ensure_valid_day (
                calitem, &calitem->selection_start_month_offset,
                &calitem->selection_start_day);
            e_calendar_item_ensure_valid_day (
                calitem, &calitem->selection_end_month_offset,
                &calitem->selection_end_day);

            if (calitem->preserve_day_when_moving) {
                e_calendar_item_preserve_day_selection (
                    calitem, selected_day,
                    &calitem->selection_start_month_offset,
                    &calitem->selection_start_day);
            }

            new_days_in_selection = e_calendar_item_get_inclusive_days (
                calitem,
                calitem->selection_start_month_offset,
                calitem->selection_start_day,
                calitem->selection_end_month_offset,
                calitem->selection_end_day);

            if (old_days_in_selection != new_days_in_selection)
                e_calendar_item_add_days_to_selection (
                    calitem, old_days_in_selection -
                    new_days_in_selection);

            /* Flag that we need to emit the "selection_changed"
             * signal. We don't want to emit it here since setting
             * the "year" and "month" args would result in 2
             * signals emitted. */
            calitem->selection_changed = TRUE;
        }
    } else {
        calitem->year = new_year;
        calitem->month = new_month;
    }

    e_calendar_item_date_range_changed (calitem);
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
}

/* Get the maximum number of days selectable */
gint
e_calendar_item_get_max_days_sel (ECalendarItem *calitem)
{
    return calitem->max_days_selected;
}

/* Set the maximum number of days selectable */
void
e_calendar_item_set_max_days_sel (ECalendarItem *calitem,
                                  gint days)
{
    calitem->max_days_selected = MAX (0, days);
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
}

/* Get the maximum number of days before whole weeks are selected */
gint
e_calendar_item_get_days_start_week_sel (ECalendarItem *calitem)
{
    return calitem->days_to_start_week_selection;
}

/* Set the maximum number of days before whole weeks are selected */
void
e_calendar_item_set_days_start_week_sel (ECalendarItem *calitem,
                                         gint days)
{
    calitem->days_to_start_week_selection = days;
}

gboolean
e_calendar_item_get_display_popup (ECalendarItem *calitem)
{
    return calitem->display_popup;
}

void
e_calendar_item_set_display_popup (ECalendarItem *calitem,
                                   gboolean display)
{
    calitem->display_popup = display;
}

/* This will make sure that the given year & month are valid, i.e. if month
 * is < 0 or > 11 the year and month will be updated accordingly. */
void
e_calendar_item_normalize_date (ECalendarItem *calitem,
                                gint *year,
                                gint *month)
{
    if (*month >= 0) {
        *year += *month / 12;
        *month = *month % 12;
    } else {
        *year += *month / 12 - 1;
        *month = *month % 12;
        if (*month != 0)
            *month += 12;
    }
}

/* Adds or subtracts days from the selection. It is used when we switch months
 * and the selection extends past the end of a month but we want to keep the
 * number of days selected the same. days should not be more than 30. */
static void
e_calendar_item_add_days_to_selection (ECalendarItem *calitem,
                                       gint days)
{
    gint year, month, days_in_month;

    year = calitem->year;
    month = calitem->month + calitem->selection_end_month_offset;
    e_calendar_item_normalize_date (calitem, &year, &month);

    calitem->selection_end_day += days;
    if (calitem->selection_end_day <= 0) {
        month--;
        e_calendar_item_normalize_date (calitem, &year, &month);
        calitem->selection_end_month_offset--;
        calitem->selection_end_day += DAYS_IN_MONTH (year, month);
    } else {
        days_in_month = DAYS_IN_MONTH (year, month);
        if (calitem->selection_end_day > days_in_month) {
            calitem->selection_end_month_offset++;
            calitem->selection_end_day -= days_in_month;
        }
    }
}

/* Gets the range of dates actually shown. Months are 0 to 11.
 * This also includes the last days of the previous month and the first days
 * of the following month, which are normally shown in gray.
 * It returns FALSE if no dates are currently shown. */
gboolean
e_calendar_item_get_date_range (ECalendarItem *calitem,
                                gint *start_year,
                                gint *start_month,
                                gint *start_day,
                                gint *end_year,
                                gint *end_month,
                                gint *end_day)
{
    gint first_day_offset, days_in_month, days_in_prev_month;

    if (calitem->rows == 0 || calitem->cols == 0)
        return FALSE;

    /* Calculate the first day shown. This will be one of the greyed-out
     * days before the first full month begins. */
    e_calendar_item_get_month_info (
        calitem, 0, 0, &first_day_offset,
        &days_in_month, &days_in_prev_month);
    *start_year = calitem->year;
    *start_month = calitem->month - 1;
    if (*start_month == -1) {
        (*start_year)--;
        *start_month = 11;
    }
    *start_day = days_in_prev_month + 1 - first_day_offset;

    /* Calculate the last day shown. This will be one of the greyed-out
     * days after the last full month ends. */
    e_calendar_item_get_month_info (
        calitem, calitem->rows - 1,
        calitem->cols - 1, &first_day_offset,
        &days_in_month, &days_in_prev_month);
    *end_month = calitem->month + calitem->rows * calitem->cols;
    *end_year = calitem->year + *end_month / 12;
    *end_month %= 12;
    *end_day = E_CALENDAR_ROWS_PER_MONTH * E_CALENDAR_COLS_PER_MONTH
        - first_day_offset - days_in_month;

    return TRUE;
}

/* Simple way to mark days so they appear bold.
 * A more flexible interface may be added later. */
void
e_calendar_item_clear_marks (ECalendarItem *calitem)
{
    GnomeCanvasItem *item;

    item = GNOME_CANVAS_ITEM (calitem);

    g_free (calitem->styles);
    calitem->styles = NULL;

    gnome_canvas_request_redraw (
        item->canvas, item->x1, item->y1,
        item->x2, item->y2);
}

/* add_day_style - whether bit-or with the actual style or change the style fully */
void
e_calendar_item_mark_day (ECalendarItem *calitem,
                          gint year,
                          gint month,
                          gint day,
                          guint8 day_style,
                          gboolean add_day_style)
{
    gint month_offset;
    gint index;

    month_offset = (year - calitem->year) * 12 + month - calitem->month;
    if (month_offset < -1 || month_offset > calitem->rows * calitem->cols)
        return;

    if (!calitem->styles)
        calitem->styles = g_new0 (guint8, (calitem->rows * calitem->cols + 2) * 32);

    index = (month_offset + 1) * 32 + day;
    calitem->styles[index] = day_style |
        (add_day_style ? calitem->styles[index] : 0);

    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
}

void
e_calendar_item_mark_days (ECalendarItem *calitem,
                           gint start_year,
                           gint start_month,
                           gint start_day,
                           gint end_year,
                           gint end_month,
                           gint end_day,
                           guint8 day_style,
                           gboolean add_day_style)
{
    gint month_offset, end_month_offset, day;

    month_offset = (start_year - calitem->year) * 12 + start_month
        - calitem->month;
    day = start_day;
    if (month_offset > calitem->rows * calitem->cols)
        return;
    if (month_offset < -1) {
        month_offset = -1;
        day = 1;
    }

    end_month_offset = (end_year - calitem->year) * 12 + end_month
        - calitem->month;
    if (end_month_offset < -1)
        return;
    if (end_month_offset > calitem->rows * calitem->cols) {
        end_month_offset = calitem->rows * calitem->cols;
        end_day = 31;
    }

    if (month_offset > end_month_offset)
        return;

    if (!calitem->styles)
        calitem->styles = g_new0 (guint8, (calitem->rows * calitem->cols + 2) * 32);

    for (;;) {
        gint index;

        if (month_offset == end_month_offset && day > end_day)
            break;

        if (month_offset < -1 || month_offset > calitem->rows * calitem->cols)
            g_warning ("Bad month offset: %i\n", month_offset);
        if (day < 1 || day > 31)
            g_warning ("Bad day: %i\n", day);

#if 0
        g_print ("Marking Month:%i Day:%i\n", month_offset, day);
#endif
        index = (month_offset + 1) * 32 + day;
        calitem->styles[index] = day_style |
            (add_day_style ? calitem->styles[index] : 0);

        day++;
        if (day == 32) {
            month_offset++;
            day = 1;
            if (month_offset > end_month_offset)
                break;
        }
    }

    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
}

/* Rounds up the given day to the end of the week. */
static void
e_calendar_item_round_up_selection (ECalendarItem *calitem,
                                    gint *month_offset,
                                    gint *day)
{
    gint year, month, weekday, days, days_in_month;
    struct tm tmp_tm = { 0 };

    year = calitem->year;
    month = calitem->month + *month_offset;
    e_calendar_item_normalize_date (calitem, &year, &month);

    tmp_tm.tm_year = year - 1900;
    tmp_tm.tm_mon = month;
    tmp_tm.tm_mday = *day;
    tmp_tm.tm_isdst = -1;
    mktime (&tmp_tm);

    /* Convert to 0 (Monday) to 6 (Sunday). */
    weekday = (tmp_tm.tm_wday + 6) % 7;

    /* Calculate how many days to the end of the row. */
    days = (calitem->week_start_day + 6 - weekday) % 7;

    *day += days;
    days_in_month = DAYS_IN_MONTH (year, month);
    if (*day > days_in_month) {
        (*month_offset)++;
        *day -= days_in_month;
    }
}

/* Rounds down the given day to the start of the week. */
static void
e_calendar_item_round_down_selection (ECalendarItem *calitem,
                                      gint *month_offset,
                                      gint *day)
{
    gint year, month, weekday, days, days_in_month;
    struct tm tmp_tm = { 0 };

    year = calitem->year;
    month = calitem->month + *month_offset;
    e_calendar_item_normalize_date (calitem, &year, &month);

    tmp_tm.tm_year = year - 1900;
    tmp_tm.tm_mon = month;
    tmp_tm.tm_mday = *day;
    tmp_tm.tm_isdst = -1;
    mktime (&tmp_tm);

    /* Convert to 0 (Monday) to 6 (Sunday). */
    weekday = (tmp_tm.tm_wday + 6) % 7;

    /* Calculate how many days to the start of the row. */
    days = (weekday + 7 - calitem->week_start_day) % 7;

    *day -= days;
    if (*day <= 0) {
        month--;
        if (month == -1) {
            year--;
            month = 11;
        }
        days_in_month = DAYS_IN_MONTH (year, month);
        (*month_offset)--;
        *day += days_in_month;
    }
}

static gint
e_calendar_item_get_inclusive_days (ECalendarItem *calitem,
                                    gint start_month_offset,
                                    gint start_day,
                                    gint end_month_offset,
                                    gint end_day)
{
    gint start_year, start_month, end_year, end_month, days = 0;

    start_year = calitem->year;
    start_month = calitem->month + start_month_offset;
    e_calendar_item_normalize_date (calitem, &start_year, &start_month);

    end_year = calitem->year;
    end_month = calitem->month + end_month_offset;
    e_calendar_item_normalize_date (calitem, &end_year, &end_month);

    while (start_year < end_year || start_month < end_month) {
        days += DAYS_IN_MONTH (start_year, start_month);
        start_month++;
        if (start_month == 12) {
            start_year++;
            start_month = 0;
        }
    }

    days += end_day - start_day + 1;

    return days;
}

/* If the day is off the end of the month it is set to the last day of the
 * month. */
static void
e_calendar_item_ensure_valid_day (ECalendarItem *calitem,
                                  gint *month_offset,
                                  gint *day)
{
    gint year, month, days_in_month;

    year = calitem->year;
    month = calitem->month + *month_offset;
    e_calendar_item_normalize_date (calitem, &year, &month);

    days_in_month = DAYS_IN_MONTH (year, month);
    if (*day > days_in_month)
        *day = days_in_month;
}

gboolean
e_calendar_item_get_selection (ECalendarItem *calitem,
                               GDate *start_date,
                               GDate *end_date)
{
    gint start_year, start_month, start_day;
    gint end_year, end_month, end_day;

    g_date_clear (start_date, 1);
    g_date_clear (end_date, 1);

    if (!calitem->selection_set)
        return FALSE;

    start_year = calitem->year;
    start_month = calitem->month + calitem->selection_start_month_offset;
    e_calendar_item_normalize_date (calitem, &start_year, &start_month);
    start_day = calitem->selection_start_day;

    end_year = calitem->year;
    end_month = calitem->month + calitem->selection_end_month_offset;
    e_calendar_item_normalize_date (calitem, &end_year, &end_month);
    end_day = calitem->selection_end_day;

    g_date_set_dmy (start_date, start_day, start_month + 1, start_year);
    g_date_set_dmy (end_date, end_day, end_month + 1, end_year);

    return TRUE;
}

static void
e_calendar_item_set_selection_if_emission (ECalendarItem *calitem,
                                           const GDate *start_date,
                                           const GDate *end_date,
                                           gboolean emission)
{
    gint start_year, start_month, start_day;
    gint end_year, end_month, end_day;
    gint new_start_month_offset, new_start_day;
    gint new_end_month_offset, new_end_day;
    gboolean need_update;

    g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));

    /* If start_date is NULL, we clear the selection without changing the
     * month shown. */
    if (start_date == NULL) {
        calitem->selection_set = FALSE;
        calitem->selection_changed = TRUE;
        e_calendar_item_queue_signal_emission (calitem);
        gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
        return;
    }

    if (end_date == NULL)
        end_date = start_date;

    g_return_if_fail (g_date_compare (start_date, end_date) <= 0);

    start_year = g_date_get_year (start_date);
    start_month = g_date_get_month (start_date) - 1;
    start_day = g_date_get_day (start_date);
    end_year = g_date_get_year (end_date);
    end_month = g_date_get_month (end_date) - 1;
    end_day = g_date_get_day (end_date);

    need_update = e_calendar_item_ensure_days_visible (
        calitem,
        start_year,
        start_month,
        start_day,
        end_year,
        end_month,
        end_day,
        emission);

    new_start_month_offset = (start_year - calitem->year) * 12
        + start_month - calitem->month;
    new_start_day = start_day;

    /* This may go outside the visible months, but we don't care. */
    new_end_month_offset = (end_year - calitem->year) * 12
        + end_month - calitem->month;
    new_end_day = end_day;

    if (!calitem->selection_set
        || calitem->selection_start_month_offset != new_start_month_offset
        || calitem->selection_start_day != new_start_day
        || calitem->selection_end_month_offset != new_end_month_offset
        || calitem->selection_end_day != new_end_day) {
        need_update = TRUE;
        if (emission) {
            calitem->selection_changed = TRUE;
            e_calendar_item_queue_signal_emission (calitem);
        }
        calitem->selection_set = TRUE;
        calitem->selection_start_month_offset = new_start_month_offset;
        calitem->selection_start_day = new_start_day;
        calitem->selection_end_month_offset = new_end_month_offset;
        calitem->selection_end_day = new_end_day;

        calitem->selection_real_start_month_offset = new_start_month_offset;
        calitem->selection_real_start_day = new_start_day;
        calitem->selection_from_full_week = FALSE;
    }

    if (need_update) {
        g_signal_emit (
            calitem,
            e_calendar_item_signals[DATE_RANGE_CHANGED], 0);
        gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
    }
}

void
e_calendar_item_style_set (GtkWidget *widget,
                           ECalendarItem *calitem)
{
    GtkStyle *style;
    GdkColor *color;

    style = gtk_widget_get_style (widget);

    color = &style->bg[GTK_STATE_SELECTED];
    calitem->colors[E_CALENDAR_ITEM_COLOR_TODAY_BOX] = *color;

    color = &style->base[GTK_STATE_NORMAL];
    calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_FG] = *color;

    color = &style->bg[GTK_STATE_SELECTED];
    calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_BG_FOCUSED] = *color;

    color = &style->fg[GTK_STATE_INSENSITIVE];
    calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_BG] = *color;

    color = &style->fg[GTK_STATE_INSENSITIVE];
    calitem->colors[E_CALENDAR_ITEM_COLOR_PREV_OR_NEXT_MONTH_FG] = *color;

    e_calendar_item_recalc_sizes (calitem);
}

void
e_calendar_item_set_selection (ECalendarItem *calitem,
                               const GDate *start_date,
                               const GDate *end_date)
{
    /* If the user is in the middle of a selection, we must abort it. */
    if (calitem->selecting) {
        gnome_canvas_item_ungrab (
            GNOME_CANVAS_ITEM (calitem),
            GDK_CURRENT_TIME);
        calitem->selecting = FALSE;
    }

    e_calendar_item_set_selection_if_emission (calitem,
                           start_date, end_date,
                           TRUE);
}

/* This tries to ensure that the given time range is visible. If the range
 * given is longer than we can show, only the start of it will be visible.
 * Note that this will not update the selection. That should be done somewhere
 * else. It returns TRUE if the visible range has been changed. */
static gboolean
e_calendar_item_ensure_days_visible (ECalendarItem *calitem,
                                     gint start_year,
                                     gint start_month,
                                     gint start_day,
                                     gint end_year,
                                     gint end_month,
                                     gint end_day,
                                     gboolean emission)
{
    gint current_end_year, current_end_month;
    gint months_shown;
    gint first_day_offset, days_in_month, days_in_prev_month;
    gboolean need_update = FALSE;

    months_shown = calitem->rows * calitem->cols;

    /* Calculate the range of months currently displayed. */
    current_end_year = calitem->year;
    current_end_month = calitem->month + months_shown - 1;
    e_calendar_item_normalize_date (
        calitem, &current_end_year,
        &current_end_month);

    /* Try to ensure that the end month is shown. */
    if ((end_year == current_end_year + 1 &&
        current_end_month == 11 && end_month == 0) ||
        (end_year == current_end_year && end_month == current_end_month + 1)) {
        /* See if the end of the selection will fit in the
         * leftover days of the month after the last one shown. */
        calitem->month += (months_shown - 1);
        e_calendar_item_normalize_date (
            calitem, &calitem->year,
            &calitem->month);

        e_calendar_item_get_month_info (
            calitem, 0, 0,
            &first_day_offset,
            &days_in_month,
            &days_in_prev_month);

        if (end_day >= E_CALENDAR_ROWS_PER_MONTH * E_CALENDAR_COLS_PER_MONTH -
            first_day_offset - days_in_month) {
            need_update = TRUE;

            calitem->year = end_year;
            calitem->month = end_month - months_shown + 1;
        } else {
            calitem->month -= (months_shown - 1);
        }

        e_calendar_item_normalize_date (
            calitem, &calitem->year,
            &calitem->month);
    }
    else if (end_year > current_end_year ||
         (end_year == current_end_year && end_month > current_end_month)) {
        /* The selection will definitely not fit in the leftover days
         * of the month after the last one shown. */
        need_update = TRUE;

        calitem->year = end_year;
        calitem->month = end_month - months_shown + 1;

        e_calendar_item_normalize_date (
            calitem, &calitem->year,
            &calitem->month);
    }

    /* Now try to ensure that the start month is shown. We do this after
     * the end month so that the start month will always be shown. */
    if (start_year < calitem->year
        || (start_year == calitem->year
        && start_month < calitem->month)) {
        need_update = TRUE;

        /* First we see if the start of the selection will fit in the
         * leftover days of the month before the first one shown. */
        calitem->year = start_year;
        calitem->month = start_month + 1;
        e_calendar_item_normalize_date (
            calitem, &calitem->year,
            &calitem->month);

        e_calendar_item_get_month_info (
            calitem, 0, 0,
            &first_day_offset,
            &days_in_month,
            &days_in_prev_month);

        if (start_day <= days_in_prev_month - first_day_offset) {
            calitem->year = start_year;
            calitem->month = start_month;
        }
    }

    if (need_update && emission)
        e_calendar_item_date_range_changed (calitem);

    return need_update;
}

static gboolean
destroy_menu_idle_cb (gpointer menu)
{
    gtk_widget_destroy (menu);

    return FALSE;
}

static void
deactivate_menu_cb (GtkWidget *menu)
{
    g_signal_handlers_disconnect_by_func (menu, deactivate_menu_cb, NULL);

    g_idle_add (destroy_menu_idle_cb, menu);
}

static void
e_calendar_item_show_popup_menu (ECalendarItem *calitem,
                                 GdkEvent *button_event,
                                 gint month_offset)
{
    GtkWidget *menu, *submenu, *menuitem, *label;
    gint year, month;
    const gchar *name;
    gchar buffer[64];
    guint event_button = 0;
    guint32 event_time;

    menu = gtk_menu_new ();

    for (year = calitem->year - 2; year <= calitem->year + 2; year++) {
        g_snprintf (buffer, 64, "%i", year);
        menuitem = gtk_menu_item_new_with_label (buffer);
        gtk_widget_show (menuitem);
        gtk_container_add (GTK_CONTAINER (menu), menuitem);

        submenu = gtk_menu_new ();
        gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);

        g_object_set_data (
            G_OBJECT (submenu), "year",
            GINT_TO_POINTER (year));
        g_object_set_data (
            G_OBJECT (submenu), "month_offset",
            GINT_TO_POINTER (month_offset));

        for (month = 0; month < 12; month++) {
            name = e_get_month_name (month + 1, FALSE);

            menuitem = gtk_menu_item_new ();
            gtk_widget_show (menuitem);
            gtk_container_add (GTK_CONTAINER (submenu), menuitem);

            label = gtk_label_new (name);
            gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
            gtk_widget_show (label);
            gtk_container_add (GTK_CONTAINER (menuitem), label);

            g_object_set_data (
                G_OBJECT (menuitem), "month",
                GINT_TO_POINTER (month));

            g_signal_connect (
                menuitem, "activate",
                G_CALLBACK (e_calendar_item_on_menu_item_activate),
                calitem);
        }
    }

    g_signal_connect (
        menu, "deactivate",
        G_CALLBACK (deactivate_menu_cb), NULL);

    gdk_event_get_button (button_event, &event_button);
    event_time = gdk_event_get_time (button_event);

    gtk_menu_popup (
        GTK_MENU (menu), NULL, NULL,
        e_calendar_item_position_menu, calitem,
        event_button, event_time);
}

static void
e_calendar_item_on_menu_item_activate (GtkWidget *menuitem,
                                       ECalendarItem *calitem)
{
    GtkWidget *parent;
    gint year, month_offset, month;
    gpointer data;

    parent = gtk_widget_get_parent (menuitem);
    data = g_object_get_data (G_OBJECT (parent), "year");
    year = GPOINTER_TO_INT (data);

    parent = gtk_widget_get_parent (menuitem);
    data = g_object_get_data (G_OBJECT (parent), "month_offset");
    month_offset = GPOINTER_TO_INT (data);

    data = g_object_get_data (G_OBJECT (menuitem), "month");
    month = GPOINTER_TO_INT (data);

    month -= month_offset;
    e_calendar_item_normalize_date (calitem, &year, &month);
    e_calendar_item_set_first_month (calitem, year, month);
}

static void
e_calendar_item_position_menu (GtkMenu *menu,
                               gint *x,
                               gint *y,
                               gboolean *push_in,
                               gpointer user_data)
{
    GtkRequisition requisition;
    gint screen_width, screen_height;

    gtk_widget_get_preferred_size (GTK_WIDGET (menu), &requisition, NULL);

    *x -= (gtk_widget_get_direction(GTK_WIDGET(menu)) == GTK_TEXT_DIR_RTL)
        ? requisition.width - 2
        : 2;
    *y -= requisition.height / 2;

    screen_width = gdk_screen_width ();
    screen_height = gdk_screen_height ();

    *x = CLAMP (*x, 0, screen_width - requisition.width);
    *y = CLAMP (*y, 0, screen_height - requisition.height);
}

/* Sets the function to call to get the colors to use for a particular day. */
void
e_calendar_item_set_style_callback (ECalendarItem *calitem,
                                    ECalendarItemStyleCallback cb,
                                    gpointer data,
                                    GDestroyNotify destroy)
{
    g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));

    if (calitem->style_callback_data && calitem->style_callback_destroy)
        (*calitem->style_callback_destroy) (calitem->style_callback_data);

    calitem->style_callback = cb;
    calitem->style_callback_data = data;
    calitem->style_callback_destroy = destroy;
}

static void
e_calendar_item_date_range_changed (ECalendarItem *calitem)
{
    g_free (calitem->styles);
    calitem->styles = NULL;
    calitem->date_range_changed = TRUE;
    e_calendar_item_queue_signal_emission (calitem);
}

static void
e_calendar_item_queue_signal_emission (ECalendarItem *calitem)
{
    if (calitem->signal_emission_idle_id == 0) {
        calitem->signal_emission_idle_id = g_idle_add_full (
            G_PRIORITY_HIGH, (GSourceFunc)
            e_calendar_item_signal_emission_idle_cb,
            calitem, NULL);
    }
}

static gboolean
e_calendar_item_signal_emission_idle_cb (gpointer data)
{
    ECalendarItem *calitem;

    g_return_val_if_fail (E_IS_CALENDAR_ITEM (data), FALSE);

    calitem = E_CALENDAR_ITEM (data);

    calitem->signal_emission_idle_id = 0;

    /* We ref the calitem & check in case it gets destroyed, since we
     * were getting a free memory write here. */
    g_object_ref ((calitem));

    if (calitem->date_range_changed) {
        calitem->date_range_changed = FALSE;
        g_signal_emit (calitem, e_calendar_item_signals[DATE_RANGE_CHANGED], 0);
    }

    if (calitem->selection_changed) {
        calitem->selection_changed = FALSE;
        g_signal_emit (calitem, e_calendar_item_signals[SELECTION_CHANGED], 0);
    }

    g_object_unref ((calitem));

    return FALSE;
}

/* Sets a callback to use to get the current time. This is useful if the
 * application needs to use its own timezone data rather than rely on the
 * Unix timezone. */
void
e_calendar_item_set_get_time_callback (ECalendarItem *calitem,
                                       ECalendarItemGetTimeCallback cb,
                                       gpointer data,
                                       GDestroyNotify destroy)
{
    g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));

    if (calitem->time_callback_data && calitem->time_callback_destroy)
        (*calitem->time_callback_destroy) (calitem->time_callback_data);

    calitem->time_callback = cb;
    calitem->time_callback_data = data;
    calitem->time_callback_destroy = destroy;
}