aboutsummaryrefslogblamecommitdiffstats
path: root/calendar/gui/alarm-notify/alarm-queue.c
blob: 83372f886f352ebca3382a0e647009d5f2931ce3 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                             
  
                                  
  
                                                        
  


                                                                   














                                                                            
                   
                 
                                                
                                 
                                    
                               

                          

                        
                          

                            
                         
                                 

                               
                                 
                         
                            
                                

                                 

                                         
                               
                                  
                                    






                                    
                               
                  
                                
                        
                         
                        
                 
                           
                           

 


                                                                   

                                                      
 





                                                                              


                                             


                                        






                                        


                                                      
                     
 


                                            








                                                                               
                                                                             





                                                
                                 
                            
 
                                                          
                                    


                                            


                                  






                                                                           
                                                                            
                                             
 


                                                             

                                                                    



                                                
                           
 

                                        
                                                                        

                                                                                          
                                                                                         
                                                                                              



                                                                               


                                                                                   
                                                    

                                                                      
 

                        
                                                     







                                                                                   
                           
 



                                                   
 


                                                              


                                                                                    
                                                                                                     










                                                                  
                                   











                                                                              




                                                   




                                                       
                            



                                                                


                                                              
 

                        





                                                      
                                  

         

                                                        

 


                                                                             
           

                                                                 
 
                        
                  
 
                  






                                                      

                       
 


                                                                         
                           
                                             
                                                                                   
                                                               


                                              








                                                                           
                          


                                                                                   
                                          
                                                          

                             
                                                          

                                   

 




                                                                   
                            
                        

                                        



                                 
                                                         
                                          
 
                                                 

                       






                                                                                
                                                                     

                                 


                                                             

                                                          

                         
                                         
                                                            

                      
                                           


                                                                  

                      
                                         
                                                           

                      
                                             
                                                                





                                        

 
                                                                                


                                                                       
                                                                    
 
                            


                              
                        
                                                       
                                               

                                                             


                       


                                          
                                      



                                                  
                                                     




                                   
                                                                                        
                                                                                      








                                                                                              
                                                     
                                   



                                                                              
                                                   


                                                                                   
                                                          






                                                                  

                                                                

 
                                                            
           
                                                        
 
                                              
 


                                                
                                                                               

                                            
                                   

                       
 






                                                                                                    
                                                                                                            

                                           
         















                                                                             

 



                                                 
                                             
                           

                          

                                                         
 




                                                                              
                                                                              
 
                                               
                                                     
                                        

 

                                                                        
                                                                   




                         
                                           

                       
                                   



                                                                                
                                                                       
 
                                                             

 
           
                                                           
 

                  





                                                                             

                                                                                



                                            
                                                                            

         



                                       
                                                         


                              
                                                 







                                                                              
                                  
 


                                                                             
                                                              





                                                                             
                                                                      

                         
                             
                                    
                       
                           
                              
                 


                  


                                                         

                                                                                       


                                           
                                                             
                                                
                                                   
                                    
                           


                                                                                            
 

                                                                                             
 
                             
                                                                



                                                     
                                 
                 
 
                                                         

                                                                
                                                          

                                              

                                 
 
                                                      

                                                                                                          
                                                               

                                                                                                        
                                               
 














                                                                     
 
                                            
 




                                                                                              
                         
 



                                                    
                                                                
                                                                                      
                 

                                                                          

                                      
         





                                                                                
                                                                      

                         
                 


                  

                                                 



 

                            





                                                                         
                             



                                                      

                       










                                                                               


                                       

 

                                                 
                                                  
 

                        
                                   

                                                           
                                                                             
 
                                             
 
                                     
                                                     

                             
                                   
                                                                                                                        
                                                                    
 
                              
                                                                                    


                                           

                                








                                                                                                              
 
                            
                                                                                    



                                    
                                                    

 
                


                          
                             
                            

                       

                              

                            
                        

                             
                         
               
 
           




































                                                
                                                                       

                            
                 
                                       
 
                                                            

                                               






                                                   

                                                   

                                                                                     
                 
         

 



                                                                           
                                       
        

                                                                                           
 

                                 
                                                                                 
                                             






                                                                                              
                                                              














                                                                                                
                      

                               
                                                                    
 


                                
 
                                                 
                                    














                                                                                              
                
                                                                                                      
 

                                               
 
                                                       
        


                                                                                                     
                                                                                      








                                                                                                   
         

         
                      
 


                                        
 
               

 
                 



                                           
        

                                                                       










                                                                                                           
                
                                                                            
















                                                                                              






                                                                                                           




                                                                                     





                    
                                                      
 
                            


           

























































































                                                                                                                            
 





































                                                                                                            
 



                                                                                                        
 










                                                                                                                                                                         
         



                                                                 


           
                                                                     
 

                                        
 















                                                                                      

 
                                  


                                                                                                                 







                                                      





                                                                                   






                                                                                
                                                



                                             
















                                                                                              
 
                                                                                  

                                                                                
                                                                                       

                                                                                                 

                                    








                                  
                          
 


                                                                    

                                                                              
                                                                      
 
                                                                   
                                

                    



                                              
                                                            
                                                                  
 
                        
                            
                                                     
                                               

                                
                               
                          
                                                               
                                   
                                         

                                 
                                                 

                       
        
                                                      
                                                  
                                                          
 



                                                     
 

                                                                








                                                                     






                                                                   



                                       



                                                                                     
                                                                                       
 

                                                             


                                             


                                                        


                                       
                                                                      
                                                       
                                                                    

                                       
                                      


                                         

                                                                      















                                                                                                  

                                                                             
 
                                             
                                                   
                                   
                                              
                                                                                   
                

                                                                                           
         




















































































                                                                                                                                        
 
      
 





                                                          

                                  
                           
                         


                                                 

                       
 
                                                                     

                                 

                                                          
 

                                                       
 
                                                  
 

                                                                           
                                                                
                 

         


                            

                                          
 
 
 




                                                                            
                         
 
                   
 










                                                                                         


                                                






                                                                 
                                                 

                            




                                                                                









                                                                                                
                                                                





                                                               
                                                                 


                                                     

                                                                                                  
                                                       
                                    
 
                                        

 



                                                                                 


                                      

                           
                  



                                                 

                       
 
                                                                     

                                 


                                                                    












                                                                                   
                                                             



                                                                      
 
                   
                                                     
                                                         
        

                                






                                                                  






                                                             

 


















                                                                                      
   
                    
  
                                                                         
                            

    
                                
 

                             
                                                       



                                                                              
                                                                            

                                                      
                                                                                 

         


                                                                                          












                                                                                                         
                                  

 
               

                                                                        


                                 














                                                                                              
                            
                            
         

                     

 
   
                    
  
                                                                            
                                                                               
                                                 

    
                       
 
                                              



                                                                       
                                                                                                


                                                  



                                                   
 
                                   

 
















                                                               
   
                          

                              
                                                                      
                                                                               
                                                                           

                                
                                                                          

                                                                              
                                               

    
                                     


                         
                                              
                                          
                                             


                                    





                                     
                         
                                  
 

                                                             
                                                                                      
 
                                                                 
                                           
                


                                                             
         

 

                                                                        
                                                       
 


                                                          
 

                                       
 
                                                              





                                                                    
                           



                                                                                 
                                                                    
 

                                          
 
                             
 
                                     

         
                           





                                                                
   
                             

                              
                                                            

    
                                        


                         
                                              
                                          
                                             



                                      
                                  


                      





                                                                                      
 





                                                                                     







                                                         










                                                                                                     


                                               


















































                                                                                                                                                                                                                                 
/* Evolution calendar - Alarm queueing engine
 *
 * Copyright (C) 2001 Ximian, Inc.
 *
 * Authors: Federico Mena-Quintero <federico@ximian.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

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

#include <string.h>
#include <glib.h>
#include <bonobo-activation/bonobo-activation.h>
#include <bonobo/bonobo-object.h>
#include <bonobo/bonobo-exception.h>
#include <bonobo/bonobo-main.h>
#include <gtk/gtksignal.h>
#include <gtk/gtkbox.h>
#include <gtk/gtkvbox.h>
#include <gtk/gtkhbox.h>
#include <gtk/gtkdialog.h>
#include <gtk/gtkeventbox.h>
#include <gtk/gtkimage.h>
#include <gtk/gtkframe.h>
#include <gtk/gtkimagemenuitem.h>
#include <gtk/gtklabel.h>
#include <gtk/gtkcheckbutton.h>
#include <gtk/gtkcheckmenuitem.h>
#include <gtk/gtkstock.h>
#include <gtk/gtktooltips.h>
#include <libgnome/gnome-i18n.h>
#include <libgnome/gnome-exec.h>
#include <libgnome/gnome-sound.h>
#include <libgnomeui/gnome-dialog-util.h>
#include <libgnomeui/gnome-uidefs.h>
#include <e-util/eggtrayicon.h>
#include <e-util/e-icon-factory.h>
#include <libecal/e-cal-time-util.h>
#include <libecal/e-cal-component.h>


#ifdef HAVE_LIBNOTIFY
#include <libnotify/notify.h>
#endif

#include "evolution-calendar.h"
#include "alarm.h"
#include "alarm-notify-dialog.h"
#include "alarm-queue.h"
#include "alarm-notify.h"
#include "config-data.h"
#include "util.h"
#include "e-util/e-popup.h"
#include "e-util/e-error.h"


/* The dialog with alarm nofications */
static AlarmNotificationsDialog *alarm_notifications_dialog = NULL;

