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


                                                               


                                                                  



                                                                    
                                                                             





                                                        
  

   
                    
                   

      
                   
                 

                       
                        
                           
                                    
 
                          
 
                      
                        
                                 
                            
                       
                                     
                                
                                 
                                 
                              

                                
                                         
                                
                  
                 
 



                                                            
                              

                                                    
 

                                                  
 
                                
                            
                                  


                                         



                                              

  
      
               

                              
                               

                            
  
 
                                                                                              

                
                    
                          
                              
                         

                      
                     
                   


                   
                                  
 

                                                                            


                                                       

                                                                                  
           
























                                                                          
                                                    








                                                  
                                                        





                                                         
                                                                                                                                     










                                                         
                         

                                 
                                  
                               

                             


                                        
                                                     
                                                    
 




                                                                                                       
                                                                                 








                                                                                                         
                                             





                                                                                

                                                                                                        








                                                                                   

                                                                                      


                                 


                                                                      




                                  

                                                                                         


                                                                                                              
                                        
                                                                        

                                                                           







                                                                                                                                                       

                                                              
                                                                   
                    
                                                                                                                             









                                                                   







                                                              
 
           



                                                
 





                                                            





                                                            
         
 

                                                                       
 





                                              
                              





                                                                             




                                                                  





                                                                              





                                                                           



                                                                                                           
         

                                                                       


           
                                       
 
                                   
 
                                                    
 





                                                         
         
 








                                                                
 




                                                                                        
 






                                                                       
                                                    
                                                                        


           
                                        
 
                                   
 
                                                    



                                                     
                                                                         


           





                                                                     

                                                        
                                                                            


           



                                                           
 

                            
                                   
                           
                                   
                                             
                                   
                            


                             
                
 
                                            
                                                       
 

                                                          
 

                                                            
                                   
                                        
 

                                                              
 


                                                      
                                                                                                         



                                                                        
         
 

                           



                                                                      

                                                                          
                                                                            




                                                                           
                                                    




                                                                            
                                                                     


                                                       

                                                                             
                                                                                          


                                                     
 
 



                                                     
                                   
                            
 
                                                
                              
 


                                                                  
 


                                                                                          
 
                                                 
 
                                                            
                                                                           
 
                                                                                                                     
         
 




                                                                

 
           


                                                   
 





                                       
 


                                                
 

                                                                                                 
 

                                                                                                          
 

                                                                                 
 


                                                                  
 
                                                                                                                  






                                                                                                                                                 
 





                                                                                                                                       
                 

         
 



                                                      
                                   





                                    
 
                                                
                              
 


                                                                  
 




                                                                                        
 



                                                                      
 
                                                          
                                                                                                        
 


                                                                                                                
 

                                                       
 


                                                
                                                                                    
 
                                                                      
                                                                      

         
                                                              
 



                                                                
 



                                       

 
               
                                                     
                                               
                                                  
 



                                                      
                           
                                            
 
                                                                    
 
                            
                             
 
                                                  
                      
                             
 



                                                                              
                             
 


                                                                        



                                                                                                     
 



                                                                      
 

                                               
 





                                                                                                       
 

                                                                   
                                                                                           
                                                                                                                        
 



                                                                                                                                                                        
 

                                                    
 







                                                                                                                         
 





                                                                                                                                      
                 
 

                                              


                                                                                                                               
         
 

                                

                   


                                                                

 

                                                       
 
                         
                                
                                   
                                  
                                
 
                                                
                              
 


                                                     
                                                                
 


                                                               
 

                                                             
 
                                             
 


                                                                        
                                               
 
                                                                            
 



                                                                                              
 


















                                                                                                                                              

                                                                                                                


                                                                                                                                          
                                                         


                                                                               





















                                                                                                                                             
 




                                                                                                
 
                                         
 
         

 
           






















                                                                  
                                                      
 

                                   
 
                                                                        
 




                                                                
                                                              
 




                                            
 






                                                       
 





                                                 









                                                 
 





                                                 











                                            









                                                        








                                                                  
 







                                                                        
 







                                                                            
 









                                                                       
 








                                                                    
 








                                                                  
 





                                                                   

                                                
 







                                                                 
 
                          
 
                                                       
 
                                      
                                                                           
 


                                                           
 
           
                                                   
 

                                   
                                                                          
 


                                                                   






                                                             
 
 






                                                                   
                                                                     

 

                                                          
                                                    

                                                              
                                                
 
                                                                             

 
        





                                                      
 
                         
                            
                                  






                                                          
                            
                             
 


                                                     

                         
 




                                                               
 
                                                            



                                                                          
 

                                                      
 
                                                                       
 




                                                                          
 

                                           
                                                                         



                                                                         
                                                                                       











                                                                                 
                                                                                
                                             

                                              






                                                                                                                                 
                 

                      
                                                
         
 











                                                                                                                       
 






                                                                                        
 
                                               
 
                   
                                                                                                                     
                                             
 



                                                            
 

                                                                       




                                                                           
                                                                         

                                                                             
                 



                                                                                                      
                            
         
 
                              
                   
 
 



                                                                   
 

                                        
 
    

                                                      

                                                         
 

                                            
 



                                                                   
 
                                     

 

                                                      
 


                                                                   
 
    

                                                      

                               
 
                                                         
 

                                                                    

                       
                                                               


                                                       
 
 



                                                                   

                                                
 
 








                                                                         

                                                              

                                                         
 
                                                  

                                                               
 















                                                                   













                                                                


                                                             




                                                                



                                                             
 
                                                                   
 

                                                                        
 

                                                     
 





                                                                 
 
                                                                    
 

                                                                             
 
                                                                               

 
    


                                                                 
 
                                  
 

                                                         
                                                               
                                                     

                                                   
 

                                                                        
 





                                                                
 
                                                                    
 

                                                                            
 













                                                                              
 
 
    
                                                                    
 
                        
                         
                            
                                  
                                 
                                  
                               
                             
 
                                                     
                                                    
 
                                                                  

                       
                                                      


                                        

                                                                                                       
                                                 
 
                                                                       
                                                                               
                                              

                                         
                                                                                                         
                              
                                             

                                                                         

                                                                        

                                                                                

                                                                                                        






                                                                                   

                                                                                      


                                 


                                                                      

                     

                                  


                                             
 


                                                                 
 

                                             
 
                                                                                                                
                                    
                                                                               
                                                       
                         
                      

                                                                       
                                



                                                                           

                                                                                         

                                                                                                                          
                                                                                                                       










                                                                                    

                                        
                                                                        

                                                                           


                                
                                                                                                                            


                                                         


                                                                                      
                                                                                                                
                                                                                                                                              
                 
 

                                                                   
                             
         


                               
                              

 
    






                                                                                  
                                                        
                                                                                                                                                 




                                       
   
                                      









                                                                       
                                                             



                                                      
 
                                   
                                

                                 
                                
                                               
                                          
                        
                          
 
                                                         
 
                                                                 
                                                                 
 

                              
                                                                      
                                                  
 



                               
                                                                                                   
 
                                                                                     

                                                           

                            
                                                                                                               



                                                       
                                                
 
                                                                                                             

                                                                     
                                             





                                                                          
                                              

                          


                                                                   

                      
                                                                      

                             
                                               
 
                                      
                      
                                             

                                              
 

                                                           

                              

                                        


   
                                       




                                                                          


                                                                        

                     


                                                                        

    



                                                              
 
                         

                                     
 
                                                         
 

                                                     



                                                                                    

                                       








                                                                                                      
                                                                               
                                                    
                                                                              
                                 
 
                                                                                    
                                      
 










                                                                                

                                                                             





                                                                    







                                                                                         
                                                                             

 

                                      

                                           
 
                                                                                  

 
            



                                                               
 
                       
                         
                            
                      
 

                                                               
 

                                               
                                             
                  
                                                             
 


                                                                  
 

                                                                                             
                                                 
                                                
                                                                      
 


                                      
                                             
 
                  






                                      
                                




                                                                



                                                          
 

                                  
                          
 
                                                         
                                                    

                                            


                                                     

                                                                                                                          

                                                                                             
                                             

                                                                      
                                                          


                                                      
 
                                                                                  
 

    

                                                         



                                                     
 

                                  
                                            

                             

                                                         


                                                     
                                               
 




                                                                 

                                             

                                                                       
                                                                                                              




                                                                                             
                                                        

                                                                     
                                                                                                                          




                                                                                                       


                                                                                                                  



                                         
                                        
                                                                         

                                                                              



                                                           
                


                                                                     
 
                                     
         
 

               
                                 
                                  
                                  
 


                            
 

                                                                       
                            
 







                                                                             
                                    
                                                                    
 


                     
              


                                   
 
                         
 
                                                                   
 
                                                                                                

 
    


                                            
 

                                   
                            

                                  
                             



                                         
 
                                                                   


                               





                                                                         


















                                                                         

 
  

                                                           



                                                  
                                               
   

        
                                                                 

                                                     

                                 
                                         


                                             
                                   
                                                          
                          
                          


                                         
                                                        
                                          
                         
                                  
                           
                                   
 
                                                  
 



                                                                          
 
                                              


                                                             

                                            
                                                                      
                                                                                   
 


                                         
                                           




                                                                                                   
                                                        
 
                                                                                                                             







                                         
                                                         
                                     

                                                           

                        
                                       


                           
                                                           
                                                                     
                                    
                                                        

                                                                                           
 
                                                                   
                     
 

                                                            
                           
                                                    
 




                                                                                                           
                    


                                                                                        
                                            
                                                                   
                                                                             
                                            

                                                                           


                             
 


                                                     
                                                                                   
                                                                       
                                             
                                                               
                                                                   
                                                                             
                                            

                                                                           



                                                        
 

                                                                                                              
                          
                                                                                                 


                                            
 


                            
                                                                     
                                                                 
 
                                                             
                                              
 
                                                                                                  
                                                             











                                                                                                                


                                                 
                                                           
                                                                                                 
                                    

                                                                   
 



                      

                                                           
                  
                                                                   
                                                                                           
                                            

                                                                           



                             

                                                            
                                                                      
 
                                                                                              
                                                                                       

                                                                    

                                              
 
                                                                                  
 


                                                          
                                                         

































                                                                          


                                                           

                             
                                                                                         
                                 
 

                     

               

                                                    







                                                                                               
                                                                             
 
                                                             
                                       

                                    
                               












                                                                          


                                                                   









                                                                           














                                                                                                 
 








                                                                                                                               





                       
    
                                                           
                                                                 


                                                         
                                                                              


    





                                           
 
                       

                             





                                           




                                                                       
                    
                                                     
                                                                       

                                                                     
                                                                       

                                                                    

                                           
                                                       

                                                                       
                                                                       

                                                                     
                                                                       
                    








                                                                       


                              
 
                                                                     
                                           



















                                                     
        

                                                   
















                                                                                  


                                                     














                                                                                                                   
























                                                                            
/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Authors:
 *      Rodrigo Moya <rodrigo@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

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

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

#include <shell/e-shell.h>

#include "comp-util.h"
#include "ea-calendar.h"
#include "e-cal-model-calendar.h"
#include "e-calendar-view.h"
#include "itip-utils.h"
#include "dialogs/comp-editor-util.h"
#include "dialogs/delete-comp.h"
#include "dialogs/delete-error.h"
#include "dialogs/event-editor.h"
#include "dialogs/send-comp.h"
#include "dialogs/cancel-comp.h"
#include "dialogs/recur-comp.h"
#include "dialogs/select-source-dialog.h"
#include "dialogs/goto-dialog.h"
#include "print.h"
#include "misc.h"

#define E_CALENDAR_VIEW_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), E_TYPE_CALENDAR_VIEW, ECalendarViewPrivate))

struct _ECalendarViewPrivate {
    /* The GnomeCalendar we are associated to */
    GnomeCalendar *calendar;

    /* The calendar model we are monitoring */
    ECalModel *model;

    gchar *default_category;
    gint time_divisions;
    GSList *selected_cut_list;

    GtkTargetList *copy_target_list;
    GtkTargetList *paste_target_list;

    /* All keyboard devices are grabbed
     * while a tooltip window is shown. */
    GQueue grabbed_keyboards;
};

enum {
    PROP_0,
    PROP_COPY_TARGET_LIST,
    PROP_MODEL,
    PROP_PASTE_TARGET_LIST,
    PROP_TIME_DIVISIONS,
    PROP_IS_EDITING
};

/* FIXME Why are we emitting these event signals here? Can't the model just be listened to? */
/* Signal IDs */
enum {
    POPUP_EVENT,
    SELECTION_CHANGED,
    SELECTED_TIME_CHANGED,
    TIMEZONE_CHANGED,
    EVENT_CHANGED,
    EVENT_ADDED,
    USER_CREATED,
    OPEN_EVENT,
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

static void calendar_view_selectable_init (ESelectableInterface *interface);

G_DEFINE_ABSTRACT_TYPE_WITH_CODE (
    ECalendarView, e_calendar_view, GTK_TYPE_TABLE,
    G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)
    G_IMPLEMENT_INTERFACE (E_TYPE_SELECTABLE, calendar_view_selectable_init));

static void
calendar_view_add_retract_data (ECalComponent *comp,
                                const gchar *retract_comment,
                                CalObjModType mod)
{
    icalcomponent *icalcomp = NULL;
    icalproperty *icalprop = NULL;

    icalcomp = e_cal_component_get_icalcomponent (comp);
    if (retract_comment && *retract_comment)
        icalprop = icalproperty_new_x (retract_comment);
    else
        icalprop = icalproperty_new_x ("0");
    icalproperty_set_x_name (icalprop, "X-EVOLUTION-RETRACT-COMMENT");
    icalcomponent_add_property (icalcomp, icalprop);

    if (mod == CALOBJ_MOD_ALL)
        icalprop = icalproperty_new_x ("All");
    else
        icalprop = icalproperty_new_x ("This");
    icalproperty_set_x_name (icalprop, "X-EVOLUTION-RECUR-MOD");
    icalcomponent_add_property (icalcomp, icalprop);
}

static gboolean
calendar_view_check_for_retract (ECalComponent *comp,
                                 ECalClient *client)
{
    ECalComponentOrganizer organizer;
    const gchar *strip;
    gchar *email = NULL;
    gboolean ret_val;

    if (!e_cal_component_has_attendees (comp))
        return FALSE;

    if (!e_cal_client_check_save_schedules (client))
        return FALSE;

    e_cal_component_get_organizer (comp, &organizer);
    strip = itip_strip_mailto (organizer.value);

    ret_val =
        e_client_get_backend_property_sync (E_CLIENT (client), CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS, &email, NULL, NULL) &&
        (g_ascii_strcasecmp (email, strip) == 0);

    g_free (email);

    return ret_val;
}

static void
calendar_view_delete_event (ECalendarView *cal_view,
                            ECalendarViewEvent *event)
{
    ECalModel *model;
    ECalComponent *comp;
    ECalComponentVType vtype;
    ESourceRegistry *registry;
    gboolean delete = TRUE;
    GError *error = NULL;

    if (!is_comp_data_valid (event))
        return;

    model = e_calendar_view_get_model (cal_view);
    registry = e_cal_model_get_registry (model);

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

    /*FIXME remove it once the we dont set the recurrence id for all the generated instances */
    if (!e_cal_client_check_recurrences_no_master (event->comp_data->client))
        e_cal_component_set_recurid (comp, NULL);

    /*FIXME Retract should be moved to Groupwise features plugin */
    if (calendar_view_check_for_retract (comp, event->comp_data->client)) {
        gchar *retract_comment = NULL;
        gboolean retract = FALSE;

        delete = prompt_retract_dialog (comp, &retract_comment, GTK_WIDGET (cal_view), &retract);
        if (retract) {
            GSList *users = NULL;
            icalcomponent *icalcomp = NULL, *mod_comp = NULL;

            calendar_view_add_retract_data (
                comp, retract_comment, CALOBJ_MOD_ALL);
            icalcomp = e_cal_component_get_icalcomponent (comp);
            icalcomponent_set_method (icalcomp, ICAL_METHOD_CANCEL);
            if (!e_cal_client_send_objects_sync (event->comp_data->client, icalcomp, &users,
                        &mod_comp, NULL, &error))   {
                delete_error_dialog (error, E_CAL_COMPONENT_EVENT);
                g_clear_error (&error);
                error = NULL;
            } else {

                if (mod_comp)
                    icalcomponent_free (mod_comp);

                if (users) {
                    g_slist_foreach (users, (GFunc) g_free, NULL);
                    g_slist_free (users);
                }
            }
        }
    } else if (e_cal_model_get_confirm_delete (model))
        delete = delete_component_dialog (
            comp, FALSE, 1, vtype, GTK_WIDGET (cal_view));

    if (delete) {
        const gchar *uid;
        gchar *rid = NULL;

        if ((itip_organizer_is_user (registry, comp, event->comp_data->client) ||
             itip_sentby_is_user (registry, comp, event->comp_data->client))
            && cancel_component_dialog ((GtkWindow *) gtk_widget_get_toplevel (GTK_WIDGET (cal_view)),
                        event->comp_data->client,
                        comp, TRUE))
            itip_send_comp (
                registry, E_CAL_COMPONENT_METHOD_CANCEL,
                comp, event->comp_data->client, NULL, NULL,
                NULL, TRUE, FALSE);

        e_cal_component_get_uid (comp, &uid);
        if (!uid || !*uid) {
            g_object_unref (comp);
            return;
        }
        rid = e_cal_component_get_recurid_as_string (comp);
        if (e_cal_util_component_is_instance (event->comp_data->icalcomp) || e_cal_util_component_has_recurrences (event->comp_data->icalcomp))
            e_cal_client_remove_object_sync (
                event->comp_data->client, uid,
                rid, CALOBJ_MOD_ALL, NULL, &error);
        else
            e_cal_client_remove_object_sync (event->comp_data->client, uid, NULL, CALOBJ_MOD_THIS, NULL, &error);

        delete_error_dialog (error, E_CAL_COMPONENT_EVENT);
        g_clear_error (&error);
        g_free (rid);
    }

    g_object_unref (comp);
}

static void
calendar_view_set_model (ECalendarView *calendar_view,
                         ECalModel *model)
{
    g_return_if_fail (calendar_view->priv->model == NULL);
    g_return_if_fail (E_IS_CAL_MODEL (model));

    calendar_view->priv->model = g_object_ref (model);
}

