diff --git a/qutebrowser/app.py b/qutebrowser/app.py index bf8380921..6d10d6b6b 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -151,7 +151,8 @@ class Application(QApplication): log.init.debug("Initializing websettings...") websettings.init() log.init.debug("Initializing quickmarks...") - quickmarks.init() + quickmark_manager = quickmarks.QuickmarkManager() + objreg.register('quickmark-manager', quickmark_manager) log.init.debug("Initializing proxy...") proxy.init() log.init.debug("Initializing cookies...") @@ -313,7 +314,7 @@ class Application(QApplication): quickstart_done = False if not quickstart_done: tabbed_browser = objreg.get('tabbed-browser', scope='window', - window='current') + window='last-focused') tabbed_browser.tabopen( QUrl('http://www.qutebrowser.org/quickstart.html')) try: @@ -341,6 +342,7 @@ class Application(QApplication): config_obj = objreg.get('config') self.lastWindowClosed.connect(self.shutdown) config_obj.style_changed.connect(style.get_stylesheet.cache_clear) + self.focusChanged.connect(self.on_focus_changed) def _get_widgets(self): """Get a string list of all widgets.""" @@ -541,7 +543,7 @@ class Application(QApplication): out = traceback.format_exc() qutescheme.pyeval_output = out tabbed_browser = objreg.get('tabbed-browser', scope='window', - window='current') + window='last-focused') tabbed_browser.openurl(QUrl('qute:pyeval'), newtab=True) @cmdutils.register(instance='app') @@ -636,12 +638,8 @@ class Application(QApplication): self.removeEventFilter(self._event_filter) except AttributeError: pass - # Close all tabs - for win_id in objreg.window_registry: - log.destroy.debug("Closing tabs in window {}...".format(win_id)) - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window=win_id) - tabbed_browser.shutdown() + # Close all windows + QApplication.closeAllWindows() # Shut down IPC try: objreg.get('ipc-server').shutdown() @@ -663,14 +661,19 @@ class Application(QApplication): pass else: to_save.append(("keyconfig", key_config.save)) - to_save += [("window geometry", self._save_geometry), - ("quickmarks", quickmarks.save)] + to_save += [("window geometry", self._save_geometry)] try: command_history = objreg.get('command-history') except KeyError: pass else: to_save.append(("command history", command_history.save)) + try: + quickmark_manager = objreg.get('quickmark-manager') + except KeyError: + pass + else: + to_save.append(("command history", quickmark_manager.save)) try: state_config = objreg.get('state-config') except KeyError: @@ -700,6 +703,20 @@ class Application(QApplication): # segfaults. QTimer.singleShot(0, functools.partial(self.exit, status)) + def on_focus_changed(self, _old, new): + """Register currently focused main window in the object registry.""" + if new is None: + window = None + else: + window = new.window() + if window is None or not isinstance(window, mainwindow.MainWindow): + try: + objreg.delete('last-focused-main-window') + except KeyError: + pass + else: + objreg.register('last-focused-main-window', window, update=True) + def exit(self, status): """Extend QApplication::exit to log the event.""" log.destroy.debug("Now calling QApplication::exit.") diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 0fc7c4ea8..5a3be8ed0 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -36,7 +36,7 @@ import pygments.formatters from qutebrowser.commands import userscripts, cmdexc, cmdutils from qutebrowser.config import config -from qutebrowser.browser import quickmarks, webelem +from qutebrowser.browser import webelem from qutebrowser.utils import (message, editor, usertypes, log, qtutils, urlutils, objreg, utils) @@ -815,7 +815,8 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') def quickmark_save(self): """Save the current page as a quickmark.""" - quickmarks.prompt_save(self._win_id, self._current_url()) + quickmark_manager = objreg.get('quickmark-manager') + quickmark_manager.prompt_save(self._win_id, self._current_url()) @cmdutils.register(instance='command-dispatcher', scope='window', completion=[usertypes.Completion.quickmark]) @@ -828,7 +829,7 @@ class CommandDispatcher: bg: Load the quickmark in a new background tab. window: Load the quickmark in a new window. """ - url = quickmarks.get(name) + url = objreg.get('quickmark-manager').get(name) self._open(url, tab, bg, window) @cmdutils.register(instance='command-dispatcher', name='inspector', diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index d47ea020a..566ee0e88 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -62,6 +62,7 @@ class DownloadItem(QObject): _reply: The QNetworkReply associated with this download. _last_done: The count of bytes which where downloaded when calculating the speed the last time. + _error: The current error message, or None Signals: data_changed: The downloads metadata changed. @@ -88,6 +89,7 @@ class DownloadItem(QObject): self._reply = reply self._bytes_total = None self._speed = 0 + self._error = None self.basename = '???' samples = int(self.SPEED_AVG_WINDOW * (1000 / self.SPEED_REFRESH_INTERVAL)) @@ -127,9 +129,13 @@ class DownloadItem(QObject): down = utils.format_size(self._bytes_done, suffix='B') perc = self._percentage() remaining = self._remaining_time() + if self._error is None: + errmsg = "" + else: + errmsg = " - {}".format(self._error) if all(e is None for e in (perc, remaining, self._bytes_total)): - return ('{name} [{speed:>10}|{down}]'.format( - name=self.basename, speed=speed, down=down)) + return ('{name} [{speed:>10}|{down}]{errmsg}'.format( + name=self.basename, speed=speed, down=down, errmsg=errmsg)) if perc is None: perc = '??' else: @@ -140,9 +146,9 @@ class DownloadItem(QObject): remaining = utils.format_seconds(remaining) total = utils.format_size(self._bytes_total, suffix='B') return ('{name} [{speed:>10}|{remaining:>5}|{perc:>2}%|' - '{down}/{total}]'.format(name=self.basename, speed=speed, - remaining=remaining, perc=perc, - down=down, total=total)) + '{down}/{total}]{errmsg}'.format( + name=self.basename, speed=speed, remaining=remaining, + perc=perc, down=down, total=total, errmsg=errmsg)) def _die(self, msg): """Abort the download and emit an error.""" @@ -150,17 +156,19 @@ class DownloadItem(QObject): self._reply.finished.disconnect() self._reply.error.disconnect() self._reply.readyRead.disconnect() + self._error = msg self._bytes_done = self._bytes_total self.timer.stop() self.error.emit(msg) self._reply.abort() self._reply.deleteLater() + self._reply = None if self._fileobj is not None: try: self._fileobj.close() except OSError as e: self.error.emit(e.strerror) - self.finished.emit() + self.data_changed.emit() def _percentage(self): """The current download percentage, or None if unknown.""" @@ -187,7 +195,10 @@ class DownloadItem(QObject): start = config.get('colors', 'downloads.bg.start') stop = config.get('colors', 'downloads.bg.stop') system = config.get('colors', 'downloads.bg.system') - if self._percentage() is None: + error = config.get('colors', 'downloads.bg.error') + if self._error is not None: + return error + elif self._percentage() is None: return start else: return utils.interpolate_color(start, stop, self._percentage(), @@ -198,8 +209,9 @@ class DownloadItem(QObject): log.downloads.debug("cancelled") self.cancelled.emit() self._is_cancelled = True - self._reply.abort() - self._reply.deleteLater() + if self._reply is not None: + self._reply.abort() + self._reply.deleteLater() if self._fileobj is not None: self._fileobj.close() if self._filename is not None and os.path.exists(self._filename): @@ -352,7 +364,7 @@ class DownloadManager(QAbstractListModel): page: The QWebPage to get the download from. """ if not url.isValid(): - urlutils.invalid_url_error('current', url, "start download") + urlutils.invalid_url_error('last-focused', url, "start download") return req = QNetworkRequest(url) reply = page.networkAccessManager().get(req) @@ -389,7 +401,8 @@ class DownloadManager(QAbstractListModel): functools.partial(self.on_finished, download)) download.data_changed.connect( functools.partial(self.on_data_changed, download)) - download.error.connect(self.on_error) + download.error.connect( + functools.partial(self.on_error, download)) download.basename = suggested_filename idx = len(self.downloads) + 1 self.beginInsertRows(QModelIndex(), idx, idx) @@ -407,7 +420,7 @@ class DownloadManager(QAbstractListModel): self.questions.append(q) download.cancelled.connect(q.abort) message_bridge = objreg.get('message-bridge', scope='window', - window='current') + window='last-focused') message_bridge.ask(q, blocking=False) @pyqtSlot(DownloadItem) @@ -428,10 +441,10 @@ class DownloadManager(QAbstractListModel): qtutils.ensure_valid(model_idx) self.dataChanged.emit(model_idx, model_idx) - @pyqtSlot(str) - def on_error(self, msg): + @pyqtSlot(DownloadItem, str) + def on_error(self, download, msg): """Display error message on download errors.""" - message.error('current', "Download error: {}".format(msg)) + message.error('last-focused', "Download error: {}".format(msg)) def last_index(self): """Get the last index in the model. @@ -465,6 +478,11 @@ class DownloadManager(QAbstractListModel): data = item.bg_color() elif role == ModelRole.item: data = item + elif role == Qt.ToolTipRole: + if item._error is None: + data = QVariant() + else: + return item._error else: data = QVariant() return data diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 1809d92bf..c97db8464 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -82,10 +82,8 @@ class HintContext: """Get the arguments, with {hint-url} replaced by the given URL.""" args = [] for arg in self.args: - if arg == '{hint-url}': - args.append(urlstr) - else: - args.append(arg) + arg = arg.replace('{hint-url}', urlstr) + args.append(arg) return args @@ -274,9 +272,14 @@ class HintManager(QObject): else: display = 'none' rect = elem.geometry() + left = rect.x() + top = rect.y() + if not config.get('ui', 'zoom-text-only'): + zoom = elem.webFrame().zoomFactor() + left /= zoom + top /= zoom return self.HINT_CSS.format( - left=rect.x(), top=rect.y(), config=objreg.get('config'), - display=display) + left=left, top=top, config=objreg.get('config'), display=display) def _draw_label(self, elem, string): """Draw a hint label over an element. diff --git a/qutebrowser/browser/quickmarks.py b/qutebrowser/browser/quickmarks.py index 4e7886aba..589bc51a2 100644 --- a/qutebrowser/browser/quickmarks.py +++ b/qutebrowser/browser/quickmarks.py @@ -27,91 +27,99 @@ to a file on shutdown, so it makes sense to keep them as strings here. import functools import collections -from PyQt5.QtCore import QStandardPaths, QUrl +from PyQt5.QtCore import pyqtSignal, QStandardPaths, QUrl, QObject from qutebrowser.utils import message, usertypes, urlutils, standarddir from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.config import lineparser -marks = collections.OrderedDict() -linecp = None +class QuickmarkManager(QObject): + """Manager for quickmarks. -def init(): - """Read quickmarks from the config file.""" - global linecp - confdir = standarddir.get(QStandardPaths.ConfigLocation) - linecp = lineparser.LineConfigParser(confdir, 'quickmarks') - for line in linecp: - try: - key, url = line.rsplit(maxsplit=1) - except ValueError: - message.error(0, "Invalid quickmark '{}'".format(line)) + Attributes: + marks: An OrderedDict of all quickmarks. + _linecp: The LineConfigParser used for the quickmarks. + """ + + changed = pyqtSignal() + + def __init__(self, parent=None): + """Initialize and read quickmarks.""" + super().__init__(parent) + + self.marks = collections.OrderedDict() + + confdir = standarddir.get(QStandardPaths.ConfigLocation) + self._linecp = lineparser.LineConfigParser(confdir, 'quickmarks') + for line in self._linecp: + try: + key, url = line.rsplit(maxsplit=1) + except ValueError: + message.error(0, "Invalid quickmark '{}'".format(line)) + else: + self.marks[key] = url + + def save(self): + """Save the quickmarks to disk.""" + self._linecp.data = [' '.join(tpl) for tpl in self.marks.items()] + self._linecp.save() + + def prompt_save(self, win_id, url): + """Prompt for a new quickmark name to be added and add it. + + Args: + win_id: The current window ID. + url: The quickmark url as a QUrl. + """ + if not url.isValid(): + urlutils.invalid_url_error(win_id, url, "save quickmark") + return + urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded) + message.ask_async( + win_id, "Add quickmark:", usertypes.PromptMode.text, + functools.partial(self.quickmark_add, win_id, urlstr)) + + @cmdutils.register(instance='quickmark-manager') + def quickmark_add(self, win_id: {'special': 'win_id'}, url, name): + """Add a new quickmark. + + Args: + win_id: The window ID to display the errors in. + url: The url to add as quickmark. + name: The name for the new quickmark. + """ + # 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!") + return + if not url: + message.error(win_id, "Can't set mark with empty URL!") + return + + def set_mark(): + """Really set the quickmark.""" + self.marks[name] = url + self.changed.emit() + + if name in self.marks: + message.confirm_async( + win_id, "Override existing quickmark?", set_mark, default=True) else: - marks[key] = url + set_mark() - -def save(): - """Save the quickmarks to disk.""" - linecp.data = [' '.join(tpl) for tpl in marks.items()] - linecp.save() - - -def prompt_save(win_id, url): - """Prompt for a new quickmark name to be added and add it. - - Args: - win_id: The current window ID. - url: The quickmark url as a QUrl. - """ - if not url.isValid(): - urlutils.invalid_url_error(win_id, url, "save quickmark") - return - urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded) - message.ask_async(win_id, "Add quickmark:", usertypes.PromptMode.text, - functools.partial(quickmark_add, win_id, urlstr)) - - -@cmdutils.register() -def quickmark_add(win_id: {'special': 'win_id'}, url, name): - """Add a new quickmark. - - Args: - win_id: The window ID to display the errors in. - url: The url to add as quickmark. - name: The name for the new quickmark. - """ - # 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!") - return - if not url: - message.error(win_id, "Can't set mark with empty URL!") - return - - def set_mark(): - """Really set the quickmark.""" - marks[name] = url - - if name in marks: - message.confirm_async(win_id, "Override existing quickmark?", set_mark, - default=True) - else: - set_mark() - - -def get(name): - """Get the URL of the quickmark named name as a QUrl.""" - if name not in marks: - raise cmdexc.CommandError( - "Quickmark '{}' does not exist!".format(name)) - urlstr = marks[name] - try: - url = urlutils.fuzzy_url(urlstr) - except urlutils.FuzzyUrlError: - raise cmdexc.CommandError( - "Invalid URL for quickmark {}: {} ({})".format(name, urlstr, - url.errorString())) - return url + def get(self, name): + """Get the URL of the quickmark named name as a QUrl.""" + if name not in self.marks: + raise cmdexc.CommandError( + "Quickmark '{}' does not exist!".format(name)) + urlstr = self.marks[name] + try: + url = urlutils.fuzzy_url(urlstr) + except urlutils.FuzzyUrlError: + raise cmdexc.CommandError( + "Invalid URL for quickmark {}: {} ({})".format( + name, urlstr, url.errorString())) + return url diff --git a/qutebrowser/commands/argparser.py b/qutebrowser/commands/argparser.py index 684361698..1d21c2ffa 100644 --- a/qutebrowser/commands/argparser.py +++ b/qutebrowser/commands/argparser.py @@ -59,7 +59,7 @@ class HelpAction(argparse.Action): def __call__(self, parser, _namespace, _values, _option_string=None): tabbed_browser = objreg.get('tabbed-browser', scope='window', - window='current') + window='last-focused') tabbed_browser.tabopen( QUrl('qute://help/commands.html#{}'.format(parser.name))) parser.exit() diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 11f51d691..257d4aaef 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -714,6 +714,10 @@ DATA = collections.OrderedDict([ ('downloads.bg.system', SettingValue(typ.ColorSystem(), 'rgb'), "Color gradient interpolation system for downloads."), + + ('downloads.bg.error', + SettingValue(typ.QtColor(), 'red'), + "Background color for downloads with errors."), )), ('fonts', sect.KeyValue( diff --git a/qutebrowser/models/completionfilter.py b/qutebrowser/models/completionfilter.py index 44ab2098f..1a5bbd36a 100644 --- a/qutebrowser/models/completionfilter.py +++ b/qutebrowser/models/completionfilter.py @@ -35,7 +35,7 @@ class CompletionFilterModel(QSortFilterProxyModel): Attributes: _pattern: The pattern to filter with. - _srcmodel: The current source model. + srcmodel: The current source model. Kept as attribute because calling `sourceModel` takes quite a long time for some reason. """ @@ -43,7 +43,7 @@ class CompletionFilterModel(QSortFilterProxyModel): def __init__(self, source, parent=None): super().__init__(parent) super().setSourceModel(source) - self._srcmodel = source + self.srcmodel = source self._pattern = '' def set_pattern(self, val): @@ -61,7 +61,7 @@ class CompletionFilterModel(QSortFilterProxyModel): self.invalidateFilter() sortcol = 0 try: - self._srcmodel.sort(sortcol) + self.srcmodel.sort(sortcol) except NotImplementedError: self.sort(sortcol) self.invalidate() @@ -111,14 +111,14 @@ class CompletionFilterModel(QSortFilterProxyModel): qtutils.ensure_valid(index) index = self.mapToSource(index) qtutils.ensure_valid(index) - self._srcmodel.mark_item(index, text) + self.srcmodel.mark_item(index, text) def setSourceModel(self, model): """Override QSortFilterProxyModel's setSourceModel to clear pattern.""" log.completion.debug("Setting source model: {}".format(model)) self.set_pattern('') super().setSourceModel(model) - self._srcmodel = model + self.srcmodel = model def filterAcceptsRow(self, row, parent): """Custom filter implementation. @@ -135,9 +135,9 @@ class CompletionFilterModel(QSortFilterProxyModel): """ if parent == QModelIndex(): return True - idx = self._srcmodel.index(row, 0, parent) + idx = self.srcmodel.index(row, 0, parent) qtutils.ensure_valid(idx) - data = self._srcmodel.data(idx) + data = self.srcmodel.data(idx) # TODO more sophisticated filtering if not self._pattern: return True @@ -159,14 +159,14 @@ class CompletionFilterModel(QSortFilterProxyModel): qtutils.ensure_valid(lindex) qtutils.ensure_valid(rindex) - left_sort = self._srcmodel.data(lindex, role=completion.Role.sort) - right_sort = self._srcmodel.data(rindex, role=completion.Role.sort) + left_sort = self.srcmodel.data(lindex, role=completion.Role.sort) + right_sort = self.srcmodel.data(rindex, role=completion.Role.sort) if left_sort is not None and right_sort is not None: return left_sort < right_sort - left = self._srcmodel.data(lindex) - right = self._srcmodel.data(rindex) + left = self.srcmodel.data(lindex) + right = self.srcmodel.data(rindex) leftstart = left.startswith(self._pattern) rightstart = right.startswith(self._pattern) diff --git a/qutebrowser/utils/completer.py b/qutebrowser/utils/completer.py index daa069286..e2935f269 100644 --- a/qutebrowser/utils/completer.py +++ b/qutebrowser/utils/completer.py @@ -181,7 +181,6 @@ class Completer(QObject): else: self._ignore_change = True self.change_completed_part.emit(data, False) - self._ignore_change = False @pyqtSlot(str, list, int) def on_update_completion(self, prefix, parts, cursor_part): @@ -204,6 +203,7 @@ class Completer(QObject): cursor_part: The part the cursor is currently over. """ if self._ignore_change: + self._ignore_change = False log.completion.debug("Ignoring completion update") return @@ -235,7 +235,7 @@ class Completer(QObject): log.completion.debug( "New completion for {}: {}, with pattern '{}'".format( - parts, model._srcmodel.__class__.__name__, pattern)) + parts, model.srcmodel.__class__.__name__, pattern)) if self._model().count() == 0: completion.hide() diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index 7b9fd5332..8018a1867 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -136,6 +136,15 @@ def _get_window_registry(window): win = app.activeWindow() if win is None or not hasattr(win, 'win_id'): raise RegistryUnavailableError('window') + elif window == 'last-focused': + try: + win = get('last-focused-main-window') + except KeyError: + try: + win = get('last-main-window') + except KeyError: + raise RegistryUnavailableError('window') + assert hasattr(win, 'registry') else: try: win = window_registry[window] diff --git a/qutebrowser/widgets/downloads.py b/qutebrowser/widgets/downloads.py index ad1c6215f..d0e1541f8 100644 --- a/qutebrowser/widgets/downloads.py +++ b/qutebrowser/widgets/downloads.py @@ -22,7 +22,7 @@ import functools import sip -from PyQt5.QtCore import pyqtSlot, QSize, Qt +from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu from qutebrowser.browser import downloads @@ -44,9 +44,16 @@ def update_geometry(obj): Original bug: https://github.com/The-Compiler/qutebrowser/issues/167 Workaround bug: https://github.com/The-Compiler/qutebrowser/issues/171 """ - if sip.isdeleted(obj): - return - obj.updateGeometry() + + def _update_geometry(): + """Actually update the geometry if the object still exists.""" + if sip.isdeleted(obj): + return + obj.updateGeometry() + + # If we don't use a singleShot QTimer, the geometry isn't updated correctly + # and won't include the new item. + QTimer.singleShot(0, _update_geometry) class DownloadView(QListView): diff --git a/qutebrowser/widgets/mainwindow.py b/qutebrowser/widgets/mainwindow.py index 1ed38ef18..c565cbec9 100644 --- a/qutebrowser/widgets/mainwindow.py +++ b/qutebrowser/widgets/mainwindow.py @@ -87,7 +87,6 @@ class MainWindow(QWidget): self._downloadview.show() self._tabbed_browser = tabbedbrowser.TabbedBrowser(win_id) - self._tabbed_browser.title_changed.connect(self.setWindowTitle) objreg.register('tabbed-browser', self._tabbed_browser, scope='window', window=win_id) self._vbox.addWidget(self._tabbed_browser) @@ -333,3 +332,4 @@ class MainWindow(QWidget): e.accept() objreg.get('app').geometry = bytes(self.saveGeometry()) log.destroy.debug("Closing window {}".format(self.win_id)) + self._tabbed_browser.shutdown() diff --git a/qutebrowser/widgets/misc.py b/qutebrowser/widgets/misc.py index 6232848b0..b212ffa78 100644 --- a/qutebrowser/widgets/misc.py +++ b/qutebrowser/widgets/misc.py @@ -20,8 +20,8 @@ """Misc. widgets used at different places.""" from PyQt5.QtCore import pyqtSlot, Qt -from PyQt5.QtWidgets import QLineEdit -from PyQt5.QtGui import QValidator +from PyQt5.QtWidgets import QLineEdit, QApplication +from PyQt5.QtGui import QValidator, QClipboard from qutebrowser.models import cmdhistory from qutebrowser.utils import utils @@ -64,6 +64,9 @@ class CommandLineEdit(QLineEdit): self.cursorPositionChanged.connect(self.__on_cursor_position_changed) self._promptlen = 0 + def __repr__(self): + return utils.get_repr(self, text=self.text()) + @pyqtSlot(str) def on_text_edited(self, _text): """Slot for textEdited. Stop history browsing.""" @@ -94,8 +97,16 @@ class CommandLineEdit(QLineEdit): if mark: self.setSelection(self._promptlen, oldpos - self._promptlen) - def __repr__(self): - return utils.get_repr(self, text=self.text()) + def keyPressEvent(self, e): + """Override keyPressEvent to paste primary selection on Shift + Ins.""" + if e.key() == Qt.Key_Insert and e.modifiers() == Qt.ShiftModifier: + clipboard = QApplication.clipboard() + if clipboard.supportsSelection(): + e.accept() + text = clipboard.text(QClipboard.Selection) + self.insert(text) + return + super().keyPressEvent(e) class _CommandValidator(QValidator): diff --git a/qutebrowser/widgets/tabbedbrowser.py b/qutebrowser/widgets/tabbedbrowser.py index e5d5b1708..c53090c33 100644 --- a/qutebrowser/widgets/tabbedbrowser.py +++ b/qutebrowser/widgets/tabbedbrowser.py @@ -79,8 +79,6 @@ class TabbedBrowser(tabwidget.TabWidget): start_download: Emitted when any tab wants to start downloading something. current_tab_changed: The current tab changed to the emitted WebView. - title_changed: Emitted when the application title should be changed. - arg: The new title as string. """ cur_progress = pyqtSignal(int) @@ -96,7 +94,6 @@ class TabbedBrowser(tabwidget.TabWidget): resized = pyqtSignal('QRect') got_cmd = pyqtSignal(str) current_tab_changed = pyqtSignal(webview.WebView) - title_changed = pyqtSignal(str) def __init__(self, win_id, parent=None): super().__init__(win_id, parent) @@ -138,9 +135,10 @@ class TabbedBrowser(tabwidget.TabWidget): def _change_app_title(self, text): """Change the window title based on the tab text.""" if not text: - self.title_changed.emit('qutebrowser') + title = 'qutebrowser' else: - self.title_changed.emit('{} - qutebrowser'.format(text)) + title = '{} - qutebrowser'.format(text) + self.window().setWindowTitle(title) def _connect_tab_signals(self, tab): """Set up the needed signals for tab.""" diff --git a/qutebrowser/widgets/webview.py b/qutebrowser/widgets/webview.py index 09faf7e38..87b504cc4 100644 --- a/qutebrowser/widgets/webview.py +++ b/qutebrowser/widgets/webview.py @@ -127,6 +127,13 @@ class WebView(QWebView): url = utils.elide(self.url().toDisplayString(), 50) return utils.get_repr(self, tab_id=self.tab_id, url=url) + def __del__(self): + # Explicitely releasing the page here seems to prevent some segfaults + # when quitting. + # Copied from: + # https://code.google.com/p/webscraping/source/browse/webkit.py#325 + self.setPage(None) + def _set_load_status(self, val): """Setter for load_status.""" if not isinstance(val, LoadStatus): @@ -257,15 +264,11 @@ class WebView(QWebView): """Shut down the webview.""" # We disable javascript because that prevents some segfaults when # quitting it seems. + log.destroy.debug("Shutting down {!r}.".format(self)) settings = self.settings() settings.setAttribute(QWebSettings.JavascriptEnabled, False) self.stop() self.page().networkAccessManager().shutdown() - # Explicitely releasing the page here seems to prevent some segfaults - # when quitting. - # Copied from: - # https://code.google.com/p/webscraping/source/browse/webkit.py#325 - self.setPage(None) def openurl(self, url): """Open a URL in the browser. diff --git a/scripts/importer.py b/scripts/importer.py index 80133411f..67ae83885 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014 Claude (longneck) @@ -49,8 +50,8 @@ def get_args(): def import_chromium(bookmarks_file): """Import bookmarks from a HTML file generated by Chromium.""" import bs4 - - soup = bs4.BeautifulSoup(open(bookmarks_file, encoding='utf-8')) + with open(bookmarks_file, encoding='utf-8') as f: + soup = bs4.BeautifulSoup(f) html_tags = soup.findAll('a') diff --git a/scripts/pylint_checkers/openencoding.py b/scripts/pylint_checkers/openencoding.py index caee996c8..07e97d70a 100644 --- a/scripts/pylint_checkers/openencoding.py +++ b/scripts/pylint_checkers/openencoding.py @@ -52,10 +52,16 @@ class OpenEncodingChecker(checkers.BaseChecker): keyword='mode') except utils.NoSuchArgumentError: mode_arg = None + _encoding = None try: - _encoding = utils.get_argument_from_call(node, position=2, - keyword='encoding') + _encoding = utils.get_argument_from_call(node, position=2) except utils.NoSuchArgumentError: + try: + _encoding = utils.get_argument_from_call(node, + keyword='encoding') + except utils.NoSuchArgumentError: + pass + if _encoding is None: if mode_arg is not None: mode = utils.safe_infer(mode_arg) if (mode_arg is not None and isinstance(mode, astroid.Const) and