917 lines
36 KiB
Python
917 lines
36 KiB
Python
# -*- 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("<html><body>Loading...</body></html>")
|
|
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 = "<h3>"
|
|
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 += (
|
|
"<b>"
|
|
+ translate(
|
|
"AddonsInstaller",
|
|
"On branch {}, update available to version",
|
|
).format(repo.branch)
|
|
+ " "
|
|
)
|
|
installed_version_string += repo.metadata.Version
|
|
installed_version_string += ".</b>"
|
|
elif repo.macro and repo.macro.version:
|
|
installed_version_string += (
|
|
"<b>"
|
|
+ translate("AddonsInstaller", "Update available to version")
|
|
+ " "
|
|
)
|
|
installed_version_string += repo.macro.version
|
|
installed_version_string += ".</b>"
|
|
else:
|
|
installed_version_string += (
|
|
"<b>"
|
|
+ translate(
|
|
"AddonsInstaller",
|
|
"An update is available",
|
|
)
|
|
+ ".</b>"
|
|
)
|
|
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 += "</h3>"
|
|
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(
|
|
"<h1>"
|
|
+ translate("AddonsInstaller", "WARNING: This addon is obsolete")
|
|
+ "</h1>"
|
|
)
|
|
self.ui.labelWarningInfo.setStyleSheet(
|
|
"color:" + utils.warning_color_string()
|
|
)
|
|
elif repo.python2:
|
|
self.ui.labelWarningInfo.show()
|
|
self.ui.labelWarningInfo.setText(
|
|
"<h1>"
|
|
+ translate("AddonsInstaller", "WARNING: This addon is Python 2 Only")
|
|
+ "</h1>"
|
|
)
|
|
self.ui.labelWarningInfo.setStyleSheet(
|
|
"color:" + utils.warning_color_string()
|
|
)
|
|
elif required_version:
|
|
self.ui.labelWarningInfo.show()
|
|
self.ui.labelWarningInfo.setText(
|
|
"<h1>"
|
|
+ translate("AddonsInstaller", "WARNING: This addon requires FreeCAD ")
|
|
+ required_version
|
|
+ "</h1>"
|
|
)
|
|
self.ui.labelWarningInfo.setStyleSheet(
|
|
"color:" + utils.warning_color_string()
|
|
)
|
|
elif repo.is_disabled():
|
|
self.ui.labelWarningInfo.show()
|
|
self.ui.labelWarningInfo.setText(
|
|
"<h2>"
|
|
+ translate(
|
|
"AddonsInstaller",
|
|
"WARNING: This addon is currently installed, but disabled. Use the 'enable' button to re-enable.",
|
|
)
|
|
+ "</h2>"
|
|
)
|
|
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"<html><body><p>{m}</p></body></html>"
|
|
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(
|
|
"<h3>"
|
|
+ translate(
|
|
"AddonsInstaller",
|
|
"This Addon will be enabled next time you restart FreeCAD.",
|
|
)
|
|
+ "</h3>"
|
|
)
|
|
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(
|
|
"<h3>"
|
|
+ translate(
|
|
"AddonsInstaller",
|
|
"This Addon will be disabled next time you restart FreeCAD.",
|
|
)
|
|
+ "</h3>"
|
|
)
|
|
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 = "<h3>"
|
|
installed_version_string += translate(
|
|
"AddonsInstaller", "Changed to git ref '{}' -- please restart to use Addon."
|
|
).format(name)
|
|
installed_version_string += "</h3>"
|
|
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(
|
|
"<h3>"
|
|
+ QCoreApplication.translate(
|
|
"AddonsInstaller",
|
|
"QtWebEngine Python bindings not installed -- using fallback README display. See Report View for details and installation instructions.",
|
|
None,
|
|
)
|
|
+ "</h3>"
|
|
)
|
|
else:
|
|
self.slowLoadLabel.setText(
|
|
QCoreApplication.translate(
|
|
"AddonsInstaller",
|
|
"The page is taking a long time to load... showing the data we have so far...",
|
|
)
|
|
)
|
|
|
|
# retranslateUi
|