# SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * * # * Copyright (c) 2022-2024 The FreeCAD Project Association AISBL * # * * # * This file is part of FreeCAD. * # * * # * FreeCAD is free software: you can redistribute it and/or modify it * # * under the terms of the GNU Lesser General Public License as * # * published by the Free Software Foundation, either version 2.1 of the * # * License, or (at your option) any later version. * # * * # * FreeCAD is distributed in the hope that it will be useful, but * # * WITHOUT ANY WARRANTY; without even the implied warranty of * # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * # * Lesser General Public License for more details. * # * * # * You should have received a copy of the GNU Lesser General Public * # * License along with FreeCAD. If not, see * # * . * # * * # *************************************************************************** import FreeCAD # Get whatever version of PySide we can try: import PySide # Use the FreeCAD wrapper except ImportError: try: import PySide6 # Outside FreeCAD, try Qt6 first PySide = PySide6 except ImportError: import PySide2 # Fall back to Qt5 (if this fails, Python will kill this module's import) PySide = PySide2 from PySide import QtCore, QtGui, QtWidgets from typing import Optional class WidgetReadmeBrowser(QtWidgets.QTextBrowser): """A QTextBrowser widget that emits signals for each requested image resource, allowing an external controller to load and re-deliver those images. Once all resources have been re-delivered, the original data is redisplayed with the images in-line. Call setUrl prior to calling setMarkdown or setHtml to ensure URLs are resolved correctly.""" load_resource = QtCore.Signal(str) # Str is a URL to a resource def __init__(self, parent: QtWidgets.QWidget = None): super().__init__(parent) self.image_map = {} self.url = "" self.stop = False self.setOpenExternalLinks(True) def setUrl(self, url: str): """Set the base URL of the page. Used to resolve relative URLs in the page source.""" self.url = url def setMarkdown(self, md: str): """Provides an optional fallback to the markdown library for older versions of Qt (prior to 5.15) that did not have native markdown support. Lacking that, plaintext is displayed.""" if hasattr(super(), "setMarkdown"): super().setMarkdown(md) else: try: import markdown html = markdown.markdown(md) self.setHtml(html) except ImportError: self.setText(md) FreeCAD.Console.Warning( "Qt < 5.15 and no `import markdown` -- falling back to plain text display\n" ) def set_resource(self, resource_url: str, image: Optional[QtGui.QImage]): """Once a resource has been fetched (or the fetch has failed), this method should be used to inform the widget that the resource has been loaded. Note that the incoming image is scaled to 97% of the widget width if it is larger than that.""" self.image_map[resource_url] = self._ensure_appropriate_width(image) def loadResource(self, resource_type: int, name: QtCore.QUrl) -> object: """Callback for resource loading. Called automatically by underlying Qt code when external resources are needed for rendering. In particular, here it is used to download and cache (in RAM) the images needed for the README and Wiki pages.""" if resource_type == QtGui.QTextDocument.ImageResource and not self.stop: full_url = self._create_full_url(name.toString()) if full_url not in self.image_map: self.load_resource.emit(full_url) self.image_map[full_url] = None return self.image_map[full_url] return super().loadResource(resource_type, name) def _ensure_appropriate_width(self, image: QtGui.QImage) -> QtGui.QImage: ninety_seven_percent = self.width() * 0.97 if image.width() < ninety_seven_percent: return image return image.scaledToWidth(ninety_seven_percent) def _create_full_url(self, url: str) -> str: if url.startswith("http"): return url if not self.url: return url lhs, slash, _ = self.url.rpartition("/") return lhs + slash + url