# -*- coding: utf-8 -*- # *************************************************************************** # * * # * Copyright (c) 2022 FreeCAD Project Association * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * # * as published by the Free Software Foundation; either version 2 of * # * the License, or (at your option) any later version. * # * for detail see the LICENCE text file. * # * * # * This program 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 Library General Public License for more details. * # * * # * You should have received a copy of the GNU Library General Public * # * License along with this program; if not, write to the Free Software * # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * # * USA * # * * # *************************************************************************** from posixpath import normpath from PySide2.QtCore import * from PySide2.QtGui import * from PySide2.QtWidgets import * import os import FreeCAD import FreeCADGui import addonmanager_utilities as utils from addonmanager_workers import GetMacroDetailsWorker, CheckSingleUpdateWorker from Addon import Addon import NetworkManager from change_branch import ChangeBranchDialog have_git = False try: import git if hasattr(git, "Repo"): have_git = True except ImportError: pass from typing import Optional translate = FreeCAD.Qt.translate show_javascript_console_output = False try: from PySide2.QtWebEngineWidgets import * HAS_QTWEBENGINE = True except Exception: FreeCAD.Console.PrintWarning( translate( "AddonsInstaller", "Addon Manager Warning: Could not import QtWebEngineWidgets, it seems to be missing from your system. Please use your system's package manager to install the python3-pyside2.qtwebengine* and python3-pyside2.qtwebchannel packages, and if possible alert your package creator to the missing dependency. Display of package README will be limited until this dependency is resolved.", ) + "\n" ) HAS_QTWEBENGINE = False class PackageDetails(QWidget): back = Signal() install = Signal(Addon) uninstall = Signal(Addon) update = Signal(Addon) execute = Signal(Addon) update_status = Signal(Addon) check_for_update = Signal(Addon) def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_PackageDetails() self.ui.setupUi(self) self.worker = None self.repo = None self.status_update_thread = None self.ui.buttonBack.clicked.connect(self.back.emit) self.ui.buttonExecute.clicked.connect(lambda: self.execute.emit(self.repo)) self.ui.buttonInstall.clicked.connect(lambda: self.install.emit(self.repo)) self.ui.buttonUninstall.clicked.connect(lambda: self.uninstall.emit(self.repo)) self.ui.buttonUpdate.clicked.connect(lambda: self.update.emit(self.repo)) self.ui.buttonCheckForUpdate.clicked.connect( lambda: self.check_for_update.emit(self.repo) ) self.ui.buttonChangeBranch.clicked.connect(self.change_branch_clicked) self.ui.buttonEnable.clicked.connect(self.enable_clicked) self.ui.buttonDisable.clicked.connect(self.disable_clicked) if HAS_QTWEBENGINE: self.ui.webView.loadStarted.connect(self.load_started) self.ui.webView.loadProgress.connect(self.load_progress) self.ui.webView.loadFinished.connect(self.load_finished) loading_html_file = os.path.join(os.path.dirname(__file__), "loading.html") with open(loading_html_file, "r", errors="ignore") as f: html = f.read() self.ui.loadingLabel.setHtml(html) self.ui.loadingLabel.show() self.ui.webView.hide() def show_repo(self, repo: Addon, reload: bool = False) -> None: # 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: self.repo = repo if HAS_QTWEBENGINE: self.ui.loadingLabel.show() self.ui.slowLoadLabel.hide() self.ui.webView.setHtml("Loading...") self.ui.webView.hide() self.ui.progressBar.show() self.timeout = QTimer.singleShot( 6000, self.long_load_running ) # Six seconds else: self.ui.missingWebViewLabel.setStyleSheet( "color:" + utils.warning_color_string() ) if self.worker is not None: if not self.worker.isFinished(): self.worker.requestInterruption() self.worker.wait() if repo.repo_type == Addon.Kind.MACRO: self.show_macro(repo) self.ui.buttonExecute.show() elif repo.repo_type == Addon.Kind.WORKBENCH: self.show_workbench(repo) self.ui.buttonExecute.hide() elif repo.repo_type == Addon.Kind.PACKAGE: self.show_package(repo) self.ui.buttonExecute.hide() if repo.status() == Addon.Status.UNCHECKED: if not self.status_update_thread: self.status_update_thread = QThread() self.status_update_worker = CheckSingleUpdateWorker(repo) self.status_update_worker.moveToThread(self.status_update_thread) self.status_update_thread.finished.connect( self.status_update_worker.deleteLater ) self.check_for_update.connect(self.status_update_worker.do_work) self.status_update_worker.update_status.connect(self.display_repo_status) self.status_update_thread.start() self.check_for_update.emit(self.repo) self.display_repo_status(self.repo.update_status) def display_repo_status(self, status): repo = self.repo self.set_change_branch_button_state() self.set_disable_button_state() if status != Addon.Status.NOT_INSTALLED: version = repo.installed_version date = "" installed_version_string = "

" if repo.updated_timestamp: date = ( QDateTime.fromTime_t(repo.updated_timestamp) .date() .toString(Qt.SystemLocaleShortDate) ) if version and date: installed_version_string += ( translate( "AddonsInstaller", "Version {version} installed on {date}" ).format(version=version, date=date) + ". " ) elif version: installed_version_string += ( translate("AddonsInstaller", "Version {version} installed") + ". " ).format(version=version) elif date: installed_version_string += ( translate("AddonsInstaller", "Installed on {date}") + ". " ).format(date=date) else: installed_version_string += ( translate("AddonsInstaller", "Installed") + ". " ) if status == Addon.Status.UPDATE_AVAILABLE: if repo.metadata: installed_version_string += ( "" + translate( "AddonsInstaller", "On branch {}, update available to version", ).format(repo.branch) + " " ) installed_version_string += repo.metadata.Version installed_version_string += "." elif repo.macro and repo.macro.version: installed_version_string += ( "" + translate("AddonsInstaller", "Update available to version") + " " ) installed_version_string += repo.macro.version installed_version_string += "." else: installed_version_string += ( "" + translate( "AddonsInstaller", "An update is available", ) + "." ) elif status == Addon.Status.NO_UPDATE_AVAILABLE: detached_head = False branch = repo.branch if have_git and repo.repo_type != Addon.Kind.MACRO: basedir = FreeCAD.getUserAppDataDir() moddir = os.path.join(basedir, "Mod", repo.name) if os.path.exists(os.path.join(moddir, ".git")): gitrepo = git.Repo(moddir) branch = gitrepo.head.ref.name detached_head = gitrepo.head.is_detached if detached_head: installed_version_string += ( translate( "AddonsInstaller", "Git tag '{}' checked out, no updates possible", ).format(branch) + "." ) else: installed_version_string += ( translate( "AddonsInstaller", "This is the latest version available for branch {}", ).format(branch) + "." ) elif status == Addon.Status.PENDING_RESTART: installed_version_string += ( translate( "AddonsInstaller", "Updated, please restart FreeCAD to use" ) + "." ) elif status == Addon.Status.UNCHECKED: pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") autocheck = pref.GetBool("AutoCheck", False) if autocheck: installed_version_string += ( translate("AddonsInstaller", "Update check in progress") + "." ) else: installed_version_string += ( translate("AddonsInstaller", "Automatic update checks disabled") + "." ) installed_version_string += "

" self.ui.labelPackageDetails.setText(installed_version_string) if repo.status() == Addon.Status.UPDATE_AVAILABLE: self.ui.labelPackageDetails.setStyleSheet( "color:" + utils.attention_color_string() ) else: self.ui.labelPackageDetails.setStyleSheet( "color:" + utils.bright_color_string() ) self.ui.labelPackageDetails.show() if repo.macro is not None: moddir = FreeCAD.getUserMacroDir(True) else: basedir = FreeCAD.getUserAppDataDir() moddir = os.path.join(basedir, "Mod", repo.name) installationLocationString = ( translate("AddonsInstaller", "Installation location") + ": " + os.path.normpath(moddir) ) self.ui.labelInstallationLocation.setText(installationLocationString) self.ui.labelInstallationLocation.show() else: self.ui.labelPackageDetails.hide() self.ui.labelInstallationLocation.hide() if status == Addon.Status.NOT_INSTALLED: self.ui.buttonInstall.show() self.ui.buttonUninstall.hide() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() elif status == Addon.Status.NO_UPDATE_AVAILABLE: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() elif status == Addon.Status.UPDATE_AVAILABLE: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.show() self.ui.buttonCheckForUpdate.hide() elif status == Addon.Status.UNCHECKED: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.show() elif status == Addon.Status.PENDING_RESTART: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.hide() self.ui.buttonCheckForUpdate.hide() elif status == Addon.Status.CANNOT_CHECK: self.ui.buttonInstall.hide() self.ui.buttonUninstall.show() self.ui.buttonUpdate.show() self.ui.buttonCheckForUpdate.hide() required_version = self.requires_newer_freecad() if repo.obsolete: self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText( "

" + translate("AddonsInstaller", "WARNING: This addon is obsolete") + "

" ) self.ui.labelWarningInfo.setStyleSheet( "color:" + utils.warning_color_string() ) elif repo.python2: self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText( "

" + translate("AddonsInstaller", "WARNING: This addon is Python 2 Only") + "

" ) self.ui.labelWarningInfo.setStyleSheet( "color:" + utils.warning_color_string() ) elif required_version: self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText( "

" + translate("AddonsInstaller", "WARNING: This addon requires FreeCAD ") + required_version + "

" ) self.ui.labelWarningInfo.setStyleSheet( "color:" + utils.warning_color_string() ) elif repo.is_disabled(): self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText( "

" + translate( "AddonsInstaller", "WARNING: This addon is currently installed, but disabled. Use the 'enable' button to re-enable.", ) + "

" ) self.ui.labelWarningInfo.setStyleSheet( "color:" + utils.warning_color_string() ) else: self.ui.labelWarningInfo.hide() def requires_newer_freecad(self) -> Optional[str]: # If it's not installed, check to see if it's for a newer version of FreeCAD if self.repo.status() == Addon.Status.NOT_INSTALLED and self.repo.metadata: # Only hide if ALL content items require a newer version, otherwise # it's possible that this package actually provides versions of itself # for newer and older versions first_supported_version = ( self.repo.metadata.getFirstSupportedFreeCADVersion() ) if first_supported_version is not None: required_version = first_supported_version.split(".") fc_major = int(FreeCAD.Version()[0]) fc_minor = int(FreeCAD.Version()[1]) if int(required_version[0]) > fc_major: return first_supported_version elif int(required_version[0]) == fc_major and len(required_version) > 1: if int(required_version[1]) > fc_minor: return first_supported_version return None def set_change_branch_button_state(self): """The change branch button is only available for installed Addons that have a .git directory and in runs where the GitPython import is available.""" self.ui.buttonChangeBranch.hide() pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons") show_switcher = pref.GetBool("ShowBranchSwitcher", False) if not show_switcher: return # Is this repo installed? If not, return. if self.repo.status() == Addon.Status.NOT_INSTALLED: return # Is it a Macro? If so, return: if self.repo.repo_type == Addon.Kind.MACRO: return # Can we actually switch branches? If not, return. if not have_git: return # Is there a .git subdirectory? If not, return. basedir = FreeCAD.getUserAppDataDir() path_to_git = os.path.join(basedir, "Mod", self.repo.name, ".git") if not os.path.isdir(path_to_git): return # If all four above checks passed, then it's possible for us to switch # branches, if there are any besides the one we are on: show the button self.ui.buttonChangeBranch.show() def set_disable_button_state(self): self.ui.buttonEnable.hide() self.ui.buttonDisable.hide() status = self.repo.status() if status != Addon.Status.NOT_INSTALLED: disabled = self.repo.is_disabled() if disabled: self.ui.buttonEnable.show() else: self.ui.buttonDisable.show() def show_workbench(self, repo: Addon) -> None: """loads information of a given workbench""" url = utils.get_readme_html_url(repo) if HAS_QTWEBENGINE: self.ui.webView.load(QUrl(url)) self.ui.urlBar.setText(url) else: readme_data = NetworkManager.AM_NETWORK_MANAGER.blocking_get(url) text = readme_data.data().decode("utf8") self.ui.textBrowserReadMe.setHtml(text) def show_package(self, repo: Addon) -> None: """Show the details for a package (a repo with a package.xml metadata file)""" readme_url = None if repo.metadata: urls = repo.metadata.Urls for url in urls: if url["type"] == "readme": readme_url = url["location"] break if not readme_url: readme_url = utils.get_readme_html_url(repo) if HAS_QTWEBENGINE: self.ui.webView.load(QUrl(readme_url)) self.ui.urlBar.setText(readme_url) else: readme_data = NetworkManager.AM_NETWORK_MANAGER.blocking_get(readme_url) text = readme_data.data().decode("utf8") self.ui.textBrowserReadMe.setHtml(text) def show_macro(self, repo: Addon) -> None: """loads information of a given macro""" if not repo.macro.url: # We need to populate the macro information... may as well do it while the user reads the wiki page self.worker = GetMacroDetailsWorker(repo) self.worker.readme_updated.connect(self.macro_readme_updated) self.worker.start() else: self.macro_readme_updated() def macro_readme_updated(self): url = self.repo.macro.wiki if not url: url = self.repo.macro.url if HAS_QTWEBENGINE: if url: self.ui.webView.load(QUrl(url)) self.ui.urlBar.setText(url) else: self.ui.urlBar.setText( "(" + translate( "AddonsInstaller", "No URL or wiki page provided by this macro" ) + ")" ) else: if url: readme_data = NetworkManager.AM_NETWORK_MANAGER.blocking_get(url) text = readme_data.data().decode("utf8") self.ui.textBrowserReadMe.setHtml(text) else: self.ui.textBrowserReadMe.setHtml( "(" + translate( "AddonsInstaller", "No URL or wiki page provided by this macro" ) + ")" ) def run_javascript(self): """Modify the page for a README to optimize for viewing in a smaller window""" s = """ ( function() { const url = new URL (window.location); const body = document.getElementsByTagName("body")[0]; if (url.hostname === "github.com") { const articles = document.getElementsByTagName("article"); if (articles.length > 0) { const article = articles[0]; body.appendChild (article); body.style.padding = "1em"; let sibling = article.previousSibling; while (sibling) { sibling.remove(); sibling = article.previousSibling; } } } else if (url.hostname === "gitlab.com" || url.hostname === "framagit.org" || url.hostname === "salsa.debian.org") { // These all use the GitLab page display... const articles = document.getElementsByTagName("article"); if (articles.length > 0) { const article = articles[0]; body.appendChild (article); body.style.padding = "1em"; let sibling = article.previousSibling; while (sibling) { sibling.remove(); sibling = article.previousSibling; } } } else if (url.hostname === "wiki.freecad.org" || url.hostname === "wiki.freecadweb.org") { const first_heading = document.getElementById('firstHeading'); const body_content = document.getElementById('bodyContent'); const new_node = document.createElement("div"); new_node.appendChild(first_heading); new_node.appendChild(body_content); body.appendChild(new_node); let sibling = new_node.previousSibling; while (sibling) { sibling.remove(); sibling = new_node.previousSibling; } } } ) () """ self.ui.webView.page().runJavaScript(s) def load_started(self): self.ui.progressBar.show() self.ui.progressBar.setValue(0) def load_progress(self, progress: int): self.ui.progressBar.setValue(progress) def load_finished(self, load_succeeded: bool): self.ui.loadingLabel.hide() self.ui.slowLoadLabel.hide() self.ui.webView.show() self.ui.progressBar.hide() url = self.ui.webView.url() if ( hasattr(self, "timeout") and hasattr(self.timeout, "isActive") and self.timeout.isActive() ): self.timeout.stop() if load_succeeded: # It says it succeeded, but it might have only succeeded in loading a "Page not found" page! title = self.ui.webView.title() path_components = url.path().split("/") expected_content = path_components[-1] if url.host() == "github.com" and expected_content not in title: self.show_error_for(url) elif title == "": self.show_error_for(url) else: self.run_javascript() else: self.show_error_for(url) def long_load_running(self): if hasattr(self.ui, "webView") and self.ui.webView.isHidden(): self.ui.slowLoadLabel.show() self.ui.loadingLabel.hide() self.ui.webView.show() def show_error_for(self, url: QUrl) -> None: m = translate( "AddonsInstaller", "Could not load README data from URL {}" ).format(url.toString()) html = f"

{m}

" self.ui.webView.setHtml(html) def change_branch_clicked(self) -> None: basedir = FreeCAD.getUserAppDataDir() path_to_repo = os.path.join(basedir, "Mod", self.repo.name) change_branch_dialog = ChangeBranchDialog(path_to_repo, self) change_branch_dialog.branch_changed.connect(self.branch_changed) change_branch_dialog.exec() def enable_clicked(self) -> None: self.repo.enable() self.repo.set_status(Addon.Status.PENDING_RESTART) self.set_disable_button_state() self.update_status.emit(self.repo) self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText( "

" + translate( "AddonsInstaller", "This Addon will be enabled next time you restart FreeCAD.", ) + "

" ) self.ui.labelWarningInfo.setStyleSheet("color:" + utils.bright_color_string()) def disable_clicked(self) -> None: self.repo.disable() self.repo.set_status(Addon.Status.PENDING_RESTART) self.set_disable_button_state() self.update_status.emit(self.repo) self.ui.labelWarningInfo.show() self.ui.labelWarningInfo.setText( "

" + translate( "AddonsInstaller", "This Addon will be disabled next time you restart FreeCAD.", ) + "

" ) self.ui.labelWarningInfo.setStyleSheet( "color:" + utils.attention_color_string() ) def branch_changed(self, name: str) -> None: QMessageBox.information( self, translate("AddonsInstaller", "Success"), translate( "AddonsInstaller", "Branch change succeeded, please restart to use the new version.", ), ) # See if this branch has a package.xml file: basedir = FreeCAD.getUserAppDataDir() path_to_metadata = os.path.join(basedir, "Mod", self.repo.name, "package.xml") if os.path.isfile(path_to_metadata): self.repo.load_metadata_file(path_to_metadata) self.repo.installed_version = self.repo.metadata.Version else: self.repo.repo_type = Addon.Kind.WORKBENCH self.repo.metadata = None self.repo.installed_version = None self.repo.updated_timestamp = QDateTime.currentDateTime().toSecsSinceEpoch() self.repo.branch = name self.repo.set_status(Addon.Status.PENDING_RESTART) installed_version_string = "

" installed_version_string += translate( "AddonsInstaller", "Changed to git ref '{}' -- please restart to use Addon." ).format(name) installed_version_string += "

" self.ui.labelPackageDetails.setText(installed_version_string) self.ui.labelPackageDetails.setStyleSheet( "color:" + utils.attention_color_string() ) self.update_status.emit(self.repo) if HAS_QTWEBENGINE: class RestrictedWebPage(QWebEnginePage): """A class that follows links to FreeCAD wiki pages, but opens all other clicked links in the system web browser""" def __init__(self, parent): super().__init__(parent) self.settings().setAttribute(QWebEngineSettings.ErrorPageEnabled, False) def acceptNavigationRequest(self, url, _type, isMainFrame): if _type == QWebEnginePage.NavigationTypeLinkClicked: # See if the link is to a FreeCAD Wiki page -- if so, follow it, otherwise ask the OS to open it if ( url.host() == "wiki.freecad.org" or url.host() == "wiki.freecadweb.org" ): return super().acceptNavigationRequest(url, _type, isMainFrame) else: QDesktopServices.openUrl(url) return False return super().acceptNavigationRequest(url, _type, isMainFrame) def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID): global show_javascript_console_output if show_javascript_console_output: tag = translate("AddonsInstaller", "Page JavaScript reported") if level == QWebEnginePage.InfoMessageLevel: FreeCAD.Console.PrintMessage(f"{tag} {lineNumber}: {message}\n") elif level == QWebEnginePage.WarningMessageLevel: FreeCAD.Console.PrintWarning(f"{tag} {lineNumber}: {message}\n") elif level == QWebEnginePage.ErrorMessageLevel: FreeCAD.Console.PrintError(f"{tag} {lineNumber}: {message}\n") class Ui_PackageDetails(object): def setupUi(self, PackageDetails): if not PackageDetails.objectName(): PackageDetails.setObjectName("PackageDetails") self.verticalLayout_2 = QVBoxLayout(PackageDetails) self.verticalLayout_2.setObjectName("verticalLayout_2") self.layoutDetailsBackButton = QHBoxLayout() self.layoutDetailsBackButton.setObjectName("layoutDetailsBackButton") self.buttonBack = QToolButton(PackageDetails) self.buttonBack.setObjectName("buttonBack") self.buttonBack.setIcon( QIcon.fromTheme("back", QIcon(":/icons/button_left.svg")) ) self.layoutDetailsBackButton.addWidget(self.buttonBack) self.horizontalSpacer = QSpacerItem( 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum ) self.layoutDetailsBackButton.addItem(self.horizontalSpacer) self.buttonInstall = QPushButton(PackageDetails) self.buttonInstall.setObjectName("buttonInstall") self.layoutDetailsBackButton.addWidget(self.buttonInstall) self.buttonUninstall = QPushButton(PackageDetails) self.buttonUninstall.setObjectName("buttonUninstall") self.layoutDetailsBackButton.addWidget(self.buttonUninstall) self.buttonUpdate = QPushButton(PackageDetails) self.buttonUpdate.setObjectName("buttonUpdate") self.layoutDetailsBackButton.addWidget(self.buttonUpdate) self.buttonCheckForUpdate = QPushButton(PackageDetails) self.buttonCheckForUpdate.setObjectName("buttonCheckForUpdate") self.layoutDetailsBackButton.addWidget(self.buttonCheckForUpdate) self.buttonChangeBranch = QPushButton(PackageDetails) self.buttonChangeBranch.setObjectName("buttonChangeBranch") self.layoutDetailsBackButton.addWidget(self.buttonChangeBranch) self.buttonExecute = QPushButton(PackageDetails) self.buttonExecute.setObjectName("buttonExecute") self.layoutDetailsBackButton.addWidget(self.buttonExecute) self.buttonDisable = QPushButton(PackageDetails) self.buttonDisable.setObjectName("buttonDisable") self.layoutDetailsBackButton.addWidget(self.buttonDisable) self.buttonEnable = QPushButton(PackageDetails) self.buttonEnable.setObjectName("buttonEnable") self.layoutDetailsBackButton.addWidget(self.buttonEnable) self.verticalLayout_2.addLayout(self.layoutDetailsBackButton) self.labelPackageDetails = QLabel(PackageDetails) self.labelPackageDetails.hide() self.verticalLayout_2.addWidget(self.labelPackageDetails) self.labelInstallationLocation = QLabel(PackageDetails) self.labelInstallationLocation.setTextInteractionFlags(Qt.TextSelectableByMouse) self.labelInstallationLocation.hide() self.verticalLayout_2.addWidget(self.labelInstallationLocation) self.labelWarningInfo = QLabel(PackageDetails) self.labelWarningInfo.hide() self.verticalLayout_2.addWidget(self.labelWarningInfo) sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) if HAS_QTWEBENGINE: self.webView = QWebEngineView(PackageDetails) self.webView.setObjectName("webView") self.webView.setSizePolicy(sizePolicy1) self.webView.setPage(RestrictedWebPage(PackageDetails)) self.verticalLayout_2.addWidget(self.webView) self.loadingLabel = QWebEngineView(PackageDetails) self.loadingLabel.setObjectName("loadingLabel") self.loadingLabel.setSizePolicy(sizePolicy1) self.verticalLayout_2.addWidget(self.loadingLabel) self.slowLoadLabel = QLabel(PackageDetails) self.slowLoadLabel.setObjectName("slowLoadLabel") self.verticalLayout_2.addWidget(self.slowLoadLabel) self.progressBar = QProgressBar(PackageDetails) self.progressBar.setObjectName("progressBar") self.progressBar.setTextVisible(False) self.verticalLayout_2.addWidget(self.progressBar) self.urlBar = QLineEdit(PackageDetails) self.urlBar.setObjectName("urlBar") self.urlBar.setReadOnly(True) self.verticalLayout_2.addWidget(self.urlBar) else: self.missingWebViewLabel = QLabel(PackageDetails) self.missingWebViewLabel.setObjectName("missingWebViewLabel") self.missingWebViewLabel.setWordWrap(True) self.verticalLayout_2.addWidget(self.missingWebViewLabel) self.textBrowserReadMe = QTextBrowser(PackageDetails) self.textBrowserReadMe.setObjectName("textBrowserReadMe") self.textBrowserReadMe.setOpenExternalLinks(True) self.textBrowserReadMe.setOpenLinks(True) self.verticalLayout_2.addWidget(self.textBrowserReadMe) self.retranslateUi(PackageDetails) QMetaObject.connectSlotsByName(PackageDetails) # setupUi def retranslateUi(self, _): self.buttonBack.setText("") self.buttonInstall.setText( QCoreApplication.translate("AddonsInstaller", "Install", None) ) self.buttonUninstall.setText( QCoreApplication.translate("AddonsInstaller", "Uninstall", None) ) self.buttonUpdate.setText( QCoreApplication.translate("AddonsInstaller", "Update", None) ) self.buttonCheckForUpdate.setText( QCoreApplication.translate("AddonsInstaller", "Check for Update", None) ) self.buttonExecute.setText( QCoreApplication.translate("AddonsInstaller", "Run Macro", None) ) self.buttonChangeBranch.setText( QCoreApplication.translate("AddonsInstaller", "Change Branch", None) ) self.buttonEnable.setText( QCoreApplication.translate("AddonsInstaller", "Enable", None) ) self.buttonDisable.setText( QCoreApplication.translate("AddonsInstaller", "Disable", None) ) self.buttonBack.setToolTip( QCoreApplication.translate( "AddonsInstaller", "Return to package list", None ) ) if not HAS_QTWEBENGINE: self.missingWebViewLabel.setText( "

" + QCoreApplication.translate( "AddonsInstaller", "QtWebEngine Python bindings not installed -- using fallback README display. See Report View for details and installation instructions.", None, ) + "

" ) else: self.slowLoadLabel.setText( QCoreApplication.translate( "AddonsInstaller", "The page is taking a long time to load... showing the data we have so far...", ) ) # retranslateUi