1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimpcriticaldialog.c
5  * Copyright (C) 2018  Jehan <jehan@gimp.org>
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  */
20 
21 /*
22  * This widget is particular that I want to be able to use it
23  * internally but also from an alternate tool (gimp-debug-tool). It
24  * means that the implementation must stay as generic glib/GTK+ as
25  * possible.
26  */
27 
28 #include "config.h"
29 
30 #include <string.h>
31 
32 #include <gtk/gtk.h>
33 #include <gegl.h>
34 
35 #ifdef PLATFORM_OSX
36 #import <Cocoa/Cocoa.h>
37 #endif
38 
39 #ifdef G_OS_WIN32
40 #undef DATADIR
41 #include <windows.h>
42 #endif
43 
44 #include "gimpcriticaldialog.h"
45 
46 #include "gimp-intl.h"
47 #include "gimp-version.h"
48 
49 
50 #define GIMP_CRITICAL_RESPONSE_CLIPBOARD 1
51 #define GIMP_CRITICAL_RESPONSE_URL       2
52 #define GIMP_CRITICAL_RESPONSE_RESTART   3
53 #define GIMP_CRITICAL_RESPONSE_DOWNLOAD  4
54 
55 #define BUTTON1_TEXT _("Copy Bug Information")
56 #define BUTTON2_TEXT _("Open Bug Tracker")
57 
58 enum
59 {
60   PROP_0,
61   PROP_LAST_VERSION,
62   PROP_RELEASE_DATE
63 };
64 
65 static void     gimp_critical_dialog_constructed  (GObject      *object);
66 static void     gimp_critical_dialog_finalize     (GObject      *object);
67 static void     gimp_critical_dialog_set_property (GObject      *object,
68                                                    guint         property_id,
69                                                    const GValue *value,
70                                                    GParamSpec   *pspec);
71 static void     gimp_critical_dialog_response     (GtkDialog    *dialog,
72                                                    gint          response_id);
73 
74 static void     gimp_critical_dialog_copy_info    (GimpCriticalDialog *dialog);
75 static gboolean browser_open_url                  (const gchar  *url,
76                                                    GError      **error);
77 
78 
G_DEFINE_TYPE(GimpCriticalDialog,gimp_critical_dialog,GTK_TYPE_DIALOG)79 G_DEFINE_TYPE (GimpCriticalDialog, gimp_critical_dialog, GTK_TYPE_DIALOG)
80 
81 #define parent_class gimp_critical_dialog_parent_class
82 
83 
84 static void
85 gimp_critical_dialog_class_init (GimpCriticalDialogClass *klass)
86 {
87   GObjectClass   *object_class = G_OBJECT_CLASS (klass);
88   GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass);
89 
90   object_class->constructed  = gimp_critical_dialog_constructed;
91   object_class->finalize     = gimp_critical_dialog_finalize;
92   object_class->set_property = gimp_critical_dialog_set_property;
93 
94   dialog_class->response = gimp_critical_dialog_response;
95 
96   g_object_class_install_property (object_class, PROP_LAST_VERSION,
97                                    g_param_spec_string ("last-version",
98                                                         NULL, NULL, NULL,
99                                                         G_PARAM_WRITABLE |
100                                                         G_PARAM_CONSTRUCT_ONLY));
101   g_object_class_install_property (object_class, PROP_RELEASE_DATE,
102                                    g_param_spec_string ("release-date",
103                                                         NULL, NULL, NULL,
104                                                         G_PARAM_WRITABLE |
105                                                         G_PARAM_CONSTRUCT_ONLY));
106 }
107 
108 static void
gimp_critical_dialog_init(GimpCriticalDialog * dialog)109 gimp_critical_dialog_init (GimpCriticalDialog *dialog)
110 {
111   PangoAttrList  *attrs;
112   PangoAttribute *attr;
113 
114   gtk_window_set_role (GTK_WINDOW (dialog), "gimp-critical");
115 
116   gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE);
117   gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE);
118 
119   dialog->main_vbox = gtk_vbox_new (FALSE, 6);
120   gtk_container_set_border_width (GTK_CONTAINER (dialog->main_vbox), 6);
121   gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
122                       dialog->main_vbox, TRUE, TRUE, 0);
123   gtk_widget_show (dialog->main_vbox);
124 
125   /* The error label. */
126   dialog->top_label = gtk_label_new (NULL);
127   gtk_misc_set_alignment (GTK_MISC (dialog->top_label), 0.0, 0.5);
128   gtk_label_set_ellipsize (GTK_LABEL (dialog->top_label), PANGO_ELLIPSIZE_END);
129   gtk_label_set_selectable (GTK_LABEL (dialog->top_label), TRUE);
130   gtk_box_pack_start (GTK_BOX (dialog->main_vbox), dialog->top_label,
131                       FALSE, FALSE, 0);
132 
133   attrs = pango_attr_list_new ();
134   attr  = pango_attr_weight_new (PANGO_WEIGHT_SEMIBOLD);
135   pango_attr_list_insert (attrs, attr);
136   gtk_label_set_attributes (GTK_LABEL (dialog->top_label), attrs);
137   pango_attr_list_unref (attrs);
138 
139   gtk_widget_show (dialog->top_label);
140 
141   dialog->center_label = gtk_label_new (NULL);
142 
143   gtk_misc_set_alignment (GTK_MISC (dialog->center_label), 0.0, 0.5);
144   gtk_label_set_selectable (GTK_LABEL (dialog->center_label), TRUE);
145   gtk_box_pack_start (GTK_BOX (dialog->main_vbox), dialog->center_label,
146                       FALSE, FALSE, 0);
147   gtk_widget_show (dialog->center_label);
148 
149   dialog->bottom_label = gtk_label_new (NULL);
150   gtk_misc_set_alignment (GTK_MISC (dialog->bottom_label), 0.0, 0.5);
151   gtk_box_pack_start (GTK_BOX (dialog->main_vbox), dialog->bottom_label, FALSE, FALSE, 0);
152 
153   attrs = pango_attr_list_new ();
154   attr  = pango_attr_style_new (PANGO_STYLE_ITALIC);
155   pango_attr_list_insert (attrs, attr);
156   gtk_label_set_attributes (GTK_LABEL (dialog->bottom_label), attrs);
157   pango_attr_list_unref (attrs);
158   gtk_widget_show (dialog->bottom_label);
159 
160   dialog->pid      = 0;
161   dialog->program  = NULL;
162 }
163 
164 static void
gimp_critical_dialog_constructed(GObject * object)165 gimp_critical_dialog_constructed (GObject *object)
166 {
167   GimpCriticalDialog *dialog = GIMP_CRITICAL_DIALOG (object);
168   GtkWidget          *scrolled;
169   GtkTextBuffer      *buffer;
170   gchar              *version;
171   gchar              *text;
172 
173   /* Bug details for developers. */
174   scrolled = gtk_scrolled_window_new (NULL, NULL);
175   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
176                                        GTK_SHADOW_IN);
177   gtk_widget_set_size_request (scrolled, -1, 200);
178 
179   if (dialog->last_version)
180     {
181       GtkWidget *expander;
182       GtkWidget *vbox;
183       GtkWidget *button;
184 
185       expander = gtk_expander_new (_("See bug details"));
186       gtk_box_pack_start (GTK_BOX (dialog->main_vbox), expander, TRUE, TRUE, 0);
187       gtk_widget_show (expander);
188 
189       vbox = gtk_vbox_new (FALSE, 4);
190       gtk_container_add (GTK_CONTAINER (expander), vbox);
191       gtk_widget_show (vbox);
192 
193       gtk_box_pack_start (GTK_BOX (vbox), scrolled, TRUE, TRUE, 0);
194       gtk_widget_show (scrolled);
195 
196       button = gtk_button_new_with_label (BUTTON1_TEXT);
197       g_signal_connect_swapped (button, "clicked",
198                                 G_CALLBACK (gimp_critical_dialog_copy_info),
199                                 dialog);
200       gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
201       gtk_widget_show (button);
202 
203       gtk_dialog_add_buttons (GTK_DIALOG (dialog),
204                               _("Go to _Download page"), GIMP_CRITICAL_RESPONSE_DOWNLOAD,
205                               _("_Close"),               GTK_RESPONSE_CLOSE,
206                               NULL);
207 
208       /* Recommend an update. */
209       text = g_strdup_printf (_("A new version of GIMP (%s) was released on %s.\n"
210                                 "It is recommended to update."),
211                               dialog->last_version, dialog->release_date);
212       gtk_label_set_text (GTK_LABEL (dialog->center_label), text);
213       g_free (text);
214 
215       text = _("You are running an unsupported version!");
216       gtk_label_set_text (GTK_LABEL (dialog->bottom_label), text);
217     }
218   else
219     {
220       /* Pack directly (and well visible) the bug details. */
221       gtk_box_pack_start (GTK_BOX (dialog->main_vbox), scrolled, TRUE, TRUE, 0);
222       gtk_widget_show (scrolled);
223 
224       gtk_dialog_add_buttons (GTK_DIALOG (dialog),
225                               BUTTON1_TEXT, GIMP_CRITICAL_RESPONSE_CLIPBOARD,
226                               BUTTON2_TEXT, GIMP_CRITICAL_RESPONSE_URL,
227                               _("_Close"),  GTK_RESPONSE_CLOSE,
228                               NULL);
229 
230       /* Generic "report a bug" instructions. */
231       text = g_strdup_printf ("%s\n"
232                               " \xe2\x80\xa2 %s %s\n"
233                               " \xe2\x80\xa2 %s %s\n"
234                               " \xe2\x80\xa2 %s\n"
235                               " \xe2\x80\xa2 %s\n"
236                               " \xe2\x80\xa2 %s\n"
237                               " \xe2\x80\xa2 %s",
238                               _("To help us improve GIMP, you can report the bug with "
239                                 "these simple steps:"),
240                               _("Copy the bug information to the clipboard by clicking: "),
241                               BUTTON1_TEXT,
242                               _("Open our bug tracker in the browser by clicking: "),
243                               BUTTON2_TEXT,
244                               _("Create a login if you don't have one yet."),
245                               _("Paste the clipboard text in a new bug report."),
246                               _("Add relevant information in English in the bug report "
247                                 "explaining what you were doing when this error occurred."),
248                               _("This error may have left GIMP in an inconsistent state. "
249                                 "It is advised to save your work and restart GIMP."));
250       gtk_label_set_text (GTK_LABEL (dialog->center_label), text);
251       g_free (text);
252 
253       text = _("You can also close the dialog directly but "
254                "reporting bugs is the best way to make your "
255                "software awesome.");
256       gtk_label_set_text (GTK_LABEL (dialog->bottom_label), text);
257     }
258 
259   buffer = gtk_text_buffer_new (NULL);
260   version = gimp_version (TRUE, FALSE);
261   text = g_strdup_printf ("<!-- %s -->\n\n\n```\n%s\n```",
262                           _("Copy-paste this whole debug data to report to developers"),
263                           version);
264   gtk_text_buffer_set_text (buffer, text, -1);
265   g_free (version);
266   g_free (text);
267 
268   dialog->details = gtk_text_view_new_with_buffer (buffer);
269   g_object_unref (buffer);
270   gtk_text_view_set_editable (GTK_TEXT_VIEW (dialog->details), FALSE);
271   gtk_widget_show (dialog->details);
272   gtk_container_add (GTK_CONTAINER (scrolled), dialog->details);
273 }
274 
275 static void
gimp_critical_dialog_finalize(GObject * object)276 gimp_critical_dialog_finalize (GObject *object)
277 {
278   GimpCriticalDialog *dialog = GIMP_CRITICAL_DIALOG (object);
279 
280   if (dialog->program)
281     g_free (dialog->program);
282   if (dialog->last_version)
283     g_free (dialog->last_version);
284   if (dialog->release_date)
285     g_free (dialog->release_date);
286 
287   G_OBJECT_CLASS (parent_class)->finalize (object);
288 }
289 
290 static void
gimp_critical_dialog_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)291 gimp_critical_dialog_set_property (GObject      *object,
292                                    guint         property_id,
293                                    const GValue *value,
294                                    GParamSpec   *pspec)
295 {
296   GimpCriticalDialog *dialog = GIMP_CRITICAL_DIALOG (object);
297 
298   switch (property_id)
299     {
300     case PROP_LAST_VERSION:
301       dialog->last_version = g_value_dup_string (value);
302       break;
303     case PROP_RELEASE_DATE:
304       dialog->release_date = g_value_dup_string (value);
305       break;
306 
307     default:
308       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
309       break;
310     }
311 }
312 
313 static void
gimp_critical_dialog_copy_info(GimpCriticalDialog * dialog)314 gimp_critical_dialog_copy_info (GimpCriticalDialog *dialog)
315 {
316   GtkClipboard *clipboard;
317 
318   clipboard = gtk_clipboard_get_for_display (gdk_display_get_default (),
319                                              GDK_SELECTION_CLIPBOARD);
320   if (clipboard)
321     {
322       GtkTextBuffer *buffer;
323       gchar         *text;
324       GtkTextIter    start;
325       GtkTextIter    end;
326 
327       buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (dialog->details));
328       gtk_text_buffer_get_iter_at_offset (buffer, &start, 0);
329       gtk_text_buffer_get_iter_at_offset (buffer, &end, -1);
330       text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
331       gtk_clipboard_set_text (clipboard, text, -1);
332       g_free (text);
333     }
334 }
335 
336 /* XXX This is taken straight from plug-ins/common/web-browser.c
337  *
338  * This really sucks but this class also needs to be called by
339  * tools/gimp-debug-tool.c as a separate process and therefore cannot
340  * make use of the PDB. Anyway shouldn't we just move this as a utils
341  * function?  Why does such basic feature as opening a URL in a
342  * cross-platform way need to be a plug-in?
343  */
344 static gboolean
browser_open_url(const gchar * url,GError ** error)345 browser_open_url (const gchar  *url,
346                   GError      **error)
347 {
348 #ifdef G_OS_WIN32
349 
350   HINSTANCE hinst = ShellExecute (GetDesktopWindow(),
351                                   "open", url, NULL, NULL, SW_SHOW);
352 
353   if ((gint) hinst <= 32)
354     {
355       const gchar *err;
356 
357       switch ((gint) hinst)
358         {
359           case 0 :
360             err = _("The operating system is out of memory or resources.");
361             break;
362           case ERROR_FILE_NOT_FOUND :
363             err = _("The specified file was not found.");
364             break;
365           case ERROR_PATH_NOT_FOUND :
366             err = _("The specified path was not found.");
367             break;
368           case ERROR_BAD_FORMAT :
369             err = _("The .exe file is invalid (non-Microsoft Win32 .exe or error in .exe image).");
370             break;
371           case SE_ERR_ACCESSDENIED :
372             err = _("The operating system denied access to the specified file.");
373             break;
374           case SE_ERR_ASSOCINCOMPLETE :
375             err = _("The file name association is incomplete or invalid.");
376             break;
377           case SE_ERR_DDEBUSY :
378             err = _("DDE transaction busy");
379             break;
380           case SE_ERR_DDEFAIL :
381             err = _("The DDE transaction failed.");
382             break;
383           case SE_ERR_DDETIMEOUT :
384             err = _("The DDE transaction timed out.");
385             break;
386           case SE_ERR_DLLNOTFOUND :
387             err = _("The specified DLL was not found.");
388             break;
389           case SE_ERR_NOASSOC :
390             err = _("There is no application associated with the given file name extension.");
391             break;
392           case SE_ERR_OOM :
393             err = _("There was not enough memory to complete the operation.");
394             break;
395           case SE_ERR_SHARE:
396             err = _("A sharing violation occurred.");
397             break;
398           default :
399             err = _("Unknown Microsoft Windows error.");
400         }
401 
402       g_set_error (error, 0, 0, _("Failed to open '%s': %s"), url, err);
403 
404       return FALSE;
405     }
406 
407   return TRUE;
408 
409 #elif defined(PLATFORM_OSX)
410 
411   NSURL    *ns_url;
412   gboolean  retval;
413 
414   NSAutoreleasePool *arp = [NSAutoreleasePool new];
415     {
416       ns_url = [NSURL URLWithString: [NSString stringWithUTF8String: url]];
417       retval = [[NSWorkspace sharedWorkspace] openURL: ns_url];
418     }
419   [arp release];
420 
421   return retval;
422 
423 #else
424 
425   return gtk_show_uri (gdk_screen_get_default (),
426                        url,
427                        gtk_get_current_event_time(),
428                        error);
429 
430 #endif
431 }
432 
433 static void
gimp_critical_dialog_response(GtkDialog * dialog,gint response_id)434 gimp_critical_dialog_response (GtkDialog *dialog,
435                                gint       response_id)
436 {
437   GimpCriticalDialog *critical = GIMP_CRITICAL_DIALOG (dialog);
438   const gchar        *url      = NULL;
439 
440   switch (response_id)
441     {
442     case GIMP_CRITICAL_RESPONSE_CLIPBOARD:
443       gimp_critical_dialog_copy_info (critical);
444       break;
445 
446     case GIMP_CRITICAL_RESPONSE_DOWNLOAD:
447       url = "https://www.gimp.org/downloads/";
448     case GIMP_CRITICAL_RESPONSE_URL:
449       if (url == NULL)
450         {
451           gchar *temp = g_ascii_strdown (BUG_REPORT_URL, -1);
452 
453           /* Only accept custom web links. */
454           if (g_str_has_prefix (temp, "http://") ||
455               g_str_has_prefix (temp, "https://"))
456             url = BUG_REPORT_URL;
457           else
458             /* XXX Ideally I'd find a way to prefill the bug report
459              * through the URL or with POST data. But I could not find
460              * any. Anyway since we may soon ditch bugzilla to follow
461              * GNOME infrastructure changes, I don't want to waste too
462              * much time digging into it.
463              */
464             url = PACKAGE_BUGREPORT;
465 
466           g_free (temp);
467         }
468 
469       browser_open_url (url, NULL);
470       break;
471 
472     case GIMP_CRITICAL_RESPONSE_RESTART:
473       {
474         gchar *args[2] = { critical->program , NULL };
475 
476 #ifndef G_OS_WIN32
477         /* It is unneeded to kill the process on Win32. This was run
478          * as an async call and the main process should already be
479          * dead by now.
480          */
481         if (critical->pid > 0)
482           kill ((pid_t ) critical->pid, SIGINT);
483 #endif
484         if (critical->program)
485           g_spawn_async (NULL, args, NULL, G_SPAWN_DEFAULT,
486                          NULL, NULL, NULL, NULL);
487       }
488       /* Fall through. */
489     case GTK_RESPONSE_DELETE_EVENT:
490     case GTK_RESPONSE_CLOSE:
491     default:
492       gtk_widget_destroy (GTK_WIDGET (dialog));
493       break;
494     }
495 }
496 
497 /*  public functions  */
498 
499 GtkWidget *
gimp_critical_dialog_new(const gchar * title,const gchar * last_version,gint64 release_timestamp)500 gimp_critical_dialog_new (const gchar *title,
501                           const gchar *last_version,
502                           gint64       release_timestamp)
503 {
504   GtkWidget *dialog;
505   gchar     *date = NULL;
506 
507   g_return_val_if_fail (title != NULL, NULL);
508 
509   if (release_timestamp > 0)
510     {
511       GDateTime *datetime;
512 
513       datetime = g_date_time_new_from_unix_local (release_timestamp);
514       date = g_date_time_format (datetime, "%x");
515       g_date_time_unref (datetime);
516     }
517 
518   dialog = g_object_new (GIMP_TYPE_CRITICAL_DIALOG,
519                          "title",        title,
520                          "last-version", last_version,
521                          "release-date", date,
522                          NULL);
523   g_free (date);
524 
525   return dialog;
526 }
527 
528 void
gimp_critical_dialog_add(GtkWidget * dialog,const gchar * message,const gchar * trace,gboolean is_fatal,const gchar * program,gint pid)529 gimp_critical_dialog_add (GtkWidget   *dialog,
530                           const gchar *message,
531                           const gchar *trace,
532                           gboolean     is_fatal,
533                           const gchar *program,
534                           gint         pid)
535 {
536   GimpCriticalDialog *critical;
537   GtkTextBuffer      *buffer;
538   GtkTextIter         end;
539   gchar              *text;
540 
541   if (! GIMP_IS_CRITICAL_DIALOG (dialog) || ! message)
542     {
543       /* This is a bit hackish. We usually should use
544        * g_return_if_fail(). But I don't want to end up in a critical
545        * recursing loop if our code had bugs. We would crash GIMP with
546        * a CRITICAL which would otherwise not have necessarily ended up
547        * in a crash.
548        */
549       return;
550     }
551   critical = GIMP_CRITICAL_DIALOG (dialog);
552 
553   /* The user text, which should be localized. */
554   if (is_fatal)
555     {
556       text = g_strdup_printf (_("GIMP crashed with a fatal error: %s"),
557                               message);
558     }
559   else if (! gtk_label_get_text (GTK_LABEL (critical->top_label)) ||
560            strlen (gtk_label_get_text (GTK_LABEL (critical->top_label))) == 0)
561     {
562       /* First error. Let's just display it. */
563       text = g_strdup_printf (_("GIMP encountered an error: %s"),
564                               message);
565     }
566   else
567     {
568       /* Let's not display all errors. They will be in the bug report
569        * part anyway.
570        */
571       text = g_strdup_printf (_("GIMP encountered several critical errors!"));
572     }
573   gtk_label_set_text (GTK_LABEL (critical->top_label),
574                       text);
575   g_free (text);
576 
577   if (is_fatal && ! critical->last_version)
578     {
579       /* Same text as before except that we don't need the last point
580        * about saving and restarting since anyway we are crashing and
581        * manual saving is not possible anymore (or even advisable since
582        * if it fails, one may corrupt files).
583        */
584       text = g_strdup_printf ("%s\n"
585                               " \xe2\x80\xa2 %s \"%s\"\n"
586                               " \xe2\x80\xa2 %s \"%s\"\n"
587                               " \xe2\x80\xa2 %s\n"
588                               " \xe2\x80\xa2 %s\n"
589                               " \xe2\x80\xa2 %s",
590                               _("To help us improve GIMP, you can report the bug with "
591                                 "these simple steps:"),
592                               _("Copy the bug information to the clipboard by clicking: "),
593                               BUTTON1_TEXT,
594                               _("Open our bug tracker in the browser by clicking: "),
595                               BUTTON2_TEXT,
596                               _("Create a login if you don't have one yet."),
597                               _("Paste the clipboard text in a new bug report."),
598                               _("Add relevant information in English in the bug report "
599                                 "explaining what you were doing when this error occurred."));
600       gtk_label_set_text (GTK_LABEL (critical->center_label), text);
601       g_free (text);
602     }
603 
604   /* The details text is untranslated on purpose. This is the message
605    * meant to go to clipboard for the bug report. It has to be in
606    * English.
607    */
608   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (critical->details));
609   gtk_text_buffer_get_iter_at_offset (buffer, &end, -1);
610   if (trace)
611     text = g_strdup_printf ("\n> %s\n\nStack trace:\n```\n%s\n```", message, trace);
612   else
613     text = g_strdup_printf ("\n> %s\n", message);
614   gtk_text_buffer_insert (buffer, &end, text, -1);
615   g_free (text);
616 
617   /* Finally when encountering a fatal message, propose one more button
618    * to restart GIMP.
619    */
620   if (is_fatal)
621     {
622       gtk_dialog_add_buttons (GTK_DIALOG (dialog),
623                               _("_Restart GIMP"), GIMP_CRITICAL_RESPONSE_RESTART,
624                               NULL);
625       critical->program = g_strdup (program);
626       critical->pid     = pid;
627     }
628 }
629