From 16c397a9d2e2862ada436af7c340a333c017b1e9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 15 Jun 2016 13:02:24 +0200 Subject: [PATCH] Fix various zooming issues --- qutebrowser/browser/commands.py | 12 +-- qutebrowser/browser/tab.py | 102 ++++++++++++++++-- qutebrowser/browser/webengine/webenginetab.py | 16 +-- qutebrowser/browser/webkit/webkittab.py | 21 ++-- qutebrowser/browser/webkit/webpage.py | 2 +- qutebrowser/browser/webkit/webview.py | 77 ++----------- qutebrowser/misc/sessions.py | 2 +- 7 files changed, 136 insertions(+), 96 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 6618aa731..99f0ddc28 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -354,7 +354,7 @@ class CommandDispatcher: if config.get('tabs', 'tabs-are-windows'): new_tabbed_browser.window().setWindowIcon(curtab.icon()) newtab.keep_icon = True - newtab.set_zoom_factor(curtab.zoom_factor()) + newtab.zoom.set_factor(curtab.zoom.factor()) newtab.history.deserialize(curtab.history.serialize()) return newtab @@ -663,7 +663,7 @@ class CommandDispatcher: """ tab = self._current_widget() try: - perc = tab.zoom(count) + perc = tab.zoom.offset(count) except ValueError as e: raise cmdexc.CommandError(e) message.info(self._win_id, "Zoom level: {}%".format(perc)) @@ -678,7 +678,7 @@ class CommandDispatcher: """ tab = self._current_widget() try: - perc = tab.zoom(-count) + perc = tab.zoom.offset(-count) except ValueError as e: raise cmdexc.CommandError(e) message.info(self._win_id, "Zoom level: {}%".format(perc)) @@ -703,9 +703,9 @@ class CommandDispatcher: tab = self._current_widget() try: - tab.zoom_perc(level) - except ValueError as e: - raise cmdexc.CommandError(e) + 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)) @cmdutils.register(instance='command-dispatcher', scope='window') diff --git a/qutebrowser/browser/tab.py b/qutebrowser/browser/tab.py index ad18de580..d09eb126b 100644 --- a/qutebrowser/browser/tab.py +++ b/qutebrowser/browser/tab.py @@ -21,11 +21,12 @@ import itertools -from PyQt5.QtCore import pyqtSignal, QUrl, QObject +from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QPoint from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QWidget, QLayout -from qutebrowser.utils import utils, objreg +from qutebrowser.config import config +from qutebrowser.utils import utils, objreg, usertypes tab_id_gen = itertools.count(0) @@ -55,6 +56,94 @@ class WrapperLayout(QLayout): self._widget.setGeometry(r) +class AbstractZoom(QObject): + + """Attribute of AbstractTab for controlling zoom. + + Attributes: + _neighborlist: A NeighborList with the zoom levels. + _default_zoom_changed: Whether the zoom was changed from the default. + """ + + def __init__(self, win_id, parent=None): + super().__init__(parent) + self.widget = None + self._win_id = win_id + self._default_zoom_changed = False + self._init_neighborlist() + objreg.get('config').changed.connect(self.on_config_changed) + + # # FIXME is this needed? + # # For some reason, this signal doesn't get disconnected automatically + # # when the WebView is destroyed on older PyQt versions. + # # See https://github.com/The-Compiler/qutebrowser/issues/390 + # self.destroyed.connect(functools.partial( + # cfg.changed.disconnect, self.init_neighborlist)) + + def _set_default_zoom(self): + default_zoom = config.get('ui', 'default-zoom') + self._set_factor_internal(float(default_zoom) / 100) + + @pyqtSlot(str, str) + def on_config_changed(self, section, option): + if section == 'ui' and option in ('zoom-levels', 'default-zoom'): + if not self._default_zoom_changed: + factor = float(config.get('ui', 'default-zoom')) / 100 + self._set_factor_internal(factor) + self._default_zoom_changed = False + self._init_neighborlist() + + def _init_neighborlist(self): + """Initialize self._neighborlist.""" + levels = config.get('ui', 'zoom-levels') + self._neighborlist = usertypes.NeighborList( + levels, mode=usertypes.NeighborList.Modes.edge) + self._neighborlist.fuzzyval = config.get('ui', 'default-zoom') + + def offset(self, offset): + """Increase/Decrease the zoom level by the given offset. + + Args: + offset: The offset in the zoom level list. + + Return: + The new zoom percentage. + """ + level = self._neighborlist.getitem(offset) + self.set_factor(float(level) / 100, fuzzyval=False) + return level + + def set_factor(self, factor, *, fuzzyval=True): + """Zoom to a given zoom factor. + + Args: + factor: The zoom factor as float. + fuzzyval: Whether to set the NeighborLists fuzzyval. + """ + if fuzzyval: + self._neighborlist.fuzzyval = int(factor * 100) + if factor < 0: + raise ValueError("Can't zoom to factor {}!".format(factor)) + self._default_zoom_changed = True + self._set_factor_internal(factor) + + def factor(self): + raise NotImplementedError + + @pyqtSlot(QPoint) + def on_mouse_wheel_zoom(self, delta): + """Handle zooming via mousewheel requested by the web view.""" + divider = config.get('input', 'mouse-zoom-divider') + factor = self.zoomFactor() + delta.y() / divider + if factor < 0: + return + perc = int(100 * factor) + message.info(self.win_id, "Zoom level: {}%".format(perc)) + self._neighborlist.fuzzyval = perc + self._set_factor_internal(factor) + self._default_zoom_changed = True + + class AbstractCaret(QObject): """Attribute of AbstractTab for caret browsing.""" @@ -262,6 +351,7 @@ class AbstractTab(QWidget): # self.history = AbstractHistory(self) # self.scroll = AbstractScroller(parent=self) # self.caret = AbstractCaret(win_id=win_id, tab=self, parent=self) + # self.zoom = AbstractZoom(win_id=win_id) self._layout = None self._widget = None self.keep_icon = False # FIXME:refactor get rid of this? @@ -272,6 +362,8 @@ class AbstractTab(QWidget): self.history.history = widget.history() self.scroll.widget = widget self.caret.widget = widget + self.zoom.widget = widget + widget.mouse_wheel_zoom.connect(self.zoom.on_mouse_wheel_zoom) widget.setParent(self) @property @@ -317,12 +409,6 @@ class AbstractTab(QWidget): def title(self): raise NotImplementedError - def set_zoom_factor(self, factor): - raise NotImplementedError - - def zoom_factor(self): - raise NotImplementedError - def icon(self): raise NotImplementedError diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 7631e95eb..25e59849a 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -76,6 +76,15 @@ class WebEngineHistory(tab.AbstractHistory): raise NotImplementedError +class WebEngineZoom(tab.AbstractZoom): + + def _set_factor_internal(self, factor): + self.widget.setZoomFactor(factor) + + def factor(self): + return self.widget.zoomFactor() + + class WebEngineViewTab(tab.AbstractTab): def __init__(self, win_id, parent=None): @@ -84,6 +93,7 @@ class WebEngineViewTab(tab.AbstractTab): self.history = WebEngineHistory(self) self.scroll = WebEngineScroller() self.caret = WebEngineCaret(win_id=win_id, tab=self, parent=self) + self.zoom = WebEngineZoom(win_id=win_id, parent=self) self._set_widget(widget) self._connect_signals() @@ -102,12 +112,6 @@ class WebEngineViewTab(tab.AbstractTab): def load_status(self): return usertypes.LoadStatus.success - def set_zoom_factor(self, factor): - self._widget.setZoomFactor(factor) - - def zoom_factor(self): - return self._widget.zoomFactor() - def dump_async(self, callback, *, plain=False): if plain: self._widget.page().toPlainText(callback) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 6960302c4..16e3187ce 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -213,6 +213,15 @@ class WebViewCaret(tab.AbstractCaret): self.widget.triggerPageAction(QWebPage.MoveToNextChar) +class WebViewZoom(tab.AbstractZoom): + + def _set_factor_internal(self, factor): + self.widget.setZoomFactor(factor) + + def factor(self): + return self.widget.zoomFactor() + + class WebViewScroller(tab.AbstractScroller): def pos_px(self): @@ -338,7 +347,7 @@ class WebViewHistory(tab.AbstractHistory): cur_data = self.history.currentItem().userData() if cur_data is not None: if 'zoom' in cur_data: - self.tab.zoom_perc(cur_data['zoom'] * 100) + self.tab.zoom.set_factor(cur_data['zoom']) if ('scroll-pos' in cur_data and self.tab.scroll.pos_px() == QPoint(0, 0)): QTimer.singleShot(0, functools.partial( @@ -349,12 +358,14 @@ class WebViewTab(tab.AbstractTab): def __init__(self, win_id, parent=None): super().__init__(win_id) - widget = webview.WebView(win_id, self.tab_id) + widget = webview.WebView(win_id, self.tab_id, tab=self) self.history = WebViewHistory(self) self.scroll = WebViewScroller(parent=self) self.caret = WebViewCaret(win_id=win_id, tab=self, parent=self) + self.zoom = WebViewZoom(win_id=win_id, parent=self) self._set_widget(widget) self._connect_signals() + self.zoom._set_default_zoom() def openurl(self, url): self._widget.openurl(url) @@ -402,12 +413,6 @@ class WebViewTab(tab.AbstractTab): def title(self): return self._widget.title() - def set_zoom_factor(self, factor): - self._widget.setZoomFactor(factor) - - def zoom_factor(self): - return self._widget.zoomFactor() - def has_selection(self): return self._widget.hasSelection() diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index 4642a9d81..39cbba46e 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -419,7 +419,7 @@ class BrowserPage(QWebPage): if data is None: return if 'zoom' in data: - frame.page().view().zoom_perc(data['zoom'] * 100) + frame.page().view().tab.zoom.set_factor(data['zoom']) if 'scroll-pos' in data and frame.scrollPosition() == QPoint(0, 0): frame.setScrollPosition(data['scroll-pos']) diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index efde518a8..bd6fffcd3 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -23,7 +23,7 @@ import sys import itertools import functools -from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl +from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl, QPoint from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import QApplication, QStyleFactory from PyQt5.QtWebKit import QWebSettings @@ -43,6 +43,7 @@ class WebView(QWebView): Our own subclass of a QWebView with some added bells and whistles. Attributes: + tab: The WebKitTab object for this WebView hintmanager: The HintManager instance for this view. progress: loading progress of this page. scroll_pos: The current scroll position as (x%, y%) tuple. @@ -57,11 +58,9 @@ class WebView(QWebView): search_flags: The search flags of the last search. _tab_id: The tab ID of the view. _has_ssl_errors: Whether SSL errors occurred during loading. - _zoom: A NeighborList with the zoom levels. _old_scroll_pos: The old scroll position. _check_insertmode: If True, in mouseReleaseEvent we should check if we need to enter/leave insert mode. - _default_zoom_changed: Whether the zoom was changed from the default. _ignore_wheel_event: Ignore the next wheel event. See https://github.com/The-Compiler/qutebrowser/issues/395 @@ -72,6 +71,9 @@ class WebView(QWebView): linkHovered: QWebPages linkHovered signal exposed. load_status_changed: The loading status changed url_text_changed: Current URL string changed. + mouse_wheel_zoom: Emitted when the page should be zoomed because the + mousewheel was used with ctrl. + arg 1: The angle delta of the wheel event (QPoint) shutting_down: Emitted when the view is shutting down. """ @@ -80,13 +82,15 @@ class WebView(QWebView): load_status_changed = pyqtSignal(str) url_text_changed = pyqtSignal(str) shutting_down = pyqtSignal() + mouse_wheel_zoom = pyqtSignal(QPoint) - def __init__(self, win_id, tab_id, parent=None): + def __init__(self, win_id, tab_id, tab, parent=None): super().__init__(parent) if sys.platform == 'darwin' and qtutils.version_check('5.4'): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948 # See https://github.com/The-Compiler/qutebrowser/issues/462 self.setStyle(QStyleFactory.create('Fusion')) + self.tab = tab self.win_id = win_id self.load_status = usertypes.LoadStatus.none self._check_insertmode = False @@ -94,21 +98,12 @@ class WebView(QWebView): self.scroll_pos = (-1, -1) self.statusbar_message = '' self._old_scroll_pos = (-1, -1) - self._zoom = None self._has_ssl_errors = False self._ignore_wheel_event = False self.keep_icon = False self.search_text = None self.search_flags = 0 - self.init_neighborlist() self._set_bg_color() - cfg = objreg.get('config') - cfg.changed.connect(self.init_neighborlist) - # For some reason, this signal doesn't get disconnected automatically - # when the WebView is destroyed on older PyQt versions. - # See https://github.com/The-Compiler/qutebrowser/issues/390 - self.destroyed.connect(functools.partial( - cfg.changed.disconnect, self.init_neighborlist)) self.cur_url = QUrl() self.progress = 0 self.registry = objreg.ObjectRegistry() @@ -129,8 +124,6 @@ class WebView(QWebView): mode_manager.entered.connect(self.on_mode_entered) mode_manager.left.connect(self.on_mode_left) self.viewing_source = False - self.setZoomFactor(float(config.get('ui', 'default-zoom')) / 100) - self._default_zoom_changed = False if config.get('input', 'rocker-gestures'): self.setContextMenuPolicy(Qt.PreventContextMenu) self.urlChanged.connect(self.on_url_changed) @@ -201,14 +194,8 @@ class WebView(QWebView): @pyqtSlot(str, str) def on_config_changed(self, section, option): - """Reinitialize the zoom neighborlist if related config changed.""" - if section == 'ui' and option in ('zoom-levels', 'default-zoom'): - if not self._default_zoom_changed: - self.setZoomFactor(float(config.get('ui', 'default-zoom')) / - 100) - self._default_zoom_changed = False - self.init_neighborlist() - elif section == 'input' and option == 'rocker-gestures': + """Update rocker gestures/background color.""" + if section == 'input' and option == 'rocker-gestures': if config.get('input', 'rocker-gestures'): self.setContextMenuPolicy(Qt.PreventContextMenu) else: @@ -216,13 +203,6 @@ class WebView(QWebView): elif section == 'colors' and option == 'webpage.bg': self._set_bg_color() - def init_neighborlist(self): - """Initialize the _zoom neighborlist.""" - levels = config.get('ui', 'zoom-levels') - self._zoom = usertypes.NeighborList( - levels, mode=usertypes.NeighborList.Modes.edge) - self._zoom.fuzzyval = config.get('ui', 'default-zoom') - def _mousepress_backforward(self, e): """Handle back/forward mouse button presses. @@ -372,33 +352,6 @@ class WebView(QWebView): bridge = objreg.get('js-bridge') frame.addToJavaScriptWindowObject('qute', bridge) - def zoom_perc(self, perc, fuzzyval=True): - """Zoom to a given zoom percentage. - - Args: - perc: The zoom percentage as int. - fuzzyval: Whether to set the NeighborLists fuzzyval. - """ - if fuzzyval: - self._zoom.fuzzyval = int(perc) - if perc < 0: - raise ValueError("Can't zoom {}%!".format(perc)) - self.setZoomFactor(float(perc) / 100) - self._default_zoom_changed = True - - def zoom(self, offset): - """Increase/Decrease the zoom level. - - Args: - offset: The offset in the zoom level list. - - Return: - The new zoom percentage. - """ - level = self._zoom.getitem(offset) - self.zoom_perc(level, fuzzyval=False) - return level - @pyqtSlot('QUrl') def on_url_changed(self, url): """Update cur_url when URL has changed. @@ -635,14 +588,6 @@ class WebView(QWebView): return if e.modifiers() & Qt.ControlModifier: e.accept() - divider = config.get('input', 'mouse-zoom-divider') - factor = self.zoomFactor() + e.angleDelta().y() / divider - if factor < 0: - return - perc = int(100 * factor) - message.info(self.win_id, "Zoom level: {}%".format(perc)) - self._zoom.fuzzyval = perc - self.setZoomFactor(factor) - self._default_zoom_changed = True + self.mouse_wheel_zoom.emit(e.angleDelta()) else: super().wheelEvent(e) diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index e61c392a7..29277f30d 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -166,7 +166,7 @@ class SessionManager(QObject): user_data = item.userData() if tab.history.current_idx() == idx: pos = tab.scroll.pos_px() - item_data['zoom'] = tab.zoom_factor() + item_data['zoom'] = tab.zoom.factor() item_data['scroll-pos'] = {'x': pos.x(), 'y': pos.y()} elif user_data is not None: if 'zoom' in user_data: