Crash dialog redesign.

We now have "Report/Don't report" buttons and a restart checkbox (checked by
default), so users don't accidentally send reports when they don't want to.
This commit is contained in:
Florian Bruhin 2015-01-24 20:13:18 +01:00
parent 7a11be1fb1
commit 9702433d4e

View File

@ -26,11 +26,11 @@ import sys
import html import html
import getpass import getpass
import traceback import traceback
import functools
from PyQt5.QtCore import pyqtSlot, Qt, QSize, QT_VERSION_STR from PyQt5.QtCore import pyqtSlot, Qt, QSize, QT_VERSION_STR
from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton, from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton,
QVBoxLayout, QHBoxLayout, QCheckBox) QVBoxLayout, QHBoxLayout, QCheckBox,
QDialogButtonBox)
import qutebrowser import qutebrowser
from qutebrowser.utils import version, log, utils, objreg from qutebrowser.utils import version, log, utils, objreg
@ -72,12 +72,11 @@ class _CrashDialog(QDialog):
_vbox: The main QVBoxLayout _vbox: The main QVBoxLayout
_lbl: The QLabel with the static text _lbl: The QLabel with the static text
_debug_log: The QTextEdit with the crash information _debug_log: The QTextEdit with the crash information
_hbox: The QHboxLayout containing the buttons _btn_box: The QDialogButtonBox containing the buttons.
_url: Pastebin URL QLabel. _url: Pastebin URL QLabel.
_crash_info: A list of tuples with title and crash information. _crash_info: A list of tuples with title and crash information.
_paste_client: A PastebinClient instance to use. _paste_client: A PastebinClient instance to use.
_paste_text: The text to pastebin. _paste_text: The text to pastebin.
_resolution: Whether the dialog should be accepted on close.
""" """
def __init__(self, debug, parent=None): def __init__(self, debug, parent=None):
@ -89,12 +88,12 @@ class _CrashDialog(QDialog):
super().__init__(parent) super().__init__(parent)
# We don't set WA_DeleteOnClose here as on an exception, we'll get # We don't set WA_DeleteOnClose here as on an exception, we'll get
# closed anyways, and it only could have unintended side-effects. # closed anyways, and it only could have unintended side-effects.
self._buttons = []
self._crash_info = [] self._crash_info = []
self._hbox = None self._btn_box = None
self._btn_report = None
self._btn_cancel = None
self._chk_restore = None
self._lbl = None self._lbl = None
self._chk_report = None
self._resolution = None
self._paste_text = None self._paste_text = None
self.setWindowTitle("Whoops!") self.setWindowTitle("Whoops!")
self.resize(QSize(640, 600)) self.resize(QSize(640, 600))
@ -104,7 +103,8 @@ class _CrashDialog(QDialog):
contact = QLabel("I'd like to be able to follow up with you, to keep " contact = QLabel("I'd like to be able to follow up with you, to keep "
"you posted on the status of this crash and get more " "you posted on the status of this crash and get more "
"information if I need it - how can I contact you?") "information if I need it - how can I contact you?",
wordWrap=True)
self._vbox.addWidget(contact) self._vbox.addWidget(contact)
self._contact = QTextEdit(tabChangesFocus=True, acceptRichText=False) self._contact = QTextEdit(tabChangesFocus=True, acceptRichText=False)
try: try:
@ -143,7 +143,8 @@ class _CrashDialog(QDialog):
self._vbox.addWidget(self._debug_log, 10) self._vbox.addWidget(self._debug_log, 10)
self._vbox.addSpacing(15) self._vbox.addSpacing(15)
self._init_checkboxes(debug) self._init_checkboxes()
self._init_info_text()
self._init_buttons() self._init_buttons()
def __repr__(self): def __repr__(self):
@ -157,28 +158,31 @@ class _CrashDialog(QDialog):
textInteractionFlags=Qt.LinksAccessibleByMouse) textInteractionFlags=Qt.LinksAccessibleByMouse)
self._vbox.addWidget(self._lbl) self._vbox.addWidget(self._lbl)
def _init_checkboxes(self, debug): def _init_checkboxes(self):
"""Initialize the checkboxes. """Initialize the checkboxes."""
self._chk_restore = QCheckBox("Restore open pages")
Args: self._chk_restore.setChecked(True)
debug: Whether a --debug arg was given. self._vbox.addWidget(self._chk_restore)
"""
self._chk_report = QCheckBox("Send a report")
if not debug:
self._chk_report.setChecked(True)
self._vbox.addWidget(self._chk_report)
info_label = QLabel("<i>Note that without your help, I can't fix the "
"bug you encountered.</i>", wordWrap=True)
self._vbox.addWidget(info_label)
def _init_buttons(self): def _init_buttons(self):
"""Initialize the buttons. """Initialize the buttons."""
self._btn_box = QDialogButtonBox()
self._vbox.addWidget(self._btn_box)
Should be extended by subclasses to provide the actual buttons. self._btn_report = QPushButton("Report", default=True)
""" self._btn_report.clicked.connect(self.on_report_clicked)
self._hbox = QHBoxLayout() self._btn_box.addButton(self._btn_report, QDialogButtonBox.AcceptRole)
self._vbox.addLayout(self._hbox)
self._hbox.addStretch() self._btn_cancel = QPushButton("Don't report", autoDefault=False)
self._btn_cancel.clicked.connect(self.finish)
self._btn_box.addButton(self._btn_cancel, QDialogButtonBox.RejectRole)
def _init_info_text(self):
"""Add an info text encouraging the user to report crashes."""
info_label = QLabel("<br/><b>Note that without your help, I can't fix "
"the bug you encountered.<br/>I read and respond "
"to all crash reports!</b>", wordWrap=True)
self._vbox.addWidget(info_label)
def _gather_crash_info(self): def _gather_crash_info(self):
"""Gather crash information to display. """Gather crash information to display.
@ -227,6 +231,14 @@ class _CrashDialog(QDialog):
title += ' - {}'.format(desc) title += ' - {}'.format(desc)
return title return title
def _save_contact_info(self):
"""Save the contact info to disk."""
try:
state = objreg.get('state-config')
state['general']['contact-info'] = self._contact.toPlainText()
except Exception:
log.misc.exception("Failed to save contact information!")
def report(self): def report(self):
"""Paste the crash info into the pastebin.""" """Paste the crash info into the pastebin."""
lines = [] lines = []
@ -252,17 +264,14 @@ class _CrashDialog(QDialog):
self.show_error(exc_text) self.show_error(exc_text)
@pyqtSlot() @pyqtSlot()
def on_button_clicked(self, button, accept): def on_report_clicked(self):
"""Report and close dialog if button was clicked.""" """Report and close dialog if report button was clicked."""
button.setText("Reporting...") self._btn_report.setEnabled(False)
for btn in self._buttons: self._btn_cancel.setEnabled(False)
btn.setEnabled(False) self._btn_report.setText("Reporting...")
self._resolution = accept
self._paste_client.success.connect(self.finish) self._paste_client.success.connect(self.finish)
self._paste_client.error.connect(self.show_error) self._paste_client.error.connect(self.show_error)
reported = self.maybe_report() self.report()
if not reported:
self.finish()
@pyqtSlot(str) @pyqtSlot(str)
def show_error(self, text): def show_error(self, text):
@ -277,29 +286,9 @@ class _CrashDialog(QDialog):
@pyqtSlot() @pyqtSlot()
def finish(self): def finish(self):
"""Accept/reject the dialog when reporting is done.""" """Save contact info and close the dialog."""
try: self._save_contact_info()
state = objreg.get('state-config') self.accept()
state['general']['contact-info'] = self._contact.toPlainText()
except Exception:
log.misc.exception("Failed to save contact information!")
if self._resolution:
self.accept()
else:
self.reject()
@pyqtSlot()
def maybe_report(self):
"""Report the bug if the user allowed us to.
Return:
True if a report was done, False otherwise.
"""
if self._chk_report.isChecked():
self.report()
return True
else:
return False
class ExceptionCrashDialog(_CrashDialog): class ExceptionCrashDialog(_CrashDialog):
@ -307,7 +296,6 @@ class ExceptionCrashDialog(_CrashDialog):
"""Dialog which gets shown on an exception. """Dialog which gets shown on an exception.
Attributes: Attributes:
_buttons: A list of buttons.
_pages: A list of lists of the open pages (URLs as strings) _pages: A list of lists of the open pages (URLs as strings)
_cmdhist: A list with the command history (as strings) _cmdhist: A list with the command history (as strings)
_exc: An exception tuple (type, value, traceback) _exc: An exception tuple (type, value, traceback)
@ -331,26 +319,12 @@ class ExceptionCrashDialog(_CrashDialog):
def _init_buttons(self): def _init_buttons(self):
super()._init_buttons() super()._init_buttons()
btn_quit = QPushButton("Quit")
btn_quit.clicked.connect(
functools.partial(self.on_button_clicked, btn_quit, False))
self._hbox.addWidget(btn_quit)
btn_restart = QPushButton("Restart", default=True) def _init_checkboxes(self):
btn_restart.clicked.connect( """Add checkboxes to the dialog."""
functools.partial(self.on_button_clicked, btn_restart, True)) super()._init_checkboxes()
self._hbox.addWidget(btn_restart) self._chk_log = QCheckBox("Include a debug log in the report",
checked=True)
self._buttons = [btn_quit, btn_restart]
def _init_checkboxes(self, debug):
"""Add checkboxes to send crash report."""
super()._init_checkboxes(debug)
self._chk_log = QCheckBox("Include a debug log and a list of open "
"pages", checked=True)
if debug:
self._chk_log.setChecked(False)
self._chk_log.setEnabled(False)
try: try:
if config.get('general', 'private-browsing'): if config.get('general', 'private-browsing'):
self._chk_log.setChecked(False) self._chk_log.setChecked(False)
@ -363,7 +337,6 @@ class ExceptionCrashDialog(_CrashDialog):
"information such as which pages you visited " "information such as which pages you visited "
"or keyboard input.</i>", wordWrap=True) "or keyboard input.</i>", wordWrap=True)
self._vbox.addWidget(info_label) self._vbox.addWidget(info_label)
self._chk_report.toggled.connect(self.on_chk_report_toggled)
def _get_error_type(self): def _get_error_type(self):
return 'exception' return 'exception'
@ -393,11 +366,12 @@ class ExceptionCrashDialog(_CrashDialog):
("Debug log", traceback.format_exc())) ("Debug log", traceback.format_exc()))
@pyqtSlot() @pyqtSlot()
def on_chk_report_toggled(self): def finish(self):
"""Disable log checkbox if report is disabled.""" self._save_contact_info()
is_checked = self._chk_report.isChecked() if self._chk_restore.isChecked():
self._chk_log.setEnabled(is_checked) self.accept()
self._chk_log.setChecked(is_checked) else:
self.reject()
class FatalCrashDialog(_CrashDialog): class FatalCrashDialog(_CrashDialog):
@ -436,14 +410,6 @@ class FatalCrashDialog(_CrashDialog):
"stacktrace.asciidoc</a> to submit a stacktrace.<br/>") "stacktrace.asciidoc</a> to submit a stacktrace.<br/>")
self._lbl.setText(text) self._lbl.setText(text)
def _init_buttons(self):
super()._init_buttons()
btn_ok = QPushButton(text="OK", default=True)
btn_ok.clicked.connect(
functools.partial(self.on_button_clicked, btn_ok, True))
self._hbox.addWidget(btn_ok)
self._buttons = [btn_ok]
def _gather_crash_info(self): def _gather_crash_info(self):
self._crash_info += [ self._crash_info += [
("Fault log", self._log), ("Fault log", self._log),
@ -464,7 +430,6 @@ class ReportDialog(_CrashDialog):
def __init__(self, pages, cmdhist, objects, parent=None): def __init__(self, pages, cmdhist, objects, parent=None):
super().__init__(False, parent) super().__init__(False, parent)
self.setAttribute(Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_DeleteOnClose)
self._btn_report = None
self._pages = pages self._pages = pages
self._cmdhist = cmdhist self._cmdhist = cmdhist
self._objects = objects self._objects = objects
@ -475,18 +440,14 @@ class ReportDialog(_CrashDialog):
text = "Please describe the bug you encountered below." text = "Please describe the bug you encountered below."
self._lbl.setText(text) self._lbl.setText(text)
def _init_buttons(self): def _init_checkboxes(self):
super()._init_buttons()
self._btn_report = QPushButton("Report", default=True)
self._btn_report.clicked.connect(
functools.partial(self.on_button_clicked, self._btn_report, True))
self._hbox.addWidget(self._btn_report)
self._buttons = [self._btn_report]
def _init_checkboxes(self, _debug):
"""We don't want any checkboxes as the user wanted to report.""" """We don't want any checkboxes as the user wanted to report."""
pass pass
def _init_info_text(self):
"""We don't want an info text as the user wanted to report."""
pass
def _get_error_type(self): def _get_error_type(self):
return 'report' return 'report'
@ -503,16 +464,6 @@ class ReportDialog(_CrashDialog):
except Exception: except Exception:
self._crash_info.append(("Debug log", traceback.format_exc())) self._crash_info.append(("Debug log", traceback.format_exc()))
@pyqtSlot()
def maybe_report(self):
"""Report the crash.
We don't have a "Send a report" checkbox here because it was a manual
report, which would be pretty useless without this info.
"""
self.report()
return True
class ReportErrorDialog(QDialog): class ReportErrorDialog(QDialog):