Fix various zooming issues

This commit is contained in:
Florian Bruhin 2016-06-15 13:02:24 +02:00
parent edb65ecf50
commit 16c397a9d2
7 changed files with 136 additions and 96 deletions

View File

@ -354,7 +354,7 @@ class CommandDispatcher:
if config.get('tabs', 'tabs-are-windows'): if config.get('tabs', 'tabs-are-windows'):
new_tabbed_browser.window().setWindowIcon(curtab.icon()) new_tabbed_browser.window().setWindowIcon(curtab.icon())
newtab.keep_icon = True newtab.keep_icon = True
newtab.set_zoom_factor(curtab.zoom_factor()) newtab.zoom.set_factor(curtab.zoom.factor())
newtab.history.deserialize(curtab.history.serialize()) newtab.history.deserialize(curtab.history.serialize())
return newtab return newtab
@ -663,7 +663,7 @@ class CommandDispatcher:
""" """
tab = self._current_widget() tab = self._current_widget()
try: try:
perc = tab.zoom(count) perc = tab.zoom.offset(count)
except ValueError as e: except ValueError as e:
raise cmdexc.CommandError(e) raise cmdexc.CommandError(e)
message.info(self._win_id, "Zoom level: {}%".format(perc)) message.info(self._win_id, "Zoom level: {}%".format(perc))
@ -678,7 +678,7 @@ class CommandDispatcher:
""" """
tab = self._current_widget() tab = self._current_widget()
try: try:
perc = tab.zoom(-count) perc = tab.zoom.offset(-count)
except ValueError as e: except ValueError as e:
raise cmdexc.CommandError(e) raise cmdexc.CommandError(e)
message.info(self._win_id, "Zoom level: {}%".format(perc)) message.info(self._win_id, "Zoom level: {}%".format(perc))
@ -703,9 +703,9 @@ class CommandDispatcher:
tab = self._current_widget() tab = self._current_widget()
try: try:
tab.zoom_perc(level) tab.zoom.set_factor(float(level) / 100)
except ValueError as e: except ValueError:
raise cmdexc.CommandError(e) raise cmdexc.CommandError("Can't zoom {}%!".format(level))
message.info(self._win_id, "Zoom level: {}%".format(level)) message.info(self._win_id, "Zoom level: {}%".format(level))
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')

View File

@ -21,11 +21,12 @@
import itertools 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.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QLayout 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) tab_id_gen = itertools.count(0)
@ -55,6 +56,94 @@ class WrapperLayout(QLayout):
self._widget.setGeometry(r) 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): class AbstractCaret(QObject):
"""Attribute of AbstractTab for caret browsing.""" """Attribute of AbstractTab for caret browsing."""
@ -262,6 +351,7 @@ class AbstractTab(QWidget):
# self.history = AbstractHistory(self) # self.history = AbstractHistory(self)
# self.scroll = AbstractScroller(parent=self) # self.scroll = AbstractScroller(parent=self)
# self.caret = AbstractCaret(win_id=win_id, tab=self, parent=self) # self.caret = AbstractCaret(win_id=win_id, tab=self, parent=self)
# self.zoom = AbstractZoom(win_id=win_id)
self._layout = None self._layout = None
self._widget = None self._widget = None
self.keep_icon = False # FIXME:refactor get rid of this? self.keep_icon = False # FIXME:refactor get rid of this?
@ -272,6 +362,8 @@ class AbstractTab(QWidget):
self.history.history = widget.history() self.history.history = widget.history()
self.scroll.widget = widget self.scroll.widget = widget
self.caret.widget = widget self.caret.widget = widget
self.zoom.widget = widget
widget.mouse_wheel_zoom.connect(self.zoom.on_mouse_wheel_zoom)
widget.setParent(self) widget.setParent(self)
@property @property
@ -317,12 +409,6 @@ class AbstractTab(QWidget):
def title(self): def title(self):
raise NotImplementedError raise NotImplementedError
def set_zoom_factor(self, factor):
raise NotImplementedError
def zoom_factor(self):
raise NotImplementedError
def icon(self): def icon(self):
raise NotImplementedError raise NotImplementedError

View File

