Initial implementation of new messages

This commit is contained in:
Florian Bruhin 2016-09-14 20:52:32 +02:00
parent 5bef7dc74c
commit f16b96aa28
40 changed files with 389 additions and 584 deletions

View File

@ -180,8 +180,7 @@ def _process_args(args):
try:
config_obj.set('temp', sect, opt, val)
except (configexc.Error, configparser.Error) as e:
message.error('current', "set: {} - {}".format(
e.__class__.__name__, e))
message.error("set: {} - {}".format(e.__class__.__name__, e))
if not args.override_restore:
_load_session(args.session)
@ -216,10 +215,9 @@ def _load_session(name):
try:
session_manager.load(name)
except sessions.SessionNotFoundError:
message.error('current', "Session {} not found!".format(name))
message.error("Session {} not found!".format(name))
except sessions.SessionError as e:
message.error('current', "Failed to load session {}: {}".format(
name, e))
message.error("Failed to load session {}: {}".format(name, e))
try:
del state_config['general']['session']
except KeyError:
@ -271,8 +269,8 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
try:
url = urlutils.fuzzy_url(cmd, cwd, relative=True)
except urlutils.InvalidUrlError as e:
message.error('current', "Error in startup argument '{}': "
"{}".format(cmd, e))
message.error("Error in startup argument '{}': {}".format(
cmd, e))
else:
background = open_target in ['tab-bg', 'tab-bg-silent']
tabbed_browser.tabopen(url, background=background,
@ -301,8 +299,7 @@ def _open_startpage(win_id=None):
try:
url = urlutils.fuzzy_url(urlstr, do_search=False)
except urlutils.InvalidUrlError as e:
message.error('current', "Error when opening startpage: "
"{}".format(e))
message.error("Error when opening startpage: {}".format(e))
tabbed_browser.tabopen(QUrl('about:blank'))
else:
tabbed_browser.tabopen(url)
@ -370,6 +367,9 @@ def _init_modules(args, crash_handler):
crash_handler: The CrashHandler instance.
"""
# pylint: disable=too-many-statements
log.init.debug("Initializing messages...")
message.init()
log.init.debug("Initializing save manager...")
save_manager = savemanager.SaveManager(qApp)
objreg.register('save-manager', save_manager)

View File

@ -174,12 +174,10 @@ class HostBlocker:
args = objreg.get('args')
if (config.get('content', 'host-block-lists') is not None and
args.basedir is None):
message.info('current',
"Run :adblock-update to get adblock lists.")
message.info("Run :adblock-update to get adblock lists.")
@cmdutils.register(instance='host-blocker')
@cmdutils.argument('win_id', win_id=True)
def adblock_update(self, win_id):
def adblock_update(self):
"""Update the adblock block lists.
This updates `~/.local/share/qutebrowser/blocked-hosts` with downloaded
@ -201,12 +199,12 @@ class HostBlocker:
try:
fileobj = open(url.path(), 'rb')
except OSError as e:
message.error(win_id, "adblock: Error while reading {}: "
"{}".format(url.path(), e.strerror))
message.error("adblock: Error while reading {}: {}".format(
url.path(), e.strerror))
continue
download = FakeDownload(fileobj)
self._in_progress.append(download)
self.on_download_finished(download, win_id)
self.on_download_finished(download)
else:
fobj = io.BytesIO()
fobj.name = 'adblock: ' + url.host()
@ -215,8 +213,7 @@ class HostBlocker:
auto_remove=True)
self._in_progress.append(download)
download.finished.connect(
functools.partial(self.on_download_finished, download,
win_id))
functools.partial(self.on_download_finished, download))
def _merge_file(self, byte_io):
"""Read and merge host files.
@ -233,8 +230,8 @@ class HostBlocker:
f = get_fileobj(byte_io)
except (OSError, UnicodeDecodeError, zipfile.BadZipFile,
zipfile.LargeZipFile) as e:
message.error('current', "adblock: Error while reading {}: {} - "
"{}".format(byte_io.name, e.__class__.__name__, e))
message.error("adblock: Error while reading {}: {} - {}".format(
byte_io.name, e.__class__.__name__, e))
return
for line in f:
line_count += 1
@ -262,16 +259,16 @@ class HostBlocker:
self._blocked_hosts.add(host)
log.misc.debug("{}: read {} lines".format(byte_io.name, line_count))
if error_count > 0:
message.error('current', "adblock: {} read errors for {}".format(
error_count, byte_io.name))
message.error("adblock: {} read errors for {}".format(error_count,
byte_io.name))
def on_lists_downloaded(self, win_id):
def on_lists_downloaded(self):
"""Install block lists after files have been downloaded."""
with open(self._local_hosts_file, 'w', encoding='utf-8') as f:
for host in sorted(self._blocked_hosts):
f.write(host + '\n')
message.info(win_id, "adblock: Read {} hosts from {} sources."
.format(len(self._blocked_hosts), self._done_count))
message.info("adblock: Read {} hosts from {} sources.".format(
len(self._blocked_hosts), self._done_count))
@config.change_filter('content', 'host-block-lists')
def on_config_changed(self):
@ -285,12 +282,11 @@ class HostBlocker:
except OSError as e:
log.misc.exception("Failed to delete hosts file: {}".format(e))
def on_download_finished(self, download, win_id):
def on_download_finished(self, download):
"""Check if all downloads are finished and if so, trigger reading.
Arguments:
download: The finished DownloadItem.
win_id: The window ID in which :adblock-update was called
"""
self._in_progress.remove(download)
if download.successful:
@ -301,6 +297,6 @@ class HostBlocker:
download.fileobj.close()
if not self._in_progress:
try:
self.on_lists_downloaded(win_id)
self.on_lists_downloaded()
except OSError:
log.misc.exception("Failed to write host block list!")

View File

@ -620,7 +620,7 @@ class AbstractTab(QWidget):
if not url.isValid():
msg = urlutils.get_errstring(url, "Invalid link clicked")
message.error(self.win_id, msg)
message.error(msg)
self.data.open_target = usertypes.ClickTarget.normal
return False

View File

@ -303,7 +303,7 @@ class CommandDispatcher:
except urlutils.InvalidUrlError as e:
# We don't use cmdexc.CommandError here as this can be
# called async from edit_url
message.error(self._win_id, str(e))
message.error(str(e))
return None
def _parse_url_input(self, url):
@ -719,7 +719,7 @@ class CommandDispatcher:
caret = self._current_widget().caret
s = caret.selection()
if not caret.has_selection() or not s:
message.info(self._win_id, "Nothing to yank")
message.info("Nothing to yank")
return
else: # pragma: no cover
raise ValueError("Invalid value {!r} for `what'.".format(what))
@ -732,10 +732,9 @@ class CommandDispatcher:
utils.set_clipboard(s, selection=sel)
if what != 'selection':
message.info(self._win_id, "Yanked {} to {}: {}".format(
what, target, s))
message.info("Yanked {} to {}: {}".format(what, target, s))
else:
message.info(self._win_id, "{} {} yanked to {}".format(
message.info("{} {} yanked to {}".format(
len(s), "char" if len(s) == 1 else "chars", target))
if not keep:
modeman.maybe_leave(self._win_id, KeyMode.caret,
@ -754,7 +753,7 @@ class CommandDispatcher:
perc = tab.zoom.offset(count)
except ValueError as e:
raise cmdexc.CommandError(e)
message.info(self._win_id, "Zoom level: {}%".format(perc))
message.info("Zoom level: {}%".format(perc))
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
@ -769,7 +768,7 @@ class CommandDispatcher:
perc = tab.zoom.offset(-count)
except ValueError as e:
raise cmdexc.CommandError(e)
message.info(self._win_id, "Zoom level: {}%".format(perc))
message.info("Zoom level: {}%".format(perc))
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
@ -793,7 +792,7 @@ class CommandDispatcher:
tab.zoom.set_factor(float(level) / 100)
except ValueError:
raise cmdexc.CommandError("Can't zoom {}%!".format(level))
message.info(self._win_id, "Zoom level: {}%".format(level))
message.info("Zoom level: {}%".format(level))
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_only(self, left=False, right=False):
@ -1073,8 +1072,7 @@ class CommandDispatcher:
self._run_userscript(cmd, *args, verbose=verbose)
else:
cmd = os.path.expanduser(cmd)
proc = guiprocess.GUIProcess(self._win_id, what='command',
verbose=verbose,
proc = guiprocess.GUIProcess(what='command', verbose=verbose,
parent=self._tabbed_browser)
if detach:
proc.start_detached(cmd, args)
@ -1211,8 +1209,7 @@ class CommandDispatcher:
raise cmdexc.CommandError(str(e))
else:
msg = "Bookmarked {}!" if was_added else "Removed bookmark {}!"
message.info(self._win_id,
msg.format(url.toDisplayString()))
message.info(msg.format(url.toDisplayString()))
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0)
@ -1304,9 +1301,8 @@ class CommandDispatcher:
mhtml_: Download the current page and all assets as mhtml file.
"""
if dest_old is not None:
message.warning(self._win_id,
":download [url] [dest] is deprecated - use"
" download --dest [dest] [url]")
message.warning(":download [url] [dest] is deprecated - use "
":download --dest [dest] [url]")
if dest is not None:
raise cmdexc.CommandError("Can't give two destinations for the"
" download.")
@ -1379,7 +1375,7 @@ class CommandDispatcher:
try:
current_url = self._current_url()
except cmdexc.CommandError as e:
message.error(self._win_id, str(e))
message.error(str(e))
return
new_tab = self._tabbed_browser.tabopen(explicit=True)
new_tab.set_html(highlighted, current_url)
@ -1404,10 +1400,9 @@ class CommandDispatcher:
with open(dest, 'w', encoding='utf-8') as f:
f.write(data)
except OSError as e:
message.error(self._win_id,
'Could not write page: {}'.format(e))
message.error('Could not write page: {}'.format(e))
else:
message.info(self._win_id, "Dumped page to {}.".format(dest))
message.info("Dumped page to {}.".format(dest))
tab.dump_async(callback, plain=plain)
@ -1477,14 +1472,14 @@ class CommandDispatcher:
def _open_editor_cb(self, elem):
"""Open editor after the focus elem was found in open_editor."""
if elem is None:
message.error(self._win_id, "No element focused!")
message.error("No element focused!")
return
if not elem.is_editable(strict=True):
message.error(self._win_id, "Focused element is not editable!")
message.error("Focused element is not editable!")
return
text = elem.text(use_js=True)
ed = editor.ExternalEditor(self._win_id, self._tabbed_browser)
ed = editor.ExternalEditor(self._tabbed_browser)
ed.editing_finished.connect(functools.partial(
self.on_editing_finished, elem))
ed.edit(text)
@ -1539,12 +1534,12 @@ class CommandDispatcher:
def _insert_text_cb(elem):
if elem is None:
message.error(self._win_id, "No element focused!")
message.error("No element focused!")
return
try:
elem.insert_text(text)
except webelem.Error as e:
message.error(self._win_id, str(e))
message.error(str(e))
return
tab.elements.find_focused(_insert_text_cb)
@ -1571,22 +1566,21 @@ class CommandDispatcher:
def single_cb(elem):
"""Click a single element."""
if elem is None:
message.error(self._win_id, "No element found!")
message.error("No element found!")
return
try:
elem.click(target)
except webelem.Error as e:
message.error(self._win_id, str(e))
message.error(str(e))
return
# def multiple_cb(elems):
# """Click multiple elements (with only one expected)."""
# if not elems:
# message.error(self._win_id, "No element found!")
# message.error("No element found!")
# return
# elif len(elems) != 1:
# message.error(self._win_id, "{} elements found!".format(
# len(elems)))
# message.error("{} elements found!".format(len(elems)))
# return
# elems[0].click(target)
@ -1616,14 +1610,11 @@ class CommandDispatcher:
if found:
# Check if the scroll position got smaller and show info.
if not going_up and tab.scroller.pos_px().y() < old_scroll_pos.y():
message.info(self._win_id, "Search hit BOTTOM, continuing "
"at TOP", immediately=True)
message.info("Search hit BOTTOM, continuing at TOP")
elif going_up and tab.scroller.pos_px().y() > old_scroll_pos.y():
message.info(self._win_id, "Search hit TOP, continuing at "
"BOTTOM", immediately=True)
message.info("Search hit TOP, continuing at BOTTOM")
else:
message.warning(self._win_id, "Text '{}' not found on "
"page!".format(text), immediately=True)
message.warning("Text '{}' not found on page!".format(text))
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0)
@ -1941,7 +1932,7 @@ class CommandDispatcher:
# BrowserPage.javaScriptConsoleMessage(), but
# distinguishing between :jseval errors and errors from the
# webpage is not trivial...
message.info(self._win_id, 'No output or error')
message.info('No output or error')
else:
# The output can be a string, number, dict, array, etc. But
# *don't* output too much data, as this will make
@ -1949,7 +1940,7 @@ class CommandDispatcher:
out = str(out)
if len(out) > 5000:
out = out[:5000] + ' [...trimmed...]'
message.info(self._win_id, out)
message.info(out)
widget = self._current_widget()
widget.run_js_async(js_code, callback=jseval_cb, world=world)
@ -2016,7 +2007,7 @@ class CommandDispatcher:
old_url = self._current_url().toString()
ed = editor.ExternalEditor(self._win_id, self._tabbed_browser)
ed = editor.ExternalEditor(self._tabbed_browser)
# Passthrough for openurl args (e.g. -t, -b, -w)
ed.editing_finished.connect(functools.partial(

View File

@ -238,7 +238,7 @@ class HintActions:
msg = "Yanked URL to {}: {}".format(
"primary selection" if sel else "clipboard",
urlstr)
message.info(self._win_id, msg)
message.info(msg)
def run_cmd(self, url, context):
"""Run the command based on a hint URL.
@ -411,7 +411,7 @@ class HintManager(QObject):
try:
return self._word_hinter.hint(elems)
except HintingError as e:
message.error(self._win_id, str(e), immediately=True)
message.error(str(e))
# falls back on letter hints
if hint_mode == 'number':
chars = '0123456789'
@ -570,7 +570,7 @@ class HintManager(QObject):
filterfunc = webelem.FILTERS.get(self._context.group, lambda e: True)
elems = [e for e in elems if filterfunc(e)]
if not elems:
message.error(self._win_id, "No elements found.", immediately=True)
message.error("No elements found.")
return
strings = self._hint_strings(elems)
log.hints.debug("hints: {}".format(', '.join(strings)))
@ -662,8 +662,8 @@ class HintManager(QObject):
raise cmdexc.CommandError("No WebView available yet!")
if (tab.backend == usertypes.Backend.QtWebEngine and
target == Target.download):
message.error(self._win_id, "The download target is not available "
"yet with QtWebEngine.", immediately=True)
message.error("The download target is not available yet with "
"QtWebEngine.")
return
mode_manager = objreg.get('mode-manager', scope='window',
@ -849,9 +849,7 @@ class HintManager(QObject):
elem = self._context.labels[keystr].elem
if not elem.has_frame():
message.error(self._win_id,
"This element has no webframe.",
immediately=True)
message.error("This element has no webframe.")
return
if self._context.target in elem_handlers:
@ -860,9 +858,7 @@ class HintManager(QObject):
elif self._context.target in url_handlers:
url = elem.resolve_url(self._context.baseurl)
if url is None:
message.error(self._win_id,
"No suitable link found for this element.",
immediately=True)
message.error("No suitable link found for this element.")
return
handler = functools.partial(url_handlers[self._context.target],
url, self._context)
@ -882,7 +878,7 @@ class HintManager(QObject):
try:
handler()
except HintingError as e:
message.error(self._win_id, str(e), immediately=True)
message.error(str(e))
@cmdutils.register(instance='hintmanager', scope='tab', hide=True,
modes=[usertypes.KeyMode.hint])

View File

@ -125,7 +125,7 @@ class MouseEventFilter(QObject):
if factor < 0:
return False
perc = int(100 * factor)
message.info(self._tab.win_id, "Zoom level: {}%".format(perc))
message.info("Zoom level: {}%".format(perc))
self._tab.zoom.set_factor(factor)
return False
@ -191,15 +191,13 @@ class MouseEventFilter(QObject):
if self._tab.history.can_go_back():
self._tab.history.back()
else:
message.error(self._tab.win_id, "At beginning of history.",
immediately=True)
message.error("At beginning of history.")
elif e.button() in [Qt.XButton2, Qt.RightButton]:
# Forward button on mice which have it, or rocker gesture
if self._tab.history.can_go_forward():
self._tab.history.forward()
else:
message.error(self._tab.win_id, "At end of history.",
immediately=True)
message.error("At end of history.")
def _mousepress_opentarget(self, e):
"""Set the open target when something was clicked.

View File

@ -116,11 +116,11 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
word = 'prev' if prev else 'forward'
if elem is None:
message.error(win_id, "No {} links found!".format(word))
message.error("No {} links found!".format(word))
return
url = elem.resolve_url(baseurl)
if url is None:
message.error(win_id, "No {} links found!".format(word))
message.error("No {} links found!".format(word))
return
qtutils.ensure_valid(url)

View File

@ -245,8 +245,8 @@ def qute_help(url):
else:
urlpath = urlpath.lstrip('/')
if not docutils.docs_up_to_date(urlpath):
message.error('current', "Your documentation is outdated! Please "
"re-run scripts/asciidoc2html.py.")
message.error("Your documentation is outdated! Please re-run "
"scripts/asciidoc2html.py.")
path = 'html/doc/{}'.format(urlpath)
if urlpath.endswith('.png'):
return 'image/png', utils.read_file(path, binary=True)

View File

@ -155,7 +155,7 @@ class QuickmarkManager(UrlMarkManager):
try:
key, url = line.rsplit(maxsplit=1)
except ValueError:
message.error('current', "Invalid quickmark '{}'".format(line))
message.error("Invalid quickmark '{}'".format(line))
else:
self.marks[key] = url
@ -167,7 +167,7 @@ class QuickmarkManager(UrlMarkManager):
url: The quickmark url as a QUrl.
"""
if not url.isValid():
urlutils.invalid_url_error(win_id, url, "save quickmark")
urlutils.invalid_url_error(url, "save quickmark")
return
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
message.ask_async(
@ -190,10 +190,10 @@ class QuickmarkManager(UrlMarkManager):
# We don't raise cmdexc.CommandError here as this can be called async
# via prompt_save.
if not name:
message.error(win_id, "Can't set mark with empty name!")
message.error("Can't set mark with empty name!")
return
if not url:
message.error(win_id, "Can't set mark with empty URL!")
message.error("Can't set mark with empty URL!")
return
def set_mark():

View File

@ -568,7 +568,7 @@ class DownloadItem(QObject):
args.append(filename)
log.downloads.debug("Opening {} with {}"
.format(filename, [cmd] + args))
proc = guiprocess.GUIProcess(self._win_id, what='download')
proc = guiprocess.GUIProcess(what='download')
proc.start_detached(cmd, args)
def set_filename(self, filename):
@ -599,7 +599,6 @@ class DownloadItem(QObject):
# may be set for XDG_DOWNLOAD_DIR
if self._filename is None:
message.error(
self._win_id,
"XDG_DOWNLOAD_DIR points to a relative path - please check"
" your ~/.config/user-dirs.dirs. The download is saved in"
" your home directory.",
@ -843,7 +842,7 @@ class DownloadManager(QObject):
The created DownloadItem.
"""
if not url.isValid():
urlutils.invalid_url_error(self._win_id, url, "start download")
urlutils.invalid_url_error(url, "start download")
return
req = QNetworkRequest(url)
return self.get_request(req, **kwargs)
@ -1005,7 +1004,7 @@ class DownloadManager(QObject):
fobj = tmp_manager.get_tmpfile(suggested_filename)
except OSError as exc:
msg = "Download error: {}".format(exc)
message.error(self._win_id, msg)
message.error(msg)
download.cancel()
return
download.finished.connect(
@ -1056,7 +1055,7 @@ class DownloadManager(QObject):
@pyqtSlot(str)
def _on_error(self, msg):
"""Display error message on download errors."""
message.error(self._win_id, "Download error: {}".format(msg))
message.error("Download error: {}".format(msg))
def has_downloads_with_nam(self, nam):
"""Check if the DownloadManager has any downloads with the given QNAM.

View File

@ -447,11 +447,10 @@ class _Downloader:
with open(self.dest, 'wb') as file_output:
self.writer.write_to(file_output)
except OSError as error:
message.error(self._win_id,
"Could not save file: {}".format(error))
message.error("Could not save file: {}".format(error))
return
log.downloads.debug("File successfully written.")
message.info(self._win_id, "Page saved as {}".format(self.dest))
message.info("Page saved as {}".format(self.dest))
def _collect_zombies(self):
"""Collect done downloads and add their data to the MHTML file.
@ -528,8 +527,7 @@ def start_download_checked(dest, tab):
# saving the file anyway.
if not os.path.isdir(os.path.dirname(path)):
folder = os.path.dirname(path)
message.error(tab.win_id,
"Directory {} does not exist.".format(folder))
message.error("Directory {} does not exist.".format(folder))
return
if not os.path.isfile(path):

View File

@ -299,8 +299,7 @@ class NetworkManager(QNetworkAccessManager):
for err in errors:
# FIXME we might want to use warn here (non-fatal error)
# https://github.com/The-Compiler/qutebrowser/issues/114
message.error(self._win_id, 'SSL error: {}'.format(
err.errorString()))
message.error('SSL error: {}'.format(err.errorString()))
reply.ignoreSslErrors()
self._accepted_ssl_errors[host_tpl] += errors

View File

@ -77,13 +77,13 @@ class JSBridge(QObject):
# https://github.com/The-Compiler/qutebrowser/issues/727
if ((sectname, optname) == ('content', 'allow-javascript') and
value == 'false'):
message.error('current', "Refusing to disable javascript via "
"qute:settings as it needs javascript support.")
message.error("Refusing to disable javascript via qute:settings "
"as it needs javascript support.")
return
try:
objreg.get('config').set('conf', sectname, optname, value)
except (configexc.Error, configparser.Error) as e:
message.error('current', e)
message.error(e)
@qutescheme.add_handler('settings', backend=usertypes.Backend.QtWebKit)

View File

@ -249,8 +249,8 @@ class BrowserPage(QWebPage):
def on_print_requested(self, frame):
"""Handle printing when requested via javascript."""
if not qtutils.check_print_compat():
message.error(self._win_id, "Printing on Qt < 5.3.0 on Windows is "
"broken, please upgrade!", immediately=True)
message.error("Printing on Qt < 5.3.0 on Windows is broken, "
"please upgrade!")
return
printdiag = QPrintDialog()
printdiag.setAttribute(Qt.WA_DeleteOnClose)

View File

@ -174,8 +174,8 @@ class Command:
"backend.".format(self.name, self.backend.name))
if self.deprecated:
message.warning(win_id, '{} is deprecated - {}'.format(
self.name, self.deprecated))
message.warning('{} is deprecated - {}'.format(self.name,
self.deprecated))
def _check_func(self):
"""Make sure the function parameters don't violate any rules."""
@ -507,7 +507,7 @@ class Command:
try:
self.namespace = self.parser.parse_args(args)
except argparser.ArgumentParserError as e:
message.error(win_id, '{}: {}'.format(self.name, e),
message.error('{}: {}'.format(self.name, e),
stack=traceback.format_exc())
return
except argparser.ArgumentParserExit as e:

View File

@ -320,8 +320,7 @@ class CommandRunner(QObject):
try:
self.run(text, count)
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
message.error(self._win_id, e, immediately=True,
stack=traceback.format_exc())
message.error(e, stack=traceback.format_exc())
@pyqtSlot(str, int)
def run_safely_init(self, text, count=None):
@ -333,4 +332,4 @@ class CommandRunner(QObject):
try:
self.run(text, count)
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
message.error(self._win_id, e, stack=traceback.format_exc())
message.error(e, stack=traceback.format_exc())

View File

@ -84,7 +84,6 @@ class _BaseUserscriptRunner(QObject):
Attributes:
_filepath: The path of the file/FIFO which is being read.
_proc: The GUIProcess which is being executed.
_win_id: The window ID this runner is associated with.
_cleaned_up: Whether temporary files were cleaned up.
_text_stored: Set when the page text was stored async.
_html_stored: Set when the page html was stored async.
@ -99,10 +98,9 @@ class _BaseUserscriptRunner(QObject):
got_cmd = pyqtSignal(str)
finished = pyqtSignal()
def __init__(self, win_id, parent=None):
def __init__(self, parent=None):
super().__init__(parent)
self._cleaned_up = False
self._win_id = win_id
self._filepath = None
self._proc = None
self._env = {}
@ -151,7 +149,7 @@ class _BaseUserscriptRunner(QObject):
self._env['QUTE_FIFO'] = self._filepath
if env is not None:
self._env.update(env)
self._proc = guiprocess.GUIProcess(self._win_id, 'userscript',
self._proc = guiprocess.GUIProcess('userscript',
additional_env=self._env,
verbose=verbose, parent=self)
self._proc.finished.connect(self.on_proc_finished)
@ -175,9 +173,8 @@ class _BaseUserscriptRunner(QObject):
except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error(
self._win_id, "Failed to delete tempfile {} ({})!".format(
fn, e))
message.error("Failed to delete tempfile {} ({})!".format(
fn, e))
self._filepath = None
self._proc = None
self._env = {}
@ -223,8 +220,8 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
_reader: The _QtFIFOReader instance.
"""
def __init__(self, win_id, parent=None):
super().__init__(win_id, parent)
def __init__(self, parent=None):
super().__init__(parent)
self._reader = None
def prepare_run(self, *args, **kwargs):
@ -242,8 +239,7 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
# pylint: disable=no-member,useless-suppression
os.mkfifo(self._filepath)
except OSError as e:
message.error(self._win_id,
"Error while creating FIFO: {}".format(e))
message.error("Error while creating FIFO: {}".format(e))
return
self._reader = _QtFIFOReader(self._filepath)
@ -315,8 +311,7 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
handle.close()
self._filepath = handle.name
except OSError as e:
message.error(self._win_id, "Error while creating tempfile: "
"{}".format(e))
message.error("Error while creating tempfile: {}".format(e))
return
@ -348,9 +343,9 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
commandrunner = runners.CommandRunner(win_id, parent=tabbed_browser)
if os.name == 'posix':
runner = _POSIXUserscriptRunner(win_id, tabbed_browser)
runner = _POSIXUserscriptRunner(tabbed_browser)
elif os.name == 'nt': # pragma: no cover
runner = _WindowsUserscriptRunner(win_id, tabbed_browser)
runner = _WindowsUserscriptRunner(tabbed_browser)
else: # pragma: no cover
raise UnsupportedError

View File

@ -816,8 +816,7 @@ class ConfigManager(QObject):
if print_:
with self._handle_config_error():
val = self.get(section_, option, transformed=False)
message.info(win_id, "{} {} = {}".format(
section_, option, val), immediately=True)
message.info("{} {} = {}".format(section_, option, val))
def set(self, layer, sectname, optname, value, validate=True):
"""Set an option.

View File

@ -1069,22 +1069,6 @@ def data(readonly=False):
SettingValue(typ.QssColor(), 'black'),
"Background color of the statusbar."),
('statusbar.fg.error',
SettingValue(typ.QssColor(), '${statusbar.fg}'),
"Foreground color of the statusbar if there was an error."),
('statusbar.bg.error',
SettingValue(typ.QssColor(), 'red'),
"Background color of the statusbar if there was an error."),
('statusbar.fg.warning',
SettingValue(typ.QssColor(), '${statusbar.fg}'),
"Foreground color of the statusbar if there is a warning."),
('statusbar.bg.warning',
SettingValue(typ.QssColor(), 'darkorange'),
"Background color of the statusbar if there is a warning."),
('statusbar.fg.prompt',
SettingValue(typ.QssColor(), '${statusbar.fg}'),
"Foreground color of the statusbar if there is a prompt."),
@ -1280,6 +1264,42 @@ def data(readonly=False):
SettingValue(typ.QssColor(), 'rgba(0, 0, 0, 80%)'),
"Background color of the keyhint widget."),
('messages.fg.error',
SettingValue(typ.QssColor(), 'white'),
"Foreground color of an error message."),
('messages.bg.error',
SettingValue(typ.QssColor(), 'red'),
"Background color of an error message."),
('messages.border.error',
SettingValue(typ.QssColor(), '#bb0000'),
"Border color of an error message."),
('messages.fg.warning',
SettingValue(typ.QssColor(), 'white'),
"Foreground color a warning message."),
('messages.bg.warning',
SettingValue(typ.QssColor(), 'darkorange'),
"Background color of a warning message."),
('messages.border.warning',
SettingValue(typ.QssColor(), '#d47300'),
"Border color of an error message."),
('messages.fg.info',
SettingValue(typ.QssColor(), 'white'),
"Foreground color an info message."),
('messages.bg.info',
SettingValue(typ.QssColor(), 'black'),
"Background color of an info message."),
('messages.border.info',
SettingValue(typ.QssColor(), '#333333'),
"Border color of an info message."),
readonly=readonly
)),
@ -1369,6 +1389,18 @@ def data(readonly=False):
SettingValue(typ.Font(), DEFAULT_FONT_SIZE + ' ${_monospace}'),
"Font used in the keyhint widget."),
('messages.error',
SettingValue(typ.Font(), DEFAULT_FONT_SIZE + ' ${_monospace}'),
"Font used for error messages."),
('messages.warning',
SettingValue(typ.Font(), DEFAULT_FONT_SIZE + ' ${_monospace}'),
"Font used for warning messages."),
('messages.info',
SettingValue(typ.Font(), DEFAULT_FONT_SIZE + ' ${_monospace}'),
"Font used for info messages."),
readonly=readonly
)),
])

View File

@ -152,9 +152,8 @@ class KeyConfigParser(QObject):
@cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True,
no_replace_variables=True)
@cmdutils.argument('win_id', win_id=True)
@cmdutils.argument('command', completion=usertypes.Completion.bind)
def bind(self, key, win_id, command=None, *, mode='normal', force=False):
def bind(self, key, command=None, *, mode='normal', force=False):
"""Bind a key to a command.
Args:
@ -172,11 +171,10 @@ class KeyConfigParser(QObject):
if command is None:
cmd = self.get_bindings_for(mode).get(key, None)
if cmd is None:
message.info(win_id, "{} is unbound in {} mode".format(
key, mode))
message.info("{} is unbound in {} mode".format(key, mode))
else:
message.info(win_id, "{} is bound to '{}' in {} mode".format(
key, cmd, mode))
message.info("{} is bound to '{}' in {} mode".format(key, cmd,
mode))
return
mode = self._normalize_sectname(mode)

View File

@ -43,8 +43,7 @@ class CommandKeyParser(BaseKeyParser):
try:
self._commandrunner.run(cmdstr, count)
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
message.error(self._win_id, e, immediately=True,
stack=traceback.format_exc())
message.error(str(e), stack=traceback.format_exc())
class PassthroughKeyParser(CommandKeyParser):

View File

@ -30,7 +30,7 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication
from qutebrowser.commands import runners, cmdutils
from qutebrowser.config import config
from qutebrowser.utils import message, log, usertypes, qtutils, objreg, utils
from qutebrowser.mainwindow import tabbedbrowser
from qutebrowser.mainwindow import tabbedbrowser, messageview
from qutebrowser.mainwindow.statusbar import bar
from qutebrowser.completion import completionwidget, completer
from qutebrowser.keyinput import modeman
@ -177,6 +177,7 @@ class MainWindow(QWidget):
partial_match=True)
self._keyhint = keyhintwidget.KeyHintView(self.win_id, self)
self._messageview = messageview.MessageView(parent=self)
log.init.debug("Initializing modes...")
modeman.init(self.win_id, self)
@ -195,8 +196,7 @@ class MainWindow(QWidget):
# When we're here the statusbar might not even really exist yet, so
# resizing will fail. Therefore, we use singleShot QTimers to make sure
# we defer this until everything else is initialized.
QTimer.singleShot(0, self._connect_resize_completion)
QTimer.singleShot(0, self._connect_resize_keyhint)
QTimer.singleShot(0, self._connect_resize_signals)
objreg.get('config').changed.connect(self.on_config_changed)
objreg.get("app").new_window.emit(self)
@ -303,15 +303,14 @@ class MainWindow(QWidget):
log.init.warning("Error while loading geometry.")
self._set_default_geometry()
def _connect_resize_completion(self):
"""Connect the resize_completion signal and resize it once."""
def _connect_resize_signals(self):
"""Connect the resize signal and resize everything once."""
self._completion.resize_completion.connect(self.resize_completion)
self.resize_completion()
def _connect_resize_keyhint(self):
"""Connect the reposition_keyhint signal and resize it once."""
self._keyhint.reposition_keyhint.connect(self.reposition_keyhint)
self._messageview.reposition.connect(self._reposition_messageview)
self.resize_completion()
self.reposition_keyhint()
self._reposition_messageview()
def _set_default_geometry(self):
"""Set some sensible default geometry."""
@ -360,9 +359,9 @@ class MainWindow(QWidget):
key_config.changed.connect(obj.on_keyconfig_changed)
# messages
message_bridge.s_error.connect(status.disp_error)
message_bridge.s_warning.connect(status.disp_warning)
message_bridge.s_info.connect(status.disp_temp_text)
message.global_bridge.show_message.connect(
self._messageview.show_message)
message_bridge.s_set_text.connect(status.set_text)
message_bridge.s_maybe_reset_text.connect(status.txt.maybe_reset_text)
message_bridge.s_set_cmd_text.connect(cmd.set_cmd_text)
@ -391,6 +390,23 @@ class MainWindow(QWidget):
completion_obj.on_clear_completion_selection)
cmd.hide_completion.connect(completion_obj.hide)
def _get_overlay_position(self, height):
"""Get the position for a full-width overlay with the given height."""
status_position = config.get('ui', 'status-position')
if status_position == 'bottom':
top = self.height() - self.status.height() - height
top = qtutils.check_overflow(top, 'int', fatal=False)
topleft = QPoint(0, top)
bottomright = self.status.geometry().topRight()
elif status_position == 'top':
topleft = self.status.geometry().bottomLeft()
bottom = self.status.height() + height
bottom = qtutils.check_overflow(bottom, 'int', fatal=False)
bottomright = QPoint(self.width(), bottom)
else:
raise ValueError("Invalid position {}!".format(status_position))
return QRect(topleft, bottomright)
@pyqtSlot()
def resize_completion(self):
"""Adjust completion according to config."""
@ -414,20 +430,7 @@ class MainWindow(QWidget):
height = contents_height
else:
contents_height = -1
status_position = config.get('ui', 'status-position')
if status_position == 'bottom':
top = self.height() - self.status.height() - height
top = qtutils.check_overflow(top, 'int', fatal=False)
topleft = QPoint(0, top)
bottomright = self.status.geometry().topRight()
elif status_position == 'top':
topleft = self.status.geometry().bottomLeft()
bottom = self.status.height() + height
bottom = qtutils.check_overflow(bottom, 'int', fatal=False)
bottomright = QPoint(self.width(), bottom)
else:
raise ValueError("Invalid position {}!".format(status_position))
rect = QRect(topleft, bottomright)
rect = self._get_overlay_position(height)
log.misc.debug('completion rect: {}'.format(rect))
if rect.isValid():
self._completion.setGeometry(rect)
@ -450,6 +453,17 @@ class MainWindow(QWidget):
if rect.isValid():
self._keyhint.setGeometry(rect)
@pyqtSlot()
def _reposition_messageview(self):
"""Position the message view correctly."""
if not self._messageview.isVisible():
return
height = self._messageview.message_height()
rect = self._get_overlay_position(height)
log.misc.debug('messageview rect: {}'.format(rect))
if rect.isValid():
self._messageview.setGeometry(rect)
@cmdutils.register(instance='main-window', scope='window')
@pyqtSlot()
def close(self):

View File

@ -0,0 +1,118 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Showing messages above the statusbar."""
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer, Qt
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel
from qutebrowser.config import config, style
from qutebrowser.utils import usertypes, objreg
class Message(QLabel):
"""A single error/warning/info message."""
def __init__(self, level, text, parent=None):
super().__init__(text, parent)
self.setAttribute(Qt.WA_StyledBackground, True)
stylesheet = """
padding-top: 2px;
padding-bottom: 2px;
"""
if level == usertypes.MessageLevel.error:
stylesheet += """
background-color: {{ color['messages.bg.error'] }};
color: {{ color['messages.fg.error'] }};
font: {{ font['messages.error'] }};
border-bottom: 1px solid {{ color['messages.border.error'] }};
"""
elif level == usertypes.MessageLevel.warning:
stylesheet += """
background-color: {{ color['messages.bg.warning'] }};
color: {{ color['messages.fg.warning'] }};
font: {{ font['messages.warning'] }};
border-bottom:
1px solid {{ color['messages.border.warning'] }};
"""
elif level == usertypes.MessageLevel.info:
stylesheet += """
background-color: {{ color['messages.bg.info'] }};
color: {{ color['messages.fg.info'] }};
font: {{ font['messages.info'] }};
border-bottom: 1px solid {{ color['messages.border.info'] }}
"""
else: # pragma: no cover
raise ValueError("Invalid level {!r}".format(level))
# We don't bother with set_register_stylesheet here as it's short-lived
# anyways.
self.setStyleSheet(style.get_stylesheet(stylesheet))
class MessageView(QWidget):
reposition = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self._vbox = QVBoxLayout(self)
self._vbox.setContentsMargins(0, 0, 0, 0)
self._vbox.setSpacing(0)
self._clear_timer = QTimer()
self._clear_timer.timeout.connect(self._clear_messages)
self._set_clear_timer_interval()
objreg.get('config').changed.connect(self._set_clear_timer_interval)
self._last_text = None
self._messages = []
@config.change_filter('ui', 'message-timeout')
def _set_clear_timer_interval(self):
self._clear_timer.setInterval(config.get('ui', 'message-timeout'))
def message_height(self):
return sum(label.sizeHint().height() for label in self._messages)
@pyqtSlot()
def _clear_messages(self):
for widget in self._messages:
self._vbox.removeWidget(widget)
widget.hide()
widget.deleteLater()
self._messages = []
self._last_text = None
self.hide()
self._clear_timer.stop()
@pyqtSlot(usertypes.MessageLevel, str)
def show_message(self, level, text):
if text == self._last_text:
return
widget = Message(level, text, parent=self)
self._vbox.addWidget(widget)
widget.show()
self._clear_timer.start()
self._messages.append(widget)
self._last_text = text
self.show()
self.reposition.emit()

View File

@ -33,9 +33,6 @@ from qutebrowser.mainwindow.statusbar import (command, progress, keystring,
from qutebrowser.mainwindow.statusbar import text as textwidget
PreviousWidget = usertypes.enum('PreviousWidget', ['none', 'prompt',
'command'])
Severity = usertypes.enum('Severity', ['normal', 'warning', 'error'])
CaretMode = usertypes.enum('CaretMode', ['off', 'on', 'selection'])
@ -52,22 +49,9 @@ class StatusBar(QWidget):
cmd: The Command widget in the statusbar.
_hbox: The main QHBoxLayout.
_stack: The QStackedLayout with cmd/txt widgets.
_text_queue: A deque of (error, text) tuples to be displayed.
error: True if message is an error, False otherwise
_text_pop_timer: A Timer displaying the error messages.
_stopwatch: A QTime for the last displayed message.
_timer_was_active: Whether the _text_pop_timer was active before hiding
the command widget.
_previous_widget: A PreviousWidget member - the widget which was
displayed when an error interrupted it.
_win_id: The window ID the statusbar is associated with.
Class attributes:
_severity: The severity of the current message, a Severity member.
For some reason we need to have this as class attribute so
pyqtProperty works correctly.
_prompt_active: If we're currently in prompt-mode.
For some reason we need to have this as class attribute
@ -129,20 +113,6 @@ class StatusBar(QWidget):
background-color: {{ color['statusbar.bg.caret-selection'] }};
}
QWidget#StatusBar[severity="error"],
QWidget#StatusBar[severity="error"] QLabel,
QWidget#StatusBar[severity="error"] QLineEdit {
color: {{ color['statusbar.fg.error'] }};
background-color: {{ color['statusbar.bg.error'] }};
}
QWidget#StatusBar[severity="warning"],
QWidget#StatusBar[severity="warning"] QLabel,
QWidget#StatusBar[severity="warning"] QLineEdit {
color: {{ color['statusbar.fg.warning'] }};
background-color: {{ color['statusbar.bg.warning'] }};
}
QWidget#StatusBar[prompt_active="true"],
QWidget#StatusBar[prompt_active="true"] QLabel,
QWidget#StatusBar[prompt_active="true"] QLineEdit {
@ -177,7 +147,6 @@ class StatusBar(QWidget):
self._win_id = win_id
self._option = None
self._stopwatch = QTime()
self._hbox = QHBoxLayout(self)
self.set_hbox_padding()
@ -195,16 +164,9 @@ class StatusBar(QWidget):
self.txt = textwidget.Text()
self._stack.addWidget(self.txt)
self._timer_was_active = False
self._text_queue = collections.deque()
self._text_pop_timer = usertypes.Timer(self, 'statusbar_text_pop')
self._text_pop_timer.timeout.connect(self._pop_text)
self.set_pop_timer_interval()
objreg.get('config').changed.connect(self.set_pop_timer_interval)
self.prompt = prompt.Prompt(win_id)
self._stack.addWidget(self.prompt)
self._previous_widget = PreviousWidget.none
self.cmd.show_cmd.connect(self._show_cmd_widget)
self.cmd.hide_cmd.connect(self._hide_cmd_widget)
@ -252,40 +214,6 @@ class StatusBar(QWidget):
padding = config.get('ui', 'statusbar-padding')
self._hbox.setContentsMargins(padding.left, 0, padding.right, 0)
@pyqtProperty(str)
def severity(self):
"""Getter for self.severity, so it can be used as Qt property.
Return:
The severity as a string (!)
"""
if self._severity is None:
return ""
else:
return self._severity.name
def _set_severity(self, severity):
"""Set the severity for the current message.
Re-set the stylesheet after setting the value, so everything gets
updated by Qt properly.
Args:
severity: A Severity member.
"""
if self._severity == severity:
# This gets called a lot (e.g. if the completion selection was
# changed), and setStyleSheet is relatively expensive, so we ignore
# this if there's nothing to change.
return
log.statusbar.debug("Setting severity to {}".format(severity))
self._severity = severity
self.setStyleSheet(style.get_stylesheet(self.STYLESHEET))
if severity != Severity.normal:
# If we got an error while command/prompt was shown, raise the text
# widget.
self._stack.setCurrentWidget(self.txt)
@pyqtProperty(bool)
def prompt_active(self):
"""Getter for self.prompt_active, so it can be used as Qt property."""
@ -349,51 +277,14 @@ class StatusBar(QWidget):
text = "-- {} MODE --".format(mode.upper())
self.txt.set_text(self.txt.Text.normal, text)
def _pop_text(self):
"""Display a text in the statusbar and pop it from _text_queue."""
try:
severity, text = self._text_queue.popleft()
except IndexError:
self._set_severity(Severity.normal)
self.txt.set_text(self.txt.Text.temp, '')
self._text_pop_timer.stop()
# If a previous widget was interrupted by an error, restore it.
if self._previous_widget == PreviousWidget.prompt:
self._stack.setCurrentWidget(self.prompt)
elif self._previous_widget == PreviousWidget.command:
self._stack.setCurrentWidget(self.cmd)
elif self._previous_widget == PreviousWidget.none:
self.maybe_hide()
else:
raise AssertionError("Unknown _previous_widget!")
return
self.show()
log.statusbar.debug("Displaying message: {} (severity {})".format(
text, severity))
log.statusbar.debug("Remaining: {}".format(self._text_queue))
self._set_severity(severity)
self.txt.set_text(self.txt.Text.temp, text)
def _show_cmd_widget(self):
"""Show command widget instead of temporary text."""
self._set_severity(Severity.normal)
self._previous_widget = PreviousWidget.command
if self._text_pop_timer.isActive():
self._timer_was_active = True
self._text_pop_timer.stop()
self._stack.setCurrentWidget(self.cmd)
self.show()
def _hide_cmd_widget(self):
"""Show temporary text instead of command widget."""
log.statusbar.debug("Hiding cmd widget, queue: {}".format(
self._text_queue))
self._previous_widget = PreviousWidget.none
if self._timer_was_active:
# Restart the text pop timer if it was active before hiding.
self._pop_text()
self._text_pop_timer.start()
self._timer_was_active = False
log.statusbar.debug("Hiding cmd widget")
self._stack.setCurrentWidget(self.txt)
self.maybe_hide()
@ -401,112 +292,17 @@ class StatusBar(QWidget):
"""Show prompt widget instead of temporary text."""
if self._stack.currentWidget() is self.prompt:
return
self._set_severity(Severity.normal)
self._set_prompt_active(True)
self._previous_widget = PreviousWidget.prompt
if self._text_pop_timer.isActive():
self._timer_was_active = True
self._text_pop_timer.stop()
self._stack.setCurrentWidget(self.prompt)
self.show()
def _hide_prompt_widget(self):
"""Show temporary text instead of prompt widget."""
self._set_prompt_active(False)
self._previous_widget = PreviousWidget.none
log.statusbar.debug("Hiding prompt widget, queue: {}".format(
self._text_queue))
if self._timer_was_active:
# Restart the text pop timer if it was active before hiding.
self._pop_text()
self._text_pop_timer.start()
self._timer_was_active = False
log.statusbar.debug("Hiding prompt widget")
self._stack.setCurrentWidget(self.txt)
self.maybe_hide()
def _disp_text(self, text, severity, immediately=False):
"""Inner logic for disp_error and disp_temp_text.
Args:
text: The message to display.
severity: The severity of the messages.
immediately: If set, message gets displayed immediately instead of
queued.
"""
log.statusbar.debug("Displaying text: {} (severity={})".format(
text, severity))
mindelta = config.get('ui', 'message-timeout')
if self._stopwatch.isNull():
delta = None
self._stopwatch.start()
else:
delta = self._stopwatch.restart()
log.statusbar.debug("queue: {} / delta: {}".format(
self._text_queue, delta))
if not self._text_queue and (delta is None or delta > mindelta):
# If the queue is empty and we didn't print messages for long
# enough, we can take the short route and display the message
# immediately. We then start the pop_timer only to restore the
# normal state in 2 seconds.
log.statusbar.debug("Displaying immediately")
self._set_severity(severity)
self.show()
self.txt.set_text(self.txt.Text.temp, text)
self._text_pop_timer.start()
elif self._text_queue and self._text_queue[-1] == (severity, text):
# If we get the same message multiple times in a row and we're
# still displaying it *anyways* we ignore the new one
log.statusbar.debug("ignoring")
elif immediately:
# This message is a reaction to a keypress and should be displayed
# immediately, temporarily interrupting the message queue.
# We display this immediately and restart the timer.to clear it and
# display the rest of the queue later.
log.statusbar.debug("Moving to beginning of queue")
self._set_severity(severity)
self.show()
self.txt.set_text(self.txt.Text.temp, text)
self._text_pop_timer.start()
else:
# There are still some messages to be displayed, so we queue this
# up.
log.statusbar.debug("queueing")
self._text_queue.append((severity, text))
self._text_pop_timer.start()
@pyqtSlot(str, bool)
def disp_error(self, text, immediately=False):
"""Display an error in the statusbar.
Args:
text: The message to display.
immediately: If set, message gets displayed immediately instead of
queued.
"""
self._disp_text(text, Severity.error, immediately)
@pyqtSlot(str, bool)
def disp_warning(self, text, immediately=False):
"""Display a warning in the statusbar.
Args:
text: The message to display.
immediately: If set, message gets displayed immediately instead of
queued.
"""
self._disp_text(text, Severity.warning, immediately)
@pyqtSlot(str, bool)
def disp_temp_text(self, text, immediately):
"""Display a temporary text in the statusbar.
Args:
text: The message to display.
immediately: If set, message gets displayed immediately instead of
queued.
"""
self._disp_text(text, Severity.normal, immediately)
@pyqtSlot(str)
def set_text(self, val):
"""Set a normal (persistent) text in the status bar."""
@ -539,11 +335,6 @@ class StatusBar(QWidget):
usertypes.KeyMode.caret]:
self.set_mode_active(old_mode, False)
@config.change_filter('ui', 'message-timeout')
def set_pop_timer_interval(self):
"""Update message timeout when config changed."""
self._text_pop_timer.setInterval(config.get('ui', 'message-timeout'))
def resizeEvent(self, e):
"""Extend resizeEvent of QWidget to emit a resized signal afterwards.

View File

@ -276,7 +276,7 @@ class TabbedBrowser(tabwidget.TabWidget):
# We display a warnings for URLs which are not empty but invalid -
# but we don't return here because we want the tab to close either
# way.
urlutils.invalid_url_error(self._win_id, tab.url(), "saving tab")
urlutils.invalid_url_error(tab.url(), "saving tab")
tab.shutdown()
self.removeTab(idx)
tab.deleteLater()
@ -650,7 +650,7 @@ class TabbedBrowser(tabwidget.TabWidget):
except qtutils.QtValueError:
# show an error only if the mark is not automatically set
if key != "'":
message.error(self._win_id, "Failed to set mark: url invalid")
message.error("Failed to set mark: url invalid")
return
point = self.currentWidget().scroller.pos_px()
@ -687,9 +687,9 @@ class TabbedBrowser(tabwidget.TabWidget):
self.openurl(url, newtab=False)
self.cur_load_finished.connect(callback)
else:
message.error(self._win_id, "Mark {} is not set".format(key))
message.error("Mark {} is not set".format(key))
elif urlkey is None:
message.error(self._win_id, "Current URL is invalid!")
message.error("Current URL is invalid!")
elif urlkey in self._local_marks and key in self._local_marks[urlkey]:
point = self._local_marks[urlkey][key]
@ -700,4 +700,4 @@ class TabbedBrowser(tabwidget.TabWidget):
tab.scroller.to_point(point)
else:
message.error(self._win_id, "Mark {} is not set".format(key))
message.error("Mark {} is not set".format(key))

View File

@ -38,17 +38,15 @@ class ExternalEditor(QObject):
_file: The file handle as tempfile.NamedTemporaryFile. Note that this
handle will be closed after the initial file has been created.
_proc: The GUIProcess of the editor.
_win_id: The window ID the ExternalEditor is associated with.
"""
editing_finished = pyqtSignal(str)
def __init__(self, win_id, parent=None):
def __init__(self, parent=None):
super().__init__(parent)
self._text = None
self._file = None
self._proc = None
self._win_id = win_id
def _cleanup(self):
"""Clean up temporary files after the editor closed."""
@ -61,8 +59,7 @@ class ExternalEditor(QObject):
except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error(self._win_id,
"Failed to delete tempfile... ({})".format(e))
message.error("Failed to delete tempfile... ({})".format(e))
@pyqtSlot(int, QProcess.ExitStatus)
def on_proc_closed(self, exitcode, exitstatus):
@ -85,8 +82,7 @@ class ExternalEditor(QObject):
except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error(self._win_id, "Failed to read back edited file: "
"{}".format(e))
message.error("Failed to read back edited file: {}".format(e))
return
log.procs.debug("Read back: {}".format(text))
self.editing_finished.emit(text)
@ -119,11 +115,9 @@ class ExternalEditor(QObject):
fobj.write(text)
self._file = fobj
except OSError as e:
message.error(self._win_id, "Failed to create initial file: "
"{}".format(e))
message.error("Failed to create initial file: {}".format(e))
return
self._proc = guiprocess.GUIProcess(self._win_id, what='editor',
parent=self)
self._proc = guiprocess.GUIProcess(what='editor', parent=self)
self._proc.finished.connect(self.on_proc_closed)
self._proc.error.connect(self.on_proc_error)
editor = config.get('general', 'editor')

