Addon Manager: Improve macro readme rendering
This commit is contained in:
committed by
Chris Hennes
parent
d646904ca7
commit
ec545b4cec
@@ -25,6 +25,8 @@
|
||||
|
||||
import Addon
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
from enum import Enum, auto
|
||||
from html.parser import HTMLParser
|
||||
|
||||
import addonmanager_freecad_interface as fci
|
||||
import addonmanager_utilities as utils
|
||||
@@ -43,18 +45,21 @@ class ReadmeViewer(QtWidgets.QTextBrowser):
|
||||
super().__init__(parent)
|
||||
NetworkManager.InitializeNetworkManager()
|
||||
NetworkManager.AM_NETWORK_MANAGER.completed.connect(self._download_completed)
|
||||
self.request_index = 0
|
||||
self.readme_request_index = 0
|
||||
self.resource_requests = {}
|
||||
self.url = ""
|
||||
self.repo: Addon.Addon = None
|
||||
self.setOpenExternalLinks(True)
|
||||
self.setOpenLinks(True)
|
||||
self.image_map = {}
|
||||
self.stop = True
|
||||
|
||||
def set_addon(self, repo: Addon):
|
||||
"""Set which Addon's information is displayed"""
|
||||
|
||||
self.setPlainText(translate("AddonsInstaller", "Loading README data..."))
|
||||
self.repo = repo
|
||||
self.stop = False
|
||||
if self.repo.repo_type == Addon.Addon.Kind.MACRO:
|
||||
self.url = self.repo.macro.wiki
|
||||
if not self.url:
|
||||
@@ -62,11 +67,13 @@ class ReadmeViewer(QtWidgets.QTextBrowser):
|
||||
else:
|
||||
self.url = utils.get_readme_url(repo)
|
||||
|
||||
self.request_index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(self.url)
|
||||
self.readme_request_index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(
|
||||
self.url
|
||||
)
|
||||
|
||||
def _download_completed(self, index: int, code: int, data: QtCore.QByteArray) -> None:
|
||||
"""Callback for handling a completed README file download."""
|
||||
if index == self.request_index:
|
||||
if index == self.readme_request_index:
|
||||
if code == 200: # HTTP success
|
||||
self._process_package_download(data.data().decode("utf-8"))
|
||||
else:
|
||||
@@ -76,34 +83,56 @@ class ReadmeViewer(QtWidgets.QTextBrowser):
|
||||
"Failed to download data from {} -- received response code {}.",
|
||||
).format(self.url, code)
|
||||
)
|
||||
elif index in self.resource_requests:
|
||||
if code == 200:
|
||||
self._process_resource_download(self.resource_requests[index], data.data())
|
||||
else:
|
||||
self.image_map[self.resource_requests[index]] = None
|
||||
del self.resource_requests[index]
|
||||
if not self.resource_requests:
|
||||
self.set_addon(self.repo) # Trigger a reload of the page now with resources
|
||||
|
||||
def _process_package_download(self, data: str):
|
||||
if self.repo.repo_type == Addon.Addon.Kind.MACRO:
|
||||
self.setHtml(data)
|
||||
parser = WikiCleaner()
|
||||
parser.feed(data)
|
||||
self.setHtml(parser.final_html)
|
||||
else:
|
||||
# Check for recent Qt (e.g. Qt5.15 or later). Check can be removed when
|
||||
# we no longer support Ubuntu 20.04LTS for compiling.
|
||||
if hasattr(self, "setMarkdown"):
|
||||
self.setMarkdown(data)
|
||||
else:
|
||||
self.setPlainText(data)
|
||||
|
||||
def _process_resource_download(self, resource_name: str, resource_data: bytes):
|
||||
image = QtGui.QImage.fromData(resource_data)
|
||||
if image:
|
||||
self.image_map[resource_name] = self._ensure_appropriate_width(image)
|
||||
else:
|
||||
self.image_map[resource_name] = None
|
||||
|
||||
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:
|
||||
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.image_map[full_url] = None
|
||||
fci.Console.PrintMessage(f"Downloading image from {full_url}...\n")
|
||||
data = NetworkManager.AM_NETWORK_MANAGER.blocking_get(full_url)
|
||||
if data and data.data():
|
||||
image = QtGui.QImage.fromData(data.data())
|
||||
if image:
|
||||
self.image_map[full_url] = self._ensure_appropriate_width(image)
|
||||
index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(full_url)
|
||||
self.resource_requests[index] = full_url
|
||||
return self.image_map[full_url]
|
||||
return super().loadResource(resource_type, name)
|
||||
|
||||
def hideEvent(self, event: QtGui.QHideEvent):
|
||||
self.stop = True
|
||||
for request in self.resource_requests:
|
||||
NetworkManager.AM_NETWORK_MANAGER.abort(request)
|
||||
self.resource_requests.clear()
|
||||
|
||||
def _create_full_url(self, url: str) -> str:
|
||||
if url.startswith("http"):
|
||||
return url
|
||||
@@ -117,3 +146,113 @@ class ReadmeViewer(QtWidgets.QTextBrowser):
|
||||
if image.width() < ninety_seven_percent:
|
||||
return image
|
||||
return image.scaledToWidth(ninety_seven_percent)
|
||||
|
||||
|
||||
class WikiCleaner(HTMLParser):
|
||||
"""This HTML parser cleans up FreeCAD Macro Wiki Page for display in a
|
||||
QTextBrowser widget (which does not deal will with tables used as formatting,
|
||||
etc.) It strips out any tables, and extracts the mw-parser-output div as the only
|
||||
thing that actually gets displayed. It also discards anything inside the [edit]
|
||||
spans that litter wiki output."""
|
||||
|
||||
class State(Enum):
|
||||
BeforeMacroContent = auto()
|
||||
InMacroContent = auto()
|
||||
InTable = auto()
|
||||
InEditSpan = auto()
|
||||
AfterMacroContent = auto()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.depth_in_div = 0
|
||||
self.depth_in_span = 0
|
||||
self.depth_in_table = 0
|
||||
self.final_html = "<html><body>"
|
||||
self.previous_state = WikiCleaner.State.BeforeMacroContent
|
||||
self.state = WikiCleaner.State.BeforeMacroContent
|
||||
|
||||
def handle_starttag(self, tag: str, attrs: list[tuple[str, str]]):
|
||||
if tag == "div":
|
||||
self.handle_div_start(attrs)
|
||||
elif tag == "span":
|
||||
self.handle_span_start(attrs)
|
||||
elif tag == "table":
|
||||
self.handle_table_start(attrs)
|
||||
else:
|
||||
if self.state == WikiCleaner.State.InMacroContent:
|
||||
self.add_tag_to_html(tag, attrs)
|
||||
|
||||
def handle_div_start(self, attrs: list[tuple[str, str]]):
|
||||
for name, value in attrs:
|
||||
if name == "class" and value == "mw-parser-output":
|
||||
self.previous_state = self.state
|
||||
self.state = WikiCleaner.State.InMacroContent
|
||||
if self.state == WikiCleaner.State.InMacroContent:
|
||||
self.depth_in_div += 1
|
||||
self.add_tag_to_html("div", attrs)
|
||||
|
||||
def handle_span_start(self, attrs: list[tuple[str, str]]):
|
||||
for name, value in attrs:
|
||||
if name == "class" and value == "mw-editsection":
|
||||
self.previous_state = self.state
|
||||
self.state = WikiCleaner.State.InEditSpan
|
||||
break
|
||||
if self.state == WikiCleaner.State.InEditSpan:
|
||||
self.depth_in_span += 1
|
||||
elif WikiCleaner.State.InMacroContent:
|
||||
self.add_tag_to_html("span", attrs)
|
||||
|
||||
def handle_table_start(self, attrs: list[tuple[str, str]]):
|
||||
if self.state != WikiCleaner.State.InTable:
|
||||
self.previous_state = self.state
|
||||
self.state = WikiCleaner.State.InTable
|
||||
self.depth_in_table += 1
|
||||
|
||||
def add_tag_to_html(self, tag, attrs=None):
|
||||
self.final_html += f"<{tag}"
|
||||
if attrs:
|
||||
self.final_html += " "
|
||||
for attr, value in attrs:
|
||||
self.final_html += f"{attr}='{value}'"
|
||||
self.final_html += ">\n"
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == "table":
|
||||
self.handle_table_end()
|
||||
elif tag == "span":
|
||||
self.handle_span_end()
|
||||
elif tag == "div":
|
||||
self.handle_div_end()
|
||||
else:
|
||||
if self.state == WikiCleaner.State.InMacroContent:
|
||||
self.add_tag_to_html(f"/{tag}")
|
||||
|
||||
def handle_span_end(self):
|
||||
if self.state == WikiCleaner.State.InEditSpan:
|
||||
self.depth_in_span -= 1
|
||||
if self.depth_in_span <= 0:
|
||||
self.depth_in_span = 0
|
||||
self.state = self.previous_state
|
||||
else:
|
||||
self.add_tag_to_html(f"/span")
|
||||
|
||||
def handle_div_end(self):
|
||||
if self.state == WikiCleaner.State.InMacroContent:
|
||||
self.depth_in_div -= 1
|
||||
if self.depth_in_div <= 0:
|
||||
self.depth_in_div = 0
|
||||
self.state = WikiCleaner.State.AfterMacroContent
|
||||
self.final_html += "</body></html>"
|
||||
else:
|
||||
self.add_tag_to_html(f"/div")
|
||||
|
||||
def handle_table_end(self):
|
||||
if self.state == WikiCleaner.State.InTable:
|
||||
self.depth_in_table -= 1
|
||||
if self.depth_in_table <= 0:
|
||||
self.depth_in_table = 0
|
||||
self.state = self.previous_state
|
||||
|
||||
def handle_data(self, data):
|
||||
if self.state == WikiCleaner.State.InMacroContent:
|
||||
self.final_html += data
|
||||
|
||||
@@ -49,8 +49,6 @@ except ImportError:
|
||||
|
||||
translate = fci.translate
|
||||
|
||||
show_javascript_console_output = False
|
||||
|
||||
|
||||
class PackageDetails(QtWidgets.QWidget):
|
||||
"""The PackageDetails QWidget shows package README information and provides
|
||||
@@ -90,7 +88,7 @@ class PackageDetails(QtWidgets.QWidget):
|
||||
|
||||
# If this is the same repo we were already showing, we do not have to do the
|
||||
# expensive refetch unless reload is true
|
||||
if self.repo != repo or reload:
|
||||
if True or self.repo != repo or reload:
|
||||
self.repo = repo
|
||||
|
||||
if self.worker is not None:
|
||||
|
||||
Reference in New Issue
Block a user