/* Whether the queueing system has been initialized */
static gboolean alarm_queue_inited;

/* When the alarm queue system is inited, this gets set to the last time an
 * alarm notification was issued.  This lets us present any notifications that
 * should have happened while the alarm daemon was not running.
 */
static time_t saved_notification_time;

/* Clients we are monitoring for alarms */
static GHashTable *client_alarms_hash = NULL;

/* List of tray icons being displayed */
static GList *tray_icons_list = NULL;

/* Top Tray Image */
static GtkWidget *tray_image = NULL;
static GtkWidget *tray_event_box = NULL;
static int tray_blink_id = -1;
static int tray_blink_state = FALSE;
static AlarmNotify *an;

/* Structure that stores a client we are monitoring */
typedef struct {
    /* Monitored client */
    ECal *client;

    /* The live query to the calendar */
    ECalView *query;

    /* Hash table of component UID -> CompQueuedAlarms.  If an element is
     * present here, then it means its cqa->queued_alarms contains at least
     * one queued alarm.  When all the alarms for a component have been
     * dequeued, the CompQueuedAlarms structure is removed from the hash
     * table.  Thus a CQA exists <=> it has queued alarms.
     */
    GHashTable *uid_alarms_hash;
} ClientAlarms;

/* Pair of a ECalComponentAlarms and the mapping from queued alarm IDs to the
 * actual alarm instance structures.
 */
typedef struct {
    /* The parent client alarms structure */
    ClientAlarms *parent_client;

    /* The component's UID */
    ECalComponentId *id;

    /* The actual component and its alarm instances */
    ECalComponentAlarms *alarms;

    /* List of QueuedAlarm structures */
    GSList *queued_alarms;

    /* Flags */
    gboolean expecting_update;
} CompQueuedAlarms;

/* Pair of a queued alarm ID and the alarm trigger instance it refers to */
typedef struct {
    /* Alarm ID from alarm.h */
    gpointer alarm_id;

    /* Instance from our parent CompQueuedAlarms->alarms->alarms list */
    ECalComponentAlarmInstance *instance;

    /* original trigger of the instance from component */
    time_t orig_trigger;

    /* Whether this is a snoozed queued alarm or a normal one */
    guint snooze : 1;
} QueuedAlarm;

/* Alarm ID for the midnight refresh function */
static gpointer midnight_refresh_id = NULL;
static time_t midnight = 0;

static void
remove_client_alarms (ClientAlarms *ca);
static void display_notification (time_t trigger, CompQueuedAlarms *cqa,
                  gpointer alarm_id, gboolean use_description);
static void audio_notification (time_t trigger, CompQueuedAlarms *cqa, gpointer alarm_id);
static void mail_notification (time_t trigger, CompQueuedAlarms *cqa, gpointer alarm_id);
static void procedure_notification (time_t trigger, CompQueuedAlarms *cqa, gpointer alarm_id);
#ifdef HAVE_LIBNOTIFY
static void popup_notification (time_t trigger, CompQueuedAlarms *cqa,
                  gpointer alarm_id, gboolean use_description);
#endif
static void query_objects_changed_cb (ECal *client, GList *objects, gpointer data);
static void query_objects_removed_cb (ECal *client, GList *objects, gpointer data);

static void remove_client_alarms (ClientAlarms *ca);
static void update_cqa (CompQueuedAlarms *cqa, ECalComponent *comp);
static void update_qa (ECalComponentAlarms *alarms, QueuedAlarm *qa); 

/* Alarm queue engine */

static void load_alarms_for_today (ClientAlarms *ca);
static void midnight_refresh_cb (gpointer alarm_id, time_t trigger, gpointer data);

/* Queues an alarm trigger for midnight so that we can load the next day's worth
 * of alarms.
 */
static void
queue_midnight_refresh (void)
{
    icaltimezone *zone;

    if (midnight_refresh_id != NULL) {
        alarm_remove (midnight_refresh_id);
        midnight_refresh_id = NULL;
    }

    zone = config_data_get_timezone ();

    midnight = time_day_end_with_zone (time (NULL), zone);

    midnight_refresh_id = alarm_add (midnight, midnight_refresh_cb, NULL, NULL);
    if (!midnight_refresh_id) {
        g_message ("queue_midnight_refresh(): Could not set up the midnight refresh alarm!");
        /* FIXME: what to do? */
    }
}

/* Loads a client's alarms; called from g_hash_table_foreach() */
static void
add_client_alarms_cb (gpointer key, gpointer value, gpointer data)
{
    ClientAlarms *ca;

    ca = value;
    load_alarms_for_today (ca);
}

/* Loads the alarms for the new day every midnight */
static void
midnight_refresh_cb (gpointer alarm_id, time_t trigger, gpointer data)
{
    /* Re-load the alarms for all clients */

    g_hash_table_foreach (client_alarms_hash, add_client_alarms_cb, NULL);

    /* Re-schedule the midnight update */

    if (midnight_refresh_id != NULL) {
        alarm_remove (midnight_refresh_id);
        midnight_refresh_id = NULL;
    }

    queue_midnight_refresh ();
}

/* Looks up a client in the client alarms hash table */
static ClientAlarms *
lookup_client (ECal *client)
{
    return g_hash_table_lookup (client_alarms_hash, client);
}

/* Looks up a queued alarm based on its alarm ID */
static QueuedAlarm *
lookup_queued_alarm (CompQueuedAlarms *cqa, gpointer alarm_id)
{
    GSList *l;
    QueuedAlarm *qa;

    qa = NULL;

    for (l = cqa->queued_alarms; l; l = l->next) {
        qa = l->data;
        if (qa->alarm_id == alarm_id)
            return qa;
    }

    /* not found, might have been updated/removed */
    return NULL;
}

/* Removes an alarm from the list of alarms of a component.  If the alarm was
 * the last one listed for the component, it removes the component itself.
 */
static void
remove_queued_alarm (CompQueuedAlarms *cqa, gpointer alarm_id,
             gboolean free_object, gboolean remove_alarm)
{
    QueuedAlarm *qa;
    GSList *l;

    qa = NULL;

    for (l = cqa->queued_alarms; l; l = l->next) {
        qa = l->data;
        if (qa->alarm_id == alarm_id)
            break;
    }

    if (!l)
        return;

    cqa->queued_alarms = g_slist_remove_link (cqa->queued_alarms, l);
    g_slist_free_1 (l);

    if (remove_alarm) {
        cqa->expecting_update = TRUE;
        e_cal_discard_alarm (cqa->parent_client->client, cqa->alarms->comp,
                     qa->instance->auid, NULL);
        cqa->expecting_update = FALSE;
    }

    g_free (qa);

    /* If this was the last queued alarm for this component, remove the
     * component itself.
     */

    if (cqa->queued_alarms != NULL)
        return;

    if (free_object) {
        g_hash_table_remove (cqa->parent_client->uid_alarms_hash, cqa->id);
        e_cal_component_free_id (cqa->id);
        cqa->id = NULL;
        cqa->parent_client = NULL;
        e_cal_component_alarms_free (cqa->alarms);
        g_free (cqa);
    } else {
        e_cal_component_alarms_free (cqa->alarms);
        cqa->alarms = NULL;
    }
}

