diff --git a/doc/TODO b/doc/TODO index 21c82679c..4af54423c 100644 --- a/doc/TODO +++ b/doc/TODO @@ -50,7 +50,6 @@ Downloads Improvements / minor features ============================= -- Make sure Question objects are deleteLater'ed correctly. - qutebrowser local_file.foo should open that file in $PWD - Distinction between :q and :wq, add ZZ and ZQ shortcuts. - set_toggle to toggle setting between two states diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index ce6050f41..1525fe114 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -389,11 +389,10 @@ class DownloadManager(QObject): q.default = suggested_filename q.answered.connect(download.set_filename) q.cancelled.connect(download.cancel) - q.answered.connect(q.deleteLater) - q.cancelled.connect(q.deleteLater) + q.completed.connect(q.deleteLater) + q.destroyed.connect(partial(self.questions.remove, q)) self.questions.append(q) download.cancelled.connect(q.abort) - download.cancelled.connect(q.deleteLater) message.instance().ask(q, blocking=False) @pyqtSlot(DownloadItem) diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py index 6f67f1def..5a5409732 100644 --- a/qutebrowser/utils/message.py +++ b/qutebrowser/utils/message.py @@ -97,6 +97,7 @@ def ask_async(message, mode, handler, default=None): q.mode = mode q.default = default q.answered.connect(handler) + q.completed.connect(q.deleteLater) bridge.ask(q, blocking=False) @@ -117,6 +118,7 @@ def confirm_async(message, yes_action, no_action=None, default=None): q.answered_yes.connect(yes_action) if no_action is not None: q.answered_no.connect(no_action) + q.completed.connect(q.deleteLater) bridge.ask(q, blocking=False) diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index ecbc6d4eb..3de928b8a 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -240,6 +240,10 @@ class Question(QObject): """A question asked to the user, e.g. via the status bar. + Note the creator is responsible for cleaning up the question after it + doesn't need it anymore, e.g. via connecting Question.completed to + Question.deleteLater. + Attributes: mode: A PromptMode enum member. yesno: A question which can be answered with yes/no. @@ -256,8 +260,6 @@ class Question(QObject): Signals: answered: Emitted when the question has been answered by the user. - This is emitted from qutebrowser.widgets.statusbar._prompt so - it can be emitted after the mode is left. arg: The answer to the question. cancelled: Emitted when the question has been cancelled by the user. aborted: Emitted when the question was aborted programatically. @@ -266,6 +268,7 @@ class Question(QObject): answered with yes. answered_no: Convienience signal emitted when a yesno question was answered with no. + completed: Emitted when the question was completed in any way. """ answered = pyqtSignal(object) @@ -273,6 +276,7 @@ class Question(QObject): aborted = pyqtSignal() answered_yes = pyqtSignal() answered_no = pyqtSignal() + completed = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) @@ -286,6 +290,21 @@ class Question(QObject): def __repr__(self): return '<{} "{}">'.format(self.__class__.__name__, self.text) + def done(self): + """Must be called when the queston was answered completely.""" + self.answered.emit(self.answer) + if self.mode == PromptMode.yesno: + if self.answer: + self.answered_yes.emit() + else: + self.answered_no.emit() + self.completed.emit() + + def cancel(self): + """Cancel the question (resulting from user-input).""" + self.cancelled.emit() + self.completed.emit() + def abort(self): """Abort the question. @@ -295,6 +314,7 @@ class Question(QObject): self.is_aborted = True try: self.aborted.emit() + self.completed.emit() except TypeError as e: # FIXME # We seem to get "pyqtSignal must be bound to a QObject, not diff --git a/qutebrowser/widgets/statusbar/prompt.py b/qutebrowser/widgets/statusbar/prompt.py index 852ba92ed..e098e1879 100644 --- a/qutebrowser/widgets/statusbar/prompt.py +++ b/qutebrowser/widgets/statusbar/prompt.py @@ -76,7 +76,7 @@ class Prompt(QWidget): self._input.setEchoMode(QLineEdit.Normal) self.hide_prompt.emit() if self.question.answer is None and not self.question.is_aborted: - self.question.cancelled.emit() + self.question.cancel() @cmdutils.register(instance='mainwindow.status.prompt', hide=True, modes=['prompt']) @@ -99,22 +99,22 @@ class Prompt(QWidget): self.question.answer = (self.question.user, password) modeman.leave('prompt', 'prompt accept') self.hide_prompt.emit() - self.question.answered.emit(self.question.answer) + self.question.done() elif self.question.mode == PromptMode.text: # User just entered text. self.question.answer = self._input.text() modeman.leave('prompt', 'prompt accept') - self.question.answered.emit(self.question.answer) + self.question.done() elif self.question.mode == PromptMode.yesno: # User wants to accept the default of a yes/no question. self.question.answer = self.question.default modeman.leave('yesno', 'yesno accept') - self.question.answered.emit(self.question.answer) + self.question.done() elif self.question.mode == PromptMode.alert: # User acknowledged an alert self.question.answer = None modeman.leave('prompt', 'alert accept') - self.question.answered.emit(self.question.answer) + self.question.done() else: raise ValueError("Invalid question mode!") @@ -127,8 +127,7 @@ class Prompt(QWidget): return self.question.answer = True modeman.leave('yesno', 'yesno accept') - self.question.answered.emit(self.question.answer) - self.question.answered_yes.emit() + self.question.done() @cmdutils.register(instance='mainwindow.status.prompt', hide=True, modes=['yesno']) @@ -139,8 +138,7 @@ class Prompt(QWidget): return self.question.answer = False modeman.leave('yesno', 'prompt accept') - self.question.answered.emit(self.question.answer) - self.question.answered_no.emit() + self.question.done() @pyqtSlot(Question, bool) def ask_question(self, question, blocking):