--- a/src/eric7/WebBrowser/WebBrowserView.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/WebBrowser/WebBrowserView.py Wed Jul 13 14:55:47 2022 +0200 @@ -14,11 +14,27 @@ import pathlib from PyQt6.QtCore import ( - pyqtSignal, pyqtSlot, Qt, QUrl, QTimer, QEvent, QPoint, QPointF, QDateTime, - QStandardPaths, QByteArray, QIODevice, QDataStream + pyqtSignal, + pyqtSlot, + Qt, + QUrl, + QTimer, + QEvent, + QPoint, + QPointF, + QDateTime, + QStandardPaths, + QByteArray, + QIODevice, + QDataStream, ) from PyQt6.QtGui import ( - QDesktopServices, QClipboard, QIcon, QContextMenuEvent, QPixmap, QCursor + QDesktopServices, + QClipboard, + QIcon, + QContextMenuEvent, + QPixmap, + QCursor, ) from PyQt6.QtWidgets import QStyle, QMenu, QApplication, QDialog from PyQt6.QtWebEngineCore import QWebEnginePage, QWebEngineDownloadRequest @@ -44,7 +60,7 @@ 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 @@ -59,6 +75,7 @@ @signal showMessage(str) emitted to show a message in the main window status bar """ + sourceChanged = pyqtSignal(QUrl) forwardAvailable = pyqtSignal(bool) backwardAvailable = pyqtSignal(bool) @@ -69,33 +86,49 @@ safeBrowsingAbort = pyqtSignal() safeBrowsingBad = pyqtSignal(str, str) showMessage = pyqtSignal(str) - + ZoomLevels = [ - 30, 40, 50, 67, 80, 90, + 30, + 40, + 50, + 67, + 80, + 90, 100, - 110, 120, 133, 150, 170, 200, 220, 233, 250, 270, 285, 300, + 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().__init__(parent) self.setObjectName(name) - + self.__rwhvqt = None self.installEventFilter(self) - + self.__speedDial = WebBrowserWindow.speedDial() - + self.__page = None self.__createNewPage() - + self.__mw = mainWindow self.__tabWidget = parent self.__isLoading = False @@ -106,57 +139,57 @@ 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.__currentEngineChanged + ) + self.setAcceptDrops(True) - + self.__rss = [] - + self.__clickedFrame = None - + self.__mw.personalInformationManager().connectPage(self.page()) - + self.__inspector = None WebInspector.registerView(self) - + self.__restoreData = None - + if self.parentWidget() is not None: self.parentWidget().installEventFilter(self) - + self.grabGesture(Qt.GestureType.PinchGesture) - + def __createNewPage(self): """ Private method to create a new page object. """ self.__page = WebBrowserPage(self, self) self.setPage(self.__page) - + self.__page.safeBrowsingAbort.connect(self.safeBrowsingAbort) self.__page.safeBrowsingBad.connect(self.safeBrowsingBad) self.__page.printPageRequested.connect(self.__printPage) self.__page.quotaRequested.connect(self.__quotaRequested) # The registerProtocolHandlerRequested signal is handled in # WebBrowserPage. - self.__page.selectClientCertificate.connect( - self.__selectClientCertificate) + self.__page.selectClientCertificate.connect(self.__selectClientCertificate) self.__page.findTextFinished.connect(self.__findTextFinished) - + def __setRwhvqt(self): """ Private slot to set widget that receives input events. @@ -167,95 +200,92 @@ self.__rwhvqt.grabGesture(Qt.GestureType.PinchGesture) self.__rwhvqt.installEventFilter(self) else: - print("Focus proxy is null!") # __IGNORE_WARNING_M801__ - + print("Focus proxy is null!") # __IGNORE_WARNING_M801__ + 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 tabWidget(self): """ Public method to get a reference to the tab widget containing this view. - + @return reference to the tab widget @rtype WebBrowserTabWidget """ return self.__tabWidget - + def load(self, url): """ Public method to load a web site. - + @param url URL to be loaded @type QUrl """ - if ( - self.__page is not None and - not self.__page.acceptNavigationRequest( - url, QWebEnginePage.NavigationType.NavigationTypeTyped, True) + if self.__page is not None and not self.__page.acceptNavigationRequest( + url, QWebEnginePage.NavigationType.NavigationTypeTyped, True ): return - + super().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(): if not os.path.exists(name.toString()): name.setScheme(Preferences.getWebBrowser("DefaultScheme")) else: if Utilities.isWindowsPlatform(): - name.setUrl("file:///" + Utilities.fromNativeSeparators( - name.toString())) + name.setUrl( + "file:///" + Utilities.fromNativeSeparators(name.toString()) + ) else: name.setUrl("file://" + name.toString()) - - if ( - len(name.scheme()) == 1 or - name.scheme() == "file" - ): + + 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 pathlib.Path(name.toLocalFile()).exists(): EricMessageBox.critical( self, self.tr("eric Web Browser"), - self.tr( - """<p>The file <b>{0}</b> does not exist.</p>""") - .format(name.toLocalFile())) + self.tr("""<p>The file <b>{0}</b> does not exist.</p>""").format( + name.toLocalFile() + ), + ) return if name.toLocalFile().lower().endswith((".pdf", ".chm")): @@ -266,8 +296,9 @@ self.tr("eric Web Browser"), self.tr( """<p>Could not start a viewer""" - """ for file <b>{0}</b>.</p>""") - .format(name.path())) + """ for file <b>{0}</b>.</p>""" + ).format(name.path()), + ) return elif name.scheme() in ["mailto"]: started = QDesktopServices.openUrl(name) @@ -277,8 +308,9 @@ self.tr("eric Web Browser"), self.tr( """<p>Could not start an application""" - """ for URL <b>{0}</b>.</p>""") - .format(name.toString())) + """ for URL <b>{0}</b>.</p>""" + ).format(name.toString()), + ) return else: if name.toString().lower().endswith((".pdf", ".chm")): @@ -289,42 +321,43 @@ self.tr("eric Web Browser"), self.tr( """<p>Could not start a viewer""" - """ for file <b>{0}</b>.</p>""") - .format(name.path())) + """ 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.WebAction.Back) self.__urlChanged(self.history().currentItem().url()) - + def forward(self): """ Public slot to move forward in history. """ self.triggerPageAction(QWebEnginePage.WebAction.Forward) self.__urlChanged(self.history().currentItem().url()) - + def home(self): """ Public slot to move to the first page loaded. @@ -332,81 +365,81 @@ 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.WebAction.Reload) - + def reloadBypassingCache(self): """ Public slot to reload the current page bypassing the cache. """ self.triggerPageAction(QWebEnginePage.WebAction.ReloadAndBypassCache) - + def copy(self): """ Public slot to copy the selected text. """ self.triggerPageAction(QWebEnginePage.WebAction.Copy) - + def cut(self): """ Public slot to cut the selected text. """ self.triggerPageAction(QWebEnginePage.WebAction.Cut) - + def paste(self): """ Public slot to paste text from the clipboard. """ self.triggerPageAction(QWebEnginePage.WebAction.Paste) - + def undo(self): """ Public slot to undo the last edit action. """ self.triggerPageAction(QWebEnginePage.WebAction.Undo) - + def redo(self): """ Public slot to redo the last edit action. """ self.triggerPageAction(QWebEnginePage.WebAction.Redo) - + def selectAll(self): """ Public slot to select all text. """ self.triggerPageAction(QWebEnginePage.WebAction.SelectAll) - + def unselect(self): """ Public slot to clear the current selection. """ self.triggerPageAction(QWebEnginePage.WebAction.Unselect) - + 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) """ @@ -417,11 +450,11 @@ 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) @param saveValue flag indicating to save the zoom value with the zoom manager @@ -432,18 +465,19 @@ 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. @@ -451,7 +485,7 @@ 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. @@ -459,37 +493,37 @@ 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 mapToViewport(self, pos): """ Public method to map a position to the viewport. - + @param pos position to be mapped @type QPoint @return viewport position @rtype QPoint """ return self.page().mapToViewport(pos) - + 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) @@ -501,71 +535,75 @@ findFlags |= QWebEnginePage.FindFlag.FindCaseSensitively if backwards: findFlags |= QWebEnginePage.FindFlag.FindBackward - + if callback is None: self.findText(txt, findFlags) else: self.findText(txt, findFlags, callback) - + def __findTextFinished(self, result): """ Private slot handling the findTextFinished signal of the web page. - + @param result reference to the QWebEngineFindTextResult object of the last search @type QWebEngineFindTextResult """ - self.showMessage.emit(self.tr("Match {0} of {1}").format( - result.activeMatch(), result.numberOfMatches()) + self.showMessage.emit( + self.tr("Match {0} of {1}").format( + result.activeMatch(), result.numberOfMatches() + ) ) - + 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, functools.partial( - self._contextMenuEvent, QContextMenuEvent(reason, pos))) + 0, functools.partial(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. - + @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.addMenu(self.__mw.adBlockIcon().menu()) - + self.__menu.addSeparator() self.__menu.addAction( UI.PixmapCache.getIcon("webInspector"), - self.tr("Inspect Element..."), self.__webInspector) - + 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 @@ -574,35 +612,36 @@ spellCheckActionCount = 0 contextMenuData = self.lastContextMenuRequest() hitTest.updateWithContextMenuData(contextMenuData) - + if bool(contextMenuData.misspelledWord()): boldFont = menu.font() boldFont.setBold(True) - + for suggestion in contextMenuData.spellCheckerSuggestions(): act = menu.addAction(suggestion) act.setFont(boldFont) act.triggered.connect( - functools.partial(self.__replaceMisspelledWord, act)) - + functools.partial(self.__replaceMisspelledWord, act) + ) + if not bool(menu.actions()): menu.addAction(self.tr("No suggestions")).setEnabled(False) - + menu.addSeparator() spellCheckActionCount = len(menu.actions()) - + if ( - not hitTest.linkUrl().isEmpty() and - hitTest.linkUrl().scheme() != "javascript" + 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(): # check, if only spell checker actions were added if len(menu.actions()) == spellCheckActionCount: @@ -614,24 +653,25 @@ menu.addAction(self.__mw.pasteAct) menu.addSeparator() self.__mw.personalInformationManager().createSubMenu( - menu, self, hitTest) - + 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 @@ -639,63 +679,59 @@ """ if not menu.isEmpty(): menu.addSeparator() - + act = menu.addAction( UI.PixmapCache.getIcon("openNewTab"), - self.tr("Open Link in New Tab\tCtrl+LMB")) + self.tr("Open Link in New Tab\tCtrl+LMB"), + ) act.setData(hitTest.linkUrl()) - act.triggered.connect( - functools.partial(self.__openLinkInNewTab, act)) + act.triggered.connect(functools.partial(self.__openLinkInNewTab, act)) act = menu.addAction( - UI.PixmapCache.getIcon("newWindow"), - self.tr("Open Link in New Window")) + UI.PixmapCache.getIcon("newWindow"), self.tr("Open Link in New Window") + ) act.setData(hitTest.linkUrl()) - act.triggered.connect( - functools.partial(self.__openLinkInNewWindow, act)) + act.triggered.connect(functools.partial(self.__openLinkInNewWindow, act)) act = menu.addAction( UI.PixmapCache.getIcon("privateMode"), - self.tr("Open Link in New Private Window")) + self.tr("Open Link in New Private Window"), + ) act.setData(hitTest.linkUrl()) - act.triggered.connect( - functools.partial(self.__openLinkInNewPrivateWindow, act)) + act.triggered.connect(functools.partial(self.__openLinkInNewPrivateWindow, act)) menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("download"), - self.tr("Save Lin&k"), self.__downloadLink) + self.tr("Save Lin&k"), + self.__downloadLink, + ) act = menu.addAction( - UI.PixmapCache.getIcon("bookmark22"), - self.tr("Bookmark this Link")) + UI.PixmapCache.getIcon("bookmark22"), self.tr("Bookmark this Link") + ) act.setData(hitTest.linkUrl()) - act.triggered.connect( - functools.partial(self.__bookmarkLink, act)) + act.triggered.connect(functools.partial(self.__bookmarkLink, act)) menu.addSeparator() act = menu.addAction( - UI.PixmapCache.getIcon("editCopy"), - self.tr("Copy URL to Clipboard")) + UI.PixmapCache.getIcon("editCopy"), self.tr("Copy URL to Clipboard") + ) act.setData(hitTest.linkUrl()) - act.triggered.connect( - functools.partial(self.__copyLink, act)) - act = menu.addAction( - UI.PixmapCache.getIcon("mailSend"), - self.tr("Send URL")) + act.triggered.connect(functools.partial(self.__copyLink, act)) + act = menu.addAction(UI.PixmapCache.getIcon("mailSend"), self.tr("Send URL")) act.setData(hitTest.linkUrl()) - act.triggered.connect( - functools.partial(self.__sendLink, act)) + act.triggered.connect(functools.partial(self.__sendLink, act)) if ( - Preferences.getWebBrowser("VirusTotalEnabled") and - Preferences.getWebBrowser("VirusTotalServiceKey") != "" + Preferences.getWebBrowser("VirusTotalEnabled") + and Preferences.getWebBrowser("VirusTotalServiceKey") != "" ): act = menu.addAction( UI.PixmapCache.getIcon("virustotal"), - self.tr("Scan Link with VirusTotal")) + self.tr("Scan Link with VirusTotal"), + ) act.setData(hitTest.linkUrl()) - act.triggered.connect( - functools.partial(self.__virusTotal, act)) - + act.triggered.connect(functools.partial(self.__virusTotal, act)) + 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 @@ -703,77 +739,70 @@ """ if not menu.isEmpty(): menu.addSeparator() - + act = menu.addAction( - UI.PixmapCache.getIcon("openNewTab"), - self.tr("Open Image in New Tab")) + UI.PixmapCache.getIcon("openNewTab"), self.tr("Open Image in New Tab") + ) act.setData(hitTest.imageUrl()) - act.triggered.connect( - functools.partial(self.__openLinkInNewTab, act)) + act.triggered.connect(functools.partial(self.__openLinkInNewTab, act)) menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("download"), - self.tr("Save Image"), self.__downloadImage) - menu.addAction( - self.tr("Copy Image to Clipboard"), self.__copyImage) + self.tr("Save Image"), + self.__downloadImage, + ) + menu.addAction(self.tr("Copy Image to Clipboard"), self.__copyImage) act = menu.addAction( - UI.PixmapCache.getIcon("editCopy"), - self.tr("Copy Image URL to Clipboard")) + UI.PixmapCache.getIcon("editCopy"), self.tr("Copy Image URL to Clipboard") + ) act.setData(hitTest.imageUrl()) - act.triggered.connect( - functools.partial(self.__copyLink, act)) + act.triggered.connect(functools.partial(self.__copyLink, act)) act = menu.addAction( - UI.PixmapCache.getIcon("mailSend"), - self.tr("Send Image URL")) + UI.PixmapCache.getIcon("mailSend"), self.tr("Send Image URL") + ) act.setData(hitTest.imageUrl()) - act.triggered.connect( - functools.partial(self.__sendLink, act)) - + act.triggered.connect(functools.partial(self.__sendLink, act)) + if hitTest.imageUrl().scheme() in ["http", "https"]: menu.addSeparator() engine = WebBrowserWindow.imageSearchEngine() searchEngineName = engine.searchEngine() act = menu.addAction( - UI.PixmapCache.getIcon("{0}".format( - searchEngineName.lower())), - self.tr("Search image in {0}").format(searchEngineName)) + UI.PixmapCache.getIcon("{0}".format(searchEngineName.lower())), + self.tr("Search image in {0}").format(searchEngineName), + ) act.setData(engine.getSearchQuery(hitTest.imageUrl())) - act.triggered.connect( - functools.partial(self.__searchImage, act)) - self.__imageSearchMenu = menu.addMenu( - self.tr("Search image with...")) + act.triggered.connect(functools.partial(self.__searchImage, act)) + self.__imageSearchMenu = menu.addMenu(self.tr("Search image with...")) for searchEngineName in engine.searchEngineNames(): act = self.__imageSearchMenu.addAction( - UI.PixmapCache.getIcon("{0}".format( - searchEngineName.lower())), - self.tr("Search image in {0}").format(searchEngineName)) - act.setData(engine.getSearchQuery( - hitTest.imageUrl(), searchEngineName)) - act.triggered.connect( - functools.partial(self.__searchImage, act)) - + UI.PixmapCache.getIcon("{0}".format(searchEngineName.lower())), + self.tr("Search image in {0}").format(searchEngineName), + ) + act.setData(engine.getSearchQuery(hitTest.imageUrl(), searchEngineName)) + act.triggered.connect(functools.partial(self.__searchImage, act)) + menu.addSeparator() act = menu.addAction( - UI.PixmapCache.getIcon("adBlockPlus"), - self.tr("Block Image")) + UI.PixmapCache.getIcon("adBlockPlus"), self.tr("Block Image") + ) act.setData(hitTest.imageUrl().toString()) - act.triggered.connect( - functools.partial(self.__blockImage, act)) + act.triggered.connect(functools.partial(self.__blockImage, act)) if ( - Preferences.getWebBrowser("VirusTotalEnabled") and - Preferences.getWebBrowser("VirusTotalServiceKey") != "" + Preferences.getWebBrowser("VirusTotalEnabled") + and Preferences.getWebBrowser("VirusTotalServiceKey") != "" ): act = menu.addAction( UI.PixmapCache.getIcon("virustotal"), - self.tr("Scan Image with VirusTotal")) + self.tr("Scan Image with VirusTotal"), + ) act.setData(hitTest.imageUrl()) - act.triggered.connect( - functools.partial(self.__virusTotal, act)) - + act.triggered.connect(functools.partial(self.__virusTotal, act)) + 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 @@ -781,44 +810,52 @@ """ if not menu.isEmpty(): menu.addSeparator() - + if hitTest.mediaPaused(): menu.addAction( UI.PixmapCache.getIcon("mediaPlaybackStart"), - self.tr("Play"), self.__pauseMedia) + self.tr("Play"), + self.__pauseMedia, + ) else: menu.addAction( UI.PixmapCache.getIcon("mediaPlaybackPause"), - self.tr("Pause"), self.__pauseMedia) + self.tr("Pause"), + self.__pauseMedia, + ) if hitTest.mediaMuted(): menu.addAction( UI.PixmapCache.getIcon("audioVolumeHigh"), - self.tr("Unmute"), self.__muteMedia) + self.tr("Unmute"), + self.__muteMedia, + ) else: menu.addAction( UI.PixmapCache.getIcon("audioVolumeMuted"), - self.tr("Mute"), self.__muteMedia) + self.tr("Mute"), + self.__muteMedia, + ) menu.addSeparator() act = menu.addAction( - UI.PixmapCache.getIcon("editCopy"), - self.tr("Copy Media URL to Clipboard")) + UI.PixmapCache.getIcon("editCopy"), self.tr("Copy Media URL to Clipboard") + ) act.setData(hitTest.mediaUrl()) - act.triggered.connect( - functools.partial(self.__copyLink, act)) + act.triggered.connect(functools.partial(self.__copyLink, act)) act = menu.addAction( - UI.PixmapCache.getIcon("mailSend"), - self.tr("Send Media URL")) + UI.PixmapCache.getIcon("mailSend"), self.tr("Send Media URL") + ) act.setData(hitTest.mediaUrl()) - act.triggered.connect( - functools.partial(self.__sendLink, act)) + act.triggered.connect(functools.partial(self.__sendLink, act)) menu.addAction( UI.PixmapCache.getIcon("download"), - self.tr("Save Media"), self.__downloadMedia) - + 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 @@ -826,25 +863,22 @@ """ if not menu.isEmpty(): menu.addSeparator() - + menu.addAction(self.__mw.copyAct) menu.addSeparator() - act = menu.addAction( - UI.PixmapCache.getIcon("mailSend"), - self.tr("Send Text")) + act = menu.addAction(UI.PixmapCache.getIcon("mailSend"), self.tr("Send Text")) act.setData(self.selectedText()) - act.triggered.connect( - functools.partial(self.__sendLink, act)) - + act.triggered.connect(functools.partial(self.__sendLink, act)) + engineName = self.__mw.openSearchManager().currentEngineName() if engineName: - menu.addAction(self.tr("Search with '{0}'").format(engineName), - self.__searchDefaultRequested) - - from .OpenSearch.OpenSearchEngineAction import ( - OpenSearchEngineAction - ) - + 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: @@ -853,48 +887,52 @@ act.setData(engineName) self.__searchMenu.addAction(act) self.__searchMenu.triggered.connect(self.__searchRequested) - + menu.addSeparator() - + from .WebBrowserLanguagesDialog import WebBrowserLanguagesDialog + languages = Preferences.toList( Preferences.getSettings().value( "WebBrowser/AcceptLanguages", - WebBrowserLanguagesDialog.defaultAcceptLanguages())) + 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())) + langCode, self.selectedText() + ) + ) act = menu.addAction( - UI.PixmapCache.getIcon("translate"), - self.tr("Google Translate")) + UI.PixmapCache.getIcon("translate"), self.tr("Google Translate") + ) act.setData(googleTranslatorUrl) - act.triggered.connect( - functools.partial(self.__openLinkInNewTab, act)) + act.triggered.connect(functools.partial(self.__openLinkInNewTab, act)) wiktionaryUrl = QUrl( - "http://{0}.wiktionary.org/wiki/Special:Search?search={1}" - .format(langCode, self.selectedText())) + "http://{0}.wiktionary.org/wiki/Special:Search?search={1}".format( + langCode, self.selectedText() + ) + ) act = menu.addAction( - UI.PixmapCache.getIcon("wikipedia"), - self.tr("Dictionary")) + UI.PixmapCache.getIcon("wikipedia"), self.tr("Dictionary") + ) act.setData(wiktionaryUrl) - act.triggered.connect( - functools.partial(self.__openLinkInNewTab, act)) + act.triggered.connect(functools.partial(self.__openLinkInNewTab, act)) menu.addSeparator() - + guessedUrl = QUrl.fromUserInput(self.selectedText().strip()) if self.__isUrlValid(guessedUrl): act = menu.addAction(self.tr("Go to web address")) act.setData(guessedUrl) - act.triggered.connect( - functools.partial(self.__openLinkInNewTab, act)) - + act.triggered.connect(functools.partial(self.__openLinkInNewTab, act)) + def __createPageContextMenu(self, menu): """ Private method to populate the basic context menu. - + @param menu reference to the menu to be populated @type QMenu """ @@ -905,7 +943,7 @@ menu.addAction(self.__mw.saveAsAct) menu.addAction(self.__mw.saveVisiblePageScreenAct) menu.addSeparator() - + if self.url().toString() == "eric:speeddial": # special menu for the spedd dial page menu.addAction(self.__mw.backAct) @@ -913,39 +951,44 @@ menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("plus"), - self.tr("Add New Page"), self.__addSpeedDial) + self.tr("Add New Page"), + self.__addSpeedDial, + ) menu.addAction( UI.PixmapCache.getIcon("preferences-general"), - self.tr("Configure Speed Dial"), self.__configureSpeedDial) + self.tr("Configure Speed Dial"), + self.__configureSpeedDial, + ) menu.addSeparator() menu.addAction( UI.PixmapCache.getIcon("reload"), - self.tr("Reload All Dials"), self.__reloadAllSpeedDials) + self.tr("Reload All Dials"), + self.__reloadAllSpeedDials, + ) menu.addSeparator() - menu.addAction( - self.tr("Reset to Default Dials"), self.__resetSpeedDials) + menu.addAction(self.tr("Reset to Default Dials"), self.__resetSpeedDials) return - + menu.addAction( UI.PixmapCache.getIcon("bookmark22"), - self.tr("Bookmark this Page"), self.addBookmark) + self.tr("Bookmark this Page"), + self.addBookmark, + ) act = menu.addAction( - UI.PixmapCache.getIcon("editCopy"), - self.tr("Copy Page URL to Clipboard")) + UI.PixmapCache.getIcon("editCopy"), self.tr("Copy Page URL to Clipboard") + ) act.setData(self.url()) - act.triggered.connect( - functools.partial(self.__copyLink, act)) + act.triggered.connect(functools.partial(self.__copyLink, act)) act = menu.addAction( - UI.PixmapCache.getIcon("mailSend"), - self.tr("Send Page URL")) + UI.PixmapCache.getIcon("mailSend"), self.tr("Send Page URL") + ) act.setData(self.url()) - act.triggered.connect( - functools.partial(self.__sendLink, act)) + act.triggered.connect(functools.partial(self.__sendLink, act)) menu.addSeparator() - + from .UserAgent.UserAgentMenu import UserAgentMenu - self.__userAgentMenu = UserAgentMenu(self.tr("User Agent"), - url=self.url()) + + self.__userAgentMenu = UserAgentMenu(self.tr("User Agent"), url=self.url()) menu.addMenu(self.__userAgentMenu) menu.addSeparator() menu.addAction(self.__mw.backAct) @@ -967,60 +1010,62 @@ 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())) - act = menu.addAction( - UI.PixmapCache.getIcon("w3"), - self.tr("Validate Page")) + b"http://validator.w3.org/check?uri=" + + QUrl.toPercentEncoding(bytes(self.url().toEncoded()).decode()) + ) + act = menu.addAction(UI.PixmapCache.getIcon("w3"), self.tr("Validate Page")) act.setData(w3url) - act.triggered.connect( - functools.partial(self.__openLinkInNewTab, act)) - + act.triggered.connect(functools.partial(self.__openLinkInNewTab, act)) + from .WebBrowserLanguagesDialog import WebBrowserLanguagesDialog + languages = Preferences.toList( Preferences.getSettings().value( "WebBrowser/AcceptLanguages", - WebBrowserLanguagesDialog.defaultAcceptLanguages())) + 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())) + b"http://translate.google.com/translate?sl=auto&tl=" + + langCode.encode() + + b"&u=" + + QUrl.toPercentEncoding(bytes(self.url().toEncoded()).decode()) + ) act = menu.addAction( - UI.PixmapCache.getIcon("translate"), - self.tr("Google Translate")) + UI.PixmapCache.getIcon("translate"), self.tr("Google Translate") + ) act.setData(googleTranslatorUrl) - act.triggered.connect( - functools.partial(self.__openLinkInNewTab, act)) - + act.triggered.connect(functools.partial(self.__openLinkInNewTab, act)) + 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 = self.mapToViewport(pos) - + from .Tools import Scripts + script = Scripts.getFormData(self.__clickedPos) self.page().runJavaScript( script, WebBrowserPage.SafeJsWorld, - lambda res: self.__checkForFormCallback(res, act)) - + 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 @@ -1028,257 +1073,259 @@ """ 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() + url.isValid() + and bool(url.host()) + and bool(url.scheme()) + and "." in url.host() ) - + def __replaceMisspelledWord(self, act): """ Private slot to replace a misspelled word under the context menu. - + @param act reference to the action that triggered @type QAction """ suggestion = act.text() self.page().replaceMisspelledWord(suggestion) - + def __openLinkInNewTab(self, act): """ Private method called by the context menu to open a link in a new tab. - + @param act reference to the action that triggered @type QAction """ url = act.data() if url.isEmpty(): return - + self.setSource(url, newTab=True) - + def __openLinkInNewWindow(self, act): """ Private slot called by the context menu to open a link in a new window. - + @param act reference to the action that triggered @type QAction """ url = act.data() if url.isEmpty(): return - + self.__mw.newWindow(url) - + def __openLinkInNewPrivateWindow(self, act): """ Private slot called by the context menu to open a link in a new private window. - + @param act reference to the action that triggered @type QAction """ url = act.data() if url.isEmpty(): return - + self.__mw.newPrivateWindow(url) - + def __bookmarkLink(self, act): """ Private slot to bookmark a link via the context menu. - + @param act reference to the action that triggered @type QAction """ 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, act): """ Private slot to send a link via email. - + @param act reference to the action that triggered @type QAction """ 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, act): """ Private slot to copy a link to the clipboard. - + @param act reference to the action that triggered @type QAction """ data = act.data() if isinstance(data, QUrl) and data.isEmpty(): return - + if isinstance(data, QUrl): data = data.toString() - + # copy the URL to both clipboard areas QApplication.clipboard().setText(data, QClipboard.Mode.Clipboard) QApplication.clipboard().setText(data, QClipboard.Mode.Selection) - + def __downloadLink(self): """ Private slot to download a link and save it to disk. """ self.triggerPageAction(QWebEnginePage.WebAction.DownloadLinkToDisk) - + def __downloadImage(self): """ Private slot to download an image and save it to disk. """ self.triggerPageAction(QWebEnginePage.WebAction.DownloadImageToDisk) - + def __copyImage(self): """ Private slot to copy an image to the clipboard. """ self.triggerPageAction(QWebEnginePage.WebAction.CopyImageToClipboard) - + def __blockImage(self, act): """ Private slot to add a block rule for an image URL. - + @param act reference to the action that triggered @type QAction """ url = act.data() dlg = WebBrowserWindow.adBlockManager().showDialog() dlg.addCustomRule(url) - + def __searchImage(self, act): """ Private slot to search for an image URL. - + @param act reference to the action that triggered @type QAction """ url = act.data() self.setSource(url, newTab=True) - + def __downloadMedia(self): """ Private slot to download a media and save it to disk. """ self.triggerPageAction(QWebEnginePage.WebAction.DownloadMediaToDisk) - + def __pauseMedia(self): """ Private slot to pause or play the selected media. """ self.triggerPageAction(QWebEnginePage.WebAction.ToggleMediaPlayPause) - + def __muteMedia(self): """ Private slot to (un)mute the selected media. """ self.triggerPageAction(QWebEnginePage.WebAction.ToggleMediaMute) - + def __virusTotal(self, act): """ Private slot to scan the selected URL with VirusTotal. - + @param act reference to the action that triggered @type QAction """ 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() engine = ( self.__mw.openSearchManager().engine(engineName) - if engineName else - self.__mw.openSearchManager().currentEngine() + if engineName + else 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, WebBrowserPage.SafeJsWorld, - lambda res: self.__mw.openSearchManager().addEngineFromForm( - res, self)) - + lambda res: self.__mw.openSearchManager().addEngineFromForm(res, self), + ) + def __webInspector(self): """ Private slot to show the web inspector window. """ from .WebInspector import WebInspector + if WebInspector.isEnabled(): if self.__inspector is None: self.__inspector = WebInspector() self.__inspector.setView(self, True) - self.__inspector.inspectorClosed.connect( - self.closeWebInspector) + self.__inspector.inspectorClosed.connect(self.closeWebInspector) self.__inspector.show() else: self.closeWebInspector() - + def closeWebInspector(self): """ Public slot to close the web inspector. @@ -1289,20 +1336,22 @@ 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, WebBrowserPage.SafeJsWorld, self.__addBookmarkCallback) - + script, WebBrowserPage.SafeJsWorld, self.__addBookmarkCallback + ) + def __addBookmarkCallback(self, res): """ Private callback method of __addBookmark(). - + @param res reference to the result list containing all meta attributes @type list @@ -1311,26 +1360,27 @@ 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() @@ -1341,21 +1391,21 @@ url = QUrl(evt.mimeData().text()) if url.isValid(): evt.acceptProposedAction() - + if not evt.isAccepted(): super().dragMoveEvent(evt) - + def dropEvent(self, evt): """ Protected method called by a drop event. - + @param evt reference to the drop event (QDropEvent) """ super().dropEvent(evt) if ( - not evt.isAccepted() and - evt.source() != self and - evt.possibleActions() & Qt.DropAction.CopyAction + not evt.isAccepted() + and evt.source() != self + and evt.possibleActions() & Qt.DropAction.CopyAction ): url = QUrl() if len(evt.mimeData().urls()) > 0: @@ -1365,47 +1415,39 @@ 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) """ if WebBrowserWindow.autoScroller().mousePress(self, evt): evt.accept() return - + def _mouseReleaseEvent(self, evt): """ Protected method called by a mouse release event. - + @param evt reference to the mouse event (QMouseEvent) """ if WebBrowserWindow.autoScroller().mouseRelease(evt): evt.accept() return - + accepted = evt.isAccepted() self.__page.event(evt) - if ( - not evt.isAccepted() and - evt.button() == Qt.MouseButton.MiddleButton - ): - url = QUrl(QApplication.clipboard().text( - QClipboard.Mode.Selection)) - if ( - not url.isEmpty() and - url.isValid() and - url.scheme() != "" - ): + if not evt.isAccepted() and evt.button() == Qt.MouseButton.MiddleButton: + url = QUrl(QApplication.clipboard().text(QClipboard.Mode.Selection)) + if not url.isEmpty() and url.isValid() and url.scheme() != "": self.setSource(url) evt.setAccepted(accepted) - + def _mouseMoveEvent(self, evt): """ Protected method to handle mouse move events. - + @param evt reference to the mouse event (QMouseEvent) """ if self.__mw and self.__mw.isFullScreen(): @@ -1414,20 +1456,20 @@ elif evt.y() < 10: # mouse is within 10px to the top self.__mw.showFullScreenNavigation() - + if WebBrowserWindow.autoScroller().mouseMove(evt): evt.accept() - + def _wheelEvent(self, evt): """ Protected method to handle wheel events. - + @param evt reference to the wheel event (QWheelEvent) """ if WebBrowserWindow.autoScroller().wheel(): evt.accept() return - + delta = evt.angleDelta().y() if evt.modifiers() & Qt.KeyboardModifier.ControlModifier: if delta < 0: @@ -1435,24 +1477,24 @@ elif delta > 0: self.zoomIn() evt.accept() - + elif evt.modifiers() & Qt.KeyboardModifier.ShiftModifier: if delta < 0: self.backward() elif delta > 0: self.forward() evt.accept() - + 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.Key_ZoomIn: self.zoomIn() evt.accept() @@ -1482,22 +1524,22 @@ if not hitTest.isContentEditable(): self.pageAction(QWebEnginePage.WebAction.Back).trigger() evt.accept() - + def _keyReleaseEvent(self, evt): """ Protected method called by a key release. - + @param evt reference to the key event (QKeyEvent) """ if evt.key() == Qt.Key.Key_Escape and self.isFullScreen(): self.triggerPageAction(QWebEnginePage.WebAction.ExitFullScreen) evt.accept() self.requestFullScreen(False) - + def _gestureEvent(self, evt): """ Protected method handling gesture events. - + @param evt reference to the gesture event @type QGestureEvent """ @@ -1509,11 +1551,11 @@ scaleFactor = pinch.totalScaleFactor() 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 @@ -1522,27 +1564,26 @@ @rtype bool """ if ( - obj is self and - evt.type() == QEvent.Type.ParentChange and - self.parentWidget() is not None + obj is self + and evt.type() == QEvent.Type.ParentChange + and self.parentWidget() is not None ): self.parentWidget().installEventFilter(self) - + # find the render widget receiving events for the web page if obj is self and evt.type() == QEvent.Type.ChildAdded: QTimer.singleShot(0, self.__setRwhvqt) - + # forward events to WebBrowserView - if ( - obj is self.__rwhvqt and - evt.type() in [QEvent.Type.KeyPress, - QEvent.Type.KeyRelease, - QEvent.Type.MouseButtonPress, - QEvent.Type.MouseButtonRelease, - QEvent.Type.MouseMove, - QEvent.Type.Wheel, - QEvent.Type.Gesture] - ): + if obj is self.__rwhvqt and evt.type() in [ + QEvent.Type.KeyPress, + QEvent.Type.KeyRelease, + QEvent.Type.MouseButtonPress, + QEvent.Type.MouseButtonRelease, + QEvent.Type.MouseMove, + QEvent.Type.Wheel, + QEvent.Type.Gesture, + ]: wasAccepted = evt.isAccepted() evt.setAccepted(False) if evt.type() == QEvent.Type.KeyPress: @@ -1562,11 +1603,11 @@ ret = evt.isAccepted() evt.setAccepted(wasAccepted) return ret - - if ( - obj is self.parentWidget() and - evt.type() in [QEvent.Type.KeyPress, QEvent.Type.KeyRelease] - ): + + if obj is self.parentWidget() and evt.type() in [ + QEvent.Type.KeyPress, + QEvent.Type.KeyRelease, + ]: wasAccepted = evt.isAccepted() evt.setAccepted(False) if evt.type() == QEvent.Type.KeyPress: @@ -1576,71 +1617,73 @@ ret = evt.isAccepted() evt.setAccepted(wasAccepted) return ret - + # block already handled events if obj is self: - if evt.type() in [QEvent.Type.KeyPress, - QEvent.Type.KeyRelease, - QEvent.Type.MouseButtonPress, - QEvent.Type.MouseButtonRelease, - QEvent.Type.MouseMove, - QEvent.Type.Wheel, - QEvent.Type.Gesture]: + if evt.type() in [ + QEvent.Type.KeyPress, + QEvent.Type.KeyRelease, + QEvent.Type.MouseButtonPress, + QEvent.Type.MouseButtonRelease, + QEvent.Type.MouseMove, + QEvent.Type.Wheel, + QEvent.Type.Gesture, + ]: return True - + elif evt.type() == QEvent.Type.Hide and self.isFullScreen(): self.triggerPageAction(QWebEnginePage.WebAction.ExitFullScreen) - + return super().eventFilter(obj, evt) - + 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.Type.Gesture: self._gestureEvent(evt) return True - + return super().event(evt) - + def inputWidget(self): """ Public method to get a reference to the render widget. - + @return reference to the render widget @rtype QWidget """ return self.__rwhvqt - + 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 """ @@ -1651,41 +1694,45 @@ self.__siteIconLoader.iconLoaded.connect(self.__iconLoaded) with contextlib.suppress(AttributeError): self.__siteIconLoader.sslConfiguration.connect( - self.page().setSslConfiguration) + self.page().setSslConfiguration + ) self.__siteIconLoader.clearSslConfiguration.connect( - self.page().clearSslConfiguration) - + self.page().clearSslConfiguration + ) + 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.faviconChanged.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 title(self): """ Public method to get the view title. - + @return view title @rtype str """ @@ -1695,95 +1742,98 @@ url = self.__page.requestedUrl() else: url = self.url() - + titleStr = url.host() if not titleStr: - titleStr = url.toString( - QUrl.UrlFormattingOption.RemoveFragment) - + titleStr = url.toString(QUrl.UrlFormattingOption.RemoveFragment) + if not titleStr or titleStr == "about:blank": titleStr = self.tr("Empty Page") - + return titleStr - + 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.RenderProcessTerminationStatus - .NormalTerminationStatus + status + == QWebEnginePage.RenderProcessTerminationStatus.NormalTerminationStatus ): return - - QTimer.singleShot(0, - functools.partial(self.__showTabCrashPage, status)) - + + QTimer.singleShot(0, functools.partial(self.__showTabCrashPage, status)) + def __showTabCrashPage(self, status): """ Private slot to show the tab crash page. - + @param status termination status @type QWebEnginePage.RenderProcessTerminationStatus """ self.page().deleteLater() self.__createNewPage() - + html = getHtmlPage("tabCrashPage.html") - html = html.replace("@IMAGE@", pixmapToDataUrl( - ericApp().style().standardIcon( - QStyle.StandardPixmap.SP_MessageBoxWarning).pixmap(48, 48) - ).toString()) - html = html.replace("@FAVICON@", pixmapToDataUrl( - ericApp().style() .standardIcon( - QStyle.StandardPixmap.SP_MessageBoxWarning).pixmap(16, 16) - ).toString()) html = html.replace( - "@TITLE@", self.tr("Render Process terminated abnormally")) + "@IMAGE@", + pixmapToDataUrl( + ericApp() + .style() + .standardIcon(QStyle.StandardPixmap.SP_MessageBoxWarning) + .pixmap(48, 48) + ).toString(), + ) html = html.replace( - "@H1@", self.tr("Render Process terminated abnormally")) + "@FAVICON@", + pixmapToDataUrl( + ericApp() + .style() + .standardIcon(QStyle.StandardPixmap.SP_MessageBoxWarning) + .pixmap(16, 16) + ).toString(), + ) + html = html.replace("@TITLE@", self.tr("Render Process terminated abnormally")) + html = html.replace("@H1@", self.tr("Render Process terminated abnormally")) if ( - status == - QWebEnginePage.RenderProcessTerminationStatus - .CrashedTerminationStatus + status + == QWebEnginePage.RenderProcessTerminationStatus.CrashedTerminationStatus ): - msg = self.tr("The render process crashed while" - " loading this page.") + msg = self.tr("The render process crashed while" " loading this page.") elif ( - status == - QWebEnginePage.RenderProcessTerminationStatus - .KilledTerminationStatus + status + == QWebEnginePage.RenderProcessTerminationStatus.KilledTerminationStatus ): msg = self.tr("The render process was killed.") else: - msg = self.tr("The render process terminated while" - " loading this page.") + msg = self.tr("The render process terminated while" " loading this page.") html = html.replace("@LI-1@", msg) html = html.replace( "@LI-2@", self.tr( "Try reloading the page or closing some tabs to make more" - " memory available.")) + " memory available." + ), + ) self.page().setHtml(html, self.url()) - + def __loadStarted(self): """ Private method to handle the loadStarted signal. @@ -1792,45 +1842,47 @@ self.findText("") 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 - + QApplication.processEvents() 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.adBlockManager().page().hideBlockedPageEntries(self.page()) self.__mw.passwordManager().completePage(self.page()) - + self.page().runJavaScript( - "document.lastModified", WebBrowserPage.SafeJsWorld, - lambda res: self.__adjustBookmark(res)) - + "document.lastModified", + WebBrowserPage.SafeJsWorld, + lambda res: self.__adjustBookmark(res), + ) + def __adjustBookmark(self, lastModified): """ Private slot to adjust the 'lastModified' value of bookmarks. - + @param lastModified last modified value @type str """ @@ -1838,45 +1890,46 @@ if modified.isValid(): from WebBrowser.WebBrowserWindow import WebBrowserWindow from .Bookmarks.BookmarkNode import BookmarkNode + manager = WebBrowserWindow.bookmarksManager() for bookmark in manager.bookmarksForUrl(self.url()): - manager.setTimestamp(bookmark, BookmarkNode.TsModified, - modified) - + manager.setTimestamp(bookmark, BookmarkNode.TsModified, modified) + 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 + + 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 - + def saveAs(self): """ Public method to save the current page to a file. @@ -1884,21 +1937,22 @@ url = self.url() if url.isEmpty(): return - + fileName, savePageFormat = self.__getSavePageFileNameAndFormat() if fileName: self.page().save(fileName, savePageFormat) - + def __getSavePageFileNameAndFormat(self): """ Private method to get the file name to save the page to. - + @return tuple containing the file name to save to and the save page format @rtype tuple of (str, QWebEngineDownloadRequest.SavePageFormat) """ documentLocation = QStandardPaths.writableLocation( - QStandardPaths.StandardLocation.DocumentsLocation) + QStandardPaths.StandardLocation.DocumentsLocation + ) filterList = [ self.tr("Web Archive (*.mhtml *.mht)"), self.tr("HTML File (*.html *.htm)"), @@ -1912,44 +1966,33 @@ (".html", ".htm"), ] if self.url().fileName(): - defaultFileName = os.path.join(documentLocation, - self.url().fileName()) + defaultFileName = os.path.join(documentLocation, self.url().fileName()) else: - defaultFileName = os.path.join(documentLocation, - self.page().title()) + defaultFileName = os.path.join(documentLocation, self.page().title()) if Utilities.isWindowsPlatform(): defaultFileName += ".mht" else: defaultFileName += ".mhtml" fileName = "" - saveFormat = ( - QWebEngineDownloadRequest.SavePageFormat.MimeHtmlSaveFormat - ) - + saveFormat = QWebEngineDownloadRequest.SavePageFormat.MimeHtmlSaveFormat + fileName, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( - None, - self.tr("Save Web Page"), - defaultFileName, - ";;".join(filterList), - None) + None, self.tr("Save Web Page"), defaultFileName, ";;".join(filterList), None + ) if fileName: index = filterList.index(selectedFilter) if index == 0: - saveFormat = ( - QWebEngineDownloadRequest.SavePageFormat.MimeHtmlSaveFormat - ) + saveFormat = QWebEngineDownloadRequest.SavePageFormat.MimeHtmlSaveFormat elif index == 1: saveFormat = ( - QWebEngineDownloadRequest.SavePageFormat - .SingleHtmlSaveFormat + QWebEngineDownloadRequest.SavePageFormat.SingleHtmlSaveFormat ) else: saveFormat = ( - QWebEngineDownloadRequest.SavePageFormat - .CompleteHtmlSaveFormat + QWebEngineDownloadRequest.SavePageFormat.CompleteHtmlSaveFormat ) - + extension = os.path.splitext(fileName)[1] if not extension: # add the platform specific default extension @@ -1959,95 +2002,95 @@ extensionsIndex = 0 extensions = extensionsList[index] fileName += extensions[extensionsIndex] - + return fileName, saveFormat - + ########################################################################### ## 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) """ - if windowType in [QWebEnginePage.WebWindowType.WebBrowserTab, - QWebEnginePage.WebWindowType.WebDialog]: + if windowType in [ + QWebEnginePage.WebWindowType.WebBrowserTab, + QWebEnginePage.WebWindowType.WebDialog, + ]: return self.__mw.newTab(addNextTo=self) elif windowType == QWebEnginePage.WebWindowType.WebBrowserWindow: return self.__mw.newWindow().currentBrowser() - elif ( - windowType == QWebEnginePage.WebWindowType.WebBrowserBackgroundTab - ): + elif windowType == QWebEnginePage.WebWindowType.WebBrowserBackgroundTab: return self.__mw.newTab(addNextTo=self, background=True) else: # default for unknow/new window types return self.__mw.newTab(addNextTo=self) - + 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 """ @@ -2055,50 +2098,48 @@ 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();", - WebBrowserPage.SafeJsWorld) - + self.__page.runJavaScript("addSpeedDial();", WebBrowserPage.SafeJsWorld) + def __configureSpeedDial(self): """ Private slot to configure the speed dial. """ - self.page().runJavaScript("configureSpeedDial();", - WebBrowserPage.SafeJsWorld) - + self.page().runJavaScript("configureSpeedDial();", WebBrowserPage.SafeJsWorld) + def __reloadAllSpeedDials(self): """ Private slot to reload all speed dials. """ self.page().runJavaScript("reloadAll();", WebBrowserPage.SafeJsWorld) - + def __resetSpeedDials(self): """ Private slot to reset all speed dials to the default pages. """ self.__speedDial.resetDials() - + ########################################################################### ## Methods below implement session related functions ########################################################################### - + def storeSessionData(self, data): """ Public method to store session data to be restored later on. - + @param data dictionary with session data to be restored @type dict """ self.__restoreData = data - + def __showEventSlot(self): """ Private slot to perform actions when the view is shown and the event @@ -2107,73 +2148,74 @@ if self.__restoreData: sessionData, self.__restoreData = self.__restoreData, None self.loadFromSessionData(sessionData) - + def showEvent(self, evt): """ Protected method to handle show events. - + @param evt reference to the show event object @type QShowEvent """ super().showEvent(evt) self.activateSession() - + def activateSession(self): """ Public slot to activate a restored session. """ if self.__restoreData and not self.__mw.isClosing(): QTimer.singleShot(0, self.__showEventSlot) - + def getSessionData(self): """ Public method to populate the session data. - + @return dictionary containing the session data @rtype dict """ if self.__restoreData: # page has not been shown yet return self.__restoreData - + sessionData = {} page = self.page() - + # 1. zoom factor sessionData["ZoomFactor"] = page.zoomFactor() - + # 2. scroll position scrollPos = page.scrollPosition() sessionData["ScrollPosition"] = { "x": scrollPos.x(), "y": scrollPos.y(), } - + # 3. page history historyArray = QByteArray() stream = QDataStream(historyArray, QIODevice.OpenModeFlag.WriteOnly) stream << page.history() sessionData["History"] = str( historyArray.toBase64(QByteArray.Base64Option.Base64UrlEncoding), - encoding="ascii") + encoding="ascii", + ) sessionData["HistoryIndex"] = page.history().currentItemIndex() - + # 4. current URL and title sessionData["Url"] = self.url().toString() sessionData["Title"] = self.title() - + # 5. web icon iconArray = QByteArray() stream = QDataStream(iconArray, QIODevice.OpenModeFlag.WriteOnly) stream << page.icon() sessionData["Icon"] = str(iconArray.toBase64(), encoding="ascii") - + return sessionData - + def loadFromSessionData(self, sessionData): """ Public method to load the session data. - + @param sessionData dictionary containing the session data as generated by getSessionData() @type dict @@ -2181,34 +2223,35 @@ page = self.page() # blank the page page.setUrl(QUrl("about:blank")) - + # 1. page history if "History" in sessionData: historyArray = QByteArray.fromBase64( sessionData["History"].encode("ascii"), - QByteArray.Base64Option.Base64UrlEncoding) + QByteArray.Base64Option.Base64UrlEncoding, + ) stream = QDataStream(historyArray, QIODevice.OpenModeFlag.ReadOnly) stream >> page.history() - + if "HistoryIndex" in sessionData: item = page.history().itemAt(sessionData["HistoryIndex"]) if item is not None: page.history().goToItem(item) - + # 2. zoom factor if "ZoomFactor" in sessionData: page.setZoomFactor(sessionData["ZoomFactor"]) - + # 3. scroll position if "ScrollPosition" in sessionData: scrollPos = sessionData["ScrollPosition"] page.scrollTo(QPointF(scrollPos["x"], scrollPos["y"])) - + def extractSessionMetaData(self, sessionData): """ Public method to extract some session meta data elements needed by the tab widget in case of deferred loading. - + @param sessionData dictionary containing the session data as generated by getSessionData() @type dict @@ -2217,28 +2260,27 @@ """ title = sessionData.get("Title", "") urlStr = sessionData.get("Url", "") - + if "Icon" in sessionData: - iconArray = QByteArray.fromBase64( - sessionData["Icon"].encode("ascii")) + iconArray = QByteArray.fromBase64(sessionData["Icon"].encode("ascii")) stream = QDataStream(iconArray, QIODevice.OpenModeFlag.ReadOnly) icon = QIcon() stream >> icon else: from .Tools import WebIconProvider - icon = WebIconProvider.instance().iconForUrl( - QUrl.fromUserInput(urlStr)) - + + icon = WebIconProvider.instance().iconForUrl(QUrl.fromUserInput(urlStr)) + return title, urlStr, icon - + ########################################################################### ## Methods below implement safe browsing related functions ########################################################################### - + def getSafeBrowsingStatus(self): """ Public method to get the safe browsing status of the current page. - + @return flag indicating a safe site @rtype bool """ @@ -2246,27 +2288,27 @@ return self.__page.getSafeBrowsingStatus() else: return True - + ########################################################################### ## Methods below implement print support from the page ########################################################################### - + @pyqtSlot() def __printPage(self): """ Private slot to support printing from the web page. """ self.__mw.tabWidget.printBrowser(browser=self) - + ########################################################################### ## Methods below implement slots for Qt 5.11+ ########################################################################### - + @pyqtSlot("QWebEngineQuotaRequest") def __quotaRequested(self, quotaRequest): """ Private slot to handle quota requests of the web page. - + @param quotaRequest reference to the quota request object @type QWebEngineQuotaRequest """ @@ -2281,30 +2323,32 @@ else: # ask user from .Download.DownloadUtilities import dataString + sizeStr = dataString(quotaRequest.requestedSize()) - + ok = EricMessageBox.yesNo( self, self.tr("Quota Request"), - self.tr("""<p> Allow the website at <b>{0}</b> to use""" - """ <b>{1}</b> of persistent storage?</p>""") - .format(quotaRequest.origin().host(), sizeStr) + self.tr( + """<p> Allow the website at <b>{0}</b> to use""" + """ <b>{1}</b> of persistent storage?</p>""" + ).format(quotaRequest.origin().host(), sizeStr), ) - + if ok: quotaRequest.accept() else: quotaRequest.reject() - + ########################################################################### ## Methods below implement slots for Qt 5.12+ ########################################################################### - + @pyqtSlot("QWebEngineClientCertificateSelection") def __selectClientCertificate(self, clientCertificateSelection): """ Private slot to handle the client certificate selection request. - + @param clientCertificateSelection list of client SSL certificates found in system's client certificate store @type QWebEngineClientCertificateSelection @@ -2317,12 +2361,13 @@ else: certificate = None from EricNetwork.EricSslCertificateSelectionDialog import ( - EricSslCertificateSelectionDialog + EricSslCertificateSelectionDialog, ) + dlg = EricSslCertificateSelectionDialog(certificates, self) if dlg.exec() == QDialog.DialogCode.Accepted: certificate = dlg.getSelectedCertificate() - + if certificate is None: clientCertificateSelection.selectNone() else: