Sun, 03 Apr 2016 17:07:25 +0200
Corrected some code style issues.
# -*- coding: utf-8 -*- # Copyright (c) 2008 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the web browser using QWebEngineView. """ from __future__ import unicode_literals try: str = unicode # __IGNORE_EXCEPTION__ except NameError: pass from PyQt5.QtCore import pyqtSignal, QUrl, QFileInfo, Qt, QTimer, QEvent, \ QPoint from PyQt5.QtGui import QDesktopServices, QClipboard, QIcon, \ QContextMenuEvent, QPixmap from PyQt5.QtWidgets import qApp, QStyle, QMenu, QApplication from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage from E5Gui import E5MessageBox from .WebBrowserPage import WebBrowserPage from .Tools.WebIconLoader import WebIconLoader from .Tools import Scripts from . import WebInspector from .Tools.WebBrowserTools import readAllFileContents, pixmapToDataUrl import Preferences import UI.PixmapCache class WebBrowserView(QWebEngineView): """ Class implementing the web browser view widget. @signal sourceChanged(QUrl) emitted after the current URL has changed @signal forwardAvailable(bool) emitted after the current URL has changed @signal backwardAvailable(bool) emitted after the current URL has changed @signal highlighted(str) emitted, when the mouse hovers over a link @signal search(QUrl) emitted, when a search is requested @signal zoomValueChanged(int) emitted to signal a change of the zoom value @signal iconChanged() emitted to signal a changed web site icon """ sourceChanged = pyqtSignal(QUrl) forwardAvailable = pyqtSignal(bool) backwardAvailable = pyqtSignal(bool) highlighted = pyqtSignal(str) search = pyqtSignal(QUrl) zoomValueChanged = pyqtSignal(int) iconChanged = pyqtSignal() ZoomLevels = [ 30, 40, 50, 67, 80, 90, 100, 110, 120, 133, 150, 170, 200, 220, 233, 250, 270, 285, 300, ] ZoomLevelDefault = 100 def __init__(self, mainWindow, parent=None, name=""): """ Constructor @param mainWindow reference to the main window (WebBrowserWindow) @param parent parent widget of this window (QWidget) @param name name of this window (string) """ super(WebBrowserView, self).__init__(parent) self.setObjectName(name) self.__rwhvqt = None self.installEventFilter(self) from WebBrowser.WebBrowserWindow import WebBrowserWindow self.__speedDial = WebBrowserWindow.speedDial() self.__page = WebBrowserPage(self) self.setPage(self.__page) self.__mw = mainWindow self.__isLoading = False self.__progress = 0 self.__siteIconLoader = None self.__siteIcon = QIcon() self.__menu = QMenu(self) self.__clickedPos = QPoint() self.__firstLoad = False self.__preview = QPixmap() self.__currentZoom = 100 self.__zoomLevels = WebBrowserView.ZoomLevels[:] self.iconUrlChanged.connect(self.__iconUrlChanged) self.urlChanged.connect(self.__urlChanged) self.page().linkHovered.connect(self.__linkHovered) self.loadStarted.connect(self.__loadStarted) self.loadProgress.connect(self.__loadProgress) self.loadFinished.connect(self.__loadFinished) self.renderProcessTerminated.connect(self.__renderProcessTerminated) self.__mw.openSearchManager().currentEngineChanged.connect( self.__currentEngineChanged) self.setAcceptDrops(True) self.__rss = [] self.__clickedFrame = None self.__mw.personalInformationManager().connectPage(self.page()) self.__inspector = None WebInspector.registerView(self) self.grabGesture(Qt.PinchGesture) def __currentEngineChanged(self): """ Private slot to track a change of the current search engine. """ if self.url().toString() == "eric:home": self.reload() def mainWindow(self): """ Public method to get a reference to the main window. @return reference to the main window @rtype WebBrowserWindow """ return self.__mw def load(self, url): """ Public method to load a web site. @param url URL to be loaded @type QUrl """ super(WebBrowserView, self).load(url) if not self.__firstLoad: self.__firstLoad = True WebInspector.pushView(self) def setSource(self, name, newTab=False): """ Public method used to set the source to be displayed. @param name filename to be shown (QUrl) @param newTab flag indicating to open the URL in a new tab (bool) """ if name is None or not name.isValid(): return if newTab: # open in a new tab self.__mw.newTab(name) return if not name.scheme(): name.setUrl(Preferences.getWebBrowser("DefaultScheme") + name.toString()) if len(name.scheme()) == 1 or \ name.scheme() == "file": # name is a local file if name.scheme() and len(name.scheme()) == 1: # it is a local path on win os name = QUrl.fromLocalFile(name.toString()) if not QFileInfo(name.toLocalFile()).exists(): E5MessageBox.critical( self, self.tr("eric6 Web Browser"), self.tr( """<p>The file <b>{0}</b> does not exist.</p>""") .format(name.toLocalFile())) return if name.toLocalFile().endswith(".pdf") or \ name.toLocalFile().endswith(".PDF") or \ name.toLocalFile().endswith(".chm") or \ name.toLocalFile().endswith(".CHM"): started = QDesktopServices.openUrl(name) if not started: E5MessageBox.critical( self, self.tr("eric6 Web Browser"), self.tr( """<p>Could not start a viewer""" """ for file <b>{0}</b>.</p>""") .format(name.path())) return elif name.scheme() in ["mailto"]: started = QDesktopServices.openUrl(name) if not started: E5MessageBox.critical( self, self.tr("eric6 Web Browser"), self.tr( """<p>Could not start an application""" """ for URL <b>{0}</b>.</p>""") .format(name.toString())) return else: if name.toString().endswith(".pdf") or \ name.toString().endswith(".PDF") or \ name.toString().endswith(".chm") or \ name.toString().endswith(".CHM"): started = QDesktopServices.openUrl(name) if not started: E5MessageBox.critical( self, self.tr("eric6 Web Browser"), self.tr( """<p>Could not start a viewer""" """ for file <b>{0}</b>.</p>""") .format(name.path())) return self.load(name) def source(self): """ Public method to return the URL of the loaded page. @return URL loaded in the help browser (QUrl) """ return self.url() def documentTitle(self): """ Public method to return the title of the loaded page. @return title (string) """ return self.title() def backward(self): """ Public slot to move backwards in history. """ self.triggerPageAction(QWebEnginePage.Back) self.__urlChanged(self.history().currentItem().url()) def forward(self): """ Public slot to move forward in history. """ self.triggerPageAction(QWebEnginePage.Forward) self.__urlChanged(self.history().currentItem().url()) def home(self): """ Public slot to move to the first page loaded. """ homeUrl = QUrl(Preferences.getWebBrowser("HomePage")) self.setSource(homeUrl) self.__urlChanged(self.history().currentItem().url()) def reload(self): """ Public slot to reload the current page. """ self.triggerPageAction(QWebEnginePage.Reload) def reloadBypassingCache(self): """ Public slot to reload the current page bypassing the cache. """ self.triggerPageAction(QWebEnginePage.ReloadAndBypassCache) def copy(self): """ Public slot to copy the selected text. """ self.triggerPageAction(QWebEnginePage.Copy) def cut(self): """ Public slot to cut the selected text. """ self.triggerPageAction(QWebEnginePage.Cut) def paste(self): """ Public slot to paste text from the clipboard. """ self.triggerPageAction(QWebEnginePage.Paste) def undo(self): """ Public slot to undo the last edit action. """ self.triggerPageAction(QWebEnginePage.Undo) def redo(self): """ Public slot to redo the last edit action. """ self.triggerPageAction(QWebEnginePage.Redo) def selectAll(self): """ Public slot to select all text. """ self.triggerPageAction(QWebEnginePage.SelectAll) def isForwardAvailable(self): """ Public method to determine, if a forward move in history is possible. @return flag indicating move forward is possible (boolean) """ return self.history().canGoForward() def isBackwardAvailable(self): """ Public method to determine, if a backwards move in history is possible. @return flag indicating move backwards is possible (boolean) """ return self.history().canGoBack() def __levelForZoom(self, zoom): """ Private method determining the zoom level index given a zoom factor. @param zoom zoom factor (integer) @return index of zoom factor (integer) """ try: index = self.__zoomLevels.index(zoom) except ValueError: for index in range(len(self.__zoomLevels)): if zoom <= self.__zoomLevels[index]: break return index def setZoomValue(self, value, saveValue=True): """ Public method to set the zoom value. @param value zoom value (integer) @keyparam saveValue flag indicating to save the zoom value with the zoom manager @type bool """ if value != self.__currentZoom: self.setZoomFactor(value / 100.0) self.__currentZoom = value if saveValue and not self.__mw.isPrivate(): from .ZoomManager import ZoomManager ZoomManager.instance().setZoomValue(self.url(), value) self.zoomValueChanged.emit(value) def zoomValue(self): """ Public method to get the current zoom value. @return zoom value (integer) """ val = self.zoomFactor() * 100 return int(val) def zoomIn(self): """ Public slot to zoom into the page. """ index = self.__levelForZoom(self.__currentZoom) if index < len(self.__zoomLevels) - 1: self.setZoomValue(self.__zoomLevels[index + 1]) def zoomOut(self): """ Public slot to zoom out of the page. """ index = self.__levelForZoom(self.__currentZoom) if index > 0: self.setZoomValue(self.__zoomLevels[index - 1]) def zoomReset(self): """ Public method to reset the zoom factor. """ index = self.__levelForZoom(WebBrowserView.ZoomLevelDefault) self.setZoomValue(self.__zoomLevels[index]) def hasSelection(self): """ Public method to determine, if there is some text selected. @return flag indicating text has been selected (boolean) """ return self.selectedText() != "" def findNextPrev(self, txt, case, backwards, callback): """ Public slot to find the next occurrence of a text. @param txt text to search for (string) @param case flag indicating a case sensitive search (boolean) @param backwards flag indicating a backwards search (boolean) @param callback reference to a function with a bool parameter @type function(bool) or None """ findFlags = QWebEnginePage.FindFlags() if case: findFlags |= QWebEnginePage.FindCaseSensitively if backwards: findFlags |= QWebEnginePage.FindBackward if callback is None: self.findText(txt, findFlags) else: self.findText(txt, findFlags, callback) def contextMenuEvent(self, evt): """ Protected method called to create a context menu. This method is overridden from QWebEngineView. @param evt reference to the context menu event object (QContextMenuEvent) """ pos = evt.pos() reason = evt.reason() QTimer.singleShot( 0, lambda: self._contextMenuEvent(QContextMenuEvent(reason, pos))) # needs to be done this way because contextMenuEvent is blocking # the main loop def _contextMenuEvent(self, evt): """ Protected method called to create a context menu. This method is overridden from QWebEngineView. @param evt reference to the context menu event object (QContextMenuEvent) """ self.__menu.clear() hitTest = self.page().hitTestContent(evt.pos()) self.__createContextMenu(self.__menu, hitTest) if not hitTest.isContentEditable() and not hitTest.isContentSelected(): self.__menu.addSeparator() self.__menu.addAction(self.__mw.adBlockIcon().menuAction()) if Preferences.getWebBrowser("WebInspectorEnabled"): self.__menu.addSeparator() self.__menu.addAction( UI.PixmapCache.getIcon("webInspector.png"), self.tr("Inspect Element..."), self.__webInspector) if not self.__menu.isEmpty(): pos = evt.globalPos() self.__menu.popup(QPoint(pos.x(), pos.y() + 1)) def __createContextMenu(self, menu, hitTest): """ Private method to populate the context menu. @param menu reference to the menu to be populated @type QMenu @param hitTest reference to the hit test object @type WebHitTestResult """ if not hitTest.linkUrl().isEmpty() and \ hitTest.linkUrl().scheme() != "javascript": self.__createLinkContextMenu(menu, hitTest) if not hitTest.imageUrl().isEmpty(): self.__createImageContextMenu(menu, hitTest) if not hitTest.mediaUrl().isEmpty(): self.__createMediaContextMenu(menu, hitTest) if hitTest.isContentEditable(): menu.addAction(self.__mw.undoAct) menu.addAction(self.__mw.redoAct) menu.addSeparator() menu.addAction(self.__mw.cutAct) menu.addAction(self.__mw.copyAct) menu.addAction(self.__mw.pasteAct) menu.addSeparator() self.__mw.personalInformationManager().createSubMenu( menu, self, hitTest) if hitTest.tagName() == "input": menu.addSeparator() act = menu.addAction("") act.setVisible(False) self.__checkForForm(act, hitTest.pos()) if self.selectedText(): self.__createSelectedTextContextMenu(menu, hitTest) if self.__menu.isEmpty(): self.__createPageContextMenu(menu) def __createLinkContextMenu(self, menu, hitTest): """ Private method to populate the context menu for URLs. @param menu reference to the menu to be populated @type QMenu @param hitTest reference to the hit test object @type WebHitTestResult """ if not menu.isEmpty(): menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("openNewTab.png"), self.tr("Open Link in New Tab\tCtrl+LMB"), self.__openLinkInNewTab).setData(hitTest.linkUrl()) menu.addAction( UI.PixmapCache.getIcon("newWindow.png"), self.tr("Open Link in New Window"), self.__openLinkInNewWindow).setData(hitTest.linkUrl()) menu.addAction( UI.PixmapCache.getIcon("privateMode.png"), self.tr("Open Link in New Private Window"), self.__openLinkInNewPrivateWindow).setData(hitTest.linkUrl()) menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("download.png"), self.tr("Save Lin&k"), self.__downloadLink) menu.addAction( UI.PixmapCache.getIcon("bookmark22.png"), self.tr("Bookmark this Link"), self.__bookmarkLink)\ .setData(hitTest.linkUrl()) menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("editCopy.png"), self.tr("Copy Link to Clipboard"), self.__copyLink)\ .setData(hitTest.linkUrl()) menu.addAction( UI.PixmapCache.getIcon("mailSend.png"), self.tr("Send Link"), self.__sendLink).setData(hitTest.linkUrl()) if Preferences.getWebBrowser("VirusTotalEnabled") and \ Preferences.getWebBrowser("VirusTotalServiceKey") != "": menu.addAction( UI.PixmapCache.getIcon("virustotal.png"), self.tr("Scan Link with VirusTotal"), self.__virusTotal).setData(hitTest.linkUrl()) def __createImageContextMenu(self, menu, hitTest): """ Private method to populate the context menu for images. @param menu reference to the menu to be populated @type QMenu @param hitTest reference to the hit test object @type WebHitTestResult """ if not menu.isEmpty(): menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("openNewTab.png"), self.tr("Open Image in New Tab"), self.__openLinkInNewTab).setData(hitTest.imageUrl()) menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("download.png"), self.tr("Save Image"), self.__downloadImage) menu.addAction( self.tr("Copy Image to Clipboard"), self.__copyImage) menu.addAction( UI.PixmapCache.getIcon("editCopy.png"), self.tr("Copy Image Location to Clipboard"), self.__copyLink).setData(hitTest.imageUrl()) menu.addAction( UI.PixmapCache.getIcon("mailSend.png"), self.tr("Send Image Link"), self.__sendLink).setData(hitTest.imageUrl()) menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("adBlockPlus.png"), self.tr("Block Image"), self.__blockImage)\ .setData(hitTest.imageUrl().toString()) if Preferences.getWebBrowser("VirusTotalEnabled") and \ Preferences.getWebBrowser("VirusTotalServiceKey") != "": menu.addAction( UI.PixmapCache.getIcon("virustotal.png"), self.tr("Scan Image with VirusTotal"), self.__virusTotal).setData(hitTest.imageUrl()) def __createMediaContextMenu(self, menu, hitTest): """ Private method to populate the context menu for media elements. @param menu reference to the menu to be populated @type QMenu @param hitTest reference to the hit test object @type WebHitTestResult """ self.__clickedPos = hitTest.pos() if not menu.isEmpty(): menu.addSeparator() if hitTest.mediaPaused(): menu.addAction( UI.PixmapCache.getIcon("mediaPlaybackStart.png"), self.tr("Play"), self.__pauseMedia) else: menu.addAction( UI.PixmapCache.getIcon("mediaPlaybackPause.png"), self.tr("Pause"), self.__pauseMedia) if hitTest.mediaMuted(): menu.addAction( UI.PixmapCache.getIcon("audioVolumeHigh.png"), self.tr("Unmute"), self.__muteMedia) else: menu.addAction( UI.PixmapCache.getIcon("audioVolumeMuted.png"), self.tr("Mute"), self.__muteMedia) menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("editCopy.png"), self.tr("Copy Media Address to Clipboard"), self.__copyLink).setData(hitTest.mediaUrl()) menu.addAction( UI.PixmapCache.getIcon("mailSend.png"), self.tr("Send Media Address"), self.__sendLink)\ .setData(hitTest.mediaUrl()) menu.addAction( UI.PixmapCache.getIcon("download.png"), self.tr("Save Media"), self.__downloadMedia) def __createSelectedTextContextMenu(self, menu, hitTest): """ Private method to populate the context menu for selected text. @param menu reference to the menu to be populated @type QMenu @param hitTest reference to the hit test object @type WebHitTestResult """ if not menu.isEmpty(): menu.addSeparator() menu.addAction(self.__mw.copyAct) menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("mailSend.png"), self.tr("Send Text"), self.__sendLink).setData(self.selectedText()) engineName = self.__mw.openSearchManager().currentEngineName() if engineName: menu.addAction(self.tr("Search with '{0}'").format(engineName), self.__searchDefaultRequested) from .OpenSearch.OpenSearchEngineAction import \ OpenSearchEngineAction self.__searchMenu = menu.addMenu(self.tr("Search with...")) engineNames = self.__mw.openSearchManager().allEnginesNames() for engineName in engineNames: engine = self.__mw.openSearchManager().engine(engineName) act = OpenSearchEngineAction(engine, self.__searchMenu) act.setData(engineName) self.__searchMenu.addAction(act) self.__searchMenu.triggered.connect(self.__searchRequested) menu.addSeparator() from .WebBrowserLanguagesDialog import WebBrowserLanguagesDialog languages = Preferences.toList( Preferences.Prefs.settings.value( "WebBrowser/AcceptLanguages", WebBrowserLanguagesDialog.defaultAcceptLanguages())) if languages: language = languages[0] langCode = language.split("[")[1][:2] googleTranslatorUrl = QUrl( "http://translate.google.com/#auto|{0}|{1}".format( langCode, self.selectedText())) menu.addAction( UI.PixmapCache.getIcon("translate.png"), self.tr("Google Translate"), self.__openLinkInNewTab)\ .setData(googleTranslatorUrl) wiktionaryUrl = QUrl( "http://{0}.wiktionary.org/wiki/Special:Search?search={1}" .format(langCode, self.selectedText())) menu.addAction( UI.PixmapCache.getIcon("wikipedia.png"), self.tr("Dictionary"), self.__openLinkInNewTab)\ .setData(wiktionaryUrl) menu.addSeparator() guessedUrl = QUrl.fromUserInput(self.selectedText().strip()) if self.__isUrlValid(guessedUrl): menu.addAction( self.tr("Go to web address"), self.__openLinkInNewTab).setData(guessedUrl) def __createPageContextMenu(self, menu): """ Private method to populate the basic context menu. @param menu reference to the menu to be populated @type QMenu """ menu.addAction(self.__mw.newTabAct) menu.addAction(self.__mw.newAct) menu.addSeparator() # TODO: Qt 5.7: Save ## menu.addAction(self.__mw.saveAsAct) ## menu.addSeparator() if self.url().toString() == "eric:speeddial": # special menu for the spedd dial page menu.addAction(self.__mw.backAct) menu.addAction(self.__mw.forwardAct) menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("plus.png"), self.tr("Add New Page"), self.__addSpeedDial) menu.addAction( UI.PixmapCache.getIcon("preferences-general.png"), self.tr("Configure Speed Dial"), self.__configureSpeedDial) menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("reload.png"), self.tr("Reload All Dials"), self.__reloadAllSpeedDials) return menu.addAction( UI.PixmapCache.getIcon("bookmark22.png"), self.tr("Bookmark this Page"), self.addBookmark) menu.addAction( UI.PixmapCache.getIcon("editCopy.png"), self.tr("Copy Page Link"), self.__copyLink).setData(self.url()) menu.addAction( UI.PixmapCache.getIcon("mailSend.png"), self.tr("Send Page Link"), self.__sendLink).setData(self.url()) menu.addSeparator() from .UserAgent.UserAgentMenu import UserAgentMenu self.__userAgentMenu = UserAgentMenu(self.tr("User Agent"), url=self.url()) menu.addMenu(self.__userAgentMenu) menu.addSeparator() menu.addAction(self.__mw.backAct) menu.addAction(self.__mw.forwardAct) menu.addAction(self.__mw.homeAct) menu.addAction(self.__mw.reloadAct) menu.addAction(self.__mw.stopAct) menu.addSeparator() menu.addAction(self.__mw.zoomInAct) menu.addAction(self.__mw.zoomResetAct) menu.addAction(self.__mw.zoomOutAct) menu.addSeparator() menu.addAction(self.__mw.selectAllAct) menu.addSeparator() menu.addAction(self.__mw.findAct) menu.addSeparator() menu.addAction(self.__mw.pageSourceAct) menu.addSeparator() menu.addAction(self.__mw.siteInfoAct) if self.url().scheme() in ["http", "https"]: menu.addSeparator() w3url = QUrl.fromEncoded( b"http://validator.w3.org/check?uri=" + QUrl.toPercentEncoding(bytes(self.url().toEncoded()).decode())) menu.addAction( UI.PixmapCache.getIcon("w3.png"), self.tr("Validate Page"), self.__openLinkInNewTab)\ .setData(w3url) from .WebBrowserLanguagesDialog import WebBrowserLanguagesDialog languages = Preferences.toList( Preferences.Prefs.settings.value( "WebBrowser/AcceptLanguages", WebBrowserLanguagesDialog.defaultAcceptLanguages())) if languages: language = languages[0] langCode = language.split("[")[1][:2] googleTranslatorUrl = QUrl.fromEncoded( b"http://translate.google.com/translate?sl=auto&tl=" + langCode.encode() + b"&u=" + QUrl.toPercentEncoding( bytes(self.url().toEncoded()).decode())) menu.addAction( UI.PixmapCache.getIcon("translate.png"), self.tr("Google Translate"), self.__openLinkInNewTab)\ .setData(googleTranslatorUrl) def __checkForForm(self, act, pos): """ Private method to check the given position for an open search form. @param act reference to the action to be populated upon success @type QAction @param pos position to be tested @type QPoint """ self.__clickedPos = pos from .Tools import Scripts script = Scripts.getFormData(pos) self.page().runJavaScript( script, lambda res: self.__checkForFormCallback(res, act)) def __checkForFormCallback(self, res, act): """ Private method handling the __checkForForm result. @param res result dictionary generated by JavaScript @type dict @param act reference to the action to be populated upon success @type QAction """ if act is None or not bool(res): return url = QUrl(res["action"]) method = res["method"] if not url.isEmpty() and method in ["get", "post"]: act.setVisible(True) act.setText(self.tr("Add to web search toolbar")) act.triggered.connect(self.__addSearchEngine) def __isUrlValid(self, url): """ Private method to check a URL for validity. @param url URL to be checked (QUrl) @return flag indicating a valid URL (boolean) """ return url.isValid() and \ bool(url.host()) and \ bool(url.scheme()) and \ "." in url.host() def __openLinkInNewTab(self): """ Private method called by the context menu to open a link in a new tab. """ act = self.sender() url = act.data() if url.isEmpty(): return self.setSource(url, newTab=True) def __openLinkInNewWindow(self): """ Private slot called by the context menu to open a link in a new window. """ act = self.sender() url = act.data() if url.isEmpty(): return self.__mw.newWindow(url) def __openLinkInNewPrivateWindow(self): """ Private slot called by the context menu to open a link in a new private window. """ act = self.sender() url = act.data() if url.isEmpty(): return self.__mw.newPrivateWindow(url) def __bookmarkLink(self): """ Private slot to bookmark a link via the context menu. """ act = self.sender() url = act.data() if url.isEmpty(): return from .Bookmarks.AddBookmarkDialog import AddBookmarkDialog dlg = AddBookmarkDialog() dlg.setUrl(bytes(url.toEncoded()).decode()) dlg.exec_() def __sendLink(self): """ Private slot to send a link via email. """ act = self.sender() data = act.data() if isinstance(data, QUrl) and data.isEmpty(): return if isinstance(data, QUrl): data = data.toString() QDesktopServices.openUrl(QUrl("mailto:?body=" + data)) def __copyLink(self): """ Private slot to copy a link to the clipboard. """ act = self.sender() data = act.data() if isinstance(data, QUrl) and data.isEmpty(): return if isinstance(data, QUrl): data = data.toString() QApplication.clipboard().setText(data) def __downloadLink(self): """ Private slot to download a link and save it to disk. """ self.triggerPageAction(QWebEnginePage.DownloadLinkToDisk) def __downloadImage(self): """ Private slot to download an image and save it to disk. """ self.triggerPageAction(QWebEnginePage.DownloadImageToDisk) def __copyImage(self): """ Private slot to copy an image to the clipboard. """ self.triggerPageAction(QWebEnginePage.CopyImageToClipboard) def __blockImage(self): """ Private slot to add a block rule for an image URL. """ from WebBrowser.WebBrowserWindow import WebBrowserWindow act = self.sender() url = act.data() dlg = WebBrowserWindow.adBlockManager().showDialog() dlg.addCustomRule(url) def __downloadMedia(self): """ Private slot to download a media and save it to disk. """ self.triggerPageAction(QWebEnginePage.DownloadMediaToDisk) def __pauseMedia(self): """ Private slot to pause or play the selected media. """ self.triggerPageAction(QWebEnginePage.ToggleMediaPlayPause) def __muteMedia(self): """ Private slot to (un)mute the selected media. """ self.triggerPageAction(QWebEnginePage.ToggleMediaMute) def __virusTotal(self): """ Private slot to scan the selected URL with VirusTotal. """ act = self.sender() url = act.data() self.__mw.requestVirusTotalScan(url) def __searchDefaultRequested(self): """ Private slot to search for some text with the current search engine. """ searchText = self.selectedText() if not searchText: return engine = self.__mw.openSearchManager().currentEngine() if engine: self.search.emit(engine.searchUrl(searchText)) def __searchRequested(self, act): """ Private slot to search for some text with a selected search engine. @param act reference to the action that triggered this slot (QAction) """ searchText = self.selectedText() if not searchText: return engineName = act.data() if engineName: engine = self.__mw.openSearchManager().engine(engineName) else: engine = self.__mw.openSearchManager().currentEngine() if engine: self.search.emit(engine.searchUrl(searchText)) def __addSearchEngine(self): """ Private slot to add a new search engine. """ from .Tools import Scripts script = Scripts.getFormData(self.__clickedPos) self.page().runJavaScript( script, lambda res: self.__mw.openSearchManager().addEngineFromForm( res, self)) def __webInspector(self): """ Private slot to show the web inspector window. """ if self.__inspector is None: from .WebInspector import WebInspector self.__inspector = WebInspector() self.__inspector.setView(self, True) self.__inspector.show() else: self.closeWebInspector() def closeWebInspector(self): """ Public slot to close the web inspector. """ if self.__inspector is not None: if self.__inspector.isVisible(): self.__inspector.hide() WebInspector.unregisterView(self.__inspector) self.__inspector.deleteLater() self.__inspector = None def addBookmark(self): """ Public slot to bookmark the current page. """ from .Tools import Scripts script = Scripts.getAllMetaAttributes() self.page().runJavaScript( script, self.__addBookmarkCallback) def __addBookmarkCallback(self, res): """ Private callback method of __addBookmark(). @param res reference to the result list containing all meta attributes @type list """ description = "" for meta in res: if meta["name"] == "description": description = meta["content"] from .Bookmarks.AddBookmarkDialog import AddBookmarkDialog dlg = AddBookmarkDialog() dlg.setUrl(bytes(self.url().toEncoded()).decode()) dlg.setTitle(self.title()) dlg.setDescription(description) dlg.exec_() def dragEnterEvent(self, evt): """ Protected method called by a drag enter event. @param evt reference to the drag enter event (QDragEnterEvent) """ evt.acceptProposedAction() def dragMoveEvent(self, evt): """ Protected method called by a drag move event. @param evt reference to the drag move event (QDragMoveEvent) """ evt.ignore() if evt.source() != self: if len(evt.mimeData().urls()) > 0: evt.acceptProposedAction() else: url = QUrl(evt.mimeData().text()) if url.isValid(): evt.acceptProposedAction() if not evt.isAccepted(): super(WebBrowserView, self).dragMoveEvent(evt) def dropEvent(self, evt): """ Protected method called by a drop event. @param evt reference to the drop event (QDropEvent) """ super(WebBrowserView, self).dropEvent(evt) if not evt.isAccepted() and \ evt.source() != self and \ evt.possibleActions() & Qt.CopyAction: url = QUrl() if len(evt.mimeData().urls()) > 0: url = evt.mimeData().urls()[0] if not url.isValid(): url = QUrl(evt.mimeData().text()) if url.isValid(): self.setSource(url) evt.acceptProposedAction() def _mousePressEvent(self, evt): """ Protected method called by a mouse press event. @param evt reference to the mouse event (QMouseEvent) """ self.__mw.setEventMouseButtons(evt.buttons()) self.__mw.setEventKeyboardModifiers(evt.modifiers()) if evt.button() == Qt.XButton1: self.pageAction(QWebEnginePage.Back).trigger() elif evt.button() == Qt.XButton2: self.pageAction(QWebEnginePage.Forward).trigger() else: super(WebBrowserView, self).mousePressEvent(evt) def _mouseReleaseEvent(self, evt): """ Protected method called by a mouse release event. @param evt reference to the mouse event (QMouseEvent) """ accepted = evt.isAccepted() self.__page.event(evt) if not evt.isAccepted() and \ self.__mw.eventMouseButtons() & Qt.MidButton: url = QUrl(QApplication.clipboard().text(QClipboard.Selection)) if not url.isEmpty() and \ url.isValid() and \ url.scheme() != "": self.__mw.setEventMouseButtons(Qt.NoButton) self.__mw.setEventKeyboardModifiers(Qt.NoModifier) self.setSource(url) evt.setAccepted(accepted) def _wheelEvent(self, evt): """ Protected method to handle wheel events. @param evt reference to the wheel event (QWheelEvent) """ delta = evt.angleDelta().y() if evt.modifiers() & Qt.ControlModifier: if delta < 0: self.zoomOut() else: self.zoomIn() evt.accept() return if evt.modifiers() & Qt.ShiftModifier: if delta < 0: self.backward() else: self.forward() evt.accept() return super(WebBrowserView, self).wheelEvent(evt) def _keyPressEvent(self, evt): """ Protected method called by a key press. @param evt reference to the key event (QKeyEvent) """ if self.__mw.personalInformationManager().viewKeyPressEvent(self, evt): evt.accept() return if evt.key() == Qt.Key_Escape: if self.isFullScreen(): self.triggerPageAction(QWebEnginePage.ExitFullScreen) evt.accept() return super(WebBrowserView, self).keyPressEvent(evt) def _keyReleaseEvent(self, evt): """ Protected method called by a key release. @param evt reference to the key event (QKeyEvent) """ super(WebBrowserView, self).keyReleaseEvent(evt) def focusOutEvent(self, evt): """ Protected method called by a focus out event. @param evt reference to the focus event (QFocusEvent) """ super(WebBrowserView, self).focusOutEvent(evt) # TODO: Gestures: Obsoleted by eventFilter() (?) def event(self, evt): """ Public method handling events. @param evt reference to the event (QEvent) @return flag indicating, if the event was handled (boolean) """ if evt.type() == QEvent.Gesture: self._gestureEvent(evt) return True return super(WebBrowserView, self).event(evt) def _gestureEvent(self, evt): """ Protected method handling gesture events. @param evt reference to the gesture event (QGestureEvent """ pinch = evt.gesture(Qt.PinchGesture) if pinch: if pinch.state() == Qt.GestureStarted: pinch.setScaleFactor(self.__currentZoom / 100.0) else: scaleFactor = pinch.scaleFactor() self.setZoomValue(int(scaleFactor * 100)) evt.accept() def eventFilter(self, obj, evt): """ Public method to process event for other objects. @param obj reference to object to process events for @type QObject @param evt reference to event to be processed @type QEvent @return flag indicating that the event should be filtered out @rtype bool """ # find the render widget receiving events for the web page if obj is self and evt.type() == QEvent.ChildAdded: child = evt.child() if child and child.inherits( "QtWebEngineCore::RenderWidgetHostViewQtDelegateWidget"): self.__rwhvqt = child self.grabGesture(Qt.PinchGesture) self.__rwhvqt.grabGesture(Qt.PinchGesture) self.__rwhvqt.installEventFilter(self) # forward events to WebBrowserView if obj is self.__rwhvqt: wasAccepted = evt.isAccepted() evt.setAccepted(False) if evt.type() == QEvent.KeyPress: self._keyPressEvent(evt) elif evt.type() == QEvent.KeyRelease: self._keyReleaseEvent(evt) elif evt.type() == QEvent.MouseButtonPress: self._mousePressEvent(evt) elif evt.type() == QEvent.MouseButtonRelease: self._mouseReleaseEvent(evt) elif evt.type() == QEvent.Wheel: self._wheelEvent(evt) elif evt.type() == QEvent.Gesture: self._gestureEvent(evt) ret = evt.isAccepted() evt.setAccepted(wasAccepted) return ret # block already handled events if obj is self: if evt.type() in [QEvent.KeyPress, QEvent.KeyRelease, QEvent.MouseButtonPress, QEvent.MouseButtonRelease, QEvent.Wheel, QEvent.Gesture]: return True return super(WebBrowserView, self).eventFilter(obj, evt) def clearHistory(self): """ Public slot to clear the history. """ self.history().clear() self.__urlChanged(self.history().currentItem().url()) ########################################################################### ## Signal converters below ########################################################################### def __urlChanged(self, url): """ Private slot to handle the urlChanged signal. @param url the new url (QUrl) """ self.sourceChanged.emit(url) self.forwardAvailable.emit(self.isForwardAvailable()) self.backwardAvailable.emit(self.isBackwardAvailable()) def __iconUrlChanged(self, url): """ Private slot to handle the iconUrlChanged signal. @param url URL to get web site icon from @type QUrl """ self.__siteIcon = QIcon() if self.__siteIconLoader is not None: self.__siteIconLoader.deleteLater() self.__siteIconLoader = WebIconLoader(url, self) self.__siteIconLoader.iconLoaded.connect(self.__iconLoaded) def __iconLoaded(self, icon): """ Private slot handling the loaded web site icon. @param icon web site icon @type QIcon """ self.__siteIcon = icon from .Tools import WebIconProvider WebIconProvider.instance().saveIcon(self) self.iconChanged.emit() def icon(self): """ Public method to get the web site icon. @return web site icon @rtype QIcon """ if not self.__siteIcon.isNull(): return QIcon(self.__siteIcon) from .Tools import WebIconProvider return WebIconProvider.instance().iconForUrl(self.url()) def __linkHovered(self, link): """ Private slot to handle the linkHovered signal. @param link the URL of the link (string) """ self.highlighted.emit(link) ########################################################################### ## Signal handlers below ########################################################################### def __renderProcessTerminated(self, status, exitCode): """ Private slot handling a crash of the web page render process. @param status termination status @type QWebEnginePage.RenderProcessTerminationStatus @param exitCode exit code of the process @type int """ if status == QWebEnginePage.NormalTerminationStatus: return QTimer.singleShot(0, self.__showTabCrashPage) def __showTabCrashPage(self): """ Private slot to show the tab crash page. """ html = readAllFileContents(":/html/tabCrashPage.html") html = html.replace("@IMAGE@", pixmapToDataUrl( qApp.style().standardIcon(QStyle.SP_MessageBoxWarning).pixmap( 48, 48)).toString()) html = html.replace("@FAVICON@", pixmapToDataUrl( qApp.style() .standardIcon(QStyle.SP_MessageBoxWarning).pixmap( 16, 16)).toString()) html = html.replace("@TITLE@", self.tr("Failed loading page")) html = html.replace("@H1@", self.tr("Failed loading page")) html = html.replace( "@LI-1@", self.tr("Something went wrong while loading this page.")) html = html.replace( "@LI-2@", self.tr( "Try reloading the page or closing some tabs to make more" " memory available.")) html = html.replace( "@BUTTON@", self.tr("Reload Page")) self.page().setHtml(html, self.url()) def __loadStarted(self): """ Private method to handle the loadStarted signal. """ self.__isLoading = True self.__progress = 0 def __loadProgress(self, progress): """ Private method to handle the loadProgress signal. @param progress progress value (integer) """ self.__progress = progress def __loadFinished(self, ok): """ Private method to handle the loadFinished signal. @param ok flag indicating the result (boolean) """ self.__isLoading = False self.__progress = 0 QTimer.singleShot(200, self.__renderPreview) from .ZoomManager import ZoomManager zoomValue = ZoomManager.instance().zoomValue(self.url()) self.setZoomValue(zoomValue) if ok: self.__mw.historyManager().addHistoryEntry(self) self.__mw.adBlockManager().page().hideBlockedPageEntries( self.page()) self.__mw.passwordManager().completePage(self.page()) def isLoading(self): """ Public method to get the loading state. @return flag indicating the loading state (boolean) """ return self.__isLoading def progress(self): """ Public method to get the load progress. @return load progress (integer) """ return self.__progress def __renderPreview(self): """ Private slot to render a preview pixmap after the page was loaded. """ from .WebBrowserSnap import renderTabPreview w = 600 # some default width, the preview gets scaled when shown h = int(w * self.height() / self.width()) self.__preview = renderTabPreview(self, w, h) def getPreview(self): """ Public method to get the preview pixmap. @return preview pixmap @rtype QPixmap """ return self.__preview # TODO: Qt 5.7: Save ## def saveAs(self): ## """ ## Public method to save the current page to a file. ## """ ## url = self.url() ## if url.isEmpty(): ## return ## ## self.__mw.downloadManager().download(url, True, mainWindow=self.__mw) ########################################################################### ## Miscellaneous methods below ########################################################################### def createWindow(self, windowType): """ Public method called, when a new window should be created. @param windowType type of the requested window (QWebEnginePage.WebWindowType) @return reference to the created browser window (WebBrowserView) """ self.__mw.newTab(addNextTo=self) return self.__mw.currentBrowser() def preferencesChanged(self): """ Public method to indicate a change of the settings. """ self.reload() ########################################################################### ## RSS related methods below ########################################################################### def checkRSS(self): """ Public method to check, if the loaded page contains feed links. @return flag indicating the existence of feed links (boolean) """ self.__rss = [] script = Scripts.getFeedLinks() feeds = self.page().execJavaScript(script) if feeds is not None: for feed in feeds: if feed["url"] and feed["title"]: self.__rss.append((feed["title"], feed["url"])) return len(self.__rss) > 0 def getRSS(self): """ Public method to get the extracted RSS feeds. @return list of RSS feeds (list of tuples of two strings) """ return self.__rss def hasRSS(self): """ Public method to check, if the loaded page has RSS links. @return flag indicating the presence of RSS links (boolean) """ return len(self.__rss) > 0 ########################################################################### ## Full Screen handling below ########################################################################### def isFullScreen(self): """ Public method to check, if full screen mode is active. @return flag indicating full screen mode @rtype bool """ return self.__mw.isFullScreen() def requestFullScreen(self, enable): """ Public method to request full screen mode. @param enable flag indicating full screen mode on or off @type bool """ if enable: self.__mw.enterHtmlFullScreen() else: self.__mw.showNormal() ########################################################################### ## Speed Dial slots below ########################################################################### def __addSpeedDial(self): """ Private slot to add a new speed dial. """ self.__page.runJavaScript("addSpeedDial();") def __configureSpeedDial(self): """ Private slot to configure the speed dial. """ self.page().runJavaScript("configureSpeedDial();") def __reloadAllSpeedDials(self): """ Private slot to reload all speed dials. """ self.page().runJavaScript("reloadAll();")