summaryrefslogtreecommitdiffstats
path: root/app/widgets/gimpcriticaldialog.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--app/widgets/gimpcriticaldialog.c626
1 files changed, 626 insertions, 0 deletions
diff --git a/app/widgets/gimpcriticaldialog.c b/app/widgets/gimpcriticaldialog.c
new file mode 100644
index 0000000..80c9314
--- /dev/null
+++ b/app/widgets/gimpcriticaldialog.c
@@ -0,0 +1,626 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This widget is particular that I want to be able to use it
+ * internally but also from an alternate tool (gimp-debug-tool). It
+ * means that the implementation must stay as generic glib/GTK+ as
+ * possible.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <gegl.h>
+
+#ifdef PLATFORM_OSX
+#import <Cocoa/Cocoa.h>
+#endif
+
+#ifdef G_OS_WIN32
+#undef DATADIR
+#include <windows.h>
+#endif
+
+#include "gimpcriticaldialog.h"
+
+#include "gimp-intl.h"
+#include "gimp-version.h"
+
+
+#define GIMP_CRITICAL_RESPONSE_CLIPBOARD 1
+#define GIMP_CRITICAL_RESPONSE_URL 2
+#define GIMP_CRITICAL_RESPONSE_RESTART 3
+#define GIMP_CRITICAL_RESPONSE_DOWNLOAD 4
+
+#define BUTTON1_TEXT _("Copy Bug Information")
+#define BUTTON2_TEXT _("Open Bug Tracker")
+
+enum
+{
+ PROP_0,
+ PROP_LAST_VERSION,
+ PROP_RELEASE_DATE
+};
+
+static void gimp_critical_dialog_constructed (GObject *object);
+static void gimp_critical_dialog_finalize (GObject *object);
+static void gimp_critical_dialog_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_critical_dialog_response (GtkDialog *dialog,
+ gint response_id);
+
+static void gimp_critical_dialog_copy_info (GimpCriticalDialog *dialog);
+static gboolean browser_open_url (const gchar *url,
+ GError **error);
+
+
+G_DEFINE_TYPE (GimpCriticalDialog, gimp_critical_dialog, GTK_TYPE_DIALOG)
+
+#define parent_class gimp_critical_dialog_parent_class
+
+
+static void
+gimp_critical_dialog_class_init (GimpCriticalDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass);
+
+ object_class->constructed = gimp_critical_dialog_constructed;
+ object_class->finalize = gimp_critical_dialog_finalize;
+ object_class->set_property = gimp_critical_dialog_set_property;
+
+ dialog_class->response = gimp_critical_dialog_response;
+
+ g_object_class_install_property (object_class, PROP_LAST_VERSION,
+ g_param_spec_string ("last-version",
+ NULL, NULL, NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class, PROP_RELEASE_DATE,
+ g_param_spec_string ("release-date",
+ NULL, NULL, NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_critical_dialog_init (GimpCriticalDialog *dialog)
+{
+ PangoAttrList *attrs;
+ PangoAttribute *attr;
+
+ gtk_window_set_role (GTK_WINDOW (dialog), "gimp-critical");
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE);
+
+ dialog->main_vbox = gtk_vbox_new (FALSE, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (dialog->main_vbox), 6);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ dialog->main_vbox, TRUE, TRUE, 0);
+ gtk_widget_show (dialog->main_vbox);
+
+ /* The error label. */
+ dialog->top_label = gtk_label_new (NULL);
+ gtk_misc_set_alignment (GTK_MISC (dialog->top_label), 0.0, 0.5);
+ gtk_label_set_ellipsize (GTK_LABEL (dialog->top_label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_selectable (GTK_LABEL (dialog->top_label), TRUE);
+ gtk_box_pack_start (GTK_BOX (dialog->main_vbox), dialog->top_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->top_label), attrs);
+ pango_attr_list_unref (attrs);
+
+ gtk_widget_show (dialog->top_label);
+
+ dialog->center_label = gtk_label_new (NULL);
+
+ gtk_misc_set_alignment (GTK_MISC (dialog->center_label), 0.0, 0.5);
+ gtk_label_set_selectable (GTK_LABEL (dialog->center_label), TRUE);
+ gtk_box_pack_start (GTK_BOX (dialog->main_vbox), dialog->center_label,
+ FALSE, FALSE, 0);
+ gtk_widget_show (dialog->center_label);
+
+ dialog->bottom_label = gtk_label_new (NULL);
+ gtk_misc_set_alignment (GTK_MISC (dialog->bottom_label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (dialog->main_vbox), dialog->bottom_label, FALSE, FALSE, 0);
+
+ attrs = pango_attr_list_new ();
+ attr = pango_attr_style_new (PANGO_STYLE_ITALIC);
+ pango_attr_list_insert (attrs, attr);
+ gtk_label_set_attributes (GTK_LABEL (dialog->bottom_label), attrs);
+ pango_attr_list_unref (attrs);
+ gtk_widget_show (dialog->bottom_label);
+
+ dialog->pid = 0;
+ dialog->program = NULL;
+}
+
+static void
+gimp_critical_dialog_constructed (GObject *object)
+{
+ GimpCriticalDialog *dialog = GIMP_CRITICAL_DIALOG (object);
+ GtkWidget *scrolled;
+ GtkTextBuffer *buffer;
+ gchar *version;
+ gchar *text;
+
+ /* Bug details for developers. */
+ scrolled = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_SHADOW_IN);
+ gtk_widget_set_size_request (scrolled, -1, 200);
+
+ if (dialog->last_version)
+ {
+ GtkWidget *expander;
+ GtkWidget *vbox;
+ GtkWidget *button;
+
+ expander = gtk_expander_new (_("See bug details"));
+ gtk_box_pack_start (GTK_BOX (dialog->main_vbox), expander, TRUE, TRUE, 0);
+ gtk_widget_show (expander);
+
+ vbox = gtk_vbox_new (FALSE, 4);
+ gtk_container_add (GTK_CONTAINER (expander), vbox);
+ gtk_widget_show (vbox);
+
+ gtk_box_pack_start (GTK_BOX (vbox), scrolled, TRUE, TRUE, 0);
+ gtk_widget_show (scrolled);
+
+ button = gtk_button_new_with_label (BUTTON1_TEXT);
+ g_signal_connect_swapped (button, "clicked",
+ G_CALLBACK (gimp_critical_dialog_copy_info),
+ dialog);
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("Go to _Download page"), GIMP_CRITICAL_RESPONSE_DOWNLOAD,
+ _("_Close"), GTK_RESPONSE_CLOSE,
+ NULL);
+
+ /* Recommend an update. */
+ text = g_strdup_printf (_("A new version of GIMP (%s) was released on %s.\n"
+ "It is recommended to update."),
+ dialog->last_version, dialog->release_date);
+ gtk_label_set_text (GTK_LABEL (dialog->center_label), text);
+ g_free (text);
+
+ text = _("You are running an unsupported version!");
+ gtk_label_set_text (GTK_LABEL (dialog->bottom_label), text);
+ }
+ else
+ {
+ /* Pack directly (and well visible) the bug details. */
+ gtk_box_pack_start (GTK_BOX (dialog->main_vbox), scrolled, TRUE, TRUE, 0);
+ gtk_widget_show (scrolled);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ BUTTON1_TEXT, GIMP_CRITICAL_RESPONSE_CLIPBOARD,
+ BUTTON2_TEXT, GIMP_CRITICAL_RESPONSE_URL,
+ _("_Close"), GTK_RESPONSE_CLOSE,
+ NULL);
+
+ /* 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",
+ _("To help us improve GIMP, you can report the bug with "
+ "these simple steps:"),
+ _("Copy the bug information to the clipboard by clicking: "),
+ BUTTON1_TEXT,
+ _("Open our bug tracker in the browser by clicking: "),
+ BUTTON2_TEXT,
+ _("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."));
+ gtk_label_set_text (GTK_LABEL (dialog->center_label), text);
+ g_free (text);
+
+ text = _("You can also close the dialog directly but "
+ "reporting bugs is the best way to make your "
+ "software awesome.");
+ gtk_label_set_text (GTK_LABEL (dialog->bottom_label), text);
+ }
+
+ buffer = gtk_text_buffer_new (NULL);
+ version = gimp_version (TRUE, FALSE);
+ text = g_strdup_printf ("```\n%s\n```", version);
+ gtk_text_buffer_set_text (buffer, text, -1);
+ g_free (version);
+ g_free (text);
+
+ 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 (scrolled), dialog->details);
+}
+
+static void
+gimp_critical_dialog_finalize (GObject *object)
+{
+ GimpCriticalDialog *dialog = GIMP_CRITICAL_DIALOG (object);
+
+ if (dialog->program)
+ g_free (dialog->program);
+ if (dialog->last_version)
+ g_free (dialog->last_version);
+ if (dialog->release_date)
+ g_free (dialog->release_date);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_critical_dialog_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCriticalDialog *dialog = GIMP_CRITICAL_DIALOG (object);
+
+ switch (property_id)
+ {
+ case PROP_LAST_VERSION:
+ dialog->last_version = g_value_dup_string (value);
+ break;
+ case PROP_RELEASE_DATE:
+ dialog->release_date = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_critical_dialog_copy_info (GimpCriticalDialog *dialog)
+{
+ 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 (dialog->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);
+ }
+}
+
+/* XXX This is taken straight from plug-ins/common/web-browser.c
+ *
+ * This really sucks but this class also needs to be called by
+ * tools/gimp-debug-tool.c as a separate process and therefore cannot
+ * make use of the PDB. Anyway shouldn't we just move this as a utils
+ * function? Why does such basic feature as opening a URL in a
+ * cross-platform way need to be a plug-in?
+ */
+static gboolean
+browser_open_url (const gchar *url,
+ GError **error)
+{
+#ifdef G_OS_WIN32
+
+ HINSTANCE hinst = ShellExecute (GetDesktopWindow(),
+ "open", url, NULL, NULL, SW_SHOW);
+
+ if ((gint) hinst <= 32)
+ {
+ const gchar *err;
+
+ switch ((gint) hinst)
+ {
+ case 0 :
+ err = _("The operating system is out of memory or resources.");
+ break;
+ case ERROR_FILE_NOT_FOUND :
+ err = _("The specified file was not found.");
+ break;
+ case ERROR_PATH_NOT_FOUND :
+ err = _("The specified path was not found.");
+ break;
+ case ERROR_BAD_FORMAT :
+ err = _("The .exe file is invalid (non-Microsoft Win32 .exe or error in .exe image).");
+ break;
+ case SE_ERR_ACCESSDENIED :
+ err = _("The operating system denied access to the specified file.");
+ break;
+ case SE_ERR_ASSOCINCOMPLETE :
+ err = _("The file name association is incomplete or invalid.");
+ break;
+ case SE_ERR_DDEBUSY :
+ err = _("DDE transaction busy");
+ break;
+ case SE_ERR_DDEFAIL :
+ err = _("The DDE transaction failed.");
+ break;
+ case SE_ERR_DDETIMEOUT :
+ err = _("The DDE transaction timed out.");
+ break;
+ case SE_ERR_DLLNOTFOUND :
+ err = _("The specified DLL was not found.");
+ break;
+ case SE_ERR_NOASSOC :
+ err = _("There is no application associated with the given file name extension.");
+ break;
+ case SE_ERR_OOM :
+ err = _("There was not enough memory to complete the operation.");
+ break;
+ case SE_ERR_SHARE:
+ err = _("A sharing violation occurred.");
+ break;
+ default :
+ err = _("Unknown Microsoft Windows error.");
+ }
+
+ g_set_error (error, 0, 0, _("Failed to open '%s': %s"), url, err);
+
+ return FALSE;
+ }
+
+ return TRUE;
+
+#elif defined(PLATFORM_OSX)
+
+ NSURL *ns_url;
+ gboolean retval;
+
+ NSAutoreleasePool *arp = [NSAutoreleasePool new];
+ {
+ ns_url = [NSURL URLWithString: [NSString stringWithUTF8String: url]];
+ retval = [[NSWorkspace sharedWorkspace] openURL: ns_url];
+ }
+ [arp release];
+
+ return retval;
+
+#else
+
+ return gtk_show_uri (gdk_screen_get_default (),
+ url,
+ gtk_get_current_event_time(),
+ error);
+
+#endif
+}
+
+static void
+gimp_critical_dialog_response (GtkDialog *dialog,
+ gint response_id)
+{
+ GimpCriticalDialog *critical = GIMP_CRITICAL_DIALOG (dialog);
+ const gchar *url = NULL;
+
+ switch (response_id)
+ {
+ case GIMP_CRITICAL_RESPONSE_CLIPBOARD:
+ gimp_critical_dialog_copy_info (critical);
+ break;
+
+ case GIMP_CRITICAL_RESPONSE_DOWNLOAD:
+ url = "https://www.gimp.org/downloads/";
+ case GIMP_CRITICAL_RESPONSE_URL:
+ if (url == NULL)
+ {
+ gchar *temp = g_ascii_strdown (BUG_REPORT_URL, -1);
+
+ /* Only accept custom web links. */
+ if (g_str_has_prefix (temp, "http://") ||
+ g_str_has_prefix (temp, "https://"))
+ url = BUG_REPORT_URL;
+ else
+ /* 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 = PACKAGE_BUGREPORT;
+
+ g_free (temp);
+ }
+
+ browser_open_url (url, NULL);
+ break;
+
+ case GIMP_CRITICAL_RESPONSE_RESTART:
+ {
+ gchar *args[2] = { critical->program , NULL };
+
+#ifndef G_OS_WIN32
+ /* It is unneeded to kill the process on Win32. This was run
+ * as an async call and the main process should already be
+ * dead by now.
+ */
+ if (critical->pid > 0)
+ kill ((pid_t ) critical->pid, SIGINT);
+#endif
+ if (critical->program)
+ g_spawn_async (NULL, args, NULL, G_SPAWN_DEFAULT,
+ NULL, NULL, NULL, NULL);
+ }
+ /* Fall through. */
+ 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,
+ const gchar *last_version,
+ gint64 release_timestamp)
+{
+ GtkWidget *dialog;
+ gchar *date = NULL;
+
+ g_return_val_if_fail (title != NULL, NULL);
+
+ if (release_timestamp > 0)
+ {
+ GDateTime *datetime;
+
+ datetime = g_date_time_new_from_unix_local (release_timestamp);
+ date = g_date_time_format (datetime, "%x");
+ g_date_time_unref (datetime);
+ }
+
+ dialog = g_object_new (GIMP_TYPE_CRITICAL_DIALOG,
+ "title", title,
+ "last-version", last_version,
+ "release-date", date,
+ NULL);
+ g_free (date);
+
+ return dialog;
+}
+
+void
+gimp_critical_dialog_add (GtkWidget *dialog,
+ const gchar *message,
+ const gchar *trace,
+ gboolean is_fatal,
+ const gchar *program,
+ gint pid)
+{
+ GimpCriticalDialog *critical;
+ GtkTextBuffer *buffer;
+ GtkTextIter end;
+ gchar *text;
+
+ if (! GIMP_IS_CRITICAL_DIALOG (dialog) || ! message)
+ {
+ /* 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;
+ }
+ critical = GIMP_CRITICAL_DIALOG (dialog);
+
+ /* The user text, which should be localized. */
+ if (is_fatal)
+ {
+ text = g_strdup_printf (_("GIMP crashed with a fatal error: %s"),
+ message);
+ }
+ else if (! gtk_label_get_text (GTK_LABEL (critical->top_label)) ||
+ strlen (gtk_label_get_text (GTK_LABEL (critical->top_label))) == 0)
+ {
+ /* First error. Let's just display it. */
+ text = g_strdup_printf (_("GIMP encountered an 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 (critical->top_label),
+ text);
+ g_free (text);
+
+ if (is_fatal && ! critical->last_version)
+ {
+ /* Same text as before except that we don't need the last point
+ * about saving and restarting since anyway we are crashing and
+ * manual saving is not possible anymore (or even advisable since
+ * if it fails, one may corrupt files).
+ */
+ 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",
+ _("To help us improve GIMP, you can report the bug with "
+ "these simple steps:"),
+ _("Copy the bug information to the clipboard by clicking: "),
+ BUTTON1_TEXT,
+ _("Open our bug tracker in the browser by clicking: "),
+ BUTTON2_TEXT,
+ _("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."));
+ gtk_label_set_text (GTK_LABEL (critical->center_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 (critical->details));
+ gtk_text_buffer_get_iter_at_offset (buffer, &end, -1);
+ if (trace)
+ text = g_strdup_printf ("\n> %s\n\nStack trace:\n```\n%s\n```", message, trace);
+ else
+ text = g_strdup_printf ("\n> %s\n", message);
+ gtk_text_buffer_insert (buffer, &end, text, -1);
+ g_free (text);
+
+ /* Finally when encountering a fatal message, propose one more button
+ * to restart GIMP.
+ */
+ if (is_fatal)
+ {
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("_Restart GIMP"), GIMP_CRITICAL_RESPONSE_RESTART,
+ NULL);
+ critical->program = g_strdup (program);
+ critical->pid = pid;
+ }
+}