Permalink
Browse files

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.
  • Loading branch information...
The-Compiler committed Aug 11, 2016
1 parent 8b0028c commit 3bffb71b551edcd4c39e9226e9a3691b3f2dba75
@@ -27,7 +27,8 @@
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 @@ def _set_load_status(self, val):
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 @@ def _handle_mouse_press(self, _obj, e):
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 @@ def _mousepress_backforward(self, e):
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 @@ def _connect_signals(self):
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 @@ def createWindow(self, _typ):
"""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 @@ def _connect_signals(self):
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.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,36 +42,35 @@ 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,
QWebPage.ChooseMultipleFilesExtension: self._handle_multiple_files,
}
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 @@ def on_start_hinting(self, hint_target):
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,56 +532,34 @@ def shouldInterruptJavaScript(self):
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
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 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
Oops, something went wrong.

0 comments on commit 3bffb71

Please sign in to comment.