Initial implementation of new messages
This commit is contained in:
parent
5bef7dc74c
commit
f16b96aa28
@ -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)
|
||||
|
@ -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!")
|
||||
|
@ -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
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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])
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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():
|
||||
|
@ -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.
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
)),
|
||||
])
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
118
qutebrowser/mainwindow/messageview.py
Normal file
118
qutebrowser/mainwindow/messageview.py
Normal 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()
|
@ -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.
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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')
|
||||
|
@ -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()
|
||||
|
@ -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)))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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():
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user