/* Callback used when an alarm triggers */
static void
alarm_trigger_cb (gpointer alarm_id, time_t trigger, gpointer data)
{
    CompQueuedAlarms *cqa;
    ECalComponent *comp;
    QueuedAlarm *qa;
    ECalComponentAlarm *alarm;
    ECalComponentAlarmAction action;

    cqa = data;
    comp = cqa->alarms->comp;

    config_data_set_last_notification_time (trigger);
    saved_notification_time = trigger;

    qa = lookup_queued_alarm (cqa, alarm_id);
    if (!qa)
        return;

    /* Decide what to do based on the alarm action.  We use the trigger that
     * is passed to us instead of the one from the instance structure
     * because this may be a snoozed alarm instead of an original
     * occurrence.
     */

    alarm = e_cal_component_get_alarm (comp, qa->instance->auid);
    g_assert (alarm != NULL);

    /* Show it independent of what the notification is?*/
    display_notification (trigger, cqa, alarm_id, TRUE);

    e_cal_component_alarm_get_action (alarm, &action);
    e_cal_component_alarm_free (alarm);

    switch (action) {
    case E_CAL_COMPONENT_ALARM_AUDIO:
        audio_notification (trigger, cqa, alarm_id);
        break;

    case E_CAL_COMPONENT_ALARM_DISPLAY:
#ifdef HAVE_LIBNOTIFY
        popup_notification (trigger, cqa, alarm_id, TRUE);
#endif
        break;

    case E_CAL_COMPONENT_ALARM_EMAIL:
        mail_notification (trigger, cqa, alarm_id);
        break;

    case E_CAL_COMPONENT_ALARM_PROCEDURE:
        procedure_notification (trigger, cqa, alarm_id);
        break;

    default:
        g_assert_not_reached ();
        break;
    }
}

/* Adds the alarms in a ECalComponentAlarms structure to the alarms queued for a
 * particular client.  Also puts the triggers in the alarm timer queue.
 */
static void
add_component_alarms (ClientAlarms *ca, ECalComponentAlarms *alarms)
{
    ECalComponentId *id;
    CompQueuedAlarms *cqa;
    GSList *l;

    /* No alarms? */
    if (alarms == NULL || alarms->alarms == NULL) {
        g_message ("No alarms to add");
        if (alarms)
            e_cal_component_alarms_free (alarms);
        return;
    }

    cqa = g_new (CompQueuedAlarms, 1);
    cqa->parent_client = ca;
    cqa->alarms = alarms;
    cqa->expecting_update = FALSE;

    cqa->queued_alarms = NULL;

    for (l = alarms->alarms; l; l = l->next) {
        ECalComponentAlarmInstance *instance;
        gpointer alarm_id;
        QueuedAlarm *qa;

        instance = l->data;

        g_message ("Adding alarm at %lu (%lu)", instance->trigger, time (NULL));
        alarm_id = alarm_add (instance->trigger, alarm_trigger_cb, cqa, NULL);
        if (!alarm_id) {
            g_message ("add_component_alarms(): Could not schedule a trigger for "
                   "%ld, discarding...", (long) instance->trigger);
            continue;
        }

        qa = g_new (QueuedAlarm, 1);
        qa->alarm_id = alarm_id;
        qa->instance = instance;
        qa->orig_trigger = instance->trigger;
        qa->snooze = FALSE;

        cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa);
    }

    id = e_cal_component_get_id (alarms->comp);

    /* If we failed to add all the alarms, then we should get rid of the cqa */
    if (cqa->queued_alarms == NULL) {
        e_cal_component_alarms_free (cqa->alarms);
        cqa->alarms = NULL;

        g_free (cqa);
        return;
    }

    cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms);
    cqa->id = id;
    g_hash_table_insert (ca->uid_alarms_hash, cqa->id, cqa);
}

/* Loads the alarms of a client for a given range of time */
static void
load_alarms (ClientAlarms *ca, time_t start, time_t end)
{
    char *str_query, *iso_start, *iso_end;

    iso_start = isodate_from_time_t (start);
    if (!iso_start)
        return;
                                                                               
    iso_end = isodate_from_time_t (end);
    if (!iso_end) {
        g_free (iso_start);
        return;
    }

    str_query = g_strdup_printf ("(has-alarms-in-range? (make-time \"%s\") (make-time \"%s\"))",
                     iso_start, iso_end);
    g_free (iso_start);
    g_free (iso_end);

    /* create the live query */
    if (ca->query) {
        g_signal_handlers_disconnect_matched (ca->query, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, ca);
        g_object_unref (ca->query);
        ca->query = NULL;
    }

    /* FIXME: handle errors */
    if (!e_cal_get_query (ca->client, str_query, &ca->query, NULL)) {
        g_warning (G_STRLOC ": Could not get query for client");
    } else {
        g_signal_connect (G_OBJECT (ca->query), "objects_added",
                  G_CALLBACK (query_objects_changed_cb), ca);
        g_signal_connect (G_OBJECT (ca->query), "objects_modified",
                  G_CALLBACK (query_objects_changed_cb), ca);
        g_signal_connect (G_OBJECT (ca->query), "objects_removed",
                  G_CALLBACK (query_objects_removed_cb), ca);

        e_cal_view_start (ca->query);
    }

    g_free (str_query);
}

/* Loads today's remaining alarms for a client */
static void
load_alarms_for_today (ClientAlarms *ca)
{
    time_t now, from, day_end, day_start;
    icaltimezone *zone;

    now = time (NULL);
    zone = config_data_get_timezone ();
    day_start = time_day_begin_with_zone (now, zone);

    /* Make sure we don't miss some events from the last notification.
     * We add 1 to the saved_notification_time to make the time ranges
     * half-open; we do not want to display the "last" displayed alarm
     * twice, once when it occurs and once when the alarm daemon restarts.
     */
    from = MAX (config_data_get_last_notification_time () + 1, day_start);

    g_message ("Loading alarms for today");
    day_end = time_day_end_with_zone (now, zone);
    load_alarms (ca, from, day_end);
}

/* Called when a calendar client finished loading; we load its alarms */
static void
cal_opened_cb (ECal *client, ECalendarStatus status, gpointer data)
{
    ClientAlarms *ca;

    ca = data;

    if (status != E_CALENDAR_STATUS_OK)
        return;

    load_alarms_for_today (ca);
}

/* Looks up a component's queued alarm structure in a client alarms structure */
static CompQueuedAlarms *
lookup_comp_queued_alarms (ClientAlarms *ca, const ECalComponentId *id)
{
    return g_hash_table_lookup (ca->uid_alarms_hash, id);
}

static void
remove_alarms (CompQueuedAlarms *cqa, gboolean free_object)
{
    GSList *l;

    for (l = cqa->queued_alarms; l;) {
        QueuedAlarm *qa;

        qa = l->data;

        /* Get the next element here because the list element will go
         * away in remove_queued_alarm().  The qa will be freed there as
         * well.
         */
        l = l->next;

        alarm_remove (qa->alarm_id);
        remove_queued_alarm (cqa, qa->alarm_id, free_object, FALSE);
    }

}

/* Removes a component an its alarms */
static void
remove_comp (ClientAlarms *ca, const ECalComponentId *id)
{
    CompQueuedAlarms *cqa;

    cqa = lookup_comp_queued_alarms (ca, id);
    if (!cqa)
        return;

    /* If a component is present, then it means we must have alarms queued
     * for it.
     */
    g_assert (cqa->queued_alarms != NULL);

    remove_alarms (cqa, TRUE);

    /* The list should be empty now, and thus the queued component alarms
     * structure should have been freed and removed from the hash table.
     */
    g_assert (lookup_comp_queued_alarms (ca, id) == NULL);
}

/* Called when a calendar component changes; we must reload its corresponding
 * alarms.
 */
static void
query_objects_changed_cb (ECal *client, GList *objects, gpointer data)
{
    ClientAlarms *ca;
    time_t from, day_end;
    ECalComponentAlarms *alarms;
    gboolean found;
    icaltimezone *zone;
    CompQueuedAlarms *cqa;
    GList *l;

    ca = data;

    from = config_data_get_last_notification_time ();
    if (from == -1)
        from = time (NULL);
    else
        from += 1; /* we add 1 to make sure the alarm is not displayed twice */

    zone = config_data_get_timezone ();

    day_end = time_day_end_with_zone (time (NULL), zone);
    g_message ("Query response for alarms");
    for (l = objects; l != NULL; l = l->next) {
        ECalComponentId *id;
        GSList *sl;
        ECalComponent *comp = e_cal_component_new ();

        e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (l->data));

        id = e_cal_component_get_id (comp);
        found = e_cal_get_alarms_for_object (ca->client, id, from, day_end, &alarms);

        if (!found) {
            g_message ("No alarms found on object");
            remove_comp (ca, id);
            e_cal_component_free_id (id);
            g_object_unref (comp);
            comp = NULL;
            continue;
        }

        cqa = lookup_comp_queued_alarms (ca, id);
        if (!cqa) {
            g_message ("No currently queue alarms");
            add_component_alarms (ca, alarms);
            g_object_unref (comp);
            comp = NULL;
            continue;
        }

        g_message ("Already existing alarms");

        /* if the alarms or the alarms list is empty remove it after updating the cqa structure */
        if (alarms == NULL || alarms->alarms == NULL) {
        
            /* update the cqa and its queued alarms for changes in summary and alarm_uid  */
            update_cqa (cqa, comp);

            if (alarms)
                e_cal_component_alarms_free (alarms);
            continue;
        }

        /* if already in the list, just update it */
        remove_alarms (cqa, FALSE);
        cqa->alarms = alarms;
        cqa->queued_alarms = NULL;
        
        /* add the new alarms */
        for (sl = cqa->alarms->alarms; sl; sl = sl->next) {
            ECalComponentAlarmInstance *instance;
            gpointer alarm_id;
            QueuedAlarm *qa;

            instance = sl->data;

            alarm_id = alarm_add (instance->trigger, alarm_trigger_cb, cqa, NULL);
            if (!alarm_id) {
                g_message (G_STRLOC ": Could not schedule a trigger for "
                       "%ld, discarding...", (long) instance->trigger);
                continue;
            }

            qa = g_new (QueuedAlarm, 1);
            qa->alarm_id = alarm_id;
            qa->instance = instance;
            qa->snooze = FALSE;
            qa->orig_trigger = instance->trigger;   
            cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa);
        }
        
        cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms);
        g_object_unref (comp);
        comp = NULL;
    }
}

/* Called when a calendar component is removed; we must delete its corresponding
 * alarms.
 */
static void
query_objects_removed_cb (ECal *client, GList *objects, gpointer data)
{
    ClientAlarms *ca;
    GList *l;

    ca = data;

    for (l = objects; l != NULL; l = l->next)
        remove_comp (ca, l->data);
}



/* Notification functions */

/* Creates a snooze alarm based on an existing one.  The snooze offset is
 * compued with respect to the current time.
 */
static void
create_snooze (CompQueuedAlarms *cqa, gpointer alarm_id, int snooze_mins)
{
    QueuedAlarm *orig_qa;
    time_t t;
    gpointer new_id;

    orig_qa = lookup_queued_alarm (cqa, alarm_id);
    if (!orig_qa)
        return;

    t = time (NULL);
    t += snooze_mins * 60;

    new_id = alarm_add (t, alarm_trigger_cb, cqa, NULL);
    if (!new_id) {
        g_message ("create_snooze(): Could not schedule a trigger for "
               "%ld, discarding...", (long) t);
        return;
    }

    orig_qa->instance->trigger = t;
    orig_qa->alarm_id = new_id;
    orig_qa->snooze = TRUE;
}

/* Launches a component editor for a component */
static void
edit_component (ECal *client, ECalComponent *comp)
{
    const char *uid;
    const char *uri;
    ECalSourceType source_type;
    CORBA_Environment ev;
    GNOME_Evolution_Calendar_CompEditorFactory factory;
    GNOME_Evolution_Calendar_CompEditorFactory_CompEditorMode corba_type;

    e_cal_component_get_uid (comp, &uid);

    uri = e_cal_get_uri (client);
    source_type = e_cal_get_source_type (client);

    /* Get the factory */
    CORBA_exception_init (&ev);
    factory = bonobo_activation_activate_from_id ("OAFIID:GNOME_Evolution_Calendar_CompEditorFactory:" BASE_VERSION,
                              0, NULL, &ev);

    if (BONOBO_EX (&ev)) {
        e_error_run (NULL, "editor-error", bonobo_exception_get_text (&ev));
        CORBA_exception_free (&ev);
        return;
    }

    /* Edit the component */
    switch (source_type) {
    case E_CAL_SOURCE_TYPE_TODO:
        corba_type = GNOME_Evolution_Calendar_CompEditorFactory_EDITOR_MODE_TODO;
        break;
    default:
        corba_type = GNOME_Evolution_Calendar_CompEditorFactory_EDITOR_MODE_EVENT;      
    }
    
    GNOME_Evolution_Calendar_CompEditorFactory_editExisting (factory, uri, (char *) uid, corba_type, &ev);

    if (BONOBO_EX (&ev))
        e_error_run (NULL, "editor-error", bonobo_exception_get_text (&ev));

    CORBA_exception_free (&ev);

    /* Get rid of the factory */
    bonobo_object_release_unref (factory, NULL);
}

typedef struct {
    char *summary;
    char *description;
    char *location;
    gboolean blink_state;
    gboolean snooze_set;
    gint blink_id;
    time_t trigger;
    CompQueuedAlarms *cqa;
    gpointer alarm_id;
    ECalComponent *comp;
    ECal *client;
    ECalView *query;
    GtkWidget *tray_icon;
    GtkWidget *image;
    GtkTreeIter iter;
} TrayIconData;

static void
free_tray_icon_data (TrayIconData *tray_data)
{
    g_return_if_fail (tray_data != NULL);

    if (tray_data->summary){
        g_free (tray_data->summary);
        tray_data->summary = NULL;
    }

    if (tray_data->description){
        g_free (tray_data->description);
        tray_data->description = NULL;
    }

    if (tray_data->location){
        g_free (tray_data->description);
        tray_data->location = NULL;
    }

    g_object_unref (tray_data->client);
    tray_data->client = NULL;

    g_object_unref (tray_data->query);
    tray_data->query = NULL;

    g_object_unref (tray_data->comp);
    tray_data->comp = NULL;
    
    tray_data->cqa = NULL;
    tray_data->alarm_id = NULL;
    tray_data->tray_icon = NULL;
    tray_data->image = NULL;

    g_free (tray_data);
}

static void
on_dialog_objs_removed_cb (ECal *client, GList *objects, gpointer data)
{
    const char *our_uid;
    GList *l;
    TrayIconData *tray_data = data;

    e_cal_component_get_uid (tray_data->comp, &our_uid);
    g_return_if_fail (our_uid && *our_uid);

    for (l = objects; l != NULL; l = l->next) {
        const char *uid = l->data;

        if (!uid)
            continue;

        if (!strcmp (uid, our_uid)) {
            tray_data->cqa = NULL;
            tray_data->alarm_id = NULL;
            tray_icons_list = g_list_remove (tray_icons_list, tray_data);
            tray_data = NULL;
        }
    }
}

/* Callback used from the alarm notify dialog */
static void
notify_dialog_cb (AlarmNotifyResult result, int snooze_mins, gpointer data)
{
    TrayIconData *tray_data = data;
    
    g_signal_handlers_disconnect_matched (tray_data->query, G_SIGNAL_MATCH_FUNC,
                          0, 0, NULL, on_dialog_objs_removed_cb, NULL);

    switch (result) {
    case ALARM_NOTIFY_SNOOZE:
        create_snooze (tray_data->cqa, tray_data->alarm_id, snooze_mins);
        tray_data->snooze_set = TRUE;
        if (alarm_notifications_dialog) {
            GtkTreeSelection *selection = 
                gtk_tree_view_get_selection (
                    GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
            GtkTreeIter iter;
            GtkTreeModel *model = NULL;
            
            /* We can` also use tray_data->iter */
            if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
                gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
                if (!gtk_tree_model_get_iter_first (model, &iter)) {
                    /* We removed the last one */
                    gtk_widget_destroy (alarm_notifications_dialog->dialog);
                    g_free (alarm_notifications_dialog);
                    alarm_notifications_dialog = NULL;
                } else {
                    /* Select the first */
                    gtk_tree_selection_select_iter (selection, &iter);
                }
            }
            
        }
        
        break;

    case ALARM_NOTIFY_EDIT:
        edit_component (tray_data->client, tray_data->comp);

        break;

    case ALARM_NOTIFY_CLOSE:

        if (alarm_notifications_dialog) {
            GList *list;
            GtkTreeIter iter;
            GtkTreeModel *model = 
                gtk_tree_view_get_model (
                    GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
            gboolean valid = gtk_tree_model_get_iter_first (model, &iter);
        
            /* Maybe we should warn about this first? */            
            while (valid) {
                gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
                valid = gtk_tree_model_iter_next (model, &iter);
            }
            
            gtk_widget_destroy (alarm_notifications_dialog->dialog);
            g_free (alarm_notifications_dialog);
            alarm_notifications_dialog = NULL;
        
            /* FIXME tray_icons_list is a global data structure - make this thread safe */

            list = tray_icons_list;
            while (list != NULL) {

                tray_data = list->data;
    
                if (!tray_data->snooze_set){
                    GList *temp = list->next;
                    tray_icons_list = g_list_remove_link (tray_icons_list, list);
        remove_queued_alarm (tray_data->cqa, tray_data->alarm_id, TRUE, TRUE);
                    free_tray_icon_data (tray_data);
                    tray_data = NULL;
                    g_list_free_1 (list);
                    if (tray_icons_list != list)    /* List head is modified */
                        list = tray_icons_list;
                    else 
                        list = temp;
                } else
                    list = list->next;
    }
    }

        break;

    default:
        g_assert_not_reached ();
    }

    return;
}

/* Callbacks.  */
static gboolean
open_alarm_dialog (TrayIconData *tray_data)
{
    QueuedAlarm *qa;
    
    qa = lookup_queued_alarm (tray_data->cqa, tray_data->alarm_id);
    if (qa) {
        GdkPixbuf *pixbuf;
        GtkTooltips *tooltips = gtk_tooltips_new ();
        
        pixbuf = e_icon_factory_get_icon ("stock_appointment-reminder", E_ICON_SIZE_LARGE_TOOLBAR);

        gtk_image_set_from_pixbuf (GTK_IMAGE (tray_image), pixbuf);
        g_object_unref (pixbuf);    

        if (tray_blink_id > -1)
            g_source_remove (tray_blink_id);
        tray_blink_id = -1;
        
        gtk_tooltips_set_tip (tooltips, tray_event_box, NULL, NULL);
    
        if (!alarm_notifications_dialog)
            alarm_notifications_dialog = notified_alarms_dialog_new ();
        
        if (alarm_notifications_dialog) {

            GtkTreeSelection *selection = NULL;
            GtkTreeModel *model = NULL;
            
            selection = gtk_tree_view_get_selection (
                GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
            model = gtk_tree_view_get_model (
                GTK_TREE_VIEW(alarm_notifications_dialog->treeview));
        
            tray_data->iter = add_alarm_to_notified_alarms_dialog (
                                   alarm_notifications_dialog,
                                   tray_data->trigger,
                                   qa->instance->occur_start,
                                   qa->instance->occur_end,
                                   e_cal_component_get_vtype (tray_data->comp),
                                   tray_data->summary,
                                   tray_data->description,
                                   tray_data->location,
                                   notify_dialog_cb, tray_data);
        
            gtk_tree_selection_select_iter (selection, &tray_data->iter);

        }
        
    }

    return TRUE;
}

static void
alarm_quit (EPopup *ep, EPopupItem *pitem, void *data)
{
    bonobo_main_quit ();
}

static void
menu_item_toggle_callback (GtkToggleButton *item, void *data)
{
    gboolean state = gtk_toggle_button_get_active (item);
    ESource *source = e_source_copy ((ESource *) data);
    GSList *groups, *p;

    if (e_source_get_uri ((ESource *)data)) {
        g_free (e_source_get_uri (source));
        e_source_set_absolute_uri (source, g_strdup (e_source_get_uri ((ESource *)data)));
    }

    if (state) {
        const char *uid = e_source_peek_uid (source);
        ESourceList *selected_cal = alarm_notify_get_selected_calendars (an);
        ESourceList *all_cal;
        ESourceGroup *sel_group = NULL;
        const char *grp_name=NULL;
        ESourceGroup *group;
        ESource *del_source;        

        e_cal_get_sources (&all_cal, E_CAL_SOURCE_TYPE_EVENT, NULL);

        alarm_notify_add_calendar (an, E_CAL_SOURCE_TYPE_EVENT, source, FALSE);
        
        /* Browse the list of calendars for the newly added calendar*/
        groups = e_source_list_peek_groups (all_cal);
        for (p = groups; p != NULL; p = p->next) {
            ESourceGroup *group = E_SOURCE_GROUP (p->data);
            ESource *sel_source = e_source_group_peek_source_by_uid (group, uid);

            if (sel_source) {
                sel_group = group;
                grp_name = e_source_group_peek_name (sel_group);
                /* You have got the group name*/
                break;
    }
}

        /* Add the source the the group name in the alarms calendar list*/
        group = e_source_list_peek_group_by_name (selected_cal, grp_name);  
        del_source = e_source_group_peek_source_by_uid (group, uid);
        
        if (!del_source) {
            char *xml, *old_xml;

            old_xml = e_source_group_to_xml (group);
            e_source_group_add_source (group, source, -1);
            xml = e_source_group_to_xml (group);
            config_data_replace_string_list ("/apps/evolution/calendar/notify/calendars", old_xml, xml);

            g_free (xml);
            g_free (old_xml);
        }

        g_object_unref (all_cal);

    } else {
        const char *uid = e_source_peek_uid (source);
        ESourceList *selected_cal = alarm_notify_get_selected_calendars (an);
        alarm_notify_remove_calendar (an, E_CAL_SOURCE_TYPE_EVENT, e_source_get_uri (source));
        
        /* Browse the calendar for alarms and remove the source */
        groups = e_source_list_peek_groups (selected_cal);
        for (p = groups; p != NULL; p = p->next) {
            ESourceGroup *group = E_SOURCE_GROUP (p->data);
            ESource *del_source;
        
            del_source = e_source_group_peek_source_by_uid (group, uid);
            if (del_source) {
                char *xml, *old_xml;

                old_xml = e_source_group_to_xml (group);

                e_source_group_remove_source_by_uid (group, uid);
                
                xml = e_source_group_to_xml (group);

                config_data_replace_string_list ("/apps/evolution/calendar/notify/calendars", old_xml, xml);

                g_free (xml);
                g_free (old_xml);
                break;
            }
        }
    }
        
}

static GtkWidget *
populate ()
{
    GtkWidget *frame = gtk_frame_new (NULL);
    GtkWidget *label1 = gtk_label_new (NULL);
    GtkWidget *box = gtk_vbox_new(FALSE, 0);
    ESourceList *selected_cal = alarm_notify_get_selected_calendars (an);   
    GSList *groups;
    GSList *p;
    ESourceList *source_list;
    
    gtk_label_set_markup (GTK_LABEL(label1), _("<b>Calendars</b>"));
    gtk_frame_set_label_widget (GTK_FRAME(frame), label1);
    
    if (!e_cal_get_sources (&source_list, E_CAL_SOURCE_TYPE_EVENT, NULL)) {
        g_message (G_STRLOC ": Could not get the list of sources to load");

        return NULL;
    }
        
    groups = e_source_list_peek_groups (source_list);

    for (p = groups; p != NULL; p = p->next) {
        ESourceGroup *group = E_SOURCE_GROUP (p->data);
        char *txt = g_strdup_printf ("<b>%s</b>", e_source_group_peek_name (group));
        GtkWidget *item = gtk_label_new (NULL);
        GSList *q;
        GtkWidget *hbox, *image;

        hbox = gtk_hbox_new (FALSE, 0);
        image = e_icon_factory_get_image  ("stock_appointment-reminder", E_ICON_SIZE_SMALL_TOOLBAR);

        gtk_box_pack_start ((GtkBox *)hbox, image, FALSE, FALSE, 2);
        gtk_box_pack_start ((GtkBox *)hbox, item, FALSE, FALSE, 2);
        
        gtk_label_set_markup (GTK_LABEL(item), txt);
        gtk_label_set_justify (GTK_LABEL(item), GTK_JUSTIFY_LEFT);
        g_free (txt);

        gtk_box_pack_start (GTK_BOX(box), hbox, TRUE, TRUE, 4);
        gtk_widget_show_all (hbox);

        for (q = e_source_group_peek_sources (group); q != NULL; q = q->next) {
            ESource *source = E_SOURCE (q->data);
            GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
            GtkWidget *item = gtk_check_button_new_with_label (e_source_peek_name (source));

            gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(item), e_source_list_peek_source_by_uid (selected_cal, e_source_peek_uid (source)) ? TRUE:FALSE);
            
            gtk_box_pack_start ((GtkBox *)hbox, item, FALSE, FALSE, 24);
            gtk_object_set_data_full (GTK_OBJECT (item), "ESourceMenu", source,
                          (GtkDestroyNotify) g_object_unref);

            g_signal_connect (item, "toggled", G_CALLBACK (menu_item_toggle_callback), source);

            gtk_box_pack_start (GTK_BOX(box), hbox, FALSE, FALSE, 2);
            gtk_widget_show_all (hbox);
        }
    }

    gtk_container_add (GTK_CONTAINER(frame), box);
    gtk_container_set_border_width (GTK_CONTAINER(frame), 6);
    return frame;
}

static void
alarm_pref_response (GtkWidget *widget, int response, gpointer dummy)
{
    gtk_widget_destroy (widget);    
}

static void
alarms_configure (EPopup *ep, EPopupItem *pitem, void *data)
{
    GtkWidget *box = populate ();
    GtkWidget *dialog;

    dialog = gtk_dialog_new_with_buttons (_("Preferences"), 
                        NULL,0,
                        GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT,
                        GTK_STOCK_HELP, GTK_RESPONSE_HELP,
                        NULL);

    gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), box);
    gtk_dialog_set_has_separator (GTK_DIALOG(dialog), FALSE);
    g_signal_connect (dialog, "response", G_CALLBACK (alarm_pref_response), NULL);
    gtk_widget_show_all (dialog);
}