static void
calendar_view_set_property (GObject *object,
                            guint property_id,
                            const GValue *value,
                            GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_MODEL:
            calendar_view_set_model (
                E_CALENDAR_VIEW (object),
                g_value_get_object (value));
            return;

        case PROP_TIME_DIVISIONS:
            e_calendar_view_set_time_divisions (
                E_CALENDAR_VIEW (object),
                g_value_get_int (value));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
calendar_view_get_property (GObject *object,
                            guint property_id,
                            GValue *value,
                            GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_COPY_TARGET_LIST:
            g_value_set_boxed (
                value, e_calendar_view_get_copy_target_list (
                E_CALENDAR_VIEW (object)));
            return;

        case PROP_MODEL:
            g_value_set_object (
                value, e_calendar_view_get_model (
                E_CALENDAR_VIEW (object)));
            return;

        case PROP_PASTE_TARGET_LIST:
            g_value_set_boxed (
                value, e_calendar_view_get_paste_target_list (
                E_CALENDAR_VIEW (object)));
            return;

        case PROP_TIME_DIVISIONS:
            g_value_set_int (
                value, e_calendar_view_get_time_divisions (
                E_CALENDAR_VIEW (object)));
            return;

        case PROP_IS_EDITING:
            g_value_set_boolean (value, e_calendar_view_is_editing (E_CALENDAR_VIEW (object)));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
calendar_view_dispose (GObject *object)
{
    ECalendarViewPrivate *priv;

    priv = E_CALENDAR_VIEW_GET_PRIVATE (object);

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

    if (priv->copy_target_list != NULL) {
        gtk_target_list_unref (priv->copy_target_list);
        priv->copy_target_list = NULL;
    }

    if (priv->paste_target_list != NULL) {
        gtk_target_list_unref (priv->paste_target_list);
        priv->paste_target_list = NULL;
    }

    if (priv->selected_cut_list) {
        g_slist_foreach (priv->selected_cut_list, (GFunc) g_object_unref, NULL);
        g_slist_free (priv->selected_cut_list);
        priv->selected_cut_list = NULL;
    }

    while (!g_queue_is_empty (&priv->grabbed_keyboards)) {
        GdkDevice *keyboard;
        keyboard = g_queue_pop_head (&priv->grabbed_keyboards);
        gdk_device_ungrab (keyboard, GDK_CURRENT_TIME);
        g_object_unref (keyboard);
    }

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

static void
calendar_view_finalize (GObject *object)
{
    ECalendarViewPrivate *priv;

    priv = E_CALENDAR_VIEW_GET_PRIVATE (object);

    g_free (priv->default_category);

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

static void
calendar_view_constructed (GObject *object)
{
    /* Do this after calendar_view_init() so extensions can query
     * the GType accurately.  See GInstanceInitFunc documentation
     * for details of the problem. */
    e_extensible_load_extensions (E_EXTENSIBLE (object));

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

static void
calendar_view_update_actions (ESelectable *selectable,
                              EFocusTracker *focus_tracker,
                              GdkAtom *clipboard_targets,
                              gint n_clipboard_targets)
{
    ECalendarView *view;
    GtkAction *action;
    GtkTargetList *target_list;
    GList *list, *iter;
    gboolean can_paste = FALSE;
    gboolean sources_are_editable = TRUE;
    gboolean recurring = FALSE;
    gboolean is_editing;
    gboolean sensitive;
    const gchar *tooltip;
    gint n_selected;
    gint ii;

    view = E_CALENDAR_VIEW (selectable);
    is_editing = e_calendar_view_is_editing (view);

    list = e_calendar_view_get_selected_events (view);
    n_selected = g_list_length (list);

    for (iter = list; iter != NULL; iter = iter->next) {
        ECalendarViewEvent *event = iter->data;
        ECalClient *client;
        icalcomponent *icalcomp;

        if (event == NULL || event->comp_data == NULL)
            continue;

        client = event->comp_data->client;
        icalcomp = event->comp_data->icalcomp;

        sources_are_editable = sources_are_editable && !e_client_is_readonly (E_CLIENT (client));

        recurring |=
            e_cal_util_component_is_instance (icalcomp) ||
            e_cal_util_component_has_recurrences (icalcomp);
    }

    g_list_free (list);

    target_list = e_selectable_get_paste_target_list (selectable);
    for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++)
        can_paste = gtk_target_list_find (
            target_list, clipboard_targets[ii], NULL);

    action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
    sensitive = (n_selected > 0) && sources_are_editable && !is_editing;
    tooltip = _("Cut selected events to the clipboard");
    gtk_action_set_sensitive (action, sensitive);
    gtk_action_set_tooltip (action, tooltip);

    action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
    sensitive = (n_selected > 0) && !is_editing;
    tooltip = _("Copy selected events to the clipboard");
    gtk_action_set_sensitive (action, sensitive);
    gtk_action_set_tooltip (action, tooltip);

    action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
    sensitive = sources_are_editable && can_paste && !is_editing;
    tooltip = _("Paste events from the clipboard");
    gtk_action_set_sensitive (action, sensitive);
    gtk_action_set_tooltip (action, tooltip);

    action = e_focus_tracker_get_delete_selection_action (focus_tracker);
    sensitive = (n_selected > 0) && sources_are_editable && !recurring && !is_editing;
    tooltip = _("Delete selected events");
    gtk_action_set_sensitive (action, sensitive);
    gtk_action_set_tooltip (action, tooltip);
}

static void
calendar_view_cut_clipboard (ESelectable *selectable)
{
    ECalendarView *cal_view;
    ECalendarViewPrivate *priv;
    GList *selected, *l;

    cal_view = E_CALENDAR_VIEW (selectable);
    priv = cal_view->priv;

    selected = e_calendar_view_get_selected_events (cal_view);
    if (!selected)
        return;

#if 0  /* KILL-BONOBO */
    e_calendar_view_set_status_message (cal_view, _("Deleting selected objects"), -1);
#endif

    e_selectable_copy_clipboard (selectable);

    for (l = selected; l != NULL; l = g_list_next (l)) {
        ECalendarViewEvent *event = (ECalendarViewEvent *) l->data;

        priv->selected_cut_list = g_slist_prepend (priv->selected_cut_list, g_object_ref (event->comp_data));
    }

#if 0  /* KILL-BONOBO */
    e_calendar_view_set_status_message (cal_view, NULL, -1);
#endif

    g_list_free (selected);
}

static void
add_related_timezones (icalcomponent *des_icalcomp,
                       icalcomponent *src_icalcomp,
                       ECalClient *client)
{
    icalproperty_kind look_in[] = {
        ICAL_DTSTART_PROPERTY,
        ICAL_DTEND_PROPERTY,
        ICAL_NO_PROPERTY
    };
    gint i;

    g_return_if_fail (des_icalcomp != NULL);
    g_return_if_fail (src_icalcomp != NULL);
    g_return_if_fail (client != NULL);

    for (i = 0; look_in[i] != ICAL_NO_PROPERTY; i++) {
        icalproperty *prop = icalcomponent_get_first_property (src_icalcomp, look_in[i]);

        if (prop) {
            icalparameter *par = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);

            if (par) {
                const gchar *tzid = icalparameter_get_tzid (par);

                if (tzid) {
                    GError *error = NULL;
                    icaltimezone *zone = NULL;

                    if (!e_cal_client_get_timezone_sync (client, tzid, &zone, NULL, &error)) {
                        g_warning ("%s: Cannot get timezone for '%s'. %s", G_STRFUNC, tzid, error ? error->message : "");
                        if (error)
                            g_error_free (error);
                    } else if (zone &&
                        icalcomponent_get_timezone (des_icalcomp, icaltimezone_get_tzid (zone)) == NULL) {
                        /* do not duplicate timezones in the component */
                        icalcomponent *vtz_comp;

                        vtz_comp = icaltimezone_get_component (zone);
                        if (vtz_comp)
                            icalcomponent_add_component (des_icalcomp, icalcomponent_new_clone (vtz_comp));
                    }
                }
            }
        }
    }
}

static void
calendar_view_copy_clipboard (ESelectable *selectable)
{
    ECalendarView *cal_view;
    ECalendarViewPrivate *priv;
    GList *selected, *l;
    gchar *comp_str;
    icalcomponent *vcal_comp;
    icalcomponent *new_icalcomp;
    ECalendarViewEvent *event;
    GtkClipboard *clipboard;

    cal_view = E_CALENDAR_VIEW (selectable);
    priv = cal_view->priv;

    selected = e_calendar_view_get_selected_events (cal_view);
    if (!selected)
        return;

    if (priv->selected_cut_list) {
        g_slist_foreach (priv->selected_cut_list, (GFunc) g_object_unref, NULL);
        g_slist_free (priv->selected_cut_list);
        priv->selected_cut_list = NULL;
    }

    /* create top-level VCALENDAR component and add VTIMEZONE's */
    vcal_comp = e_cal_util_new_top_level ();
    for (l = selected; l != NULL; l = l->next) {
        event = (ECalendarViewEvent *) l->data;

        if (event && is_comp_data_valid (event)) {
            e_cal_util_add_timezones_from_component (vcal_comp, event->comp_data->icalcomp);

            add_related_timezones (vcal_comp, event->comp_data->icalcomp, event->comp_data->client);
        }
    }

    for (l = selected; l != NULL; l = l->next) {
        event = (ECalendarViewEvent *) l->data;

        if (!is_comp_data_valid (event))
            continue;

        new_icalcomp = icalcomponent_new_clone (event->comp_data->icalcomp);

        /* do not remove RECURRENCE-IDs from copied objects */
        icalcomponent_add_component (vcal_comp, new_icalcomp);
    }

    comp_str = icalcomponent_as_ical_string_r (vcal_comp);

    /* copy the VCALENDAR to the clipboard */
    clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
    e_clipboard_set_calendar (clipboard, comp_str, -1);
    gtk_clipboard_store (clipboard);

    /* free memory */
    icalcomponent_free (vcal_comp);
    g_free (comp_str);
    g_list_free (selected);
}

static gboolean
clipboard_get_calendar_data (ECalendarView *cal_view,
                             const gchar *text,
                             GSList **copied_list)
{
    icalcomponent *icalcomp;
    icalcomponent_kind kind;
    time_t selected_time_start, selected_time_end;
    icaltimezone *default_zone;
    ECalClient *client;
    gboolean in_top_canvas, ret = FALSE;

    g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE);

    if (!text || !*text)
        return FALSE;

    icalcomp = icalparser_parse_string (text);
    if (!icalcomp)
        return FALSE;

    /* check the type of the component */
    /* FIXME An error dialog if we return? */
    kind = icalcomponent_isa (icalcomp);
    if (kind != ICAL_VCALENDAR_COMPONENT && kind != ICAL_VEVENT_COMPONENT)
        return FALSE;

    default_zone = e_cal_model_get_timezone (cal_view->priv->model);
    client = e_cal_model_ref_default_client (cal_view->priv->model);

#if 0  /* KILL-BONOBO */
    e_calendar_view_set_status_message (cal_view, _("Updating objects"), -1);
#endif
    e_calendar_view_get_selected_time_range (cal_view, &selected_time_start, &selected_time_end);

    if ((selected_time_end - selected_time_start) == 60 * 60 * 24)
        in_top_canvas = TRUE;
    else
        in_top_canvas = FALSE;

    if (kind == ICAL_VCALENDAR_COMPONENT) {
        icalcomponent *subcomp;

        /* add timezones first, to have them ready */
        for (subcomp = icalcomponent_get_first_component (icalcomp, ICAL_VTIMEZONE_COMPONENT);
             subcomp;
             subcomp = icalcomponent_get_next_component (icalcomp, ICAL_VTIMEZONE_COMPONENT)) {
            icaltimezone *zone;
            GError *error = NULL;

            zone = icaltimezone_new ();
            icaltimezone_set_component (zone, subcomp);
            if (!e_cal_client_add_timezone_sync (client, zone, NULL, &error)) {
                icalproperty *tzidprop = icalcomponent_get_first_property (subcomp, ICAL_TZID_PROPERTY);

                g_warning ("%s: Add zone '%s' failed. %s", G_STRFUNC, tzidprop ? icalproperty_get_tzid (tzidprop) : "???", error ? error->message : "");
                if (error)
                    g_error_free (error);
            }

            icaltimezone_free (zone, 1);
        }

        for (subcomp = icalcomponent_get_first_component (icalcomp, ICAL_VEVENT_COMPONENT);
             subcomp;
             subcomp = icalcomponent_get_next_component (icalcomp, ICAL_VEVENT_COMPONENT)) {
            if (e_cal_util_component_has_recurrences (subcomp)) {
                icalproperty *icalprop = icalcomponent_get_first_property (subcomp, ICAL_RRULE_PROPERTY);
                if (icalprop)
                    icalproperty_remove_parameter_by_name (icalprop, "X-EVOLUTION-ENDDATE");
            }

            ret = e_calendar_view_add_event (cal_view, client, selected_time_start, default_zone, subcomp, in_top_canvas);
            if (!ret)
                break;

            if (copied_list)
                *copied_list = g_slist_prepend (*copied_list, g_strdup (icalcomponent_get_uid (subcomp)));
        }

        icalcomponent_free (icalcomp);
    } else {
        ret = e_calendar_view_add_event (cal_view, client, selected_time_start, default_zone, icalcomp, in_top_canvas);
        if (ret && copied_list)
            *copied_list = g_slist_prepend (*copied_list, g_strdup (icalcomponent_get_uid (icalcomp)));
    }

    g_object_unref (client);

    return ret;

#if 0  /* KILL-BONOBO */
    e_calendar_view_set_status_message (cal_view, NULL, -1);
#endif
}

static void
calendar_view_paste_clipboard (ESelectable *selectable)
{
    ECalModel *model;
    ECalendarView *cal_view;
    ECalendarViewPrivate *priv;
    ESourceRegistry *registry;
    GtkClipboard *clipboard;

    cal_view = E_CALENDAR_VIEW (selectable);
    priv = cal_view->priv;

    model = e_calendar_view_get_model (cal_view);
    registry = e_cal_model_get_registry (model);

    clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);

    /* Paste text into an event being edited. */
    if (gtk_clipboard_wait_is_text_available (clipboard)) {
        ECalendarViewClass *class;

        class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
        g_return_if_fail (class->paste_text != NULL);

        class->paste_text (cal_view);

    /* Paste iCalendar data into the view. */
    } else if (e_clipboard_wait_is_calendar_available (clipboard)) {
        gchar *calendar_source;
        GSList *copied_list = NULL, *l;

        calendar_source = e_clipboard_wait_for_calendar (clipboard);

        if (priv->selected_cut_list)
            clipboard_get_calendar_data (cal_view, calendar_source, &copied_list);
        else
            clipboard_get_calendar_data (cal_view, calendar_source, NULL);

        if (copied_list && priv->selected_cut_list) {
            for (l = priv->selected_cut_list; l != NULL; l = l->next) {
                ECalComponent *comp;
                ECalModelComponent *comp_data = (ECalModelComponent *) l->data;
                const gchar *uid;
                GError *error = NULL;
                GSList *found = NULL;

                /* Remove them one by one after ensuring it has been copied to the destination successfully */
                found = g_slist_find_custom (copied_list, icalcomponent_get_uid (comp_data->icalcomp), (GCompareFunc) strcmp);
                if (!found)
                    continue;

                g_free (found->data);
                copied_list = g_slist_delete_link (copied_list, found);

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

                if ((itip_organizer_is_user (registry, comp, comp_data->client) ||
                            itip_sentby_is_user (registry, comp, comp_data->client))
                        && cancel_component_dialog ((GtkWindow *) gtk_widget_get_toplevel (GTK_WIDGET (cal_view)),
                            comp_data->client, comp, TRUE))
                    itip_send_comp (
                        registry,
                        E_CAL_COMPONENT_METHOD_CANCEL,
                        comp, comp_data->client,
                        NULL, NULL, NULL, TRUE, FALSE);

                e_cal_component_get_uid (comp, &uid);
                if (e_cal_component_is_instance (comp)) {
                    gchar *rid = NULL;
                    icalcomponent *icalcomp;

                    /* when cutting detached instances, only cut that instance */
                    rid = e_cal_component_get_recurid_as_string (comp);
                    if (e_cal_client_get_object_sync (comp_data->client, uid, rid, &icalcomp, NULL, NULL)) {
                        e_cal_client_remove_object_sync (comp_data->client, uid, rid, CALOBJ_MOD_THIS, NULL, &error);
                        icalcomponent_free (icalcomp);
                    } else
                        e_cal_client_remove_object_sync (comp_data->client, uid, NULL, CALOBJ_MOD_ALL, NULL, &error);
                    g_free (rid);
                } else
                    e_cal_client_remove_object_sync (comp_data->client, uid, NULL, CALOBJ_MOD_ALL, NULL, &error);
                delete_error_dialog (error, E_CAL_COMPONENT_EVENT);

                g_clear_error (&error);
                g_object_unref (comp);
            }
        }

        if (priv->selected_cut_list) {
            g_slist_foreach (priv->selected_cut_list, (GFunc) g_object_unref, NULL);
            g_slist_free (priv->selected_cut_list);
        }
        priv->selected_cut_list = NULL;

        g_free (calendar_source);

    }
}

static void
calendar_view_delete_selection (ESelectable *selectable)
{
    ECalendarView *cal_view;
    GList *selected, *iter;

    cal_view = E_CALENDAR_VIEW (selectable);

    selected = e_calendar_view_get_selected_events (cal_view);

    for (iter = selected; iter != NULL; iter = iter->next) {
        ECalendarViewEvent *event = iter->data;

        /* XXX Why would this ever be NULL? */
        if (event == NULL)
            continue;

        calendar_view_delete_event (cal_view, event);
    }

    g_list_free (selected);
}

static void
e_calendar_view_class_init (ECalendarViewClass *class)
{
    GObjectClass *object_class;
    GtkBindingSet *binding_set;

    g_type_class_add_private (class, sizeof (ECalendarViewPrivate));

    object_class = G_OBJECT_CLASS (class);
    object_class->set_property = calendar_view_set_property;
    object_class->get_property = calendar_view_get_property;
    object_class->dispose = calendar_view_dispose;
    object_class->finalize = calendar_view_finalize;
    object_class->constructed = calendar_view_constructed;

    class->selection_changed = NULL;
    class->selected_time_changed = NULL;
    class->event_changed = NULL;
    class->event_added = NULL;
    class->user_created = NULL;

    class->get_selected_events = NULL;
    class->get_selected_time_range = NULL;
    class->set_selected_time_range = NULL;
    class->get_visible_time_range = NULL;
    class->update_query = NULL;
    class->open_event = e_calendar_view_open_event;
    class->paste_text = NULL;

    /* Inherited from ESelectableInterface */
    g_object_class_override_property (
        object_class,
        PROP_COPY_TARGET_LIST,
        "copy-target-list");

    g_object_class_install_property (
        object_class,
        PROP_MODEL,
        g_param_spec_object (
            "model",
            "Model",
            NULL,
            E_TYPE_CAL_MODEL,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT_ONLY));

    /* Inherited from ESelectableInterface */
    g_object_class_override_property (
        object_class,
        PROP_PASTE_TARGET_LIST,
        "paste-target-list");

    g_object_class_install_property (
        object_class,
        PROP_TIME_DIVISIONS,
        g_param_spec_int (
            "time-divisions",
            "Time Divisions",
            NULL,
            G_MININT,
            G_MAXINT,
            30,
            G_PARAM_READWRITE));

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

    signals[POPUP_EVENT] = g_signal_new (
        "popup-event",
        G_TYPE_FROM_CLASS (class),
        G_SIGNAL_RUN_FIRST,
        G_STRUCT_OFFSET (ECalendarViewClass, popup_event),
        NULL, NULL,
        g_cclosure_marshal_VOID__BOXED,
        G_TYPE_NONE, 1,
        GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

    signals[SELECTION_CHANGED] = g_signal_new (
        "selection-changed",
        G_TYPE_FROM_CLASS (class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ECalendarViewClass, selection_changed),
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);

    signals[SELECTED_TIME_CHANGED] = g_signal_new (
        "selected-time-changed",
        G_TYPE_FROM_CLASS (class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ECalendarViewClass, selected_time_changed),
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);

    signals[TIMEZONE_CHANGED] = g_signal_new (
        "timezone-changed",
        G_TYPE_FROM_CLASS (class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ECalendarViewClass, timezone_changed),
        NULL, NULL,
        e_marshal_VOID__POINTER_POINTER,
        G_TYPE_NONE, 2,
        G_TYPE_POINTER,
        G_TYPE_POINTER);

    signals[EVENT_CHANGED] = g_signal_new (
        "event-changed",
        G_TYPE_FROM_CLASS (object_class),
        G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
        G_STRUCT_OFFSET (ECalendarViewClass, event_changed),
        NULL, NULL,
        g_cclosure_marshal_VOID__POINTER,
        G_TYPE_NONE, 1,
        G_TYPE_POINTER);

    signals[EVENT_ADDED] = g_signal_new (
        "event-added",
        G_TYPE_FROM_CLASS (object_class),
        G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
        G_STRUCT_OFFSET (ECalendarViewClass, event_added),
        NULL, NULL,
        g_cclosure_marshal_VOID__POINTER,
        G_TYPE_NONE, 1,
        G_TYPE_POINTER);

    signals[USER_CREATED] = g_signal_new (
        "user-created",
        G_TYPE_FROM_CLASS (class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ECalendarViewClass, user_created),
        NULL, NULL,
        g_cclosure_marshal_VOID__OBJECT,
        G_TYPE_NONE, 1, G_TYPE_OBJECT);

    signals[OPEN_EVENT] = g_signal_new (
        "open-event",
        G_TYPE_FROM_CLASS (class),
        G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
        G_STRUCT_OFFSET (ECalendarViewClass, open_event),
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);

    /* Key bindings */

    binding_set = gtk_binding_set_by_class (class);

    gtk_binding_entry_add_signal (
        binding_set, GDK_KEY_o, GDK_CONTROL_MASK, "open-event", 0);

    /* init the accessibility support for e_day_view */
    e_cal_view_a11y_init ();
}

static void
e_calendar_view_init (ECalendarView *calendar_view)
{
    GtkTargetList *target_list;

    calendar_view->priv = E_CALENDAR_VIEW_GET_PRIVATE (calendar_view);

    /* Set this early to avoid a divide-by-zero during init. */
    calendar_view->priv->time_divisions = 30;

    target_list = gtk_target_list_new (NULL, 0);
    e_target_list_add_calendar_targets (target_list, 0);
    calendar_view->priv->copy_target_list = target_list;

    target_list = gtk_target_list_new (NULL, 0);
    e_target_list_add_calendar_targets (target_list, 0);
    calendar_view->priv->paste_target_list = target_list;
}

static void
calendar_view_selectable_init (ESelectableInterface *interface)
{
    interface->update_actions = calendar_view_update_actions;
    interface->cut_clipboard = calendar_view_cut_clipboard;
    interface->copy_clipboard = calendar_view_copy_clipboard;
    interface->paste_clipboard = calendar_view_paste_clipboard;
    interface->delete_selection = calendar_view_delete_selection;
}

void
e_calendar_view_popup_event (ECalendarView *calendar_view,
                             GdkEvent *button_event)
{
    g_return_if_fail (E_IS_CALENDAR_VIEW (calendar_view));
    g_return_if_fail (button_event != NULL);

    g_signal_emit (calendar_view, signals[POPUP_EVENT], 0, button_event);
}

gboolean
e_calendar_view_add_event (ECalendarView *cal_view,
                           ECalClient *client,
                           time_t dtstart,
                           icaltimezone *default_zone,
                           icalcomponent *icalcomp,
                           gboolean in_top_canvas)
{
    ECalModel *model;
    ECalComponent *comp;
    ESourceRegistry *registry;
    struct icaltimetype itime, old_dtstart, old_dtend;
    time_t tt_start, tt_end, new_dtstart = 0;
    struct icaldurationtype ic_dur, ic_oneday;
    gchar *uid;
    gint start_offset, end_offset;
    gboolean all_day_event = FALSE;
    GnomeCalendarViewType view_type;
    gboolean ret = TRUE;
    GError *error = NULL;

    model = e_calendar_view_get_model (cal_view);
    registry = e_cal_model_get_registry (model);

    start_offset = 0;
    end_offset = 0;

    old_dtstart = icalcomponent_get_dtstart (icalcomp);
    tt_start = icaltime_as_timet (old_dtstart);
    old_dtend = icalcomponent_get_dtend (icalcomp);
    tt_end = icaltime_as_timet (old_dtend);
    ic_dur = icaldurationtype_from_int (tt_end - tt_start);

    if (icaldurationtype_as_int (ic_dur) > 60 *60 *24) {
        /* This is a long event */
        start_offset = old_dtstart.hour * 60 + old_dtstart.minute;
        end_offset = old_dtstart.hour * 60 + old_dtend.minute;
    }

    ic_oneday = icaldurationtype_null_duration ();
    ic_oneday.days = 1;

    view_type = gnome_calendar_get_view (cal_view->priv->calendar);

    switch (view_type) {
    case GNOME_CAL_DAY_VIEW:
    case GNOME_CAL_WORK_WEEK_VIEW:
        if (start_offset == 0 && end_offset == 0 && in_top_canvas)
            all_day_event = TRUE;

        if (all_day_event) {
            ic_dur = ic_oneday;
        } else if (icaldurationtype_as_int (ic_dur) >= 60 *60 *24
                && !in_top_canvas) {
            /* copy & paste from top canvas to main canvas */
            gint time_divisions;

            time_divisions = e_calendar_view_get_time_divisions (cal_view);
            ic_dur = icaldurationtype_from_int (time_divisions * 60);
        }

        if (in_top_canvas)
            new_dtstart = dtstart + start_offset * 60;
        else
            new_dtstart = dtstart;
        break;
    case GNOME_CAL_WEEK_VIEW:
    case GNOME_CAL_MONTH_VIEW:
    case GNOME_CAL_LIST_VIEW:
        if (old_dtstart.is_date && old_dtend.is_date
            && memcmp (&ic_dur, &ic_oneday, sizeof (ic_dur)) == 0) {
            all_day_event = TRUE;
            new_dtstart = dtstart;
        } else {
            icaltimetype new_time = icaltime_from_timet_with_zone (dtstart, FALSE, default_zone);

            new_time.hour = old_dtstart.hour;
            new_time.minute = old_dtstart.minute;
            new_time.second = old_dtstart.second;

            new_dtstart = icaltime_as_timet_with_zone (new_time, old_dtstart.zone ? old_dtstart.zone : default_zone);
        }
        break;
    default:
        g_return_val_if_reached (FALSE);
    }

    itime = icaltime_from_timet_with_zone (new_dtstart, FALSE, old_dtstart.zone ? old_dtstart.zone : default_zone);
    /* set the timezone properly */
    itime.zone = old_dtstart.zone ? old_dtstart.zone : default_zone;
    if (all_day_event)
        itime.is_date = TRUE;
    icalcomponent_set_dtstart (icalcomp, itime);

    itime.is_date = FALSE;
    itime = icaltime_add (itime, ic_dur);
    if (all_day_event)
        itime.is_date = TRUE;
    icalcomponent_set_dtend (icalcomp, itime);

    /* FIXME The new uid stuff can go away once we actually set it in the backend */
    uid = e_cal_component_gen_uid ();
    comp = e_cal_component_new ();
    e_cal_component_set_icalcomponent (
        comp, icalcomponent_new_clone (icalcomp));
    e_cal_component_set_uid (comp, uid);
    g_free (uid);

    e_cal_component_commit_sequence (comp);

    uid = NULL;
    if (e_cal_client_create_object_sync (client, e_cal_component_get_icalcomponent (comp), &uid, NULL, &error)) {
        gboolean strip_alarms = TRUE;

        if (uid) {
            e_cal_component_set_uid (comp, uid);
            g_free (uid);
        }

        if ((itip_organizer_is_user (registry, comp, client) ||
             itip_sentby_is_user (registry, comp, client)) &&
            send_component_dialog (
                (GtkWindow *) gtk_widget_get_toplevel (
                    GTK_WIDGET (cal_view)),
                client, comp, TRUE, &strip_alarms, NULL)) {
            itip_send_comp (
                registry, E_CAL_COMPONENT_METHOD_REQUEST,
                comp, client, NULL, NULL, NULL, strip_alarms,
                FALSE);
        }
    } else {
        g_message (G_STRLOC ": Could not create the object! %s", error ? error->message : "");
        if (error)
            g_error_free (error);
        ret = FALSE;
    }

    g_object_unref (comp);
    return ret;
}

GnomeCalendar *
e_calendar_view_get_calendar (ECalendarView *cal_view)
{
    g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);

    return cal_view->priv->calendar;
}

void
e_calendar_view_set_calendar (ECalendarView *cal_view,
                              GnomeCalendar *calendar)
{
    g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));

    cal_view->priv->calendar = calendar;
}

ECalModel *
e_calendar_view_get_model (ECalendarView *cal_view)
{
    g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);

    return cal_view->priv->model;
}

icaltimezone *
e_calendar_view_get_timezone (ECalendarView *cal_view)
{
    g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
    return e_cal_model_get_timezone (cal_view->priv->model);
}

void
e_calendar_view_set_timezone (ECalendarView *cal_view,
                              icaltimezone *zone)
{
    icaltimezone *old_zone;

    g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));

    old_zone = e_cal_model_get_timezone (cal_view->priv->model);
    if (old_zone == zone)
        return;

    e_cal_model_set_timezone (cal_view->priv->model, zone);
    g_signal_emit (
        cal_view, signals[TIMEZONE_CHANGED], 0,
        old_zone, zone);
}

const gchar *
e_calendar_view_get_default_category (ECalendarView *cal_view)
{
    g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);

    return cal_view->priv->default_category;
}

/**
 * e_calendar_view_set_default_category
 * @cal_view: A calendar view.
 * @category: Default category name or NULL for no category.
 *
 * Sets the default category that will be used when creating new calendar
 * components from the given calendar view.
 */
void
e_calendar_view_set_default_category (ECalendarView *cal_view,
                                      const gchar *category)
{
    g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));

    g_free (cal_view->priv->default_category);
    cal_view->priv->default_category = g_strdup (category);
}

GtkTargetList *
e_calendar_view_get_copy_target_list (ECalendarView *cal_view)
{
    g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);

    return cal_view->priv->copy_target_list;
}

GtkTargetList *
e_calendar_view_get_paste_target_list (ECalendarView *cal_view)
{
    g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);

    return cal_view->priv->paste_target_list;
}

gint
e_calendar_view_get_time_divisions (ECalendarView *cal_view)
{
    g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), 0);

    return cal_view->priv->time_divisions;
}

void
e_calendar_view_set_time_divisions (ECalendarView *cal_view,
                                    gint time_divisions)
{
    g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));

    if (cal_view->priv->time_divisions == time_divisions)
        return;

    cal_view->priv->time_divisions = time_divisions;

    g_object_notify (G_OBJECT (cal_view), "time-divisions");
}

GList *
e_calendar_view_get_selected_events (ECalendarView *cal_view)
{
    ECalendarViewClass *class;

    g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);

    class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
    g_return_val_if_fail (class->get_selected_events != NULL, NULL);

    return class->get_selected_events (cal_view);
}

gboolean
e_calendar_view_get_selected_time_range (ECalendarView *cal_view,
                                         time_t *start_time,
                                         time_t *end_time)
{
    ECalendarViewClass *class;

    g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE);

    class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
    g_return_val_if_fail (class->get_selected_time_range != NULL, FALSE);

    return class->get_selected_time_range (cal_view, start_time, end_time);
}

void
e_calendar_view_set_selected_time_range (ECalendarView *cal_view,
                                         time_t start_time,
                                         time_t end_time)
{
    ECalendarViewClass *class;

    g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));

    /* Not all views implement this, so return silently. */
    class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
    if (class->set_selected_time_range == NULL)
        return;

    class->set_selected_time_range (cal_view, start_time, end_time);
}

gboolean
e_calendar_view_get_visible_time_range (ECalendarView *cal_view,
                                        time_t *start_time,
                                        time_t *end_time)
{
    ECalendarViewClass *class;

    g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE);

    class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
    g_return_val_if_fail (class->get_visible_time_range != NULL, FALSE);

    return class->get_visible_time_range (cal_view, start_time, end_time);
}

void
e_calendar_view_update_query (ECalendarView *cal_view)
{
    ECalendarViewClass *class;

    g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));

    class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
    g_return_if_fail (class->update_query != NULL);

    class->update_query (cal_view);
}

