/* * 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, "<"); else if (c == '>') g_string_append(out, ">"); else if (c == '"') g_string_append(out, """); else if (c == '\'') g_string_append(out, "'"); else if (c == '&') g_string_append(out, "&"); 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; }