static EPopupItem tray_items[] = {
    { E_POPUP_ITEM, "00.configure", N_("_Configure Alarms"), alarms_configure, NULL, GTK_STOCK_PREFERENCES },
    { E_POPUP_BAR , "10.bar" },
    { E_POPUP_ITEM, "10.quit", N_("_Quit"), alarm_quit, NULL, GTK_STOCK_QUIT },
};

static void
tray_popup_free(EPopup *ep, GSList *items, void *data)
{
    g_slist_free(items);
}

static gint
tray_icon_clicked_cb (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
{
    TrayIconData *tray_data = user_data;

    if (event->type == GDK_BUTTON_PRESS) {
        if (event->button == 1 && g_list_length (tray_icons_list) > 0) {
            GList *tmp;
            for (tmp = tray_icons_list; tmp; tmp = tmp->next) {
                open_alarm_dialog (tmp->data);
            }
            
            return TRUE;
        } else if (event->button == 3) {
            GtkMenu *menu;
            GSList *menus = NULL;
            EPopup *ep;
            int i;
            GdkPixbuf *pixbuf;
            GtkTooltips *tooltips = gtk_tooltips_new ();
            
            tray_blink_state = FALSE;
            pixbuf = e_icon_factory_get_icon  (tray_blink_state == TRUE ?
                               "stock_appointment-reminder-excl" :
                               "stock_appointment-reminder",
                               E_ICON_SIZE_LARGE_TOOLBAR);
    
            gtk_image_set_from_pixbuf (GTK_IMAGE (tray_image), pixbuf);
            g_object_unref (pixbuf);    

            if (tray_blink_id > -1)
                g_source_remove (tray_blink_id);
            tray_blink_id = -1;

            gtk_tooltips_set_tip (tooltips, tray_event_box, NULL, NULL);

            ep = e_popup_new("org.gnome.evolution.alarmNotify.popup");
            for (i=0;i<sizeof(tray_items)/sizeof(tray_items[0]);i++)
                menus = g_slist_prepend(menus, &tray_items[i]);
            e_popup_add_items(ep, menus, NULL, tray_popup_free, tray_data);
            menu = e_popup_create_menu_once(ep, NULL, 0);
            gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time);

            return TRUE;
        }
    }

    return FALSE;
}

static gboolean
tray_icon_blink_cb (gpointer data)
{
    GdkPixbuf *pixbuf;

    tray_blink_state = tray_blink_state == TRUE ? FALSE: TRUE;
    
    pixbuf = e_icon_factory_get_icon  (tray_blink_state == TRUE?
                       "stock_appointment-reminder-excl" :
                       "stock_appointment-reminder",
                       E_ICON_SIZE_LARGE_TOOLBAR);

    gtk_image_set_from_pixbuf (GTK_IMAGE (tray_image), pixbuf);
    g_object_unref (pixbuf);

    return TRUE;
}

/* Performs notification of a display alarm */
static void
display_notification (time_t trigger, CompQueuedAlarms *cqa,
              gpointer alarm_id, gboolean use_description)
{
    QueuedAlarm *qa;
    ECalComponent *comp;
    const char *summary, *description, *location;
    GtkWidget *tray_icon=NULL, *image=NULL;
    GtkTooltips *tooltips;
    TrayIconData *tray_data;
    ECalComponentText text;
    GSList *text_list;
    char *str, *start_str, *end_str, *alarm_str, *time_str;
    icaltimezone *current_zone;
    ECalComponentOrganizer organiser;

    comp = cqa->alarms->comp;
    qa = lookup_queued_alarm (cqa, alarm_id);
    if (!qa)
        return;
    
    /* get a sensible description for the event */
    e_cal_component_get_summary (comp, &text);
    e_cal_component_get_organizer (comp, &organiser); 

    if (text.value)
        summary = text.value;
    else
            summary = _("No summary available.");

    e_cal_component_get_description_list (comp, &text_list);

    if (text_list) {
        text = *((ECalComponentText *)text_list->data);
        if (text.value)
            description = text.value;
        else
            description = _("No description available.");
    } else {
        description = _("No description available.");
    }

    e_cal_component_free_text_list (text_list);

    e_cal_component_get_location (comp, &location);

    if (!location)
            location = _("No location information available.");

    /* create the tray icon */
    tooltips = gtk_tooltips_new ();

    current_zone = config_data_get_timezone ();
    alarm_str = timet_to_str_with_zone (trigger, current_zone);
    start_str = timet_to_str_with_zone (qa->instance->occur_start, current_zone);
    end_str = timet_to_str_with_zone (qa->instance->occur_end, current_zone);
    time_str = calculate_time (qa->instance->occur_start, qa->instance->occur_end);

    str = g_strdup_printf ("%s\n%s %s",
                   summary, start_str, time_str);

    /* create the private structure */
    tray_data = g_new0 (TrayIconData, 1);
    tray_data->summary = g_strdup (summary);
    tray_data->description = g_strdup (description);
    tray_data->location = g_strdup (location);
    tray_data->trigger = trigger;
    tray_data->cqa = cqa;
    tray_data->alarm_id = alarm_id;
    tray_data->comp = g_object_ref (e_cal_component_clone (comp));
    tray_data->client = cqa->parent_client->client;
    tray_data->query = g_object_ref (cqa->parent_client->query);
    tray_data->image = image;
    tray_data->blink_state = FALSE;
    tray_data->snooze_set = FALSE;
    g_object_ref (tray_data->client);
    tray_data->tray_icon = tray_icon;

    tray_icons_list = g_list_prepend (tray_icons_list, tray_data);

    if (g_list_length (tray_icons_list) > 1) {
        char *tip;

        tip =  g_strdup_printf (_("You have %d alarms"), g_list_length (tray_icons_list));
        gtk_tooltips_set_tip (GTK_TOOLTIPS (tooltips), tray_event_box, tip, NULL);
        g_free (tip);
    }
    else 
        gtk_tooltips_set_tip (GTK_TOOLTIPS (tooltips), tray_event_box, str, NULL);

    g_free (start_str);
    g_free (end_str);
    g_free (alarm_str);
    g_free (time_str);
    g_free (str);

    g_signal_connect (G_OBJECT (tray_data->query), "objects_removed",
              G_CALLBACK (on_dialog_objs_removed_cb), tray_data);

    // FIXME: We should remove this check
    if (!config_data_get_notify_with_tray ()) {
        tray_blink_id = -1;
        open_alarm_dialog (tray_data);
        gtk_window_stick (GTK_WINDOW (alarm_notifications_dialog->dialog));
    } else {
        if (tray_blink_id == -1)
            tray_blink_id = g_timeout_add (500, tray_icon_blink_cb, tray_data);
    }
    }

#ifdef HAVE_LIBNOTIFY
static void
popup_notification (time_t trigger, CompQueuedAlarms *cqa,
                gpointer alarm_id, gboolean use_description)
{
    QueuedAlarm *qa;
    ECalComponent *comp;
    const char *summary, *location;
    GtkTooltips *tooltips;
    ECalComponentText text;
    char *str, *start_str, *end_str, *alarm_str, *time_str;
    icaltimezone *current_zone;
    ECalComponentOrganizer organiser;
    NotifyIcon *icon;
    char *filename;
    char *body;
    
    comp = cqa->alarms->comp;
    qa = lookup_queued_alarm (cqa, alarm_id);
    if (!qa)
        return;
    if (!notify_is_initted ())
        notify_init("Evolution Alarm Notify");
    filename = e_icon_factory_get_icon_filename ("stock_appointment-reminder", E_ICON_SIZE_DIALOG);
    icon = notify_icon_new_from_uri (filename);
    g_free (filename);
    
    /* get a sensible description for the event */
    e_cal_component_get_summary (comp, &text);
    e_cal_component_get_organizer (comp, &organiser); 


    if (text.value)
        summary = text.value;
    else
            summary = _("No summary available.");

    e_cal_component_get_location (comp, &location);

    /* create the tray icon */
    tooltips = gtk_tooltips_new ();

    current_zone = config_data_get_timezone ();
    alarm_str = timet_to_str_with_zone (trigger, current_zone);
    start_str = timet_to_str_with_zone (qa->instance->occur_start, current_zone);
    end_str = timet_to_str_with_zone (qa->instance->occur_end, current_zone);
    time_str = calculate_time (qa->instance->occur_start, qa->instance->occur_end);

    str = g_strdup_printf ("%s %s",
                   start_str, time_str);

    if (organiser.cn) {
        if (location)
            body = g_strdup_printf ("<b>%s</b>\n%s %s\n%s %s", organiser.cn, _("Location:"), location, start_str, time_str);
        else
            body = g_strdup_printf ("<b>%s</b>\n%s %s", organiser.cn, start_str, time_str);
    }
    else {
        if (location)
            body = g_strdup_printf ("%s %s\n%s %s", _("Location:"), location, start_str, time_str);
        else
            body = g_strdup_printf ("%s %s", start_str, time_str);          
}

    if (!notify_send_notification (
                    NULL, "device", NOTIFY_URGENCY_NORMAL,
                    summary,
                    body,   /* body text */
                    icon,           /* icon */
                    TRUE, 0,        /* expiry, server default */
                    NULL,           /* hints */
                    NULL,           /* no user_data */
                    0))         /* no actions */
                    g_warning ("Could not send notification to daemon\n");  

    /* create the private structure */
    g_free (start_str);
    g_free (end_str);
    g_free (alarm_str);
    g_free (time_str);
    g_free (str);


}
#endif

/* Performs notification of an audio alarm */
static void
audio_notification (time_t trigger, CompQueuedAlarms *cqa,
            gpointer alarm_id)
{
    QueuedAlarm *qa;
    ECalComponent *comp;
    ECalComponentAlarm *alarm;
    icalattach *attach;
    int flag = 0;

    comp = cqa->alarms->comp;
    qa = lookup_queued_alarm (cqa, alarm_id);
    if (!qa)
        return;

    alarm = e_cal_component_get_alarm (comp, qa->instance->auid);
    g_assert (alarm != NULL);

    e_cal_component_alarm_get_attach (alarm, &attach);
    e_cal_component_alarm_free (alarm);

    if (attach && icalattach_get_is_url (attach)) {
        const char *url;

        url = icalattach_get_url (attach);

        if (url && *url && g_file_test (url, G_FILE_TEST_EXISTS)) {
            flag = 1;
            gnome_sound_play (url); /* this sucks */
        }
    }

    if (!flag)
        gdk_beep ();

    if (attach)
        icalattach_unref (attach);

}

/* Performs notification of a mail alarm */
static void
mail_notification (time_t trigger, CompQueuedAlarms *cqa, gpointer alarm_id)
{
    GtkWidget *dialog;
    GtkWidget *label;

    /* FIXME */

    dialog = gtk_dialog_new_with_buttons (_("Warning"),
                          NULL, 0,
                          GTK_STOCK_OK, GTK_RESPONSE_CANCEL,
                          NULL);
    label = gtk_label_new (_("Evolution does not support calendar reminders with\n"
                 "email notifications yet, but this reminder was\n"
                 "configured to send an email.  Evolution will display\n"
                 "a normal reminder dialog box instead."));
    gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), label, TRUE, TRUE, 4);

    gtk_dialog_run (GTK_DIALOG (dialog));
}

/* Performs notification of a procedure alarm */
static gboolean
procedure_notification_dialog (const char *cmd, const char *url) 
{
    GtkWidget *dialog, *label, *checkbox;
    char *str;
    int btn;
    
    if (config_data_is_blessed_program (url))
        return TRUE;
    
    dialog = gtk_dialog_new_with_buttons (_("Warning"),
                          NULL, 0,
                          GTK_STOCK_NO, GTK_RESPONSE_CANCEL,
                          GTK_STOCK_YES, GTK_RESPONSE_OK,
                          NULL);

    str = g_strdup_printf (_("An Evolution Calendar reminder is about to trigger. "
                 "This reminder is configured to run the following program:\n\n"
                 "        %s\n\n"
                 "Are you sure you want to run this program?"),
                   cmd);
    label = gtk_label_new (str);
    gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
    gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
    gtk_widget_show (label);
    gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
                label, TRUE, TRUE, 4);
    g_free (str);

    checkbox = gtk_check_button_new_with_label
        (_("Do not ask me about this program again."));
    gtk_widget_show (checkbox);
    gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), 
                checkbox, TRUE, TRUE, 4);

    /* Run the dialog */
    btn = gtk_dialog_run (GTK_DIALOG (dialog));
    if (btn == GTK_RESPONSE_OK && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbox)))
        config_data_save_blessed_program (url);
    gtk_widget_destroy (dialog);

    return (btn == GTK_RESPONSE_OK);
}

static void
procedure_notification (time_t trigger, CompQueuedAlarms *cqa, gpointer alarm_id)
{
    QueuedAlarm *qa;
    ECalComponent *comp;
    ECalComponentAlarm *alarm;
    ECalComponentText description;
    icalattach *attach;
    const char *url;
    char *cmd;
    int result;

    comp = cqa->alarms->comp;
    qa = lookup_queued_alarm (cqa, alarm_id);
    if (!qa)
        return;

    alarm = e_cal_component_get_alarm (comp, qa->instance->auid);
    g_assert (alarm != NULL);

    e_cal_component_alarm_get_attach (alarm, &attach);
    e_cal_component_alarm_get_description (alarm, &description);
    e_cal_component_alarm_free (alarm);

    /* If the alarm has no attachment, simply display a notification dialog. */
    if (!attach)
        goto fallback;

    if (!icalattach_get_is_url (attach)) {
        icalattach_unref (attach);
        goto fallback;
    }

    url = icalattach_get_url (attach);
    g_assert (url != NULL);

    /* Ask for confirmation before executing the stuff */
    if (description.value)
        cmd = g_strconcat (url, " ", description.value, NULL);
    else
        cmd = (char *) url;

    result = 0;
    if (procedure_notification_dialog (cmd, url))
        result = gnome_execute_shell (NULL, cmd);
    
    if (cmd != (char *) url)
        g_free (cmd);

    icalattach_unref (attach);

    /* Fall back to display notification if we got an error */
    if (result < 0)
        goto fallback;

    return;

 fallback:

    display_notification (trigger, cqa, alarm_id, FALSE);
}



static gboolean
check_midnight_refresh (gpointer user_data)
{
    time_t new_midnight;
    icaltimezone *zone;

    zone = config_data_get_timezone ();
    new_midnight = time_day_end_with_zone (time (NULL), zone);

    if (new_midnight > midnight) {
        /* Re-load the alarms for all clients */
        g_hash_table_foreach (client_alarms_hash, add_client_alarms_cb, NULL);

        queue_midnight_refresh ();
    }

    return TRUE;
}

/**
 * alarm_queue_init:
 *
 * Initializes the alarm queueing system.  This should be called near the
 * beginning of the program.
 **/
