/* * 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> * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #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-error.h" #define d(x) struct _e_error_button { struct _e_error_button *next; const gchar *stock; const gchar *label; gint response; }; struct _e_error { 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_error_button *buttons; }; struct _e_error_table { const gchar *domain; const gchar *translation_domain; GHashTable *errors; }; static GHashTable *error_table; /* ********************************************************************** */ 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, 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; } /* 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 gchar *path) { xmlDocPtr doc = NULL; xmlNodePtr root, error, scan; struct _e_error *e; struct _e_error_button *lastbutton; struct _e_error_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(error_table, tmp); if (table == NULL) { gchar *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, (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_error_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_error_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->errors, (gpointer) e->id, e); } } xmlFreeDoc(doc); } static void ee_load_tables(void) { GDir *dir; const gchar *d; gchar *base; struct _e_error_table *table; gint 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 < G_N_ELEMENTS (default_errors); i++) g_hash_table_insert(table->errors, (gpointer) default_errors[i].id, &default_errors[i]); g_hash_table_insert(error_table, (gpointer) table->domain, table); /* look for installed error 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); ee_load(path); g_free(path); } g_dir_close(dir); g_free (base); } /* unfortunately, gmarkup_escape doesn't expose its gstring based api :( */ static void ee_append_text(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 ee_build_label(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) ee_append_text(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); } static void ee_response(GtkWidget *w, guint button, struct _e_error *e) { if (button == GTK_RESPONSE_HELP) { g_signal_stop_emission_by_name(w, "response"); e_display_help (GTK_WINDOW (w), e->help_uri); } } GtkWidget * e_error_newv(GtkWindow *parent, const gchar *tag, const gchar *arg0, va_list ap) { struct _e_error_table *table; struct _e_error *e; struct _e_error_button *b; GtkWidget *hbox, *w, *scroll=NULL; GtkWidget *action_area; GtkWidget *content_area; gchar *tmp, *domain, *id; GString *out, *oerr; GPtrArray *args; GtkDialog *dialog; gchar *str, *perr=NULL, *serr=NULL; if (error_table == NULL) ee_load_tables(); dialog = (GtkDialog *)gtk_dialog_new(); action_area = gtk_dialog_get_action_area (dialog); content_area = gtk_dialog_get_content_area (dialog); gtk_dialog_set_has_separator(dialog, FALSE); gtk_widget_ensure_style ((GtkWidget *)dialog); gtk_container_set_border_width (GTK_CONTAINER (action_area), 12); gtk_container_set_border_width (GTK_CONTAINER (content_area), 0); if (parent) gtk_window_set_transient_for ((GtkWindow *)dialog, parent); else g_warning ( "Something called %s() with a NULL parent window. " "This is no longer legal, please fix it.", G_STRFUNC); 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 */ str = g_strdup_printf(_("Internal error, unknown error '%s' requested"), tag); tmp = g_strdup_printf("<span weight=\"bold\">%s</span>", str); g_free(str); 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_UNSET_FLAGS (w, GTK_CAN_FOCUS); gtk_widget_show(w); gtk_box_pack_start (GTK_BOX (content_area), 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, FALSE, FALSE, 12); args = g_ptr_array_new(); tmp = (gchar *)arg0; while (tmp) { g_ptr_array_add(args, tmp); tmp = va_arg(ap, gchar *); } out = g_string_new(""); if (e->title && *e->title) { ee_build_label(out, e->title, args, FALSE); gtk_window_set_title((GtkWindow *)dialog, out->str); g_string_truncate(out, 0); } else gtk_window_set_title((GtkWindow *)dialog, out->str); if (e->primary) { g_string_append(out, "<span weight=\"bold\" size=\"larger\">"); ee_build_label(out, e->primary, args, TRUE); g_string_append(out, "</span>\n\n"); oerr = g_string_new(""); ee_build_label(oerr, e->primary, args, FALSE); perr = g_strdup (oerr->str); g_string_free (oerr, TRUE); } else perr = g_strdup (gtk_window_get_title (GTK_WINDOW (dialog))); if (e->secondary) { ee_build_label(out, e->secondary, args, TRUE); oerr = g_string_new(""); ee_build_label(oerr, e->secondary, args, TRUE); serr = g_strdup (oerr->str); g_string_free (oerr, TRUE); } g_ptr_array_free(args, TRUE); if (e->scroll) { scroll = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy ((GtkScrolledWindow *)scroll, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); } 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); GTK_WIDGET_UNSET_FLAGS (w, GTK_CAN_FOCUS); g_string_free(out, TRUE); if (e->scroll) { gtk_scrolled_window_add_with_viewport ((GtkScrolledWindow *)scroll, w); gtk_box_pack_start((GtkBox *)hbox, scroll, FALSE, FALSE, 0); gtk_window_set_default_size ((GtkWindow *)dialog, 360, 180); } else gtk_box_pack_start((GtkBox *)hbox, w, TRUE, TRUE, 0); gtk_widget_show_all(hbox); gtk_box_pack_start (GTK_BOX (content_area), hbox, TRUE, TRUE, 0); g_object_set_data_full ((GObject *) dialog, "primary", perr, g_free); g_object_set_data_full ((GObject *) dialog, "secondary", serr, g_free); 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. **/ GtkWidget * e_error_new(GtkWindow *parent, const gchar *tag, const gchar *arg0, ...) { GtkWidget *w; va_list ap; va_start(ap, arg0); w = e_error_newv(parent, tag, arg0, ap); va_end(ap); return w; } gint e_error_runv(GtkWindow *parent, const gchar *tag, const gchar *arg0, va_list ap) { GtkWidget *w; gint 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. **/ gint e_error_run(GtkWindow *parent, const gchar *tag, const gchar *arg0, ...) { GtkWidget *w; va_list ap; gint 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; } /** * e_error_count_buttons: * @dialog: a #GtkDialog * * Counts the number of buttons in @dialog's action area. * * Returns: number of action area buttons **/ guint e_error_count_buttons (GtkDialog *dialog) { GtkWidget *container; GList *children, *iter; guint n_buttons = 0; g_return_val_if_fail (GTK_DIALOG (dialog), 0); container = gtk_dialog_get_action_area (dialog); children = gtk_container_get_children (GTK_CONTAINER (container)); /* Iterate over the children looking for buttons. */ for (iter = children; iter != NULL; iter = iter->next) if (GTK_IS_BUTTON (iter->data)) n_buttons++; g_list_free (children); return n_buttons; }