Split browser.py into smaller files
This commit is contained in:
parent
98fb1a0250
commit
9ee4e93e0a
102
qutebrowser/widgets/browserpage.py
Normal file
102
qutebrowser/widgets/browserpage.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||||
|
#
|
||||||
|
# This file is part of qutebrowser.
|
||||||
|
#
|
||||||
|
# qutebrowser is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# qutebrowser is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""The main browser widgets."""
|
||||||
|
|
||||||
|
import sip
|
||||||
|
from PyQt5.QtNetwork import QNetworkReply
|
||||||
|
from PyQt5.QtWebKitWidgets import QWebPage
|
||||||
|
|
||||||
|
import qutebrowser.utils.url as urlutils
|
||||||
|
from qutebrowser.network.networkmanager import NetworkManager
|
||||||
|
from qutebrowser.utils.misc import read_file
|
||||||
|
|
||||||
|
|
||||||
|
class BrowserPage(QWebPage):
|
||||||
|
|
||||||
|
"""Our own QWebPage with advanced features.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_extension_handlers: Mapping of QWebPage extensions to their handlers.
|
||||||
|
network_access_manager: The QNetworkAccessManager used.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._extension_handlers = {
|
||||||
|
QWebPage.ErrorPageExtension: self._handle_errorpage,
|
||||||
|
}
|
||||||
|
self.network_access_manager = NetworkManager(self)
|
||||||
|
self.setNetworkAccessManager(self.network_access_manager)
|
||||||
|
|
||||||
|
def _handle_errorpage(self, opt, out):
|
||||||
|
"""Display an error page if needed.
|
||||||
|
|
||||||
|
Loosly based on Helpviewer/HelpBrowserWV.py from eric5
|
||||||
|
(line 260 @ 5d937eb378dd)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
opt: The QWebPage.ErrorPageExtensionOption instance.
|
||||||
|
out: The QWebPage.ErrorPageExtensionReturn instance to write return
|
||||||
|
values to.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
False if no error page should be displayed, True otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
|
info = sip.cast(opt, QWebPage.ErrorPageExtensionOption)
|
||||||
|
errpage = sip.cast(out, QWebPage.ErrorPageExtensionReturn)
|
||||||
|
errpage.baseUrl = info.url
|
||||||
|
if (info.domain == QWebPage.QtNetwork and
|
||||||
|
info.error == QNetworkReply.OperationCanceledError):
|
||||||
|
return False
|
||||||
|
urlstr = urlutils.urlstring(info.url)
|
||||||
|
title = "Error loading page: {}".format(urlstr)
|
||||||
|
errpage.content = read_file('html/error.html').format(
|
||||||
|
title=title, url=urlstr, error=info.errorString, icon='')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def supportsExtension(self, ext):
|
||||||
|
"""Override QWebPage::supportsExtension to provide error pages.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ext: The extension to check for.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
True if the extension can be handled, False otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return ext in self._extension_handlers
|
||||||
|
|
||||||
|
def extension(self, ext, opt, out):
|
||||||
|
"""Override QWebPage::extension to provide error pages.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ext: The extension.
|
||||||
|
opt: Extension options instance.
|
||||||
|
out: Extension output instance.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
Handler return value.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
handler = self._extension_handlers[ext]
|
||||||
|
except KeyError:
|
||||||
|
return super().extension(ext, opt, out)
|
||||||
|
return handler(opt, out)
|
238
qutebrowser/widgets/browsertab.py
Normal file
238
qutebrowser/widgets/browsertab.py
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||||
|
#
|
||||||
|
# This file is part of qutebrowser.
|
||||||
|
#
|
||||||
|
# qutebrowser is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# qutebrowser is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""The main browser widgets."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent
|
||||||
|
from PyQt5.QtWebKit import QWebSettings
|
||||||
|
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
||||||
|
|
||||||
|
import qutebrowser.utils.url as urlutils
|
||||||
|
import qutebrowser.config.config as config
|
||||||
|
from qutebrowser.widgets.browserpage import BrowserPage
|
||||||
|
from qutebrowser.utils.signals import SignalCache
|
||||||
|
from qutebrowser.utils.usertypes import NeighborList
|
||||||
|
|
||||||
|
|
||||||
|
class BrowserTab(QWebView):
|
||||||
|
|
||||||
|
"""One browser tab in TabbedBrowser.
|
||||||
|
|
||||||
|
Our own subclass of a QWebView with some added bells and whistles.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
page_: The QWebPage behind the view
|
||||||
|
signal_cache: The signal cache associated with the view.
|
||||||
|
_zoom: A NeighborList with the zoom levels.
|
||||||
|
_scroll_pos: The old scroll position.
|
||||||
|
_shutdown_callback: Callback to be called after shutdown.
|
||||||
|
_open_new_tab: Whether to open a new tab for the next action.
|
||||||
|
_shutdown_callback: The callback to call after shutting down.
|
||||||
|
_destroyed: Dict of all items to be destroyed on shtudown.
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
scroll_pos_changed: Scroll percentage of current tab changed.
|
||||||
|
arg 1: x-position in %.
|
||||||
|
arg 2: y-position in %.
|
||||||
|
open_tab: A new tab should be opened.
|
||||||
|
arg: The address to open
|
||||||
|
linkHovered: QWebPages linkHovered signal exposed.
|
||||||
|
temp_message: Show a temporary message in the statusbar.
|
||||||
|
arg: Message to be shown.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
scroll_pos_changed = pyqtSignal(int, int)
|
||||||
|
open_tab = pyqtSignal('QUrl')
|
||||||
|
linkHovered = pyqtSignal(str, str, str)
|
||||||
|
temp_message = pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._scroll_pos = (-1, -1)
|
||||||
|
self._shutdown_callback = None
|
||||||
|
self._open_new_tab = False
|
||||||
|
self._destroyed = {}
|
||||||
|
self._zoom = NeighborList(
|
||||||
|
config.config.get('general', 'zoomlevels').split(','),
|
||||||
|
default=config.config.get('general', 'defaultzoom'),
|
||||||
|
mode=NeighborList.BLOCK)
|
||||||
|
self.page_ = BrowserPage(self)
|
||||||
|
self.setPage(self.page_)
|
||||||
|
self.signal_cache = SignalCache(uncached=['linkHovered'])
|
||||||
|
self.page_.setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
|
||||||
|
self.page_.linkHovered.connect(self.linkHovered)
|
||||||
|
self.linkClicked.connect(self.on_link_clicked)
|
||||||
|
# FIXME find some way to hide scrollbars without setScrollBarPolicy
|
||||||
|
|
||||||
|
def openurl(self, url):
|
||||||
|
"""Open an URL in the browser.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: The URL to load, as string or QUrl.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
Return status of self.load
|
||||||
|
|
||||||
|
Emit:
|
||||||
|
titleChanged and urlChanged
|
||||||
|
|
||||||
|
"""
|
||||||
|
u = urlutils.fuzzy_url(url)
|
||||||
|
logging.debug('New title: {}'.format(urlutils.urlstring(u)))
|
||||||
|
self.titleChanged.emit(urlutils.urlstring(u))
|
||||||
|
self.urlChanged.emit(urlutils.qurl(u))
|
||||||
|
return self.load(u)
|
||||||
|
|
||||||
|
def zoom(self, offset):
|
||||||
|
"""Increase/Decrease the zoom level.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
offset: The offset in the zoom level list.
|
||||||
|
|
||||||
|
Emit:
|
||||||
|
temp_message: Emitted with new zoom level.
|
||||||
|
|
||||||
|
"""
|
||||||
|
level = self._zoom.getitem(offset)
|
||||||
|
self.setZoomFactor(float(level) / 100)
|
||||||
|
self.temp_message.emit("Zoom level: {}%".format(level))
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def on_link_clicked(self, url):
|
||||||
|
"""Handle a link.
|
||||||
|
|
||||||
|
Called from the linkClicked signal. Checks if it should open it in a
|
||||||
|
tab (middle-click or control) or not, and does so.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: The url to handle, as string or QUrl.
|
||||||
|
|
||||||
|
Emit:
|
||||||
|
open_tab: Emitted if window should be opened in a new tab.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self._open_new_tab:
|
||||||
|
self.open_tab.emit(url)
|
||||||
|
else:
|
||||||
|
self.openurl(url)
|
||||||
|
|
||||||
|
def shutdown(self, callback=None):
|
||||||
|
"""Shut down the tab cleanly and remove it.
|
||||||
|
|
||||||
|
Inspired by [1].
|
||||||
|
|
||||||
|
[1] https://github.com/integricho/path-of-a-pyqter/tree/master/qttut08
|
||||||
|
|
||||||
|
Args:
|
||||||
|
callback: Function to call after shutting down.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._shutdown_callback = callback
|
||||||
|
try:
|
||||||
|
# Avoid loading finished signal when stopping
|
||||||
|
self.loadFinished.disconnect()
|
||||||
|
except TypeError:
|
||||||
|
logging.exception("This should never happen.")
|
||||||
|
self.stop()
|
||||||
|
self.close()
|
||||||
|
self.settings().setAttribute(QWebSettings.JavascriptEnabled, False)
|
||||||
|
|
||||||
|
self._destroyed[self.page_] = False
|
||||||
|
self.page_.destroyed.connect(functools.partial(self._on_destroyed,
|
||||||
|
self.page_))
|
||||||
|
self.page_.deleteLater()
|
||||||
|
|
||||||
|
self._destroyed[self] = False
|
||||||
|
self.destroyed.connect(functools.partial(self._on_destroyed, self))
|
||||||
|
self.deleteLater()
|
||||||
|
|
||||||
|
netman = self.page_.network_access_manager
|
||||||
|
self._destroyed[netman] = False
|
||||||
|
netman.abort_requests()
|
||||||
|
netman.destroyed.connect(functools.partial(self._on_destroyed, netman))
|
||||||
|
netman.deleteLater()
|
||||||
|
logging.debug("Tab shutdown scheduled")
|
||||||
|
|
||||||
|
def _on_destroyed(self, sender):
|
||||||
|
"""Called when a subsystem has been destroyed during shutdown.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sender: The object which called the callback.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._destroyed[sender] = True
|
||||||
|
dbgout = '\n'.join(['{}: {}'.format(k.__class__.__name__, v)
|
||||||
|
for (k, v) in self._destroyed.items()])
|
||||||
|
logging.debug("{} has been destroyed, new status:\n{}".format(
|
||||||
|
sender.__class__.__name__, dbgout))
|
||||||
|
if all(self._destroyed.values()):
|
||||||
|
if self._shutdown_callback is not None:
|
||||||
|
logging.debug("Everything destroyed, calling callback")
|
||||||
|
self._shutdown_callback()
|
||||||
|
|
||||||
|
def paintEvent(self, e):
|
||||||
|
"""Extend paintEvent to emit a signal if the scroll position changed.
|
||||||
|
|
||||||
|
This is a bit of a hack: We listen to repaint requests here, in the
|
||||||
|
hope a repaint will always be requested when scrolling, and if the
|
||||||
|
scroll position actually changed, we emit a signal.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
e: The QPaintEvent.
|
||||||
|
|
||||||
|
Emit:
|
||||||
|
scroll_pos_changed; If the scroll position changed.
|
||||||
|
|
||||||
|
"""
|
||||||
|
frame = self.page_.mainFrame()
|
||||||
|
new_pos = (frame.scrollBarValue(Qt.Horizontal),
|
||||||
|
frame.scrollBarValue(Qt.Vertical))
|
||||||
|
if self._scroll_pos != new_pos:
|
||||||
|
self._scroll_pos = new_pos
|
||||||
|
logging.debug("Updating scroll position")
|
||||||
|
frame = self.page_.mainFrame()
|
||||||
|
m = (frame.scrollBarMaximum(Qt.Horizontal),
|
||||||
|
frame.scrollBarMaximum(Qt.Vertical))
|
||||||
|
perc = (round(100 * new_pos[0] / m[0]) if m[0] != 0 else 0,
|
||||||
|
round(100 * new_pos[1] / m[1]) if m[1] != 0 else 0)
|
||||||
|
self.scroll_pos_changed.emit(*perc)
|
||||||
|
# Let superclass handle the event
|
||||||
|
return super().paintEvent(e)
|
||||||
|
|
||||||
|
def event(self, e):
|
||||||
|
"""Check if a link was clicked with the middle button or Ctrl.
|
||||||
|
|
||||||
|
Extend the superclass event().
|
||||||
|
|
||||||
|
This also is a bit of a hack, but it seems it's the only possible way.
|
||||||
|
Set the _open_new_tab attribute accordingly.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
e: The arrived event.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
The superclass event return value.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if e.type() in [QEvent.MouseButtonPress, QEvent.MouseButtonDblClick]:
|
||||||
|
self._open_new_tab = (e.button() == Qt.MidButton or
|
||||||
|
e.modifiers() & Qt.ControlModifier)
|
||||||
|
return super().event(e)
|
@ -24,7 +24,7 @@ from PyQt5.QtCore import QRect, QPoint
|
|||||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout
|
||||||
|
|
||||||
from qutebrowser.widgets.statusbar import StatusBar
|
from qutebrowser.widgets.statusbar import StatusBar
|
||||||
from qutebrowser.widgets.browser import TabbedBrowser
|
from qutebrowser.widgets.tabbedbrowser import TabbedBrowser
|
||||||
from qutebrowser.widgets.completion import CompletionView
|
from qutebrowser.widgets.completion import CompletionView
|
||||||
import qutebrowser.config.config as config
|
import qutebrowser.config.config as config
|
||||||
|
|
||||||
|
@ -15,33 +15,22 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""The main browser widget.
|
"""The main tabbed browser widget."""
|
||||||
|
|
||||||
Defines BrowserTab (our own QWebView subclass) and TabbedBrowser (a TabWidget
|
|
||||||
containing BrowserTabs).
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
import sip
|
|
||||||
from PyQt5.QtWidgets import QApplication, QShortcut, QSizePolicy
|
from PyQt5.QtWidgets import QApplication, QShortcut, QSizePolicy
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QObject
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject
|
||||||
from PyQt5.QtGui import QClipboard
|
from PyQt5.QtGui import QClipboard
|
||||||
from PyQt5.QtPrintSupport import QPrintPreviewDialog
|
from PyQt5.QtPrintSupport import QPrintPreviewDialog
|
||||||
from PyQt5.QtNetwork import QNetworkReply
|
|
||||||
from PyQt5.QtWebKit import QWebSettings
|
|
||||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
|
||||||
|
|
||||||
import qutebrowser.utils.url as urlutils
|
import qutebrowser.utils.url as urlutils
|
||||||
import qutebrowser.config.config as config
|
import qutebrowser.config.config as config
|
||||||
import qutebrowser.commands.utils as cmdutils
|
import qutebrowser.commands.utils as cmdutils
|
||||||
from qutebrowser.widgets.tabbar import TabWidget
|
from qutebrowser.widgets.tabbar import TabWidget
|
||||||
from qutebrowser.network.networkmanager import NetworkManager
|
from qutebrowser.widgets.browsertab import BrowserTab
|
||||||
from qutebrowser.utils.signals import SignalCache, dbg_signal
|
from qutebrowser.utils.signals import dbg_signal
|
||||||
from qutebrowser.utils.misc import read_file
|
|
||||||
from qutebrowser.utils.usertypes import NeighborList
|
|
||||||
|
|
||||||
|
|
||||||
class TabbedBrowser(TabWidget):
|
class TabbedBrowser(TabWidget):
|
||||||
@ -718,286 +707,3 @@ class CurCommandDispatcher(QObject):
|
|||||||
"""
|
"""
|
||||||
tab = self.tabs.currentWidget()
|
tab = self.tabs.currentWidget()
|
||||||
tab.zoom(-count)
|
tab.zoom(-count)
|
||||||
|
|
||||||
|
|
||||||
class BrowserTab(QWebView):
|
|
||||||
|
|
||||||
"""One browser tab in TabbedBrowser.
|
|
||||||
|
|
||||||
Our own subclass of a QWebView with some added bells and whistles.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
page_: The QWebPage behind the view
|
|
||||||
signal_cache: The signal cache associated with the view.
|
|
||||||
_zoom: A NeighborList with the zoom levels.
|
|
||||||
_scroll_pos: The old scroll position.
|
|
||||||
_shutdown_callback: Callback to be called after shutdown.
|
|
||||||
_open_new_tab: Whether to open a new tab for the next action.
|
|
||||||
_shutdown_callback: The callback to call after shutting down.
|
|
||||||
_destroyed: Dict of all items to be destroyed on shtudown.
|
|
||||||
|
|
||||||
Signals:
|
|
||||||
scroll_pos_changed: Scroll percentage of current tab changed.
|
|
||||||
arg 1: x-position in %.
|
|
||||||
arg 2: y-position in %.
|
|
||||||
open_tab: A new tab should be opened.
|
|
||||||
arg: The address to open
|
|
||||||
linkHovered: QWebPages linkHovered signal exposed.
|
|
||||||
temp_message: Show a temporary message in the statusbar.
|
|
||||||
arg: Message to be shown.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
scroll_pos_changed = pyqtSignal(int, int)
|
|
||||||
open_tab = pyqtSignal('QUrl')
|
|
||||||
linkHovered = pyqtSignal(str, str, str)
|
|
||||||
temp_message = pyqtSignal(str)
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self._scroll_pos = (-1, -1)
|
|
||||||
self._shutdown_callback = None
|
|
||||||
self._open_new_tab = False
|
|
||||||
self._destroyed = {}
|
|
||||||
self._zoom = NeighborList(
|
|
||||||
config.config.get('general', 'zoomlevels').split(','),
|
|
||||||
default=config.config.get('general', 'defaultzoom'),
|
|
||||||
mode=NeighborList.BLOCK)
|
|
||||||
self.page_ = BrowserPage(self)
|
|
||||||
self.setPage(self.page_)
|
|
||||||
self.signal_cache = SignalCache(uncached=['linkHovered'])
|
|
||||||
self.page_.setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
|
|
||||||
self.page_.linkHovered.connect(self.linkHovered)
|
|
||||||
self.linkClicked.connect(self.on_link_clicked)
|
|
||||||
# FIXME find some way to hide scrollbars without setScrollBarPolicy
|
|
||||||
|
|
||||||
def openurl(self, url):
|
|
||||||
"""Open an URL in the browser.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
url: The URL to load, as string or QUrl.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
Return status of self.load
|
|
||||||
|
|
||||||
Emit:
|
|
||||||
titleChanged and urlChanged
|
|
||||||
|
|
||||||
"""
|
|
||||||
u = urlutils.fuzzy_url(url)
|
|
||||||
logging.debug('New title: {}'.format(urlutils.urlstring(u)))
|
|
||||||
self.titleChanged.emit(urlutils.urlstring(u))
|
|
||||||
self.urlChanged.emit(urlutils.qurl(u))
|
|
||||||
return self.load(u)
|
|
||||||
|
|
||||||
def zoom(self, offset):
|
|
||||||
"""Increase/Decrease the zoom level.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
offset: The offset in the zoom level list.
|
|
||||||
|
|
||||||
Emit:
|
|
||||||
temp_message: Emitted with new zoom level.
|
|
||||||
|
|
||||||
"""
|
|
||||||
level = self._zoom.getitem(offset)
|
|
||||||
self.setZoomFactor(float(level) / 100)
|
|
||||||
self.temp_message.emit("Zoom level: {}%".format(level))
|
|
||||||
|
|
||||||
@pyqtSlot(str)
|
|
||||||
def on_link_clicked(self, url):
|
|
||||||
"""Handle a link.
|
|
||||||
|
|
||||||
Called from the linkClicked signal. Checks if it should open it in a
|
|
||||||
tab (middle-click or control) or not, and does so.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
url: The url to handle, as string or QUrl.
|
|
||||||
|
|
||||||
Emit:
|
|
||||||
open_tab: Emitted if window should be opened in a new tab.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self._open_new_tab:
|
|
||||||
self.open_tab.emit(url)
|
|
||||||
else:
|
|
||||||
self.openurl(url)
|
|
||||||
|
|
||||||
def shutdown(self, callback=None):
|
|
||||||
"""Shut down the tab cleanly and remove it.
|
|
||||||
|
|
||||||
Inspired by [1].
|
|
||||||
|
|
||||||
[1] https://github.com/integricho/path-of-a-pyqter/tree/master/qttut08
|
|
||||||
|
|
||||||
Args:
|
|
||||||
callback: Function to call after shutting down.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self._shutdown_callback = callback
|
|
||||||
try:
|
|
||||||
# Avoid loading finished signal when stopping
|
|
||||||
self.loadFinished.disconnect()
|
|
||||||
except TypeError:
|
|
||||||
logging.exception("This should never happen.")
|
|
||||||
self.stop()
|
|
||||||
self.close()
|
|
||||||
self.settings().setAttribute(QWebSettings.JavascriptEnabled, False)
|
|
||||||
|
|
||||||
self._destroyed[self.page_] = False
|
|
||||||
self.page_.destroyed.connect(functools.partial(self._on_destroyed,
|
|
||||||
self.page_))
|
|
||||||
self.page_.deleteLater()
|
|
||||||
|
|
||||||
self._destroyed[self] = False
|
|
||||||
self.destroyed.connect(functools.partial(self._on_destroyed, self))
|
|
||||||
self.deleteLater()
|
|
||||||
|
|
||||||
netman = self.page_.network_access_manager
|
|
||||||
self._destroyed[netman] = False
|
|
||||||
netman.abort_requests()
|
|
||||||
netman.destroyed.connect(functools.partial(self._on_destroyed, netman))
|
|
||||||
netman.deleteLater()
|
|
||||||
logging.debug("Tab shutdown scheduled")
|
|
||||||
|
|
||||||
def _on_destroyed(self, sender):
|
|
||||||
"""Called when a subsystem has been destroyed during shutdown.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sender: The object which called the callback.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self._destroyed[sender] = True
|
|
||||||
dbgout = '\n'.join(['{}: {}'.format(k.__class__.__name__, v)
|
|
||||||
for (k, v) in self._destroyed.items()])
|
|
||||||
logging.debug("{} has been destroyed, new status:\n{}".format(
|
|
||||||
sender.__class__.__name__, dbgout))
|
|
||||||
if all(self._destroyed.values()):
|
|
||||||
if self._shutdown_callback is not None:
|
|
||||||
logging.debug("Everything destroyed, calling callback")
|
|
||||||
self._shutdown_callback()
|
|
||||||
|
|
||||||
def paintEvent(self, e):
|
|
||||||
"""Extend paintEvent to emit a signal if the scroll position changed.
|
|
||||||
|
|
||||||
This is a bit of a hack: We listen to repaint requests here, in the
|
|
||||||
hope a repaint will always be requested when scrolling, and if the
|
|
||||||
scroll position actually changed, we emit a signal.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
e: The QPaintEvent.
|
|
||||||
|
|
||||||
Emit:
|
|
||||||
scroll_pos_changed; If the scroll position changed.
|
|
||||||
|
|
||||||
"""
|
|
||||||
frame = self.page_.mainFrame()
|
|
||||||
new_pos = (frame.scrollBarValue(Qt.Horizontal),
|
|
||||||
frame.scrollBarValue(Qt.Vertical))
|
|
||||||
if self._scroll_pos != new_pos:
|
|
||||||
self._scroll_pos = new_pos
|
|
||||||
logging.debug("Updating scroll position")
|
|
||||||
frame = self.page_.mainFrame()
|
|
||||||
m = (frame.scrollBarMaximum(Qt.Horizontal),
|
|
||||||
frame.scrollBarMaximum(Qt.Vertical))
|
|
||||||
perc = (round(100 * new_pos[0] / m[0]) if m[0] != 0 else 0,
|
|
||||||
round(100 * new_pos[1] / m[1]) if m[1] != 0 else 0)
|
|
||||||
self.scroll_pos_changed.emit(*perc)
|
|
||||||
# Let superclass handle the event
|
|
||||||
return super().paintEvent(e)
|
|
||||||
|
|
||||||
def event(self, e):
|
|
||||||
"""Check if a link was clicked with the middle button or Ctrl.
|
|
||||||
|
|
||||||
Extend the superclass event().
|
|
||||||
|
|
||||||
This also is a bit of a hack, but it seems it's the only possible way.
|
|
||||||
Set the _open_new_tab attribute accordingly.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
e: The arrived event.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
The superclass event return value.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if e.type() in [QEvent.MouseButtonPress, QEvent.MouseButtonDblClick]:
|
|
||||||
self._open_new_tab = (e.button() == Qt.MidButton or
|
|
||||||
e.modifiers() & Qt.ControlModifier)
|
|
||||||
return super().event(e)
|
|
||||||
|
|
||||||
|
|
||||||
class BrowserPage(QWebPage):
|
|
||||||
|
|
||||||
"""Our own QWebPage with advanced features.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
_extension_handlers: Mapping of QWebPage extensions to their handlers.
|
|
||||||
network_access_manager: The QNetworkAccessManager used.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self._extension_handlers = {
|
|
||||||
QWebPage.ErrorPageExtension: self._handle_errorpage,
|
|
||||||
}
|
|
||||||
self.network_access_manager = NetworkManager(self)
|
|
||||||
self.setNetworkAccessManager(self.network_access_manager)
|
|
||||||
|
|
||||||
def _handle_errorpage(self, opt, out):
|
|
||||||
"""Display an error page if needed.
|
|
||||||
|
|
||||||
Loosly based on Helpviewer/HelpBrowserWV.py from eric5
|
|
||||||
(line 260 @ 5d937eb378dd)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
opt: The QWebPage.ErrorPageExtensionOption instance.
|
|
||||||
out: The QWebPage.ErrorPageExtensionReturn instance to write return
|
|
||||||
values to.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
False if no error page should be displayed, True otherwise.
|
|
||||||
|
|
||||||
"""
|
|
||||||
info = sip.cast(opt, QWebPage.ErrorPageExtensionOption)
|
|
||||||
errpage = sip.cast(out, QWebPage.ErrorPageExtensionReturn)
|
|
||||||
errpage.baseUrl = info.url
|
|
||||||
if (info.domain == QWebPage.QtNetwork and
|
|
||||||
info.error == QNetworkReply.OperationCanceledError):
|
|
||||||
return False
|
|
||||||
urlstr = urlutils.urlstring(info.url)
|
|
||||||
title = "Error loading page: {}".format(urlstr)
|
|
||||||
errpage.content = read_file('html/error.html').format(
|
|
||||||
title=title, url=urlstr, error=info.errorString, icon='')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def supportsExtension(self, ext):
|
|
||||||
"""Override QWebPage::supportsExtension to provide error pages.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
ext: The extension to check for.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
True if the extension can be handled, False otherwise.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return ext in self._extension_handlers
|
|
||||||
|
|
||||||
def extension(self, ext, opt, out):
|
|
||||||
"""Override QWebPage::extension to provide error pages.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
ext: The extension.
|
|
||||||
opt: Extension options instance.
|
|
||||||
out: Extension output instance.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
Handler return value.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
handler = self._extension_handlers[ext]
|
|
||||||
except KeyError:
|
|
||||||
return super().extension(ext, opt, out)
|
|
||||||
return handler(opt, out)
|
|
Loading…
Reference in New Issue
Block a user