Clean up Question objects correctly

This commit is contained in:
Florian Bruhin 2014-06-27 07:56:16 +02:00
parent cb6550debb
commit b79cdbc416
5 changed files with 33 additions and 15 deletions

View File

@ -50,7 +50,6 @@ Downloads
Improvements / minor features Improvements / minor features
============================= =============================
- Make sure Question objects are deleteLater'ed correctly.
- qutebrowser local_file.foo should open that file in $PWD - qutebrowser local_file.foo should open that file in $PWD
- Distinction between :q and :wq, add ZZ and ZQ shortcuts. - Distinction between :q and :wq, add ZZ and ZQ shortcuts.
- set_toggle to toggle setting between two states - set_toggle to toggle setting between two states

View File

@ -389,11 +389,10 @@ class DownloadManager(QObject):
q.default = suggested_filename q.default = suggested_filename
q.answered.connect(download.set_filename) q.answered.connect(download.set_filename)
q.cancelled.connect(download.cancel) q.cancelled.connect(download.cancel)
q.answered.connect(q.deleteLater) q.completed.connect(q.deleteLater)
q.cancelled.connect(q.deleteLater) q.destroyed.connect(partial(self.questions.remove, q))
self.questions.append(q) self.questions.append(q)
download.cancelled.connect(q.abort) download.cancelled.connect(q.abort)
download.cancelled.connect(q.deleteLater)
message.instance().ask(q, blocking=False) message.instance().ask(q, blocking=False)
@pyqtSlot(DownloadItem) @pyqtSlot(DownloadItem)

View File

@ -97,6 +97,7 @@ def ask_async(message, mode, handler, default=None):
q.mode = mode q.mode = mode
q.default = default q.default = default
q.answered.connect(handler) q.answered.connect(handler)
q.completed.connect(q.deleteLater)
bridge.ask(q, blocking=False) 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) q.answered_yes.connect(yes_action)
if no_action is not None: if no_action is not None:
q.answered_no.connect(no_action) q.answered_no.connect(no_action)
q.completed.connect(q.deleteLater)
bridge.ask(q, blocking=False) bridge.ask(q, blocking=False)

View File

@ -240,6 +240,10 @@ class Question(QObject):
"""A question asked to the user, e.g. via the status bar. """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: Attributes:
mode: A PromptMode enum member. mode: A PromptMode enum member.
yesno: A question which can be answered with yes/no. yesno: A question which can be answered with yes/no.
@ -256,8 +260,6 @@ class Question(QObject):
Signals: Signals:
answered: Emitted when the question has been answered by the user. 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. arg: The answer to the question.
cancelled: Emitted when the question has been cancelled by the user. cancelled: Emitted when the question has been cancelled by the user.
aborted: Emitted when the question was aborted programatically. aborted: Emitted when the question was aborted programatically.
@ -266,6 +268,7 @@ class Question(QObject):
answered with yes. answered with yes.
answered_no: Convienience signal emitted when a yesno question was answered_no: Convienience signal emitted when a yesno question was
answered with no. answered with no.
completed: Emitted when the question was completed in any way.
""" """
answered = pyqtSignal(object) answered = pyqtSignal(object)
@ -273,6 +276,7 @@ class Question(QObject):
aborted = pyqtSignal() aborted = pyqtSignal()
answered_yes = pyqtSignal() answered_yes = pyqtSignal()
answered_no = pyqtSignal() answered_no = pyqtSignal()
completed = pyqtSignal()
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
@ -286,6 +290,21 @@ class Question(QObject):
def __repr__(self): def __repr__(self):
return '<{} "{}">'.format(self.__class__.__name__, self.text) 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): def abort(self):
"""Abort the question. """Abort the question.
@ -295,6 +314,7 @@ class Question(QObject):
self.is_aborted = True self.is_aborted = True
try: try:
self.aborted.emit() self.aborted.emit()
self.completed.emit()
except TypeError as e: except TypeError as e:
# FIXME # FIXME
# We seem to get "pyqtSignal must be bound to a QObject, not # We seem to get "pyqtSignal must be bound to a QObject, not

View File

@ -76,7 +76,7 @@ class Prompt(QWidget):
self._input.setEchoMode(QLineEdit.Normal) self._input.setEchoMode(QLineEdit.Normal)
self.hide_prompt.emit() self.hide_prompt.emit()
if self.question.answer is None and not self.question.is_aborted: 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, @cmdutils.register(instance='mainwindow.status.prompt', hide=True,
modes=['prompt']) modes=['prompt'])
@ -99,22 +99,22 @@ class Prompt(QWidget):
self.question.answer = (self.question.user, password) self.question.answer = (self.question.user, password)
modeman.leave('prompt', 'prompt accept') modeman.leave('prompt', 'prompt accept')
self.hide_prompt.emit() self.hide_prompt.emit()
self.question.answered.emit(self.question.answer) self.question.done()
elif self.question.mode == PromptMode.text: elif self.question.mode == PromptMode.text:
# User just entered text. # User just entered text.
self.question.answer = self._input.text() self.question.answer = self._input.text()
modeman.leave('prompt', 'prompt accept') modeman.leave('prompt', 'prompt accept')
self.question.answered.emit(self.question.answer) self.question.done()
elif self.question.mode == PromptMode.yesno: elif self.question.mode == PromptMode.yesno:
# User wants to accept the default of a yes/no question. # User wants to accept the default of a yes/no question.
self.question.answer = self.question.default self.question.answer = self.question.default
modeman.leave('yesno', 'yesno accept') modeman.leave('yesno', 'yesno accept')
self.question.answered.emit(self.question.answer) self.question.done()
elif self.question.mode == PromptMode.alert: elif self.question.mode == PromptMode.alert:
# User acknowledged an alert # User acknowledged an alert
self.question.answer = None self.question.answer = None
modeman.leave('prompt', 'alert accept') modeman.leave('prompt', 'alert accept')
self.question.answered.emit(self.question.answer) self.question.done()
else: else:
raise ValueError("Invalid question mode!") raise ValueError("Invalid question mode!")
@ -127,8 +127,7 @@ class Prompt(QWidget):
return return
self.question.answer = True self.question.answer = True
modeman.leave('yesno', 'yesno accept') modeman.leave('yesno', 'yesno accept')
self.question.answered.emit(self.question.answer) self.question.done()
self.question.answered_yes.emit()
@cmdutils.register(instance='mainwindow.status.prompt', hide=True, @cmdutils.register(instance='mainwindow.status.prompt', hide=True,
modes=['yesno']) modes=['yesno'])
@ -139,8 +138,7 @@ class Prompt(QWidget):
return return
self.question.answer = False self.question.answer = False
modeman.leave('yesno', 'prompt accept') modeman.leave('yesno', 'prompt accept')
self.question.answered.emit(self.question.answer) self.question.done()
self.question.answered_no.emit()
@pyqtSlot(Question, bool) @pyqtSlot(Question, bool)
def ask_question(self, question, blocking): def ask_question(self, question, blocking):