Convert ProblemReportDialog to use Inspector views

Aside from the code re-use, this allows including log files in problem
reports.
This commit is contained in:
Michael Gratton 2019-07-04 13:30:42 +10:00 committed by Michael James Gratton
parent 2841cb0b66
commit 6f65062219
8 changed files with 450 additions and 169 deletions

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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);
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.error_pane = new Components.InspectorErrorView(
error, account, service
);
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();
public override bool key_press_event(Gdk.EventKey event) {
bool ret = Gdk.EVENT_PROPAGATE;
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);
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 (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 (ret == Gdk.EVENT_PROPAGATE) {
ret = base.key_press_event(event);
}
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);
}
}
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 (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 (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());
}
}
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();
}
}

View file

@ -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',

View file

@ -80,6 +80,7 @@ geary_bin_dependencies = [
gmime,
goa,
gtk,
libhandy,
libmath,
libsoup,
webkit2gtk,

View file

@ -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>

View file

@ -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>

View file

@ -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="position">1</property>
</packing>
</child>
<child type="title">
<object class="GtkStackSwitcher">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stack">stack</property>
</object>
</child>
<child>
<object class="GtkButton" id="save_as_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">Save logs entries and details</property>
<signal name="clicked" handler="on_save_as_clicked" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-save-as-symbolic</property>
</object>
</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="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-copy-symbolic</property>
</object>
</child>
</object>
<packing>
<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>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</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="GtkStack" id="stack">
<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>
<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="width_request">600</property>
<property name="height_request">200</property>
<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>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<signal name="notify::visible-child" handler="on_visible_child_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">0</property>
</packing>
</child>
</object>