Full scrolling implementation

This commit is contained in:
Florian Bruhin 2016-06-14 17:32:36 +02:00
parent 56852821e8
commit 34d3d2cda6
8 changed files with 204 additions and 109 deletions

View File

@ -513,7 +513,7 @@ class CommandDispatcher:
dy *= count
cmdutils.check_overflow(dx, 'int')
cmdutils.check_overflow(dy, 'int')
self._current_widget().page().currentFrame().scroll(dx, dy)
self._current_widget().scroll.delta(dx, dy)
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
@ -526,54 +526,29 @@ class CommandDispatcher:
(up/down/left/right/top/bottom).
count: multiplier
"""
fake_keys = {
'up': Qt.Key_Up,
'down': Qt.Key_Down,
'left': Qt.Key_Left,
'right': Qt.Key_Right,
'top': Qt.Key_Home,
'bottom': Qt.Key_End,
'page-up': Qt.Key_PageUp,
'page-down': Qt.Key_PageDown,
tab = self._current_widget()
funcs = {
'up': tab.scroll.up,
'down': tab.scroll.down,
'left': tab.scroll.left,
'right': tab.scroll.right,
'top': tab.scroll.top,
'bottom': tab.scroll.bottom,
'page-up': tab.scroll.page_up,
'page-down': tab.scroll.page_down,
}
try:
key = fake_keys[direction]
func = funcs[direction]
except KeyError:
expected_values = ', '.join(sorted(fake_keys))
expected_values = ', '.join(sorted(funcs))
raise cmdexc.CommandError("Invalid value {!r} for direction - "
"expected one of: {}".format(
direction, expected_values))
widget = self._current_widget()
frame = widget.page().currentFrame()
press_evt = QKeyEvent(QEvent.KeyPress, key, Qt.NoModifier, 0, 0, 0)
release_evt = QKeyEvent(QEvent.KeyRelease, key, Qt.NoModifier, 0, 0, 0)
# Count doesn't make sense with top/bottom
if direction in ('top', 'bottom'):
count = 1
max_min = {
'up': [Qt.Vertical, frame.scrollBarMinimum],
'down': [Qt.Vertical, frame.scrollBarMaximum],
'left': [Qt.Horizontal, frame.scrollBarMinimum],
'right': [Qt.Horizontal, frame.scrollBarMaximum],
'page-up': [Qt.Vertical, frame.scrollBarMinimum],
'page-down': [Qt.Vertical, frame.scrollBarMaximum],
}
for _ in range(count):
# Abort scrolling if the minimum/maximum was reached.
try:
qt_dir, getter = max_min[direction]
except KeyError:
pass
else:
if frame.scrollBarValue(qt_dir) == getter(qt_dir):
return
widget.keyPressEvent(press_evt)
widget.keyReleaseEvent(release_evt)
func()
else:
func(count=count)
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
@ -598,19 +573,14 @@ class CommandDispatcher:
elif count is not None:
perc = count
orientation = Qt.Horizontal if horizontal else Qt.Vertical
if perc == 0 and orientation == Qt.Vertical:
self.scroll('top')
elif perc == 100 and orientation == Qt.Vertical:
self.scroll('bottom')
if horizontal:
x = perc
y = None
else:
perc = qtutils.check_overflow(perc, 'int', fatal=False)
frame = self._current_widget().page().currentFrame()
m = frame.scrollBarMaximum(orientation)
if m == 0:
return
frame.setScrollBarValue(orientation, int(m * perc / 100))
x = None
y = perc
self._current_widget().scroll.to_perc(x, y)
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
@ -633,38 +603,19 @@ class CommandDispatcher:
scrolling up at the top of the page.
count: multiplier
"""
frame = self._current_widget().page().currentFrame()
if not frame.url().isValid():
tab = self._current_widget()
if not tab.cur_url.isValid():
# See https://github.com/The-Compiler/qutebrowser/issues/701
return
if (bottom_navigate is not None and
frame.scrollPosition().y() >=
frame.scrollBarMaximum(Qt.Vertical)):
if bottom_navigate is not None and tab.scroll.at_bottom():
self.navigate(bottom_navigate)
return
elif top_navigate is not None and frame.scrollPosition().y() == 0:
elif top_navigate is not None and tab.scroll.at_top():
self.navigate(top_navigate)
return
mult_x = count * x
mult_y = count * y
if mult_y.is_integer():
if mult_y == 0:
pass
elif mult_y < 0:
self.scroll('page-up', count=-int(mult_y))
elif mult_y > 0: # pragma: no branch
self.scroll('page-down', count=int(mult_y))
mult_y = 0
if mult_x == 0 and mult_y == 0:
return
size = frame.geometry()
dx = mult_x * size.width()
dy = mult_y * size.height()
cmdutils.check_overflow(dx, 'int')
cmdutils.check_overflow(dy, 'int')
frame.scroll(dx, dy)
tab.scroll.delta_page(count * x, count * y)
@cmdutils.register(instance='command-dispatcher', scope='window')
def yank(self, title=False, sel=False, domain=False, pretty=False):

View File

@ -21,7 +21,7 @@
import itertools
from PyQt5.QtCore import pyqtSignal, QUrl
from PyQt5.QtCore import pyqtSignal, QUrl, QObject
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QLayout
@ -55,6 +55,65 @@ class WrapperLayout(QLayout):
self._widget.setGeometry(r)
class AbstractScroller(QObject):
"""Attribute of AbstractTab to manage scroll position."""
perc_changed = pyqtSignal(int, int)
def __init__(self, parent=None):
super().__init__(parent)
self.widget = None
def pos_px(self):
raise NotImplementedError
def pos_perc(self):
raise NotImplementedError
def to_perc(self, x=None, y=None):
raise NotImplementedError
def to_point(self, point):
raise NotImplementedError
def delta(self, x=0, y=0):
raise NotImplementedError
def delta_page(self, x=0, y=0):
raise NotImplementedError
def up(self, count=1):
raise NotImplementedError
def down(self, count=1):
raise NotImplementedError
def left(self, count=1):
raise NotImplementedError
def right(self, count=1):
raise NotImplementedError
def top(self):
raise NotImplementedError
def bottom(self):
raise NotImplementedError
def page_up(self, count=1):
raise NotImplementedError
def page_down(self, count=1):
raise NotImplementedError
def at_top(self):
raise NotImplementedError
def at_bottom(self):
raise NotImplementedError
class AbstractHistory:
"""The history attribute of a AbstractTab."""
@ -116,7 +175,6 @@ class AbstractTab(QWidget):
load_started = pyqtSignal()
load_progress = pyqtSignal(int)
load_finished = pyqtSignal(bool)
scroll_pos_changed = pyqtSignal(int, int)
icon_changed = pyqtSignal(QIcon)
# FIXME:refactor get rid of this altogether?
url_text_changed = pyqtSignal(str)
@ -129,6 +187,7 @@ class AbstractTab(QWidget):
self.tab_id = next(tab_id_gen)
super().__init__(parent)
self.history = AbstractHistory(self)
self.scroll = AbstractScroller(parent=self)
self._layout = None
self._widget = None
self.keep_icon = False # FIXME:refactor get rid of this?
@ -137,6 +196,7 @@ class AbstractTab(QWidget):
self._layout = WrapperLayout(widget, self)
self._widget = widget
self.history.history = widget.history()
self.scroll.widget = widget
widget.setParent(self)
@property
@ -151,12 +211,6 @@ class AbstractTab(QWidget):
def load_status(self):
raise NotImplementedError
def scroll_pos_perc(self):
raise NotImplementedError
def scroll_pos_px(self):
raise NotImplementedError
def openurl(self, url):
raise NotImplementedError

View File

@ -31,6 +31,13 @@ from qutebrowser.browser import tab
from qutebrowser.utils import usertypes, qtutils
class WebEngineScroller(tab.AbstractScroller):
## TODO
pass
class WebEngineHistory(tab.AbstractHistory):
def __iter__(self):
@ -68,6 +75,7 @@ class WebEngineViewTab(tab.AbstractTab):
super().__init__(win_id)
widget = QWebEngineView()
self.history = WebEngineHistory(self)
self.scroll = WebEngineScroller(parent=self)
self._set_widget(widget)
self._connect_signals()
@ -86,12 +94,6 @@ class WebEngineViewTab(tab.AbstractTab):
def load_status(self):
return usertypes.LoadStatus.success
def scroll_pos_perc(self):
return (0, 0) # FIXME
def scroll_pos_px(self):
return self._widget.page().scrollPosition()
def set_zoom_factor(self, factor):
self._widget.setZoomFactor(factor)
@ -135,6 +137,6 @@ class WebEngineViewTab(tab.AbstractTab):
page.loadFinished.connect(self.load_finished)
# FIXME:refactor
# view.iconChanged.connect(self.icon_changed)
# view.scroll_pos_changed.connect(self.scroll_pos_changed)
# view.scroll.pos_changed.connect(self.scroll.perc_changed)
# view.url_text_changed.connect(self.url_text_changed)
# view.load_status_changed.connect(self.load_status_changed)

View File

@ -19,7 +19,8 @@
"""Wrapper over our (QtWebKit) WebView."""
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtCore import pyqtSlot, Qt, QEvent
from PyQt5.QtGui import QKeyEvent
from PyQt5.QtWebKitWidgets import QWebPage
from qutebrowser.browser import tab
@ -27,6 +28,97 @@ from qutebrowser.browser.webkit import webview
from qutebrowser.utils import qtutils
class WebViewScroller(tab.AbstractScroller):
def pos_px(self):
return self.widget.page().mainFrame().scrollPosition()
def pos_perc(self):
return self.widget.scroll_pos
def to_point(self, point):
self.widget.page().mainFrame().setScrollPosition(point)
def delta(x=0, y=0):
qtutils.check_overflow(x, 'int')
qtutils.check_overflow(y, 'int')
self.widget.page().mainFrame().scroll(x, y)
def delta_page(self, x=0, y=0):
if y.is_integer():
y = int(y)
if y == 0:
pass
elif y < 0:
self.page_up(count=y)
elif y > 0:
self.page_down(count=y)
y = 0
if x == 0 and y == 0:
return
size = frame.geometry()
self.delta(x * size.width(), y * size.height())
def to_perc(self, x=None, y=None):
if x is None and y == 0:
self.top()
elif x is None and y == 100:
self.bottom()
else:
for val, orientation in [(x, Qt.Horizontal), (y, Qt.Vertical)]:
perc = qtutils.check_overflow(val, 'int', fatal=False)
frame = self.widget.page().mainFrame()
m = frame.scrollBarMaximum(orientation)
if m == 0:
continue
frame.setScrollBarValue(orientation, int(m * val / 100))
def _key_press(self, key, count=1, getter_name=None, direction=None):
frame = self.widget.page().mainFrame()
press_evt = QKeyEvent(QEvent.KeyPress, key, Qt.NoModifier, 0, 0, 0)
release_evt = QKeyEvent(QEvent.KeyRelease, key, Qt.NoModifier, 0, 0, 0)
getter = None if getter_name is None else getattr(frame, getter_name)
for _ in range(count):
# Abort scrolling if the minimum/maximum was reached.
if frame.scrollBarValue(direction) == getter(direction):
return
self.widget.keyPressEvent(press_evt)
self.widget.keyReleaseEvent(release_evt)
def up(self, count=1):
self._key_press(Qt.Key_Up, count, 'scrollBarMinimum', Qt.Vertical)
def down(self, count=1):
self._key_press(Qt.Key_Down, count, 'scrollBarMaximum', Qt.Vertical)
def left(self, count=1):
self._key_press(Qt.Key_Left, count, 'scrollBarMinimum', Qt.Horizontal)
def right(self, count=1):
self._key_press(Qt.Key_Right, count, 'scrollBarMaximum', Qt.Horizontal)
def top(self):
self._key_press(Qt.Key_Home)
def bottom(self):
self._key_press(Qt.Key_End)
def page_up(self, count=1):
self._key_press(Qt.Key_PageUp, count, 'scrollBarMinimum', Qt.Vertical)
def page_down(self, count=1):
self._key_press(Qt.Key_PageDown, count, 'scrollBarMaximum',
Qt.Vertical)
def at_top(self):
return self.pos_px().y() == 0
def at_bottom(self):
frame = self.widget.page().currentFrame()
return self.pos_px().y() >= frame.scrollBarMaximum(Qt.Vertical)
class WebViewHistory(tab.AbstractHistory):
def __iter__(self):
@ -63,7 +155,7 @@ class WebViewHistory(tab.AbstractHistory):
if 'zoom' in cur_data:
self.tab.zoom_perc(cur_data['zoom'] * 100)
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(
self.tab.scroll, cur_data['scroll-pos']))
@ -74,6 +166,7 @@ class WebViewTab(tab.AbstractTab):
super().__init__(win_id)
widget = webview.WebView(win_id, self.tab_id)
self.history = WebViewHistory(self)
self.scroll = WebViewScroller(parent=self)
self._set_widget(widget)
self._connect_signals()
@ -92,12 +185,6 @@ class WebViewTab(tab.AbstractTab):
def load_status(self):
return self._widget.load_status
def scroll_pos_perc(self):
return self._widget.scroll_pos
def scroll_pos_px(self):
return self._widget.page().mainFrame().scrollPosition()
def dump_async(self, callback=None, *, plain=False):
frame = self._widget.page().mainFrame()
if plain:
@ -138,7 +225,7 @@ class WebViewTab(tab.AbstractTab):
page.linkHovered.connect(self.link_hovered)
page.loadProgress.connect(self.load_progress)
frame.loadStarted.connect(self.load_started)
view.scroll_pos_changed.connect(self.scroll_pos_changed)
view.scroll_pos_changed.connect(self.scroll.perc_changed)
view.titleChanged.connect(self.title_changed)
view.url_text_changed.connect(self.url_text_changed)
view.load_status_changed.connect(self.load_status_changed)

