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




                                                                
  



                                                                    
  
                                                                   
                                                                             
  
  
           

                                                        
  
                                                        
                                       


   
                   


                      



                             
                    
                       
 

                                       
                   
                           
                    
                         
 
            
 

                                          
                 
                        
                                    
                              

                                    
                              

  
                       

                                        
                           

  






                              
                               


                                                                            
                                         
                                                 

  
                                           



                                                          



                                                                            







                                    
                         




                                                                       

  




















                                   
           
                                
 

                          
 


                                                          
 
                                                  

 

                            
 

                          
 
                                                         
                                                       
                                   
 
                                                                  




             
                                                         
                                    

                                               

                                                        

         

           
                                
 
                             
                                     
                           
                                 
                                     
                   

                                                   
 
                                      




                                                             
                                          
                        

                                                                                     
                                                                  
                                 


                       
                                                       
                            
                            
 



                                                                                   
 
                                                                                       
                           

                                                                    
 
                                                                                                  
                                   

                                                                                 



                                                                                           
                      

                                                                

                                                                               


                                         

                                                    
 
                                      
                                                                 
 
                                                                                 
                                                         
                                
                                              
 
                                                                                    
                                  

                                                                         
                         
 
                                                                           
                                                                              
                                                                                         
                                                                                                                       
                                                              
                                         
                                                                                       
                                                                                         
                                                                                                                         
                                                              
                                         
                                                                                    
                                                             
                                                            
                                                               
 
                                                                          
                                                                                                 
                                                  

                                                                            
                                                              
                                         

                                                                           
                                                  



                                                                           
                                                                      
                                                              
                                         

                                                                              
                                                  

                                                                           
                                                              

                                         
                                                                                




                                                                             
                                                                  
                                                               
                                                                
                                                

                                                                          



                                         
                                                                                 


                 
                         


           
                          
 
                  

                       
                                     
               
 
                                

                       
                                                                 
 
                                      
                                            
                                  
                                                                   
                                                           


                                                                  
                                                                           
 
                                             
                                                                        
                                         

                              
                       
         
 
                                             
                            
 
                                

                                 


                                                        

         
                          
                      

 











                                                                 

















                                                                        


                                                               







                                             
           

                                
 


                                     
 











                                                                      
         







                                                             
 
 







                                                                
           



                                        


                                         
                              
                              




                                                            

                                                                      


















                                                            
         

                                                                       


           



                                      
 
                                         
 



                                                                     
 




















                                                                     

         
                                                                       


           

                               
                                         
 



                                                          
 

                                                           
 
                                                                  

                                                                            

                                        





                                                                
                                
 
                            
 
                                      
 


                                      
 
                                            
 

                                                                 

 
           








                                             
                                              






























                                                                           
 

                                                                    


           
                                       
 





                                                                 
                                              
                                                
                                                      

























































                                                             
 









                                                        

                               
 
           
                            
 

                                                    

                                             


   


                                                                    

                      

                                                                     
                                                                    
                            

                         
    
        
                                   
 
                  
                   
 


                                         



                 
        
                                                 
 

                        
                   

                                                       
 
                                   
                     
                                                       
                                           

         




                                              
 
 
        
                                                     


                                                                           
 

                                            
 
                                                     
 
                                             
 
 


                                               
 
                                              
 
                                                    
 
 

                                        
 
                                                                     
 
                                         

 


                                                      
 




                                                           

 

                                        
 
                                                        
 

                                              
 

                                            
 

                                                          
 

                                      
 


                                                         
 


                                         
 




                                                      
 

                                                            
 
                                                           
 
 

                                          
 
                                                        
 







                                                            
 

                                      
 


                                                           
 

                                           

 


                                                        
 





                                                                
 
 

                                    























                                                             


                        



                                      
 































                                                                         











                                                        



                                                         







                                                                 


























                                                                        
    
                                       





                                 
                                                    



                    
                                              




                                        
                                                        


                                             
                                                      

                               
/*
 * 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:
 *   Michael Zucchi <notzed@ximian.com>
 *   Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 * Copyright (C) 2009 Intel Corporation
 *
 */

#include <config.h>

#include <string.h>
#include <sys/types.h>

#include <libxml/parser.h>
#include <libxml/xmlmemory.h>

#include <gtk/gtk.h>
#include <glib/gi18n.h>

#include <libedataserver/e-xml-utils.h>

#include "e-util.h"
#include "e-util-private.h"
#include "e-alert.h"
#include "e-alert-sink.h"

#define d(x)

typedef struct _EAlertButton EAlertButton;

struct _e_alert {
    const gchar *id;
    GtkMessageType message_type;
    gint default_response;
    const gchar *primary_text;
    const gchar *secondary_text;
    EAlertButton *buttons;
};

struct _e_alert_table {
    const gchar *domain;
    const gchar *translation_domain;
    GHashTable *alerts;
};

struct _EAlertButton {
    EAlertButton *next;
    const gchar *stock_id;
    const gchar *label;
    gint response_id;
};

static GHashTable *alert_table;

/* ********************************************************************** */

static EAlertButton default_ok_button = {
    NULL, GTK_STOCK_OK, NULL, GTK_RESPONSE_OK
};

static struct _e_alert default_alerts[] = {
    { "error", GTK_MESSAGE_ERROR, GTK_RESPONSE_OK,
      "{0}", "{1}", &default_ok_button },
    { "warning", GTK_MESSAGE_WARNING, GTK_RESPONSE_OK,
      "{0}", "{1}", &default_ok_button }
};

/* ********************************************************************** */

struct _EAlertPrivate {
    gchar *tag;
    GPtrArray *args;
    gchar *primary_text;
    gchar *secondary_text;
    struct _e_alert *definition;
    GtkMessageType message_type;
    gint default_response;
    guint timeout_id;

    /* It may occur to one that we could use a GtkActionGroup here,
     * but we need to preserve the button order and GtkActionGroup
     * uses a hash table, which does not preserve order. */
    GQueue actions;
};

enum {
    PROP_0,
    PROP_ARGS,
    PROP_TAG,
    PROP_MESSAGE_TYPE,
    PROP_PRIMARY_TEXT,
    PROP_SECONDARY_TEXT
};

enum {
    RESPONSE,
    LAST_SIGNAL
};

static gulong signals[LAST_SIGNAL];

G_DEFINE_TYPE (
    EAlert,
    e_alert,
    G_TYPE_OBJECT)

static gint
map_response (const gchar *name)
{
    GEnumClass *class;
    GEnumValue *value;

    class = g_type_class_ref (GTK_TYPE_RESPONSE_TYPE);
    value = g_enum_get_value_by_name (class, name);
    g_type_class_unref (class);

    return (value != NULL) ? value->value : 0;
}

static GtkMessageType
map_type (const gchar *nick)
{
    GEnumClass *class;
    GEnumValue *value;

    class = g_type_class_ref (GTK_TYPE_MESSAGE_TYPE);
    value = g_enum_get_value_by_nick (class, nick);
    g_type_class_unref (class);

    return (value != NULL) ? value->value : GTK_MESSAGE_ERROR;
}

/*
  XML format:

 <error id="error-id" type="info|warning|question|error"?
      response="default_response"? >
  <primary>Primary error text.</primary>?
  <secondary>Secondary error text.</secondary>?
  <button stock="stock-button-id"? label="button label"?
      response="response_id"? /> *
 </error>

*/
static void
e_alert_load (const gchar *path)
{
    xmlDocPtr doc = NULL;
    xmlNodePtr root, error, scan;
    struct _e_alert *e;
    EAlertButton *lastbutton;
    struct _e_alert_table *table;
    gchar *tmp;

    d(printf("loading error file %s\n", path));

    doc = e_xml_parse_file (path);
    if (doc == NULL) {
        g_warning("Error file '%s' not found", path);
        return;
    }

    root = xmlDocGetRootElement (doc);
    if (root == NULL
        || strcmp((gchar *)root->name, "error-list") != 0
        || (tmp = (gchar *)xmlGetProp(root, (const guchar *)"domain")) == NULL) {
        g_warning("Error file '%s' invalid format", path);
        xmlFreeDoc (doc);
        return;
    }

    table = g_hash_table_lookup (alert_table, tmp);
    if (table == NULL) {
        gchar *tmp2;

        table = g_malloc0 (sizeof (*table));
        table->domain = g_strdup (tmp);
        table->alerts = g_hash_table_new (g_str_hash, g_str_equal);
        g_hash_table_insert (alert_table, (gpointer) table->domain, table);

        tmp2 = (gchar *)xmlGetProp(root, (const guchar *)"translation-domain");
        if (tmp2) {
            table->translation_domain = g_strdup (tmp2);
            xmlFree (tmp2);

            tmp2 = (gchar *)xmlGetProp(root, (const guchar *)"translation-localedir");
            if (tmp2) {
                bindtextdomain (table->translation_domain, tmp2);
                xmlFree (tmp2);
            }
        }
    } else
        g_warning("Error file '%s', domain '%s' already used, merging", path, tmp);
    xmlFree (tmp);

    for (error = root->children;error;error = error->next) {
        if (!strcmp((gchar *)error->name, "error")) {
            tmp = (gchar *)xmlGetProp(error, (const guchar *)"id");
            if (tmp == NULL)
                continue;

            e = g_malloc0 (sizeof (*e));
            e->id = g_strdup (tmp);

            xmlFree (tmp);
            lastbutton = (EAlertButton *)&e->buttons;

            tmp = (gchar *)xmlGetProp(error, (const guchar *)"type");
            e->message_type = map_type (tmp);
            if (tmp)
                xmlFree (tmp);

            tmp = (gchar *)xmlGetProp(error, (const guchar *)"default");
            if (tmp) {
                e->default_response = map_response (tmp);
                xmlFree (tmp);
            }

            for (scan = error->children;scan;scan=scan->next) {
                if (!strcmp((gchar *)scan->name, "primary")) {
                    if ((tmp = (gchar *) xmlNodeGetContent (scan))) {
                        e->primary_text = g_strdup (dgettext (table->translation_domain, tmp));
                        xmlFree (tmp);
                    }
                } else if (!strcmp((gchar *)scan->name, "secondary")) {
                    if ((tmp = (gchar *) xmlNodeGetContent (scan))) {
                        e->secondary_text = g_strdup (dgettext (table->translation_domain, tmp));
                        xmlFree (tmp);
                    }
                } else if (!strcmp((gchar *)scan->name, "button")) {
                    EAlertButton *button;
                    gchar *label = NULL;
                    gchar *stock_id = NULL;

                    button = g_new0 (EAlertButton, 1);
                    tmp = (gchar *)xmlGetProp(scan, (const guchar *)"stock");
                    if (tmp) {
                        stock_id = g_strdup (tmp);
                        button->stock_id = stock_id;
                        xmlFree (tmp);
                    }
                    tmp = (gchar *) xmlGetProp (
                        scan, (xmlChar *) "label");
                    if (tmp) {
                        label = g_strdup (
                            dgettext (table->
                            translation_domain,
                            tmp));
                        button->label = label;
                        xmlFree (tmp);
                    }
                    tmp = (gchar *) xmlGetProp (
                        scan, (xmlChar *) "response");
                    if (tmp) {
                        button->response_id =
                            map_response (tmp);
                        xmlFree (tmp);
                    }

                    if (stock_id == NULL && label == NULL) {
                        g_warning (
                            "Error file '%s': "
                            "missing button "
                            "details in error "
                            "'%s'", path, e->id);
                        g_free (stock_id);
                        g_free (label);
                        g_free (button);
                    } else {
                        lastbutton->next = button;
                        lastbutton = button;
                    }
                }
            }

            g_hash_table_insert (table->alerts, (gpointer) e->id, e);
        }
    }

    xmlFreeDoc (doc);
}

static void
e_alert_load_tables (void)
{
    GDir *dir;
    const gchar *d;
    gchar *base;
    struct _e_alert_table *table;
    gint i;

    if (alert_table != NULL)
        return;

    alert_table = g_hash_table_new (g_str_hash, g_str_equal);

    /* setup system alert types */
    table = g_malloc0 (sizeof (*table));
    table->domain = "builtin";
    table->alerts = g_hash_table_new (g_str_hash, g_str_equal);
    for (i = 0; i < G_N_ELEMENTS (default_alerts); i++)
        g_hash_table_insert (
            table->alerts, (gpointer)
            default_alerts[i].id, &default_alerts[i]);
    g_hash_table_insert (alert_table, (gpointer) table->domain, table);

    /* look for installed alert tables */
    base = g_build_filename (EVOLUTION_PRIVDATADIR, "errors", NULL);
    dir = g_dir_open (base, 0, NULL);
    if (dir == NULL) {
        g_free (base);
        return;
    }

    while ((d = g_dir_read_name (dir))) {
        gchar *path;

        if (d[0] == '.')
            continue;

        path = g_build_filename (base, d, NULL);
        e_alert_load (path);
        g_free (path);
    }

    g_dir_close (dir);
    g_free (base);
}

static void
alert_action_activate (EAlert *alert,
                       GtkAction *action)
{
    GObject *object;
    gpointer data;

    object = G_OBJECT (action);
    data = g_object_get_data (object, "e-alert-response-id");
    e_alert_response (alert, GPOINTER_TO_INT (data));
}

static gchar *
alert_format_string (const gchar *format,
                     GPtrArray *args)
{
    GString *string;
    const gchar *end, *newstart;
    gint id;

    string = g_string_sized_new (strlen (format));

    while (format
           && (newstart = strchr (format, '{'))
           && (end = strchr (newstart+1, '}'))) {
        g_string_append_len (string, format, newstart - format);
        id = atoi (newstart + 1);
        if (id < args->len) {
            g_string_append (string, args->pdata[id]);
        } else
            g_warning (
                "Error references argument %d "
                "not supplied by caller", id);
        format = end + 1;
    }

    g_string_append (string, format);

    return g_string_free (string, FALSE);
}

static void
alert_set_tag (EAlert *alert,
               const gchar *tag)
{
    struct _e_alert *definition;
    struct _e_alert_table *table;
    gchar *domain, *id;

    alert->priv->tag = g_strdup (tag);

    g_return_if_fail (alert_table);

    domain = g_alloca (strlen (tag) + 1);
    strcpy (domain, tag);
    id = strchr (domain, ':');
    if (id)
        *id++ = 0;
    else {
        g_warning ("Alert tag '%s' is missing a domain", tag);
        return;
    }

    table = g_hash_table_lookup (alert_table, domain);
    g_return_if_fail (table);

    definition = g_hash_table_lookup (table->alerts, id);
    g_warn_if_fail (definition);

    alert->priv->definition = definition;
}

static gboolean
alert_timeout_cb (EAlert *alert)
{
    e_alert_response (alert, alert->priv->default_response);

    return FALSE;
}

static void
alert_set_property (GObject *object,
                    guint property_id,
                    const GValue *value,
                    GParamSpec *pspec)
{
    EAlert *alert = (EAlert*) object;

    switch (property_id) {
        case PROP_TAG:
            alert_set_tag (
                E_ALERT (object),
                g_value_get_string (value));
            return;

        case PROP_ARGS:
            alert->priv->args = g_value_dup_boxed (value);
            return;

        case PROP_MESSAGE_TYPE:
            e_alert_set_message_type (
                E_ALERT (object),
                g_value_get_enum (value));
            return;

        case PROP_PRIMARY_TEXT:
            e_alert_set_primary_text (
                E_ALERT (object),
                g_value_get_string (value));
            return;

        case PROP_SECONDARY_TEXT:
            e_alert_set_secondary_text (
                E_ALERT (object),
                g_value_get_string (value));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
alert_get_property (GObject *object,
                    guint property_id,
                    GValue *value,
                    GParamSpec *pspec)
{
    EAlert *alert = (EAlert*) object;

    switch (property_id) {
        case PROP_TAG:
            g_value_set_string (value, alert->priv->tag);
            return;

        case PROP_ARGS:
            g_value_set_boxed (value, alert->priv->args);
            return;

        case PROP_MESSAGE_TYPE:
            g_value_set_enum (
                value, e_alert_get_message_type (
                E_ALERT (object)));
            return;

        case PROP_PRIMARY_TEXT:
            g_value_set_string (
                value, e_alert_get_primary_text (
                E_ALERT (object)));
            return;

        case PROP_SECONDARY_TEXT:
            g_value_set_string (
                value, e_alert_get_secondary_text (
                E_ALERT (object)));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
alert_dispose (GObject *object)
{
    EAlert *alert = E_ALERT (object);

    if (alert->priv->timeout_id > 0) {
        g_source_remove (alert->priv->timeout_id);
        alert->priv->timeout_id = 0;
    }

    while (!g_queue_is_empty (&alert->priv->actions)) {
        GtkAction *action;

        action = g_queue_pop_head (&alert->priv->actions);
        g_signal_handlers_disconnect_by_func (
            action, G_CALLBACK (alert_action_activate), object);
        g_object_unref (action);
    }

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

static void
alert_finalize (GObject *object)
{
    EAlertPrivate *priv;

    priv = E_ALERT (object)->priv;

    g_free (priv->tag);
    g_free (priv->primary_text);
    g_free (priv->secondary_text);

    g_ptr_array_free (priv->args, TRUE);

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

static void
alert_constructed (GObject *object)
{
    EAlert *alert;
    EAlertButton *button;
    struct _e_alert *definition;
    gint ii = 0;

    alert = E_ALERT (object);
    definition = alert->priv->definition;
    g_return_if_fail (definition != NULL);

    e_alert_set_message_type (alert, definition->message_type);
    e_alert_set_default_response (alert, definition->default_response);

    /* Build actions out of the button definitions. */
    button = definition->buttons;
    while (button != NULL) {
        GtkAction *action;
        gchar *action_name;

        action_name = g_strdup_printf ("alert-response-%d", ii++);

        if (button->stock_id != NULL) {
            action = gtk_action_new (
                action_name, NULL, NULL, button->stock_id);
            e_alert_add_action (
                alert, action, button->response_id);
            g_object_unref (action);

        } else if (button->label != NULL) {
            action = gtk_action_new (
                action_name, button->label, NULL, NULL);
            e_alert_add_action (
                alert, action, button->response_id);
            g_object_unref (action);
        }

        g_free (action_name);

        button = button->next;
    }

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

static void
e_alert_class_init (EAlertClass *class)
{
    GObjectClass *object_class = G_OBJECT_CLASS (class);

    g_type_class_add_private (class, sizeof (EAlertPrivate));

    object_class->set_property = alert_set_property;
    object_class->get_property = alert_get_property;
    object_class->dispose = alert_dispose;
    object_class->finalize = alert_finalize;
    object_class->constructed = alert_constructed;

    g_object_class_install_property (
        object_class,
        PROP_ARGS,
        g_param_spec_boxed (
            "args",
            "Arguments",
            "Arguments for formatting the alert",
            G_TYPE_PTR_ARRAY,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT_ONLY |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_TAG,
        g_param_spec_string (
            "tag",
            "alert tag",
            "A tag describing the alert",
            "",
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT_ONLY |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_MESSAGE_TYPE,
        g_param_spec_enum (
            "message-type",
            NULL,
            NULL,
            GTK_TYPE_MESSAGE_TYPE,
            GTK_MESSAGE_ERROR,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_PRIMARY_TEXT,
        g_param_spec_string (
            "primary-text",
            NULL,
            NULL,
            NULL,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_SECONDARY_TEXT,
        g_param_spec_string (
            "secondary-text",
            NULL,
            NULL,
            NULL,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    signals[RESPONSE] = g_signal_new (
        "response",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (EAlertClass, response),
        NULL, NULL,
        g_cclosure_marshal_VOID__INT,
        G_TYPE_NONE, 1,
        G_TYPE_INT);

    e_alert_load_tables ();
}

static void
e_alert_init (EAlert *alert)
{
    alert->priv = G_TYPE_INSTANCE_GET_PRIVATE (
        alert, E_TYPE_ALERT, EAlertPrivate);

    g_queue_init (&alert->priv->actions);
}

/**
 * e_alert_new:
 * @tag: alert identifier
 * @arg0: The first argument for the alert formatter.  The list must
 * be NULL terminated.
 *
 * Creates a new EAlert.  The @tag argument is used to determine
 * which alert to use, it is in the format domain:alert-id.  The NULL
 * terminated list of arguments, starting with @arg0 is used to fill
 * out the alert definition.
 *
 * Returns: a new #EAlert
 **/
EAlert *
e_alert_new (const gchar *tag, ...)
{
    EAlert *e;
    va_list va;

    va_start (va, tag);
    e = e_alert_new_valist (tag, va);
    va_end (va);

    return e;
}

EAlert *
e_alert_new_valist (const gchar *tag, va_list va)
{
    EAlert *alert;
    GPtrArray *args;
    gchar *tmp;

    args = g_ptr_array_new_with_free_func (g_free);

    tmp = va_arg (va, gchar *);
    while (tmp) {
        g_ptr_array_add (args, g_strdup (tmp));
        tmp = va_arg (va, gchar *);
    }

    alert = e_alert_new_array (tag, args);

    g_ptr_array_unref (args);

    return alert;
}

EAlert *
e_alert_new_array (const gchar *tag, GPtrArray *args)
{
    return g_object_new (E_TYPE_ALERT, "tag", tag, "args", args, NULL);
}

gint
e_alert_get_default_response (EAlert *alert)
{
    g_return_val_if_fail (E_IS_ALERT (alert), 0);

    return alert->priv->default_response;
}

void
e_alert_set_default_response (EAlert *alert,
                              gint response_id)
{
    g_return_if_fail (E_IS_ALERT (alert));

    alert->priv->default_response = response_id;
}

GtkMessageType
e_alert_get_message_type (EAlert *alert)
{
    g_return_val_if_fail (E_IS_ALERT (alert), GTK_MESSAGE_OTHER);

    return alert->priv->message_type;
}

void
e_alert_set_message_type (EAlert *alert,
                          GtkMessageType message_type)
{
    g_return_if_fail (E_IS_ALERT (alert));

    alert->priv->message_type = message_type;

    g_object_notify (G_OBJECT (alert), "message-type");
}

const gchar *
e_alert_get_primary_text (EAlert *alert)
{
    g_return_val_if_fail (E_IS_ALERT (alert), NULL);

    if (alert->priv->primary_text != NULL)
        goto exit;

    if (alert->priv->definition == NULL)
        goto exit;

    if (alert->priv->definition->primary_text == NULL)
        goto exit;

    if (alert->priv->args == NULL)
        goto exit;

    alert->priv->primary_text = alert_format_string (
        alert->priv->definition->primary_text,
        alert->priv->args);

exit:
    return alert->priv->primary_text;
}

void
e_alert_set_primary_text (EAlert *alert,
                            const gchar *primary_text)
{
    g_return_if_fail (E_IS_ALERT (alert));

    g_free (alert->priv->primary_text);
    alert->priv->primary_text = g_strdup (primary_text);

    g_object_notify (G_OBJECT (alert), "primary-text");
}

const gchar *
e_alert_get_secondary_text (EAlert *alert)
{
    g_return_val_if_fail (E_IS_ALERT (alert), NULL);

    if (alert->priv->secondary_text != NULL)
        goto exit;

    if (alert->priv->definition == NULL)
        goto exit;

    if (alert->priv->definition->secondary_text == NULL)
        goto exit;

    if (alert->priv->args == NULL)
        goto exit;

    alert->priv->secondary_text = alert_format_string (
        alert->priv->definition->secondary_text,
        alert->priv->args);

exit:
    return alert->priv->secondary_text;
}

void
e_alert_set_secondary_text (EAlert *alert,
                            const gchar *secondary_text)
{
    g_return_if_fail (E_IS_ALERT (alert));

    g_free (alert->priv->secondary_text);
    alert->priv->secondary_text = g_strdup (secondary_text);

    g_object_notify (G_OBJECT (alert), "secondary-text");
}

const gchar *
e_alert_get_stock_id (EAlert *alert)
{
    const gchar *stock_id;

    g_return_val_if_fail (E_IS_ALERT (alert), NULL);

    switch (e_alert_get_message_type (alert)) {
        case GTK_MESSAGE_INFO:
            stock_id = GTK_STOCK_DIALOG_INFO;
            break;
        case GTK_MESSAGE_WARNING:
            stock_id = GTK_STOCK_DIALOG_WARNING;
            break;
        case GTK_MESSAGE_QUESTION:
            stock_id = GTK_STOCK_DIALOG_QUESTION;
            break;
        case GTK_MESSAGE_ERROR:
            stock_id = GTK_STOCK_DIALOG_ERROR;
            break;
        default:
            stock_id = GTK_STOCK_MISSING_IMAGE;
            g_warn_if_reached ();
            break;
    }

    return stock_id;
}

void
e_alert_add_action (EAlert *alert,
                    GtkAction *action,
                    gint response_id)
{
    g_return_if_fail (E_IS_ALERT (alert));
    g_return_if_fail (GTK_ACTION (action));

    g_object_set_data (
        G_OBJECT (action), "e-alert-response-id",
        GINT_TO_POINTER (response_id));

    g_signal_connect_swapped (
        action, "activate",
        G_CALLBACK (alert_action_activate), alert);

    g_queue_push_tail (&alert->priv->actions, g_object_ref (action));
}

GList *
e_alert_peek_actions (EAlert *alert)
{
    g_return_val_if_fail (E_IS_ALERT (alert), NULL);

    /* Make sure we have at least one action.  Do this on-demand
     * in case the XML definition did not specify any actions but
     * other actions were added via e_alert_add_action(). */
    if (g_queue_is_empty (&alert->priv->actions)) {
        GtkAction *action;

        action = gtk_action_new (
            "alert-response-0", _("_Dismiss"), NULL, NULL);
        e_alert_add_action (alert, action, GTK_RESPONSE_CLOSE);
        g_object_unref (action);
    }

    return g_queue_peek_head_link (&alert->priv->actions);
}

GtkWidget *
e_alert_create_image (EAlert *alert,
                      GtkIconSize size)
{
    const gchar *stock_id;

    g_return_val_if_fail (E_IS_ALERT (alert), NULL);

    stock_id = e_alert_get_stock_id (alert);

    return gtk_image_new_from_stock (stock_id, size);
}

void
e_alert_response (EAlert *alert,
                  gint response_id)
{
    g_return_if_fail (E_IS_ALERT (alert));

    g_signal_emit (alert, signals[RESPONSE], 0, response_id);
}

/**
 * e_alert_start_timer:
 * @alert: an #EAlert
 * @seconds: seconds until timeout occurs
 *
 * Starts an internal timer for @alert.  When the timer expires, @alert
 * will emit the default response.  There is only one timer per #EAlert,
 * so calling this function repeatedly on the same #EAlert will restart
 * its timer each time.  If @seconds is zero, the timer is cancelled and
 * no response will be emitted.
 **/
void
e_alert_start_timer (EAlert *alert,
                     guint seconds)
{
    g_return_if_fail (E_IS_ALERT (alert));

    if (alert->priv->timeout_id > 0) {
        g_source_remove (alert->priv->timeout_id);
        alert->priv->timeout_id = 0;
    }

    if (seconds > 0)
        alert->priv->timeout_id = g_timeout_add_seconds (
            seconds, (GSourceFunc) alert_timeout_cb, alert);
}

void
e_alert_submit (EAlertSink *alert_sink,
                const gchar *tag,
                ...)
{
    va_list va;

    va_start (va, tag);
    e_alert_submit_valist (alert_sink, tag, va);
    va_end (va);
}

void
e_alert_submit_valist (EAlertSink *alert_sink,
                       const gchar *tag,
                       va_list va)
{
    EAlert *alert;

    g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
    g_return_if_fail (tag != NULL);

    alert = e_alert_new_valist (tag, va);
    e_alert_sink_submit_alert (alert_sink, alert);
    g_object_unref (alert);
}