[gimp/wip/bug792787-debug-stacktrace-GUI: 1/3] app: new error dialog to backtrace and encourage people to report bugs.



commit 991cf702edc656aa244afe74c6209fd097ef2fb2
Author: Jehan <jehan girinstud io>
Date:   Tue Jan 23 03:38:46 2018 +0100

    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 |    9 ++
 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                      |  211 +++++++++++++++++++----------
 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, 738 insertions(+), 129 deletions(-)
---
diff --git a/app/core/gimp-gui.c b/app/core/gimp-gui.c
index 189910f..4c3842b 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 6118765..e80516d 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 ef3c933..c2f1a04 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 7ef92fe..9438311 100644
--- a/app/dialogs/dialogs-constructors.c
+++ b/app/dialogs/dialogs-constructors.c
@@ -216,6 +216,15 @@ dialogs_error_get (GimpDialogFactory *factory,
 }
 
 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,
                        GimpUIManager     *ui_manager,
diff --git a/app/dialogs/dialogs-constructors.h b/app/dialogs/dialogs-constructors.h
index bcdfbed..6c86557 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 d817d74..ee744b8 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 e1520f0..e254792 100644
--- a/app/errors.c
+++ b/app/errors.c
@@ -21,8 +21,11 @@
 
 #include <signal.h>
 #include <stdarg.h>
+#include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/types.h>
+#include <sys/wait.h>
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
@@ -41,6 +44,7 @@
 #include <windows.h>
 #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 5ab7608..c30cd4c 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,14 +66,20 @@ 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;
+
+  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;
 
-  if (GIMP_IS_PROGRESS (handler))
+      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 45f4300..805f266 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 9df0980..7f2184e 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 de439bc..15bc0db 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);
-
-  gimp_show_library_version ("GLib",
-                             GLIB_MAJOR_VERSION,
-                             GLIB_MINOR_VERSION,
-                             GLIB_MICRO_VERSION,
-                             glib_major_version,
-                             glib_minor_version,
-                             glib_micro_version);
-
-  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_versions = gimp_library_version ("GEGL",
+                                       GEGL_MAJOR_VERSION,
+                                       GEGL_MINOR_VERSION,
+                                       GEGL_MICRO_VERSION,
+                                       gegl_major_version,
+                                       gegl_minor_version,
+                                       gegl_micro_version,
+                                       localized);
+
+  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;
+
+  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);
-
-  gimp_show_library_version ("Fontconfig",
-                             FC_MAJOR, FC_MINOR, FC_REVISION,
-                             FcGetVersion () / 100 / 100,
-                             FcGetVersion () / 100 % 100,
-                             FcGetVersion () % 100);
-
-  g_print (_("using %s version %s (compiled against version %s)"),
-           "Cairo", cairo_version_string (), CAIRO_VERSION_STRING);
-  g_print ("\n");
+  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;
+
+  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;
+
+  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);
-
-      g_print ("\n");
-      gimp_show_library_versions ();
+      gchar *verbose_info;
+      gchar *lib_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 60ca9d0..9f5f61a 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 bb6546c..d7013ef 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 0000000..80cd56f
--- /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 <jehan gimp org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 0000000..070fc32
--- /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 <jehan gimp org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 30f79c5..b82adb0 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;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]