From f43f51aa2f3f439aa69d42aa0a794d5cccd76877 Mon Sep 17 00:00:00 2001 From: Timmy Welch Date: Sat, 1 Jul 2023 23:29:38 -0700 Subject: [PATCH] Fix #396 Use a QWebEngineView if QtWebEngine is available. If QtWebEngine is not available replace figure tags with div's to allow the QTextEdit to render the rest of the html properly --- comictaggerlib/gui.py | 8 +++ comictaggerlib/issueselectionwindow.py | 26 ++++++++-- comictaggerlib/seriesselectionwindow.py | 25 +++++++-- comictaggerlib/ui/qtutils.py | 67 +++++++++++++++++++++++++ setup.cfg | 4 ++ 5 files changed, 123 insertions(+), 7 deletions(-) diff --git a/comictaggerlib/gui.py b/comictaggerlib/gui.py index ccd33aa..9c77ba5 100644 --- a/comictaggerlib/gui.py +++ b/comictaggerlib/gui.py @@ -63,6 +63,14 @@ try: qt_exception_hook = UncaughtHook() from comictaggerlib.taggerwindow import TaggerWindow + try: + # needed here to initialize QWebEngine + from PyQt5.QtWebEngineWidgets import QWebEngineView # noqa: F401 + + qt_webengine_available = True + except ImportError: + qt_webengine_available = False + class Application(QtWidgets.QApplication): openFileRequest = QtCore.pyqtSignal(QtCore.QUrl, name="openfileRequest") diff --git a/comictaggerlib/issueselectionwindow.py b/comictaggerlib/issueselectionwindow.py index 4e673e7..b70fa6c 100644 --- a/comictaggerlib/issueselectionwindow.py +++ b/comictaggerlib/issueselectionwindow.py @@ -23,7 +23,7 @@ from comicapi.issuestring import IssueString from comictaggerlib.coverimagewidget import CoverImageWidget from comictaggerlib.ctsettings import ct_ns from comictaggerlib.ui import ui_path -from comictaggerlib.ui.qtutils import reduce_widget_font_size +from comictaggerlib.ui.qtutils import new_web_view, reduce_widget_font_size from comictalker.comictalker import ComicTalker, TalkerError from comictalker.resulttypes import ComicIssue @@ -58,6 +58,19 @@ class IssueSelectionWindow(QtWidgets.QDialog): gridlayout.addWidget(self.coverWidget) gridlayout.setContentsMargins(0, 0, 0, 0) + self.teDescription: QtWidgets.QWidget + webengine = new_web_view(self) + if webengine: + self.teDescription.hide() + self.teDescription.deleteLater() + # I don't know how to replace teDescription, this is the result of teDescription.height() once rendered + webengine.resize(webengine.width(), 141) + self.splitter.addWidget(webengine) + self.teDescription = webengine + logger.info("successfully loaded QWebEngineView") + else: + logger.info("failed to open QWebEngineView") + reduce_widget_font_size(self.twList) reduce_widget_font_size(self.teDescription, 1) @@ -189,6 +202,13 @@ class IssueSelectionWindow(QtWidgets.QDialog): def cell_double_clicked(self, r: int, c: int) -> None: self.accept() + def set_description(self, widget: QtWidgets.QWidget, text: str) -> None: + if isinstance(widget, QtWidgets.QTextEdit): + widget.setText(text.replace("", "").replace(" None: if curr is None: return @@ -203,8 +223,8 @@ class IssueSelectionWindow(QtWidgets.QDialog): self.issue_number = record.issue_number self.coverWidget.set_issue_details(self.issue_id, [record.image_url, *record.alt_image_urls]) if record.description is None: - self.teDescription.setText("") + self.set_description(self.teDescription, "") else: - self.teDescription.setText(record.description) + self.set_description(self.teDescription, record.description) break diff --git a/comictaggerlib/seriesselectionwindow.py b/comictaggerlib/seriesselectionwindow.py index fee4e5d..0ae7085 100644 --- a/comictaggerlib/seriesselectionwindow.py +++ b/comictaggerlib/seriesselectionwindow.py @@ -20,7 +20,7 @@ import logging from collections import deque from PyQt5 import QtCore, QtGui, QtWidgets, uic -from PyQt5.QtCore import pyqtSignal +from PyQt5.QtCore import QUrl, pyqtSignal from comicapi import utils from comicapi.comicarchive import ComicArchive @@ -32,7 +32,7 @@ from comictaggerlib.issueselectionwindow import IssueSelectionWindow from comictaggerlib.matchselectionwindow import MatchSelectionWindow from comictaggerlib.progresswindow import IDProgressWindow from comictaggerlib.ui import ui_path -from comictaggerlib.ui.qtutils import reduce_widget_font_size +from comictaggerlib.ui.qtutils import new_web_view, reduce_widget_font_size from comictalker.comictalker import ComicTalker, TalkerError from comictalker.resulttypes import ComicSeries @@ -122,6 +122,16 @@ class SeriesSelectionWindow(QtWidgets.QDialog): gridlayout.addWidget(self.imageWidget) gridlayout.setContentsMargins(0, 0, 0, 0) + self.teDetails: QtWidgets.QWidget + webengine = new_web_view(self) + if webengine: + self.teDetails.hide() + self.teDetails.deleteLater() + # I don't know how to replace teDetails, this is the result of teDetails.height() once rendered + webengine.resize(webengine.width(), 141) + self.splitter.addWidget(webengine) + self.teDetails = webengine + reduce_widget_font_size(self.teDetails, 1) reduce_widget_font_size(self.twList) @@ -533,6 +543,13 @@ class SeriesSelectionWindow(QtWidgets.QDialog): def cell_double_clicked(self, r: int, c: int) -> None: self.show_issues() + def set_description(self, widget: QtWidgets.QWidget, text: str) -> None: + if isinstance(widget, QtWidgets.QTextEdit): + widget.setText(text.replace("", "").replace(" None: if curr is None: return @@ -545,8 +562,8 @@ class SeriesSelectionWindow(QtWidgets.QDialog): for record in self.ct_search_results: if record.id == self.series_id: if record.description is None: - self.teDetails.setText("") + self.set_description(self.teDetails, "") else: - self.teDetails.setText(record.description) + self.set_description(self.teDetails, record.description) self.imageWidget.set_url(record.image_url) break diff --git a/comictaggerlib/ui/qtutils.py b/comictaggerlib/ui/qtutils.py index c3ce3dc..94a2f42 100644 --- a/comictaggerlib/ui/qtutils.py +++ b/comictaggerlib/ui/qtutils.py @@ -5,6 +5,10 @@ from __future__ import annotations import io import logging import traceback +import webbrowser + +from PyQt5.QtCore import QUrl +from PyQt5.QtWidgets import QWidget from comictaggerlib.graphics import graphics_path @@ -12,6 +16,7 @@ logger = logging.getLogger(__name__) try: from PyQt5 import QtGui, QtWidgets + from PyQt5.QtCore import Qt qt_available = True except ImportError: @@ -25,6 +30,68 @@ if qt_available: except ImportError: pil_available = False + try: + from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineView + + class WebPage(QWebEnginePage): + def acceptNavigationRequest( + self, url: QUrl, n_type: QWebEnginePage.NavigationType, isMainFrame: bool + ) -> bool: + if n_type in ( + QWebEnginePage.NavigationType.NavigationTypeOther, + QWebEnginePage.NavigationType.NavigationTypeTyped, + ): + return True + if n_type in (QWebEnginePage.NavigationType.NavigationTypeLinkClicked,) and url.scheme() in ( + "http", + "https", + ): + webbrowser.open(url.toString()) + return False + + def new_web_view(parent: QWidget) -> QWebEngineView: + webengine = QWebEngineView(parent) + webengine.setPage(WebPage(parent)) + webengine.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) + settings = webengine.settings() + settings.setAttribute(settings.WebAttribute.AutoLoadImages, True) + settings.setAttribute(settings.WebAttribute.JavascriptEnabled, False) + settings.setAttribute(settings.WebAttribute.JavascriptCanOpenWindows, False) + settings.setAttribute(settings.WebAttribute.JavascriptCanAccessClipboard, False) + settings.setAttribute(settings.WebAttribute.LinksIncludedInFocusChain, False) + settings.setAttribute(settings.WebAttribute.LocalStorageEnabled, False) + settings.setAttribute(settings.WebAttribute.LocalContentCanAccessRemoteUrls, False) + settings.setAttribute(settings.WebAttribute.XSSAuditingEnabled, False) + settings.setAttribute(settings.WebAttribute.SpatialNavigationEnabled, True) + settings.setAttribute(settings.WebAttribute.LocalContentCanAccessFileUrls, False) + settings.setAttribute(settings.WebAttribute.HyperlinkAuditingEnabled, False) + settings.setAttribute(settings.WebAttribute.ScrollAnimatorEnabled, False) + settings.setAttribute(settings.WebAttribute.ErrorPageEnabled, False) + settings.setAttribute(settings.WebAttribute.PluginsEnabled, False) + settings.setAttribute(settings.WebAttribute.FullScreenSupportEnabled, False) + settings.setAttribute(settings.WebAttribute.ScreenCaptureEnabled, False) + settings.setAttribute(settings.WebAttribute.WebGLEnabled, False) + settings.setAttribute(settings.WebAttribute.Accelerated2dCanvasEnabled, False) + settings.setAttribute(settings.WebAttribute.AutoLoadIconsForPage, False) + settings.setAttribute(settings.WebAttribute.TouchIconsEnabled, False) + settings.setAttribute(settings.WebAttribute.FocusOnNavigationEnabled, False) + settings.setAttribute(settings.WebAttribute.PrintElementBackgrounds, False) + settings.setAttribute(settings.WebAttribute.AllowRunningInsecureContent, False) + settings.setAttribute(settings.WebAttribute.AllowGeolocationOnInsecureOrigins, False) + settings.setAttribute(settings.WebAttribute.AllowWindowActivationFromJavaScript, False) + settings.setAttribute(settings.WebAttribute.ShowScrollBars, True) + settings.setAttribute(settings.WebAttribute.PlaybackRequiresUserGesture, True) + settings.setAttribute(settings.WebAttribute.JavascriptCanPaste, False) + settings.setAttribute(settings.WebAttribute.WebRTCPublicInterfacesOnly, False) + settings.setAttribute(settings.WebAttribute.DnsPrefetchEnabled, False) + settings.setAttribute(settings.WebAttribute.PdfViewerEnabled, False) + return webengine + + except ImportError: + + def new_web_view(parent: QWidget) -> QWebEngineView: + ... + def reduce_widget_font_size(widget: QtWidgets.QWidget, delta: int = 2) -> None: f = widget.font() if f.pointSize() > 10: diff --git a/setup.cfg b/setup.cfg index 8a13e72..32825de 100644 --- a/setup.cfg +++ b/setup.cfg @@ -74,8 +74,12 @@ GUI = PyQt5 ICU = pyicu;sys_platform == 'linux' or sys_platform == 'darwin' +QTW = + PyQt5 + PyQtWebEngine all = PyQt5 + PyQtWebEngine py7zr rarfile>=4.0 pyicu;sys_platform == 'linux' or sys_platform == 'darwin'