void
e_calendar_view_delete_selected_occurrence (ECalendarView *cal_view)
{
    GList *selected;
    ECalModel *model;
    ECalComponent *comp;
    ECalendarViewEvent *event;
    ECalComponentVType vtype;
    ESourceRegistry *registry;
    gboolean delete = TRUE;
    GError *error = NULL;

    model = e_calendar_view_get_model (cal_view);
    registry = e_cal_model_get_registry (model);

    selected = e_calendar_view_get_selected_events (cal_view);
    if (!selected)
        return;
    event = (ECalendarViewEvent *) selected->data;
    if (!is_comp_data_valid (event))
        return;

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

    /*FIXME Retract should be moved to Groupwise features plugin */
    if (calendar_view_check_for_retract (comp, event->comp_data->client)) {
        gchar *retract_comment = NULL;
        gboolean retract = FALSE;

        delete = prompt_retract_dialog (comp, &retract_comment, GTK_WIDGET (cal_view), &retract);
        if (retract) {
            GSList *users = NULL;
            icalcomponent *icalcomp = NULL, *mod_comp = NULL;

            calendar_view_add_retract_data (
                comp, retract_comment, CALOBJ_MOD_THIS);
            icalcomp = e_cal_component_get_icalcomponent (comp);
            icalcomponent_set_method (icalcomp, ICAL_METHOD_CANCEL);
            if (!e_cal_client_send_objects_sync (event->comp_data->client, icalcomp, &users,
                        &mod_comp, NULL, &error))   {
                delete_error_dialog (error, E_CAL_COMPONENT_EVENT);
                g_clear_error (&error);
                error = NULL;
            } else {
                if (mod_comp)
                    icalcomponent_free (mod_comp);
                if (users) {
                    g_slist_foreach (users, (GFunc) g_free, NULL);
                    g_slist_free (users);
                }
            }
        }
    } else if (e_cal_model_get_confirm_delete (model))
        delete = delete_component_dialog (
            comp, FALSE, 1, vtype, GTK_WIDGET (cal_view));

    if (delete) {
        const gchar *uid;
        gchar *rid = NULL;
        ECalComponentDateTime dt;
        icaltimezone *zone = NULL;
        gboolean is_instance = FALSE;

        e_cal_component_get_uid (comp, &uid);
        e_cal_component_get_dtstart (comp, &dt);
        is_instance = e_cal_component_is_instance (comp);

        if (dt.tzid) {
            GError *error = NULL;

            e_cal_client_get_timezone_sync (event->comp_data->client, dt.tzid, &zone, NULL, &error);
            if (error) {
                zone = e_calendar_view_get_timezone (cal_view);
                g_clear_error (&error);
            }
        } else
            zone = e_calendar_view_get_timezone (cal_view);

        if (is_instance)
            rid = e_cal_component_get_recurid_as_string (comp);

        e_cal_component_free_datetime (&dt);

        if ((itip_organizer_is_user (registry, comp, event->comp_data->client) ||
             itip_sentby_is_user (registry, comp, event->comp_data->client))
                && cancel_component_dialog ((GtkWindow *) gtk_widget_get_toplevel (GTK_WIDGET (cal_view)),
                    event->comp_data->client,
                    comp, TRUE) && !e_cal_client_check_save_schedules (event->comp_data->client)) {
            if (!e_cal_component_is_instance (comp)) {
                ECalComponentRange range;

                /* set the recurrence ID of the object we send */
                range.type = E_CAL_COMPONENT_RANGE_SINGLE;
                e_cal_component_get_dtstart (comp, &range.datetime);
                range.datetime.value->is_date = 1;
                e_cal_component_set_recurid (comp, &range);

                e_cal_component_free_datetime (&range.datetime);
            }

            itip_send_comp (
                registry, E_CAL_COMPONENT_METHOD_CANCEL,
                comp, event->comp_data->client, NULL, NULL,
                NULL, TRUE, FALSE);
        }

        if (is_instance)
            e_cal_client_remove_object_sync (event->comp_data->client, uid, rid, CALOBJ_MOD_THIS, NULL, &error);
        else {
            struct icaltimetype instance_rid;

            instance_rid = icaltime_from_timet_with_zone (
                event->comp_data->instance_start,
                TRUE, zone ? zone : icaltimezone_get_utc_timezone ());
            e_cal_util_remove_instances (event->comp_data->icalcomp, instance_rid, CALOBJ_MOD_THIS);
            e_cal_client_modify_object_sync (event->comp_data->client, event->comp_data->icalcomp, CALOBJ_MOD_THIS, NULL, &error);
        }

        delete_error_dialog (error, E_CAL_COMPONENT_EVENT);
        g_clear_error (&error);
        g_free (rid);
    }

    /* free memory */
    g_list_free (selected);
    g_object_unref (comp);
}

void
e_calendar_view_open_event (ECalendarView *cal_view)
{
    GList *selected;

    selected = e_calendar_view_get_selected_events (cal_view);
    if (selected) {
        ECalendarViewEvent *event = (ECalendarViewEvent *) selected->data;
        if (event && is_comp_data_valid (event))
            e_calendar_view_edit_appointment (cal_view, event->comp_data->client, event->comp_data->icalcomp, EDIT_EVENT_AUTODETECT);

        g_list_free (selected);
    }
}

/**
 * e_calendar_view_new_appointment_for
 * @cal_view: A calendar view.
 * @dtstart: A Unix time_t that marks the beginning of the appointment.
 * @dtend: A Unix time_t that marks the end of the appointment.
 * @all_day: If TRUE, the dtstart and dtend are expanded to cover
 * the entire day, and the event is set to TRANSPARENT.
 * @meeting: Whether the appointment is a meeting or not.
 *
 * Opens an event editor dialog for a new appointment.
 */
void
e_calendar_view_new_appointment_for (ECalendarView *cal_view,
                                     time_t dtstart,
                                     time_t dtend,
                                     gboolean all_day,
                                     gboolean meeting)
{
    ECalendarViewPrivate *priv;
    struct icaltimetype itt;
    ECalComponentDateTime dt;
    ECalComponent *comp;
    icalcomponent *icalcomp;
    ECalComponentTransparency transparency;
    ECalClient *default_client = NULL;
    gpointer parent;
    guint32 flags = 0;

    g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));

    parent = gtk_widget_get_toplevel (GTK_WIDGET (cal_view));
    parent = gtk_widget_is_toplevel (parent) ? parent : NULL;

    priv = cal_view->priv;

    default_client = e_cal_model_ref_default_client (priv->model);
    g_return_if_fail (default_client != NULL);

    dt.value = &itt;
    if (all_day)
        dt.tzid = NULL;
    else
        dt.tzid = icaltimezone_get_tzid (e_cal_model_get_timezone (cal_view->priv->model));

    icalcomp = e_cal_model_create_component_with_defaults (priv->model, all_day);
    comp = e_cal_component_new ();
    e_cal_component_set_icalcomponent (comp, icalcomp);

    /* DTSTART, DTEND */
    itt = icaltime_from_timet_with_zone (dtstart, FALSE, e_cal_model_get_timezone (cal_view->priv->model));
    if (all_day) {
        itt.hour = itt.minute = itt.second = 0;
        itt.is_date = TRUE;
    }
    e_cal_component_set_dtstart (comp, &dt);

    itt = icaltime_from_timet_with_zone (dtend, FALSE, e_cal_model_get_timezone (cal_view->priv->model));
    if (all_day) {
        /* We round it up to the end of the day, unless it is
         * already set to midnight */
        if (itt.hour != 0 || itt.minute != 0 || itt.second != 0) {
            icaltime_adjust (&itt, 1, 0, 0, 0);
        }
        itt.hour = itt.minute = itt.second = 0;
        itt.is_date = TRUE;
    }
    e_cal_component_set_dtend (comp, &dt);

    /* TRANSPARENCY */
    transparency = all_day ? E_CAL_COMPONENT_TRANSP_TRANSPARENT
        : E_CAL_COMPONENT_TRANSP_OPAQUE;
    e_cal_component_set_transparency (comp, transparency);

    /* CATEGORY */
    e_cal_component_set_categories (comp, priv->default_category);

    /* edit the object */
    e_cal_component_commit_sequence (comp);

    flags |= COMP_EDITOR_NEW_ITEM;
    if (meeting) {
        flags |= COMP_EDITOR_MEETING;
        flags |= COMP_EDITOR_USER_ORG;
    }

    e_calendar_view_open_event_with_flags (
        cal_view, default_client, icalcomp, flags);

    g_object_unref (comp);

    g_object_unref (default_client);
}

/**
 * e_calendar_view_new_appointment_full
 * @cal_view: an #ECalendarView
 * @all_day: Whether create all day event or not.
 * @meeting: This is a meeting or an appointment.
 * @no_past_date: Don't create event in past date, use actual date instead
 * (if %TRUE).
 *
 * Opens an event editor dialog for a new appointment. The appointment's
 * start and end times are set to the currently selected time range in
 * the calendar view.
 *
 * When the selection is for all day and we don't need @all_day event,
 * then this do a rounding to the actual hour for actual day (today) and
 * to the 'day begins' from preferences in other selected day.
 */
void
e_calendar_view_new_appointment_full (ECalendarView *cal_view,
                                      gboolean all_day,
                                      gboolean meeting,
                                      gboolean no_past_date)
{
    ECalModel *model;
    time_t dtstart, dtend, now;
    gboolean do_rounding = FALSE;

    g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));

    model = e_calendar_view_get_model (cal_view);

    now = time (NULL);

    if (!e_calendar_view_get_selected_time_range (cal_view, &dtstart, &dtend)) {
        dtstart = now;
        dtend = dtstart + 3600;
    }

    if (no_past_date && dtstart < now) {
        dtend = time_day_begin (now) + (dtend - dtstart);
        dtstart = time_day_begin (now);
        do_rounding = TRUE;
    }

    /* We either need rounding or don't want to set all_day for this, we will rather use actual */
    /* time in this cases; dtstart should be a midnight in this case */
    if (do_rounding || (!all_day && (dtend - dtstart) == (60 * 60 * 24))) {
        struct tm local = *localtime (&now);
        gint time_div = e_calendar_view_get_time_divisions (cal_view);
        gint hours, mins;

        if (!time_div) /* Possible if your settings values aren't so nice */
            time_div = 30;

        if (time_day_begin (now) == time_day_begin (dtstart)) {
            /* same day as today */
            hours = local.tm_hour;
            mins = local.tm_min;

            /* round minutes to nearest time division, up or down */
            if ((mins % time_div) >= time_div / 2)
                mins += time_div;
            mins = (mins - (mins % time_div));
        } else {
            /* other day than today */
            hours = e_cal_model_get_work_day_start_hour (model);
            mins = e_cal_model_get_work_day_start_minute (model);
        }

        dtstart = dtstart + (60 * 60 * hours) + (mins * 60);
        dtend = dtstart + (time_div * 60);
    }

    e_calendar_view_new_appointment_for (cal_view, dtstart, dtend, all_day, meeting);
}

void
e_calendar_view_new_appointment (ECalendarView *cal_view)
{
    g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));

    e_calendar_view_new_appointment_full (cal_view, FALSE, FALSE, FALSE);
}

/* Ensures the calendar is selected */
static void
object_created_cb (CompEditor *ce,
                   ECalendarView *cal_view)
{
    e_calendar_view_emit_user_created (cal_view, comp_editor_get_client (ce));
}

CompEditor *
e_calendar_view_open_event_with_flags (ECalendarView *cal_view,
                                       ECalClient *client,
                                       icalcomponent *icalcomp,
                                       guint32 flags)
{
    CompEditor *ce;
    const gchar *uid;
    ECalComponent *comp;
    EShell *shell;

    /* FIXME ECalendarView should own an EShell pointer. */
    shell = e_shell_get_default ();

    uid = icalcomponent_get_uid (icalcomp);

    ce = comp_editor_find_instance (uid);
    if (!ce) {
        ce = event_editor_new (client, shell, flags);

        g_signal_connect (
            ce, "object_created",
            G_CALLBACK (object_created_cb), cal_view);

        comp = e_cal_component_new ();
        e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp));
        comp_editor_edit_comp (ce, comp);
        if (flags & COMP_EDITOR_MEETING)
            event_editor_show_meeting (EVENT_EDITOR (ce));

        g_object_unref (comp);
    }

    gtk_window_present (GTK_WINDOW (ce));

    return ce;
}