View File

@ -50,7 +50,6 @@ class GUIProcess(QObject):
verbose: Whether to show more messages.
_started: Whether the underlying process is started.
_proc: The underlying QProcess.
_win_id: The window ID this process is used in.
_what: What kind of thing is spawned (process/editor/userscript/...).
Used in messages.
@ -62,10 +61,9 @@ class GUIProcess(QObject):
finished = pyqtSignal(int, QProcess.ExitStatus)
started = pyqtSignal()
def __init__(self, win_id, what, *, verbose=False, additional_env=None,
def __init__(self, what, *, verbose=False, additional_env=None,
parent=None):
super().__init__(parent)
self._win_id = win_id
self._what = what
self.verbose = verbose
self._started = False
@ -90,8 +88,7 @@ class GUIProcess(QObject):
def on_error(self, error):
"""Show a message if there was an error while spawning."""
msg = ERROR_STRINGS[error]
message.error(self._win_id, "Error while spawning {}: {}".format(
self._what, msg), immediately=True)
message.error("Error while spawning {}: {}".format(self._what, msg))
@pyqtSlot(int, QProcess.ExitStatus)
def on_finished(self, code, status):
@ -100,18 +97,16 @@ class GUIProcess(QObject):
log.procs.debug("Process finished with code {}, status {}.".format(
code, status))
if status == QProcess.CrashExit:
message.error(self._win_id,
"{} crashed!".format(self._what.capitalize()),
immediately=True)
message.error("{} crashed!".format(self._what.capitalize()))
elif status == QProcess.NormalExit and code == 0:
if self.verbose:
message.info(self._win_id, "{} exited successfully.".format(
message.info("{} exited successfully.".format(
self._what.capitalize()))
else:
assert status == QProcess.NormalExit
# We call this 'status' here as it makes more sense to the user -
# it's actually 'code'.
message.error(self._win_id, "{} exited with status {}.".format(
message.error("{} exited with status {}.".format(
self._what.capitalize(), code))
stderr = bytes(self._proc.readAllStandardError()).decode('utf-8')
@ -137,7 +132,7 @@ class GUIProcess(QObject):
fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args))
log.procs.debug("Executing: {}".format(fake_cmdline))
if self.verbose:
message.info(self._win_id, 'Executing: ' + fake_cmdline)
message.info('Executing: ' + fake_cmdline)
def start(self, cmd, args, mode=None):
"""Convenience wrapper around QProcess::start."""
@ -158,8 +153,8 @@ class GUIProcess(QObject):
log.procs.debug("Process started.")
self._started = True
else:
message.error(self._win_id, "Error while spawning {}: {}.".format(
self._what, self._proc.error()), immediately=True)
message.error("Error while spawning {}: {}.".format(self._what,
self._proc.error()))
def exit_status(self):
return self._proc.exitStatus()

View File

@ -180,17 +180,14 @@ class SaveManager(QObject):
try:
saveable.save(silent=True)
except OSError as e:
message.error('current', "Failed to auto-save {}: "
"{}".format(key, e))
message.error("Failed to auto-save {}: {}".format(key, e))
@cmdutils.register(instance='save-manager', name='save',
star_args_optional=True)
@cmdutils.argument('win_id', win_id=True)
def save_command(self, win_id, *what):
def save_command(self, *what):
"""Save configs and state.
Args:
win_id: The window this command is executed in.
*what: What to save (`config`/`key-config`/`cookies`/...).
If not given, everything is saved.
"""
@ -201,12 +198,10 @@ class SaveManager(QObject):
explicit = False
for key in what:
if key not in self.saveables:
message.error(win_id, "{} is nothing which can be "
"saved".format(key))
message.error("{} is nothing which can be saved".format(key))
else:
try:
self.save(key, explicit=explicit, force=True)
except OSError as e:
message.error(win_id, "Could not save {}: "
"{}".format(key, e))
message.error("Could not save {}: {}".format(key, e))
log.save.debug(":save saved {}".format(', '.join(what)))

View File

@ -429,14 +429,12 @@ class SessionManager(QObject):
win.close()
@cmdutils.register(name=['session-save', 'w'], instance='session-manager')
@cmdutils.argument('win_id', win_id=True)
@cmdutils.argument('name', completion=usertypes.Completion.sessions)
def session_save(self, win_id, name: str=default, current=False,
quiet=False, force=False):
def session_save(self, name: str=default, current=False, quiet=False,
force=False):
"""Save a session.
Args:
win_id: The current window ID.
name: The name of the session. If not given, the session configured
in general -> session-default-name is saved.
current: Save the current session instead of the default.
@ -460,8 +458,7 @@ class SessionManager(QObject):
.format(e))
else:
if not quiet:
message.info(win_id, "Saved session {}.".format(name),
immediately=True)
message.info("Saved session {}.".format(name))
@cmdutils.register(instance='session-manager')
@cmdutils.argument('name', completion=usertypes.Completion.sessions)

View File

@ -87,36 +87,33 @@ def repeat(times: int, command, win_id):
@cmdutils.register(hide=True)
@cmdutils.argument('win_id', win_id=True)
def message_error(win_id, text):
def message_error(text):
"""Show an error message in the statusbar.
Args:
text: The text to show.
"""
message.error(win_id, text)
message.error(text)
@cmdutils.register(hide=True)
@cmdutils.argument('win_id', win_id=True)
def message_info(win_id, text):
def message_info(text):
"""Show an info message in the statusbar.
Args:
text: The text to show.
"""
message.info(win_id, text)
message.info(text)
@cmdutils.register(hide=True)
@cmdutils.argument('win_id', win_id=True)
def message_warning(win_id, text):
def message_warning(text):
"""Show a warning message in the statusbar.
Args:
text: The text to show.
"""
message.warning(win_id, text)
message.warning(text)
@cmdutils.register(debug=True)

View File

@ -37,6 +37,14 @@ QueuedMsg = collections.namedtuple(
'QueuedMsg', ['time', 'win_id', 'method_name', 'text', 'args', 'kwargs'])
global_bridge = None
def init():
global global_bridge
global_bridge = GlobalMessageBridge()
def _log_stack(typ, stack):
"""Log the given message stacktrace.
@ -132,12 +140,11 @@ def on_focus_changed():
getattr(bridge, msg.method_name)(text, *msg.args, **msg.kwargs)
def error(win_id, message, immediately=False, *, stack=None):
def error(message, *, stack=None):
"""Convenience function to display an error message in the statusbar.
Args:
win_id: The ID of the window which is calling this function.
others: See MessageBridge.error.
message: The message to show
stack: The stack trace to show.
"""
if stack is None:
@ -146,28 +153,18 @@ def error(win_id, message, immediately=False, *, stack=None):
else:
typ = 'error (from exception)'
_log_stack(typ, stack)
_wrapper(win_id, 'error', message, immediately)
global_bridge.show_message.emit(usertypes.MessageLevel.error, message)
def warning(win_id, message, immediately=False):
"""Convenience function to display a warning message in the statusbar.
Args:
win_id: The ID of the window which is calling this function.
others: See MessageBridge.warning.
"""
def warning(message):
"""Convenience function to display a warning message in the statusbar."""
_log_stack('warning', traceback.format_stack())
_wrapper(win_id, 'warning', message, immediately)
global_bridge.show_message.emit(usertypes.MessageLevel.warning, message)
def info(win_id, message, immediately=True):
"""Convenience function to display an info message in the statusbar.
Args:
win_id: The ID of the window which is calling this function.
others: See MessageBridge.info.
"""
_wrapper(win_id, 'info', message, immediately)
def info(message):
"""Convenience function to display an info message in the statusbar."""
global_bridge.show_message.emit(usertypes.MessageLevel.info, message)
def set_cmd_text(win_id, txt):
@ -252,19 +249,24 @@ def confirm_async(win_id, message, yes_action, no_action=None, default=None):
_wrapper(win_id, 'ask', q, blocking=False)
class GlobalMessageBridge(QObject):
"""Global (not per-window) message bridge for errors/infos/warnings.
Signals:
show_message: Show a message
arg 0: A MessageLevel member
arg 1: The text to show
"""
show_message = pyqtSignal(usertypes.MessageLevel, str)
class MessageBridge(QObject):
"""Bridge for messages to be shown in the statusbar.
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_warning: Display a warning message.
args: See s_error.
s_set_text: Set a persistent text in the statusbar.
arg: The text to set.
s_maybe_reset_text: Reset the text if it hasn't been changed yet.
@ -280,9 +282,6 @@ class MessageBridge(QObject):
Qt.DirectConnection!
"""
s_error = pyqtSignal(str, bool)
s_warning = pyqtSignal(str, bool)
s_info = pyqtSignal(str, bool)
s_set_text = pyqtSignal(str)
s_maybe_reset_text = pyqtSignal(str)
s_set_cmd_text = pyqtSignal(str)
@ -291,55 +290,6 @@ class MessageBridge(QObject):
def __repr__(self):
return utils.get_repr(self)
def error(self, msg, immediately=False, *, log_stack=True):
"""Display an error in the statusbar.
Args:
msg: The message to show.
immediately: Whether to display the message immediately (True) or
queue it for displaying when all other messages are
displayed (False). Messages resulting from direct user
input should be displayed immediately, all other
messages should be queued.
log_stack: Log the stacktrace of the error.
"""
msg = str(msg)
log.message.error(msg)
if log_stack:
_log_stack('error (delayed)', traceback.format_stack())
self.s_error.emit(msg, immediately)
def warning(self, msg, immediately=False, *, log_stack=True):
"""Display a warning in the statusbar.
Args:
msg: The message to show.
immediately: Whether to display the message immediately (True) or
queue it for displaying when all other messages are
displayed (False). Messages resulting from direct user
input should be displayed immediately, all other
messages should be queued.
log_stack: Log the stacktrace of the warning.
"""
msg = str(msg)
log.message.warning(msg)
if log_stack:
_log_stack('warning (delayed)', traceback.format_stack())
self.s_warning.emit(msg, immediately)
def info(self, msg, immediately=True, *, log_stack=False):
"""Display an info text in the statusbar.
Args:
See error(). Note immediately is True by default, because messages
do rarely happen without user interaction.
log_stack is ignored.
"""
msg = str(msg)
log.message.info(msg)
self.s_info.emit(msg, immediately)
def set_cmd_text(self, text, *, log_stack=False):
"""Set the command text of the statusbar.

View File

@ -321,11 +321,10 @@ def qurl_from_user_input(urlstr):
return QUrl('http://[{}]{}'.format(ipstr, rest))
def invalid_url_error(win_id, url, action):
def invalid_url_error(url, action):
"""Display an error message for a URL.
Args:
win_id: The window ID to show the error message in.
action: The action which was interrupted by the error.
"""
if url.isValid():
@ -333,7 +332,7 @@ def invalid_url_error(win_id, url, action):
url.toDisplayString()))
errstring = get_errstring(
url, "Trying to {} with invalid URL".format(action))
message.error(win_id, errstring)
message.error(errstring)
def raise_cmdexc_if_invalid(url):

View File

@ -265,6 +265,9 @@ arg2backend = {
JsWorld = enum('JsWorld', ['main', 'application', 'user', 'jseval'])
MessageLevel = enum('MessageLevel', ['error', 'warning', 'info'])
# Where a download should be saved
class DownloadTarget:

View File

@ -274,21 +274,6 @@ def format_seconds(total_seconds):
return prefix + ':'.join(chunks)
def format_timedelta(td):
"""Format a timedelta to get a "1h 5m 1s" string."""
prefix = '-' if td.total_seconds() < 0 else ''
hours, rem = divmod(abs(round(td.total_seconds())), 3600)
minutes, seconds = divmod(rem, 60)
chunks = []
if hours:
chunks.append('{}h'.format(hours))
if minutes:
chunks.append('{}m'.format(minutes))
if seconds or not chunks:
chunks.append('{}s'.format(seconds))
return prefix + ' '.join(chunks)
def format_size(size, base=1024, suffix=''):
"""Format a byte size so it's human readable.

View File

@ -83,12 +83,7 @@ class LogLine(testprocess.Line):
if self.function is None and self.line == 0:
self.line = None
self.traceback = line.get('traceback')
self.full_message = line['message']
msg_match = re.match(r'^(\[(?P<prefix>\d+s ago)\] )?(?P<message>.*)',
self.full_message, re.DOTALL)
self.prefix = msg_match.group('prefix')
self.message = msg_match.group('message')
self.message = line['message']
self.expected = is_ignored_qt_message(self.message)
self.use_color = False

View File

@ -211,13 +211,6 @@ def test_quteprocess_quitting(qtbot, quteproc_process):
'line': 233,
}
),
(
# With [2s ago] marker
'{"created": 0, "msecs": 0, "levelname": "DEBUG", "name": "foo", '
'"module": "foo", "funcName": "foo", "lineno": 0, "levelno": 10, '
'"message": "[2s ago] test"}',
{'prefix': '2s ago', 'message': 'test'}
),
(
# ResourceWarning
'{"created": 0, "msecs": 0, "levelname": "WARNING", '
@ -228,8 +221,7 @@ def test_quteprocess_quitting(qtbot, quteproc_process):
{'category': 'py.warnings'}
),
], ids=['normal', 'vdebug', 'unknown module', 'expected message',
'weird Qt location', 'QXcbXSettings', '2s ago marker',
'resourcewarning'])
'weird Qt location', 'QXcbXSettings', 'resourcewarning'])
def test_log_line_parse(data, attrs):
line = quteprocess.LogLine(data)
for name, expected in attrs.items():

