Clean up message API
This commit is contained in:
parent
297c2bcad1
commit
92ff957543
2
doc/TODO
2
doc/TODO
@ -42,6 +42,7 @@ New big features
|
||||
Improvements / minor features
|
||||
=============================
|
||||
|
||||
- Make sure Question objects are deleteLater'ed correctly.
|
||||
- qutebrowser local_file.foo should open that file in $PWD
|
||||
- Downloading: Download to default filename if only path is given
|
||||
- Downloading: Remember last path for prompt, if explicitely set.
|
||||
@ -102,7 +103,6 @@ style
|
||||
=====
|
||||
|
||||
- Clean up the package checking mess in earlyinit.py
|
||||
- Clean up the message API (sync. vs. async.? queue or not?)
|
||||
- Use list models for completion and a proxy model which converts them to a
|
||||
tree?
|
||||
|
||||
|
@ -380,12 +380,12 @@ class Application(QApplication):
|
||||
tabs.hint_strings_updated.connect(kp['hint'].on_hint_strings_updated)
|
||||
|
||||
# messages
|
||||
self.messagebridge.error.connect(status.disp_error)
|
||||
self.messagebridge.info.connect(status.disp_temp_text)
|
||||
self.messagebridge.text.connect(status.set_text)
|
||||
self.messagebridge.set_cmd_text.connect(cmd.set_cmd_text)
|
||||
self.messagebridge.question.connect(status.prompt.ask_question,
|
||||
Qt.DirectConnection)
|
||||
self.messagebridge.s_error.connect(status.disp_error)
|
||||
self.messagebridge.s_info.connect(status.disp_temp_text)
|
||||
self.messagebridge.s_set_text.connect(status.set_text)
|
||||
self.messagebridge.s_set_cmd_text.connect(cmd.set_cmd_text)
|
||||
self.messagebridge.s_question.connect(status.prompt.ask_question,
|
||||
Qt.DirectConnection)
|
||||
|
||||
# config
|
||||
self.config.style_changed.connect(style.invalidate_caches)
|
||||
|
@ -415,4 +415,4 @@ class DownloadManager(QObject):
|
||||
@pyqtSlot(str)
|
||||
def on_error(self, msg):
|
||||
"""Display error message on download errors."""
|
||||
message.error("Download error: {}".format(msg), queue=True)
|
||||
message.error("Download error: {}".format(msg))
|
||||
|
@ -444,7 +444,7 @@ class HintManager(QObject):
|
||||
raise CommandError("No elements found.")
|
||||
ctx.target = target
|
||||
ctx.baseurl = baseurl
|
||||
message.text(self.HINT_TEXTS[target])
|
||||
message.instance().set_text(self.HINT_TEXTS[target])
|
||||
strings = self._hint_strings(visible_elems)
|
||||
for e, string in zip(visible_elems, strings):
|
||||
label = self._draw_label(e, string)
|
||||
@ -532,7 +532,8 @@ class HintManager(QObject):
|
||||
elif self._context.target in url_handlers:
|
||||
url = self._resolve_url(elem)
|
||||
if url is None:
|
||||
message.error("No suitable link found for this element.")
|
||||
message.error("No suitable link found for this element.",
|
||||
immediately=True)
|
||||
return
|
||||
url_handlers[self._context.target](url)
|
||||
else:
|
||||
@ -577,4 +578,4 @@ class HintManager(QObject):
|
||||
if not elem.label.isNull():
|
||||
elem.label.removeFromDocument()
|
||||
self._context = None
|
||||
message.clear()
|
||||
message.instance().set_text('')
|
||||
|
@ -51,7 +51,7 @@ def init():
|
||||
try:
|
||||
key, url = line.split(maxsplit=1)
|
||||
except ValueError:
|
||||
message.error("Invalid quickmark '{}'".format(line), queue=True)
|
||||
message.error("Invalid quickmark '{}'".format(line))
|
||||
else:
|
||||
marks[key] = url
|
||||
|
||||
@ -70,8 +70,8 @@ def prompt_save(url):
|
||||
"""
|
||||
qt_ensure_valid(url)
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
message.question("Add quickmark:", PromptMode.text,
|
||||
partial(quickmark_add, urlstr))
|
||||
message.ask_async("Add quickmark:", PromptMode.text,
|
||||
partial(quickmark_add, urlstr))
|
||||
|
||||
|
||||
@cmdutils.register()
|
||||
@ -92,8 +92,8 @@ def quickmark_add(urlstr, name):
|
||||
marks[name] = urlstr
|
||||
|
||||
if name in marks:
|
||||
message.confirm_action("Override existing quickmark?", set_mark,
|
||||
default=True)
|
||||
message.confirm_async("Override existing quickmark?", set_mark,
|
||||
default=True)
|
||||
else:
|
||||
set_mark()
|
||||
|
||||
|
@ -76,8 +76,7 @@ class BrowserPage(QWebPage):
|
||||
|
||||
http://www.riverbankcomputing.com/pipermail/pyqt/2014-June/034385.html
|
||||
"""
|
||||
answer = message.modular_question(
|
||||
"js: {}".format(msg), PromptMode.text, default)
|
||||
answer = message.ask("js: {}".format(msg), PromptMode.text, default)
|
||||
if answer is None:
|
||||
return (False, "")
|
||||
else:
|
||||
@ -149,7 +148,7 @@ class BrowserPage(QWebPage):
|
||||
"""Handle printing when requested via javascript."""
|
||||
if not check_print_compat():
|
||||
message.error("Printing on Qt < 5.3.0 on Windows is broken, "
|
||||
"please upgrade!")
|
||||
"please upgrade!", immediately=True)
|
||||
return
|
||||
printdiag = QPrintDialog()
|
||||
printdiag.setAttribute(Qt.WA_DeleteOnClose)
|
||||
@ -205,11 +204,11 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def javaScriptAlert(self, _frame, msg):
|
||||
"""Override javaScriptAlert to use the statusbar."""
|
||||
message.modular_question("js: {}".format(msg), PromptMode.alert)
|
||||
message.ask("js: {}".format(msg), PromptMode.alert)
|
||||
|
||||
def javaScriptConfirm(self, _frame, msg):
|
||||
"""Override javaScriptConfirm to use the statusbar."""
|
||||
ans = message.modular_question("js: {}".format(msg), PromptMode.yesno)
|
||||
ans = message.ask("js: {}".format(msg), PromptMode.yesno)
|
||||
return bool(ans)
|
||||
|
||||
def javaScriptConsoleMessage(self, msg, line, source):
|
||||
@ -228,8 +227,8 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def shouldInterruptJavaScript(self):
|
||||
"""Override shouldInterruptJavaScript to use the statusbar."""
|
||||
answer = message.modular_question("Interrupt long-running javascript?",
|
||||
PromptMode.yesno)
|
||||
answer = message.ask("Interrupt long-running javascript?",
|
||||
PromptMode.yesno)
|
||||
if answer is None:
|
||||
answer = True
|
||||
return answer
|
||||
|
@ -258,7 +258,7 @@ class CommandManager:
|
||||
try:
|
||||
self.run(text, count)
|
||||
except (CommandMetaError, CommandError) as e:
|
||||
message.error(e)
|
||||
message.error(e, immediately=True)
|
||||
|
||||
@pyqtSlot(str, int)
|
||||
def run_safely_init(self, text, count=None):
|
||||
@ -269,4 +269,4 @@ class CommandManager:
|
||||
try:
|
||||
self.run(text, count)
|
||||
except (CommandMetaError, CommandError) as e:
|
||||
message.error(e, queue=True)
|
||||
message.error(e)
|
||||
|
@ -270,7 +270,8 @@ class ConfigManager(QObject):
|
||||
except (NoOptionError, NoSectionError) as e:
|
||||
raise CommandError("get: {} - {}".format(e.__class__.__name__, e))
|
||||
else:
|
||||
message.info("{} {} = {}".format(sectname, optname, val))
|
||||
message.info("{} {} = {}".format(sectname, optname, val),
|
||||
immediately=True)
|
||||
|
||||
def get(self, sectname, optname, raw=False, transformed=True):
|
||||
"""Get the value from a section/option.
|
||||
|
@ -54,7 +54,7 @@ class CommandKeyParser(BaseKeyParser):
|
||||
cmdstr))
|
||||
message.set_cmd_text(':{} '.format(cmdstr))
|
||||
except (CommandMetaError, CommandError) as e:
|
||||
message.error(e)
|
||||
message.error(e, immediately=True)
|
||||
|
||||
def execute(self, cmdstr, _keytype, count=None):
|
||||
self._run_or_fill(cmdstr, count)
|
||||
|
@ -92,24 +92,21 @@ class NetworkManager(QNetworkAccessManager):
|
||||
return
|
||||
for err in errors:
|
||||
# FIXME we might want to use warn here (non-fatal error)
|
||||
message.error('SSL error: {}'.format(err.errorString()),
|
||||
queue=True)
|
||||
message.error('SSL error: {}'.format(err.errorString()))
|
||||
reply.ignoreSslErrors()
|
||||
|
||||
@pyqtSlot('QNetworkReply', 'QAuthenticator')
|
||||
def on_authentication_required(self, _reply, authenticator):
|
||||
"""Called when a website needs authentication."""
|
||||
answer = message.modular_question(
|
||||
"Username ({}):".format(authenticator.realm()),
|
||||
mode=PromptMode.user_pwd)
|
||||
answer = message.ask("Username ({}):".format(authenticator.realm()),
|
||||
mode=PromptMode.user_pwd)
|
||||
self._fill_authenticator(authenticator, answer)
|
||||
|
||||
@pyqtSlot('QNetworkProxy', 'QAuthenticator')
|
||||
def on_proxy_authentication_required(self, _proxy, authenticator):
|
||||
"""Called when a proxy needs authentication."""
|
||||
answer = message.modular_question(
|
||||
"Proxy username ({}):".format(authenticator.realm()),
|
||||
mode=PromptMode.user_pwd)
|
||||
answer = message.ask("Proxy username ({}):".format(
|
||||
authenticator.realm()), mode=PromptMode.user_pwd)
|
||||
self._fill_authenticator(authenticator, answer)
|
||||
|
||||
def createRequest(self, op, req, outgoing_data):
|
||||
|
@ -30,44 +30,31 @@ def instance():
|
||||
return QCoreApplication.instance().messagebridge
|
||||
|
||||
|
||||
def error(message, queue=False):
|
||||
"""Display an error message in the statusbar.
|
||||
def error(message, immediately=False):
|
||||
"""Convienience function to display an error message in the statusbar.
|
||||
|
||||
Args:
|
||||
message: The message to display.
|
||||
queue: If set, message gets queued rather than being displayed
|
||||
immediately.
|
||||
See MessageBridge.error.
|
||||
"""
|
||||
message = str(message)
|
||||
logger.error(message)
|
||||
bridge = instance()
|
||||
bridge.queue(bridge.error, message, queue)
|
||||
instance().error(message, immediately)
|
||||
|
||||
|
||||
def info(message, queue=False):
|
||||
"""Display a temporary info message in the statusbar.
|
||||
def info(message, immediately=True):
|
||||
"""Convienience function to display an info message in the statusbar.
|
||||
|
||||
Args:
|
||||
message: The message to display.
|
||||
queue: If set, message gets queued rather than being displayed
|
||||
immediately.
|
||||
See MessageBridge.info.
|
||||
"""
|
||||
message = str(message)
|
||||
logger.info(message)
|
||||
bridge = instance()
|
||||
bridge.queue(bridge.info, message, queue)
|
||||
instance().info(message, immediately)
|
||||
|
||||
|
||||
def text(message):
|
||||
"""Display a persistent message in the statusbar."""
|
||||
message = str(message)
|
||||
logger.debug(message)
|
||||
bridge = instance()
|
||||
bridge.queue(bridge.text, message)
|
||||
def set_cmd_text(txt):
|
||||
"""Convienience function to Set the statusbar command line to a text."""
|
||||
instance().set_cmd_text(txt)
|
||||
|
||||
|
||||
def modular_question(message, mode, default=None):
|
||||
"""Ask a modular question in the statusbar.
|
||||
def ask(message, mode, default=None):
|
||||
"""Ask a modular question in the statusbar (blocking).
|
||||
|
||||
Args:
|
||||
message: The message to display to the user.
|
||||
@ -95,29 +82,25 @@ def alert(message):
|
||||
q.deleteLater()
|
||||
|
||||
|
||||
def question(message, mode, handler, cancelled_handler=None, default=None):
|
||||
def ask_async(message, mode, handler, default=None):
|
||||
"""Ask an async question in the statusbar.
|
||||
|
||||
Args:
|
||||
message: The message to display to the user.
|
||||
mode: A PromptMode.
|
||||
handler: The function to get called with the answer as argument.
|
||||
cancelled_handler: The function to get called when the prompt was
|
||||
cancelled by the user, or None.
|
||||
default: The default value to display.
|
||||
"""
|
||||
q = Question(instance())
|
||||
bridge = instance()
|
||||
q = Question(bridge)
|
||||
q.text = message
|
||||
q.mode = mode
|
||||
q.default = default
|
||||
q.answered.connect(handler)
|
||||
if cancelled_handler is not None:
|
||||
q.cancelled.connect(cancelled_handler)
|
||||
bridge = instance()
|
||||
bridge.queue(bridge.question, q, False)
|
||||
|
||||
|
||||
def confirm_action(message, yes_action, no_action=None, default=None):
|
||||
def confirm_async(message, yes_action, no_action=None, default=None):
|
||||
"""Ask a yes/no question to the user and execute the given actions.
|
||||
|
||||
Args:
|
||||
@ -126,47 +109,120 @@ def confirm_action(message, yes_action, no_action=None, default=None):
|
||||
no_action: Callable to be called when the user answered no.
|
||||
default: True/False to set a default value, or None.
|
||||
"""
|
||||
q = Question(instance())
|
||||
bridge = instance()
|
||||
q = Question(bridge)
|
||||
q.text = message
|
||||
q.mode = PromptMode.yesno
|
||||
q.default = default
|
||||
q.answered_yes.connect(yes_action)
|
||||
if no_action is not None:
|
||||
q.answered_no.connect(no_action)
|
||||
bridge = instance()
|
||||
bridge.queue(bridge.question, q, False)
|
||||
|
||||
|
||||
def clear():
|
||||
"""Clear a persistent message in the statusbar."""
|
||||
bridge = instance()
|
||||
bridge.queue(bridge.text, '')
|
||||
|
||||
|
||||
def set_cmd_text(txt):
|
||||
"""Set the statusbar command line to a preset text."""
|
||||
bridge = instance()
|
||||
bridge.queue(bridge.set_cmd_text, txt)
|
||||
|
||||
|
||||
class MessageBridge(QObject):
|
||||
|
||||
"""Bridge for messages to be shown in the statusbar."""
|
||||
"""Bridge for messages to be shown in the statusbar.
|
||||
|
||||
error = pyqtSignal(str, bool)
|
||||
info = pyqtSignal(str, bool)
|
||||
text = pyqtSignal(str)
|
||||
set_cmd_text = pyqtSignal(str)
|
||||
question = pyqtSignal(Question, bool)
|
||||
Signals:
|
||||
s_error: Display an error message.
|
||||
arg 0: The error message to show.
|
||||
arg 1: Whether to show it immediately (True) or queue it
|
||||
(False).
|
||||
s_info: Display an info message.
|
||||
args: See s_error.
|
||||
s_set_text: Set a persistent text in the statusbar.
|
||||
arg: The text to set.
|
||||
s_set_cmd_text: Pre-set a text for the commandline prompt.
|
||||
arg: The text to set.
|
||||
|
||||
s_question: Ask a question to the user in the statusbar.
|
||||
arg 0: The Question object to ask.
|
||||
arg 1: Whether to block (True) or ask async (False).
|
||||
|
||||
IMPORTANT: Slots need to be connected to this signal via a
|
||||
Qt.DirectConnection!
|
||||
"""
|
||||
|
||||
s_error = pyqtSignal(str, bool)
|
||||
s_info = pyqtSignal(str, bool)
|
||||
s_set_text = pyqtSignal(str)
|
||||
s_set_cmd_text = pyqtSignal(str)
|
||||
s_question = pyqtSignal(Question, bool)
|
||||
|
||||
def __repr__(self):
|
||||
return '<{}>'.format(self.__class__.__name__)
|
||||
|
||||
def queue(self, signal, *args):
|
||||
"""Queue a message to be emitted.
|
||||
def _emit_later(self, signal, *args):
|
||||
"""Emit a message later when the mainloop is not busy anymore.
|
||||
|
||||
This is especially useful when messages are sent during init, before
|
||||
the messagebridge signals are connected - messages would get lost if we
|
||||
did normally emit them.
|
||||
|
||||
Args:
|
||||
signal: The signal to be emitted.
|
||||
*args: Args to be passed to the signal.
|
||||
"""
|
||||
QTimer.singleShot(0, lambda: signal.emit(*args))
|
||||
|
||||
def error(self, msg, immediately=False):
|
||||
"""Display an error in the statusbar.
|
||||
|
||||
Args:
|
||||
msg: The message to show.
|
||||
queue: Whether to queue the message (True) or display it
|
||||
immediately (False). Messages resulting from direct user
|
||||
input should be displayed immediately, all other messages
|
||||
should be queued.
|
||||
"""
|
||||
msg = str(msg)
|
||||
logger.error(msg)
|
||||
self._emit_later(self.s_error, msg, immediately)
|
||||
|
||||
def info(self, msg, immediately=True):
|
||||
"""Display an info text in the statusbar.
|
||||
|
||||
Args:
|
||||
See error(). Note immediately is True by default, because messages
|
||||
do rarely happen without user interaction.
|
||||
"""
|
||||
msg = str(msg)
|
||||
logger.info(msg)
|
||||
self._emit_later(self.s_info, msg, immediately)
|
||||
|
||||
def set_cmd_text(self, text):
|
||||
"""Set the command text of the statusbar.
|
||||
|
||||
Args:
|
||||
text: The text to set.
|
||||
"""
|
||||
text = str(text)
|
||||
logger.debug(text)
|
||||
self._emit_later(self.s_set_cmd_text, text)
|
||||
|
||||
def set_text(self, text):
|
||||
"""Set the normal text of the statusbar.
|
||||
|
||||
Args:
|
||||
text: The text to set.
|
||||
"""
|
||||
text = str(text)
|
||||
logger.debug(text)
|
||||
self._emit_later(self.s_set_text, text)
|
||||
|
||||
def ask(self, question, blocking):
|
||||
"""Ask a question to the user.
|
||||
|
||||
Note this method doesn't return the answer, it only blocks. The caller
|
||||
needs to construct a Question object and get the answer.
|
||||
|
||||
We don't use _emit_later here as this makes no sense with a blocking
|
||||
question.
|
||||
|
||||
Args:
|
||||
question: A Question object.
|
||||
blocking: Whether to return immediately or wait until the
|
||||
question is answered.
|
||||
"""
|
||||
self.question.emit(question, blocking)
|
||||
|
@ -177,8 +177,7 @@ class MainWindow(QWidget):
|
||||
else:
|
||||
text = "Close {} {}?".format(
|
||||
count, "tab" if count == 1 else "tabs")
|
||||
confirmed = message.modular_question(text, PromptMode.yesno,
|
||||
default=True)
|
||||
confirmed = message.ask(text, PromptMode.yesno, default=True)
|
||||
if confirmed:
|
||||
e.accept()
|
||||
else:
|
||||
|
@ -279,14 +279,14 @@ class StatusBar(QWidget):
|
||||
self._timer_was_active = False
|
||||
self._stack.setCurrentWidget(self.txt)
|
||||
|
||||
def _disp_text(self, text, error, queue=False):
|
||||
def _disp_text(self, text, error, immediately=False):
|
||||
"""Inner logic for disp_error and disp_temp_text.
|
||||
|
||||
Args:
|
||||
text: The message to display.
|
||||
error: Whether it's an error message (True) or normal text (False)
|
||||
queue: If set, message gets queued rather than being displayed
|
||||
immediately.
|
||||
immediately: If set, message gets displayed immediately instead of
|
||||
queued.
|
||||
"""
|
||||
# FIXME probably using a QTime here would be easier.
|
||||
logger.debug("Displaying text: {} (error={})".format(text, error))
|
||||
@ -310,7 +310,7 @@ class StatusBar(QWidget):
|
||||
# If we get the same message multiple times in a row and we're
|
||||
# still displaying it *anyways* we ignore the new one
|
||||
logger.debug("ignoring")
|
||||
elif not queue:
|
||||
elif immediately:
|
||||
# This message is a reaction to a keypress and should be displayed
|
||||
# immediately, temporarely interrupting the message queue.
|
||||
# We display this immediately and restart the timer.to clear it and
|
||||
@ -327,26 +327,26 @@ class StatusBar(QWidget):
|
||||
self._text_pop_timer.start()
|
||||
|
||||
@pyqtSlot(str, bool)
|
||||
def disp_error(self, text, queue=False):
|
||||
def disp_error(self, text, immediately=False):
|
||||
"""Display an error in the statusbar.
|
||||
|
||||
Args:
|
||||
text: The message to display.
|
||||
queue: If set, message gets queued rather than being displayed
|
||||
immediately.
|
||||
immediately: If set, message gets displayed immediately instead of
|
||||
queued.
|
||||
"""
|
||||
self._disp_text(text, True, queue)
|
||||
self._disp_text(text, True, immediately)
|
||||
|
||||
@pyqtSlot(str, bool)
|
||||
def disp_temp_text(self, text, queue):
|
||||
def disp_temp_text(self, text, immediately):
|
||||
"""Display a temporary text in the statusbar.
|
||||
|
||||
Args:
|
||||
text: The message to display.
|
||||
queue: If set, message gets queued rather than being displayed
|
||||
immediately.
|
||||
immediately: If set, message gets displayed immediately instead of
|
||||
queued.
|
||||
"""
|
||||
self._disp_text(text, False, queue)
|
||||
self._disp_text(text, False, immediately)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_text(self, val):
|
||||
|
@ -190,13 +190,13 @@ class WebView(QWebView):
|
||||
try:
|
||||
self.go_back()
|
||||
except CommandError as ex:
|
||||
message.error(ex)
|
||||
message.error(ex, immediately=True)
|
||||
elif e.button() == Qt.XButton2:
|
||||
# Forward button on mice which have it.
|
||||
try:
|
||||
self.go_forward()
|
||||
except CommandError as ex:
|
||||
message.error(ex)
|
||||
message.error(ex, immediately=True)
|
||||
|
||||
def _mousepress_insertmode(self, e):
|
||||
"""Switch to insert mode when an editable element was clicked.
|
||||
|
Loading…
Reference in New Issue
Block a user