From c77589a821ae27095bcbb41e15a3d1a3e56cd794 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 11 Feb 2014 14:03:26 +0100 Subject: [PATCH] Make SignalCache an own class. --- qutebrowser/utils/misc.py | 18 ------- qutebrowser/utils/signals.py | 91 ++++++++++++++++++++++++++++++++++ qutebrowser/widgets/browser.py | 50 +++---------------- 3 files changed, 98 insertions(+), 61 deletions(-) create mode 100644 qutebrowser/utils/signals.py diff --git a/qutebrowser/utils/misc.py b/qutebrowser/utils/misc.py index 0b3ef7a1e..ef2e4d817 100644 --- a/qutebrowser/utils/misc.py +++ b/qutebrowser/utils/misc.py @@ -17,8 +17,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -import re - from PyQt5.QtCore import pyqtRemoveInputHook try: @@ -27,22 +25,6 @@ except ImportError: from pdb import set_trace as pdb_set_trace -def signal_name(sig): - """Return a cleaned up name of a signal.""" - m = re.match(r'[0-9]+(.*)\(.*\)', sig.signal) - return m.group(1) - - -def dbg_signal(sig, args): - """Return a string representation of a signal for debugging. - - sig -- A pyqtSignal. - args -- The arguments as list of strings. - - """ - return '{}({})'.format(signal_name(sig), ', '.join(map(str, args))) - - def set_trace(): """ Set a tracepoint in the Python debugger that works with Qt. diff --git a/qutebrowser/utils/signals.py b/qutebrowser/utils/signals.py new file mode 100644 index 000000000..36257ef0c --- /dev/null +++ b/qutebrowser/utils/signals.py @@ -0,0 +1,91 @@ +"""Utilities regarding signals.""" + +# Copyright 2014 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 . + +import re +import logging +from collections import OrderedDict + +from PyQt5.QtCore import QObject + + +def signal_name(sig): + """Return a cleaned up name of a signal.""" + m = re.match(r'[0-9]+(.*)\(.*\)', sig.signal) + return m.group(1) + + +def dbg_signal(sig, args): + """Return a string representation of a signal for debugging. + + sig -- A pyqtSignal. + args -- The arguments as list of strings. + + """ + return '{}({})'.format(signal_name(sig), ', '.join(map(str, args))) + + +class SignalCache(QObject): + + """Cache signals emitted by an object, and re-emit them later.""" + + uncached = None + signal_dict = None + + def __init__(self, uncached=None): + """Create a new SignalCache. + + uncached -- A list of signal names (as string) which should never be + cached. + + """ + super().__init__() + if uncached is None: + self.uncached = [] + else: + self.uncached = uncached + self.signal_dict = OrderedDict() + + def add(self, sig, args): + """Add a new signal to the signal cache. + + If the signal doesn't need caching it will be ignored. + If it's already in the cache, it'll be updated and moved to the front. + If not, it will be added. + + """ + if not self._signal_needs_caching(sig): + return + had_signal = sig.signal in self.signal_dict + self.signal_dict[sig.signal] = (sig, args) + if had_signal: + self.signal_dict.move_to_end(sig.signal) + + def clear(self): + """Clear/purge the signal cache.""" + self.signal_dict.clear() + + def replay(self): + """Replay all cached signals.""" + for (signal, args) in self.signal_dict.values(): + logging.debug('emitting {}'.format(dbg_signal(signal, args))) + signal.emit(*args) + + def _signal_needs_caching(self, signal): + """Return True if a signal should be cached, false otherwise.""" + return not signal_name(signal) in self.uncached diff --git a/qutebrowser/widgets/browser.py b/qutebrowser/widgets/browser.py index a52ba72a7..c53ec9651 100644 --- a/qutebrowser/widgets/browser.py +++ b/qutebrowser/widgets/browser.py @@ -24,7 +24,6 @@ containing BrowserTabs). import logging import functools -from collections import OrderedDict from PyQt5.QtWidgets import QShortcut, QApplication, QSizePolicy from PyQt5.QtCore import pyqtSignal, Qt, QEvent @@ -36,7 +35,7 @@ import qutebrowser.utils.about as about import qutebrowser.utils.config as config import qutebrowser.utils.url as urlutils from qutebrowser.widgets.tabbar import TabWidget -from qutebrowser.utils.misc import dbg_signal, signal_name +from qutebrowser.utils.signals import dbg_signal, SignalCache class TabbedBrowser(TabWidget): @@ -69,7 +68,8 @@ class TabbedBrowser(TabWidget): def __init__(self, parent): super().__init__(parent) - self.currentChanged.connect(self._currentChanged_handler) + self.currentChanged.connect(lambda idx: + self.widget(idx).signal_cache.replay()) space = QShortcut(self) space.setKey(Qt.Key_Space) space.setContext(Qt.WidgetWithChildrenShortcut) @@ -92,7 +92,8 @@ class TabbedBrowser(TabWidget): tab.linkHovered.connect(self._filter_factory(self.cur_link_hovered)) tab.loadProgress.connect(self._filter_factory(self.cur_progress)) tab.loadFinished.connect(self._filter_factory(self.cur_load_finished)) - tab.loadStarted.connect(self._clear_signal_cache) + tab.loadStarted.connect(lambda: # pylint: disable=unnecessary-lambda + self.sender().signal_cache.clear()) tab.loadStarted.connect(self._filter_factory(self.cur_load_started)) tab.statusBarMessage.connect( self._filter_factory(self.cur_statusbar_message)) @@ -412,19 +413,7 @@ class TabbedBrowser(TabWidget): logging.warn('Got signal {} by {} which is no tab!'.format( dbg_signal(signal, args), sender)) return - if (signal.signal in sender.signal_cache and - self._signal_needs_caching(signal)): - if log_signal: - logging.debug(" Moving to the end of signal cache") - sender.signal_cache[signal.signal] = (signal, args) - sender.signal_cache.move_to_end(signal.signal) - elif self._signal_needs_caching(signal): - if log_signal: - logging.debug(" Adding to signal cache") - sender.signal_cache[signal.signal] = (signal, args) - else: - if log_signal: - logging.debug(" Ignoring for signal cache") + sender.signal_cache.add(signal, args) if self.currentWidget() == sender: if log_signal: logging.debug(' emitting') @@ -433,31 +422,6 @@ class TabbedBrowser(TabWidget): if log_signal: logging.debug(' ignoring') - def _currentChanged_handler(self, idx): - """Update status bar values when a tab was changed. - - Populates all signals from the signal cache. - - Slot for the currentChanged signal. - - """ - for (signal, args) in self.widget(idx).signal_cache.values(): - logging.debug('signal cache: emitting {} for tab {}'.format( - dbg_signal(signal, args), idx)) - signal.emit(*args) - - def _clear_signal_cache(self): - """Clear the signal cache of the sender of the signal.""" - sender = self.sender() - logging.debug("Clearing signal cache of tab {}".format(self.indexOf( - sender))) - sender.signal_cache.clear() - - def _signal_needs_caching(self, signal): - """Return True if a signal should be cached, false otherwise.""" - ignore_signals = ['linkHovered'] - return not signal_name(signal) in ignore_signals - class BrowserTab(QWebView): @@ -478,7 +442,7 @@ class BrowserTab(QWebView): def __init__(self, parent): super().__init__(parent) - self.signal_cache = OrderedDict() + self.signal_cache = SignalCache(uncached=['linkHovered']) self.loadProgress.connect(self.set_progress) self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.page().linkHovered.connect(self.linkHovered)