[geary/wip/problem-report-logs: 13/22] Convert ProblemReportDialog to use Inspector views



commit f1ae9b19b4a0b52a0f2f2541567d2d1b9cd70d1b
Author: Michael Gratton <mike vee net>
Date:   Thu Jul 4 13:30:42 2019 +1000

    Convert ProblemReportDialog to use Inspector views
    
    Aside from the code re-use, this allows including log files in problem
    reports.

 .../components-inspector-error-view.vala           |  75 ++++++
 src/client/components/main-window-info-bar.vala    |  12 +-
 .../dialogs/dialogs-problem-details-dialog.vala    | 265 ++++++++++++++++-----
 src/client/meson.build                             |   1 +
 src/meson.build                                    |   1 +
 ui/components-inspector-error-view.ui              |  95 ++++++++
 ui/org.gnome.Geary.gresource.xml                   |   1 +
 ui/problem-details-dialog.ui                       | 157 +++++-------
 8 files changed, 444 insertions(+), 163 deletions(-)
---
diff --git a/src/client/components/components-inspector-error-view.vala 
b/src/client/components/components-inspector-error-view.vala
new file mode 100644
index 00000000..2850701a
--- /dev/null
+++ b/src/client/components/components-inspector-error-view.vala
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2019 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * A view that displays information about an application error.
+ */
+[GtkTemplate (ui = "/org/gnome/Geary/components-inspector-error-view.ui")]
+public class Components.InspectorErrorView : Gtk.Grid {
+
+
+    [GtkChild]
+    private Gtk.TextView problem_text;
+
+    private string details;
+
+
+    public InspectorErrorView(Geary.ErrorContext error,
+                              Geary.AccountInformation? account,
+                              Geary.ServiceInformation? service) {
+        this.details = format_problem(error, account, service);
+        this.problem_text.buffer.text = this.details;
+    }
+
+    public void save(GLib.DataOutputStream out, GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        out.put_string(this.details, cancellable);
+    }
+
+    private string format_problem(Geary.ErrorContext error,
+                                  Geary.AccountInformation? account,
+                                  Geary.ServiceInformation? service) {
+        StringBuilder details = new StringBuilder();
+        if (account != null) {
+            details.append_printf(
+                "Account id: %s\n",
+                account.id
+            );
+            details.append_printf(
+                "Account provider: %s\n",
+                account.service_provider.to_string()
+            );
+        }
+        if (service != null) {
+            details.append_printf(
+                "Service type: %s\n",
+                service.protocol.to_string()
+            );
+            details.append_printf(
+                "Service host: %s\n",
+                service.host
+            );
+        }
+        if (error == null) {
+            details.append("No error reported");
+        } else {
+            details.append_printf(
+                "Error type: %s\n", error.format_error_type()
+            );
+            details.append_printf(
+                "Message: %s\n", error.thrown.message
+            );
+            details.append("Back trace:\n");
+            foreach (Geary.ErrorContext.StackFrame frame in
+                     error.backtrace) {
+                details.append_printf(" - %s\n", frame.to_string());
+            }
+        }
+        return details.str;
+    }
+
+}
diff --git a/src/client/components/main-window-info-bar.vala b/src/client/components/main-window-info-bar.vala
index 1084c71e..11ee77ce 100644
--- a/src/client/components/main-window-info-bar.vala
+++ b/src/client/components/main-window-info-bar.vala
@@ -123,9 +123,17 @@ public class MainWindowInfoBar : Gtk.InfoBar {
     }
 
     private void show_details() {
+        Geary.ServiceProblemReport? service_report =
+            this.report as Geary.ServiceProblemReport;
+        Geary.AccountProblemReport? account_report =
+            this.report as Geary.AccountProblemReport;
+
         Dialogs.ProblemDetailsDialog dialog =
-            new Dialogs.ProblemDetailsDialog.for_problem_report(
-                get_toplevel() as Gtk.Window, this.report
+            new Dialogs.ProblemDetailsDialog(
+                get_toplevel() as MainWindow,
+                this.report.error,
+                account_report != null ? account_report.account : null,
+                service_report != null ? service_report.service : null
         );
         dialog.run();
         dialog.destroy();
diff --git a/src/client/dialogs/dialogs-problem-details-dialog.vala 
b/src/client/dialogs/dialogs-problem-details-dialog.vala
index 32bb55d1..8716217f 100644
--- a/src/client/dialogs/dialogs-problem-details-dialog.vala
+++ b/src/client/dialogs/dialogs-problem-details-dialog.vala
@@ -9,18 +9,46 @@
  * Displays technical details when a problem has been reported.
  */
 [GtkTemplate (ui = "/org/gnome/Geary/problem-details-dialog.ui")]
-public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
+public class Dialogs.ProblemDetailsDialog : Hdy.Dialog {
 
 
+    private const string ACTION_CLOSE = "problem-details-close";
+    private const string ACTION_SEARCH_TOGGLE = "toggle-search";
+    private const string ACTION_SEARCH_ACTIVATE = "activate-search";
+
+    private const ActionEntry[] action_entries = {
+        {GearyApplication.ACTION_CLOSE, on_close },
+        {GearyApplication.ACTION_COPY,  on_copy_clicked },
+        {ACTION_CLOSE,                  on_close },
+        {ACTION_SEARCH_TOGGLE,          on_logs_search_toggled, null, "false" },
+        {ACTION_SEARCH_ACTIVATE,        on_logs_search_activated },
+    };
+
+    public static void add_window_accelerators(GearyApplication app) {
+        app.add_window_accelerators(ACTION_CLOSE, { "Escape" } );
+        app.add_window_accelerators(ACTION_SEARCH_ACTIVATE, { "<Ctrl>F" } );
+    }
+
+
+    [GtkChild]
+    private Gtk.Stack stack;
+
+    [GtkChild]
+    private Gtk.Button copy_button;
+
+    [GtkChild]
+    private Gtk.ToggleButton search_button;
+
+    private Components.InspectorErrorView error_pane;
+    private Components.InspectorLogView log_pane;
+    private Components.InspectorSystemView system_pane;
+
     private Geary.ErrorContext error;
     private Geary.AccountInformation? account;
     private Geary.ServiceInformation? service;
 
-    [GtkChild]
-    private Gtk.TextView detail_text;
-
 
-    public ProblemDetailsDialog(Gtk.Window parent,
+    public ProblemDetailsDialog(MainWindow parent,
                                 Geary.ErrorContext error,
                                 Geary.AccountInformation? account,
                                 Geary.ServiceInformation? service) {
@@ -28,84 +56,191 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
             transient_for: parent,
             use_header_bar: 1
         );
-        set_default_size(600, -1);
+        set_default_size(600, 400);
 
         this.error = error;
         this.account = account;
         this.service = service;
 
-        this.detail_text.buffer.text = format_details();
-    }
+        GLib.SimpleActionGroup actions = new GLib.SimpleActionGroup();
+        actions.add_action_entries(ProblemDetailsDialog.action_entries, this);
+        insert_action_group("win", actions);
+
+        this.error_pane = new Components.InspectorErrorView(
+            error, account, service
+        );
 
-    public ProblemDetailsDialog.for_problem_report(Gtk.Window parent,
-                                                   Geary.ProblemReport report) {
-        Geary.ServiceProblemReport? service_report =
-            report as Geary.ServiceProblemReport;
-        Geary.AccountProblemReport? account_report =
-            report as Geary.AccountProblemReport;
-        this(
-            parent,
-            report.error,
-            account_report != null ? account_report.account : null,
-            service_report != null ? service_report.service : null
+        this.log_pane = new Components.InspectorLogView(
+            parent.application.config, null
         );
+        this.log_pane.load();
+        this.log_pane.record_selection_changed.connect(
+            on_logs_selection_changed
+        );
+
+        this.system_pane = new Components.InspectorSystemView(
+            parent.application
+        );
+
+        /// Translators: Title for problem report dialog error
+        /// information pane
+        this.stack.add_titled(this.error_pane, "error_pane", _("Details"));
+        /// Translators: Title for problem report dialog logs pane
+        this.stack.add_titled(this.log_pane, "log_pane", _("Logs"));
+        /// Translators: Title for problem report system information
+        /// pane
+        this.stack.add_titled(this.system_pane, "system_pane", _("System"));
     }
 
-    private string format_details() {
-        StringBuilder details = new StringBuilder();
-
-        Gtk.ApplicationWindow? parent =
-            this.get_toplevel() as Gtk.ApplicationWindow;
-        GearyApplication? app = (parent != null)
-            ? parent.application as GearyApplication
-            : null;
-        if (app != null) {
-            foreach (GearyApplication.RuntimeDetail? detail
-                     in app.get_runtime_information()) {
-                details.append_printf("%s: %s", detail.name, detail.value);
-            }
+    public override bool key_press_event(Gdk.EventKey event) {
+        bool ret = Gdk.EVENT_PROPAGATE;
+
+        if (this.log_pane.search_mode_enabled &&
+            event.keyval == Gdk.Key.Escape) {
+            // Manually deactivate search so the button stays in sync
+            this.search_button.set_active(false);
+            ret = Gdk.EVENT_STOP;
         }
-        if (this.account != null) {
-            details.append_printf(
-                "Account id: %s\n",
-                this.account.id
-            );
-            details.append_printf(
-                "Account provider: %s\n",
-                this.account.service_provider.to_string()
-            );
+
+        if (ret == Gdk.EVENT_PROPAGATE &&
+            this.log_pane.search_mode_enabled) {
+            // Ensure <Space> and others are passed to the search
+            // entry before getting used as an accelerator.
+            ret = this.log_pane.handle_key_press(event);
         }
-        if (this.service != null) {
-            details.append_printf(
-                "Service type: %s\n",
-                this.service.protocol.to_string()
-            );
-            details.append_printf(
-                "Service host: %s\n",
-                this.service.host
-            );
+
+        if (ret == Gdk.EVENT_PROPAGATE) {
+            ret = base.key_press_event(event);
         }
-        if (this.error == null) {
-            details.append("No error reported");
-        } else {
-            details.append_printf(
-                "Error type: %s\n", this.error.format_error_type()
-            );
-            details.append_printf(
-                "Message: %s\n", this.error.thrown.message
-            );
-            details.append("Back trace:\n");
-            foreach (Geary.ErrorContext.StackFrame frame in
-                     this.error.backtrace) {
-                details.append_printf(" - %s\n", frame.to_string());
+
+        if (ret == Gdk.EVENT_PROPAGATE &&
+            !this.log_pane.search_mode_enabled) {
+            // Nothing has handled the event yet, and search is not
+            // active, so see if we want to activate it now.
+            ret = this.log_pane.handle_key_press(event);
+            if (ret == Gdk.EVENT_STOP) {
+                this.search_button.set_active(true);
             }
         }
-        return details.str;
+
+        return ret;
+    }
+
+    private async void save(string path,
+                            GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        GLib.File dest = GLib.File.new_for_path(path);
+        GLib.FileIOStream dest_io = yield dest.replace_readwrite_async(
+            null,
+            false,
+            GLib.FileCreateFlags.NONE,
+            GLib.Priority.DEFAULT,
+            cancellable
+        );
+        GLib.DataOutputStream out = new GLib.DataOutputStream(
+            new GLib.BufferedOutputStream(dest_io.get_output_stream())
+        );
+
+        this.error_pane.save(@out, cancellable);
+        out.put_byte('\n');
+        out.put_byte('\n');
+        this.system_pane.save(@out, cancellable);
+        out.put_byte('\n');
+        out.put_byte('\n');
+        this.log_pane.save(@out, true, cancellable);
+
+        yield out.close_async();
+        yield dest_io.close_async();
+    }
+
+    private void update_ui() {
+        bool logs_visible = this.stack.visible_child == this.log_pane;
+        uint logs_selected = this.log_pane.count_selected_records();
+        this.copy_button.set_sensitive(!logs_visible || logs_selected > 0);
+        this.search_button.set_visible(logs_visible);
     }
 
     [GtkCallback]
+    private void on_visible_child_changed() {
+        update_ui();
+    }
+
     private void on_copy_clicked() {
-        get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(format_details(), -1);
+        GLib.MemoryOutputStream bytes = new GLib.MemoryOutputStream.resizable();
+        GLib.DataOutputStream out = new GLib.DataOutputStream(bytes);
+        try {
+            if (this.stack.visible_child == this.error_pane) {
+                this.error_pane.save(@out, null);
+            } else if (this.stack.visible_child == this.log_pane) {
+                this.log_pane.save(@out, false, null);
+            } else if (this.stack.visible_child == this.system_pane) {
+                this.system_pane.save(@out, null);
+            }
+
+            // Ensure the data is a valid string
+            out.put_byte(0, null);
+        } catch (GLib.Error err) {
+            warning(
+                "Error saving inspector data for clipboard: %s",
+                err.message
+            );
+        }
+
+        string clipboard_value = (string) bytes.get_data();
+        if (!Geary.String.is_empty(clipboard_value)) {
+            get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(clipboard_value, -1);
+        }
+    }
+
+    [GtkCallback]
+    private void on_save_as_clicked() {
+        Gtk.FileChooserNative chooser = new Gtk.FileChooserNative(
+            _("Save As"),
+            this,
+            Gtk.FileChooserAction.SAVE,
+            _("Save As"),
+            _("Cancel")
+        );
+        chooser.set_current_name(
+            new GLib.DateTime.now_local().format(
+                "Geary Problem Report - %F %T.txt"
+            )
+        );
+
+        if (chooser.run() == Gtk.ResponseType.ACCEPT) {
+            this.save.begin(
+                chooser.get_filename(),
+                null,
+                (obj, res) => {
+                    try {
+                        this.save.end(res);
+                    } catch (GLib.Error err) {
+                        warning(
+                            "Failed to save problem report data: %s", err.message
+                        );
+                    }
+                }
+            );
+        }
+    }
+
+    private void on_logs_selection_changed() {
+        update_ui();
+    }
+
+    private void on_logs_search_toggled(GLib.SimpleAction action,
+                                        GLib.Variant? param) {
+        bool enabled = !((bool) action.state);
+        this.log_pane.search_mode_enabled = enabled;
+        action.set_state(enabled);
+    }
+
+    private void on_logs_search_activated() {
+        this.search_button.set_active(true);
+    }
+
+    private void on_close() {
+        destroy();
     }
 
 }
diff --git a/src/client/meson.build b/src/client/meson.build
index 95aab1e3..7f1a1df7 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -24,6 +24,7 @@ geary_client_vala_sources = files(
 
   'components/client-web-view.vala',
   'components/components-inspector.vala',
+  'components/components-inspector-error-view.vala',
   'components/components-inspector-log-view.vala',
   'components/components-inspector-system-view.vala',
   'components/components-placeholder-pane.vala',
diff --git a/src/meson.build b/src/meson.build
index 5ef921fd..31894cb3 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -80,6 +80,7 @@ geary_bin_dependencies = [
   gmime,
   goa,
   gtk,
+  libhandy,
   libmath,
   libsoup,
   webkit2gtk,
diff --git a/ui/components-inspector-error-view.ui b/ui/components-inspector-error-view.ui
new file mode 100644
index 00000000..140f8ee5
--- /dev/null
+++ b/ui/components-inspector-error-view.ui
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <requires lib="libhandy" version="0.0"/>
+  <template class="ComponentsInspectorErrorView" parent="GtkGrid">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="HdyColumn">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin_left">16</property>
+        <property name="margin_right">16</property>
+        <property name="margin_top">32</property>
+        <property name="margin_bottom">32</property>
+        <property name="maximum_width">500</property>
+        <property name="linear_growth_width">1</property>
+        <child>
+          <object class="GtkGrid">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="row_spacing">6</property>
+            <property name="column_spacing">12</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="halign">start</property>
+                <property name="valign">baseline</property>
+                <property name="margin_bottom">12</property>
+                <property name="hexpand">True</property>
+                <property name="label" translatable="yes">If the problem is serious or persists, please copy 
and send these details to the &lt;a href="https://wiki.gnome.org/Apps/Geary/Contact"&gt;mailing 
list&lt;/a&gt; or file a &lt;a href="https://wiki.gnome.org/Apps/Geary/ReportingABug"&gt;new bug 
report&lt;/a&gt;.</property>
+                <property name="use_markup">True</property>
+                <property name="wrap">True</property>
+                <property name="xalign">0</property>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">start</property>
+                <property name="valign">baseline</property>
+                <property name="label" translatable="yes">Details:</property>
+                <property name="track_visited_links">False</property>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkScrolledWindow">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
+                <property name="shadow_type">in</property>
+                <child>
+                  <object class="GtkTextView" id="problem_text">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hexpand">True</property>
+                    <property name="vexpand">True</property>
+                    <property name="editable">False</property>
+                    <property name="wrap_mode">none</property>
+                    <property name="left_margin">6</property>
+                    <property name="right_margin">6</property>
+                    <property name="top_margin">6</property>
+                    <property name="bottom_margin">6</property>
+                    <property name="cursor_visible">False</property>
+                    <property name="monospace">True</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">2</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">0</property>
+      </packing>
+    </child>
+  </template>
+</interface>
diff --git a/ui/org.gnome.Geary.gresource.xml b/ui/org.gnome.Geary.gresource.xml
index 1065416e..1883b4cd 100644
--- a/ui/org.gnome.Geary.gresource.xml
+++ b/ui/org.gnome.Geary.gresource.xml
@@ -11,6 +11,7 @@
     <file compressed="true">client-web-view.js</file>
     <file compressed="true">client-web-view-allow-remote-images.js</file>
     <file compressed="true" preprocess="xml-stripblanks">components-inspector.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">components-inspector-error-view.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">components-inspector-log-view.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">components-inspector-system-view.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">components-placeholder-pane.ui</file>
diff --git a/ui/problem-details-dialog.ui b/ui/problem-details-dialog.ui
index 0e7d2b88..6497cc6b 100644
--- a/ui/problem-details-dialog.ui
+++ b/ui/problem-details-dialog.ui
@@ -2,10 +2,9 @@
 <!-- Generated with glade 3.22.1 -->
 <interface>
   <requires lib="gtk+" version="3.20"/>
-  <template class="DialogsProblemDetailsDialog" parent="GtkDialog">
+  <requires lib="libhandy" version="0.0"/>
+  <template class="DialogsProblemDetailsDialog" parent="HdyDialog">
     <property name="can_focus">False</property>
-    <property name="modal">True</property>
-    <property name="type_hint">dialog</property>
     <child type="titlebar">
       <object class="GtkHeaderBar">
         <property name="visible">True</property>
@@ -13,123 +12,89 @@
         <property name="title" translatable="yes" comments="Dialog title for displaying technical details of 
a problem. Same as the button that invokes it.">Details</property>
         <property name="show_close_button">True</property>
         <child>
-          <object class="GtkButton">
-            <property name="label" translatable="yes" comments="Button label for copying technical 
information to the clipboard">Copy to Clipboard</property>
+          <object class="GtkToggleButton" id="search_button">
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <property name="receives_default">True</property>
-            <property name="tooltip_text" translatable="yes" comments="Button tooltip for copying technical 
information to the clipboard">Copy technical details to clipboard for pasting into an email or bug 
report</property>
-            <signal name="clicked" handler="on_copy_clicked" swapped="no"/>
-            <style>
-              <class name="suggested-action"/>
-            </style>
+            <property name="tooltip_text" translatable="yes" comments="Tooltip for problem report 
button">Search for matching log entries</property>
+            <property name="action_name">win.toggle-search</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">edit-find-symbolic</property>
+              </object>
+            </child>
           </object>
           <packing>
-            <property name="pack_type">end</property>
+            <property name="position">1</property>
           </packing>
         </child>
-      </object>
-    </child>
-    <child internal-child="vbox">
-      <object class="GtkBox">
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox">
+        <child type="title">
+          <object class="GtkStackSwitcher">
+            <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="layout_style">end</property>
-            <child>
-              <placeholder/>
-            </child>
-            <child>
-              <placeholder/>
-            </child>
+            <property name="stack">stack</property>
           </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">0</property>
-          </packing>
         </child>
         <child>
-          <object class="GtkGrid" id="problem_details">
+          <object class="GtkButton" id="save_as_button">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="margin_left">18</property>
-            <property name="margin_right">18</property>
-            <property name="margin_top">18</property>
-            <property name="margin_bottom">18</property>
-            <property name="row_spacing">6</property>
-            <property name="column_spacing">12</property>
-            <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="halign">start</property>
-                <property name="valign">baseline</property>
-                <property name="margin_bottom">12</property>
-                <property name="hexpand">True</property>
-                <property name="label" translatable="yes">If the problem is serious or persists, please copy 
and send these details to the &lt;a href="https://wiki.gnome.org/Apps/Geary/Contact"&gt;mailing 
list&lt;/a&gt; or file a &lt;a href="https://wiki.gnome.org/Apps/Geary/ReportingABug"&gt;new bug 
report&lt;/a&gt;.</property>
-                <property name="use_markup">True</property>
-                <property name="wrap">True</property>
-                <property name="xalign">0</property>
-              </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">0</property>
-              </packing>
-            </child>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="tooltip_text" translatable="yes" comments="Tooltip for problem report 
button">Save logs entries and details</property>
+            <signal name="clicked" handler="on_save_as_clicked" swapped="no"/>
             <child>
-              <object class="GtkLabel">
+              <object class="GtkImage">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
-                <property name="halign">start</property>
-                <property name="valign">baseline</property>
-                <property name="label" translatable="yes">Details:</property>
-                <property name="track_visited_links">False</property>
+                <property name="icon_name">document-save-as-symbolic</property>
               </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">1</property>
-              </packing>
             </child>
+          </object>
+          <packing>
+            <property name="pack_type">end</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="copy_button">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="tooltip_text" translatable="yes" comments="Tooltip for problem report 
button">Copy to clipboard</property>
+            <property name="action_name">win.copy</property>
             <child>
-              <object class="GtkScrolledWindow">
-                <property name="width_request">600</property>
-                <property name="height_request">200</property>
+              <object class="GtkImage">
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="hexpand">True</property>
-                <property name="vexpand">True</property>
-                <property name="shadow_type">in</property>
-                <child>
-                  <object class="GtkTextView" id="detail_text">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="vexpand">True</property>
-                    <property name="editable">False</property>
-                    <property name="wrap_mode">word</property>
-                    <property name="left_margin">6</property>
-                    <property name="right_margin">6</property>
-                    <property name="top_margin">6</property>
-                    <property name="bottom_margin">6</property>
-                    <property name="cursor_visible">False</property>
-                    <property name="monospace">True</property>
-                  </object>
-                </child>
+                <property name="can_focus">False</property>
+                <property name="icon_name">edit-copy-symbolic</property>
               </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">2</property>
-              </packing>
             </child>
           </object>
           <packing>
-            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="border_width">0</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkStack" id="stack">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <signal name="notify::visible-child" handler="on_visible_child_changed" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">True</property>
             <property name="fill">True</property>
-            <property name="position">1</property>
+            <property name="position">0</property>
           </packing>
         </child>
       </object>


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