--- a/src/eric7/HelpViewer/HelpViewerImplQTB.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/HelpViewer/HelpViewerImplQTB.py Wed Jul 13 14:55:47 2022 +0200 @@ -11,7 +11,13 @@ import functools from PyQt6.QtCore import ( - pyqtSlot, Qt, QByteArray, QUrl, QEvent, QCoreApplication, QPoint + pyqtSlot, + Qt, + QByteArray, + QUrl, + QEvent, + QCoreApplication, + QPoint, ) from PyQt6.QtGui import QDesktopServices, QImage, QGuiApplication, QClipboard from PyQt6.QtWidgets import QTextBrowser, QMenu @@ -23,10 +29,8 @@ AboutBlank = QCoreApplication.translate( "HelpViewer", - "<html>" - "<head><title>about:blank</title></head>" - "<body></body>" - "</html>") + "<html>" "<head><title>about:blank</title></head>" "<body></body>" "</html>", +) PageNotFound = QCoreApplication.translate( "HelpViewer", @@ -35,17 +39,19 @@ """<body><div align="center"><br><br>""" """<h1>The page could not be found</h1><br>""" """<h3>'{0}'</h3></div></body>""" - """</html>""") + """</html>""", +) class HelpViewerImplQTB(HelpViewerImpl, QTextBrowser): """ Class implementing the QTextBrowser based help viewer class. """ + def __init__(self, engine, parent=None): """ Constructor - + @param engine reference to the help engine @type QHelpEngine @param parent reference to the parent widget @@ -53,23 +59,23 @@ """ QTextBrowser.__init__(self, parent=parent) HelpViewerImpl.__init__(self, engine) - + self.__helpViewerWidget = parent - + self.__zoomCount = 0 - + self.__menu = QMenu(self) self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self.__showContextMenu) - + self.sourceChanged.connect(self.titleChanged) - + self.grabGesture(Qt.GestureType.PinchGesture) - + def setLink(self, url): """ Public method to set the URL of the document to be shown. - + @param url source of the document @type QUrl """ @@ -77,20 +83,20 @@ self.setHtml(self.__helpViewerWidget.emptyDocument()) else: self.setSource(url) - + def link(self): """ Public method to get the URL of the shown document. - + @return URL of the document @rtype QUrl """ return self.source() - + def doSetSource(self, url, type_): """ Public method to load the data and show it. - + @param url URL of resource to load @type QUrl @param type_ type of the resource to load @@ -99,17 +105,17 @@ if not self.__canLoadResource(url): QDesktopServices.openUrl(url) return - + super().doSetSource(url, type_) - + self.sourceChanged.emit(url) self.loadFinished.emit(True) - + def loadResource(self, type_, name): """ Public method to load data of the specified type from the resource with the given name. - + @param type_ resource type @type int @param name resource name @@ -119,8 +125,8 @@ """ ba = QByteArray() scheme = name.scheme() - - if type_ < 4: # QTextDocument.ResourceType.MarkdownResource + + if type_ < 4: # QTextDocument.ResourceType.MarkdownResource if scheme == "about": if name.toString() == "about:blank": return QByteArray(AboutBlank.encode("utf-8")) @@ -132,24 +138,22 @@ url = self._engine.findFile(name) if url.isValid(): ba = self._engine.fileData(url) - + if name.toString().lower().endswith(".svg"): image = QImage() image.loadFromData(ba, "svg") if not image.isNull(): return image - + if ba.isEmpty(): - ba = QByteArray( - PageNotFound.format(name.toString()).encode("utf-8") - ) - + ba = QByteArray(PageNotFound.format(name.toString()).encode("utf-8")) + return ba - + def __canLoadResource(self, url): """ Private method to check, if the given resource can be loaded. - + @param url URL of resource to be loaded @type QUrl @return flag indicating, that the given URL can be handled @@ -157,41 +161,40 @@ """ scheme = url.scheme() return scheme in ("about", "qthelp", "file", "") - + def pageTitle(self): """ Public method get the page title. - + @return page title @rtype str """ titleStr = self.documentTitle() if not titleStr: url = self.link() - + 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 isEmptyPage(self): """ Public method to check, if the current page is the empty page. - + @return flag indicating an empty page is loaded @rtype bool """ return self.pageTitle() == self.tr("Empty Page") - + def mousePressEvent(self, evt): """ Protected method called by a mouse press event. - + @param evt reference to the mouse event @type QMouseEvent """ @@ -203,17 +206,17 @@ evt.accept() else: super().mousePressEvent(evt) - + def mouseReleaseEvent(self, evt): """ Protected method called by a mouse release event. - + @param evt reference to the mouse event @type QMouseEvent """ hasModifier = evt.modifiers() != Qt.KeyboardModifier.NoModifier if evt.button() == Qt.MouseButton.LeftButton and hasModifier: - + anchor = self.anchorAt(evt.pos()) if anchor: url = self.link().resolved(QUrl(anchor)) @@ -224,11 +227,11 @@ evt.accept() else: super().mousePressEvent(evt) - + def gotoHistory(self, index): """ Public method to step through the history. - + @param index history index (<0 backward, >0 forward) @type int """ @@ -240,27 +243,27 @@ # forward for _ind in range(index): self.forward() - + def isBackwardAvailable(self): """ Public method to check, if stepping backward through the history is available. - + @return flag indicating backward stepping is available @rtype bool """ return QTextBrowser.isBackwardAvailable(self) - + def isForwardAvailable(self): """ Public method to check, if stepping forward through the history is available. - + @return flag indicating forward stepping is available @rtype bool """ return QTextBrowser.isForwardAvailable(self) - + def scaleUp(self): """ Public method to zoom in. @@ -269,7 +272,7 @@ self.__zoomCount += 1 self.zoomIn() self.zoomChanged.emit() - + def scaleDown(self): """ Public method to zoom out. @@ -278,11 +281,11 @@ self.__zoomCount -= 1 self.zoomOut() self.zoomChanged.emit() - + def setScale(self, scale): """ Public method to set the zoom level. - + @param scale zoom level to set @type int """ @@ -290,7 +293,7 @@ self.zoomOut(scale) self.__zoomCount = scale self.zoomChanged.emit() - + def resetScale(self): """ Public method to reset the zoom level. @@ -299,38 +302,38 @@ self.zoomOut(self.__zoomCount) self.zoomChanged.emit() self.__zoomCount = 0 - + def scale(self): """ Public method to get the zoom level. - + @return current zoom level @rtype int """ return self.__zoomCount - + def isScaleUpAvailable(self): """ Public method to check, if the max. zoom level is reached. - + @return flag indicating scale up is available @rtype bool """ return self.__zoomCount < 10 - + def isScaleDownAvailable(self): """ Public method to check, if the min. zoom level is reached. - + @return flag indicating scale down is available @rtype bool """ return self.__zoomCount > -5 - + def wheelEvent(self, evt): """ Protected method to handle wheel event to zoom. - + @param evt reference to the event object @type QWheelEvent """ @@ -341,47 +344,40 @@ else: self.scaleDown() evt.accept() - + elif evt.modifiers() & Qt.KeyboardModifier.ShiftModifier: if delta < 0: self.backward() elif delta > 0: self.forward() evt.accept() - + else: QTextBrowser.wheelEvent(self, evt) - + def keyPressEvent(self, evt): """ Protected method to handle key press events. - + @param evt reference to the key event @type QKeyEvent """ key = evt.key() - isControlModifier = ( - evt.modifiers() == Qt.KeyboardModifier.ControlModifier - ) - - if ( - key == Qt.Key.Key_ZoomIn or - (key == Qt.Key.Key_Plus and isControlModifier) - ): + isControlModifier = evt.modifiers() == Qt.KeyboardModifier.ControlModifier + + if key == Qt.Key.Key_ZoomIn or (key == Qt.Key.Key_Plus and isControlModifier): self.scaleUp() evt.accept() - elif ( - key == Qt.Key.Key_ZoomOut or - (key == Qt.Key.Key_Minus and isControlModifier) + elif key == Qt.Key.Key_ZoomOut or ( + key == Qt.Key.Key_Minus and isControlModifier ): self.scaleDown() evt.accept() elif key == Qt.Key.Key_0 and isControlModifier: self.resetScale() evt.accept() - elif ( - key == Qt.Key.Key_Backspace or - (key == Qt.Key.Key_Left and isControlModifier) + elif key == Qt.Key.Key_Backspace or ( + key == Qt.Key.Key_Left and isControlModifier ): self.backward() evt.accept() @@ -391,25 +387,22 @@ elif key == Qt.Key.Key_F and isControlModifier: self.__helpViewerWidget.showHideSearch(True) evt.accept() - elif ( - key == Qt.Key.Key_F3 and - evt.modifiers() == Qt.KeyboardModifier.NoModifier - ): + elif key == Qt.Key.Key_F3 and evt.modifiers() == Qt.KeyboardModifier.NoModifier: self.__helpViewerWidget.searchNext() evt.accept() elif ( - key == Qt.Key.Key_F3 and - evt.modifiers() == Qt.KeyboardModifier.ShiftModifier + key == Qt.Key.Key_F3 + and evt.modifiers() == Qt.KeyboardModifier.ShiftModifier ): self.__helpViewerWidget.searchPrev() evt.accept() else: super().keyPressEvent(evt) - + def event(self, evt): """ Public method handling events. - + @param evt reference to the event @type QEvent @return flag indicating the event was handled @@ -418,13 +411,13 @@ if evt.type() == QEvent.Type.Gesture: self.gestureEvent(evt) return True - + return super().event(evt) - + def gestureEvent(self, evt): """ Protected method handling gesture events. - + @param evt reference to the gesture event @type QGestureEvent """ @@ -443,16 +436,16 @@ pinch.setTotalScaleFactor(1.6) self.setScale(zoom) evt.accept() - + ####################################################################### ## Context menu related methods below ####################################################################### - + @pyqtSlot(QPoint) def __showContextMenu(self, pos): """ Private slot to show the context menu. - + @param pos position to show the context menu at @type QPoint """ @@ -460,97 +453,86 @@ anchor = self.anchorAt(pos) linkUrl = self.link().resolved(QUrl(anchor)) if anchor else QUrl() selectedText = self.textCursor().selectedText() - + act = self.__menu.addAction( - UI.PixmapCache.getIcon("back"), - self.tr("Backward"), - self.backward) + UI.PixmapCache.getIcon("back"), self.tr("Backward"), self.backward + ) act.setEnabled(self.isBackwardAvailable()) - + act = self.__menu.addAction( - UI.PixmapCache.getIcon("forward"), - self.tr("Forward"), - self.forward) + UI.PixmapCache.getIcon("forward"), self.tr("Forward"), self.forward + ) act.setEnabled(self.isForwardAvailable()) - + act = self.__menu.addAction( - UI.PixmapCache.getIcon("reload"), - self.tr("Reload"), - self.reload) - + UI.PixmapCache.getIcon("reload"), self.tr("Reload"), self.reload + ) + if not linkUrl.isEmpty() and linkUrl.scheme() != "javascript": self.__createLinkContextMenu(self.__menu, linkUrl) - + self.__menu.addSeparator() - - act = self.__menu.addAction( - UI.PixmapCache.getIcon("editCopy"), - self.tr("Copy Page URL to Clipboard")) - act.setData(self.link()) - act.triggered.connect( - functools.partial(self.__copyLink, act)) - + act = self.__menu.addAction( - UI.PixmapCache.getIcon("bookmark22"), - self.tr("Bookmark Page")) - act.setData({ - "title": self.pageTitle(), - "url": self.link() - }) - act.triggered.connect( - functools.partial(self.__bookmarkPage, act)) - + UI.PixmapCache.getIcon("editCopy"), self.tr("Copy Page URL to Clipboard") + ) + act.setData(self.link()) + act.triggered.connect(functools.partial(self.__copyLink, act)) + + act = self.__menu.addAction( + UI.PixmapCache.getIcon("bookmark22"), self.tr("Bookmark Page") + ) + act.setData({"title": self.pageTitle(), "url": self.link()}) + act.triggered.connect(functools.partial(self.__bookmarkPage, act)) + self.__menu.addSeparator() - + act = self.__menu.addAction( - UI.PixmapCache.getIcon("zoomIn"), - self.tr("Zoom in"), - self.scaleUp) + UI.PixmapCache.getIcon("zoomIn"), self.tr("Zoom in"), self.scaleUp + ) act.setEnabled(self.isScaleUpAvailable()) - + act = self.__menu.addAction( - UI.PixmapCache.getIcon("zoomOut"), - self.tr("Zoom out"), - self.scaleDown) + UI.PixmapCache.getIcon("zoomOut"), self.tr("Zoom out"), self.scaleDown + ) act.setEnabled(self.isScaleDownAvailable()) - + self.__menu.addAction( - UI.PixmapCache.getIcon("zoomReset"), - self.tr("Zoom reset"), - self.resetScale) - + UI.PixmapCache.getIcon("zoomReset"), self.tr("Zoom reset"), self.resetScale + ) + self.__menu.addSeparator() - + act = self.__menu.addAction( - UI.PixmapCache.getIcon("editCopy"), - self.tr("Copy"), - self.copy) + UI.PixmapCache.getIcon("editCopy"), self.tr("Copy"), self.copy + ) act.setEnabled(bool(selectedText)) - + self.__menu.addAction( UI.PixmapCache.getIcon("editSelectAll"), self.tr("Select All"), - self.selectAll) - + self.selectAll, + ) + self.__menu.addSeparator() - + self.__menu.addAction( - UI.PixmapCache.getIcon("tabClose"), - self.tr('Close'), - self.__closePage) - + UI.PixmapCache.getIcon("tabClose"), self.tr("Close"), self.__closePage + ) + act = self.__menu.addAction( UI.PixmapCache.getIcon("tabCloseOther"), self.tr("Close Others"), - self.__closeOtherPages) + self.__closeOtherPages, + ) act.setEnabled(self.__helpViewerWidget.openPagesCount() > 1) - + self.__menu.popup(self.mapToGlobal(pos)) - + def __createLinkContextMenu(self, menu, linkUrl): """ Private method to populate the context menu for URLs. - + @param menu reference to the menu to be populated @type QMenu @param linkUrl URL to create the menu part for @@ -558,61 +540,58 @@ """ if not menu.isEmpty(): menu.addSeparator() - + act = menu.addAction( - UI.PixmapCache.getIcon("openNewTab"), - self.tr("Open Link in New Page")) + UI.PixmapCache.getIcon("openNewTab"), self.tr("Open Link in New Page") + ) act.setData(linkUrl) - act.triggered.connect( - functools.partial(self.__openLinkInNewPage, act)) - + act.triggered.connect(functools.partial(self.__openLinkInNewPage, act)) + act = menu.addAction( - UI.PixmapCache.getIcon("newWindow"), - self.tr("Open Link in Background Page")) + UI.PixmapCache.getIcon("newWindow"), self.tr("Open Link in Background Page") + ) act.setData(linkUrl) - act.triggered.connect( - functools.partial(self.__openLinkInBackgroundPage, act)) - + act.triggered.connect(functools.partial(self.__openLinkInBackgroundPage, 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(linkUrl) - act.triggered.connect( - functools.partial(self.__copyLink, act)) - + act.triggered.connect(functools.partial(self.__copyLink, act)) + def __openLinkInNewPage(self, act): """ Private method called by the context menu to open a link in a new page. - + @param act reference to the action that triggered @type QAction """ url = act.data() if url.isEmpty(): return - + self.__helpViewerWidget.openUrlNewPage(url) - + def __openLinkInBackgroundPage(self, act): """ Private method called by the context menu to open a link in a background page. - + @param act reference to the action that triggered @type QAction """ url = act.data() if url.isEmpty(): return - + self.__helpViewerWidget.openUrlNewBackgroundPage(url) - + def __bookmarkPage(self, act): """ Private method called by the context menu to bookmark the page. - + @param act reference to the action that triggered @type QAction """ @@ -621,34 +600,34 @@ with contextlib.suppress(KeyError): url = data["url"] title = data["title"] - + self.__helpViewerWidget.bookmarkPage(title, url) - + def __copyLink(self, act): """ Private method called by the context menu 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 QGuiApplication.clipboard().setText(data, QClipboard.Mode.Clipboard) QGuiApplication.clipboard().setText(data, QClipboard.Mode.Selection) - + def __closePage(self): """ Private method called by the context menu to close the current page. """ self.__helpViewerWidget.closeCurrentPage() - + def __closeOtherPages(self): """ Private method called by the context menu to close all other pages.