View File

@ -52,4 +52,4 @@ class Percentage(textbase.TextBase):
@pyqtSlot(webview.WebView)
def on_tab_changed(self, tab):
"""Update scroll position when tab changed."""
self.set_perc(*tab.scroll_pos_perc())
self.set_perc(*tab.scroll.pos_perc())

View File

@ -183,9 +183,9 @@ class TabbedBrowser(tabwidget.TabWidget):
# https://github.com/The-Compiler/qutebrowser/issues/1579
# tab.statusBarMessage.connect(
# self._filter.create(self.cur_statusbar_message, tab))
tab.scroll_pos_changed.connect(
tab.scroll.perc_changed.connect(
self._filter.create(self.cur_scroll_perc_changed, tab))
tab.scroll_pos_changed.connect(self.on_scroll_pos_changed)
tab.scroll.perc_changed.connect(self.on_scroll_pos_changed)
tab.url_text_changed.connect(
self._filter.create(self.cur_url_text_changed, tab))
tab.load_status_changed.connect(
@ -654,7 +654,7 @@ class TabbedBrowser(tabwidget.TabWidget):
if key != "'":
message.error(self._win_id, "Failed to set mark: url invalid")
return
point = self.currentWidget().page().currentFrame().scrollPosition()
point = self.currentWidget().scroll.pos_px()
if key.isupper():
self._global_marks[key] = point, url
@ -675,7 +675,7 @@ class TabbedBrowser(tabwidget.TabWidget):
except qtutils.QtValueError:
urlkey = None
frame = self.currentWidget().page().currentFrame()
tab = self.currentWidget()
if key.isupper():
if key in self._global_marks:
@ -686,6 +686,7 @@ class TabbedBrowser(tabwidget.TabWidget):
if ok:
self.cur_load_finished.disconnect(callback)
frame.setScrollPosition(point)
tab.scroll.to_point(point)
self.openurl(url, newtab=False)
self.cur_load_finished.connect(callback)
@ -701,6 +702,6 @@ class TabbedBrowser(tabwidget.TabWidget):
# "'" would just jump to the current position every time
self.set_mark("'")
frame.setScrollPosition(point)
tab.scroll.to_point(point)
else:
message.error(self._win_id, "Mark {} is not set".format(key))

View File

@ -127,7 +127,7 @@ class TabWidget(QTabWidget):
except qtutils.QtValueError:
fields['host'] = ''
y = widget.scroll_pos_perc()[1]
y = widget.scroll.pos_perc()[1]
if y <= 0:
scroll_pos = 'top'
elif y >= 100:

View File

@ -165,7 +165,7 @@ class SessionManager(QObject):
user_data = item.userData()
if tab.history.current_idx() == idx:
pos = tab.scroll_pos_px()
pos = tab.scroll.pos_px()
item_data['zoom'] = tab.zoom_factor()
item_data['scroll-pos'] = {'x': pos.x(), 'y': pos.y()}
elif user_data is not None: