Full scrolling implementation
This commit is contained in:
parent
56852821e8
commit
34d3d2cda6
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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))
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user