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 dy *= count
cmdutils.check_overflow(dx, 'int') cmdutils.check_overflow(dx, 'int')
cmdutils.check_overflow(dy, '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, @cmdutils.register(instance='command-dispatcher', hide=True,
scope='window') scope='window')
@ -526,54 +526,29 @@ class CommandDispatcher:
(up/down/left/right/top/bottom). (up/down/left/right/top/bottom).
count: multiplier count: multiplier
""" """
fake_keys = { tab = self._current_widget()
'up': Qt.Key_Up, funcs = {
'down': Qt.Key_Down, 'up': tab.scroll.up,
'left': Qt.Key_Left, 'down': tab.scroll.down,
'right': Qt.Key_Right, 'left': tab.scroll.left,
'top': Qt.Key_Home, 'right': tab.scroll.right,
'bottom': Qt.Key_End, 'top': tab.scroll.top,
'page-up': Qt.Key_PageUp, 'bottom': tab.scroll.bottom,
'page-down': Qt.Key_PageDown, 'page-up': tab.scroll.page_up,
'page-down': tab.scroll.page_down,
} }
try: try:
key = fake_keys[direction] func = funcs[direction]
except KeyError: except KeyError:
expected_values = ', '.join(sorted(fake_keys)) expected_values = ', '.join(sorted(funcs))
raise cmdexc.CommandError("Invalid value {!r} for direction - " raise cmdexc.CommandError("Invalid value {!r} for direction - "
"expected one of: {}".format( "expected one of: {}".format(
direction, expected_values)) 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'): if direction in ('top', 'bottom'):
count = 1 func()
else:
max_min = { func(count=count)
'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)
@cmdutils.register(instance='command-dispatcher', hide=True, @cmdutils.register(instance='command-dispatcher', hide=True,
scope='window') scope='window')
@ -598,19 +573,14 @@ class CommandDispatcher:
elif count is not None: elif count is not None:
perc = count perc = count
orientation = Qt.Horizontal if horizontal else Qt.Vertical if horizontal:
x = perc
if perc == 0 and orientation == Qt.Vertical: y = None
self.scroll('top')
elif perc == 100 and orientation == Qt.Vertical:
self.scroll('bottom')
else: else:
perc = qtutils.check_overflow(perc, 'int', fatal=False) x = None
frame = self._current_widget().page().currentFrame() y = perc
m = frame.scrollBarMaximum(orientation)
if m == 0: self._current_widget().scroll.to_perc(x, y)
return
frame.setScrollBarValue(orientation, int(m * perc / 100))
@cmdutils.register(instance='command-dispatcher', hide=True, @cmdutils.register(instance='command-dispatcher', hide=True,
scope='window') scope='window')
@ -633,38 +603,19 @@ class CommandDispatcher:
scrolling up at the top of the page. scrolling up at the top of the page.
count: multiplier count: multiplier
""" """
frame = self._current_widget().page().currentFrame() tab = self._current_widget()
if not frame.url().isValid(): if not tab.cur_url.isValid():
# See https://github.com/The-Compiler/qutebrowser/issues/701 # See https://github.com/The-Compiler/qutebrowser/issues/701
return return
if (bottom_navigate is not None and if bottom_navigate is not None and tab.scroll.at_bottom():
frame.scrollPosition().y() >=
frame.scrollBarMaximum(Qt.Vertical)):
self.navigate(bottom_navigate) self.navigate(bottom_navigate)
return 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) self.navigate(top_navigate)
return return
mult_x = count * x tab.scroll.delta_page(count * x, count * y)
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)
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
def yank(self, title=False, sel=False, domain=False, pretty=False): def yank(self, title=False, sel=False, domain=False, pretty=False):

View File

@ -21,7 +21,7 @@
import itertools import itertools
from PyQt5.QtCore import pyqtSignal, QUrl from PyQt5.QtCore import pyqtSignal, QUrl, QObject
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QLayout from PyQt5.QtWidgets import QWidget, QLayout
@ -55,6 +55,65 @@ class WrapperLayout(QLayout):
self._widget.setGeometry(r) 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: class AbstractHistory:
"""The history attribute of a AbstractTab.""" """The history attribute of a AbstractTab."""
@ -116,7 +175,6 @@ class AbstractTab(QWidget):
load_started = pyqtSignal() load_started = pyqtSignal()
load_progress = pyqtSignal(int) load_progress = pyqtSignal(int)
load_finished = pyqtSignal(bool) load_finished = pyqtSignal(bool)
scroll_pos_changed = pyqtSignal(int, int)
icon_changed = pyqtSignal(QIcon) icon_changed = pyqtSignal(QIcon)
# FIXME:refactor get rid of this altogether? # FIXME:refactor get rid of this altogether?
url_text_changed = pyqtSignal(str) url_text_changed = pyqtSignal(str)
@ -129,6 +187,7 @@ class AbstractTab(QWidget):
self.tab_id = next(tab_id_gen) self.tab_id = next(tab_id_gen)
super().__init__(parent) super().__init__(parent)
self.history = AbstractHistory(self) self.history = AbstractHistory(self)
self.scroll = AbstractScroller(parent=self)
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?
@ -137,6 +196,7 @@ class AbstractTab(QWidget):
self._layout = WrapperLayout(widget, self) self._layout = WrapperLayout(widget, self)
self._widget = widget self._widget = widget
self.history.history = widget.history() self.history.history = widget.history()
self.scroll.widget = widget
widget.setParent(self) widget.setParent(self)
@property @property
@ -151,12 +211,6 @@ class AbstractTab(QWidget):
def load_status(self): def load_status(self):
raise NotImplementedError raise NotImplementedError
def scroll_pos_perc(self):
raise NotImplementedError
def scroll_pos_px(self):
raise NotImplementedError
def openurl(self, url): def openurl(self, url):
raise NotImplementedError raise NotImplementedError

View File

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

View File

@ -19,7 +19,8 @@
"""Wrapper over our (QtWebKit) WebView.""" """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 PyQt5.QtWebKitWidgets import QWebPage
from qutebrowser.browser import tab from qutebrowser.browser import tab
@ -27,6 +28,97 @@ from qutebrowser.browser.webkit import webview
from qutebrowser.utils import qtutils 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): class WebViewHistory(tab.AbstractHistory):
def __iter__(self): def __iter__(self):
@ -63,7 +155,7 @@ class WebViewHistory(tab.AbstractHistory):
if 'zoom' in cur_data: if 'zoom' in cur_data:
self.tab.zoom_perc(cur_data['zoom'] * 100) self.tab.zoom_perc(cur_data['zoom'] * 100)
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(
self.tab.scroll, cur_data['scroll-pos'])) self.tab.scroll, cur_data['scroll-pos']))
@ -74,6 +166,7 @@ class WebViewTab(tab.AbstractTab):
super().__init__(win_id) super().__init__(win_id)
widget = webview.WebView(win_id, self.tab_id) widget = webview.WebView(win_id, self.tab_id)
self.history = WebViewHistory(self) self.history = WebViewHistory(self)
self.scroll = WebViewScroller(parent=self)
self._set_widget(widget) self._set_widget(widget)
self._connect_signals() self._connect_signals()
@ -92,12 +185,6 @@ class WebViewTab(tab.AbstractTab):
def load_status(self): def load_status(self):
return self._widget.load_status 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): def dump_async(self, callback=None, *, plain=False):
frame = self._widget.page().mainFrame() frame = self._widget.page().mainFrame()
if plain: if plain:
@ -138,7 +225,7 @@ class WebViewTab(tab.AbstractTab):
page.linkHovered.connect(self.link_hovered) page.linkHovered.connect(self.link_hovered)
page.loadProgress.connect(self.load_progress) page.loadProgress.connect(self.load_progress)
frame.loadStarted.connect(self.load_started) 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.titleChanged.connect(self.title_changed)
view.url_text_changed.connect(self.url_text_changed) view.url_text_changed.connect(self.url_text_changed)
view.load_status_changed.connect(self.load_status_changed) view.load_status_changed.connect(self.load_status_changed)

View File

@ -52,4 +52,4 @@ class Percentage(textbase.TextBase):
@pyqtSlot(webview.WebView) @pyqtSlot(webview.WebView)
def on_tab_changed(self, tab): def on_tab_changed(self, tab):
"""Update scroll position when tab changed.""" """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 # https://github.com/The-Compiler/qutebrowser/issues/1579
# tab.statusBarMessage.connect( # tab.statusBarMessage.connect(
# self._filter.create(self.cur_statusbar_message, tab)) # 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)) 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( tab.url_text_changed.connect(
self._filter.create(self.cur_url_text_changed, tab)) self._filter.create(self.cur_url_text_changed, tab))
tab.load_status_changed.connect( tab.load_status_changed.connect(
@ -654,7 +654,7 @@ class TabbedBrowser(tabwidget.TabWidget):
if key != "'": if key != "'":
message.error(self._win_id, "Failed to set mark: url invalid") message.error(self._win_id, "Failed to set mark: url invalid")
return return
point = self.currentWidget().page().currentFrame().scrollPosition() point = self.currentWidget().scroll.pos_px()
if key.isupper(): if key.isupper():
self._global_marks[key] = point, url self._global_marks[key] = point, url
@ -675,7 +675,7 @@ class TabbedBrowser(tabwidget.TabWidget):
except qtutils.QtValueError: except qtutils.QtValueError:
urlkey = None urlkey = None
frame = self.currentWidget().page().currentFrame() tab = self.currentWidget()
if key.isupper(): if key.isupper():
if key in self._global_marks: if key in self._global_marks:
@ -686,6 +686,7 @@ class TabbedBrowser(tabwidget.TabWidget):
if ok: if ok:
self.cur_load_finished.disconnect(callback) self.cur_load_finished.disconnect(callback)
frame.setScrollPosition(point) frame.setScrollPosition(point)
tab.scroll.to_point(point)
self.openurl(url, newtab=False) self.openurl(url, newtab=False)
self.cur_load_finished.connect(callback) self.cur_load_finished.connect(callback)
@ -701,6 +702,6 @@ class TabbedBrowser(tabwidget.TabWidget):
# "'" would just jump to the current position every time # "'" would just jump to the current position every time
self.set_mark("'") self.set_mark("'")
frame.setScrollPosition(point) tab.scroll.to_point(point)
else: else:
message.error(self._win_id, "Mark {} is not set".format(key)) message.error(self._win_id, "Mark {} is not set".format(key))

View File

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

View File

@ -165,7 +165,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: