From 6f1adaad4345f1c3b4839d4a757e1a93ab2dd003 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 19 Oct 2023 15:48:28 +0200 Subject: [PATCH] Make Conan/Python installs available for whole project and not just the AboutDialog Generation of dependency list now happens in Also cleaned up the AboutDialog.qml CURA-10561 --- .github/workflows/linux.yml | 20 +- .github/workflows/macos.yml | 20 +- .github/workflows/windows.yml | 20 +- .gitignore | 1 - AboutDialogVersionsList.qml.jinja | 61 ----- CuraVersion.py.jinja | 5 +- conanfile.py | 135 ++--------- cura/ApplicationMetadata.py | 32 ++- cura/CuraApplication.py | 18 +- resources/qml/Dialogs/AboutDialog.qml | 330 +++++++++++++++----------- 10 files changed, 280 insertions(+), 362 deletions(-) delete mode 100644 AboutDialogVersionsList.qml.jinja diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 719a07250cb..1830a023c43 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -155,7 +155,7 @@ jobs: run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache" - name: Create the Packages (Bash) - run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING --json "cura_inst/conan_install_info.json" + run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING - name: Upload the Package(s) if: always() @@ -206,12 +206,7 @@ jobs: import json from pathlib import Path - conan_install_info_path = Path("cura_inst/conan_install_info.json") - conan_info = {"installed": []} - if os.path.exists(conan_install_info_path): - with open(conan_install_info_path, "r") as f: - conan_info = json.load(f) - sorted_deps = sorted([dep["recipe"]["id"].replace('#', r' rev: ') for dep in conan_info["installed"]]) + from cura.CuraVersion import ConanInstalls summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" @@ -223,14 +218,17 @@ jobs: f.write(content) f.writelines("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n") f.writelines("## Conan packages:\n") - for dep in sorted_deps: - f.writelines(f"`{dep}`\n") + for dep_name, dep_info in ConanDependencies.items(): + f.writelines(f"`{dep_name} {dep_info["version"]} {dep_info["revision"]}`\n") - name: Summarize the used Python modules shell: python run: | import os import pkg_resources + + from cura.CuraVersion import ConanDependencies + summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" if os.path.exists(summary_env): @@ -240,8 +238,8 @@ jobs: with open(summary_env, "w") as f: f.write(content) f.writelines("## Python modules:\n") - for package in pkg_resources.working_set: - f.writelines(f"`{package.key}/{package.version}`\n") + for dep_name, dep_info in ConanDependencies.items(): + f.writelines(f"`{dep_name} {dep_info["version"]}`\n") - name: Create the Linux AppImage (Bash) run: | diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 01a64f5180a..b8ade8f4dae 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -155,7 +155,7 @@ jobs: run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache" - name: Create the Packages (Bash) - run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING --json "cura_inst/conan_install_info.json" + run: conan install $CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$ENTERPRISE -o cura:staging=$STAGING" - name: Upload the Package(s) if: ${{ inputs.operating_system != 'self-hosted' }} @@ -210,12 +210,7 @@ jobs: import json from pathlib import Path - conan_install_info_path = Path("cura_inst/conan_install_info.json") - conan_info = {"installed": []} - if os.path.exists(conan_install_info_path): - with open(conan_install_info_path, "r") as f: - conan_info = json.load(f) - sorted_deps = sorted([dep["recipe"]["id"].replace('#', r' rev: ') for dep in conan_info["installed"]]) + from cura.CuraVersion import ConanInstalls summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" @@ -227,14 +222,17 @@ jobs: f.write(content) f.writelines("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n") f.writelines("## Conan packages:\n") - for dep in sorted_deps: - f.writelines(f"`{dep}`\n") + for dep_name, dep_info in ConanDependencies.items(): + f.writelines(f"`{dep_name} {dep_info["version"]} {dep_info["revision"]}`\n") - name: Summarize the used Python modules shell: python run: | import os import pkg_resources + + from cura.CuraVersion import PythonInstalls + summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" if os.path.exists(summary_env): @@ -244,8 +242,8 @@ jobs: with open(summary_env, "w") as f: f.write(content) f.writelines("## Python modules:\n") - for package in pkg_resources.working_set: - f.writelines(f"`{package.key}/{package.version}`\n") + for dep_name, dep_info in ConanDependencies.items(): + f.writelines(f"`{dep_name} {dep_info["version"]}`\n") - name: Create the Macos dmg (Bash) run: python ../cura_inst/packaging/MacOS/build_macos.py --source_path ../cura_inst --dist_path . --cura_conan_version $CURA_CONAN_VERSION --filename "${{ steps.filename.outputs.INSTALLER_FILENAME }}" --build_dmg --build_pkg --app_name "$CURA_APP_NAME" diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 9c9775cae77..067d811e9ff 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -122,7 +122,7 @@ jobs: run: conan config set storage.download_cache="C:\Users\runneradmin\.conan\conan_download_cache" - name: Create the Packages (Powershell) - run: conan install $Env:CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$Env:ENTERPRISE -o cura:staging=$Env:STAGING --json "cura_inst/conan_install_info.json" + run: conan install $Env:CURA_CONAN_VERSION ${{ inputs.conan_args }} --build=missing --update -if cura_inst -g VirtualPythonEnv -o cura:enterprise=$Env:ENTERPRISE -o cura:staging=$Env:STAGING - name: Upload the Package(s) if: always() @@ -169,12 +169,7 @@ jobs: import json from pathlib import Path - conan_install_info_path = Path("cura_inst/conan_install_info.json") - conan_info = {"installed": []} - if os.path.exists(conan_install_info_path): - with open(conan_install_info_path, "r") as f: - conan_info = json.load(f) - sorted_deps = sorted([dep["recipe"]["id"].replace('#', r' rev: ') for dep in conan_info["installed"]]) + from cura.CuraVersion import ConanInstalls summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" @@ -186,14 +181,17 @@ jobs: f.write(content) f.writelines("# ${{ steps.filename.outputs.INSTALLER_FILENAME }}\n") f.writelines("## Conan packages:\n") - for dep in sorted_deps: - f.writelines(f"`{dep}`\n") + for dep_name, dep_info in ConanDependencies.items(): + f.writelines(f"`{dep_name} {dep_info["version"]} {dep_info["revision"]}`\n") - name: Summarize the used Python modules shell: python run: | import os import pkg_resources + + from cura.CuraVersion import PythonInstalls + summary_env = os.environ["GITHUB_STEP_SUMMARY"] content = "" if os.path.exists(summary_env): @@ -203,8 +201,8 @@ jobs: with open(summary_env, "w") as f: f.write(content) f.writelines("## Python modules:\n") - for package in pkg_resources.working_set: - f.writelines(f"`{package.key}/{package.version}`\n") + for dep_name, dep_info in ConanDependencies.items(): + f.writelines(f"`{dep_name} {dep_info["version"]}`\n") - name: Create PFX certificate from BASE64_PFX_CONTENT secret id: create-pfx diff --git a/.gitignore b/.gitignore index f1a72d342eb..0290869b41e 100644 --- a/.gitignore +++ b/.gitignore @@ -101,7 +101,6 @@ graph_info.json Ultimaker-Cura.spec .run/ /printer-linter/src/printerlinter.egg-info/ -/resources/qml/Dialogs/AboutDialogVersionsList.qml /plugins/CuraEngineGradualFlow /resources/bundled_packages/bundled_*.json curaengine_plugin_gradual_flow diff --git a/AboutDialogVersionsList.qml.jinja b/AboutDialogVersionsList.qml.jinja deleted file mode 100644 index 05034696602..00000000000 --- a/AboutDialogVersionsList.qml.jinja +++ /dev/null @@ -1,61 +0,0 @@ -import QtQuick 2.2 -import QtQuick.Controls 2.9 - -import UM 1.6 as UM -import Cura 1.5 as Cura - - -ListView -{ - id: projectBuildInfoList - visible: false - anchors.top: creditsNotes.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - height: base.height - y - (2 * UM.Theme.getSize("default_margin").height + closeButton.height) - - ScrollBar.vertical: UM.ScrollBar - { - id: projectBuildInfoListScrollBar - } - - delegate: Row - { - spacing: UM.Theme.getSize("narrow_margin").width - UM.Label - { - text: (model.name) - width: (projectBuildInfoList.width* 0.4) | 0 - elide: Text.ElideRight - } - UM.Label - { - text: (model.version) - width: (projectBuildInfoList.width *0.6) | 0 - elide: Text.ElideRight - } - - } - model: ListModel - { - id: developerInfo - } - Component.onCompleted: - { - var conan_installs = {{ conan_installs }}; - var python_installs = {{ python_installs }}; - developerInfo.append({ name : "

Conan Installs

", version : '' }); - for (var n in conan_installs) - { - developerInfo.append({ name : conan_installs[n][0], version : conan_installs[n][1] }); - } - developerInfo.append({ name : '', version : '' }); - developerInfo.append({ name : "

Python Installs

", version : '' }); - for (var n in python_installs) - { - developerInfo.append({ name : python_installs[n][0], version : python_installs[n][1] }); - } - - } -} - diff --git a/CuraVersion.py.jinja b/CuraVersion.py.jinja index 87ef7d205d9..515293b8af0 100644 --- a/CuraVersion.py.jinja +++ b/CuraVersion.py.jinja @@ -1,4 +1,4 @@ -# Copyright (c) 2022 UltiMaker +# Copyright (c) 2023 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. CuraAppName = "{{ cura_app_name }}" @@ -12,3 +12,6 @@ CuraCloudAccountAPIRoot = "{{ cura_cloud_account_api_root }}" CuraMarketplaceRoot = "{{ cura_marketplace_root }}" CuraDigitalFactoryURL = "{{ cura_digital_factory_url }}" CuraLatestURL = "{{ cura_latest_url }}" + +ConanInstalls = {{ conan_installs }} +PythonInstalls = {{ python_installs }} diff --git a/conanfile.py b/conanfile.py index 4c6138656b2..fc56d5033c8 100644 --- a/conanfile.py +++ b/conanfile.py @@ -137,18 +137,21 @@ def _pyinstaller_spec_arch(self): return "'x86_64'" return "None" - def _generate_about_versions(self, location): - with open(os.path.join(self.recipe_folder, "AboutDialogVersionsList.qml.jinja"), "r") as f: - cura_version_py = Template(f.read()) + def _conan_installs(self): + conan_installs = {} - conan_installs = [] - python_installs = [] + # list of conan installs + for dependency in self.dependencies.host.values(): + conan_installs[dependency.ref.name] = { + "version": dependency.ref.version, + "revision": dependency.ref.revision + } + return conan_installs - # list of conan installs - for _, dependency in self.dependencies.host.items(): - conan_installs.append([dependency.ref.name,dependency.ref.version]) + def _python_installs(self): + python_installs = {} - #list of python installs + # list of python installs outer = '"' if self.settings.os == "Windows" else "'" inner = "'" if self.settings.os == "Windows" else '"' python_ins_cmd = f"python -c {outer}import pkg_resources; print({inner};{inner}.join([(s.key+{inner},{inner}+ s.version) for s in pkg_resources.working_set])){outer}" @@ -157,16 +160,12 @@ def _generate_about_versions(self, location): self.run(python_ins_cmd, run_environment= True, env = "conanrun", output=buffer) packages = str(buffer.getvalue()).split("-----------------\n") - package = packages[1].strip('\r\n').split(";") - for pack in package: - python_installs.append(pack.split(",")) - - with open(os.path.join(location, "AboutDialogVersionsList.qml"), "w") as f: - f.write(cura_version_py.render( - conan_installs = conan_installs, - python_installs = python_installs - )) + packages = packages[1].strip('\r\n').split(";") + for package in packages: + name, version = package.split(",") + python_installs[name] = {"version": version} + return python_installs def _generate_cura_version(self, location): with open(os.path.join(self.recipe_folder, "CuraVersion.py.jinja"), "r") as f: @@ -192,89 +191,9 @@ def _generate_cura_version(self, location): cura_cloud_account_api_root = self.conan_data["urls"][self._urls]["cloud_account_api_root"], cura_marketplace_root = self.conan_data["urls"][self._urls]["marketplace_root"], cura_digital_factory_url = self.conan_data["urls"][self._urls]["digital_factory_url"], - cura_latest_url = self.conan_data["urls"][self._urls]["cura_latest_url"])) - - def _generate_pyinstaller_spec(self, location, entrypoint_location, icon_path, entitlements_file): - pyinstaller_metadata = self.conan_data["pyinstaller"] - datas = [(str(self._base_dir.joinpath("conan_install_info.json")), ".")] - for data in pyinstaller_metadata["datas"].values(): - if not self.options.internal and data.get("internal", False): - continue - - if "package" in data: # get the paths from conan package - if data["package"] == self.name: - if self.in_local_cache: - src_path = os.path.join(self.package_folder, data["src"]) - else: - src_path = os.path.join(self.source_folder, data["src"]) - else: - src_path = os.path.join(self.deps_cpp_info[data["package"]].rootpath, data["src"]) - elif "root" in data: # get the paths relative from the install folder - src_path = os.path.join(self.install_folder, data["root"], data["src"]) - else: - continue - if Path(src_path).exists(): - datas.append((str(src_path), data["dst"])) - - binaries = [] - for binary in pyinstaller_metadata["binaries"].values(): - if "package" in binary: # get the paths from conan package - src_path = os.path.join(self.deps_cpp_info[binary["package"]].rootpath, binary["src"]) - elif "root" in binary: # get the paths relative from the sourcefolder - src_path = str(self.source_path.joinpath(binary["root"], binary["src"])) - if self.settings.os == "Windows": - src_path = src_path.replace("\\", "\\\\") - else: - continue - if not Path(src_path).exists(): - self.output.warning(f"Source path for binary {binary['binary']} does not exist") - continue - - for bin in Path(src_path).glob(binary["binary"] + "*[.exe|.dll|.so|.dylib|.so.]*"): - binaries.append((str(bin), binary["dst"])) - for bin in Path(src_path).glob(binary["binary"]): - binaries.append((str(bin), binary["dst"])) - - # Make sure all Conan dependencies which are shared are added to the binary list for pyinstaller - for _, dependency in self.dependencies.host.items(): - for bin_paths in dependency.cpp_info.bindirs: - binaries.extend([(f"{p}", ".") for p in Path(bin_paths).glob("**/*.dll")]) - for lib_paths in dependency.cpp_info.libdirs: - binaries.extend([(f"{p}", ".") for p in Path(lib_paths).glob("**/*.so*")]) - binaries.extend([(f"{p}", ".") for p in Path(lib_paths).glob("**/*.dylib*")]) - - # Copy dynamic libs from lib path - binaries.extend([(f"{p}", ".") for p in Path(self._base_dir.joinpath("lib")).glob("**/*.dylib*")]) - binaries.extend([(f"{p}", ".") for p in Path(self._base_dir.joinpath("lib")).glob("**/*.so*")]) - - # Collect all dll's from PyQt6 and place them in the root - binaries.extend([(f"{p}", ".") for p in Path(self._site_packages, "PyQt6", "Qt6").glob("**/*.dll")]) - - with open(os.path.join(self.recipe_folder, "UltiMaker-Cura.spec.jinja"), "r") as f: - pyinstaller = Template(f.read()) - - version = self.conf_info.get("user.cura:version", default = self.version, check_type = str) - cura_version = Version(version) - - with open(os.path.join(location, "UltiMaker-Cura.spec"), "w") as f: - f.write(pyinstaller.render( - name = str(self.options.display_name).replace(" ", "-"), - display_name = self._app_name, - entrypoint = entrypoint_location, - datas = datas, - binaries = binaries, - venv_script_path = str(self._script_dir), - hiddenimports = pyinstaller_metadata["hiddenimports"], - collect_all = pyinstaller_metadata["collect_all"], - icon = icon_path, - entitlements_file = entitlements_file, - osx_bundle_identifier = "'nl.ultimaker.cura'" if self.settings.os == "Macos" else "None", - upx = str(self.settings.os == "Windows"), - strip = False, # This should be possible on Linux and MacOS but, it can also cause issues on some distributions. Safest is to disable it for now - target_arch = self._pyinstaller_spec_arch, - macos = self.settings.os == "Macos", - version = f"'{version}'", - short_version = f"'{cura_version.major}.{cura_version.minor}.{cura_version.patch}'", + cura_latest_url=self.conan_data["urls"][self._urls]["cura_latest_url"], + conan_installs=self._conan_installs(), + python_installs=self._python_installs(), )) def export_sources(self): @@ -346,7 +265,6 @@ def generate(self): vr.generate() self._generate_cura_version(os.path.join(self.source_folder, "cura")) - self._generate_about_versions(os.path.join(self.source_folder, "resources","qml", "Dialogs")) if not self.in_local_cache: # Copy CuraEngine.exe to bindirs of Virtual Python Environment @@ -387,12 +305,6 @@ def generate(self): copy(self, "*", cura_private_data.resdirs[0], str(self._share_dir.joinpath("cura"))) if self.options.devtools: - entitlements_file = "'{}'".format(os.path.join(self.source_folder, "packaging", "MacOS", "cura.entitlements")) - self._generate_pyinstaller_spec(location = self.generators_folder, - entrypoint_location = "'{}'".format(os.path.join(self.source_folder, self.conan_data["pyinstaller"]["runinfo"]["entrypoint"])).replace("\\", "\\\\"), - icon_path = "'{}'".format(os.path.join(self.source_folder, "packaging", self.conan_data["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"), - entitlements_file = entitlements_file if self.settings.os == "Macos" else "None") - # Update the po and pot files if self.settings.os != "Windows" or self.conf.get("tools.microsoft.bash:path", check_type=str): vb = VirtualBuildEnv(self) @@ -451,13 +363,6 @@ def deploy(self): save(self, os.path.join(self._script_dir, f"activate_github_actions_version_env{ext}"), activate_github_actions_version_env) self._generate_cura_version(os.path.join(self._site_packages, "cura")) - self._generate_about_versions(str(self._share_dir.joinpath("cura", "resources", "qml", "Dialogs"))) - - entitlements_file = "'{}'".format(Path(self.cpp_info.res_paths[2], "MacOS", "cura.entitlements")) - self._generate_pyinstaller_spec(location = self._base_dir, - entrypoint_location = "'{}'".format(os.path.join(self.package_folder, self.cpp_info.bindirs[0], self.conan_data["pyinstaller"]["runinfo"]["entrypoint"])).replace("\\", "\\\\"), - icon_path = "'{}'".format(os.path.join(self.package_folder, self.cpp_info.resdirs[2], self.conan_data["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"), - entitlements_file = entitlements_file if self.settings.os == "Macos" else "None") def package(self): copy(self, "cura_app.py", src = self.source_folder, dst = os.path.join(self.package_folder, self.cpp.package.bindirs[0])) diff --git a/cura/ApplicationMetadata.py b/cura/ApplicationMetadata.py index 96cfa6c64d2..9d399e7ad85 100644 --- a/cura/ApplicationMetadata.py +++ b/cura/ApplicationMetadata.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 UltiMaker +# Copyright (c) 2023 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. # --------- @@ -69,13 +69,25 @@ except ImportError: CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME -DEPENDENCY_INFO = {} + try: - from pathlib import Path - conan_install_info = Path(__file__).parent.parent.joinpath("conan_install_info.json") - if conan_install_info.exists(): - import json - with open(conan_install_info, "r") as f: - DEPENDENCY_INFO = json.loads(f.read()) -except: - pass + from cura.CuraVersion import ConanInstalls + + if type(ConanInstalls) == dict: + CONAN_INSTALLS = ConanInstalls + else: + CONAN_INSTALLS = {} + +except ImportError: + CONAN_INSTALLS = {} + +try: + from cura.CuraVersion import PythonInstalls + + if type(PythonInstalls) == dict: + PYTHON_INSTALLS = PythonInstalls + else: + PYTHON_INSTALLS = {} + +except ImportError: + PYTHON_INSTALLS = {} diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index e075fe92f58..b51fbd9d821 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -269,6 +269,9 @@ def __init__(self, *args, **kwargs): CentralFileStorage.setIsEnterprise(ApplicationMetadata.IsEnterpriseVersion) Resources.setIsEnterprise(ApplicationMetadata.IsEnterpriseVersion) + self._conan_installs = ApplicationMetadata.CONAN_INSTALLS + self._python_installs = ApplicationMetadata.PYTHON_INSTALLS + @pyqtProperty(str, constant=True) def ultimakerCloudApiRootUrl(self) -> str: return UltimakerCloudConstants.CuraCloudAPIRoot @@ -851,11 +854,8 @@ def run(self): self._log_hardware_info() - if len(ApplicationMetadata.DEPENDENCY_INFO) > 0: - Logger.debug("Using Conan managed dependencies: " + ", ".join( - [dep["recipe"]["id"] for dep in ApplicationMetadata.DEPENDENCY_INFO["installed"] if dep["recipe"]["version"] != "latest"])) - else: - Logger.warning("Could not find conan_install_info.json") + Logger.debug("Using conan dependencies: {}", str(self.conanInstalls)) + Logger.debug("Using python dependencies: {}", str(self.pythonInstalls)) Logger.log("i", "Initializing machine error checker") self._machine_error_checker = MachineErrorChecker(self) @@ -2130,3 +2130,11 @@ def getInstance(cls, *args, **kwargs) -> "CuraApplication": @pyqtProperty(bool, constant=True) def isEnterprise(self) -> bool: return ApplicationMetadata.IsEnterpriseVersion + + @pyqtProperty("QVariant", constant=True) + def conanInstalls(self) -> Dict[str, Dict[str, str]]: + return self._conan_installs + + @pyqtProperty("QVariant", constant=True) + def pythonInstalls(self) -> Dict[str, Dict[str, str]]: + return self._python_installs diff --git a/resources/qml/Dialogs/AboutDialog.qml b/resources/qml/Dialogs/AboutDialog.qml index b0cd9d2ad34..bbd7c45b8d3 100644 --- a/resources/qml/Dialogs/AboutDialog.qml +++ b/resources/qml/Dialogs/AboutDialog.qml @@ -1,19 +1,22 @@ -// Copyright (c) 2022 UltiMaker +// Copyright (c) 2023 UltiMaker // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.4 import QtQuick.Controls 2.9 +import QtQuick.Layouts 1.3 import UM 1.6 as UM -import Cura 1.5 as Cura +import Cura 1.6 as Cura UM.Dialog { id: base - //: About dialog title title: catalog.i18nc("@title:window The argument is the application name.", "About %1").arg(CuraApplication.applicationDisplayName) + // Flag to toggle between main dependencies information and extensive dependencies information + property bool showDefaultDependencies: true + minimumWidth: 500 * screenScaleFactor minimumHeight: 700 * screenScaleFactor width: minimumWidth @@ -21,186 +24,241 @@ UM.Dialog backgroundColor: UM.Theme.getColor("main_background") - - Rectangle + headerComponent: Rectangle { - id: header - width: parent.width + 2 * margin // margin from Dialog.qml - height: childrenRect.height + topPadding - - anchors.top: parent.top - anchors.topMargin: -margin - anchors.horizontalCenter: parent.horizontalCenter - - property real topPadding: UM.Theme.getSize("wide_margin").height - + width: parent.width + height: logo.height + 2 * UM.Theme.getSize("wide_margin").height color: UM.Theme.getColor("main_window_header_background") Image { id: logo - width: (base.minimumWidth * 0.85) | 0 - height: (width * (UM.Theme.getSize("logo").height / UM.Theme.getSize("logo").width)) | 0 + width: Math.floor(base.width * 0.85) + height: Math.floor(width * UM.Theme.getSize("logo").height / UM.Theme.getSize("logo").width) source: UM.Theme.getImage("logo") - sourceSize.width: width - sourceSize.height: height fillMode: Image.PreserveAspectFit - anchors.top: parent.top - anchors.topMargin: parent.topPadding - anchors.horizontalCenter: parent.horizontalCenter + anchors.centerIn: parent - UM.I18nCatalog{id: catalog; name: "cura"} - MouseArea - { - anchors.fill: parent - onClicked: - { - projectsList.visible = !projectsList.visible; - projectBuildInfoList.visible = !projectBuildInfoList.visible; - } - } + UM.I18nCatalog{ id: catalog; name: "cura" } } UM.Label { id: version - text: catalog.i18nc("@label","version: %1").arg(UM.Application.version) font: UM.Theme.getFont("large_bold") color: UM.Theme.getColor("button_text") anchors.right : logo.right anchors.top: logo.bottom - anchors.topMargin: (UM.Theme.getSize("default_margin").height / 2) | 0 + } + + MouseArea + { + anchors.fill: parent + onDoubleClicked: showDefaultDependencies = !showDefaultDependencies } } - UM.Label + // Reusable component to display a dependency + readonly property Component dependency_row: RowLayout { - id: description - width: parent.width + spacing: UM.Theme.getSize("narrow_margin").width - //: About dialog application description - text: catalog.i18nc("@label","End-to-end solution for fused filament 3D printing.") - font: UM.Theme.getFont("system") - wrapMode: Text.WordWrap - anchors.top: header.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - } + UM.Label + { + text: { + if (typeof(url) !== "undefined" && url !== "") { + return "" + name + ""; + } else { + return name; + } + } + visible: text !== "" + Layout.fillWidth: true + Layout.preferredWidth: 2 + elide: Text.ElideRight + } - UM.Label - { - id: creditsNotes - width: parent.width + UM.Label + { + text: description + visible: text !== "" + Layout.fillWidth: true + Layout.preferredWidth: 3 + elide: Text.ElideRight + } + + UM.Label + { + text: license + visible: text !== "" + Layout.fillWidth: true + Layout.preferredWidth: 2 + elide: Text.ElideRight + } - //: About dialog application author note - text: catalog.i18nc("@info:credit","Cura is developed by UltiMaker in cooperation with the community.\nCura proudly uses the following open source projects:") - font: UM.Theme.getFont("system") - wrapMode: Text.WordWrap - anchors.top: description.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height + UM.Label + { + text: version + visible: text !== "" + Layout.fillWidth: true + Layout.preferredWidth: 2 + elide: Text.ElideRight + } } - ListView + Flickable { - id: projectsList - anchors.top: creditsNotes.bottom - anchors.topMargin: UM.Theme.getSize("default_margin").height - width: parent.width - height: base.height - y - (2 * UM.Theme.getSize("default_margin").height + closeButton.height) - - ScrollBar.vertical: UM.ScrollBar - { - id: projectsListScrollBar + anchors.fill: parent + ScrollBar.vertical: UM.ScrollBar { + visible: contentHeight > height } + contentHeight: content.height + clip: true - delegate: Row + Column { + id: content spacing: UM.Theme.getSize("narrow_margin").width + width: parent.width + UM.Label { - text: "%2".arg(model.url).arg(model.name) - width: (projectsList.width * 0.25) | 0 - elide: Text.ElideRight - onLinkActivated: Qt.openUrlExternally(link) + text: catalog.i18nc("@label", "End-to-end solution for fused filament 3D printing.") + font: UM.Theme.getFont("system") + wrapMode: Text.WordWrap } + UM.Label { - text: model.description - elide: Text.ElideRight - width: ((projectsList.width * 0.6) | 0) - parent.spacing * 2 - projectsListScrollBar.width + text: catalog.i18nc("@info:credit", "Cura is developed by UltiMaker in cooperation with the community.\nCura proudly uses the following open source projects:") + font: UM.Theme.getFont("system") + wrapMode: Text.WordWrap + } + + Column + { + visible: showDefaultDependencies + width: parent.width + + Repeater + { + width: parent.width + + delegate: Loader { + sourceComponent: dependency_row + width: parent.width + property string name: model.name + property string description: model.description + property string license: model.license + property string url: model.url + } + + model: ListModel + { + id: projectsModel + } + Component.onCompleted: + { + //Do NOT add dependencies of our dependencies here, nor CI-dependencies! + //UltiMaker's own projects and forks. + projectsModel.append({ name: "Cura", description: catalog.i18nc("@label Description for application component", "Graphical user interface"), license: "LGPLv3", url: "https://github.com/Ultimaker/Cura" }); + projectsModel.append({ name: "Uranium", description: catalog.i18nc("@label Description for application component", "Application framework"), license: "LGPLv3", url: "https://github.com/Ultimaker/Uranium" }); + projectsModel.append({ name: "CuraEngine", description: catalog.i18nc("@label Description for application component", "G-code generator"), license: "AGPLv3", url: "https://github.com/Ultimaker/CuraEngine" }); + projectsModel.append({ name: "libArcus", description: catalog.i18nc("@label Description for application component", "Interprocess communication library"), license: "LGPLv3", url: "https://github.com/Ultimaker/libArcus" }); + projectsModel.append({ name: "pynest2d", description: catalog.i18nc("@label Description for application component", "Python bindings for libnest2d"), license: "LGPL", url: "https://github.com/Ultimaker/pynest2d" }); + projectsModel.append({ name: "libnest2d", description: catalog.i18nc("@label Description for application component", "Polygon packing library, developed by Prusa Research"), license: "LGPL", url: "https://github.com/tamasmeszaros/libnest2d" }); + projectsModel.append({ name: "libSavitar", description: catalog.i18nc("@label Description for application component", "Support library for handling 3MF files"), license: "LGPLv3", url: "https://github.com/ultimaker/libsavitar" }); + projectsModel.append({ name: "libCharon", description: catalog.i18nc("@label Description for application component", "Support library for file metadata and streaming"), license: "LGPLv3", url: "https://github.com/ultimaker/libcharon" }); + + //Direct dependencies of the front-end. + projectsModel.append({ name: "Python", description: catalog.i18nc("@label Description for application dependency", "Programming language"), license: "Python", url: "http://python.org/" }); + projectsModel.append({ name: "Qt6", description: catalog.i18nc("@label Description for application dependency", "GUI framework"), license: "LGPLv3", url: "https://www.qt.io/" }); + projectsModel.append({ name: "PyQt", description: catalog.i18nc("@label Description for application dependency", "GUI framework bindings"), license: "GPL", url: "https://riverbankcomputing.com/software/pyqt" }); + projectsModel.append({ name: "SIP", description: catalog.i18nc("@label Description for application dependency", "C/C++ Binding library"), license: "GPL", url: "https://riverbankcomputing.com/software/sip" }); + projectsModel.append({ name: "Protobuf", description: catalog.i18nc("@label Description for application dependency", "Data interchange format"), license: "BSD", url: "https://developers.google.com/protocol-buffers" }); + projectsModel.append({ name: "Noto Sans", description: catalog.i18nc("@label", "Font"), license: "Apache 2.0", url: "https://www.google.com/get/noto/" }); + + //CuraEngine's dependencies. + projectsModel.append({ name: "Clipper", description: catalog.i18nc("@label Description for application dependency", "Polygon clipping library"), license: "Boost", url: "http://www.angusj.com/delphi/clipper.php" }); + projectsModel.append({ name: "RapidJSON", description: catalog.i18nc("@label Description for application dependency", "JSON parser"), license: "MIT", url: "https://rapidjson.org/" }); + projectsModel.append({ name: "STB", description: catalog.i18nc("@label Description for application dependency", "Utility functions, including an image loader"), license: "Public Domain", url: "https://github.com/nothings/stb" }); + projectsModel.append({ name: "Boost", description: catalog.i18nc("@label Description for application dependency", "Utility library, including Voronoi generation"), license: "Boost", url: "https://www.boost.org/" }); + + //Python modules. + projectsModel.append({ name: "Certifi", description: catalog.i18nc("@label Description for application dependency", "Root Certificates for validating SSL trustworthiness"), license: "MPL", url: "https://github.com/certifi/python-certifi" }); + projectsModel.append({ name: "Cryptography", description: catalog.i18nc("@label Description for application dependency", "Root Certificates for validating SSL trustworthiness"), license: "APACHE and BSD", url: "https://cryptography.io/" }); + projectsModel.append({ name: "Future", description: catalog.i18nc("@label Description for application dependency", "Compatibility between Python 2 and 3"), license: "MIT", url: "https://python-future.org/" }); + projectsModel.append({ name: "keyring", description: catalog.i18nc("@label Description for application dependency", "Support library for system keyring access"), license: "MIT", url: "https://github.com/jaraco/keyring" }); + projectsModel.append({ name: "NumPy", description: catalog.i18nc("@label Description for application dependency", "Support library for faster math"), license: "BSD", url: "http://www.numpy.org/" }); + projectsModel.append({ name: "NumPy-STL", description: catalog.i18nc("@label Description for application dependency", "Support library for handling STL files"), license: "BSD", url: "https://github.com/WoLpH/numpy-stl" }); + projectsModel.append({ name: "PyClipper", description: catalog.i18nc("@label Description for application dependency", "Python bindings for Clipper"), license: "MIT", url: "https://github.com/fonttools/pyclipper" }); + projectsModel.append({ name: "PySerial", description: catalog.i18nc("@label Description for application dependency", "Serial communication library"), license: "Python", url: "http://pyserial.sourceforge.net/" }); + projectsModel.append({ name: "SciPy", description: catalog.i18nc("@label Description for application dependency", "Support library for scientific computing"), license: "BSD-new", url: "https://www.scipy.org/" }); + projectsModel.append({ name: "Sentry", description: catalog.i18nc("@Label Description for application dependency", "Python Error tracking library"), license: "BSD 2-Clause 'Simplified'", url: "https://sentry.io/for/python/" }); + projectsModel.append({ name: "Trimesh", description: catalog.i18nc("@label Description for application dependency", "Support library for handling triangular meshes"), license: "MIT", url: "https://trimsh.org" }); + projectsModel.append({ name: "python-zeroconf", description: catalog.i18nc("@label Description for application dependency", "ZeroConf discovery library"), license: "LGPL", url: "https://github.com/jstasiak/python-zeroconf" }); + + //Building/packaging. + projectsModel.append({ name: "CMake", description: catalog.i18nc("@label Description for development tool", "Universal build system configuration"), license: "BSD 3-Clause", url: "https://cmake.org/" }); + projectsModel.append({ name: "Conan", description: catalog.i18nc("@label Description for development tool", "Dependency and package manager"), license: "MIT", url: "https://conan.io/" }); + projectsModel.append({ name: "Pyinstaller", description: catalog.i18nc("@label Description for development tool", "Packaging Python-applications"), license: "GPLv2", url: "https://pyinstaller.org/" }); + projectsModel.append({ name: "AppImageKit", description: catalog.i18nc("@label Description for development tool", "Linux cross-distribution application deployment"), license: "MIT", url: "https://github.com/AppImage/AppImageKit" }); + projectsModel.append({ name: "NSIS", description: catalog.i18nc("@label Description for development tool", "Generating Windows installers"), license: "Zlib", url: "https://nsis.sourceforge.io/" }); + } + } } + UM.Label { - text: model.license - elide: Text.ElideRight - width: (projectsList.width * 0.15) | 0 + visible: !showDefaultDependencies + text: "Conan Installs" + font: UM.Theme.getFont("large_bold") } - } - model: ListModel - { - id: projectsModel - } - Component.onCompleted: - { - //Do NOT add dependencies of our dependencies here, nor CI-dependencies! - //UltiMaker's own projects and forks. - projectsModel.append({ name: "Cura", description: catalog.i18nc("@label Description for application component", "Graphical user interface"), license: "LGPLv3", url: "https://github.com/Ultimaker/Cura" }); - projectsModel.append({ name: "Uranium", description: catalog.i18nc("@label Description for application component", "Application framework"), license: "LGPLv3", url: "https://github.com/Ultimaker/Uranium" }); - projectsModel.append({ name: "CuraEngine", description: catalog.i18nc("@label Description for application component", "G-code generator"), license: "AGPLv3", url: "https://github.com/Ultimaker/CuraEngine" }); - projectsModel.append({ name: "libArcus", description: catalog.i18nc("@label Description for application component", "Interprocess communication library"), license: "LGPLv3", url: "https://github.com/Ultimaker/libArcus" }); - projectsModel.append({ name: "pynest2d", description: catalog.i18nc("@label Description for application component", "Python bindings for libnest2d"), license: "LGPL", url: "https://github.com/Ultimaker/pynest2d" }); - projectsModel.append({ name: "libnest2d", description: catalog.i18nc("@label Description for application component", "Polygon packing library, developed by Prusa Research"), license: "LGPL", url: "https://github.com/tamasmeszaros/libnest2d" }); - projectsModel.append({ name: "libSavitar", description: catalog.i18nc("@label Description for application component", "Support library for handling 3MF files"), license: "LGPLv3", url: "https://github.com/ultimaker/libsavitar" }); - projectsModel.append({ name: "libCharon", description: catalog.i18nc("@label Description for application component", "Support library for file metadata and streaming"), license: "LGPLv3", url: "https://github.com/ultimaker/libcharon" }); - - //Direct dependencies of the front-end. - projectsModel.append({ name: "Python", description: catalog.i18nc("@label Description for application dependency", "Programming language"), license: "Python", url: "http://python.org/" }); - projectsModel.append({ name: "Qt6", description: catalog.i18nc("@label Description for application dependency", "GUI framework"), license: "LGPLv3", url: "https://www.qt.io/" }); - projectsModel.append({ name: "PyQt", description: catalog.i18nc("@label Description for application dependency", "GUI framework bindings"), license: "GPL", url: "https://riverbankcomputing.com/software/pyqt" }); - projectsModel.append({ name: "SIP", description: catalog.i18nc("@label Description for application dependency", "C/C++ Binding library"), license: "GPL", url: "https://riverbankcomputing.com/software/sip" }); - projectsModel.append({ name: "Protobuf", description: catalog.i18nc("@label Description for application dependency", "Data interchange format"), license: "BSD", url: "https://developers.google.com/protocol-buffers" }); - projectsModel.append({ name: "Noto Sans", description: catalog.i18nc("@label", "Font"), license: "Apache 2.0", url: "https://www.google.com/get/noto/" }); - - //CuraEngine's dependencies. - projectsModel.append({ name: "Clipper", description: catalog.i18nc("@label Description for application dependency", "Polygon clipping library"), license: "Boost", url: "http://www.angusj.com/delphi/clipper.php" }); - projectsModel.append({ name: "RapidJSON", description: catalog.i18nc("@label Description for application dependency", "JSON parser"), license: "MIT", url: "https://rapidjson.org/" }); - projectsModel.append({ name: "STB", description: catalog.i18nc("@label Description for application dependency", "Utility functions, including an image loader"), license: "Public Domain", url: "https://github.com/nothings/stb" }); - projectsModel.append({ name: "Boost", description: catalog.i18nc("@label Description for application dependency", "Utility library, including Voronoi generation"), license: "Boost", url: "https://www.boost.org/" }); - - //Python modules. - projectsModel.append({ name: "Certifi", description: catalog.i18nc("@label Description for application dependency", "Root Certificates for validating SSL trustworthiness"), license: "MPL", url: "https://github.com/certifi/python-certifi" }); - projectsModel.append({ name: "Cryptography", description: catalog.i18nc("@label Description for application dependency", "Root Certificates for validating SSL trustworthiness"), license: "APACHE and BSD", url: "https://cryptography.io/" }); - projectsModel.append({ name: "Future", description: catalog.i18nc("@label Description for application dependency", "Compatibility between Python 2 and 3"), license: "MIT", url: "https://python-future.org/" }); - projectsModel.append({ name: "keyring", description: catalog.i18nc("@label Description for application dependency", "Support library for system keyring access"), license: "MIT", url: "https://github.com/jaraco/keyring" }); - projectsModel.append({ name: "NumPy", description: catalog.i18nc("@label Description for application dependency", "Support library for faster math"), license: "BSD", url: "http://www.numpy.org/" }); - projectsModel.append({ name: "NumPy-STL", description: catalog.i18nc("@label Description for application dependency", "Support library for handling STL files"), license: "BSD", url: "https://github.com/WoLpH/numpy-stl" }); - projectsModel.append({ name: "PyClipper", description: catalog.i18nc("@label Description for application dependency", "Python bindings for Clipper"), license: "MIT", url: "https://github.com/fonttools/pyclipper" }); - projectsModel.append({ name: "PySerial", description: catalog.i18nc("@label Description for application dependency", "Serial communication library"), license: "Python", url: "http://pyserial.sourceforge.net/" }); - projectsModel.append({ name: "SciPy", description: catalog.i18nc("@label Description for application dependency", "Support library for scientific computing"), license: "BSD-new", url: "https://www.scipy.org/" }); - projectsModel.append({ name: "Sentry", description: catalog.i18nc("@Label Description for application dependency", "Python Error tracking library"), license: "BSD 2-Clause 'Simplified'", url: "https://sentry.io/for/python/" }); - projectsModel.append({ name: "Trimesh", description: catalog.i18nc("@label Description for application dependency", "Support library for handling triangular meshes"), license: "MIT", url: "https://trimsh.org" }); - projectsModel.append({ name: "python-zeroconf", description: catalog.i18nc("@label Description for application dependency", "ZeroConf discovery library"), license: "LGPL", url: "https://github.com/jstasiak/python-zeroconf" }); - - //Building/packaging. - projectsModel.append({ name: "CMake", description: catalog.i18nc("@label Description for development tool", "Universal build system configuration"), license: "BSD 3-Clause", url: "https://cmake.org/" }); - projectsModel.append({ name: "Conan", description: catalog.i18nc("@label Description for development tool", "Dependency and package manager"), license: "MIT", url: "https://conan.io/" }); - projectsModel.append({ name: "Pyinstaller", description: catalog.i18nc("@label Description for development tool", "Packaging Python-applications"), license: "GPLv2", url: "https://pyinstaller.org/" }); - projectsModel.append({ name: "AppImageKit", description: catalog.i18nc("@label Description for development tool", "Linux cross-distribution application deployment"), license: "MIT", url: "https://github.com/AppImage/AppImageKit" }); - projectsModel.append({ name: "NSIS", description: catalog.i18nc("@label Description for development tool", "Generating Windows installers"), license: "Zlib", url: "https://nsis.sourceforge.io/" }); - } - } - AboutDialogVersionsList{ - id: projectBuildInfoList + Column + { + visible: !showDefaultDependencies + width: parent.width - } + Repeater + { + width: parent.width + model: Object.entries(CuraApplication.conanInstalls).map(function (item) { return { name: item[0], version: item[1].version } }) + delegate: Loader { + sourceComponent: dependency_row + width: parent.width + property string name: modelData.name + property string version: modelData.version + } + } + } + UM.Label + { + visible: !showDefaultDependencies + text: "Python Installs" + font: UM.Theme.getFont("large_bold") + } - onVisibleChanged: - { - projectsList.visible = true; - projectBuildInfoList.visible = false; + Column + { + width: parent.width + visible: !showDefaultDependencies + Repeater + { + delegate: Loader { + sourceComponent: dependency_row + width: parent.width + property string name: modelData.name + property string version: modelData.version + } + width: parent.width + model: Object.entries(CuraApplication.pythonInstalls).map(function (item) { return { name: item[0], version: item[1].version } }) + } + } + } } rightButtons: Cura.TertiaryButton