From 9fdf35550bf592ec6235ec553d49fa467980b41d Mon Sep 17 00:00:00 2001 From: Jehan Date: Tue, 23 Jan 2018 03:38:46 +0100 Subject: [PATCH] app: new error dialog to backtrace and encourage people to report bugs. GIMP will now try to get a backtrace (on Unix machines only for now, using g_on_error_stack_trace(); for Windows, we will likely have to look into DrMinGW). This is now applied to CRITICAL errors only, which usually means major bugs but are currently mostly hidden unless you run GIMP in terminal. We limit to 3 backtraces, because many CRITICAL typically get into domino effect and cause more CRITICALs (for instance when a g_return*_if_fail() returns too early). --- app/core/gimp-gui.c | 11 +- app/core/gimp-gui.h | 6 +- app/core/gimp.c | 4 +- app/dialogs/dialogs-constructors.c | 10 ++ app/dialogs/dialogs-constructors.h | 4 + app/dialogs/dialogs.c | 2 + app/errors.c | 159 ++++++++++++++--- app/gui/gui-message.c | 119 ++++++++++--- app/gui/gui-message.h | 3 +- app/pdb/message-cmds.c | 2 +- app/version.c | 201 ++++++++++++++-------- app/version.h | 8 +- app/widgets/Makefile.am | 2 + app/widgets/gimpcriticaldialog.c | 262 +++++++++++++++++++++++++++++ app/widgets/gimpcriticaldialog.h | 64 +++++++ app/widgets/widgets-types.h | 1 + 16 files changed, 734 insertions(+), 124 deletions(-) create mode 100644 app/widgets/gimpcriticaldialog.c create mode 100644 app/widgets/gimpcriticaldialog.h 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;