diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index f787a8b29b..1ea81863f0 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -96,8 +96,11 @@ class CuraPackageManager(QObject): package_info["is_bundled"] = False return package_info - # TODO: get from plugin registry - #self._plugin_registry. + for section, packages in self.getAllInstalledPackagesInfo().items(): + for package in packages: + if package["package_id"] == package_id: + package_info = package + return package_info return None @@ -133,7 +136,7 @@ class CuraPackageManager(QObject): if package_id in managed_package_id_set: continue - plugin_package_info["is_bundled"] = True + plugin_package_info["is_bundled"] = True if plugin_package_info["author"]["name"] == "Ultimaker B.V." else False plugin_package_info["is_active"] = self._plugin_registry.isActivePlugin(package_id) package_type = "plugin" if package_type not in installed_packages_dict: diff --git a/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml b/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml index 689a008a67..c12b1cfa31 100644 --- a/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml +++ b/plugins/Toolbox/resources/qml/ToolboxDetailTile.qml @@ -9,6 +9,7 @@ import UM 1.1 as UM Rectangle { + property bool installed: toolbox.isInstalled(model.id) width: base.width - UM.Theme.getSize("double_margin").width height: UM.Theme.getSize("base_unit").height * 8 color: "transparent" @@ -48,17 +49,28 @@ Rectangle Button { id: installButton text: { - if ( toolbox.isDownloading && toolbox.activePackage == model ) + if (installed) { - return catalog.i18nc("@action:button", "Cancel") + return catalog.i18nc("@action:button", "Installed") } else { - return catalog.i18nc("@action:button", "Install") + if ( toolbox.isDownloading && toolbox.activePackage == model ) + { + return catalog.i18nc("@action:button", "Cancel") + } + else + { + return catalog.i18nc("@action:button", "Install") + } } } enabled: { + if (installed) + { + return true + } if ( toolbox.isDownloading ) { return toolbox.activePackage == model ? true : false @@ -74,12 +86,47 @@ Rectangle { implicitWidth: 96 implicitHeight: 30 - color: control.hovered ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary") + color: + { + if (installed) + { + return UM.Theme.getColor("action_button_disabled") + } + else + { + if ( control.hovered ) + { + return UM.Theme.getColor("primary_hover") + } + else + { + return UM.Theme.getColor("primary") + } + } + + } } label: Label { text: control.text - color: control.hovered ? UM.Theme.getColor("button_text") : UM.Theme.getColor("button_text_hover") + color: + { + if (installed) + { + return UM.Theme.getColor("action_button_disabled_text") + } + else + { + if ( control.hovered ) + { + return UM.Theme.getColor("button_text_hover") + } + else + { + return UM.Theme.getColor("button_text") + } + } + } verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter font: UM.Theme.getFont("default_bold") @@ -87,22 +134,29 @@ Rectangle } onClicked: { - console.log( "MODEL", model.id ) - toolbox.activePackage = model - // if ( toolbox.isDownloading && toolbox.activePackage == model ) - if ( toolbox.isDownloading ) + if (installed) { - toolbox.cancelDownload(); + toolbox.viewCategory = "installed" } else { - // toolbox.activePackage = model; - if ( model.can_upgrade ) + // if ( toolbox.isDownloading && toolbox.activePackage == model ) + if ( toolbox.isDownloading ) { - // toolbox.downloadAndInstallPlugin( model.update_url ); + toolbox.cancelDownload(); } - else { - toolbox.startDownload( model.download_url ); + else + { + toolbox.activePackage = model + // toolbox.activePackage = model; + if ( model.can_upgrade ) + { + // toolbox.downloadAndInstallPlugin( model.update_url ); + } + else + { + toolbox.startDownload( model.download_url ); + } } } } @@ -115,4 +169,9 @@ Rectangle height: UM.Theme.getSize("default_lining").height anchors.bottom: parent.bottom } + Connections + { + target: toolbox + onInstallChanged: installed = toolbox.isInstalled(model.id) + } } diff --git a/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml b/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml index 75314e9af5..0f30e2e78d 100644 --- a/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml +++ b/plugins/Toolbox/resources/qml/ToolboxInstalledTile.qml @@ -11,6 +11,8 @@ import UM 1.1 as UM Item { id: base + property bool canUpdate: false + property bool isEnabled: true height: UM.Theme.getSize("base_unit").height * 8 anchors { @@ -27,7 +29,7 @@ Item Column { id: pluginInfo - property var color: model.enabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining") + property var color: isEnabled ? UM.Theme.getColor("text") : UM.Theme.getColor("lining") height: parent.height anchors { @@ -75,7 +77,7 @@ Item } Label { - text: ""+model.author+"" + text: ""+model.author_name+"" width: parent.width height: 24 wrapMode: Text.WordWrap @@ -102,8 +104,17 @@ Item Button { id: removeButton - text: "Uninstall" - // visible: model.can_uninstall && model.status == "installed" + text: + { + if (model.is_bundled) + { + return isEnabled ? catalog.i18nc("@action:button", "Disable") : catalog.i18nc("@action:button", "Enable") + } + else + { + return catalog.i18nc("@action:button", "Uninstall") + } + } enabled: !toolbox.isDownloading style: ButtonStyle { @@ -125,13 +136,30 @@ Item horizontalAlignment: Text.AlignHCenter } } - onClicked: toolbox.removePlugin( model.id ) + onClicked: + { + if (model.is_bundled) + { + if (toolbox.isEnabled(model.id)) + { + toolbox.disable(model.id) + } + else + { + toolbox.enable(model.id) + } + } + else + { + toolbox.uninstall( model.id ) + } + } } Button { id: updateButton - text: "Update" - // enabled: model.can_update + text: catalog.i18nc("@action:button", "Update") + visible: canUpdate style: ButtonStyle { background: Rectangle @@ -151,7 +179,7 @@ Item } onClicked: { - toolbox.updatePackage(model.id); + toolbox.update(model.id); } } ProgressBar @@ -177,4 +205,10 @@ Item } } } + Connections + { + target: toolbox + onEnabledChanged: isEnabled = toolbox.isEnabled(model.id) + onMetadataChanged: canUpdate = toolbox.canUpdate(model.id) + } } diff --git a/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml b/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml index 0e89c49d1a..9109f407c8 100644 --- a/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml +++ b/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml @@ -59,7 +59,7 @@ UM.Dialog { onClicked: { licenseDialog.close(); - toolbox.installPlugin(licenseDialog.pluginFileLocation); + toolbox.install(licenseDialog.pluginFileLocation); } }, Button diff --git a/plugins/Toolbox/src/PackagesModel.py b/plugins/Toolbox/src/PackagesModel.py index dc1eb2cb78..c81f97c944 100644 --- a/plugins/Toolbox/src/PackagesModel.py +++ b/plugins/Toolbox/src/PackagesModel.py @@ -12,34 +12,23 @@ from UM.Qt.ListModel import ListModel ## Model that holds cura packages. By setting the filter property the instances held by this model can be changed. class PackagesModel(ListModel): - IdRole = Qt.UserRole + 1 - TypeRole = Qt.UserRole + 2 - NameRole = Qt.UserRole + 3 - VersionRole = Qt.UserRole + 4 - AuthorNameRole = Qt.UserRole + 5 - AuthorEmailRole = Qt.UserRole + 6 - DescriptionRole = Qt.UserRole + 7 - IconURLRole = Qt.UserRole + 8 - ImageURLsRole = Qt.UserRole + 9 - DownloadURLRole = Qt.UserRole + 10 - LastUpdatedRole = Qt.UserRole + 11 - def __init__(self, parent = None): super().__init__(parent) self._metadata = None - self.addRoleName(PackagesModel.IdRole, "id") - self.addRoleName(PackagesModel.TypeRole, "type") - self.addRoleName(PackagesModel.NameRole, "name") - self.addRoleName(PackagesModel.VersionRole, "version") - self.addRoleName(PackagesModel.AuthorNameRole, "author_name") - self.addRoleName(PackagesModel.AuthorEmailRole, "author_email") - self.addRoleName(PackagesModel.DescriptionRole, "description") - self.addRoleName(PackagesModel.IconURLRole, "icon_url") - self.addRoleName(PackagesModel.ImageURLsRole, "image_urls") - self.addRoleName(PackagesModel.DownloadURLRole, "download_url") - self.addRoleName(PackagesModel.LastUpdatedRole, "last_updated") + self.addRoleName(Qt.UserRole + 1, "id") + self.addRoleName(Qt.UserRole + 2, "type") + self.addRoleName(Qt.UserRole + 3, "name") + self.addRoleName(Qt.UserRole + 4, "version") + self.addRoleName(Qt.UserRole + 5, "author_name") + self.addRoleName(Qt.UserRole + 6, "author_email") + self.addRoleName(Qt.UserRole + 7, "description") + self.addRoleName(Qt.UserRole + 8, "icon_url") + self.addRoleName(Qt.UserRole + 9, "image_urls") + self.addRoleName(Qt.UserRole + 10, "download_url") + self.addRoleName(Qt.UserRole + 11, "last_updated") + self.addRoleName(Qt.UserRole + 12, "is_bundled") # List of filters for queries. The result is the union of the each list of results. self._filter = {} # type: Dict[str, str] @@ -63,7 +52,8 @@ class PackagesModel(ListModel): "icon_url": package["icon_url"] if "icon_url" in package else None, "image_urls": package["image_urls"] if "image_urls" in package else None, "download_url": package["download_url"] if "download_url" in package else None, - "last_updated": package["last_updated"] if "last_updated" in package else None + "last_updated": package["last_updated"] if "last_updated" in package else None, + "is_bundled": package["is_bundled"] if "is_bundled" in package else False }) # Filter on all the key-word arguments. diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index dacf806fe6..efc40a01b1 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -70,6 +70,12 @@ class Toolbox(QObject, Extension): "plugins_installed": [], # TODO: Replace this with a proper API call: "materials_showcase": [ + { + "name": "Ultimaker", + "email": "ian.paschal@gmail.com", + "website": "ultimaker.com", + "type": "material" + }, { "name": "DSM", "email": "contact@dsm.nl", @@ -137,6 +143,8 @@ class Toolbox(QObject, Extension): onDownloadProgressChanged = pyqtSignal() onIsDownloadingChanged = pyqtSignal() restartRequiredChanged = pyqtSignal() + installChanged = pyqtSignal() + enabledChanged = pyqtSignal() # UI changes viewChanged = pyqtSignal() @@ -213,8 +221,9 @@ class Toolbox(QObject, Extension): return dialog @pyqtSlot(str) - def installPlugin(self, file_path): + def install(self, file_path): self._package_manager.installPackage(file_path) + self.installChanged.emit() self.metadataChanged.emit() # TODO: Stuff self.openRestartDialog("TODO") @@ -222,7 +231,7 @@ class Toolbox(QObject, Extension): self.restartRequiredChanged.emit() @pyqtSlot(str) - def removePlugin(self, plugin_id): + def uninstall(self, plugin_id): self._package_manager.removePackage(plugin_id) self.metadataChanged.emit() self._restart_required = True @@ -231,15 +240,15 @@ class Toolbox(QObject, Extension): Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), "TODO") @pyqtSlot(str) - def enablePlugin(self, plugin_id): - self._plugin_registry.setPluginEnabled(plugin_id, True) - self.metadataChanged.emit() + def enable(self, plugin_id): + self._plugin_registry.enablePlugin(plugin_id) + self.enabledChanged.emit() Logger.log("i", "%s was set as 'active'.", plugin_id) @pyqtSlot(str) - def disablePlugin(self, plugin_id): - self._plugin_registry.setPluginEnabled(plugin_id, False) - self.metadataChanged.emit() + def disable(self, plugin_id): + self._plugin_registry.disablePlugin(plugin_id) + self.enabledChanged.emit() Logger.log("i", "%s was set as 'deactive'.", plugin_id) @pyqtProperty(bool, notify = metadataChanged) @@ -258,18 +267,30 @@ class Toolbox(QObject, Extension): # Checks # -------------------------------------------------------------------------- - def _checkCanUpgrade(self, package_id: str, version: str) -> bool: - installed_plugin_data = self._package_manager.getInstalledPackageInfo(package_id) - if installed_plugin_data is None: + @pyqtSlot(str, result = bool) + def canUpdate(self, package_id: str) -> bool: + local_package = self._package_manager.getInstalledPackageInfo(package_id) + if local_package is None: return False - installed_version = installed_plugin_data["package_version"] - return compareSemanticVersions(version, installed_version) > 0 - def _checkInstalled(self, package_id: str): + remote_package = None + for package in self._metadata["packages"]: + if package["package_id"] == package_id: + remote_package = package + if remote_package is None: + return False + + local_version = local_package["package_version"] + remote_version = remote_package["package_version"] + return compareSemanticVersions(remote_version, local_version) > 0 + + @pyqtSlot(str, result = bool) + def isInstalled(self, package_id) -> bool: return self._package_manager.isPackageInstalled(package_id) - def _checkEnabled(self, id): - if id in self._plugin_registry.getActivePlugins(): + @pyqtSlot(str, result = bool) + def isEnabled(self, package_id) -> bool: + if package_id in self._plugin_registry.getActivePlugins(): return True return False @@ -382,6 +403,12 @@ class Toolbox(QObject, Extension): self._models["materials_showcase"] = AuthorsModel(self) # TODO: Replace this with a proper API call: self._models["materials_showcase"].setMetadata(self._metadata["materials_showcase"]) + + # This part is also needed for comparing downloaded packages to + # installed packages. + self._models["packages"].setMetadata(self._metadata["packages"]) + + self.metadataChanged.emit() self.setViewPage("overview") @@ -407,6 +434,8 @@ class Toolbox(QObject, Extension): except json.decoder.JSONDecodeError: Logger.log("w", "Toolbox: Received invalid JSON for showcase.") return + + else: # Ignore any operation that is not a get operation pass @@ -440,7 +469,7 @@ class Toolbox(QObject, Extension): self.openLicenseDialog(package_info["package_id"], license_content, file_path) return - self.installPlugin(file_path) + self.install(file_path) return