src/eric7/WebBrowser/SiteInfo/SiteInfoDialog.py

Sat, 26 Apr 2025 12:34:32 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 26 Apr 2025 12:34:32 +0200
branch
eric7
changeset 11240
c48c615c04a3
parent 11090
f5f5f5803935
permissions
-rw-r--r--

MicroPython
- Added a configuration option to disable the support for the no longer produced Pimoroni Pico Wireless Pack.

# -*- coding: utf-8 -*-

# Copyright (c) 2011 - 2025 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a dialog to show some information about a site.
"""

from PyQt6.QtCore import Qt, QUrl, pyqtSlot
from PyQt6.QtGui import QBrush, QColor, QImage, QPainter, QPixmap
from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest
from PyQt6.QtWidgets import (
    QApplication,
    QDialog,
    QGraphicsPixmapItem,
    QGraphicsScene,
    QMenu,
    QTreeWidgetItem,
)

try:
    from PyQt6.QtNetwork import QSslCertificate  # __IGNORE_WARNING__

    SSL = True
except ImportError:
    SSL = False

from eric7 import Preferences
from eric7.EricGui import EricPixmapCache
from eric7.EricWidgets import EricFileDialog, EricMessageBox
from eric7.WebBrowser.WebBrowserWindow import WebBrowserWindow

from ..Tools import Scripts, WebBrowserTools
from ..WebBrowserPage import WebBrowserPage
from .Ui_SiteInfoDialog import Ui_SiteInfoDialog


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
        @type WebBrowserView
        @param parent reference to the parent widget
        @type QWidget
        """
        super().__init__(parent)
        self.setupUi(self)
        self.setWindowFlags(Qt.WindowType.Window)

        # put icons
        self.tabWidget.setTabIcon(0, EricPixmapCache.getIcon("siteinfo-general"))
        self.tabWidget.setTabIcon(1, EricPixmapCache.getIcon("siteinfo-media"))
        if SSL:
            self.tabWidget.setTabIcon(2, EricPixmapCache.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(
                    EricPixmapCache.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(
                    EricPixmapCache.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(EricPixmapCache.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
        @type QTreeWidgetItem
        @param _previous old current entry (unused)
        @type 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

            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
        @type 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 (*)"),
            options=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

eric ide

mercurial