@ -76,6 +76,15 @@ class WebEngineHistory(tab.AbstractHistory):
raise NotImplementedError 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): class WebEngineViewTab(tab.AbstractTab):
def __init__(self, win_id, parent=None): def __init__(self, win_id, parent=None):
@ -84,6 +93,7 @@ class WebEngineViewTab(tab.AbstractTab):
self.history = WebEngineHistory(self) self.history = WebEngineHistory(self)
self.scroll = WebEngineScroller() self.scroll = WebEngineScroller()
self.caret = WebEngineCaret(win_id=win_id, tab=self, parent=self) 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._set_widget(widget)
self._connect_signals() self._connect_signals()
@ -102,12 +112,6 @@ class WebEngineViewTab(tab.AbstractTab):
def load_status(self): def load_status(self):
return usertypes.LoadStatus.success 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): def dump_async(self, callback, *, plain=False):
if plain: if plain:
self._widget.page().toPlainText(callback) self._widget.page().toPlainText(callback)

View File

@ -213,6 +213,15 @@ class WebViewCaret(tab.AbstractCaret):
self.widget.triggerPageAction(QWebPage.MoveToNextChar) 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): class WebViewScroller(tab.AbstractScroller):
def pos_px(self): def pos_px(self):
@ -338,7 +347,7 @@ class WebViewHistory(tab.AbstractHistory):
cur_data = self.history.currentItem().userData() cur_data = self.history.currentItem().userData()
if cur_data is not None: if cur_data is not None:
if 'zoom' in cur_data: 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 if ('scroll-pos' in cur_data and
self.tab.scroll.pos_px() == QPoint(0, 0)): self.tab.scroll.pos_px() == QPoint(0, 0)):
QTimer.singleShot(0, functools.partial( QTimer.singleShot(0, functools.partial(
@ -349,12 +358,14 @@ class WebViewTab(tab.AbstractTab):
def __init__(self, win_id, parent=None): def __init__(self, win_id, parent=None):
super().__init__(win_id) 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.history = WebViewHistory(self)
self.scroll = WebViewScroller(parent=self) self.scroll = WebViewScroller(parent=self)
self.caret = WebViewCaret(win_id=win_id, tab=self, 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._set_widget(widget)
self._connect_signals() self._connect_signals()
self.zoom._set_default_zoom()
def openurl(self, url): def openurl(self, url):
self._widget.openurl(url) self._widget.openurl(url)
@ -402,12 +413,6 @@ class WebViewTab(tab.AbstractTab):
def title(self): def title(self):
return self._widget.title() 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): def has_selection(self):
return self._widget.hasSelection() return self._widget.hasSelection()

View File

@ -419,7 +419,7 @@ class BrowserPage(QWebPage):
if data is None: if data is None:
return return
if 'zoom' in data: 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): if 'scroll-pos' in data and frame.scrollPosition() == QPoint(0, 0):
frame.setScrollPosition(data['scroll-pos']) frame.setScrollPosition(data['scroll-pos'])

View File

@ -23,7 +23,7 @@ import sys
import itertools import itertools
import functools 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.QtGui import QPalette
from PyQt5.QtWidgets import QApplication, QStyleFactory from PyQt5.QtWidgets import QApplication, QStyleFactory
from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKit import QWebSettings
@ -43,6 +43,7 @@ class WebView(QWebView):
Our own subclass of a QWebView with some added bells and whistles. Our own subclass of a QWebView with some added bells and whistles.
Attributes: Attributes:
tab: The WebKitTab object for this WebView
hintmanager: The HintManager instance for this view. hintmanager: The HintManager instance for this view.
progress: loading progress of this page. progress: loading progress of this page.
scroll_pos: The current scroll position as (x%, y%) tuple. 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. search_flags: The search flags of the last search.
_tab_id: The tab ID of the view. _tab_id: The tab ID of the view.
_has_ssl_errors: Whether SSL errors occurred during loading. _has_ssl_errors: Whether SSL errors occurred during loading.
_zoom: A NeighborList with the zoom levels.
_old_scroll_pos: The old scroll position. _old_scroll_pos: The old scroll position.
_check_insertmode: If True, in mouseReleaseEvent we should check if we _check_insertmode: If True, in mouseReleaseEvent we should check if we
need to enter/leave insert mode. 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. _ignore_wheel_event: Ignore the next wheel event.
See https://github.com/The-Compiler/qutebrowser/issues/395 See https://github.com/The-Compiler/qutebrowser/issues/395
@ -72,6 +71,9 @@ class WebView(QWebView):
linkHovered: QWebPages linkHovered signal exposed. linkHovered: QWebPages linkHovered signal exposed.
load_status_changed: The loading status changed load_status_changed: The loading status changed
url_text_changed: Current URL string 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. shutting_down: Emitted when the view is shutting down.
""" """
@ -80,13 +82,15 @@ class WebView(QWebView):
load_status_changed = pyqtSignal(str) load_status_changed = pyqtSignal(str)
url_text_changed = pyqtSignal(str) url_text_changed = pyqtSignal(str)
shutting_down = pyqtSignal() 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) super().__init__(parent)
if sys.platform == 'darwin' and qtutils.version_check('5.4'): if sys.platform == 'darwin' and qtutils.version_check('5.4'):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948 # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
# See https://github.com/The-Compiler/qutebrowser/issues/462 # See https://github.com/The-Compiler/qutebrowser/issues/462
self.setStyle(QStyleFactory.create('Fusion')) self.setStyle(QStyleFactory.create('Fusion'))
self.tab = tab
self.win_id = win_id self.win_id = win_id
self.load_status = usertypes.LoadStatus.none self.load_status = usertypes.LoadStatus.none
self._check_insertmode = False self._check_insertmode = False
@ -94,21 +98,12 @@ class WebView(QWebView):
self.scroll_pos = (-1, -1) self.scroll_pos = (-1, -1)
self.statusbar_message = '' self.statusbar_message = ''
self._old_scroll_pos = (-1, -1) self._old_scroll_pos = (-1, -1)
self._zoom = None
self._has_ssl_errors = False self._has_ssl_errors = False
self._ignore_wheel_event = False self._ignore_wheel_event = False
self.keep_icon = False self.keep_icon = False
self.search_text = None self.search_text = None
self.search_flags = 0 self.search_flags = 0
self.init_neighborlist()
self._set_bg_color() 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.cur_url = QUrl()
self.progress = 0 self.progress = 0
self.registry = objreg.ObjectRegistry() self.registry = objreg.ObjectRegistry()
@ -129,8 +124,6 @@ class WebView(QWebView):
mode_manager.entered.connect(self.on_mode_entered) mode_manager.entered.connect(self.on_mode_entered)
mode_manager.left.connect(self.on_mode_left) mode_manager.left.connect(self.on_mode_left)
self.viewing_source = False self.viewing_source = False
self.setZoomFactor(float(config.get('ui', 'default-zoom')) / 100)
self._default_zoom_changed = False
if config.get('input', 'rocker-gestures'): if config.get('input', 'rocker-gestures'):
self.setContextMenuPolicy(Qt.PreventContextMenu) self.setContextMenuPolicy(Qt.PreventContextMenu)
self.urlChanged.connect(self.on_url_changed) self.urlChanged.connect(self.on_url_changed)
@ -201,14 +194,8 @@ class WebView(QWebView):
@pyqtSlot(str, str) @pyqtSlot(str, str)
def on_config_changed(self, section, option): def on_config_changed(self, section, option):
"""Reinitialize the zoom neighborlist if related config changed.""" """Update rocker gestures/background color."""
if section == 'ui' and option in ('zoom-levels', 'default-zoom'): if section == 'input' and option == 'rocker-gestures':
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':
if config.get('input', 'rocker-gestures'): if config.get('input', 'rocker-gestures'):
self.setContextMenuPolicy(Qt.PreventContextMenu) self.setContextMenuPolicy(Qt.PreventContextMenu)
else: else:
@ -216,13 +203,6 @@ class WebView(QWebView):
elif section == 'colors' and option == 'webpage.bg': elif section == 'colors' and option == 'webpage.bg':
self._set_bg_color() 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): def _mousepress_backforward(self, e):
"""Handle back/forward mouse button presses. """Handle back/forward mouse button presses.
@ -372,33 +352,6 @@ class WebView(QWebView):
bridge = objreg.get('js-bridge') bridge = objreg.get('js-bridge')
frame.addToJavaScriptWindowObject('qute', 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') @pyqtSlot('QUrl')
def on_url_changed(self, url): def on_url_changed(self, url):
"""Update cur_url when URL has changed. """Update cur_url when URL has changed.
@ -635,14 +588,6 @@ class WebView(QWebView):
return return
if e.modifiers() & Qt.ControlModifier: if e.modifiers() & Qt.ControlModifier:
e.accept() e.accept()
divider = config.get('input', 'mouse-zoom-divider') self.mouse_wheel_zoom.emit(e.angleDelta())
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
else: else:
super().wheelEvent(e) super().wheelEvent(e)

View File

@ -166,7 +166,7 @@ class SessionManager(QObject):
user_data = item.userData() user_data = item.userData()
if tab.history.current_idx() == idx: if tab.history.current_idx() == idx:
pos = tab.scroll.pos_px() 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()} item_data['scroll-pos'] = {'x': pos.x(), 'y': pos.y()}
elif user_data is not None: elif user_data is not None:
if 'zoom' in user_data: if 'zoom' in user_data: