aboutsummaryrefslogblamecommitdiffstats
path: root/calendar/gui/e-week-view.c
blob: df1f01c5fce8a2a8059a70037ae31976517f9e12 (plain) (tree)
1
2
3
4
5
6
7
8
9

                                                                           
  
            
                                    
                                     
  
                               
                               
  
                                                                
                                                                   
                                                        
















                                                                      

                        
                        
 
                 
                           
                             

                              
                          
                        


                                
                                  
                                               
                              
                                       
                                    
                                  
                                       
                                  
                                
                                 
                              
                                
                               
                      
                       

                              
                            
                  
                 
                                 
                                   
                               

                                    
                 

            

                        
                              
                       
 
                                       
 





                                          

                                           



                                                                            
 




                                      















                                                                  







                                                                           
                                                                   



                                                                                                              







                                                                     


                                                              








                                                                      
                                                           



                                                          
                                                            
                                                                    



                                                                 







                                                                         

                                                                             
                                                                                     







                                                                                       


                                                                      


                                                                        


                                                                              



                                                                                

                                                           





                                                                  
                                                                                    
 



                                                              
                                   
 
                                                                         
                                                       





                                              
                                  
 
                                                        

                                                
                                             










                                                                    
                                                                 
                                                                   
                                                            
 


                                                                                  
                                                                                
                                                                   


                                                            

 






















                                                                           
                                                          
 



                                       

                                       

                          


                                                        
                                






                                                                  

                                         

                                

                                           


                                           


                                                             












                                                              

                                  

                                         




                                                                                                       
 





                                                                              

































                                                                                          
                                                                             
                                                                                     
                                                                         
                                                                                 
                                                                 
                                                                         
                                                                        
                                                                         
 









                                                                        

                                                                                            
         

                                                                          
                                  
 




                                                              
                                                                           






                                                                                 
                                 
                                                                 


                                                                                



                                                                   












                              


                                                          
 
                                                                                              
 

                               










                                         

                                              




                                                       

                               

                                                                                            
                                                  


                                        
                                         
                                                                         
                                                  

         











                                                                  
 







                                                          


                                                 





                                                                    



                                                    


                                                                        




                                                                      


                                                                                




                                                                    











                                                                        



                                                                                   







                                                                          



                                                                 









                                                                                                                                          
                                                                                                                                            






                                         
                              





                                          
                                                    
                                                                                       





                                                      



                                                                      
































                                                                               


                                         
                                                 

                             
                        



                                                                       


                                        
                            




                                                                                      
                                              






                                                                                              

                                                                        


                                                                                             

                                                                                                                                                    

                                                                      
                                         


                                                                                 


                                                          

                                                           

                                                                                                









                                                                           
                                                              

                                                               
 
                                                           
                                                              











                                                                         
                                                                

                                                                     
 
                                                           
                                                                



                                                                               








                                                                                       



                                                               
 
                                                              
                                                                             
                                                              
                                                                             
 
                                
                                                







                                                                        
                                               

                                         




                                                                               






























                                                                                        
                                                   
                      
                          
                        
                                       


                                        
 


                                                                         









                                                                            

                                                                    


                                                                   
                                       








                                                                            

                                                                      


                                                                   
                                        






                                                                             



                                                          


                                              

                                     
                       
 




                                                                                              


                                                                             


                                                                                      


                                                                   

                                                                                      



















                                                                                       

                                                                   
                                                       
                                                                      
                                                   
                                                                                   



                                                                                  
                                                   
                                                                                   



                                                                        

                                                





                                                              

                             



                                                              

                                         
                                                     

                                                       

















                                                               






















                                                                                








                                                                     






























                                                             






                                                                       

                                                             


                                                                      










                                                                 

                                  
                                                                        




                                                              





                                                                                 


                                                                          

                                                                          



                                                                    






                                                              





                                                                          

























                                                                                                    



                        


                                                           
 
                                     

                                                      
 
                                                                
 






                                                                          
 
                  
                                                    
 
                    

 
           
                                                                       
 
                              
                                 
                                  

                                    




                                                                 


                                                                                                     
 
                                                                                               


                       

                                           



                                                                            

                                       


                                                                          

                                                                                                          
                                                         

                                                                          
     

                                                                       
                                                                                                                    
                                              
                                                  


                                                                       
 

                                                                   


                                                                   


                                                                                

                                          

         
                                              
                                                                               
 

                                             
                                           

                                                                      
                                                                             
                                                                                    
                                                                                        
 
                              
 
                                             

 

                                        
                                             
 

                                                      
 
                                                       

                                             
 






                                                                                                            
         

 
           
                                              
 



                                  
 




                                                                
 


                                                     
 




                                                         
 
 


                                                                               
           
                                                                  



                                                                    
                                                              
                                                 
                                                      

                                                      
 
                                                                                                      
 
                                         


                                                                           

                                                                           

                                               









                                                                                     
                








                                                                                     

















                                                                          
                                                                             
                                                                                                       
                                                                  
                                                                                                         
                                                                      
                                                                  





                                                              
                                                                 
                                                                                                     

                                                                              
                                                                                                                     




                                                                        
                                                                               












                                                                                            


                                                       









                                                                            
                                                                                                      
        




                                                                 
                                                                                                     

                                                                              
                                                                                                                     















                                                                               
 
                                      
           
                                                                  



                                                                    
                                                      








                                                   




                                                               

 

                                                                               
                                                                  



                                                                    
                                                      











                                                                               
 















                                                                             
                                           
                                                 
                                                                             
                                                             













                                                             




                                                                      
















                                                                          





                                                                   
                                                                                                            
 
                                                                      
                                                                  



























                                                                                            



                                                       







                                                                     
                                                                               



                                               
                                                               
                                                                                                     





                                                      
                                                           


                                                                 
                                          



    

                                                                 





                                                      
                                                          

                       
                                                     
 
                              












                                                                   
                                                  



                                                                              




































                                                                           
                                                          


                                                                                                 
 
                                                                  
         















                                                                   

                                     







                                                      
                                        

                       
                                                  













                                                                                                 
 

                                                       
 
 










































                                                                   












                                                                              



















                                                                            


 







                                                  

                                      
 
                         

                                                                              
 



                                                                   
 




                                                                            
                                                                               

                                                                        
                                                            
 

                                                                             

                 


                                                       
 














                                                                             
 


                                                    

                              

                                                                          
 
                                                                       
                                            






                                                                      










                                                                              



                                                                        
 














                                                                                
 



                                                                   
 



                                                            










                                                           
                                    
 





                                                                          
 

                                                
 



                                                             





                                                                              




                                                                              









                                                           
                      












                                                                              






                                                                                       
                             








                                                                              
                                      




                                                                              
                                                      













                                                   
     
                                                     



                                                 

      






                                                                    




                                                                     
                                                                    


                            
                                 
                                               














                                                                             

                                                                       
 



                                                                                                 
 



                                                                       
                                                                   

         
                    









                                                     



                                                       




                                                                              
 

                                                                                                         




                     
























                                                                               



































                                                                          
                                           




















                                                              
                                         
                                  







                                                                                 

                         






                                                                         














































                                                                               


                                              
                              
                                 
                                                
 





                                          
 


                                                                              

                                                                           
         
 















                                                                                        





                                                                               




                                                                      



                                                                          

                                          




                                        
                                     

                             
                                             
 
                              

                                                   
                                                                                                               


                                                                     
                                                                                                           

                             
                                                  

                                                                                             
 
                                                               
                                                                                                                    
                                                           
                                                                                                                  
 










                                                                                                                                         




                              

                                                                  


                                                  
                                         



                                                                                      

                                                


                                                                      
 















                                                                  







                                                             









                                                                            











                                                       
    


























                                                 

                                                                 








                                                                              

                                                                         
                                                                               

                                              
                                                                                            
                                                              




                                                                       




















                                                                                                                                                                  









                                                     



                                                                        
                           


                                               



                                        



                                                                              

                                                                                                     












                                                                                

                                      


                       






                                                                                              

                                                                               





                                                                       
                                                       

                         
                                               
 
                                                    
                                    
                                                         
                                    
                                              

                                    
                                                                           








                                                                                        

                                                                     
















                                                                                                                
                                      
 
                                                        


                                                                                                                



                                                                       
                                                                                    
                                                                    
                                                                                              
                                                                                                           
                                                     
 


                                                                              


                                                             




                                                                               
                                                                              





                                                                               
                                                                   


                                                                       
                                                                              



                                                                          



                                                                             

                            




                                                                           



                                                     




                                                                               


                                                      


                                                                            



                                           






                                                                               


                                    

                                                                       

                                                                 
                                                               

                                                                          
                                                                        
                        


                                                                            
                                                                                       

                                       

                                                                              




                                                                      


                                                                                        



                                                                             
                                                                       






                                                                               


                                                                             

                                                                                  
                                                                       
 

                                                                               






                                                                              
                                                                        


                                                                                
                                                                       








                                                                   
                                                             
                                 
 
                                               


                                                               
                                                                      
 
                              
                                
                                                


 





                                                             
                                                             























                                                                           



                                              
                                                                                                         

                                                         
                                                          




                                                                              

                                                                                                         
         


 
        












                                                               
                            






                                                                              
                             






                                                            



                                                                               
                                                         

                                                            
                                                                                             


                                                       

                                                            
         
                    


 
                                  















                                                                    






                                                                                           
                             










                                                                                                    

                                                                                        




                                                   

                                                      
                                                   

                                                     
                              

                                 



                                                        
                                 
                           
                                                                     








                                                                            


                                                                                  

                                                                            
                                    

                      







                                                                              


                                                                                


                                                                          
                              



                                                                              
                                                                              
                                          


                                                                                          

                                                                               
 
                                                                                                  
 


                                                                                
 




                                                                                



                                                                 
 
                                                                        



                                                                        


                                                                             



                                                                           
                                                 

                                    



                                              
                                                         













                                                                           
                         





                                                                        

                                                  

                              
                                                
                                                                         
                        
                                                                         
                 



















                                                                      
     
                                                                                                      
      
 








                                                                          
 

                                                        
                                                               










                                                      
                           

                                 

                           















                                                                              

                                                  
                                                                 
                 
                       
 
                                                                    
                                                                       
                                
 


                                                                                                     


                                                                           

                                
                                                   





                                                                                       

         
                                                   
                                                   
                                                             


                                                                             
                                                              

                                                                                 

                                      
                                                           










                                                                                                       

                                 








                                                                                                               
                 

         

     
                      
                              
 
                                                               


 
        




























                                                                              





                                                                            
               

                                                             




                                                                    



                               

                                                                  
                              
 

                                                                          
 
                                                                       
                                            


































                                                                              

                                                                

                             
                           
                                

                            
                                  
                                

                              
                                    
 







                                                              
                                                                






                                                                          

















                                                            






                                                                      

                                                                           
                                          
                                                   
                             

                                                                                                
 
                                                          
                                                                                                              

                             



                                                         
 

                                                                        
 
                          
                         
 
                                                                    
                                                                                                 

                                                                   
                                                                                                       
                                                
 
                                                                 
                                                                                                       
                                              
 

                                                                                
 

                                                                          


                                                                      

                                                       
 
                                                                           

                                                                         

                                                                      

         


                                      
                              
 


                    












































































































































































                                                                                        











                                                                                           




                                                        

                       
                                               
 
                                                                      
                                                  

 







                                                                   
 
    

















                                                                                    








                                                        





















                                                                               
                                                                       

                                                                 

                 















                                                                                                



                     













                                                                           
                                                                         























                                                                   
                                                                    





                                                           
                                                                        




                                                               
 































                                                                                                                                    









                                                                
 



                                                                   
 



                                                                                              
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 * Authors :
 *  Damon Chaplin <damon@ximian.com>
 *  Rodrigo Moya <rodrigo@ximian.com>
 *
 * Copyright 1999, Ximian, Inc.
 * Copyright 2001, Ximian, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

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

#include <config.h>

#include "e-week-view.h"
#include "ea-calendar.h"

#include <math.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtkselection.h>
#include <gtk/gtksignal.h>
#include <gtk/gtkvscrollbar.h>
#include <gtk/gtkwindow.h>
#include <gtk/gtkmain.h>
#include <libgnome/gnome-i18n.h>
#include <libgnome/gnome-exec.h>
#include <libgnome/gnome-util.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <libgnomecanvas/gnome-canvas-pixbuf.h>
#include <gal/e-text/e-text.h>
#include <gal/widgets/e-canvas-utils.h>
#include <gal/widgets/e-gui-utils.h>
#include <gal/widgets/e-unicode.h>
#include <e-util/e-categories-config.h>
#include <e-util/e-dialog-utils.h>
#include "dialogs/delete-comp.h"
#include "dialogs/delete-error.h"
#include "dialogs/send-comp.h"
#include "dialogs/cancel-comp.h"
#include "dialogs/recur-comp.h"
#include "comp-util.h"
#include "itip-utils.h"
#include "cal-util/timeutil.h"
#include "calendar-commands.h"
#include "calendar-config.h"
#include "print.h"
#include "goto.h"
#include "e-cal-model-calendar.h"
#include "e-week-view-event-item.h"
#include "e-week-view-layout.h"
#include "e-week-view-main-item.h"
#include "e-week-view-titles-item.h"
#include "misc.h"

/* Images */
#include "art/bell.xpm"
#include "art/recur.xpm"
#include "art/timezone-16.xpm"
#include "art/jump.xpm"

#define E_WEEK_VIEW_SMALL_FONT_PTSIZE 7

#define E_WEEK_VIEW_JUMP_BUTTON_WIDTH   16
#define E_WEEK_VIEW_JUMP_BUTTON_HEIGHT  8

#define E_WEEK_VIEW_JUMP_BUTTON_X_PAD   3
#define E_WEEK_VIEW_JUMP_BUTTON_Y_PAD   3

#define E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS -1

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


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

static void e_week_view_class_init (EWeekViewClass *class);
static void e_week_view_init (EWeekView *week_view);
static void e_week_view_destroy (GtkObject *object);
static void e_week_view_realize (GtkWidget *widget);
static void e_week_view_unrealize (GtkWidget *widget);
static void e_week_view_style_set (GtkWidget *widget,
                   GtkStyle  *previous_style);
static void e_week_view_size_allocate (GtkWidget *widget,
                       GtkAllocation *allocation);
static void e_week_view_recalc_cell_sizes (EWeekView *week_view);
static gint e_week_view_focus_in (GtkWidget *widget,
                  GdkEventFocus *event);
static gint e_week_view_focus_out (GtkWidget *widget,
                   GdkEventFocus *event);
static gint e_week_view_expose_event (GtkWidget *widget,
                      GdkEventExpose *event);
static gboolean e_week_view_get_next_tab_event (EWeekView *week_view,
                        GtkDirectionType direction,
                        gint current_event_num,
                        gint current_span_num,
                        gint *next_event_num,
                        gint *next_span_num);
static gboolean e_week_view_focus (GtkWidget *widget,
                   GtkDirectionType direction);
static GList *e_week_view_get_selected_events (ECalView *cal_view);
static void e_week_view_get_selected_time_range (ECalView *cal_view, time_t *start_time, time_t *end_time);
static void e_week_view_set_selected_time_range (ECalView *cal_view, time_t start_time, time_t end_time);
static gboolean e_week_view_get_visible_time_range (ECalView *cal_view, time_t *start_time, time_t *end_time);
static void e_week_view_update_query (ECalView *cal_view);
static void e_week_view_draw_shadow (EWeekView *week_view);

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

static void e_week_view_free_events (EWeekView *week_view);
static gboolean e_week_view_add_event (CalComponent *comp,
                       time_t     start,
                       time_t     end,
                       gpointer   data);
static void e_week_view_check_layout (EWeekView *week_view);
static void e_week_view_ensure_events_sorted (EWeekView *week_view);
static void e_week_view_reshape_events (EWeekView *week_view);
static void e_week_view_reshape_event_span (EWeekView *week_view,
                        gint event_num,
                        gint span_num);
static void e_week_view_recalc_day_starts (EWeekView *week_view,
                       time_t lower);
static void e_week_view_on_adjustment_changed (GtkAdjustment *adjustment,
                           EWeekView *week_view);
static void e_week_view_on_editing_started (EWeekView *week_view,
                        GnomeCanvasItem *item);
static void e_week_view_on_editing_stopped (EWeekView *week_view,
                        GnomeCanvasItem *item);
static gboolean e_week_view_find_event_from_uid (EWeekView    *week_view,
                         const gchar      *uid,
                         gint         *event_num_return);
typedef gboolean (* EWeekViewForeachEventCallback) (EWeekView *week_view,
                            gint event_num,
                            gpointer data);

static void e_week_view_foreach_event_with_uid (EWeekView *week_view,
                        const gchar *uid,
                        EWeekViewForeachEventCallback callback,
                        gpointer data);
static gboolean e_week_view_on_text_item_event (GnomeCanvasItem *item,
                        GdkEvent *event,
                        EWeekView *week_view);
static gboolean e_week_view_on_jump_button_event (GnomeCanvasItem *item,
                          GdkEvent *event,
                          EWeekView *week_view);
static gboolean e_week_view_key_press (GtkWidget *widget, GdkEventKey *event);
static gboolean e_week_view_do_key_press (GtkWidget *widget,
                      GdkEventKey *event);
static void e_week_view_on_key_up (EWeekView *week_view, GdkEventKey *event);
static void e_week_view_on_key_down (EWeekView *week_view, GdkEventKey *event);
static void e_week_view_on_key_left (EWeekView *week_view, GdkEventKey *event);
static void e_week_view_on_key_right (EWeekView *week_view, GdkEventKey *event);
static gboolean e_week_view_popup_menu (GtkWidget *widget);

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

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

static ECalViewClass *parent_class;

E_MAKE_TYPE (e_week_view, "EWeekView", EWeekView, e_week_view_class_init,
         e_week_view_init, e_cal_view_get_type ());

static void
e_week_view_class_init (EWeekViewClass *class)
{
    GtkObjectClass *object_class;
    GtkWidgetClass *widget_class;
    ECalViewClass *view_class;

    parent_class = g_type_class_peek_parent (class);
    object_class = (GtkObjectClass *) class;
    widget_class = (GtkWidgetClass *) class;
    view_class = (ECalViewClass *) class;

    /* Method override */
    object_class->destroy       = e_week_view_destroy;

    widget_class->realize       = e_week_view_realize;
    widget_class->unrealize     = e_week_view_unrealize;
    widget_class->style_set     = e_week_view_style_set;
    widget_class->size_allocate = e_week_view_size_allocate;
    widget_class->focus_in_event    = e_week_view_focus_in;
    widget_class->focus_out_event   = e_week_view_focus_out;
    widget_class->key_press_event   = e_week_view_key_press;
    widget_class->popup_menu        = e_week_view_popup_menu;
    widget_class->expose_event  = e_week_view_expose_event;
    widget_class->focus             = e_week_view_focus;

    view_class->get_selected_events = e_week_view_get_selected_events;
    view_class->get_selected_time_range = e_week_view_get_selected_time_range;
    view_class->set_selected_time_range = e_week_view_set_selected_time_range;
    view_class->get_visible_time_range = e_week_view_get_visible_time_range;
    view_class->update_query        = e_week_view_update_query;

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

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

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

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

    /* Recalculate the new start of the first week. We just use exactly
       the same time, but with the new timezone. */
    tt.year = g_date_year (&week_view->first_day_shown);
    tt.month = g_date_month (&week_view->first_day_shown);
    tt.day = g_date_day (&week_view->first_day_shown);

    lower = icaltime_as_timet_with_zone (tt, new_zone);

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

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

    GTK_WIDGET_SET_FLAGS (week_view, GTK_CAN_FOCUS);

    week_view->query = NULL;

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

    week_view->layout_timeout_id = 0;

    week_view->spans = NULL;

    week_view->multi_week_view = FALSE;
    week_view->weeks_shown = 6;
    week_view->rows = 6;
    week_view->columns = 2;
    week_view->compress_weekend = TRUE;
    week_view->show_event_end_times = TRUE;
    week_view->week_start_day = 0;      /* Monday. */
    week_view->display_start_day = 0;   /* Monday. */

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

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

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

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

    week_view->main_gc = NULL;

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

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

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

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


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

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

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

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

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

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

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

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

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

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

    gdk_pixbuf_unref (pixbuf);

    /*
     * Scrollbar.
     */
    adjustment = gtk_adjustment_new (0, -52, 52, 1, 1, 1);
    gtk_signal_connect (adjustment, "value_changed",
                G_CALLBACK (e_week_view_on_adjustment_changed),
                week_view);

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

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

    /* connect to ECalView's signals */
    g_signal_connect (G_OBJECT (week_view), "timezone_changed",
              G_CALLBACK (timezone_changed_cb), NULL);
}


/**
 * e_week_view_new:
 * @Returns: a new #EWeekView.
 *
 * Creates a new #EWeekView.
 **/
GtkWidget *
e_week_view_new (void)
{
    GtkWidget *week_view;
    ECalModel *model;
    
    model = E_CAL_MODEL (e_cal_model_calendar_new ());

    week_view = GTK_WIDGET (g_object_new (e_week_view_get_type (), "model", model, NULL));

    g_object_unref (model);
    
    return week_view;
}


static void
e_week_view_destroy (GtkObject *object)
{
    EWeekView *week_view;

    week_view = E_WEEK_VIEW (object);

    e_week_view_cancel_layout (week_view);

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

    if (week_view->query) {
        g_signal_handlers_disconnect_matched (week_view->query, G_SIGNAL_MATCH_DATA,
                              0, 0, NULL, NULL, week_view);
        g_object_unref (week_view->query);
        week_view->query = NULL;
    }

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

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

    GTK_OBJECT_CLASS (parent_class)->destroy (object);
}


static void
e_week_view_realize (GtkWidget *widget)
{
    EWeekView *week_view;
    GdkColormap *colormap;
    gboolean success[E_WEEK_VIEW_COLOR_LAST];
    gint nfailed;

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

    week_view = E_WEEK_VIEW (widget);
    week_view->main_gc = gdk_gc_new (widget->window);

    colormap = gtk_widget_get_colormap (widget);

    /* Allocate the colors. */
    week_view->colors[E_WEEK_VIEW_COLOR_EVEN_MONTHS].red   = 0xe0e0;
    week_view->colors[E_WEEK_VIEW_COLOR_EVEN_MONTHS].green = 0xe0e0;
    week_view->colors[E_WEEK_VIEW_COLOR_EVEN_MONTHS].blue  = 0xe0e0;

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

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

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

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

    week_view->colors[E_WEEK_VIEW_COLOR_GRID].red   = 0 * 257;
    week_view->colors[E_WEEK_VIEW_COLOR_GRID].green = 0 * 257;
    week_view->colors[E_WEEK_VIEW_COLOR_GRID].blue  = 0 * 257;

    week_view->colors[E_WEEK_VIEW_COLOR_SELECTED].red   = 0 * 257;
    week_view->colors[E_WEEK_VIEW_COLOR_SELECTED].green = 0 * 257;
    week_view->colors[E_WEEK_VIEW_COLOR_SELECTED].blue  = 156 * 257;

    week_view->colors[E_WEEK_VIEW_COLOR_SELECTED_UNFOCUSSED].red   = 16 * 257;
    week_view->colors[E_WEEK_VIEW_COLOR_SELECTED_UNFOCUSSED].green = 78 * 257;
    week_view->colors[E_WEEK_VIEW_COLOR_SELECTED_UNFOCUSSED].blue  = 139 * 257;

    week_view->colors[E_WEEK_VIEW_COLOR_DATES].red   = 0 * 257;
    week_view->colors[E_WEEK_VIEW_COLOR_DATES].green = 0 * 257;
    week_view->colors[E_WEEK_VIEW_COLOR_DATES].blue  = 0 * 257;

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

    week_view->colors[E_WEEK_VIEW_COLOR_TODAY].red   = 65535;
    week_view->colors[E_WEEK_VIEW_COLOR_TODAY].green = 0;
    week_view->colors[E_WEEK_VIEW_COLOR_TODAY].blue  = 0;

    nfailed = gdk_colormap_alloc_colors (colormap, week_view->colors,
                         E_WEEK_VIEW_COLOR_LAST, FALSE,
                         TRUE, success);
    if (nfailed)
        g_warning ("Failed to allocate all colors");


    /* Create the pixmaps. */
    week_view->reminder_icon = gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &week_view->reminder_mask, NULL, bell_xpm);
    week_view->recurrence_icon = gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &week_view->recurrence_mask, NULL, recur_xpm);
    week_view->timezone_icon = gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &week_view->timezone_mask, NULL, timezone_16_xpm);
}


static void
e_week_view_unrealize (GtkWidget *widget)
{
    EWeekView *week_view;
    GdkColormap *colormap;

    week_view = E_WEEK_VIEW (widget);

    gdk_gc_unref (week_view->main_gc);
    week_view->main_gc = NULL;

    colormap = gtk_widget_get_colormap (widget);
    gdk_colormap_free_colors (colormap, week_view->colors, E_WEEK_VIEW_COLOR_LAST);

    gdk_pixmap_unref (week_view->reminder_icon);
    week_view->reminder_icon = NULL;
    gdk_pixmap_unref (week_view->recurrence_icon);
    week_view->recurrence_icon = NULL;

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

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

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

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

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

        digit_char = digit;

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

        max_digit_width = MAX (max_digit_width, digit_width);
    }

    return max_digit_width;
}

static void
e_week_view_style_set (GtkWidget *widget,
               GtkStyle  *previous_style)
{
    EWeekView *week_view;
    GtkStyle *style;
    gint day, day_width, max_day_width, max_abbr_day_width;
    gint month, month_width, max_month_width, max_abbr_month_width;
    GDate date;
    gchar buffer[128];
    PangoFontDescription *font_desc;
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;
    PangoLayout *layout;

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

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

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

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

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

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

    /* Save the sizes of various strings in the font, so we can quickly
       decide which date formats to use. */
    g_date_clear (&date, 1);
    g_date_set_dmy (&date, 27, 3, 2000);    /* Must be a Monday. */

    max_day_width = 0;
    max_abbr_day_width = 0;
    for (day = 0; day < 7; day++) {
        g_date_strftime (buffer, 128, "%A", &date);
        day_width = get_string_width (layout, buffer);
        week_view->day_widths[day] = day_width;
        max_day_width = MAX (max_day_width, day_width);

        g_date_strftime (buffer, 128, "%a", &date);
        day_width = get_string_width (layout, buffer);
        week_view->abbr_day_widths[day] = day_width;
        max_abbr_day_width = MAX (max_abbr_day_width, day_width);

        g_date_add_days (&date, 1);
    }

    max_month_width = 0;
    max_abbr_month_width = 0;
    for (month = 0; month < 12; month++) {
        g_date_set_month (&date, month + 1);

        g_date_strftime (buffer, 128, "%B", &date);
        month_width = get_string_width (layout, buffer);
        week_view->month_widths[month] = month_width;
        max_month_width = MAX (max_month_width, month_width);

        g_date_strftime (buffer, 128, "%b", &date);
        month_width = get_string_width (layout, buffer);
        week_view->abbr_month_widths[month] = month_width;
        max_abbr_month_width = MAX (max_abbr_month_width, month_width);
    }

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

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

    g_object_unref (layout);
    pango_font_metrics_unref (font_metrics);
}


/* This recalculates the sizes of each column. */
static void
e_week_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
    EWeekView *week_view;
    gdouble old_x2, old_y2, new_x2, new_y2;

    week_view = E_WEEK_VIEW (widget);

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

    e_week_view_recalc_cell_sizes (week_view);

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


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

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


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

    if (week_view->multi_week_view) {
        week_view->rows = week_view->weeks_shown * 2;
        week_view->columns = week_view->compress_weekend ? 6 : 7;
    } else {
        week_view->rows = 6;
        week_view->columns = 2;
    }

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

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

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

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


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

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


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

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

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

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

    time_width = e_week_view_get_time_string_width (week_view);

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

    pango_font_metrics_unref (font_metrics);
}


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

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

    week_view = E_WEEK_VIEW (widget);

    GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);

    gtk_widget_queue_draw (week_view->main_canvas);

    return FALSE;
}


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

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

    week_view = E_WEEK_VIEW (widget);

    GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);

    gtk_widget_queue_draw (week_view->main_canvas);

    return FALSE;
}


