Blob Blame History Raw
From 136dfd3c7ccf2d3235ec69db4eb9af00b364ba8c Mon Sep 17 00:00:00 2001
From: Jakub Filak <jfilak@redhat.com>
Date: Mon, 25 Aug 2014 17:18:02 +0200
Subject: [LIBREPORT PATCH] gui: Problem Details suite

+ ProblemDetailsWidget
+ ProblemDetailsDialog

Related to abrt/gnome-abrt#64

Signed-off-by: Jakub Filak <jfilak@redhat.com>
---
 po/POTFILES.in                           |   2 +
 src/gtk-helpers/Makefile.am              |  10 +-
 src/gtk-helpers/internal_libreport_gtk.h |   6 +
 src/gtk-helpers/problem_details_dialog.c |  80 +++++++
 src/gtk-helpers/problem_details_dialog.h |  37 +++
 src/gtk-helpers/problem_details_widget.c | 375 +++++++++++++++++++++++++++++++
 src/gtk-helpers/problem_details_widget.h |  62 +++++
 src/gtk-helpers/utils.c                  |  25 +++
 src/gui-wizard-gtk/wizard.c              |  21 +-
 9 files changed, 596 insertions(+), 22 deletions(-)
 create mode 100644 src/gtk-helpers/problem_details_dialog.c
 create mode 100644 src/gtk-helpers/problem_details_dialog.h
 create mode 100644 src/gtk-helpers/problem_details_widget.c
 create mode 100644 src/gtk-helpers/problem_details_widget.h
 create mode 100644 src/gtk-helpers/utils.c

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 4a0b565..c599dab 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -10,6 +10,8 @@ src/gtk-helpers/config_dialog.c
 src/gtk-helpers/event_config_dialog.c
 src/gtk-helpers/secrets.c
 src/gtk-helpers/workflow_config_dialog.c
+src/gtk-helpers/problem_details_widget.c
+src/gtk-helpers/problem_details_dialog.c
 src/gui-wizard-gtk/main.c
 src/gui-wizard-gtk/wizard.c
 src/gui-wizard-gtk/wizard.glade
diff --git a/src/gtk-helpers/Makefile.am b/src/gtk-helpers/Makefile.am
index 13b9072..a7cc554 100644
--- a/src/gtk-helpers/Makefile.am
+++ b/src/gtk-helpers/Makefile.am
@@ -3,12 +3,15 @@ libreport_gtk_includedir = \
     $(includedir)/libreport
 
 libreport_gtk_include_HEADERS = \
-    internal_libreport_gtk.h
+    internal_libreport_gtk.h \
+    problem_details_widget.h \
+    problem_details_dialog.h
 
 lib_LTLIBRARIES = \
     libreport-gtk.la
 
 libreport_gtk_la_SOURCES = \
+    utils.c \
     event_config_dialog.c \
     secrets.c \
     hyperlinks.c \
@@ -16,11 +19,14 @@ libreport_gtk_la_SOURCES = \
     workflow_config_dialog.c \
     config_dialog.c \
     ask_dialogs.c \
-    search_item.c search_item.h
+    search_item.c search_item.h \
+    problem_details_widget.c problem_details_widget.h \
+    problem_details_dialog.c problem_details_dialog.h
 
 libreport_gtk_la_CPPFLAGS = \
     -I$(srcdir)/../include \
     -I$(srcdir)/../lib \
+    -Wno-error=unused-local-typedefs \
     $(GTK_CFLAGS) \
     $(GLIB_CFLAGS) \
     $(GIO_CFLAGS) \
diff --git a/src/gtk-helpers/internal_libreport_gtk.h b/src/gtk-helpers/internal_libreport_gtk.h
index f8f1c13..57f5889 100644
--- a/src/gtk-helpers/internal_libreport_gtk.h
+++ b/src/gtk-helpers/internal_libreport_gtk.h
@@ -20,6 +20,8 @@
 #define INTERNAL_LIBREPORT_GTK_H_
 
 #include <gtk/gtk.h>
+#include "problem_details_dialog.h"
+#include "problem_details_widget.h"
 #include "report.h"
 #include "internal_libreport.h"
 
@@ -96,6 +98,10 @@ struct url_token
 #define find_url_tokens libreport_find_url_tokens
 GList *find_url_tokens(const char *line);
 
+
+#define reload_text_to_text_view libreport_reload_text_to_text_view
+void reload_text_to_text_view(GtkTextView *tv, const char *text);
+
 /* Ask dialogs */
 
 /*
diff --git a/src/gtk-helpers/problem_details_dialog.c b/src/gtk-helpers/problem_details_dialog.c
new file mode 100644
index 0000000..dc2362e
--- /dev/null
+++ b/src/gtk-helpers/problem_details_dialog.c
@@ -0,0 +1,80 @@
+/*
+    Copyright (C) 2014  ABRT Team
+    Copyright (C) 2014  RedHat inc.
+
+    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 2 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, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "problem_details_dialog.h"
+#include "internal_libreport_gtk.h"
+#include "internal_libreport.h"
+
+GtkWidget *
+problem_details_dialog_new(problem_data_t *problem, GtkWindow *parent)
+{
+    INITIALIZE_LIBREPORT();
+
+    GtkWidget *dialog = gtk_dialog_new_with_buttons(
+            _("Problem details"),
+            parent,
+            GTK_DIALOG_DESTROY_WITH_PARENT,
+            _("OK"),
+            GTK_RESPONSE_NONE,
+            NULL
+            );
+
+    gtk_window_set_default_size(GTK_WINDOW(dialog), 800, 600);
+
+    g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog);
+
+    ProblemDetailsWidget *details = problem_details_widget_new(problem);
+
+    GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL);
+    gtk_widget_set_halign(scrolled, GTK_ALIGN_FILL);
+    gtk_widget_set_valign(scrolled, GTK_ALIGN_FILL);
+    gtk_widget_set_hexpand(scrolled, TRUE);
+    gtk_widget_set_vexpand(scrolled, TRUE);
+
+    gtk_container_add(GTK_CONTAINER(scrolled), GTK_WIDGET(details));
+
+    GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+    gtk_container_add(GTK_CONTAINER(content_area), GTK_WIDGET(scrolled));
+
+    gtk_widget_show_all(dialog);
+
+    return dialog;
+}
+
+GtkWidget *
+problem_details_dialog_new_for_dir(const char *dir, GtkWindow *parent)
+{
+    INITIALIZE_LIBREPORT();
+
+    struct dump_dir *dd = dd_opendir(dir, DD_OPEN_READONLY);
+    if (!dd)
+        return NULL;
+
+    problem_data_t *problem = create_problem_data_from_dump_dir(dd);
+    problem_data_add_text_noteditable(problem, CD_DUMPDIR, dir);
+
+    dd_close(dd);
+
+    GtkWidget *dialog = problem_details_dialog_new(problem, parent);
+
+    g_signal_connect_swapped(dialog, "destroy", G_CALLBACK(problem_data_free), problem);
+
+    return dialog;
+}
+
diff --git a/src/gtk-helpers/problem_details_dialog.h b/src/gtk-helpers/problem_details_dialog.h
new file mode 100644
index 0000000..e8ba8ad
--- /dev/null
+++ b/src/gtk-helpers/problem_details_dialog.h
@@ -0,0 +1,37 @@
+/*
+    Copyright (C) 2014  ABRT Team
+    Copyright (C) 2014  RedHat inc.
+
+    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 2 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, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#ifndef _PROBLEM_DETAILS_DIALOG_H
+#define _PROBLEM_DETAILS_DIALOG_H
+
+#include <gtk/gtk.h>
+#include "problem_data.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+GtkWidget *problem_details_dialog_new(problem_data_t *problem, GtkWindow *parent);
+GtkWidget *problem_details_dialog_new_for_dir(const char *dir, GtkWindow *parent);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _PROBLEM_DETAILS_DIALOG_H */
+
diff --git a/src/gtk-helpers/problem_details_widget.c b/src/gtk-helpers/problem_details_widget.c
new file mode 100644
index 0000000..2cb3206
--- /dev/null
+++ b/src/gtk-helpers/problem_details_widget.c
@@ -0,0 +1,375 @@
+/*
+    Copyright (C) 2014  ABRT Team
+    Copyright (C) 2014  RedHat inc.
+
+    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 2 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, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "problem_details_widget.h"
+#include "internal_libreport_gtk.h"
+#include "internal_libreport.h"
+
+#define PROBLEM_DETAILS_WIDGET_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), TYPE_PROBLEM_DETAILS_WIDGET, ProblemDetailsWidgetPrivate))
+
+#define EXPLICIT_ITEMS \
+    CD_DUMPDIR, \
+    FILENAME_TIME, \
+    FILENAME_LAST_OCCURRENCE, \
+    FILENAME_UID, \
+    FILENAME_USERNAME, \
+    FILENAME_TYPE, \
+    FILENAME_COMMENT, \
+    FILENAME_ANALYZER
+
+#define ORDERED_ITEMS \
+    FILENAME_EXPLOITABLE, \
+    FILENAME_NOT_REPORTABLE, \
+    FILENAME_REASON, \
+    FILENAME_BACKTRACE, \
+    FILENAME_CRASH_FUNCTION, \
+    FILENAME_CMDLINE, \
+    FILENAME_EXECUTABLE, \
+    FILENAME_PACKAGE, \
+    FILENAME_COMPONENT, \
+    FILENAME_PID, \
+    FILENAME_PWD, \
+    FILENAME_HOSTNAME, \
+    FILENAME_COUNT
+
+static const char *items_orderlist[] = {
+    ORDERED_ITEMS,
+    NULL,
+};
+
+static const char *items_auto_blacklist[] = {
+    EXPLICIT_ITEMS,
+    ORDERED_ITEMS,
+    FILENAME_PKG_NAME,
+    FILENAME_PKG_VERSION,
+    FILENAME_PKG_RELEASE,
+    FILENAME_PKG_ARCH,
+    FILENAME_PKG_EPOCH,
+    NULL,
+};
+
+struct ProblemDetailsWidgetPrivate {
+    PangoFontDescription *font;
+    gulong rows;
+    problem_data_t *problem_data;
+};
+
+G_DEFINE_TYPE(ProblemDetailsWidget, problem_details_widget, GTK_TYPE_GRID)
+
+static void problem_details_widget_finalize(GObject *object);
+
+static void
+problem_details_widget_class_init(ProblemDetailsWidgetClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    object_class->finalize = problem_details_widget_finalize;
+
+    g_type_class_add_private(klass, sizeof(ProblemDetailsWidgetPrivate));
+}
+
+static void
+problem_details_widget_finalize(GObject *object)
+{
+    ProblemDetailsWidget *self;
+
+    self = PROBLEM_DETAILS_WIDGET(object);
+
+    self->priv->problem_data = (void *)0xdeadbeaf;
+
+    G_OBJECT_CLASS(problem_details_widget_parent_class)->finalize(object);
+}
+
+static gulong
+problem_details_widget_append_row(ProblemDetailsWidget *self)
+{
+    gtk_grid_insert_row(GTK_GRID(self), self->priv->rows);
+    return self->priv->rows++;
+}
+
+static void
+problem_details_widget_add_single_line(ProblemDetailsWidget *self, const char *name, const char *content)
+{
+    GtkWidget *label = gtk_label_new(name);
+    gtk_widget_set_halign(label, GTK_ALIGN_START);
+    gtk_widget_set_valign(label, GTK_ALIGN_START);
+    gtk_widget_set_margin_start(label, 20);
+    gtk_widget_set_margin_end(label, 20);
+
+    GtkWidget *value = gtk_label_new(content);
+    gtk_label_set_selectable(GTK_LABEL(value), TRUE);
+    gtk_label_set_line_wrap(GTK_LABEL(value), TRUE);
+    gtk_label_set_line_wrap_mode(GTK_LABEL(value), GTK_WRAP_WORD);
+    gtk_widget_set_halign(value, GTK_ALIGN_START);
+    gtk_widget_set_hexpand(value, TRUE);
+    gtk_widget_set_margin_start(value, 5);
+    gtk_widget_override_font(GTK_WIDGET(value), self->priv->font);
+
+    const gulong row = problem_details_widget_append_row(self);
+
+    gtk_grid_attach(GTK_GRID(self), label, 0, row, 1, 1);
+    gtk_grid_attach(GTK_GRID(self), value, 1, row, 1, 1);
+}
+
+static void
+problem_details_widget_add_multi_line(ProblemDetailsWidget *self, const char *name, const char *content)
+{
+#if 0
+    GtkWidget *value = gtk_text_view_new();
+    gtk_text_view_set_editable(GTK_TEXT_VIEW(value), FALSE);
+
+    if (strcmp(name, FILENAME_COMMENT) == 0
+            || strcmp(name, FILENAME_REASON) == 0)
+        gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(value), GTK_WRAP_WORD);
+
+    reload_text_to_text_view(GTK_TEXT_VIEW(value), content);
+#else
+    GtkWidget *value = gtk_label_new(content);
+    gtk_widget_set_halign(value, GTK_ALIGN_START);
+
+    if (strcmp(name, FILENAME_COMMENT) == 0
+            || strcmp(name, FILENAME_REASON) == 0)
+    {
+        gtk_label_set_line_wrap(GTK_LABEL(value), TRUE);
+        gtk_label_set_line_wrap_mode(GTK_LABEL(value), GTK_WRAP_WORD);
+        gtk_widget_set_margin_bottom(value, 12);
+    }
+
+    gtk_label_set_selectable(GTK_LABEL(value), TRUE);
+#endif
+
+    gtk_widget_override_font(GTK_WIDGET(value), self->priv->font);
+
+    GtkWidget *expander = gtk_expander_new(name);
+    gtk_widget_set_hexpand(expander, TRUE);
+    gtk_container_add(GTK_CONTAINER(expander), value);
+
+    const gulong row = problem_details_widget_append_row(self);
+
+    gtk_grid_attach(GTK_GRID(self), expander, 0, row, 2, 1);
+}
+
+static void
+problem_details_widget_add_binary(ProblemDetailsWidget *self, const char *label, const char *path)
+{
+    struct stat statbuf;
+    statbuf.st_size = 0;
+
+    if (stat(path, &statbuf) != 0)
+    {
+        log("File '%s' does not exist", path);
+        return;
+    }
+
+    gchar *size = g_format_size_full((long long)statbuf.st_size, G_FORMAT_SIZE_IEC_UNITS);
+    char *msg = xasprintf(_("$DATA_DIRECTORY/%s (binary file, %s)"), label, size);
+    problem_details_widget_add_single_line(self, label, msg);
+    free(msg);
+    g_free(size);
+}
+
+static void
+problem_details_widget_add_time_stamp(ProblemDetailsWidget *self, const char *label, const char *stamp)
+{
+    struct tm tm;
+    memset(&tm, 0, sizeof(struct tm));
+
+    const char *ret = strptime(stamp, "%s", &tm);
+
+    if (ret == NULL || ret[0] != '\0')
+        return;
+
+    char buf[255];
+    strftime(buf, sizeof(buf), "%F %T", &tm);
+
+    problem_details_widget_add_single_line(self, label, buf);
+}
+
+static void
+problem_details_widget_add_problem_item(ProblemDetailsWidget *self, const char *name, problem_item *item)
+{
+    if (item->flags & CD_FLAG_TXT)
+    {
+        if (strchr(item->content, '\n') == NULL)
+            problem_details_widget_add_single_line(self, name, item->content);
+        else
+            problem_details_widget_add_multi_line(self, name, item->content);
+    }
+    else if (item->flags & CD_FLAG_BIN)
+        problem_details_widget_add_binary(self, name, item->content);
+    else
+        log("Unsupported file type");
+}
+
+/* Callback for GHashTable */
+static void
+problem_data_entry_to_grid_row_one_line(const char *item_name, problem_item *item, ProblemDetailsWidget *self)
+{
+    if (((item->flags & CD_FLAG_TXT) && (strchr(item->content, '\n') == NULL))
+             && !is_in_string_list(item_name, (char **)items_auto_blacklist))
+        problem_details_widget_add_single_line(self, item_name, item->content);
+}
+
+static void
+problem_data_entry_to_grid_row_multi_line(const char *item_name, problem_item *item, ProblemDetailsWidget *self)
+{
+    if (((item->flags & CD_FLAG_TXT) && (strchr(item->content, '\n') != NULL))
+            && !is_in_string_list(item_name, (char **)items_auto_blacklist))
+        problem_details_widget_add_multi_line(self, item_name, item->content);
+}
+
+static void
+problem_data_entry_to_grid_row_binary(const char *item_name, problem_item *item, ProblemDetailsWidget *self)
+{
+    if ((item->flags & CD_FLAG_BIN)
+            && !is_in_string_list(item_name, (char **)items_auto_blacklist))
+        problem_details_widget_add_binary(self, item_name, item->content);
+}
+
+static void
+problem_details_widget_populate(ProblemDetailsWidget *self)
+{
+    {   /* Explicit order */
+        for (const char **iter = items_orderlist; *iter; ++iter)
+        {
+            struct problem_item *item = problem_data_get_item_or_NULL(
+                    self->priv->problem_data, *iter);
+
+            if (item == NULL)
+                continue;
+
+            problem_details_widget_add_problem_item(self, *iter, item);
+        }
+    }
+
+    { /* comment: */
+        const char *dd = problem_data_get_content_or_NULL(
+                self->priv->problem_data, FILENAME_COMMENT);
+        if (dd)
+            problem_details_widget_add_multi_line(self, FILENAME_COMMENT, dd);
+    }
+
+    { /* First occurence: 2014-08-26 11:08 */
+        const char *ts = problem_data_get_content_or_NULL(
+                self->priv->problem_data, FILENAME_TIME);
+        if (ts)
+            problem_details_widget_add_time_stamp(self, "first_occurence", ts);
+    }
+
+    { /* Last occurence: 2014-08-27 11:08 */
+        const char *ts = problem_data_get_content_or_NULL(
+                self->priv->problem_data, FILENAME_LAST_OCCURRENCE);
+        if (ts)
+            problem_details_widget_add_time_stamp(self, "last_occurence", ts);
+    }
+
+    { /* User: login(UID) */
+        const char *uid = problem_data_get_content_or_NULL(
+                self->priv->problem_data, FILENAME_UID);
+
+        const char *username = problem_data_get_content_or_NULL(
+                self->priv->problem_data, "username");
+
+        char *line = NULL;
+        if (uid && username)
+            line = xasprintf("%s (%s)", username, uid);
+        else if (!uid && !username)
+            line = xstrdup("unknown user");
+        else
+            line = xasprintf("%s", uid ? uid : username);
+
+        problem_details_widget_add_single_line(self, "user", line);
+    }
+
+    { /* Type/Analyzer: CCpp */
+        const char *type = problem_data_get_content_or_NULL(
+                self->priv->problem_data, FILENAME_TYPE);
+        const char *analyzer = problem_data_get_content_or_NULL(
+                self->priv->problem_data, FILENAME_ANALYZER);
+
+        char *label = NULL;
+        char *line = NULL;
+        if (type != NULL && analyzer != NULL)
+        {
+            if (strcmp(type, analyzer) != 0)
+            {
+                label = xstrdup("type/analyzer");
+                line = xasprintf("%s/%s", type, analyzer);
+            }
+            else
+            {
+                label = xstrdup("type");
+                line = xstrdup(type);
+            }
+        }
+        else
+        {
+            label = xstrdup(type ? "type" : "anlyzer");
+            line = xstrdup(type ? type : analyzer);
+        }
+
+        problem_details_widget_add_single_line(self, label, line);
+
+        free(line);
+        free(label);
+    }
+
+    g_hash_table_foreach(self->priv->problem_data,
+            (GHFunc)problem_data_entry_to_grid_row_one_line, self);
+
+    { /* data directory: */
+        const char *dd = problem_data_get_content_or_NULL(
+                self->priv->problem_data, CD_DUMPDIR);
+        if (dd)
+            problem_details_widget_add_single_line(self, "data_directory", dd);
+
+        /* show binaries below the data_directory entry */
+        g_hash_table_foreach(self->priv->problem_data,
+            (GHFunc)problem_data_entry_to_grid_row_binary, self);
+    }
+
+    g_hash_table_foreach(self->priv->problem_data,
+            (GHFunc)problem_data_entry_to_grid_row_multi_line, self);
+}
+
+static void
+problem_details_widget_init(ProblemDetailsWidget *self)
+{
+    self->priv = PROBLEM_DETAILS_WIDGET_GET_PRIVATE(self);
+    self->priv->font = pango_font_description_from_string("monospace");
+    self->priv->rows = 0;
+    self->priv->problem_data = NULL;
+}
+
+ProblemDetailsWidget *
+problem_details_widget_new(problem_data_t *problem)
+{
+    INITIALIZE_LIBREPORT();
+
+    GObject *object = g_object_new(TYPE_PROBLEM_DETAILS_WIDGET, NULL);
+    ProblemDetailsWidget *self = PROBLEM_DETAILS_WIDGET(object);
+    self->priv->problem_data = problem;
+
+    problem_details_widget_populate(self);
+    gtk_widget_show_all(GTK_WIDGET(self));
+
+    return self;
+}
+
diff --git a/src/gtk-helpers/problem_details_widget.h b/src/gtk-helpers/problem_details_widget.h
new file mode 100644
index 0000000..c736a5d
--- /dev/null
+++ b/src/gtk-helpers/problem_details_widget.h
@@ -0,0 +1,62 @@
+/*
+    Copyright (C) 2014  ABRT Team
+    Copyright (C) 2014  RedHat inc.
+
+    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 2 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, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#ifndef _PROBLEM_DETAILS_WIDGET_H
+#define _PROBLEM_DETAILS_WIDGET_H
+
+#include <gtk/gtk.h>
+#include "problem_data.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+G_BEGIN_DECLS
+
+#define TYPE_PROBLEM_DETAILS_WIDGET            (problem_details_widget_get_type())
+#define PROBLEM_DETAILS_WIDGET(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_PROBLEM_DETAILS_WIDGET, ProblemDetailsWidget))
+#define PROBLEM_DETAILS_WIDGET_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_PROBLEM_DETAILS_WIDGET, ProblemDetailsWidgetClass))
+#define IS_PROBLEM_DETAILS_WIDGET(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_PROBLEM_DETAILS_WIDGET))
+#define IS_PROBLEM_DETAILS_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_PROBLEM_DETAILS_WIDGET))
+#define PROBLEM_DETAILS_WIDGET_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_PROBLEM_DETAILS_WIDGET, ProblemDetailsWidgetClass))
+
+typedef struct _ProblemDetailsWidget        ProblemDetailsWidget;
+typedef struct _ProblemDetailsWidgetClass   ProblemDetailsWidgetClass;
+typedef struct ProblemDetailsWidgetPrivate  ProblemDetailsWidgetPrivate;
+
+struct _ProblemDetailsWidget {
+   GtkGrid    parent_instance;
+   ProblemDetailsWidgetPrivate *priv;
+};
+
+struct _ProblemDetailsWidgetClass {
+   GtkGridClass parent_class;
+};
+
+GType problem_details_widget_get_type (void) G_GNUC_CONST;
+
+ProblemDetailsWidget *problem_details_widget_new(problem_data_t *problem);
+
+G_END_DECLS
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _PROBLEM_DETAILS_WIDGET_H */
+
diff --git a/src/gtk-helpers/utils.c b/src/gtk-helpers/utils.c
new file mode 100644
index 0000000..4ba2c0e
--- /dev/null
+++ b/src/gtk-helpers/utils.c
@@ -0,0 +1,25 @@
+#include "internal_libreport_gtk.h"
+
+void reload_text_to_text_view(GtkTextView *tv, const char *text)
+{
+    GtkTextBuffer *tb = gtk_text_view_get_buffer(tv);
+    GtkTextIter beg_iter, end_iter;
+    gtk_text_buffer_get_iter_at_offset(tb, &beg_iter, 0);
+    gtk_text_buffer_get_iter_at_offset(tb, &end_iter, -1);
+    gtk_text_buffer_delete(tb, &beg_iter, &end_iter);
+
+    if (!text)
+        return;
+
+    const gchar *end;
+    while (!g_utf8_validate(text, -1, &end))
+    {
+        gtk_text_buffer_insert_at_cursor(tb, text, end - text);
+        char buf[8];
+        unsigned len = snprintf(buf, sizeof(buf), "<%02X>", (unsigned char)*end);
+        gtk_text_buffer_insert_at_cursor(tb, buf, len);
+        text = end + 1;
+    }
+
+    gtk_text_buffer_insert_at_cursor(tb, text, strlen(text));
+}
diff --git a/src/gui-wizard-gtk/wizard.c b/src/gui-wizard-gtk/wizard.c
index 525d275..54b9b10 100644
--- a/src/gui-wizard-gtk/wizard.c
+++ b/src/gui-wizard-gtk/wizard.c
@@ -393,31 +393,12 @@ static void load_text_to_text_view(GtkTextView *tv, const char *name)
     /* a result of xstrdup() is freed */
     g_hash_table_insert(g_loaded_texts, (gpointer)xstrdup(name), (gpointer)1);
 
