diff --git a/app/core/gimp-gui.c b/app/core/gimp-gui.c index 189910f523..4c3842b3d8 100644 --- a/app/core/gimp-gui.c +++ b/app/core/gimp-gui.c @@ -159,9 +159,10 @@ gimp_show_message (Gimp *gimp, GObject *handler, GimpMessageSeverity severity, const gchar *domain, - const gchar *message) + const gchar *message, + const gchar *trace) { - const gchar *desc = "Message"; + const gchar *desc = trace ? "Error" : "Message"; g_return_if_fail (GIMP_IS_GIMP (gimp)); g_return_if_fail (handler == NULL || G_IS_OBJECT (handler)); @@ -174,8 +175,8 @@ gimp_show_message (Gimp *gimp, { if (gimp->gui.show_message) { - gimp->gui.show_message (gimp, handler, - severity, domain, message); + gimp->gui.show_message (gimp, handler, severity, + domain, message, trace); return; } else if (GIMP_IS_PROGRESS (handler) && @@ -190,6 +191,8 @@ gimp_show_message (Gimp *gimp, gimp_enum_get_value (GIMP_TYPE_MESSAGE_SEVERITY, severity, NULL, NULL, &desc, NULL); g_printerr ("%s-%s: %s\n\n", domain, desc, message); + if (trace) + g_printerr ("Back trace:\n%s\n\n", trace); } void diff --git a/app/core/gimp-gui.h b/app/core/gimp-gui.h index 6118765260..e80516df05 100644 --- a/app/core/gimp-gui.h +++ b/app/core/gimp-gui.h @@ -35,7 +35,8 @@ struct _GimpGui GObject *handler, GimpMessageSeverity severity, const gchar *domain, - const gchar *message); + const gchar *message, + const gchar *trace); void (* help) (Gimp *gimp, GimpProgress *progress, const gchar *help_domain, @@ -144,7 +145,8 @@ void gimp_show_message (Gimp *gimp, GObject *handler, GimpMessageSeverity severity, const gchar *domain, - const gchar *message); + const gchar *message, + const gchar *trace); void gimp_help (Gimp *gimp, GimpProgress *progress, const gchar *help_domain, diff --git a/app/core/gimp.c b/app/core/gimp.c index ef3c9333dc..c2f1a0449a 100644 --- a/app/core/gimp.c +++ b/app/core/gimp.c @@ -1123,7 +1123,7 @@ gimp_message_valist (Gimp *gimp, message = g_strdup_vprintf (format, args); - gimp_show_message (gimp, handler, severity, NULL, message); + gimp_show_message (gimp, handler, severity, NULL, message, NULL); g_free (message); } @@ -1138,7 +1138,7 @@ gimp_message_literal (Gimp *gimp, g_return_if_fail (handler == NULL || G_IS_OBJECT (handler)); g_return_if_fail (message != NULL); - gimp_show_message (gimp, handler, severity, NULL, message); + gimp_show_message (gimp, handler, severity, NULL, message, NULL); } void diff --git a/app/dialogs/dialogs-constructors.c b/app/dialogs/dialogs-constructors.c index 7ef92fe973..f9abe47193 100644 --- a/app/dialogs/dialogs-constructors.c +++ b/app/dialogs/dialogs-constructors.c @@ -35,6 +35,7 @@ #include "widgets/gimpchanneltreeview.h" #include "widgets/gimpcoloreditor.h" #include "widgets/gimpcolormapeditor.h" +#include "widgets/gimpcriticaldialog.h" #include "widgets/gimpdashboard.h" #include "widgets/gimpdevicestatus.h" #include "widgets/gimpdialogfactory.h" @@ -215,6 +216,15 @@ dialogs_error_get (GimpDialogFactory *factory, return gimp_error_dialog_new (_("GIMP Message")); } +GtkWidget * +dialogs_critical_get (GimpDialogFactory *factory, + GimpContext *context, + GimpUIManager *ui_manager, + gint view_size) +{ + return gimp_critical_dialog_new (_("GIMP critical error")); +} + GtkWidget * dialogs_close_all_get (GimpDialogFactory *factory, GimpContext *context, diff --git a/app/dialogs/dialogs-constructors.h b/app/dialogs/dialogs-constructors.h index bcdfbedcc6..6c86557bd9 100644 --- a/app/dialogs/dialogs-constructors.h +++ b/app/dialogs/dialogs-constructors.h @@ -77,6 +77,10 @@ GtkWidget * dialogs_error_get (GimpDialogFactory *factory, GimpContext *context, GimpUIManager *ui_manager, gint view_size); +GtkWidget * dialogs_critical_get (GimpDialogFactory *factory, + GimpContext *context, + GimpUIManager *ui_manager, + gint view_size); GtkWidget * dialogs_close_all_get (GimpDialogFactory *factory, GimpContext *context, GimpUIManager *ui_manager, diff --git a/app/dialogs/dialogs.c b/app/dialogs/dialogs.c index d817d741be..ee744b868c 100644 --- a/app/dialogs/dialogs.c +++ b/app/dialogs/dialogs.c @@ -286,6 +286,8 @@ static const GimpDialogFactoryEntry entries[] = dialogs_action_search_get, TRUE, TRUE, TRUE), TOPLEVEL ("gimp-error-dialog", dialogs_error_get, TRUE, FALSE, FALSE), + TOPLEVEL ("gimp-critical-dialog", + dialogs_critical_get, TRUE, FALSE, FALSE), TOPLEVEL ("gimp-close-all-dialog", dialogs_close_all_get, TRUE, FALSE, FALSE), TOPLEVEL ("gimp-quit-dialog", diff --git a/app/errors.c b/app/errors.c index e1520f0faf..e25479206b 100644 --- a/app/errors.c +++ b/app/errors.c @@ -21,8 +21,11 @@ #include #include +#include #include +#include #include +#include #ifdef HAVE_UNISTD_H #include #endif @@ -41,6 +44,7 @@ #include #endif +#define MAX_TRACES 3 /* private variables */ @@ -52,23 +56,24 @@ static gchar *full_prog_name = NULL; /* local function prototypes */ -static G_GNUC_NORETURN void gimp_eek (const gchar *reason, - const gchar *message, - gboolean use_handler); +static void gimp_third_party_message_log_func (const gchar *log_domain, + GLogLevelFlags flags, + const gchar *message, + gpointer data); +static void gimp_message_log_func (const gchar *log_domain, + GLogLevelFlags flags, + const gchar *message, + gpointer data); +static void gimp_error_log_func (const gchar *domain, + GLogLevelFlags flags, + const gchar *message, + gpointer data) G_GNUC_NORETURN; -static void gimp_third_party_message_log_func (const gchar *log_domain, - GLogLevelFlags flags, - const gchar *message, - gpointer data); -static void gimp_message_log_func (const gchar *log_domain, - GLogLevelFlags flags, - const gchar *message, - gpointer data); -static void gimp_error_log_func (const gchar *domain, - GLogLevelFlags flags, - const gchar *message, - gpointer data) G_GNUC_NORETURN; +static G_GNUC_NORETURN void gimp_eek (const gchar *reason, + const gchar *message, + gboolean use_handler); +static gchar * gimp_get_stack_trace (void); /* public functions */ @@ -129,7 +134,7 @@ errors_init (Gimp *gimp, for (i = 0; i < G_N_ELEMENTS (log_domains); i++) g_log_set_handler (log_domains[i], - G_LOG_LEVEL_MESSAGE, + G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_CRITICAL, gimp_message_log_func, gimp); g_log_set_handler ("GEGL", @@ -176,7 +181,7 @@ gimp_third_party_message_log_func (const gchar *log_domain, * messages. */ gimp_show_message (gimp, NULL, GIMP_MESSAGE_WARNING, - log_domain, message); + log_domain, message, NULL); } else { @@ -190,17 +195,45 @@ gimp_message_log_func (const gchar *log_domain, const gchar *message, gpointer data) { - Gimp *gimp = data; + static gint n_traces; + GimpMessageSeverity severity = GIMP_MESSAGE_WARNING; + Gimp *gimp = data; + gchar *trace = NULL; + + if (flags & G_LOG_LEVEL_CRITICAL) + { + severity = GIMP_MESSAGE_ERROR; + + if (n_traces < MAX_TRACES) + { + /* Getting debug traces is time-expensive, and worse, some + * critical errors have the bad habit to create more errors + * (the first ones are therefore usually the most useful). + * This is why we keep track of how many times we made traces + * and stop doing them after a while. + * Hence when this happens, critical errors are simply processed as + * lower level errors. + */ + trace = gimp_get_stack_trace (); + n_traces++; + } + } if (gimp) { - gimp_show_message (gimp, NULL, GIMP_MESSAGE_WARNING, NULL, message); + gimp_show_message (gimp, NULL, severity, + NULL, message, trace); } else { g_printerr ("%s: %s\n\n", gimp_filename_to_utf8 (full_prog_name), message); + if (trace) + g_printerr ("Back trace:\n%s\n\n", trace); } + + if (trace) + g_free (trace); } static void @@ -268,3 +301,91 @@ gimp_eek (const gchar *reason, exit (EXIT_FAILURE); } + +static gchar * +gimp_get_stack_trace (void) +{ + gchar *trace = NULL; +#if defined(G_OS_UNIX) + GString *gtrace = NULL; + gchar buffer[256]; + ssize_t read_n; + pid_t pid; + int status; + int out_fd[2]; +#endif + + /* Though we should theoretically ask with GIMP_STACK_TRACE_QUERY, we + * just assume yes right now. TODO: improve this! + */ + if (stack_trace_mode == GIMP_STACK_TRACE_NEVER) + return NULL; + + /* This works only on UNIX systems. On Windows, we'll have to find + * another method, probably with DrMingW. + */ +#if defined(G_OS_UNIX) + if (pipe (out_fd) == -1) + { + return NULL; + } + + /* This is a trick to get the stack trace inside a string. + * GLib's g_on_error_stack_trace() unfortunately writes directly to + * the standard output, which is a very unfortunate implementation. + */ + pid = fork (); + if (pid == 0) + { + /* Child process. */ + + /* XXX I just don't understand why, but somehow the parent process + * doesn't get the output if I don't print something first. I just + * leave this very dirty hack until I figure out what's going on. + */ + printf(" "); + + /* Redirect the debugger output. */ + dup2 (out_fd[1], STDOUT_FILENO); + close (out_fd[0]); + close (out_fd[1]); + g_on_error_stack_trace (full_prog_name); + _exit (0); + } + else if (pid > 0) + { + /* Main process. */ + waitpid (pid, &status, 0); + } + else if (pid == (pid_t) -1) + { + /* No trace can be done. */ + return NULL; + } + + gtrace = g_string_new (""); + + /* It is important to close the writing side of the pipe, otherwise + * the read() will wait forever without getting the information that + * writing is finished. + */ + close (out_fd[1]); + + while ((read_n = read (out_fd[0], buffer, 256)) > 0) + { + g_string_append_len (gtrace, buffer, read_n); + } + close (out_fd[0]); + + if (gtrace) + trace = g_string_free (gtrace, FALSE); + if (trace && strlen (g_strstrip (trace)) == 0) + { + /* Empty strings are the same as no strings. */ + g_free (trace); + trace = NULL; + } +#endif + + return trace; +} diff --git a/app/gui/gui-message.c b/app/gui/gui-message.c index 5ab7608e88..c30cd4c343 100644 --- a/app/gui/gui-message.c +++ b/app/gui/gui-message.c @@ -34,6 +34,7 @@ #include "plug-in/gimpplugin.h" +#include "widgets/gimpcriticaldialog.h" #include "widgets/gimpdialogfactory.h" #include "widgets/gimpdockable.h" #include "widgets/gimperrorconsole.h" @@ -55,6 +56,7 @@ typedef struct Gimp *gimp; gchar *domain; gchar *message; + gchar *trace; GObject *handler; GimpMessageSeverity severity; } GimpLogMessageData; @@ -64,13 +66,19 @@ static gboolean gui_message_idle (gpointer user_data); static gboolean gui_message_error_console (Gimp *gimp, GimpMessageSeverity severity, const gchar *domain, - const gchar *message); + const gchar *message, + const gchar *trace); static gboolean gui_message_error_dialog (Gimp *gimp, GObject *handler, GimpMessageSeverity severity, const gchar *domain, - const gchar *message); + const gchar *message, + const gchar *trace); static void gui_message_console (GimpMessageSeverity severity, + const gchar *domain, + const gchar *message, + const gchar *trace); +static gchar * gui_message_format (GimpMessageSeverity severity, const gchar *domain, const gchar *message); @@ -80,12 +88,13 @@ gui_message (Gimp *gimp, GObject *handler, GimpMessageSeverity severity, const gchar *domain, - const gchar *message) + const gchar *message, + const gchar *trace) { switch (gimp->message_handler) { case GIMP_ERROR_CONSOLE: - if (gui_message_error_console (gimp, severity, domain, message)) + if (gui_message_error_console (gimp, severity, domain, message, trace)) return; gimp->message_handler = GIMP_MESSAGE_BOX; @@ -104,6 +113,7 @@ gui_message (Gimp *gimp, data->gimp = gimp; data->domain = g_strdup (domain); data->message = g_strdup (message); + data->trace = trace ? g_strdup (trace) : NULL; data->handler = handler? g_object_ref (handler) : NULL; data->severity = severity; @@ -112,14 +122,15 @@ gui_message (Gimp *gimp, data, g_free); return; } - if (gui_message_error_dialog (gimp, handler, severity, domain, message)) + if (gui_message_error_dialog (gimp, handler, severity, + domain, message, trace)) return; gimp->message_handler = GIMP_CONSOLE; /* fallthru */ case GIMP_CONSOLE: - gui_message_console (severity, domain, message); + gui_message_console (severity, domain, message, trace); break; } } @@ -133,14 +144,18 @@ gui_message_idle (gpointer user_data) data->handler, data->severity, data->domain, - data->message)) + data->message, + data->trace)) { gui_message_console (data->severity, data->domain, - data->message); + data->message, + data->trace); } g_free (data->domain); g_free (data->message); + if (data->trace) + g_free (data->trace); if (data->handler) g_object_unref (data->handler); @@ -151,10 +166,13 @@ static gboolean gui_message_error_console (Gimp *gimp, GimpMessageSeverity severity, const gchar *domain, - const gchar *message) + const gchar *message, + const gchar *trace) { GtkWidget *dockable; + /* TODO: right now the error console does nothing with the trace. */ + dockable = gimp_dialog_factory_find_widget (gimp_dialog_factory_get_singleton (), "gimp-error-console"); @@ -255,16 +273,59 @@ global_error_dialog (void) FALSE); } +static GtkWidget * +global_critical_dialog (void) +{ + GdkScreen *screen; + gint monitor; + + monitor = gimp_get_monitor_at_pointer (&screen); + + return gimp_dialog_factory_dialog_new (gimp_dialog_factory_get_singleton (), + screen, monitor, + NULL /*ui_manager*/, + "gimp-critical-dialog", -1, + FALSE); +} + static gboolean gui_message_error_dialog (Gimp *gimp, GObject *handler, GimpMessageSeverity severity, const gchar *domain, - const gchar *message) + const gchar *message, + const gchar *trace) { - GtkWidget *dialog; + GtkWidget *dialog; + GtkMessageType type = GTK_MESSAGE_ERROR; - if (GIMP_IS_PROGRESS (handler)) + switch (severity) + { + case GIMP_MESSAGE_INFO: type = GTK_MESSAGE_INFO; break; + case GIMP_MESSAGE_WARNING: type = GTK_MESSAGE_WARNING; break; + case GIMP_MESSAGE_ERROR: type = GTK_MESSAGE_ERROR; break; + } + + if (trace) + { + /* Process differently when traces are included. + * The reason is that this will take significant place, and cannot + * be processed as a progress message or in the global dialog. It + * will require its own dedicated dialog which will encourage + * people to report the bug. + */ + gchar *text; + + dialog = global_critical_dialog (); + text = gui_message_format (severity, domain, message); + gimp_critical_dialog_add (GIMP_CRITICAL_DIALOG (dialog), + text, trace); + g_free (text); + gtk_widget_show (dialog); + + return TRUE; + } + else if (GIMP_IS_PROGRESS (handler)) { /* If there's already an error dialog associated with this * progress, then continue without trying gimp_progress_message(). @@ -278,15 +339,7 @@ gui_message_error_dialog (Gimp *gimp, } else if (GTK_IS_WIDGET (handler)) { - GtkWidget *parent = GTK_WIDGET (handler); - GtkMessageType type = GTK_MESSAGE_ERROR; - - switch (severity) - { - case GIMP_MESSAGE_INFO: type = GTK_MESSAGE_INFO; break; - case GIMP_MESSAGE_WARNING: type = GTK_MESSAGE_WARNING; break; - case GIMP_MESSAGE_ERROR: type = GTK_MESSAGE_ERROR; break; - } + GtkWidget *parent = GTK_WIDGET (handler); dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (parent)), @@ -324,11 +377,31 @@ gui_message_error_dialog (Gimp *gimp, static void gui_message_console (GimpMessageSeverity severity, const gchar *domain, - const gchar *message) + const gchar *message, + const gchar *trace) +{ + gchar *formatted_message; + + formatted_message = gui_message_format (severity, domain, message); + g_printerr ("%s\n\n", formatted_message); + g_free (formatted_message); + + if (trace) + g_printerr ("Stack trace:\n%s\n\n", trace); +} + +static gchar * +gui_message_format (GimpMessageSeverity severity, + const gchar *domain, + const gchar *message) { const gchar *desc = "Message"; + gchar *formatted_message; gimp_enum_get_value (GIMP_TYPE_MESSAGE_SEVERITY, severity, NULL, NULL, &desc, NULL); - g_printerr ("%s-%s: %s\n\n", domain, desc, message); + + formatted_message = g_strdup_printf ("%s-%s: %s", domain, desc, message); + + return formatted_message; } diff --git a/app/gui/gui-message.h b/app/gui/gui-message.h index 45f430018a..805f2669c5 100644 --- a/app/gui/gui-message.h +++ b/app/gui/gui-message.h @@ -23,7 +23,8 @@ void gui_message (Gimp *gimp, GObject *handler, GimpMessageSeverity severity, const gchar *domain, - const gchar *message); + const gchar *message, + const gchar *trace); #endif /* __GUI_VTABLE_H__ */ diff --git a/app/pdb/message-cmds.c b/app/pdb/message-cmds.c index 9df09804c0..7f2184e693 100644 --- a/app/pdb/message-cmds.c +++ b/app/pdb/message-cmds.c @@ -61,7 +61,7 @@ message_invoker (GimpProcedure *procedure, if (gimp->plug_in_manager->current_plug_in) domain = gimp_plug_in_get_undo_desc (gimp->plug_in_manager->current_plug_in); gimp_show_message (gimp, G_OBJECT (progress), GIMP_MESSAGE_WARNING, - domain, message); + domain, message, NULL); } return gimp_procedure_get_return_values (procedure, success, diff --git a/app/version.c b/app/version.c index de439bc3e1..15bc0dbeca 100644 --- a/app/version.c +++ b/app/version.c @@ -38,15 +38,17 @@ #include "gimp-intl.h" -static void -gimp_show_library_version (const gchar *package, - gint build_time_major, - gint build_time_minor, - gint build_time_micro, - gint run_time_major, - gint run_time_minor, - gint run_time_micro) +static gchar * +gimp_library_version (const gchar *package, + gint build_time_major, + gint build_time_minor, + gint build_time_micro, + gint run_time_major, + gint run_time_minor, + gint run_time_micro, + gboolean localized) { + gchar *lib_version; gchar *build_time_version; gchar *run_time_version; @@ -60,91 +62,156 @@ gimp_show_library_version (const gchar *package, run_time_micro); /* show versions of libraries used by GIMP */ - g_print (_("using %s version %s (compiled against version %s)"), - package, run_time_version, build_time_version); - g_print ("\n"); - + lib_version = g_strdup_printf (localized ? + _("using %s version %s (compiled against version %s)") : + "using %s version %s (compiled against version %s)", + package, run_time_version, build_time_version); g_free (run_time_version); g_free (build_time_version); + + return lib_version; } -static void -gimp_show_library_versions (void) +static gchar * +gimp_library_versions (gboolean localized) { - gint gegl_major_version; - gint gegl_minor_version; - gint gegl_micro_version; + gchar *lib_versions; + gchar *lib_version; + gchar *temp; + gint gegl_major_version; + gint gegl_minor_version; + gint gegl_micro_version; gegl_get_version (&gegl_major_version, &gegl_minor_version, &gegl_micro_version); - gimp_show_library_version ("GEGL", - GEGL_MAJOR_VERSION, - GEGL_MINOR_VERSION, - GEGL_MICRO_VERSION, - gegl_major_version, - gegl_minor_version, - gegl_micro_version); + lib_versions = gimp_library_version ("GEGL", + GEGL_MAJOR_VERSION, + GEGL_MINOR_VERSION, + GEGL_MICRO_VERSION, + gegl_major_version, + gegl_minor_version, + gegl_micro_version, + localized); - gimp_show_library_version ("GLib", - GLIB_MAJOR_VERSION, - GLIB_MINOR_VERSION, - GLIB_MICRO_VERSION, - glib_major_version, - glib_minor_version, - glib_micro_version); + lib_version = gimp_library_version ("GLib", + GLIB_MAJOR_VERSION, + GLIB_MINOR_VERSION, + GLIB_MICRO_VERSION, + glib_major_version, + glib_minor_version, + glib_micro_version, + localized); + temp = g_strdup_printf ("%s\n%s", lib_versions, lib_version); + g_free (lib_versions); + g_free (lib_version); + lib_versions = temp; - gimp_show_library_version ("GdkPixbuf", - GDK_PIXBUF_MAJOR, - GDK_PIXBUF_MINOR, - GDK_PIXBUF_MICRO, - gdk_pixbuf_major_version, - gdk_pixbuf_minor_version, - gdk_pixbuf_micro_version); + lib_version = gimp_library_version ("GdkPixbuf", + GDK_PIXBUF_MAJOR, + GDK_PIXBUF_MINOR, + GDK_PIXBUF_MICRO, + gdk_pixbuf_major_version, + gdk_pixbuf_minor_version, + gdk_pixbuf_micro_version, + localized); + temp = g_strdup_printf ("%s\n%s", lib_versions, lib_version); + g_free (lib_versions); + g_free (lib_version); + lib_versions = temp; #ifndef GIMP_CONSOLE_COMPILATION - gimp_show_library_version ("GTK+", - GTK_MAJOR_VERSION, - GTK_MINOR_VERSION, - GTK_MICRO_VERSION, - gtk_major_version, - gtk_minor_version, - gtk_micro_version); + lib_version = gimp_library_version ("GTK+", + GTK_MAJOR_VERSION, + GTK_MINOR_VERSION, + GTK_MICRO_VERSION, + gtk_major_version, + gtk_minor_version, + gtk_micro_version, + localized); + temp = g_strdup_printf ("%s\n%s", lib_versions, lib_version); + g_free (lib_versions); + g_free (lib_version); + lib_versions = temp; #endif - gimp_show_library_version ("Pango", - PANGO_VERSION_MAJOR, - PANGO_VERSION_MINOR, - PANGO_VERSION_MICRO, - pango_version () / 100 / 100, - pango_version () / 100 % 100, - pango_version () % 100); + lib_version = gimp_library_version ("Pango", + PANGO_VERSION_MAJOR, + PANGO_VERSION_MINOR, + PANGO_VERSION_MICRO, + pango_version () / 100 / 100, + pango_version () / 100 % 100, + pango_version () % 100, + localized); + temp = g_strdup_printf ("%s\n%s", lib_versions, lib_version); + g_free (lib_versions); + g_free (lib_version); + lib_versions = temp; - gimp_show_library_version ("Fontconfig", - FC_MAJOR, FC_MINOR, FC_REVISION, - FcGetVersion () / 100 / 100, - FcGetVersion () / 100 % 100, - FcGetVersion () % 100); + lib_version = gimp_library_version ("Fontconfig", + FC_MAJOR, FC_MINOR, FC_REVISION, + FcGetVersion () / 100 / 100, + FcGetVersion () / 100 % 100, + FcGetVersion () % 100, + localized); + temp = g_strdup_printf ("%s\n%s", lib_versions, lib_version); + g_free (lib_versions); + g_free (lib_version); + lib_versions = temp; - g_print (_("using %s version %s (compiled against version %s)"), - "Cairo", cairo_version_string (), CAIRO_VERSION_STRING); - g_print ("\n"); + lib_version = g_strdup_printf (localized ? + _("using %s version %s (compiled against version %s)") : + "using %s version %s (compiled against version %s)", + "Cairo", cairo_version_string (), CAIRO_VERSION_STRING); + temp = g_strdup_printf ("%s\n%s\n", lib_versions, lib_version); + g_free (lib_versions); + g_free (lib_version); + lib_versions = temp; + + return lib_versions; } void gimp_version_show (gboolean be_verbose) { - g_print (_("%s version %s"), GIMP_NAME, GIMP_VERSION); - g_print ("\n"); + gchar *version = gimp_version (be_verbose, TRUE); + + g_print ("%s", version); + + g_free (version); +} + +gchar * +gimp_version (gboolean be_verbose, + gboolean localized) +{ + gchar *version; + gchar *temp; + + version = g_strdup_printf (localized ? _("%s version %s") : "%s version %s", + GIMP_NAME, GIMP_VERSION);; + temp = g_strconcat (version, "\n", NULL); + g_free (version); + version = temp; if (be_verbose) { - g_print ("git-describe: %s", GIMP_GIT_VERSION); - g_print ("\n"); - g_print ("C compiler:\n%s", CC_VERSION); + gchar *verbose_info; + gchar *lib_versions; - g_print ("\n"); - gimp_show_library_versions (); + lib_versions = gimp_library_versions (localized); + verbose_info = g_strdup_printf ("git-describe: %s\n" + "C compiler:\n%s\n%s", + GIMP_GIT_VERSION, CC_VERSION, + lib_versions); + g_free (lib_versions); + + temp = g_strconcat (version, verbose_info, NULL); + g_free (version); + g_free (verbose_info); + version = temp; } + + return version; } diff --git a/app/version.h b/app/version.h index 60ca9d0032..9f5f61ac3e 100644 --- a/app/version.h +++ b/app/version.h @@ -19,12 +19,10 @@ #define __VERSION_H__ -#ifndef GIMP_APP_GLUE_COMPILATION -#error You must not #include "version.h" from an app/ subdir -#endif - -void gimp_version_show (gboolean be_verbose); +void gimp_version_show (gboolean be_verbose); +gchar * gimp_version (gboolean be_verbose, + gboolean localized); #endif /* __VERSION_H__ */ diff --git a/app/widgets/Makefile.am b/app/widgets/Makefile.am index bb6546ce43..d7013ef55f 100644 --- a/app/widgets/Makefile.am +++ b/app/widgets/Makefile.am @@ -116,6 +116,8 @@ libappwidgets_a_sources = \ gimpcontrollermouse.h \ gimpcontrollerwheel.c \ gimpcontrollerwheel.h \ + gimpcriticaldialog.c \ + gimpcriticaldialog.h \ gimpcursor.c \ gimpcursor.h \ gimpcurveview.c \ diff --git a/app/widgets/gimpcriticaldialog.c b/app/widgets/gimpcriticaldialog.c new file mode 100644 index 0000000000..80cd56fbf2 --- /dev/null +++ b/app/widgets/gimpcriticaldialog.c @@ -0,0 +1,262 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcriticaldialog.c + * Copyright (C) 2018 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * 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, see . + */ + +#include "config.h" + +#include + +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gimpcriticaldialog.h" + +#include "version.h" + +#include "gimp-intl.h" + +#define GIMP_CRITICAL_RESPONSE_CLIPBOARD 1 +#define GIMP_CRITICAL_RESPONSE_URL 2 + + +static void gimp_critical_dialog_response (GtkDialog *dialog, + gint response_id); + +G_DEFINE_TYPE (GimpCriticalDialog, gimp_critical_dialog, GIMP_TYPE_DIALOG) + +#define parent_class gimp_critical_dialog_parent_class + + +static void +gimp_critical_dialog_class_init (GimpCriticalDialogClass *klass) +{ + GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass); + + dialog_class->response = gimp_critical_dialog_response; +} + +static void +gimp_critical_dialog_init (GimpCriticalDialog *dialog) +{ + PangoAttrList *attrs; + PangoAttribute *attr; + gchar *text; + gchar *version; + GtkWidget *widget; + GtkTextBuffer *buffer; + const gchar *button1 = _("Copy bug information"); + const gchar *button2 = _("Open bug tracker"); + + gtk_window_set_role (GTK_WINDOW (dialog), "gimp-critical"); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + button1, GIMP_CRITICAL_RESPONSE_CLIPBOARD, + button2, GIMP_CRITICAL_RESPONSE_URL, + _("_Close"), GTK_RESPONSE_CLOSE, + NULL); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE); + gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE); + + dialog->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + dialog->vbox, TRUE, TRUE, 0); + gtk_widget_show (dialog->vbox); + + /* The error label. */ + dialog->label = gtk_label_new (NULL); + gtk_label_set_justify (GTK_LABEL (dialog->label), GTK_JUSTIFY_CENTER); + gtk_label_set_selectable (GTK_LABEL (dialog->label), TRUE); + gtk_box_pack_start (GTK_BOX (dialog->vbox), + dialog->label, FALSE, FALSE, 0); + + attrs = pango_attr_list_new (); + attr = pango_attr_weight_new (PANGO_WEIGHT_SEMIBOLD); + pango_attr_list_insert (attrs, attr); + gtk_label_set_attributes (GTK_LABEL (dialog->label), attrs); + pango_attr_list_unref (attrs); + + gtk_widget_show (dialog->label); + + /* Generic "report a bug" instructions. */ + text = g_strdup_printf ("%s\n" + " \xe2\x80\xa2 %s %s\n" + " \xe2\x80\xa2 %s %s\n" + " \xe2\x80\xa2 %s \n" + " \xe2\x80\xa2 %s \n" + " \xe2\x80\xa2 %s \n" + " \xe2\x80\xa2 %s\n\n" + "%s", + _("To help us improve GIMP, you can report the bug with " + "these simple steps:"), + _("Copy the bug information to clipboard by clicking: "), + button1, + _("Open our bug tracker in browser by clicking: "), + button2, + _("Create a login if you don't have one yet."), + _("Paste the clipboard text in a new bug report."), + _("Add relevant information in English in the bug report " + "explaining what you were doing when this error occurred."), + _("This error may have left GIMP in an inconsistent state. " + "It is advised to save your work and restart GIMP."), + _("Note: you can also close the dialog directly but reporting " + "bugs is the best way to make your software awesome.")); + widget = gtk_label_new (text); + g_free (text); + gtk_label_set_selectable (GTK_LABEL (widget), TRUE); + gtk_box_pack_start (GTK_BOX (dialog->vbox), widget, FALSE, FALSE, 0); + gtk_widget_show (widget); + + /* Bug details for developers. */ + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_box_pack_start (GTK_BOX (dialog->vbox), widget, TRUE, TRUE, 0); + gtk_widget_show (widget); + + buffer = gtk_text_buffer_new (NULL); + version = gimp_version (TRUE, FALSE); + gtk_text_buffer_set_text (buffer, version, -1); + g_free (version); + + dialog->details = gtk_text_view_new_with_buffer (buffer); + g_object_unref (buffer); + gtk_text_view_set_editable (GTK_TEXT_VIEW (dialog->details), FALSE); + gtk_widget_show (dialog->details); + gtk_container_add (GTK_CONTAINER (widget), dialog->details); +} + +static void +gimp_critical_dialog_response (GtkDialog *dialog, + gint response_id) +{ + GimpCriticalDialog *critical = GIMP_CRITICAL_DIALOG (dialog); + + switch (response_id) + { + case GIMP_CRITICAL_RESPONSE_CLIPBOARD: + { + GtkClipboard *clipboard; + + clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (), + GDK_SELECTION_CLIPBOARD); + if (clipboard) + { + GtkTextBuffer *buffer; + gchar *text; + GtkTextIter start; + GtkTextIter end; + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (critical->details)); + gtk_text_buffer_get_iter_at_offset (buffer, &start, 0); + gtk_text_buffer_get_iter_at_offset (buffer, &end, -1); + text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); + gtk_clipboard_set_text (clipboard, text, -1); + g_free (text); + } + } + break; + case GIMP_CRITICAL_RESPONSE_URL: + { + const gchar *url; + + /* XXX Ideally I'd find a way to prefill the bug report + * through the URL or with POST data. But I could not find + * any. Anyway since we may soon ditch bugzilla to follow + * GNOME infrastructure changes, I don't want to waste too + * much time digging into it. + */ + url = "https://bugzilla.gnome.org/enter_bug.cgi?product=GIMP"; + gtk_show_uri (gdk_screen_get_default (), + url, + gtk_get_current_event_time(), + NULL); + } + break; + case GTK_RESPONSE_DELETE_EVENT: + case GTK_RESPONSE_CLOSE: + default: + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + } +} + +/* public functions */ + +GtkWidget * +gimp_critical_dialog_new (const gchar *title) +{ + g_return_val_if_fail (title != NULL, NULL); + + return g_object_new (GIMP_TYPE_CRITICAL_DIALOG, + "title", title, + NULL); +} + +void +gimp_critical_dialog_add (GimpCriticalDialog *dialog, + const gchar *message, + const gchar *trace) +{ + GtkTextBuffer *buffer; + GtkTextIter end; + gchar *text; + + if (! GIMP_IS_CRITICAL_DIALOG (dialog) || ! message || ! trace) + { + /* This is a bit hackish. We usually should use + * g_return_if_fail(). But I don't want to end up in a critical + * recursing loop if our code had bugs. We would crash GIMP with + * a CRITICAL which would otherwise not have necessarily ended up + * in a crash. + */ + return; + } + + /* The user text, which should be localized. */ + if (! gtk_label_get_text (GTK_LABEL (dialog->label)) || + strlen (gtk_label_get_text (GTK_LABEL (dialog->label))) == 0) + { + /* First critical error. Let's just display it. */ + text = g_strdup_printf (_("GIMP encountered a critical error: %s"), + message); + } + else + { + /* Let's not display all errors. They will be in the bug report + * part anyway. + */ + text = g_strdup_printf (_("GIMP encountered several critical errors!")); + } + gtk_label_set_text (GTK_LABEL (dialog->label), + text); + g_free (text); + + /* The details text is untranslated on purpose. This is the message + * meant to go to clipboard for the bug report. It has to be in + * English. + */ + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (dialog->details)); + gtk_text_buffer_get_iter_at_offset (buffer, &end, -1); + text = g_strdup_printf ("\n\n> %s\n\nStack trace:\n%s", message, trace); + gtk_text_buffer_insert (buffer, &end, text, -1); + g_free (text); +} diff --git a/app/widgets/gimpcriticaldialog.h b/app/widgets/gimpcriticaldialog.h new file mode 100644 index 0000000000..070fc329aa --- /dev/null +++ b/app/widgets/gimpcriticaldialog.h @@ -0,0 +1,64 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcriticaldialog.h + * Copyright (C) 2018 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * 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, see . + */ + +#ifndef __GIMP_CRITICAL_DIALOG_H__ +#define __GIMP_CRITICAL_DIALOG_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_CRITICAL_DIALOG (gimp_critical_dialog_get_type ()) +#define GIMP_CRITICAL_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CRITICAL_DIALOG, GimpCriticalDialog)) +#define GIMP_CRITICAL_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CRITICAL_DIALOG, GimpCriticalDialogClass)) +#define GIMP_IS_CRITICAL_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CRITICAL_DIALOG)) +#define GIMP_IS_CRITICAL_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CRITICAL_DIALOG)) +#define GIMP_CRITICAL_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CRITICAL_DIALOG, GimpCriticalDialogClass)) + + +typedef struct _GimpCriticalDialogClass GimpCriticalDialogClass; + +struct _GimpCriticalDialog +{ + GimpDialog parent_instance; + + GtkWidget *vbox; + + GtkWidget *label; + GtkWidget *details; +}; + +struct _GimpCriticalDialogClass +{ + GimpDialogClass parent_class; +}; + + +GType gimp_critical_dialog_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_critical_dialog_new (const gchar *title); +void gimp_critical_dialog_add (GimpCriticalDialog *dialog, + const gchar *message, + const gchar *trace); + + + +G_END_DECLS + +#endif /* __GIMP_CRITICAL_DIALOG_H__ */ diff --git a/app/widgets/widgets-types.h b/app/widgets/widgets-types.h index 30f79c5c2f..b82adb060c 100644 --- a/app/widgets/widgets-types.h +++ b/app/widgets/widgets-types.h @@ -145,6 +145,7 @@ typedef struct _GimpSaveDialog GimpSaveDialog; /* misc dialogs */ typedef struct _GimpColorDialog GimpColorDialog; +typedef struct _GimpCriticalDialog GimpCriticalDialog; typedef struct _GimpErrorDialog GimpErrorDialog; typedef struct _GimpMessageDialog GimpMessageDialog; typedef struct _GimpProgressDialog GimpProgressDialog;