From f16b96aa28fc68c0b9caf15241067e51797dd604 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Sep 2016 20:52:32 +0200 Subject: [PATCH] Initial implementation of new messages --- qutebrowser/app.py | 18 +- qutebrowser/browser/adblock.py | 34 ++- qutebrowser/browser/browsertab.py | 2 +- qutebrowser/browser/commands.py | 67 +++--- qutebrowser/browser/hints.py | 20 +- qutebrowser/browser/mouse.py | 8 +- qutebrowser/browser/navigate.py | 4 +- qutebrowser/browser/qutescheme.py | 4 +- qutebrowser/browser/urlmarks.py | 8 +- qutebrowser/browser/webkit/downloads.py | 9 +- qutebrowser/browser/webkit/mhtml.py | 8 +- .../browser/webkit/network/networkmanager.py | 3 +- .../webkit/network/webkitqutescheme.py | 6 +- qutebrowser/browser/webkit/webpage.py | 4 +- qutebrowser/commands/command.py | 6 +- qutebrowser/commands/runners.py | 5 +- qutebrowser/commands/userscripts.py | 25 +- qutebrowser/config/config.py | 3 +- qutebrowser/config/configdata.py | 64 ++++-- qutebrowser/config/parsers/keyconf.py | 10 +- qutebrowser/keyinput/keyparser.py | 3 +- qutebrowser/mainwindow/mainwindow.py | 66 +++--- qutebrowser/mainwindow/messageview.py | 118 ++++++++++ qutebrowser/mainwindow/statusbar/bar.py | 213 +----------------- qutebrowser/mainwindow/tabbedbrowser.py | 10 +- qutebrowser/misc/editor.py | 16 +- qutebrowser/misc/guiprocess.py | 21 +- qutebrowser/misc/savemanager.py | 13 +- qutebrowser/misc/sessions.py | 9 +- qutebrowser/misc/utilcmds.py | 15 +- qutebrowser/utils/message.py | 110 +++------ qutebrowser/utils/urlutils.py | 5 +- qutebrowser/utils/usertypes.py | 3 + qutebrowser/utils/utils.py | 15 -- tests/end2end/fixtures/quteprocess.py | 7 +- tests/end2end/fixtures/test_quteprocess.py | 10 +- tests/unit/misc/test_editor.py | 2 +- tests/unit/misc/test_guiprocess.py | 6 +- tests/unit/utils/test_urlutils.py | 4 +- tests/unit/utils/test_utils.py | 19 -- 40 files changed, 389 insertions(+), 584 deletions(-) create mode 100644 qutebrowser/mainwindow/messageview.py diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 9f08a636b..94b8b7e26 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -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) diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py index 0b02edc7e..e7131640f 100644 --- a/qutebrowser/browser/adblock.py +++ b/qutebrowser/browser/adblock.py @@ -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!") diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index d8d0b7b62..29b82d969 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -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 diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index df8b13876..473906132 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -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( diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index d7f70e980..a80935a7d 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -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]) diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index a91f8961b..aaa5dd82f 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -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. diff --git a/qutebrowser/browser/navigate.py b/qutebrowser/browser/navigate.py index cf4619924..df9fce6ab 100644 --- a/qutebrowser/browser/navigate.py +++ b/qutebrowser/browser/navigate.py @@ -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) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 2f28ea58a..3c426c232 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -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) diff --git a/qutebrowser/browser/urlmarks.py b/qutebrowser/browser/urlmarks.py index 4280f0cd9..90b2defc0 100644 --- a/qutebrowser/browser/urlmarks.py +++ b/qutebrowser/browser/urlmarks.py @@ -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(): diff --git a/qutebrowser/browser/webkit/downloads.py b/qutebrowser/browser/webkit/downloads.py index 9a92c1e29..164440687 100644 --- a/qutebrowser/browser/webkit/downloads.py +++ b/qutebrowser/browser/webkit/downloads.py @@ -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. diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index c615fcad4..cc9d75d00 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -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): diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index f076ca152..04ce58975 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -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 diff --git a/qutebrowser/browser/webkit/network/webkitqutescheme.py b/qutebrowser/browser/webkit/network/webkitqutescheme.py index 997ed262f..4ae68d3d7 100644 --- a/qutebrowser/browser/webkit/network/webkitqutescheme.py +++ b/qutebrowser/browser/webkit/network/webkitqutescheme.py @@ -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) diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index 0db7b1468..2f60886b3 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -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) diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index 1d28bde2a..ea1d0d46b 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -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: diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 970fbadbe..c96d809ad 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -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()) diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index a8d741f6c..4601ed528 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -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 diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index e6aa3db7c..46bd5faf8 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -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. diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 43923a795..3b62f3183 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -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 )), ]) diff --git a/qutebrowser/config/parsers/keyconf.py b/qutebrowser/config/parsers/keyconf.py index c994913db..7952c32e5 100644 --- a/qutebrowser/config/parsers/keyconf.py +++ b/qutebrowser/config/parsers/keyconf.py @@ -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) diff --git a/qutebrowser/keyinput/keyparser.py b/qutebrowser/keyinput/keyparser.py index cec080f53..0b46dffc4 100644 --- a/qutebrowser/keyinput/keyparser.py +++ b/qutebrowser/keyinput/keyparser.py @@ -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): diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index b2fa4d69d..2b72e5f06 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -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): diff --git a/qutebrowser/mainwindow/messageview.py b/qutebrowser/mainwindow/messageview.py new file mode 100644 index 000000000..43ad1a132 --- /dev/null +++ b/qutebrowser/mainwindow/messageview.py @@ -0,0 +1,118 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 Florian Bruhin (The Compiler) +# +# 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 . + +"""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() diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index de9df501e..5e67e7d4e 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -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. diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 1f0055df1..6ff19e69d 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -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)) diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index c72841f30..1705df09c 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -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') diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 220d87a32..e8e0d579e 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -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() diff --git a/qutebrowser/misc/savemanager.py b/qutebrowser/misc/savemanager.py index ac42d2c32..7e85b013e 100644 --- a/qutebrowser/misc/savemanager.py +++ b/qutebrowser/misc/savemanager.py @@ -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))) diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index c6ea12444..cd3d2f3bc 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -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) diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index b28f100d2..2447afd53 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -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) diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py index 4a40e6032..96d132fcb 100644 --- a/qutebrowser/utils/message.py +++ b/qutebrowser/utils/message.py @@ -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. diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 0ba14c479..7af8f933a 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -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): diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 49dd938e9..6dd3d9674 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -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: diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 72d6f83e2..2e5142511 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -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. diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 52100f378..acd6b0f49 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -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\d+s ago)\] )?(?P.*)', - 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 diff --git a/tests/end2end/fixtures/test_quteprocess.py b/tests/end2end/fixtures/test_quteprocess.py index 875ea38e7..80235e233 100644 --- a/tests/end2end/fixtures/test_quteprocess.py +++ b/tests/end2end/fixtures/test_quteprocess.py @@ -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(): diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index c6c45df20..8bb07698e 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -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() diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index 39cd59092..a706accb5 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -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 diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index d827625cb..3515696d0 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -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: diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index cd2edb8d2..99439a583 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -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.