-    GtkTextBuffer *tb = gtk_text_view_get_buffer(tv);
-
     const char *str = g_cd ? problem_data_get_content_or_NULL(g_cd, name) : NULL;
     /* Bad: will choke at any text with non-Unicode parts: */
     /* gtk_text_buffer_set_text(tb, (str ? str : ""), -1);*/
     /* Start torturing ourself instead: */
 
-    GtkTextIter beg_iter, end_iter;
-    gtk_text_buffer_get_iter_at_offset(tb, &beg_iter, 0);
-    gtk_text_buffer_get_iter_at_offset(tb, &end_iter, -1);
-    gtk_text_buffer_delete(tb, &beg_iter, &end_iter);
-
-    if (!str)
-        return;
-
-    const gchar *end;
-    while (!g_utf8_validate(str, -1, &end))
-    {
-        gtk_text_buffer_insert_at_cursor(tb, str, end - str);
-        char buf[8];
-        unsigned len = snprintf(buf, sizeof(buf), "<%02X>", (unsigned char)*end);
-        gtk_text_buffer_insert_at_cursor(tb, buf, len);
-        str = end + 1;
-    }
-    gtk_text_buffer_insert_at_cursor(tb, str, strlen(str));
+    reload_text_to_text_view(tv, str);
 }
 
 static gchar *get_malloced_string_from_text_view(GtkTextView *tv)
-- 
1.8.3.1