/* This draws a shadow around the top display and main display. */
static gint
e_week_view_expose_event (GtkWidget *widget,
              GdkEventExpose *event)
{
    EWeekView *week_view;

    week_view = E_WEEK_VIEW (widget);

    e_week_view_draw_shadow (week_view);

    if (GTK_WIDGET_CLASS (parent_class)->expose_event)
        (*GTK_WIDGET_CLASS (parent_class)->expose_event)(widget, event);

    return FALSE;
}

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

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

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

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

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

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

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

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

    week_view = E_WEEK_VIEW (widget);

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

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

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

        editable = e_week_view_start_editing_event (week_view,
                                new_event_num,
                                new_span_num,
                                NULL);
        if (editable)
            break;
        else {
            /* check if we should go to the jump button */

            EWeekViewEvent *event;
            EWeekViewEventSpan *span;
            gint current_day;

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

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

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

/* Returns the currently-selected event, or NULL if none */
static GList *
e_week_view_get_selected_events (ECalView *cal_view)
{
    EWeekViewEvent *event = NULL;
    GList *list = NULL;
    EWeekView *week_view = (EWeekView *) cal_view;

    g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), NULL);

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

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

    return list;
}

static void
process_component (EWeekView *week_view, ECalModelComponent *comp_data)
{
    EWeekViewEvent *event;
    gint event_num, num_days;
    CalComponent *comp = NULL;
    AddEventData add_event_data;
    const char *uid;

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

    comp = cal_component_new ();
    if (!cal_component_set_icalcomponent (comp, icalcomponent_new_clone (comp_data->icalcomp))) {
        g_object_unref (comp);

        g_message ("process_component(): Could not set icalcomponent on CalComponent");
        return;
    }

    cal_component_get_uid (comp, &uid);

    /* If the event already exists and the dates didn't change, we can
       update the event fairly easily without changing the events arrays
       or computing a new layout. */
    if (e_week_view_find_event_from_uid (week_view, uid, &event_num)) {
        CalComponent *tmp_comp;

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

        tmp_comp = cal_component_new ();
        cal_component_set_icalcomponent (tmp_comp, icalcomponent_new_clone (comp_data->icalcomp));
        if (!cal_component_has_recurrences (comp)
            && !cal_component_has_recurrences (tmp_comp)
            && cal_component_event_dates_match (comp, tmp_comp)) {
#if 0
            g_print ("updated object's dates unchanged\n");
#endif
            e_week_view_foreach_event_with_uid (week_view, uid, e_week_view_update_event_cb, comp_data);
            g_object_unref (comp);
            g_object_unref (tmp_comp);
            gtk_widget_queue_draw (week_view->main_canvas);
            return;
        }

        /* The dates have changed, so we need to remove the
           old occurrrences before adding the new ones. */
#if 0
        g_print ("dates changed - removing occurrences\n");
#endif
        e_week_view_foreach_event_with_uid (week_view, uid,
                            e_week_view_remove_event_cb,
                            NULL);

        g_object_unref (tmp_comp);
    }

    /* Add the occurrences of the event */
    num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;

    add_event_data.week_view = week_view;
    add_event_data.comp_data = comp_data;
    cal_recur_generate_instances (comp,
                      week_view->day_starts[0],
                      week_view->day_starts[num_days],
                      e_week_view_add_event, &add_event_data,
                      cal_client_resolve_tzid_cb, comp_data->client,
                      e_cal_view_get_timezone (E_CAL_VIEW (week_view)));

    g_object_unref (comp);

    e_week_view_queue_layout (week_view);
}

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

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

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

        comp_data = e_cal_model_get_component_at (e_cal_view_get_model (E_CAL_VIEW (week_view)), r);
        g_assert (comp_data != NULL);
        process_component (week_view, comp_data);
    }
}

static void
e_week_view_draw_shadow (EWeekView *week_view)
{
    gint x1, y1, x2, y2;
    GtkStyle *style;
    GdkGC *light_gc, *dark_gc;
    GdkWindow *window;

    /* Draw the shadow around the graphical displays. */
    x1 = week_view->main_canvas->allocation.x - 1;
    y1 = week_view->main_canvas->allocation.y - 1;
    x2 = x1 + week_view->main_canvas->allocation.width + 2;
    y2 = y1 + week_view->main_canvas->allocation.height + 2;

    style = GTK_WIDGET (week_view)->style;
    dark_gc = style->dark_gc[GTK_STATE_NORMAL];
    light_gc = style->light_gc[GTK_STATE_NORMAL];

    window = GTK_WIDGET (week_view)->window;
    gdk_draw_line (window, dark_gc, x1, y1, x1, y2);
    gdk_draw_line (window, dark_gc, x1, y1, x2, y1);
    gdk_draw_line (window, light_gc, x2, y1, x2, y2);
    gdk_draw_line (window, light_gc, x1, y2, x2, y2);
}

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

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    time_to_gdate_with_zone (&date, start_time, e_cal_view_get_timezone (E_CAL_VIEW (week_view)));

    if (week_view->multi_week_view) {
        /* Find the number of days since the start of the month. */
        day_offset = g_date_day (&date) - 1;

        /* Find the 1st week which starts at or before the start of
           the month. */
        base_date = date;
        g_date_set_day (&base_date, 1);

        /* Calculate the weekday of the 1st of the month, 0 = Mon. */
        weekday = g_date_weekday (&base_date) - 1;

        /* Convert it to an offset from the start of the display. */
        week_start_offset = (weekday + 7 - week_view->display_start_day) % 7;

        /* Add it to the day offset so we go back to the 1st week at
           or before the start of the month. */
        day_offset += week_start_offset;
    } else {
        /* Calculate the weekday of the given date, 0 = Mon. */
        weekday = g_date_weekday (&date) - 1;

        /* Convert it to an offset from the start of the display. */
        week_start_offset = (weekday + 7 - week_view->display_start_day) % 7;

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

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

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

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

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

    /* Make sure the selection is valid. */
    num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
    num_days--;
    week_view->selection_start_day = CLAMP (week_view->selection_start_day,
                        0, num_days);
    week_view->selection_end_day = CLAMP (week_view->selection_end_day,
                          week_view->selection_start_day,
                          num_days);

    /* Reset the adjustment value to 0 if the base address has changed.
       Note that we do this after updating first_day_shown so that our
       signal handler will not try to reload the events. */
    if (update_adjustment_value)
        gtk_adjustment_set_value (GTK_RANGE (week_view->vscrollbar)->adjustment, 0);

    gtk_widget_queue_draw (week_view->main_canvas);
}

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

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

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

    /* Make sure the selection is valid. */
    num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
    num_days--;
    week_view->selection_start_day = CLAMP (week_view->selection_start_day,
                        0, num_days);
    week_view->selection_end_day = CLAMP (week_view->selection_end_day,
                          week_view->selection_start_day,
                          num_days);

    gtk_widget_queue_draw (week_view->main_canvas);
}


/* Returns the selected time range. */
static void
e_week_view_get_selected_time_range (ECalView   *cal_view,
                     time_t     *start_time,
                     time_t     *end_time)
{
    gint start_day, end_day;
    EWeekView *week_view = E_WEEK_VIEW (cal_view);

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

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

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

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

/* Gets the visible time range. Returns FALSE if no time range has been set. */
static gboolean
e_week_view_get_visible_time_range  (ECalView   *cal_view,
                     time_t     *start_time,
                     time_t     *end_time)
{
    gint num_days;
    EWeekView *week_view = E_WEEK_VIEW (cal_view);

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

    num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;
    *start_time = week_view->day_starts[0];
    *end_time = week_view->day_starts[num_days];

    return TRUE;
}


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


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

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

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

    /* Calculate the weekday of the given date, 0 = Mon. */
    weekday = g_date_weekday (date) - 1;

    /* Convert it to an offset from the start of the display. */
    day_offset = (weekday + 7 - week_view->display_start_day) % 7;

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

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

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

        start_tt.year = g_date_year (&base_date);
        start_tt.month = g_date_month (&base_date);
        start_tt.day = g_date_day (&base_date);

        start_time = icaltime_as_timet_with_zone (start_tt,
                              e_cal_view_get_timezone (E_CAL_VIEW (week_view)));

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

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

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

    /* Reset the adjustment value to 0 if the base address has changed.
       Note that we do this after updating first_day_shown so that our
       signal handler will not try to reload the events. */
    if (update_adjustment_value)
        gtk_adjustment_set_value (GTK_RANGE (week_view->vscrollbar)->adjustment, 0);

    gtk_widget_queue_draw (week_view->main_canvas);
}


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

    num_days = week_view->multi_week_view ? week_view->weeks_shown * 7 : 7;

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


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

    return week_view->multi_week_view;
}


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

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

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

    week_view->multi_week_view = multi_week_view;

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

    adjustment = GTK_RANGE (week_view->vscrollbar)->adjustment;
    adjustment->page_increment = page_increment;
    adjustment->page_size = page_size;
    gtk_adjustment_changed (adjustment);

    e_week_view_recalc_cell_sizes (week_view);

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


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

    return week_view->weeks_shown;
}


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

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

    weeks_shown = MIN (weeks_shown, E_WEEK_VIEW_MAX_WEEKS);

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

    week_view->weeks_shown = weeks_shown;

    if (week_view->multi_week_view) {
        page_increment = 4;
        page_size = 5;

        adjustment = GTK_RANGE (week_view->vscrollbar)->adjustment;
        adjustment->page_increment = page_increment;
        adjustment->page_size = page_size;
        gtk_adjustment_changed (adjustment);

        e_week_view_recalc_cell_sizes (week_view);

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

        e_week_view_update_query ((ECalView *) week_view);
    }
}


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

    return week_view->compress_weekend;
}


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

    g_return_if_fail (E_IS_WEEK_VIEW (week_view));

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

    week_view->compress_weekend = compress;

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

    e_week_view_recalc_cell_sizes (week_view);

    need_reload = e_week_view_recalc_display_start_day (week_view);

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

    gtk_widget_queue_draw (week_view->main_canvas);
}