QtWebEngine: Implement mouse opentarget handling
This moves various stuff around and out of QtWebKit code: - open_target and hint_target are now in TabData, not on the WebPage - As much as possible got extracted from WebPage.acceptNavigationRequest to AbstractTab._on_link_clicked, with a new link_clicked signal added to WebPage. However, we need to decide whether to handle the request in this tab or not inside acceptNavigationRequest, so we have some code duplicated there and in WebEnginePage.acceptNavigationRequest. - _mousepress_opentarget (i.e., setting the open_target) is now handled in MouseEventFilter, not in WebView.
This commit is contained in:
parent
8b0028cb87
commit
3bffb71b55
@ -27,7 +27,8 @@ from PyQt5.QtWidgets import QWidget
|
||||
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, objreg, usertypes, message, log, qtutils
|
||||
from qutebrowser.utils import (utils, objreg, usertypes, message, log, qtutils,
|
||||
debug, urlutils)
|
||||
from qutebrowser.misc import miscwidgets
|
||||
from qutebrowser.browser import mouse
|
||||
|
||||
@ -68,14 +69,25 @@ class TabData:
|
||||
load.
|
||||
inspector: The QWebInspector used for this webview.
|
||||
viewing_source: Set if we're currently showing a source view.
|
||||
open_target: How the next clicked link should be opened.
|
||||
hint_target: Override for open_target for hints.
|
||||
"""
|
||||
|
||||
__slots__ = ['keep_icon', 'viewing_source', 'inspector']
|
||||
__slots__ = ['keep_icon', 'viewing_source', 'inspector', 'open_target',
|
||||
'hint_target']
|
||||
|
||||
def __init__(self):
|
||||
self.keep_icon = False
|
||||
self.viewing_source = False
|
||||
self.inspector = None
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
self.hint_target = None
|
||||
|
||||
def combined_target(self):
|
||||
if self.hint_target is not None:
|
||||
return self.hint_target
|
||||
else:
|
||||
return self.open_target
|
||||
|
||||
|
||||
class AbstractPrinting:
|
||||
@ -495,6 +507,43 @@ class AbstractTab(QWidget):
|
||||
self._load_status = val
|
||||
self.load_status_changed.emit(val.name)
|
||||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_link_clicked(self, url):
|
||||
log.webview.debug("link clicked: url {}, hint target {}, "
|
||||
"open_target {}".format(
|
||||
url.toDisplayString(),
|
||||
self.data.hint_target, self.data.open_target))
|
||||
|
||||
if not url.isValid():
|
||||
msg = urlutils.get_errstring(url, "Invalid link clicked")
|
||||
message.error(self.win_id, msg)
|
||||
self.data.open_target = usertypes.ClickTarget.normal
|
||||
return False
|
||||
|
||||
target = self.data.combined_target()
|
||||
|
||||
if target == usertypes.ClickTarget.normal:
|
||||
return
|
||||
elif target == usertypes.ClickTarget.tab:
|
||||
win_id = self.win_id
|
||||
bg_tab = False
|
||||
elif target == usertypes.ClickTarget.tab_bg:
|
||||
win_id = self.win_id
|
||||
bg_tab = True
|
||||
elif target == usertypes.ClickTarget.window:
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
window = mainwindow.MainWindow()
|
||||
window.show()
|
||||
win_id = window.win_id
|
||||
bg_tab = False
|
||||
else:
|
||||
raise ValueError("Invalid ClickTarget {}".format(target))
|
||||
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
tabbed_browser.tabopen(url, background=bg_tab)
|
||||
self.data.open_target = usertypes.ClickTarget.normal
|
||||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_url_changed(self, url):
|
||||
"""Update title when URL has changed and no title is available."""
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, log
|
||||
from qutebrowser.utils import message, log, usertypes
|
||||
|
||||
|
||||
from PyQt5.QtCore import QObject, QEvent, Qt
|
||||
@ -85,7 +85,10 @@ class MouseEventFilter(QObject):
|
||||
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
|
||||
self._mousepress_backforward(e)
|
||||
return True
|
||||
|
||||
self._ignore_wheel_event = True
|
||||
self._mousepress_opentarget(e)
|
||||
|
||||
return False
|
||||
|
||||
def _handle_wheel(self, _obj, e):
|
||||
@ -131,6 +134,27 @@ class MouseEventFilter(QObject):
|
||||
message.error(self._tab.win_id, "At end of history.",
|
||||
immediately=True)
|
||||
|
||||
def _mousepress_opentarget(self, e):
|
||||
"""Set the open target when something was clicked.
|
||||
|
||||
Args:
|
||||
e: The QMouseEvent.
|
||||
"""
|
||||
if e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier:
|
||||
background_tabs = config.get('tabs', 'background-tabs')
|
||||
if e.modifiers() & Qt.ShiftModifier:
|
||||
background_tabs = not background_tabs
|
||||
if background_tabs:
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
else:
|
||||
target = usertypes.ClickTarget.tab
|
||||
self._tab.data.open_target = target
|
||||
log.mouse.debug("Ctrl/Middle click, setting target: {}".format(
|
||||
target))
|
||||
else:
|
||||
self._tab.data.open_target = usertypes.ClickTarget.normal
|
||||
log.mouse.debug("Normal click, setting normal target")
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Filter events going to a QWeb(Engine)View."""
|
||||
evtype = event.type()
|
||||
|
@ -322,7 +322,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
|
||||
def __init__(self, win_id, mode_manager, parent=None):
|
||||
super().__init__(win_id)
|
||||
widget = webview.WebEngineView()
|
||||
widget = webview.WebEngineView(tabdata=self.data)
|
||||
self.history = WebEngineHistory(self)
|
||||
self.scroller = WebEngineScroller(self, parent=self)
|
||||
self.caret = WebEngineCaret(win_id=win_id, mode_manager=mode_manager,
|
||||
@ -499,6 +499,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
view.urlChanged.connect(self._on_url_changed)
|
||||
page.loadFinished.connect(self._on_load_finished)
|
||||
page.certificate_error.connect(self._on_ssl_errors)
|
||||
page.link_clicked.connect(self._on_link_clicked)
|
||||
try:
|
||||
view.iconChanged.connect(self.icon_changed)
|
||||
except AttributeError:
|
||||
|
@ -20,29 +20,39 @@
|
||||
"""The main browser widget for QtWebEngine."""
|
||||
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, Qt, QPoint
|
||||
from PyQt5.QtCore import pyqtSignal, Qt, QPoint, QUrl
|
||||
# pylint: disable=no-name-in-module,import-error,useless-suppression
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||
# pylint: enable=no-name-in-module,import-error,useless-suppression
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import log
|
||||
from qutebrowser.utils import log, debug, qtutils, usertypes
|
||||
|
||||
|
||||
class WebEngineView(QWebEngineView):
|
||||
|
||||
"""Custom QWebEngineView subclass with qutebrowser-specific features."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, tabdata, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setPage(WebEnginePage(self))
|
||||
self.setPage(WebEnginePage(tabdata, parent=self))
|
||||
|
||||
|
||||
class WebEnginePage(QWebEnginePage):
|
||||
|
||||
"""Custom QWebEnginePage subclass with qutebrowser-specific features."""
|
||||
"""Custom QWebEnginePage subclass with qutebrowser-specific features.
|
||||
|
||||
Signals:
|
||||
certificate_error: FIXME:qtwebengine
|
||||
link_clicked: Emitted when a link was clicked on a page.
|
||||
"""
|
||||
|
||||
certificate_error = pyqtSignal()
|
||||
link_clicked = pyqtSignal(QUrl)
|
||||
|
||||
def __init__(self, tabdata, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tabdata = tabdata
|
||||
|
||||
def certificateError(self, error):
|
||||
self.certificate_error.emit()
|
||||
@ -68,3 +78,31 @@ class WebEnginePage(QWebEnginePage):
|
||||
"""Handle new windows via JS."""
|
||||
log.stub()
|
||||
return None
|
||||
|
||||
def acceptNavigationRequest(self,
|
||||
url: QUrl,
|
||||
typ: QWebEnginePage.NavigationType,
|
||||
_is_main_frame: bool):
|
||||
"""Override acceptNavigationRequest to handle clicked links.
|
||||
|
||||
Setting linkDelegationPolicy to DelegateAllLinks and using a slot bound
|
||||
to linkClicked won't work correctly, because when in a frameset, we
|
||||
have no idea in which frame the link should be opened.
|
||||
|
||||
Checks if it should open it in a tab (middle-click or control) or not,
|
||||
and then conditionally opens the URL. Opening it in a new tab/window
|
||||
is handled in the slot connected to link_clicked.
|
||||
"""
|
||||
target = self._tabdata.combined_target()
|
||||
log.webview.debug("navigation request: url {}, type {}, "
|
||||
"target {}".format(
|
||||
url.toDisplayString(),
|
||||
debug.qenum_key(QWebEnginePage, typ),
|
||||
target))
|
||||
|
||||
if typ != QWebEnginePage.NavigationTypeLinkClicked:
|
||||
return True
|
||||
|
||||
self.link_clicked.emit(url)
|
||||
|
||||
return url.isValid() and target == usertypes.ClickTarget.normal
|
||||
|
@ -638,3 +638,4 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
page.frameCreated.connect(self._on_frame_created)
|
||||
frame.contentsSizeChanged.connect(self._on_contents_size_changed)
|
||||
frame.initialLayoutCompleted.connect(self._on_history_trigger)
|
||||
page.link_clicked.connect(self._on_link_clicked)
|
||||
|
@ -26,14 +26,14 @@ from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||
from PyQt5.QtWidgets import QFileDialog
|
||||
from PyQt5.QtPrintSupport import QPrintDialog
|
||||
from PyQt5.QtWebKitWidgets import QWebPage
|
||||
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.browser import pdfjs
|
||||
from qutebrowser.browser.webkit import http
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
from qutebrowser.utils import (message, usertypes, log, jinja, qtutils, utils,
|
||||
objreg, debug, urlutils)
|
||||
objreg, debug)
|
||||
|
||||
|
||||
class BrowserPage(QWebPage):
|
||||
@ -42,27 +42,28 @@ class BrowserPage(QWebPage):
|
||||
|
||||
Attributes:
|
||||
error_occurred: Whether an error occurred while loading.
|
||||
open_target: Where to open the next navigation request.
|
||||
("normal", "tab", "tab_bg")
|
||||
_hint_target: Override for open_target while hinting, or None.
|
||||
_extension_handlers: Mapping of QWebPage extensions to their handlers.
|
||||
_networkmanager: The NetworkManager used.
|
||||
_win_id: The window ID this BrowserPage is associated with.
|
||||
_ignore_load_started: Whether to ignore the next loadStarted signal.
|
||||
_is_shutting_down: Whether the page is currently shutting down.
|
||||
_tabdata: The TabData object of the tab this page is in.
|
||||
|
||||
Signals:
|
||||
shutting_down: Emitted when the page is currently shutting down.
|
||||
reloading: Emitted before a web page reloads.
|
||||
arg: The URL which gets reloaded.
|
||||
link_clicked: Emitted when a link was clicked on a page.
|
||||
"""
|
||||
|
||||
shutting_down = pyqtSignal()
|
||||
reloading = pyqtSignal(QUrl)
|
||||
link_clicked = pyqtSignal(QUrl)
|
||||
|
||||
def __init__(self, win_id, tab_id, parent=None):
|
||||
def __init__(self, win_id, tab_id, tabdata, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._tabdata = tabdata
|
||||
self._is_shutting_down = False
|
||||
self._extension_handlers = {
|
||||
QWebPage.ErrorPageExtension: self._handle_errorpage,
|
||||
@ -70,8 +71,6 @@ class BrowserPage(QWebPage):
|
||||
}
|
||||
self._ignore_load_started = False
|
||||
self.error_occurred = False
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
self._hint_target = None
|
||||
self._networkmanager = networkmanager.NetworkManager(
|
||||
win_id, tab_id, self)
|
||||
self.setNetworkAccessManager(self._networkmanager)
|
||||
@ -429,14 +428,16 @@ class BrowserPage(QWebPage):
|
||||
Args:
|
||||
hint_target: A ClickTarget member to set self._hint_target to.
|
||||
"""
|
||||
# FIXME move this away
|
||||
log.webview.debug("Setting force target to {}".format(hint_target))
|
||||
self._hint_target = hint_target
|
||||
self._tabdata.hint_target = hint_target
|
||||
|
||||
@pyqtSlot()
|
||||
def on_stop_hinting(self):
|
||||
"""Emitted when hinting is finished."""
|
||||
# FIXME move this away
|
||||
log.webview.debug("Finishing hinting.")
|
||||
self._hint_target = None
|
||||
self._tabdata.hint_target = None
|
||||
|
||||
def userAgentForUrl(self, url):
|
||||
"""Override QWebPage::userAgentForUrl to customize the user agent."""
|
||||
@ -531,7 +532,10 @@ class BrowserPage(QWebPage):
|
||||
answer = True
|
||||
return answer
|
||||
|
||||
def acceptNavigationRequest(self, _frame, request, typ):
|
||||
def acceptNavigationRequest(self,
|
||||
_frame: QWebFrame,
|
||||
request: QNetworkRequest,
|
||||
typ: QWebPage.NavigationType):
|
||||
"""Override acceptNavigationRequest to handle clicked links.
|
||||
|
||||
Setting linkDelegationPolicy to DelegateAllLinks and using a slot bound
|
||||
@ -539,48 +543,23 @@ class BrowserPage(QWebPage):
|
||||
have no idea in which frame the link should be opened.
|
||||
|
||||
Checks if it should open it in a tab (middle-click or control) or not,
|
||||
and then opens the URL.
|
||||
|
||||
Args:
|
||||
_frame: QWebFrame (target frame)
|
||||
request: QNetworkRequest
|
||||
typ: QWebPage::NavigationType
|
||||
and then conditionally opens the URL. Opening it in a new tab/window
|
||||
is handled in the slot connected to link_clicked.
|
||||
"""
|
||||
url = request.url()
|
||||
urlstr = url.toDisplayString()
|
||||
target = self._tabdata.combined_target()
|
||||
log.webview.debug("navigation request: url {}, type {}, "
|
||||
"target {}".format(
|
||||
url.toDisplayString(),
|
||||
debug.qenum_key(QWebPage, typ),
|
||||
target))
|
||||
|
||||
if typ == QWebPage.NavigationTypeReload:
|
||||
self.reloading.emit(url)
|
||||
if typ != QWebPage.NavigationTypeLinkClicked:
|
||||
return True
|
||||
if not url.isValid():
|
||||
msg = urlutils.get_errstring(url, "Invalid link clicked")
|
||||
message.error(self._win_id, msg)
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
return False
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._win_id)
|
||||
log.webview.debug("acceptNavigationRequest, url {}, type {}, hint "
|
||||
"target {}, open_target {}".format(
|
||||
urlstr, debug.qenum_key(QWebPage, typ),
|
||||
self._hint_target, self.open_target))
|
||||
if self._hint_target is not None:
|
||||
target = self._hint_target
|
||||
else:
|
||||
target = self.open_target
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
if target == usertypes.ClickTarget.tab:
|
||||
tabbed_browser.tabopen(url, False)
|
||||
return False
|
||||
elif target == usertypes.ClickTarget.tab_bg:
|
||||
tabbed_browser.tabopen(url, True)
|
||||
return False
|
||||
elif target == usertypes.ClickTarget.window:
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
window = mainwindow.MainWindow()
|
||||
window.show()
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=window.win_id)
|
||||
tabbed_browser.tabopen(url, False)
|
||||
return False
|
||||
else:
|
||||
elif typ != QWebPage.NavigationTypeLinkClicked:
|
||||
return True
|
||||
|
||||
self.link_clicked.emit(url)
|
||||
|
||||
return url.isValid() and target == usertypes.ClickTarget.normal
|
||||
|
@ -74,7 +74,7 @@ class WebView(QWebView):
|
||||
self._set_bg_color()
|
||||
self._tab_id = tab_id
|
||||
|
||||
page = self._init_page()
|
||||
page = self._init_page(tab.data)
|
||||
hintmanager = hints.HintManager(win_id, self._tab_id, self)
|
||||
hintmanager.mouse_event.connect(self.on_mouse_event)
|
||||
hintmanager.start_hinting.connect(page.on_start_hinting)
|
||||
@ -89,9 +89,14 @@ class WebView(QWebView):
|
||||
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||
objreg.get('config').changed.connect(self.on_config_changed)
|
||||
|
||||
def _init_page(self):
|
||||
"""Initialize the QWebPage used by this view."""
|
||||
page = webpage.BrowserPage(self.win_id, self._tab_id, self)
|
||||
def _init_page(self, tabdata):
|
||||
"""Initialize the QWebPage used by this view.
|
||||
|
||||
Args:
|
||||
tabdata: The TabData object for this tab.
|
||||
"""
|
||||
page = webpage.BrowserPage(self.win_id, self._tab_id, tabdata,
|
||||
parent=self)
|
||||
self.setPage(page)
|
||||
page.mainFrame().loadFinished.connect(self.on_load_finished)
|
||||
return page
|
||||
@ -201,26 +206,6 @@ class WebView(QWebView):
|
||||
modeman.maybe_leave(self.win_id, usertypes.KeyMode.insert,
|
||||
'click-delayed')
|
||||
|
||||
def _mousepress_opentarget(self, e):
|
||||
"""Set the open target when something was clicked.
|
||||
|
||||
Args:
|
||||
e: The QMouseEvent.
|
||||
"""
|
||||
if e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier:
|
||||
background_tabs = config.get('tabs', 'background-tabs')
|
||||
if e.modifiers() & Qt.ShiftModifier:
|
||||
background_tabs = not background_tabs
|
||||
if background_tabs:
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
else:
|
||||
target = usertypes.ClickTarget.tab
|
||||
self.page().open_target = target
|
||||
log.mouse.debug("Middle click, setting target: {}".format(target))
|
||||
else:
|
||||
self.page().open_target = usertypes.ClickTarget.normal
|
||||
log.mouse.debug("Normal click, setting normal target")
|
||||
|
||||
def shutdown(self):
|
||||
"""Shut down the webview."""
|
||||
self.shutting_down.emit()
|
||||
@ -382,7 +367,6 @@ class WebView(QWebView):
|
||||
The superclass return value.
|
||||
"""
|
||||
self._mousepress_insertmode(e)
|
||||
self._mousepress_opentarget(e)
|
||||
super().mousePressEvent(e)
|
||||
|
||||
def mouseReleaseEvent(self, e):
|
||||
|
Loading…
Reference in New Issue
Block a user