diff options
Diffstat (limited to 'e-util/e-error.c')
-rw-r--r-- | e-util/e-error.c | 628 |
1 files changed, 628 insertions, 0 deletions
diff --git a/e-util/e-error.c b/e-util/e-error.c new file mode 100644 index 0000000000..f7dc888609 --- /dev/null +++ b/e-util/e-error.c @@ -0,0 +1,628 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * + * + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2004 Novell Inc. (www.novell.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 Street #330, Boston, MA 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <sys/types.h> +#include <dirent.h> + +#include <libxml/parser.h> +#include <libxml/xmlmemory.h> + +#include <glib.h> +#include <gtk/gtkbutton.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkdialog.h> +#include <gtk/gtkwindow.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkimage.h> + +#include <libgnome/gnome-i18n.h> +#include <libgnome/gnome-url.h> + +#include "e-error.h" + +#define d(x) + +struct _e_error_button { + struct _e_error_button *next; + char *stock; + char *label; + int response; +}; + +struct _e_error { + guint32 flags; + char *id; + int type; + int default_response; + char *title; + char *primary; + char *secondary; + char *help_uri; + struct _e_error_button *buttons; +}; + +struct _e_error_table { + char *domain; + char *translation_domain; + GHashTable *errors; +}; + +static GHashTable *error_table; +static GSList *ee_parent_list; + +/* ********************************************************************** */ + +static struct _e_error_button default_ok_button = { + NULL, "gtk-ok", NULL, GTK_RESPONSE_OK +}; + +static struct _e_error default_errors[] = { + { GTK_DIALOG_MODAL, "error", 3, GTK_RESPONSE_OK, N_("Evolution Error"), "{0}", "{1}", NULL, &default_ok_button }, + { GTK_DIALOG_MODAL, "error-primary", 3, GTK_RESPONSE_OK, N_("Evolution Error"), "{0}", NULL, NULL, &default_ok_button }, + { GTK_DIALOG_MODAL, "warning", 1, GTK_RESPONSE_OK, N_("Evolution Warning"), "{0}", "{1}", NULL, &default_ok_button }, + { GTK_DIALOG_MODAL, "warning-primary", 1, GTK_RESPONSE_OK, N_("Evolution Warning"), "{0}", NULL, NULL, &default_ok_button }, +}; + +/* ********************************************************************** */ + +static struct { + char *name; + int 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 int +map_response(const char *name) +{ + int i; + + for (i=0;i<sizeof(response_map)/sizeof(response_map[0]);i++) + if (!strcmp(name, response_map[i].name)) + return response_map[i].id; + + return 0; +} + +static struct { + const char *name; + const char *icon; + const char *title; +} type_map[] = { + { "info", GTK_STOCK_DIALOG_INFO, N_("Evolution Information") }, + { "warning", GTK_STOCK_DIALOG_WARNING, N_("Evolution Warning") }, + { "question", GTK_STOCK_DIALOG_QUESTION, N_("Evolution Query") }, + { "error", GTK_STOCK_DIALOG_ERROR, N_("Evolution Error") }, +}; + +static int +map_type(const char *name) +{ + int i; + + if (name) { + for (i=0;i<sizeof(type_map)/sizeof(type_map[0]);i++) + if (!strcmp(name, type_map[i].name)) + return i; + } + + return 3; +} + +/* + 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 +ee_load(const char *path) +{ + xmlDocPtr doc; + xmlNodePtr root, error, scan; + struct _e_error *e; + struct _e_error_button *lastbutton; + struct _e_error_table *table; + char *tmp; + + d(printf("loading error file %s\n", path)); + + doc = xmlParseFile(path); + if (doc == NULL) { + g_warning("Error file '%s' not found", path); + return; + } + + root = xmlDocGetRootElement(doc); + if (root == NULL + || strcmp(root->name, "error-list") != 0 + || (tmp = xmlGetProp(root, "domain")) == NULL) { + g_warning("Error file '%s' invalid format", path); + xmlFreeDoc(doc); + return; + } + + table = g_hash_table_lookup(error_table, tmp); + if (table == NULL) { + char *tmp2; + + table = g_malloc0(sizeof(*table)); + table->domain = g_strdup(tmp); + table->errors = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(error_table, table->domain, table); + + tmp2 = xmlGetProp(root, "translation-domain"); + if (tmp2) { + table->translation_domain = g_strdup(tmp2); + xmlFree(tmp2); + + tmp2 = xmlGetProp(root, "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(error->name, "error")) { + tmp = xmlGetProp(error, "id"); + if (tmp == NULL) + continue; + + e = g_malloc0(sizeof(*e)); + e->id = g_strdup(tmp); + xmlFree(tmp); + lastbutton = (struct _e_error_button *)&e->buttons; + + tmp = xmlGetProp(error, "modal"); + if (tmp) { + if (!strcmp(tmp, "true")) + e->flags |= GTK_DIALOG_MODAL; + xmlFree(tmp); + } + + tmp = xmlGetProp(error, "type"); + e->type = map_type(tmp); + if (tmp) + xmlFree(tmp); + + tmp = xmlGetProp(error, "default"); + if (tmp) { + e->default_response = map_response(tmp); + xmlFree(tmp); + } + + for (scan = error->children;scan;scan=scan->next) { + if (!strcmp(scan->name, "primary")) { + if ((tmp = xmlNodeGetContent(scan))) { + e->primary = g_strdup(dgettext(table->translation_domain, tmp)); + xmlFree(tmp); + } + } else if (!strcmp(scan->name, "secondary")) { + if ((tmp = xmlNodeGetContent(scan))) { + e->secondary = g_strdup(dgettext(table->translation_domain, tmp)); + xmlFree(tmp); + } + } else if (!strcmp(scan->name, "title")) { + if ((tmp = xmlNodeGetContent(scan))) { + e->title = g_strdup(dgettext(table->translation_domain, tmp)); + xmlFree(tmp); + } + } else if (!strcmp(scan->name, "help")) { + tmp = xmlGetProp(scan, "uri"); + if (tmp) { + e->help_uri = g_strdup(tmp); + xmlFree(tmp); + } + } else if (!strcmp(scan->name, "button")) { + struct _e_error_button *b; + + b = g_malloc0(sizeof(*b)); + tmp = xmlGetProp(scan, "stock"); + if (tmp) { + b->stock = g_strdup(tmp); + xmlFree(tmp); + } + tmp = xmlGetProp(scan, "label"); + if (tmp) { + b->label = g_strdup(dgettext(table->translation_domain, tmp)); + xmlFree(tmp); + } + tmp = xmlGetProp(scan, "response"); + if (tmp) { + b->response = map_response(tmp); + xmlFree(tmp); + } + + if (b->stock == NULL && b->label == NULL) { + g_warning("Error file '%s': missing button details in error '%s'", path, e->id); + g_free(b->stock); + g_free(b->label); + g_free(b); + } else { + lastbutton->next = b; + lastbutton = b; + } + } + } + + g_hash_table_insert(table->errors, e->id, e); + } + } + + xmlFreeDoc(doc); +} + +static void +ee_load_tables(void) +{ + DIR *dir; + struct dirent *d; + const char *base = EVOLUTION_PRIVDATADIR "/errors"; + struct _e_error_table *table; + int i; + + if (error_table != NULL) + return; + + error_table = g_hash_table_new(g_str_hash, g_str_equal); + + /* setup system error types */ + table = g_malloc0(sizeof(*table)); + table->domain = "builtin"; + table->errors = g_hash_table_new(g_str_hash, g_str_equal); + for (i=0;i<sizeof(default_errors)/sizeof(default_errors[0]);i++) + g_hash_table_insert(table->errors, default_errors[i].id, &default_errors[i]); + g_hash_table_insert(error_table, table->domain, table); + + /* look for installed error tables */ + dir = opendir(base); + if (dir == NULL) + return; + + while ( (d = readdir(dir)) ) { + char *path; + + if (d->d_name[0] == '.') + continue; + + path = g_build_filename(base, d->d_name, NULL); + ee_load(path); + g_free(path); + } + + closedir(dir); +} + +/* unfortunately, gmarkup_escape doesn't expose its gstring based api :( */ +static void +ee_append_text(GString *out, const char *text) +{ + char 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 +ee_build_label(GString *out, const char *fmt, GPtrArray *args) +{ + const char *end, *newstart; + int 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) + ee_append_text(out, args->pdata[id]); + else + g_warning("Error references argument %d not supplied by caller", id); + fmt = end+1; + } + + g_string_append(out, fmt); +} + +static void +ee_response(GtkWidget *w, guint button, struct _e_error *e) +{ + GError *err = NULL; + + if (button == GTK_RESPONSE_HELP) { + g_signal_stop_emission_by_name(w, "response"); + gnome_url_show(e->help_uri, &err); + if (err) { + g_warning("Unable to run help uri: %s", err->message); + g_error_free(err); + } + } +} + +GtkWidget * +e_error_newv(GtkWindow *parent, const char *tag, const char *arg0, va_list ap) +{ + struct _e_error_table *table; + struct _e_error *e; + struct _e_error_button *b; + GtkWidget *hbox, *w; + char *tmp, *domain, *id; + GString *out; + GPtrArray *args; + GtkDialog *dialog; + + if (error_table == NULL) + ee_load_tables(); + + dialog = (GtkDialog *)gtk_dialog_new(); + gtk_dialog_set_has_separator(dialog, FALSE); + + gtk_widget_ensure_style ((GtkWidget *)dialog); + gtk_container_set_border_width ((GtkContainer *)(dialog->vbox), 0); + gtk_container_set_border_width ((GtkContainer *)(dialog->action_area), 12); + + if (parent == NULL && ee_parent_list) + parent = (GtkWindow *)ee_parent_list->data; + if (parent) + gtk_window_set_transient_for((GtkWindow *)dialog, parent); + else + g_warning("No parent set, or default parent available for error dialog"); + + domain = alloca(strlen(tag)+1); + strcpy(domain, tag); + id = strchr(domain, ':'); + if (id) + *id++ = 0; + + if ( id == NULL + || (table = g_hash_table_lookup(error_table, domain)) == NULL + || (e = g_hash_table_lookup(table->errors, id)) == NULL) { + /* setup a dummy error */ + tmp = g_strdup_printf(_("<span weight=\"bold\">Internal error, unknown error '%s' requested</span>"), tag); + w = gtk_label_new(NULL); + gtk_label_set_selectable((GtkLabel *)w, TRUE); + gtk_label_set_line_wrap((GtkLabel *)w, TRUE); + gtk_label_set_markup((GtkLabel *)w, tmp); + gtk_widget_show(w); + gtk_box_pack_start((GtkBox *)dialog->vbox, w, TRUE, TRUE, 12); + + return (GtkWidget *)dialog; + } + + if (e->flags & GTK_DIALOG_MODAL) + gtk_window_set_modal((GtkWindow *)dialog, TRUE); + gtk_window_set_destroy_with_parent((GtkWindow *)dialog, TRUE); + + if (e->help_uri) { + w = gtk_dialog_add_button(dialog, GTK_STOCK_HELP, GTK_RESPONSE_HELP); + g_signal_connect(dialog, "response", G_CALLBACK(ee_response), e); + } + + b = e->buttons; + if (b == NULL) { + gtk_dialog_add_button(dialog, GTK_STOCK_OK, GTK_RESPONSE_OK); + } else { + for (b = e->buttons;b;b=b->next) { + if (b->stock) { + if (b->label) { +#if 0 + /* FIXME: So although this looks like it will work, it wont. + Need to do it the hard way ... it also breaks the + default_response stuff */ + w = gtk_button_new_from_stock(b->stock); + gtk_button_set_label((GtkButton *)w, b->label); + gtk_widget_show(w); + gtk_dialog_add_action_widget(dialog, w, b->response); +#endif + gtk_dialog_add_button(dialog, b->label, b->response); + } else + gtk_dialog_add_button(dialog, b->stock, b->response); + } else + gtk_dialog_add_button(dialog, b->label, b->response); + } + } + + if (e->default_response) + gtk_dialog_set_default_response(dialog, e->default_response); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_container_set_border_width((GtkContainer *)hbox, 12); + + w = gtk_image_new_from_stock(type_map[e->type].icon, GTK_ICON_SIZE_DIALOG); + gtk_misc_set_alignment((GtkMisc *)w, 0.0, 0.0); + gtk_box_pack_start((GtkBox *)hbox, w, TRUE, TRUE, 12); + + args = g_ptr_array_new(); + tmp = (char *)arg0; + while (tmp) { + g_ptr_array_add(args, tmp); + tmp = va_arg(ap, char *); + } + + out = g_string_new(""); + + if (e->title) { + ee_build_label(out, dgettext(table->translation_domain, e->title), args); + gtk_window_set_title((GtkWindow *)dialog, out->str); + g_string_truncate(out, 0); + } else + gtk_window_set_title((GtkWindow *)dialog, dgettext(table->translation_domain, type_map[e->type].title)); + + + if (e->primary) { + g_string_append(out, "<span weight=\"bold\" size=\"larger\">"); + ee_build_label(out, dgettext(table->translation_domain, e->primary), args); + g_string_append(out, "</span>\n\n"); + } + + if (e->secondary) + ee_build_label(out, dgettext(table->translation_domain, e->secondary), args); + + g_ptr_array_free(args, TRUE); + + w = gtk_label_new(NULL); + gtk_label_set_selectable((GtkLabel *)w, TRUE); + gtk_label_set_line_wrap((GtkLabel *)w, TRUE); + gtk_label_set_markup((GtkLabel *)w, out->str); + g_string_free(out, TRUE); + gtk_box_pack_start((GtkBox *)hbox, w, FALSE, FALSE, 0); + gtk_widget_show_all(hbox); + + gtk_box_pack_start((GtkBox *)dialog->vbox, hbox, TRUE, TRUE, 0); + + return (GtkWidget *)dialog; +} + +/** + * e_error_new: + * @parent: + * @tag: error identifier + * @arg0: The first argument for the error formatter. The list must + * be NULL terminated. + * + * Creates a new error widget. The @tag argument is used to determine + * which error to use, it is in the format domain:error-id. The NULL + * terminated list of arguments, starting with @arg0 is used to fill + * out the error definition. + * + * Return value: A GtkDialog which can be used for showing an error + * dialog asynchronously. + **/ +struct _GtkWidget * +e_error_new(struct _GtkWindow *parent, const char *tag, const char *arg0, ...) +{ + GtkWidget *w; + va_list ap; + + va_start(ap, arg0); + w = e_error_newv(parent, tag, arg0, ap); + va_end(ap); + + return w; +} + +int +e_error_runv(GtkWindow *parent, const char *tag, const char *arg0, va_list ap) +{ + GtkWidget *w; + int res; + + w = e_error_newv(parent, tag, arg0, ap); + + res = gtk_dialog_run((GtkDialog *)w); + gtk_widget_destroy(w); + + return res; +} + +/** + * e_error_run: + * @parent: + * @tag: + * @arg0: + * + * Sets up, displays, runs and destroys a standard evolution error + * dialog based on @tag, which is in the format domain:error-id. + * + * Return value: The response id of the button pressed. + **/ +int +e_error_run(GtkWindow *parent, const char *tag, const char *arg0, ...) +{ + GtkWidget *w; + va_list ap; + int res; + + va_start(ap, arg0); + w = e_error_newv(parent, tag, arg0, ap); + va_end(ap); + + res = gtk_dialog_run((GtkDialog *)w); + gtk_widget_destroy(w); + + return res; +} + +static void +remove_parent(GtkWidget *w, GtkWidget *parent) +{ + ee_parent_list = g_slist_remove(ee_parent_list, parent); +} + +/** + * e_error_default_parent: + * @parent: + * + * Bit of a hack, set a default parent that will be used to parent any + * error boxes if none is supplied. + * + * This may be called multiple times, and the last call will be the + * main default. This function will keep track of the parents + * destruction state. + **/ +void +e_error_default_parent(struct _GtkWindow *parent) +{ + if (g_slist_find(ee_parent_list, parent) == NULL) { + ee_parent_list = g_slist_prepend(ee_parent_list, parent); + g_signal_connect(parent, "destroy", G_CALLBACK(remove_parent), parent); + } +} + |