--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/WebBrowser/SiteInfo/SiteInfoDialog.py Thu Jul 07 11:23:56 2022 +0200 @@ -0,0 +1,363 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2011 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to show some information about a site. +""" + +from PyQt6.QtCore import pyqtSlot, QUrl, Qt +from PyQt6.QtGui import QPixmap, QImage, QPainter, QColor, QBrush +from PyQt6.QtNetwork import QNetworkRequest, QNetworkReply +from PyQt6.QtWidgets import ( + QDialog, QTreeWidgetItem, QGraphicsScene, QMenu, QApplication, + QGraphicsPixmapItem +) +try: + from PyQt6.QtNetwork import QSslCertificate # __IGNORE_WARNING__ + SSL = True +except ImportError: + SSL = False + +from EricWidgets import EricMessageBox, EricFileDialog + +from .Ui_SiteInfoDialog import Ui_SiteInfoDialog + +from ..Tools import Scripts, WebBrowserTools +from ..WebBrowserPage import WebBrowserPage + +import UI.PixmapCache +import Preferences + +from WebBrowser.WebBrowserWindow import WebBrowserWindow + + +class SiteInfoDialog(QDialog, Ui_SiteInfoDialog): + """ + Class implementing a dialog to show some information about a site. + """ + securityStyleFormat = "QLabel {{ background-color : {0}; }}" + + def __init__(self, browser, parent=None): + """ + Constructor + + @param browser reference to the browser window (HelpBrowser) + @param parent reference to the parent widget (QWidget) + """ + super().__init__(parent) + self.setupUi(self) + self.setWindowFlags(Qt.WindowType.Window) + + # put icons + self.tabWidget.setTabIcon( + 0, UI.PixmapCache.getIcon("siteinfo-general")) + self.tabWidget.setTabIcon( + 1, UI.PixmapCache.getIcon("siteinfo-media")) + if SSL: + self.tabWidget.setTabIcon( + 2, UI.PixmapCache.getIcon("siteinfo-security")) + + self.__imageReply = None + + self.__baseUrl = browser.url() + title = browser.title() + sslInfo = browser.page().getSslCertificateChain() + + #prepare background of image preview + self.__imagePreviewStandardBackground = ( + self.imagePreview.backgroundBrush() + ) + color1 = QColor(220, 220, 220) + color2 = QColor(160, 160, 160) + self.__tilePixmap = QPixmap(8, 8) + self.__tilePixmap.fill(color1) + tilePainter = QPainter(self.__tilePixmap) + tilePainter.fillRect(0, 0, 4, 4, color2) + tilePainter.fillRect(4, 4, 4, 4, color2) + tilePainter.end() + + # populate General tab + self.heading.setText("<b>{0}</b>".format(title)) + self.siteAddressLabel.setText(self.__baseUrl.toString()) + if self.__baseUrl.scheme() in ["https"]: + if WebBrowserWindow.networkManager().isInsecureHost( + self.__baseUrl.host() + ): + self.securityIconLabel.setPixmap( + UI.PixmapCache.getPixmap("securityMedium")) + self.securityLabel.setStyleSheet( + SiteInfoDialog.securityStyleFormat.format( + Preferences.getWebBrowser("InsecureUrlColor").name() + ) + ) + self.securityLabel.setText(self.tr( + '<b>Connection is encrypted but may be insecure.</b>')) + else: + self.securityIconLabel.setPixmap( + UI.PixmapCache.getPixmap("securityHigh")) + self.securityLabel.setStyleSheet( + SiteInfoDialog.securityStyleFormat.format( + Preferences.getWebBrowser("SecureUrlColor").name() + ) + ) + self.securityLabel.setText( + self.tr('<b>Connection is encrypted.</b>')) + else: + self.securityIconLabel.setPixmap( + UI.PixmapCache.getPixmap("securityLow")) + self.securityLabel.setText( + self.tr('<b>Connection is not encrypted.</b>')) + browser.page().runJavaScript( + "document.charset", WebBrowserPage.SafeJsWorld, + lambda res: self.encodingLabel.setText(res)) + + # populate the Security tab + if sslInfo and SSL: + self.sslWidget.showCertificateChain(sslInfo) + self.tabWidget.setTabEnabled(2, SSL and bool(sslInfo)) + self.securityDetailsButton.setEnabled(SSL and bool(sslInfo)) + + # populate Meta tags + browser.page().runJavaScript(Scripts.getAllMetaAttributes(), + WebBrowserPage.SafeJsWorld, + self.__processMetaAttributes) + + # populate Media tab + browser.page().runJavaScript(Scripts.getAllImages(), + WebBrowserPage.SafeJsWorld, + self.__processImageTags) + + self.tabWidget.setCurrentIndex(0) + + @pyqtSlot() + def on_securityDetailsButton_clicked(self): + """ + Private slot to show security details. + """ + self.tabWidget.setCurrentIndex( + self.tabWidget.indexOf(self.securityTab)) + + def __processImageTags(self, res): + """ + Private method to process the image tags. + + @param res result of the JavaScript script + @type list of dict + """ + for img in res: + src = img["src"] + alt = img["alt"] + if not alt: + if src.find("/") == -1: + alt = src + else: + pos = src.rfind("/") + alt = src[pos + 1:] + + if not src or not alt: + continue + + QTreeWidgetItem(self.imagesTree, [alt, src]) + + for col in range(self.imagesTree.columnCount()): + self.imagesTree.resizeColumnToContents(col) + if self.imagesTree.columnWidth(0) > 300: + self.imagesTree.setColumnWidth(0, 300) + self.imagesTree.setCurrentItem(self.imagesTree.topLevelItem(0)) + self.imagesTree.setContextMenuPolicy( + Qt.ContextMenuPolicy.CustomContextMenu) + self.imagesTree.customContextMenuRequested.connect( + self.__imagesTreeContextMenuRequested) + + def __processMetaAttributes(self, res): + """ + Private method to process the meta attributes. + + @param res result of the JavaScript script + @type list of dict + """ + for meta in res: + content = meta["content"] + name = meta["name"] + if not name: + name = meta["httpequiv"] + + if not name or not content: + continue + + if meta["charset"]: + self.encodingLabel.setText(meta["charset"]) + if "charset=" in content: + self.encodingLabel.setText( + content[content.index("charset=") + 8:]) + + QTreeWidgetItem(self.tagsTree, [name, content]) + for col in range(self.tagsTree.columnCount()): + self.tagsTree.resizeColumnToContents(col) + + @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) + def on_imagesTree_currentItemChanged(self, current, previous): + """ + Private slot to show a preview of the selected image. + + @param current current image entry (QTreeWidgetItem) + @param previous old current entry (QTreeWidgetItem) + """ + if current is None: + return + + imageUrl = QUrl(current.text(1)) + if imageUrl.isRelative(): + imageUrl = self.__baseUrl.resolved(imageUrl) + + pixmap = QPixmap() + loading = False + + if imageUrl.scheme() == "data": + encodedUrl = current.text(1).encode("utf-8") + imageData = encodedUrl[encodedUrl.find(b",") + 1:] + pixmap = WebBrowserTools.pixmapFromByteArray(imageData) + elif imageUrl.scheme() == "file": + pixmap = QPixmap(imageUrl.toLocalFile()) + elif imageUrl.scheme() == "qrc": + pixmap = QPixmap(imageUrl.toString()[3:]) + else: + if self.__imageReply is not None: + self.__imageReply.deleteLater() + self.__imageReply = None + + from WebBrowser.WebBrowserWindow import WebBrowserWindow + self.__imageReply = WebBrowserWindow.networkManager().get( + QNetworkRequest(imageUrl)) + self.__imageReply.finished.connect(self.__imageReplyFinished) + loading = True + self.__showLoadingText() + + if not loading: + self.__showPixmap(pixmap) + + @pyqtSlot() + def __imageReplyFinished(self): + """ + Private slot handling the loading of an image. + """ + if self.__imageReply.error() != QNetworkReply.NetworkError.NoError: + return + + data = self.__imageReply.readAll() + self.__showPixmap(QPixmap.fromImage(QImage.fromData(data))) + + def __showPixmap(self, pixmap): + """ + Private method to show a pixmap in the preview pane. + + @param pixmap pixmap to be shown + @type QPixmap + """ + scene = QGraphicsScene(self.imagePreview) + if pixmap.isNull(): + self.imagePreview.setBackgroundBrush( + self.__imagePreviewStandardBackground) + scene.addText(self.tr("Preview not available.")) + else: + self.imagePreview.setBackgroundBrush(QBrush(self.__tilePixmap)) + scene.addPixmap(pixmap) + self.imagePreview.setScene(scene) + + def __showLoadingText(self): + """ + Private method to show some text while loading an image. + """ + self.imagePreview.setBackgroundBrush( + self.__imagePreviewStandardBackground) + scene = QGraphicsScene(self.imagePreview) + scene.addText(self.tr("Loading...")) + self.imagePreview.setScene(scene) + + def __imagesTreeContextMenuRequested(self, pos): + """ + Private slot to show a context menu for the images list. + + @param pos position for the menu (QPoint) + """ + itm = self.imagesTree.itemAt(pos) + if itm is None: + return + + menu = QMenu() + act1 = menu.addAction(self.tr("Copy Image Location to Clipboard")) + act1.setData(itm.text(1)) + act1.triggered.connect(lambda: self.__copyAction(act1)) + act2 = menu.addAction(self.tr("Copy Image Name to Clipboard")) + act2.setData(itm.text(0)) + act2.triggered.connect(lambda: self.__copyAction(act2)) + menu.addSeparator() + act3 = menu.addAction(self.tr("Save Image")) + act3.setData(self.imagesTree.indexOfTopLevelItem(itm)) + act3.triggered.connect(lambda: self.__saveImage(act3)) + menu.exec(self.imagesTree.viewport().mapToGlobal(pos)) + + def __copyAction(self, act): + """ + Private slot to copy the image URL or the image name to the clipboard. + + @param act reference to the action that triggered + @type QAction + """ + QApplication.clipboard().setText(act.data()) + + def __saveImage(self, act): + """ + Private slot to save the selected image to disk. + + @param act reference to the action that triggered + @type QAction + """ + index = act.data() + itm = self.imagesTree.topLevelItem(index) + if itm is None: + return + + if ( + not self.imagePreview.scene() or + len(self.imagePreview.scene().items()) == 0 + ): + return + + pixmapItem = self.imagePreview.scene().items()[0] + if not isinstance(pixmapItem, QGraphicsPixmapItem): + return + + if pixmapItem.pixmap().isNull(): + EricMessageBox.warning( + self, + self.tr("Save Image"), + self.tr( + """<p>This preview is not available.</p>""")) + return + + imageFileName = WebBrowserTools.getFileNameFromUrl(QUrl(itm.text(1))) + index = imageFileName.rfind(".") + if index != -1: + imageFileName = imageFileName[:index] + ".png" + + filename = EricFileDialog.getSaveFileName( + self, + self.tr("Save Image"), + imageFileName, + self.tr("All Files (*)"), + EricFileDialog.DontConfirmOverwrite) + + if not filename: + return + + if not pixmapItem.pixmap().save(filename, "PNG"): + EricMessageBox.critical( + self, + self.tr("Save Image"), + self.tr( + """<p>Cannot write to file <b>{0}</b>.</p>""") + .format(filename)) + return