diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index e6ad425954..a1f3f45e7e 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -58,25 +58,26 @@ class LocalPackageList(PackageList): def _makePackageModel(self, package_info: Dict[str, Any]) -> PackageModel: """ Create a PackageModel from the package_info and determine its section_title""" - bundled_or_installed = "bundled" if self._manager.isBundledPackage(package_info["package_id"]) else "installed" + package_id = package_info["package_id"] + bundled_or_installed = "bundled" if self._manager.isBundledPackage(package_id) else "installed" package_type = package_info["package_type"] section_title = self.PACKAGE_CATEGORIES[bundled_or_installed][package_type] package = PackageModel(package_info, section_title = section_title, parent = self) - if package_info["package_id"] in self._manager.getPackagesToRemove() or package_info["package_id"] in self._manager.getPackagesToInstall(): + if package_id in self._manager.getPackagesToRemove() or package_id in self._manager.getPackagesToInstall(): package.is_recently_managed = True + package.can_downgrade = self._manager.canDowngrade(package_id) self._connectManageButtonSignals(package) return package def checkForUpdates(self, packages: List[Dict[str, Any]]): - if self._account.isLoggedIn: - installed_packages = "installed_packages=".join([f"{package['package_id']}:{package['package_version']}&" for package in packages]) - request_url = f"{PACKAGE_UPDATES_URL}?installed_packages={installed_packages[:-1]}" + installed_packages = "installed_packages=".join([f"{package['package_id']}:{package['package_version']}&" for package in packages]) + request_url = f"{PACKAGE_UPDATES_URL}?installed_packages={installed_packages[:-1]}" - self._ongoing_request = HttpRequestManager.getInstance().get( - request_url, - scope = self._scope, - callback = self._parseResponse - ) + self._ongoing_request = HttpRequestManager.getInstance().get( + request_url, + scope = self._scope, + callback = self._parseResponse + ) def _parseResponse(self, reply: "QNetworkReply") -> None: """ @@ -95,6 +96,6 @@ class LocalPackageList(PackageList): for package_data in response_data["data"]: package = self.getPackageModel(package_data["package_id"]) package.download_url = package_data.get("download_url", "") - package.canUpdate = True + package.can_update = True self.sort(attrgetter("sectionTitle", "can_update", "displayName"), key = "package", reverse = True) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 7bb1076988..a9d4e698ed 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -12,7 +12,7 @@ from UM.Qt.ListModel import ListModel from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope from UM.TaskManagement.HttpRequestManager import HttpRequestData, HttpRequestManager from UM.Logger import Logger -from UM.PluginRegistry import PluginRegistry +from UM import PluginRegistry from cura.CuraApplication import CuraApplication from cura import CuraPackageManager @@ -38,6 +38,7 @@ class PackageList(ListModel): def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) self._manager: CuraPackageManager = CuraApplication.getInstance().getPackageManager() + self._plugin_registry: PluginRegistry = CuraApplication.getInstance().getPluginRegistry() self._account = CuraApplication.getInstance().getCuraAPI().account self._error_message = "" self.addRoleName(self.PackageRole, "package") @@ -194,7 +195,6 @@ class PackageList(ListModel): if update: package.is_updating = False else: - Logger.debug(f"Setting recently installed for package: {package_id}") package.is_recently_managed = True package.is_installing = False self.subscribeUserToPackage(package_id, str(package.sdk_version)) @@ -258,7 +258,7 @@ class PackageList(ListModel): def _connectManageButtonSignals(self, package: PackageModel) -> None: package.installPackageTriggered.connect(self.installPackage) package.uninstallPackageTriggered.connect(self.uninstallPackage) - package.updatePackageTriggered.connect(self.installPackage) + package.updatePackageTriggered.connect(self.updatePackage) package.enablePackageTriggered.connect(self.enablePackage) package.disablePackageTriggered.connect(self.disablePackage) @@ -294,7 +294,8 @@ class PackageList(ListModel): package = self.getPackageModel(package_id) package.is_enabling = True Logger.debug(f"Enabling {package_id}") - # TODO: implement enabling functionality + self._plugin_registry.enablePlugin(package_id) + package.is_active = True package.is_enabling = False @pyqtSlot(str) @@ -302,5 +303,6 @@ class PackageList(ListModel): package = self.getPackageModel(package_id) package.is_enabling = True Logger.debug(f"Disabling {package_id}") - # TODO: implement disabling functionality + self._plugin_registry.disablePlugin(package_id) + package.is_active = False package.is_enabling = False diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index 58184ac1c3..92daf310a3 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -66,6 +66,7 @@ class PackageModel(QObject): self._can_update = False self._is_updating = False self._is_enabling = False + self._can_downgrade = False self._section_title = section_title self.sdk_version = package_data.get("sdk_version_semver", "") # Note that there's a lot more info in the package_data than just these specified here. @@ -287,8 +288,6 @@ class PackageModel(QObject): if self._is_recently_managed: return "hidden" if self._package_type == "material": - if self._is_bundled: # TODO: Check if a bundled material can/should be un-/install en-/disabled - return "secondary" return "hidden" if not self._is_installed: return "hidden" @@ -306,6 +305,16 @@ class PackageModel(QObject): self._is_enabling = value self.stateManageButtonChanged.emit() + @property + def is_active(self) -> bool: + return self._is_active + + @is_active.setter + def is_active(self, value: bool) -> None: + if value != self._is_active: + self._is_active = value + self.stateManageButtonChanged.emit() + # --- Installing --- @pyqtProperty(str, notify = stateManageButtonChanged) @@ -315,7 +324,7 @@ class PackageModel(QObject): if self._is_recently_managed: return "hidden" if self._is_installed: - if self._is_bundled: + if self._is_bundled and not self._can_downgrade: return "hidden" else: return "secondary" @@ -342,6 +351,16 @@ class PackageModel(QObject): self._is_installing = value self.stateManageButtonChanged.emit() + @property + def can_downgrade(self) -> bool: + return self._can_downgrade + + @can_downgrade.setter + def can_downgrade(self, value: bool) -> None: + if value != self._can_downgrade: + self._can_downgrade = value + self.stateManageButtonChanged.emit() + # --- Updating --- @pyqtProperty(str, notify = stateManageButtonChanged) @@ -371,78 +390,3 @@ class PackageModel(QObject): if value != self._can_update: self._can_update = value self.stateManageButtonChanged.emit() - - # ---- - - - - - - - - - - - # isInstalledChanged = pyqtSignal() - # - # @pyqtProperty(bool, notify = isInstalledChanged) - # def isInstalled(self): - # return self._is_installed - # - # isEnabledChanged = pyqtSignal() - # - # - #f - # @pyqtProperty(bool, notify = isEnabledChanged) - # def isEnabled(self) -> bool: - # return self._is_active - # - # - # - # isManageEnableStateChanged = pyqtSignalf() - # - # @pyqtProperty(str, notify = isManageEnableStateChanged) - # def isManageEnableState(self) -> str: - # if self.isEnabling: - # return "busy" - # if self. - # - # manageEnableStateChanged = pyqtSignal() - # - # @pyqtProperty(str, notify = manageEnableStateChanged) - # def manageEnableState(self) -> str: - # # TODO: Handle manual installed packages - # if self._is_installed: - # if self._is_active: - # return "secondary" - # else: - # return "primary" - # else: - # return "hidden" - # - # manageInstallStateChanged = pyqtSignal() - # - # def setManageInstallState(self, value: bool) -> None: - # if value != self._is_installed: - # self._is_installed = value - # self.manageInstallStateChanged.emit() - # self.manageEnableStateChanged.emit() - # - # @pyqtProperty(str, notify = manageInstallStateChanged) - # def manageInstallState(self) -> str: - # if self._is_installed: - # if self._is_bundled: - # return "hidden" - # else: - # return "secondary" - # else: - # return "primary" - # - # manageUpdateStateChanged = pyqtSignal() - # - # @pyqtProperty(str, notify = manageUpdateStateChanged) - # def manageUpdateState(self) -> str: - # if self._can_update: - # return "primary" - # return "hidden" - # diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index cdd52c9b90..323fea03f1 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -15,9 +15,8 @@ RowLayout property alias secondaryText: secondaryButton.text property string busyPrimaryText: busyMessageText.text property string busySecondaryText: busyMessageText.text - property string mainState: "primary" property bool enabled: true - property bool busy + property bool busy: state == "busy" signal clicked(bool primary_action) @@ -77,7 +76,7 @@ RowLayout { id: busyMessageText visible: parent.visible - text: manageButton.mainState == "primary" ? manageButton.busyPrimaryText : manageButton.busySecondaryText + text: manageButton.state == "primary" ? manageButton.busyPrimaryText : manageButton.busySecondaryText anchors.left: busyIndicator.right anchors.verticalCenter: parent.verticalCenter diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 44f7777b35..62f9673072 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -106,9 +106,8 @@ Window height: UM.Theme.getSize("button_icon").height + UM.Theme.getSize("default_margin").height spacing: UM.Theme.getSize("thin_margin").width - Rectangle + Item { - color: "transparent" Layout.preferredHeight: parent.height Layout.preferredWidth: searchBar.visible ? UM.Theme.getSize("thin_margin").width : 0 Layout.fillWidth: ! searchBar.visible @@ -228,4 +227,51 @@ Window } } } + + Rectangle + { + height: quitButton.height + 2 * UM.Theme.getSize("default_margin").width + color: UM.Theme.getColor("primary") + visible: false // TODO: enable this when restart is required + anchors + { + left: parent.left + right: parent.right + bottom: parent.bottom + } + + RowLayout + { + anchors + { + left: parent.left + right: parent.right + verticalCenter: parent.verticalCenter + margins: UM.Theme.getSize("default_margin").width + } + spacing: UM.Theme.getSize("default_margin").width + UM.RecolorImage + { + id: bannerIcon + source: UM.Theme.getIcon("Plugin") + + color: UM.Theme.getColor("primary_button_text") + implicitWidth: UM.Theme.getSize("banner_icon_size").width + implicitHeight: UM.Theme.getSize("banner_icon_size").height + } + Text + { + color: UM.Theme.getColor("primary_button_text") + text: catalog.i18nc("@button", "In order to use the package you will need to restart Cura") + font: UM.Theme.getFont("default") + renderType: Text.NativeRendering + Layout.fillWidth: true + } + Cura.SecondaryButton + { + id: quitButton + text: catalog.i18nc("@button", "Quit Ultimaker Cura") + } + } + } } diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml index 89ae6091fc..bf0dda217b 100644 --- a/plugins/Marketplace/resources/qml/PackageCard.qml +++ b/plugins/Marketplace/resources/qml/PackageCard.qml @@ -94,7 +94,7 @@ Rectangle left: packageItem.right leftMargin: UM.Theme.getSize("default_margin").width right: parent.right - rightMargin: UM.Theme.getSize("thick_margin").width + rightMargin: UM.Theme.getSize("default_margin").width top: parent.top topMargin: UM.Theme.getSize("narrow_margin").height } @@ -114,47 +114,13 @@ Rectangle color: UM.Theme.getColor("text") verticalAlignment: Text.AlignTop } - - Control + VerifiedIcon { - Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width - Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height - enabled: packageData.isCheckedByUltimaker visible: packageData.isCheckedByUltimaker - - Cura.ToolTip - { - tooltipText: - { - switch(packageData.packageType) - { - case "plugin": return catalog.i18nc("@info", "Ultimaker Verified Plug-in"); - case "material": return catalog.i18nc("@info", "Ultimaker Certified Material"); - default: return catalog.i18nc("@info", "Ultimaker Verified Package"); - } - } - visible: parent.hovered - targetPoint: Qt.point(0, Math.round(parent.y + parent.height / 4)) - } - - Rectangle - { - anchors.fill: parent - color: UM.Theme.getColor("action_button_hovered") - radius: width - UM.RecolorImage - { - anchors.fill: parent - color: UM.Theme.getColor("primary") - source: packageData.packageType == "plugin" ? UM.Theme.getIcon("CheckCircle") : UM.Theme.getIcon("Certified") - } - } - - //NOTE: Can we link to something here? (Probably a static link explaining what verified is): - // onClicked: Qt.openUrlExternally( XXXXXX ) } + Control { Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width @@ -362,12 +328,8 @@ Rectangle secondaryText: catalog.i18nc("@button", "Disable") busySecondaryText: catalog.i18nc("@button", "disabling...") enabled: !(installManageButton.busy || updateManageButton.busy) - } - Connections - { - target: enableManageButton - function onClicked(primary_action) - { + + onClicked: { if (primary_action) { packageData.enablePackageTriggered(packageData.packageId) @@ -389,11 +351,7 @@ Rectangle secondaryText: catalog.i18nc("@button", "Uninstall") busySecondaryText: catalog.i18nc("@button", "uninstalling...") enabled: !(enableManageButton.busy || updateManageButton.busy) - } - Connections - { - target: installManageButton - function onClicked(primary_action) + onClicked: { if (primary_action) { @@ -414,14 +372,7 @@ Rectangle primaryText: catalog.i18nc("@button", "Update") busyPrimaryText: catalog.i18nc("@button", "updating...") enabled: !(installManageButton.busy || enableManageButton.busy) - } - Connections - { - target: updateManageButton - function onClicked(primary_action) - { - packageData.updatePackageTriggered(packageData.packageId) - } + onClicked: packageData.updatePackageTriggered(packageData.packageId) } } } diff --git a/plugins/Marketplace/resources/qml/VerifiedIcon.qml b/plugins/Marketplace/resources/qml/VerifiedIcon.qml new file mode 100644 index 0000000000..30ef3080a0 --- /dev/null +++ b/plugins/Marketplace/resources/qml/VerifiedIcon.qml @@ -0,0 +1,45 @@ +// Copyright (c) 2021 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.1 + +import UM 1.6 as UM +import Cura 1.6 as Cura +Control +{ + implicitWidth: UM.Theme.getSize("card_tiny_icon").width + implicitHeight: UM.Theme.getSize("card_tiny_icon").height + + Cura.ToolTip + { + tooltipText: + { + switch(packageData.packageType) + { + case "plugin": return catalog.i18nc("@info", "Ultimaker Verified Plug-in"); + case "material": return catalog.i18nc("@info", "Ultimaker Certified Material"); + default: return catalog.i18nc("@info", "Ultimaker Verified Package"); + } + } + visible: parent.hovered + targetPoint: Qt.point(0, Math.round(parent.y + parent.height / 4)) + } + + Rectangle + { + anchors.fill: parent + color: UM.Theme.getColor("action_button_hovered") + radius: width + UM.RecolorImage + { + anchors.fill: parent + color: UM.Theme.getColor("primary") + source: packageData.packageType == "plugin" ? UM.Theme.getIcon("CheckCircle") : UM.Theme.getIcon("Certified") + } + } + + //NOTE: Can we link to something here? (Probably a static link explaining what verified is): + // onClicked: Qt.openUrlExternally( XXXXXX ) +} \ No newline at end of file diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 8ca9f72ca8..149bfab308 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -179,7 +179,7 @@ "lining": [192, 193, 194, 255], "viewport_overlay": [246, 246, 246, 255], - "primary": [50, 130, 255, 255], + "primary": [25, 110, 240, 255], "primary_shadow": [64, 47, 205, 255], "primary_hover": [48, 182, 231, 255], "primary_text": [255, 255, 255, 255],