View File

@ -43,7 +43,7 @@ def patch_things(config_stub, message_mock, monkeypatch, stubs):
@pytest.fixture
def editor():
ed = editormod.ExternalEditor(0)
ed = editormod.ExternalEditor()
ed.editing_finished = mock.Mock()
yield ed
ed._cleanup()

View File

@ -37,7 +37,7 @@ def guiprocess_message_mock(message_mock):
@pytest.fixture()
def proc(qtbot):
"""A fixture providing a GUIProcess and cleaning it up after the test."""
p = guiprocess.GUIProcess(0, 'testprocess')
p = guiprocess.GUIProcess('testprocess')
yield p
if p._proc.state() == QProcess.Running:
with qtbot.waitSignal(p.finished, timeout=10000,
@ -50,7 +50,7 @@ def proc(qtbot):
@pytest.fixture()
def fake_proc(monkeypatch, stubs):
"""A fixture providing a GUIProcess with a mocked QProcess."""
p = guiprocess.GUIProcess(0, 'testprocess')
p = guiprocess.GUIProcess('testprocess')
monkeypatch.setattr(p, '_proc', stubs.fake_qprocess())
return p
@ -86,7 +86,7 @@ def test_start_verbose(proc, qtbot, guiprocess_message_mock, py_proc):
def test_start_env(monkeypatch, qtbot, py_proc):
monkeypatch.setenv('QUTEBROWSER_TEST_1', '1')
env = {'QUTEBROWSER_TEST_2': '2'}
proc = guiprocess.GUIProcess(0, 'testprocess', additional_env=env)
proc = guiprocess.GUIProcess('testprocess', additional_env=env)
argv = py_proc("""
import os

View File

@ -431,11 +431,11 @@ def test_invalid_url_error(urlutils_message_mock, url, valid, has_err_string):
assert qurl.isValid() == valid
if valid:
with pytest.raises(ValueError):
urlutils.invalid_url_error(0, qurl, '')
urlutils.invalid_url_error(qurl, '')
assert not urlutils_message_mock.messages
else:
assert bool(qurl.errorString()) == has_err_string
urlutils.invalid_url_error(0, qurl, 'frozzle')
urlutils.invalid_url_error(qurl, 'frozzle')
msg = urlutils_message_mock.getmsg(urlutils_message_mock.Level.error)
if has_err_string:

View File

@ -377,25 +377,6 @@ def test_format_seconds(seconds, out):
assert utils.format_seconds(seconds) == out
@pytest.mark.parametrize('td, out', [
(datetime.timedelta(seconds=-1), '-1s'),
(datetime.timedelta(seconds=0), '0s'),
(datetime.timedelta(seconds=59), '59s'),
(datetime.timedelta(seconds=120), '2m'),
(datetime.timedelta(seconds=60.4), '1m'),
(datetime.timedelta(seconds=63), '1m 3s'),
(datetime.timedelta(seconds=-64), '-1m 4s'),
(datetime.timedelta(seconds=3599), '59m 59s'),
(datetime.timedelta(seconds=3600), '1h'),
(datetime.timedelta(seconds=3605), '1h 5s'),
(datetime.timedelta(seconds=3723), '1h 2m 3s'),
(datetime.timedelta(seconds=3780), '1h 3m'),
(datetime.timedelta(seconds=36000), '10h'),
])
def test_format_timedelta(td, out):
assert utils.format_timedelta(td) == out
class TestFormatSize:
"""Tests for format_size.