aboutsummaryrefslogblamecommitdiffstats
path: root/e-util/e-alert.c
blob: 20ab3221ec6cf08aa860d995cd675646a05aa999 (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"

#define d(x)

struct _e_alert {
    guint32 flags;
    const gchar *id;
    gint type;
    gint default_response;
    const gchar *title;
    const gchar *primary;
    const gchar *secondary;
    const gchar *help_uri;
    gboolean scroll;
    struct _e_alert_button *buttons;
};

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

static GHashTable *alert_table;

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

static struct _e_alert_button default_ok_button = {
    NULL, "gtk-ok", NULL, GTK_RESPONSE_OK
};

static struct _e_alert default_alerts[] = {
    { GTK_DIALOG_MODAL, "error", 3, GTK_RESPONSE_OK, N_("Evolution Error"), "{0}", "{1}", NULL, FALSE, &default_ok_button },
    { GTK_DIALOG_MODAL, "error-primary", 3, GTK_RESPONSE_OK, N_("Evolution Error"), "{0}", NULL, NULL, FALSE, &default_ok_button },
    { GTK_DIALOG_MODAL, "warning", 1, GTK_RESPONSE_OK, N_("Evolution Warning"), "{0}", "{1}", NULL, FALSE, &default_ok_button },
    { GTK_DIALOG_MODAL, "warning-primary", 1, GTK_RESPONSE_OK, N_("Evolution Warning"), "{0}", NULL, NULL, FALSE, &default_ok_button },
};

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

static struct {
    const gchar *name;
    gint id;
} response_map[] = {
    { "GTK_RESPONSE_REJECT", GTK_RESPONSE_REJECT },
    { "GTK_RESPONSE_ACCEPT", GTK_RESPONSE_ACCEPT },
    { "GTK_RESPONSE_OK", GTK_RESPONSE_OK },
    { "GTK_RESPONSE_CANCEL", GTK_RESPONSE_CANCEL },
    { "GTK_RESPONSE_CLOSE", GTK_RESPONSE_CLOSE },
    { "GTK_RESPONSE_YES", GTK_RESPONSE_YES },
    { "GTK_RESPONSE_NO", GTK_RESPONSE_NO },
    { "GTK_RESPONSE_APPLY", GTK_RESPONSE_APPLY },
    { "GTK_RESPONSE_HELP", GTK_RESPONSE_HELP },
};

static gint
map_response(const gchar *name)
{
    gint i;

    for (i = 0; i < G_N_ELEMENTS (response_map); i++)
        if (!strcmp(name, response_map[i].name))
            return response_map[i].id;

    return 0;
}

static struct {
    const gchar *name;
    const gchar *icon;
} type_map[] = {
    { "info", GTK_STOCK_DIALOG_INFO },
    { "warning", GTK_STOCK_DIALOG_WARNING },
    { "question", GTK_STOCK_DIALOG_QUESTION },
    { "error", GTK_STOCK_DIALOG_ERROR },
};

static gint
map_type(const gchar *name)
{
    gint i;

    if (name) {
        for (i = 0; i < G_N_ELEMENTS (type_map); i++)
            if (!strcmp(name, type_map[i].name))
                return i;
    }

    return 3;
}

G_DEFINE_TYPE (EAlert, e_alert, G_TYPE_OBJECT)

#define ALERT_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), E_TYPE_ALERT, EAlertPrivate))

enum
{
    PROP_0,
    PROP_TAG,
    PROP_ARGS
};

struct _EAlertPrivate
{
    gchar *tag;
    GPtrArray *args;
    struct _e_alert *definition;
};

/*
  XML format:

 <error id="error-id" type="info|warning|question|error"? response="default_response"? modal="true"? >
  <title>Window Title</title>?
  <primary>Primary error text.</primary>?
  <secondary>Secondary error text.</secondary>?
  <help uri="help uri"/> ?
  <button stock="stock-button-id"? label="button label"? response="response_id"? /> *
 </error>

 The tool e-error-tool is used to extract the translatable strings for
 translation.

*/
static void
e_alert_load(const gchar *path)
{
    xmlDocPtr doc = NULL;
    xmlNodePtr root, error, scan;
    struct _e_alert *e;
    struct _e_alert_button *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);
            e->scroll = FALSE;

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

            tmp = (gchar *)xmlGetProp(error, (const guchar *)"modal");
            if (tmp) {
                if (!strcmp(tmp, "true"))
                    e->flags |= GTK_DIALOG_MODAL;
                xmlFree(tmp);
            }

            tmp = (gchar *)xmlGetProp(error, (const guchar *)"type");
            e->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);
            }

            tmp = (gchar *)xmlGetProp(error, (const guchar *)"scroll");
            if (tmp) {
                if (!strcmp(tmp, "yes"))
                    e->scroll = TRUE;
                xmlFree(tmp);
            }

            for (scan = error->children;scan;scan=scan->next) {
                if (!strcmp((gchar *)scan->name, "primary")) {
                    if ((tmp = (gchar *)xmlNodeGetContent(scan))) {
                        e->primary = g_strdup(dgettext(table->translation_domain, tmp));
                        xmlFree(tmp);
                    }
                } else if (!strcmp((gchar *)scan->name, "secondary")) {
                    if ((tmp = (gchar *)xmlNodeGetContent(scan))) {
                        e->secondary = g_strdup(dgettext(table->translation_domain, tmp));
                        xmlFree(tmp);
                    }
                } else if (!strcmp((gchar *)scan->name, "title")) {
                    if ((tmp = (gchar *)xmlNodeGetContent(scan))) {
                        e->title = g_strdup(dgettext(table->translation_domain, tmp));
                        xmlFree(tmp);
                    }
                } else if (!strcmp((gchar *)scan->name, "help")) {
                    tmp = (gchar *)xmlGetProp(scan, (const guchar *)"uri");
                    if (tmp) {
                        e->help_uri = g_strdup(tmp);
                        xmlFree(tmp);
                    }
                } else if (!strcmp((gchar *)scan->name, "button")) {
                    struct _e_alert_button *b;
                    gchar *label = NULL;
                    gchar *stock = NULL;

                    b = g_malloc0(sizeof(*b));
                    tmp = (gchar *)xmlGetProp(scan, (const guchar *)"stock");
                    if (tmp) {
                        stock = g_strdup(tmp);
                        b->stock = stock;
                        xmlFree(tmp);
                    }
                    tmp = (gchar *)xmlGetProp(scan, (const guchar *)"label");
                    if (tmp) {
                        label = g_strdup(dgettext(table->translation_domain, tmp));
                        b->label = label;
                        xmlFree(tmp);
                    }
                    tmp = (gchar *)xmlGetProp(scan, (const guchar *)"response");
                    if (tmp) {
                        b->response = map_response(tmp);
                        xmlFree(tmp);
                    }

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

            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
e_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);
            break;
        case PROP_ARGS:
            g_value_set_boxed (value, alert->priv->args);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

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

    switch (property_id)
    {
        case PROP_TAG:
            alert->priv->tag = g_value_dup_string (value);
            break;
        case PROP_ARGS:
            alert->priv->args = g_value_dup_boxed (value);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

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

    if (alert->priv->tag) {
        g_free (alert->priv->tag);
        alert->priv->tag = NULL;
    }

    if (alert->priv->args) {
        /* arg strings will be freed automatically since we set a free func when
         * creating the ptr array */
        g_boxed_free (G_TYPE_PTR_ARRAY, alert->priv->args);
        alert->priv->args = NULL;
    }

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

static void
e_alert_constructed (GObject *obj)
{
    EAlert *alert = E_ALERT (obj);

    struct _e_alert_table *table;
    gchar *domain, *id;

    g_return_if_fail (alert_table);
    g_return_if_fail (alert->priv->tag);

    domain = g_alloca(strlen(alert->priv->tag)+1);
    strcpy(domain, alert->priv->tag);
    id = strchr(domain, ':');
    if (id)
        *id++ = 0;

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

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

    g_warn_if_fail(alert->priv->definition);

}

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

    g_type_class_add_private (klass, sizeof (EAlertPrivate));

    object_class->get_property = e_alert_get_property;
    object_class->set_property = e_alert_set_property;
    object_class->dispose = e_alert_dispose;
    object_class->constructed = e_alert_constructed;

    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_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));

    e_alert_load_tables ();
}

static void
e_alert_init (EAlert *self)
{
    self->priv = ALERT_PRIVATE (self);
}

/**
 * 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.
 **/
EAlert *
e_alert_new(const gchar *tag, const gchar *arg0, ...)
{
    EAlert *e;
    va_list ap;

    va_start(ap, arg0);
    e = e_alert_new_valist(tag, arg0, ap);
    va_end(ap);

    return e;
}

EAlert *
e_alert_new_valist(const gchar *tag, const gchar *arg0, va_list ap)
{
    gchar *tmp;
    GPtrArray *args = g_ptr_array_new_with_free_func (g_free);

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

    return e_alert_new_array (tag, args);
}

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

/* unfortunately, gmarkup_escape doesn't expose its gstring based api :( */
static void
e_alert_append_text_escaped (GString *out, const gchar *text)
{
    gchar c;

    while ((c=*text++)) {
        if (c == '<')
            g_string_append(out, "&lt;");
        else if (c == '>')
            g_string_append(out, "&gt;");
        else if (c == '"')
            g_string_append(out, "&quot;");
        else if (c == '\'')
            g_string_append(out, "&apos;");
        else if (c == '&')
            g_string_append(out, "&amp;");
        else
            g_string_append_c(out, c);
    }
}

static void
e_alert_format_string (GString *out, const gchar *fmt, GPtrArray *args, gboolean escape_args)
{
    const gchar *end, *newstart;
    gint id;

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

    g_string_append(out, fmt);
}

guint32
e_alert_get_flags (EAlert *alert)
{
    g_return_val_if_fail (alert && alert->priv && alert->priv->definition, 0);
    return alert->priv->definition->flags;
}

const gchar *
e_alert_peek_stock_image (EAlert *alert)
{
    g_return_val_if_fail (alert && alert->priv && alert->priv->definition, NULL);
    return type_map[alert->priv->definition->type].icon;
}

gint
e_alert_get_default_response (EAlert *alert)
{
    g_return_val_if_fail (alert && alert->priv && alert->priv->definition, 0);
    return alert->priv->definition->default_response;
}

gchar *
e_alert_get_title (EAlert *alert)
{
    GString *formatted;
    g_return_val_if_fail (alert && alert->priv && alert->priv->definition, NULL);

    formatted = g_string_new ("");
    if (alert->priv->definition->title) {
        e_alert_format_string (formatted,
                       alert->priv->definition->title,
                       alert->priv->args, FALSE);
    }
    return g_string_free (formatted, FALSE);
}

gchar *
e_alert_get_primary_text (EAlert *alert)
{
    GString *formatted;
    g_return_val_if_fail (alert && alert->priv, NULL);

    formatted = g_string_new ("");
    if (alert->priv->definition)
        if (alert->priv->definition->primary) {
            e_alert_format_string (formatted,
                           alert->priv->definition->primary,
                           alert->priv->args, FALSE);
        } else {
            gchar *title = e_alert_get_title (alert);
            g_string_append (formatted, title);
            g_free (title);
        }
    else {
        g_string_append_printf(formatted,
                       _("Internal error, unknown error '%s' requested"),
                       alert->priv->tag);
    }

    return g_string_free (formatted, FALSE);
}

gchar *
e_alert_get_secondary_text (EAlert *alert)
{
    GString *formatted;
    g_return_val_if_fail (alert && alert->priv && alert->priv->definition, NULL);

    formatted = g_string_new ("");
    if (alert->priv->definition->secondary) {
        e_alert_format_string (formatted,
                       alert->priv->definition->secondary,
                       alert->priv->args, TRUE);
    }
    return g_string_free (formatted, FALSE);
}

const gchar *
e_alert_peek_help_uri (EAlert *alert)
{
    g_return_val_if_fail (alert && alert->priv && alert->priv->definition, NULL);
    return alert->priv->definition->help_uri;
}

gboolean
e_alert_get_scroll (EAlert *alert)
{
    g_return_val_if_fail (alert && alert->priv && alert->priv->definition, FALSE);
    return alert->priv->definition->scroll;
}

struct _e_alert_button *
e_alert_peek_buttons (EAlert *alert)
{
    g_return_val_if_fail (alert && alert->priv && alert->priv->definition, NULL);
    return alert->priv->definition->buttons;
}