void
alarm_queue_init (gpointer data)
{
    GtkWidget *tray_icon;
    an = data;
    g_return_if_fail (alarm_queue_inited == FALSE);

    client_alarms_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
    queue_midnight_refresh ();

    saved_notification_time = config_data_get_last_notification_time ();
    if (saved_notification_time == -1) {
        saved_notification_time = time (NULL);
        config_data_set_last_notification_time (saved_notification_time);
    }

    /* install timeout handler (every 30 mins) for not missing the midnight refresh */
    g_timeout_add (1800000, (GSourceFunc) check_midnight_refresh, NULL);

    tray_icon = GTK_WIDGET (egg_tray_icon_new ("Evolution Alarm"));
    tray_image = e_icon_factory_get_image  ("stock_appointment-reminder", E_ICON_SIZE_LARGE_TOOLBAR);
    tray_event_box = gtk_event_box_new ();
    gtk_container_add (GTK_CONTAINER (tray_event_box), tray_image);
    gtk_container_add (GTK_CONTAINER (tray_icon), tray_event_box);
    g_signal_connect (G_OBJECT (tray_event_box), "button_press_event",
              G_CALLBACK (tray_icon_clicked_cb), NULL);
    gtk_widget_show_all (tray_icon);

#ifdef HAVE_LIBNOTIFY
    notify_init("Evolution Alarms");
#endif

    alarm_queue_inited = TRUE;
}

static gboolean
free_client_alarms_cb (gpointer key, gpointer value, gpointer user_data)
{
    ClientAlarms *ca = value;

    if (ca) {
        remove_client_alarms (ca);
        if (ca->client) {
            g_signal_handlers_disconnect_matched (ca->client, G_SIGNAL_MATCH_DATA,
                                  0, 0, NULL, NULL, ca);
            g_object_unref (ca->client);
        }

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

        g_hash_table_destroy (ca->uid_alarms_hash);

        g_free (ca);
        return TRUE;
    }

    return FALSE;
}

/**
 * alarm_queue_done:
 *
 * Shuts down the alarm queueing system.  This should be called near the end
 * of the program.  All the monitored calendar clients should already have been
 * unregistered with alarm_queue_remove_client().
 **/
void
alarm_queue_done (void)
{
    g_return_if_fail (alarm_queue_inited);

    /* All clients must be unregistered by now */
    g_return_if_fail (g_hash_table_size (client_alarms_hash) == 0);

    g_hash_table_foreach_remove (client_alarms_hash, (GHRFunc) free_client_alarms_cb, NULL);
    g_hash_table_destroy (client_alarms_hash);
    client_alarms_hash = NULL;

    if (midnight_refresh_id != NULL) {
        alarm_remove (midnight_refresh_id);
        midnight_refresh_id = NULL;
    }

    alarm_queue_inited = FALSE;
}

static gboolean 
compare_ids (gpointer a, gpointer b)
{
    ECalComponentId *id, *id1;

    id = a;
    id1 = b;
    
    if (g_str_equal (id->uid, id1->uid)) {
        if (id->rid && id1->rid)
            return g_str_equal (id->rid, id1->rid);
        else if (!(id->rid && id1->rid))
            return TRUE;
    } 
    return FALSE;
}

/**
 * alarm_queue_add_client:
 * @client: A calendar client.
 *
 * Adds a calendar client to the alarm queueing system.  Alarm trigger
 * notifications will be presented at the appropriate times.  The client should
 * be removed with alarm_queue_remove_client() when receiving notifications
 * from it is no longer desired.
 *
 * A client can be added any number of times to the alarm queueing system,
 * but any single alarm trigger will only be presented once for a particular
 * client.  The client must still be removed the same number of times from the
 * queueing system when it is no longer wanted.
 **/
void
alarm_queue_add_client (ECal *client)
{
    ClientAlarms *ca;

    g_return_if_fail (alarm_queue_inited);
    g_return_if_fail (client != NULL);
    g_return_if_fail (E_IS_CAL (client));

    ca = lookup_client (client);
    if (ca) {
        return;
    }

    ca = g_new (ClientAlarms, 1);

    ca->client = client;
    ca->query = NULL;
    g_object_ref (ca->client);

    g_hash_table_insert (client_alarms_hash, client, ca);

    ca->uid_alarms_hash = g_hash_table_new (g_str_hash, (GEqualFunc) compare_ids);

    if (e_cal_get_load_state (client) == E_CAL_LOAD_LOADED) {
        load_alarms_for_today (ca);
    } else {
        g_signal_connect (client, "cal_opened",
                  G_CALLBACK (cal_opened_cb),
                  ca);
    }
}

/* Called from g_hash_table_foreach(); adds a component UID to a list */
static void
add_id_cb (gpointer key, gpointer value, gpointer data)
{
    GSList **ids = (GSList **) data;
    ECalComponentId *id = g_new0 (ECalComponentId, 1);
    ECalComponentId *temp = (ECalComponentId *)key;

    id->uid = g_strdup (temp->uid);
    id->rid = g_strdup (temp->rid);

    *ids = g_slist_prepend (*ids, (ECalComponentId *) id);
}

/* Removes all the alarms queued for a particular calendar client */
static void
remove_client_alarms (ClientAlarms *ca)
{
    GSList *ids = NULL;
    GSList *l;

    /* First we build a list of UIDs so that we can remove them one by one */

    g_hash_table_foreach (ca->uid_alarms_hash, add_id_cb, &ids);

    for (l = ids; l; l = l->next) {
        const ECalComponentId *id;

        id = l->data;

        remove_comp (ca, id);
    }

    g_slist_free (ids);

    /* The hash table should be empty now */

    g_assert (g_hash_table_size (ca->uid_alarms_hash) == 0);
}

/**
 * alarm_queue_remove_client:
 * @client: A calendar client.
 *
 * Removes a calendar client from the alarm queueing system.
 **/
void
alarm_queue_remove_client (ECal *client)
{
    ClientAlarms *ca;

    g_return_if_fail (alarm_queue_inited);
    g_return_if_fail (client != NULL);
    g_return_if_fail (E_IS_CAL (client));

    ca = lookup_client (client);
    g_return_if_fail (ca != NULL);

    remove_client_alarms (ca);

    /* Clean up */

    if (ca->client) {
        g_signal_handlers_disconnect_matched (ca->client, G_SIGNAL_MATCH_DATA,
                              0, 0, NULL, NULL, ca);
        g_object_unref (ca->client);
        ca->client = NULL;
    }

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

    g_hash_table_destroy (ca->uid_alarms_hash);
    ca->uid_alarms_hash = NULL;

    g_free (ca);

    g_hash_table_remove (client_alarms_hash, client);
}

/* Update non-time related variables for various structures on modification of an existing component 
   to be called only from query_objects_changed_cb */
static void 
update_cqa (CompQueuedAlarms *cqa, ECalComponent *newcomp)
{
    ECalComponent *oldcomp;
    ECalComponentAlarms *alarms = NULL;
    GSList *qa_list;    /* List of current QueuedAlarms corresponding to cqa */
    time_t from, to;
    icaltimezone *zone;
    ECalComponentAlarmAction omit[] = {-1};

    oldcomp = cqa->alarms->comp;

    zone = config_data_get_timezone ();
    from = time_day_begin_with_zone (time (NULL), zone);
    to = time_day_end_with_zone (time (NULL), zone);


    alarms = e_cal_util_generate_alarms_for_comp (newcomp, from, to, omit, 
                    e_cal_resolve_tzid_cb, cqa->parent_client->client, zone);

    /* Update auids in Queued Alarms*/
    for (qa_list = cqa->queued_alarms; qa_list; qa_list = qa_list->next) {
        QueuedAlarm *qa = qa_list->data;
        char *check_auid = (char *) qa->instance->auid;

        if (e_cal_component_get_alarm (newcomp, check_auid))
            continue;
        else {
            if (e_cal_component_get_alarm (oldcomp, check_auid)) { /* Need to update QueuedAlarms */
                if (alarms == NULL) {
                    g_warning ("No alarms found on the modified component\n");
                    break;
                }
                update_qa (alarms, qa);
            }
            else
                g_warning ("Failed in auid lookup for old component also\n");
        }       
    }

    /* Update the actual component stored in CompQueuedAlarms structure */
    g_object_unref (cqa->alarms->comp);
    cqa->alarms->comp = newcomp;
    if (alarms != NULL )
        e_cal_component_alarms_free (alarms);
}

static void
update_qa (ECalComponentAlarms *alarms, QueuedAlarm *qa)
{
    ECalComponentAlarmInstance *al_inst;
    GSList *instance_list;
    
    for (instance_list = alarms->alarms; instance_list; instance_list = instance_list->next) {
        al_inst = instance_list->data;
        if (al_inst->trigger == qa->orig_trigger) {  /* FIXME if two or more alarm instances (audio, note)                                    for same component have same trigger */
            g_free ((char *) qa->instance->auid);
            qa->instance->auid = g_strdup (al_inst->auid);
            break;
        }
    }
}