/**
 * e_calendar_view_edit_appointment
 * @cal_view: A calendar view.
 * @client: Calendar client.
 * @icalcomp: The object to be edited.
 * @mode: one of #EEditEventMode
 *
 * Opens an editor window to allow the user to edit the selected
 * object.
 */
void
e_calendar_view_edit_appointment (ECalendarView *cal_view,
                                  ECalClient *client,
                                  icalcomponent *icalcomp,
                                  EEditEventMode mode)
{
    ECalModel *model;
    ESourceRegistry *registry;
    guint32 flags = 0;

    g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
    g_return_if_fail (E_IS_CAL_CLIENT (client));
    g_return_if_fail (icalcomp != NULL);

    model = e_calendar_view_get_model (cal_view);
    registry = e_cal_model_get_registry (model);

    if ((mode == EDIT_EVENT_AUTODETECT && icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY) != NULL)
        || mode == EDIT_EVENT_FORCE_MEETING) {
        ECalComponent *comp = e_cal_component_new ();
        e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp));
        flags |= COMP_EDITOR_MEETING;
        if (itip_organizer_is_user (registry, comp, client) ||
            itip_sentby_is_user (registry, comp, client) ||
            !e_cal_component_has_attendees (comp))
            flags |= COMP_EDITOR_USER_ORG;
        g_object_unref (comp);
    }

    e_calendar_view_open_event_with_flags (cal_view, client, icalcomp, flags);
}

void
e_calendar_view_modify_and_send (ECalendarView *cal_view,
                                 ECalComponent *comp,
                                 ECalClient *client,
                                 CalObjModType mod,
                                 GtkWindow *toplevel,
                                 gboolean new)
{
    ECalModel *model;
    ESourceRegistry *registry;
    gboolean only_new_attendees = FALSE;
    GError *error = NULL;

    g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));

    model = e_calendar_view_get_model (cal_view);
    registry = e_cal_model_get_registry (model);

    e_cal_component_commit_sequence (comp);

    e_cal_client_modify_object_sync (
        client, e_cal_component_get_icalcomponent (comp),
        mod, NULL, &error);

    if (error == NULL) {
        gboolean strip_alarms = TRUE;

        if ((itip_organizer_is_user (registry, comp, client) ||
             itip_sentby_is_user (registry, comp, client)) &&
            send_component_dialog (toplevel, client, comp, new, &strip_alarms, &only_new_attendees)) {
            ECalComponent *send_comp = NULL;

            if (mod == CALOBJ_MOD_ALL && e_cal_component_is_instance (comp)) {
                /* Ensure we send the master object, not the instance only */
                icalcomponent *icalcomp = NULL;
                const gchar *uid = NULL;

                e_cal_component_get_uid (comp, &uid);
                if (e_cal_client_get_object_sync (client, uid, NULL, &icalcomp, NULL, NULL) && icalcomp) {
                    send_comp = e_cal_component_new ();
                    if (!e_cal_component_set_icalcomponent (send_comp, icalcomp)) {
                        icalcomponent_free (icalcomp);
                        g_object_unref (send_comp);
                        send_comp = NULL;
                    } else if (only_new_attendees) {
                        /* copy new-attendees information too if required for later use */
                        comp_editor_copy_new_attendees (send_comp, comp);
                    }
                }
            }

            itip_send_comp (
                registry, E_CAL_COMPONENT_METHOD_REQUEST,
                send_comp ? send_comp : comp, client, NULL,
                NULL, NULL, strip_alarms, only_new_attendees);

            if (send_comp)
                g_object_unref (send_comp);
        }
    } else {
        g_message (
            G_STRLOC ": Could not update the object! %s",
            error->message);

        g_error_free (error);
    }
}

static gboolean
tooltip_grab (GtkWidget *tooltip,
              GdkEvent *key_event,
              ECalendarView *view)
{
    GtkWidget *widget;
    GdkDevice *keyboard;
    guint32 event_time;

    widget = g_object_get_data (G_OBJECT (view), "tooltip-window");
    if (widget == NULL)
        return TRUE;

    event_time = gdk_event_get_time (key_event);

    while (!g_queue_is_empty (&view->priv->grabbed_keyboards)) {
        keyboard = g_queue_pop_head (&view->priv->grabbed_keyboards);
        gdk_device_ungrab (keyboard, event_time);
        g_object_unref (keyboard);
    }

    gtk_widget_destroy (widget);
    g_object_set_data (G_OBJECT (view), "tooltip-window", NULL);

    return FALSE;
}

static gchar *
get_label (struct icaltimetype *tt,
           icaltimezone *f_zone,
           icaltimezone *t_zone)
{
    struct tm tmp_tm;

    tmp_tm = icaltimetype_to_tm_with_zone (tt, f_zone, t_zone);

    return e_datetime_format_format_tm ("calendar", "table", DTFormatKindDateTime, &tmp_tm);
}

void
e_calendar_view_move_tip (GtkWidget *widget,
                          gint x,
                          gint y)
{
    GtkAllocation allocation;
    GtkRequisition requisition;
    GdkDisplay *display;
    GdkScreen *screen;
    GdkScreen *pointer_screen;
    GdkRectangle monitor;
    GdkDeviceManager *device_manager;
    GdkDevice *pointer;
    gint monitor_num, px, py;
    gint w, h;

    gtk_widget_get_preferred_size (widget, &requisition, NULL);
    w = requisition.width;
    h = requisition.height;

    screen = gtk_widget_get_screen (widget);
    display = gdk_screen_get_display (screen);
    device_manager = gdk_display_get_device_manager (display);
    pointer = gdk_device_manager_get_client_pointer (device_manager);

    gdk_device_get_position (pointer, &pointer_screen, &px, &py);
    if (pointer_screen != screen) {
        px = x;
        py = y;
    }
    monitor_num = gdk_screen_get_monitor_at_point (screen, px, py);
    gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);

    if ((x + w) > monitor.x + monitor.width)
        x -= (x + w) - (monitor.x + monitor.width);
    else if (x < monitor.x)
        x = monitor.x;

    gtk_widget_get_allocation (widget, &allocation);

    if ((y + h + allocation.height + 4) > monitor.y + monitor.height)
        y = y - h - 36;

    gtk_window_move (GTK_WINDOW (widget), x, y);
    gtk_widget_show (widget);
}

/*
 * It is expected to show the tooltips in this below format
 *
 *  <B>SUBJECT OF THE MEETING</B>
 *  Organiser: NameOfTheUser<email@ofuser.com>
 *  Location: PlaceOfTheMeeting
 *  Time : DateAndTime (xx Minutes)
 *      Status: Accepted: X   Declined: Y   ...
 */

gboolean
e_calendar_view_get_tooltips (const ECalendarViewEventData *data)
{
    GtkWidget *label, *box, *hbox, *ebox, *frame;
    const gchar *str;
    gchar *tmp, *tmp1, *tmp2;
    ECalComponentOrganizer organiser;
    ECalComponentDateTime dtstart, dtend;
    icalcomponent *clone_comp;
    time_t t_start, t_end;
    ECalendarViewEvent *pevent;
    GtkStyle *style = gtk_widget_get_default_style ();
    GtkWidget *widget;
    GdkWindow *window;
    GdkDisplay *display;
    GdkDeviceManager *device_manager;
    GQueue *grabbed_keyboards;
    ECalComponent *newcomp = e_cal_component_new ();
    icaltimezone *zone, *default_zone;
    ECalModel *model;
    ECalClient *client = NULL;
    GList *list, *link;
    gboolean free_text = FALSE;

    /* This function is a timeout callback. */

    g_return_val_if_fail (data != NULL, FALSE);
    g_return_val_if_fail (E_IS_CALENDAR_VIEW (data->cal_view), FALSE);

    model = e_calendar_view_get_model (data->cal_view);

    /* Delete any stray tooltip if left */
    widget = g_object_get_data (
        G_OBJECT (data->cal_view), "tooltip-window");
    if (GTK_IS_WIDGET (widget))
        gtk_widget_destroy (widget);

    default_zone = e_calendar_view_get_timezone  (data->cal_view);
    pevent = data->get_view_event (data->cal_view, data->day, data->event_num);

    if (!is_comp_data_valid (pevent))
        return FALSE;

    client = pevent->comp_data->client;

    clone_comp = icalcomponent_new_clone (pevent->comp_data->icalcomp);
    if (!e_cal_component_set_icalcomponent (newcomp, clone_comp))
        g_warning ("couldn't update calendar component with modified data from backend\n");

    box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);

    str = e_calendar_view_get_icalcomponent_summary (pevent->comp_data->client, pevent->comp_data->icalcomp, &free_text);

    if (!(str && *str)) {
        g_object_unref (newcomp);
        gtk_widget_destroy (box);

        return FALSE;
    }

    tmp = g_markup_printf_escaped ("<b>%s</b>", str);
    label = gtk_label_new (NULL);
    gtk_label_set_line_wrap ((GtkLabel *) label, TRUE);
    gtk_label_set_markup ((GtkLabel *) label, tmp);

    if (free_text) {
        g_free ((gchar *) str);
        str = NULL;
    }

    hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0);
    ebox = gtk_event_box_new ();
    gtk_container_add ((GtkContainer *) ebox, hbox);
    gtk_widget_modify_bg (ebox, GTK_STATE_NORMAL, &(style->bg[GTK_STATE_SELECTED]));
    gtk_widget_modify_fg (label, GTK_STATE_NORMAL, &(style->text[GTK_STATE_SELECTED]));

    gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
    g_free (tmp);

    e_cal_component_get_organizer (newcomp, &organiser);
    if (organiser.cn) {
        gchar *ptr;
        ptr = strchr (organiser.value, ':');

        if (ptr) {
            ptr++;
            /* To Translators: It will display "Organiser: NameOfTheUser <email@ofuser.com>" */
            tmp = g_strdup_printf (_("Organizer: %s <%s>"), organiser.cn, ptr);
        }
        else
            /* With SunOne accouts, there may be no ':' in organiser.value*/
            tmp = g_strdup_printf (_("Organizer: %s"), organiser.cn);

        label = gtk_label_new (tmp);
        hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
        gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0);
        ebox = gtk_event_box_new ();
        gtk_container_add ((GtkContainer *) ebox, hbox);
        gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);

        g_free (tmp);
    }

    e_cal_component_get_location (newcomp, &str);

    if (str) {
        /* To Translators: It will display "Location: PlaceOfTheMeeting" */
        tmp = g_markup_printf_escaped (_("Location: %s"), str);
        label = gtk_label_new (NULL);
        gtk_label_set_markup ((GtkLabel *) label, tmp);
        hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
        gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0);
        ebox = gtk_event_box_new ();
        gtk_container_add ((GtkContainer *) ebox, hbox);
        gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
        g_free (tmp);
    }
    e_cal_component_get_dtstart (newcomp, &dtstart);
    e_cal_component_get_dtend (newcomp, &dtend);

    if (dtstart.tzid) {
        zone = icalcomponent_get_timezone (e_cal_component_get_icalcomponent (newcomp), dtstart.tzid);
        if (!zone)
            e_cal_client_get_timezone_sync (client, dtstart.tzid, &zone, NULL, NULL);

        if (!zone)
            zone = default_zone;

    } else {
        zone = NULL;
    }
    t_start = icaltime_as_timet_with_zone (*dtstart.value, zone);
    t_end = icaltime_as_timet_with_zone (*dtend.value, zone);

    tmp1 = get_label (dtstart.value, zone, default_zone);
    tmp = calculate_time (t_start, t_end);

    /* To Translators: It will display "Time: ActualStartDateAndTime (DurationOfTheMeeting)"*/
    tmp2 = g_strdup_printf (_("Time: %s %s"), tmp1, tmp);
    if (zone && !cal_comp_util_compare_event_timezones (newcomp, client, default_zone)) {
        g_free (tmp);
        g_free (tmp1);

        tmp1 = get_label (dtstart.value, zone, zone);
        tmp = g_strconcat (tmp2, "\n\t[ ", tmp1, " ", icaltimezone_get_display_name (zone), " ]", NULL);
    } else {
        g_free (tmp);
        tmp = tmp2;
        tmp2 = NULL;
    }

    e_cal_component_free_datetime (&dtstart);
    e_cal_component_free_datetime (&dtend);

    hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_pack_start ((GtkBox *) hbox, gtk_label_new_with_mnemonic (tmp), FALSE, FALSE, 0);
    ebox = gtk_event_box_new ();
    gtk_container_add ((GtkContainer *) ebox, hbox);
    gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);

    g_free (tmp);
    g_free (tmp2);
    g_free (tmp1);

    tmp = e_cal_model_get_attendees_status_info (
        model, newcomp, pevent->comp_data->client);
    if (tmp) {
        hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
        gtk_box_pack_start ((GtkBox *) hbox, gtk_label_new (tmp), FALSE, FALSE, 0);
        ebox = gtk_event_box_new ();
        gtk_container_add ((GtkContainer *) ebox, hbox);
        gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);

        g_free (tmp);
    }

    pevent->tooltip = gtk_window_new (GTK_WINDOW_POPUP);
    frame = gtk_frame_new (NULL);
    gtk_frame_set_shadow_type ((GtkFrame *) frame, GTK_SHADOW_IN);

    gtk_window_set_type_hint (GTK_WINDOW (pevent->tooltip), GDK_WINDOW_TYPE_HINT_TOOLTIP);
    gtk_window_move ((GtkWindow *) pevent->tooltip, pevent->x +16, pevent->y + 16);
    gtk_container_add ((GtkContainer *) frame, box);
    gtk_container_add ((GtkContainer *) pevent->tooltip, frame);

    gtk_widget_show_all (pevent->tooltip);

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

    /* Grab all keyboard devices.  A key press from
     * any of them will dismiss the tooltip window. */

    window = gtk_widget_get_window (pevent->tooltip);
    display = gdk_window_get_display (window);
    device_manager = gdk_display_get_device_manager (display);

    grabbed_keyboards = &data->cal_view->priv->grabbed_keyboards;
    g_warn_if_fail (g_queue_is_empty (grabbed_keyboards));

    list = gdk_device_manager_list_devices (
        device_manager, GDK_DEVICE_TYPE_MASTER);

    for (link = list; link != NULL; link = g_list_next (link)) {
        GdkDevice *device = GDK_DEVICE (link->data);
        GdkGrabStatus grab_status;

        if (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD)
            continue;

        grab_status = gdk_device_grab (
            device,
            window,
            GDK_OWNERSHIP_NONE,
            FALSE,
            GDK_KEY_PRESS_MASK |
            GDK_KEY_RELEASE_MASK,
            NULL,
            GDK_CURRENT_TIME);

        if (grab_status == GDK_GRAB_SUCCESS)
            g_queue_push_tail (
                grabbed_keyboards,
                g_object_ref (device));
    }

    g_list_free (list);

    g_signal_connect (
        pevent->tooltip, "key-press-event",
        G_CALLBACK (tooltip_grab), data->cal_view);
    pevent->timeout = -1;

    g_object_set_data (G_OBJECT (data->cal_view), "tooltip-window", pevent->tooltip);
    g_object_unref (newcomp);

    return FALSE;
}

static gboolean
icalcomp_contains_category (icalcomponent *icalcomp,
                            const gchar *category)
{
    icalproperty *property;

    g_return_val_if_fail (icalcomp != NULL && category != NULL, FALSE);

    for (property = icalcomponent_get_first_property (icalcomp, ICAL_CATEGORIES_PROPERTY);
         property != NULL;
         property = icalcomponent_get_next_property (icalcomp, ICAL_CATEGORIES_PROPERTY)) {
        gchar *value = icalproperty_get_value_as_string_r (property);

        if (value && strcmp (category, value) == 0) {
            g_free (value);
            return TRUE;
        }
        g_free (value);
    }

    return FALSE;
}

/* e_calendar_view_get_icalcomponent_summary returns summary of calcomp,
 * and for type of birthday or anniversary it append number of years since
 * beginning. In this case, the free_text is set to TRUE and caller need
 * to g_free returned string, otherwise free_text is set to FALSE and
 * returned value is owned by calcomp.
 */

const gchar *
e_calendar_view_get_icalcomponent_summary (ECalClient *client,
                                           icalcomponent *icalcomp,
                                           gboolean *free_text)
{
    const gchar *summary;

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

    *free_text = FALSE;
    summary = icalcomponent_get_summary (icalcomp);

    if (icalcomp_contains_category (icalcomp, _("Birthday")) ||
        icalcomp_contains_category (icalcomp, _("Anniversary"))) {
        icalproperty *xprop;

        for (xprop = icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY);
             xprop;
             xprop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY)) {
            const gchar *xname = icalproperty_get_x_name (xprop);

            if (xname && g_ascii_strcasecmp (xname, "X-EVOLUTION-SINCE-YEAR") == 0) {
                struct icaltimetype dtnow;
                gint since_year;
                gchar *str;

                str = icalproperty_get_value_as_string_r (xprop);
                since_year = str ? atoi (str) : 0;
                g_free (str);

                dtnow = icalcomponent_get_dtstart (icalcomp);

                if (since_year > 0 && dtnow.year - since_year > 0) {
                    summary = g_strdup_printf ("%s (%d)", summary ? summary : "", dtnow.year - since_year);
                    *free_text = summary != NULL;
                }

                break;
            }
        }
    }

    return summary;
}

void
e_calendar_view_emit_user_created (ECalendarView *cal_view,
                                   ECalClient *where_was_created)
{
    g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));

    g_signal_emit (cal_view, signals[USER_CREATED], 0, where_was_created);
}

void
draw_curved_rectangle (cairo_t *cr,
                       gdouble x0,
                       gdouble y0,
                       gdouble rect_width,
                       gdouble rect_height,
                       gdouble radius)
{
    gdouble x1, y1;

    x1 = x0 + rect_width;
    y1 = y0 + rect_height;

    if (!rect_width || !rect_height)
        return;
    if (rect_width / 2 < radius) {
        if (rect_height / 2 < radius) {
        cairo_move_to  (cr, x0, (y0 + y1) / 2);
        cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0);
        cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
        cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
        cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
        } else {
        cairo_move_to  (cr, x0, y0 + radius);
        cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0);
        cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
        cairo_line_to (cr, x1 , y1 - radius);
        cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
        cairo_curve_to (cr, x0, y1, x0, y1, x0, y1- radius);
        }
    } else {
        if (rect_height / 2 < radius) {
        cairo_move_to  (cr, x0, (y0 + y1) / 2);
        cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
        cairo_line_to (cr, x1 - radius, y0);
        cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
        cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
        cairo_line_to (cr, x0 + radius, y1);
        cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
        } else {
        cairo_move_to  (cr, x0, y0 + radius);
        cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
        cairo_line_to (cr, x1 - radius, y0);
        cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
        cairo_line_to (cr, x1 , y1 - radius);
        cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
        cairo_line_to (cr, x0 + radius, y1);
        cairo_curve_to (cr, x0, y1, x0, y1, x0, y1- radius);
        }
    }
    cairo_close_path (cr);
}

/* returns either light or dark yellow, based on the base_background,
 * which is the default background color */
GdkColor
get_today_background (const GdkColor base_background)
{
    GdkColor res = base_background;

    if (res.red > 0x7FFF) {
        /* light yellow for a light theme */
        res.red   = 0xFFFF;
        res.green = 0xFFFF;
        res.blue  = 0xC0C0;
    } else {
        /* dark yellow for a dark theme */
        res.red   = 0x3F3F;
        res.green = 0x3F3F;
        res.blue  = 0x0000;
    }

    return res;
}

gboolean
is_comp_data_valid_func (ECalendarViewEvent *event,
                         const gchar *location)
{
    g_return_val_if_fail (location != NULL, FALSE);

    if (!event) {
        g_warning ("%s: event is NULL", location);
        return FALSE;
    }

    if (!event->comp_data) {
        g_warning ("%s: event's (%p) comp_data is NULL", location, event);
        return FALSE;
    }

    return TRUE;
}

gboolean
is_array_index_in_bounds_func (GArray *array,
                               gint index,
                               const gchar *location)
{
    g_return_val_if_fail (location != NULL, FALSE);

    if (!array) {
        g_warning ("%s: array is NULL", location);
        return FALSE;
    }

    if (index < 0 || index >= array->len) {
        g_warning ("%s: index %d is out of bounds [0,%d) at array %p", location, index, array->len, array);
        return FALSE;
    }

    return TRUE;
}

gboolean
e_calendar_view_is_editing (ECalendarView *cal_view)
{
    static gboolean in = FALSE;
    gboolean is_editing = FALSE;

    g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE);

    /* this should be called from the main thread only,
       and each descendant overrides the property,
       thus might cause no call recursion */
    if (in) {
        g_warn_if_reached ();
        return FALSE;
    }

    in = TRUE;

    g_object_get (G_OBJECT (cal_view), "is-editing", &is_editing, NULL);

    in = FALSE;

    return is_editing;
}