diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index b9f3253b85..32e60b2518 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -66,9 +66,7 @@ class LocalPackageList(PackageList): section_title = self.PACKAGE_CATEGORIES[bundled_or_installed][package_type] package = PackageModel(package_info, section_title = section_title, parent = self) self._connectManageButtonSignals(package) - package.can_downgrade = self._manager.canDowngrade(package_id) - if package_id in self._manager.getPackagesToRemove() or package_id in self._manager.getPackagesToInstall(): - package.installation_status_changed = True + package.setCanDowngrade(self._manager.canDowngrade(package_id)) return package def checkForUpdates(self, packages: List[Dict[str, Any]]): diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 21c1da004b..e6e5e78ba9 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -18,7 +18,7 @@ from cura.CuraApplication import CuraApplication from cura.CuraPackageManager import CuraPackageManager from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. -from .PackageModel import PackageModel, ManageState +from .PackageModel import PackageModel from .Constants import USER_PACKAGES_URL if TYPE_CHECKING: @@ -166,7 +166,6 @@ class PackageList(ListModel): dialog.deleteLater() # reset package card package = self.getPackageModel(package_id) - package.is_installing = ManageState.FAILED def _requestInstall(self, package_id: str, update: bool = False) -> None: package_path = self._to_install[package_id] @@ -184,12 +183,8 @@ class PackageList(ListModel): package_path = self._to_install.pop(package_id) to_be_installed = self._manager.installPackage(package_path) is not None package = self.getPackageModel(package_id) - if package.can_update and to_be_installed: - package.can_update = False - if update: - package.is_updating = ManageState.HALTED - else: - package.is_installing = ManageState.HALTED + # TODO handle failure + package.isRecentlyInstalledChanged.emit(update) self.subscribeUserToPackage(package_id, str(package.sdk_version)) def download(self, package_id: str, url: str, update: bool = False) -> None: @@ -239,10 +234,7 @@ class PackageList(ListModel): Logger.error(f"Failed to download package: {package_id} due to {reply_string}") try: package = self.getPackageModel(package_id) - if update: - package.is_updating = ManageState.FAILED - else: - package.is_installing = ManageState.FAILED + # TODO: handle error except RuntimeError: # Setting the ownership of this object to not qml can still result in a RuntimeError. Which can occur when quickly toggling # between de-/constructing Remote or Local PackageLists. This try-except is here to prevent a hard crash when the wrapped C++ object @@ -285,7 +277,6 @@ class PackageList(ListModel): :param package_id: the package identification string """ package = self.getPackageModel(package_id) - package.is_installing = ManageState.PROCESSING url = package.download_url self.download(package_id, url, False) @@ -295,10 +286,9 @@ class PackageList(ListModel): :param package_id: the package identification string """ package = self.getPackageModel(package_id) - package.is_installing = ManageState.PROCESSING self._manager.removePackage(package_id) self.unsunscribeUserFromPackage(package_id) - package.is_installing = ManageState.HALTED + package.isRecentlyInstalledChanged.emit(False) def updatePackage(self, package_id: str) -> None: """Update a package from the Marketplace @@ -306,7 +296,6 @@ class PackageList(ListModel): :param package_id: the package identification string """ package = self.getPackageModel(package_id) - package.is_updating = ManageState.PROCESSING self._manager.removePackage(package_id, force_add = True) url = package.download_url self.download(package_id, url, True) @@ -317,10 +306,8 @@ class PackageList(ListModel): :param package_id: the package identification string """ package = self.getPackageModel(package_id) - package.is_enabling = ManageState.PROCESSING self._plugin_registry.enablePlugin(package_id) package.is_active = True - package.is_enabling = ManageState.HALTED def disablePackage(self, package_id: str) -> None: """Disable a package in the plugin registry @@ -328,7 +315,5 @@ class PackageList(ListModel): :param package_id: the package identification string """ package = self.getPackageModel(package_id) - package.is_enabling = ManageState.PROCESSING self._plugin_registry.disablePlugin(package_id) package.is_active = False - package.is_enabling = ManageState.HALTED diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py index fbc998b5c9..93d41187e1 100644 --- a/plugins/Marketplace/PackageModel.py +++ b/plugins/Marketplace/PackageModel.py @@ -13,13 +13,6 @@ from UM.i18n import i18nCatalog # To translate placeholder names if data is not catalog = i18nCatalog("cura") - -class ManageState(Enum): - PROCESSING = 1 - HALTED = 0 - FAILED = -1 - - class PackageModel(QObject): """ Represents a package, containing all the relevant information to be displayed about a package. @@ -69,19 +62,45 @@ class PackageModel(QObject): if not self._icon_url or self._icon_url == "": self._icon_url = author_data.get("icon_url", "") - self._is_installing: ManageState = ManageState.HALTED - self._installation_status_changed = False + self._is_installing = False + self._install_status_changing = False + self._is_recently_installed = False self._is_recently_updated = False - self._is_recently_enabled = False self._can_update = False - self._is_updating: ManageState = ManageState.HALTED - self._is_enabling: ManageState = ManageState.HALTED + self._is_updating = 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. + def install_clicked(package_id): + self._install_status_changing = True + self.setIsInstalling(True) + + self.installPackageTriggered.connect(install_clicked) + + def uninstall_clicked(package_id): + self._install_status_changing = False + self.setIsInstalling(True) + + self.uninstallPackageTriggered.connect(uninstall_clicked) + + def update_clicked(package_id): + self.setIsUpdating(True) + + self.updatePackageTriggered.connect(update_clicked) + + def finished_installed(is_updating): + if is_updating: + self._is_recently_installed = True + self.setIsUpdating(False) + else: + self._is_recently_updated + self.setIsInstalling(False) + + self.isRecentlyInstalledChanged.connect(finished_installed) + def __eq__(self, other: object): if isinstance(other, PackageModel): return other == self @@ -278,6 +297,10 @@ class PackageModel(QObject): def isCompatibleAirManager(self) -> bool: return self._is_compatible_air_manager + @pyqtProperty(bool, constant = True) + def isBundled(self) -> bool: + return self._is_bundled + # --- manage buttons signals --- stateManageButtonChanged = pyqtSignal() @@ -292,33 +315,14 @@ class PackageModel(QObject): disablePackageTriggered = pyqtSignal(str) - recentlyInstalledChanged = pyqtSignal(bool) + isRecentlyInstalledChanged = pyqtSignal(bool) # --- enabling --- - @pyqtProperty(str, notify = stateManageButtonChanged) - def stateManageEnableButton(self) -> str: + @pyqtProperty(bool, notify = stateManageButtonChanged) + def stateManageEnableButton(self) -> bool: """The state of the manage Enable Button of this package""" - if self._is_enabling == ManageState.PROCESSING: - return "busy" - if self._package_type == "material" or not self._is_installed: - return "hidden" - if self._is_installed and self._is_active: - return "secondary" - return "primary" - - @property - def is_enabling(self) -> ManageState: - """Flag if the package is being enabled/disabled""" - return self._is_enabling - - @is_enabling.setter - def is_enabling(self, value: ManageState) -> None: - if value != self._is_enabling: - self._is_enabling = value - if value == ManageState.HALTED: - self._is_recently_enabled = True - self.stateManageButtonChanged.emit() + return not (self._is_installed and self._is_active) @property def is_active(self) -> bool: @@ -333,85 +337,67 @@ class PackageModel(QObject): # --- Installing --- - @pyqtProperty(str, notify = stateManageButtonChanged) - def stateManageInstallButton(self) -> str: + @pyqtProperty(bool, notify = stateManageButtonChanged) + def stateManageInstallButton(self) -> bool: """The state of the Manage Install package card""" - if self._is_installing == ManageState.PROCESSING: - return "busy" - if self._installation_status_changed: - return "confirmed" - if self._is_installed: - if self._is_bundled and not self._can_downgrade: - return "hidden" - else: - return "secondary" - else: - return "primary" + return not self._is_installed - @property - def is_installing(self) -> ManageState: - """Flag is we're currently installing, when setting this to ``None`` in indicates a failed installation""" - return self._is_installing - - @is_installing.setter - def is_installing(self, value: ManageState) -> None: + def setIsInstalling(self, value: bool) -> None: if value != self._is_installing: self._is_installing = value - if value == ManageState.HALTED: - self._installation_status_changed = True self.stateManageButtonChanged.emit() - @property - def installation_status_changed(self): - return self._installation_status_changed + @pyqtProperty(bool, fset = setIsInstalling, notify = stateManageButtonChanged) + def isInstalling(self) -> bool: + return self._is_installing - @installation_status_changed.setter - def installation_status_changed(self, value): - if value != self._installation_status_changed: - self._installation_status_changed = value + def setInstallStatusChanging(self, value: bool) -> None: + if value != self._install_status_changing: + self._install_status_changing = value self.stateManageButtonChanged.emit() + @pyqtProperty(bool, fset = setInstallStatusChanging, notify = stateManageButtonChanged) + def installStatusChanging(self) -> bool: + return self._install_status_changing + @pyqtProperty(bool, notify = stateManageButtonChanged) - def installationStatus(self): + def isInstalled(self) -> bool: return self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToInstall() - @property - def can_downgrade(self) -> bool: - """Flag if the installed package can be downgraded to a bundled version""" - return self._can_downgrade + @pyqtProperty(bool, notify = stateManageButtonChanged) + def isUninstalled(self) -> bool: + return self._package_id in CuraApplication.getInstance().getPackageManager().getPackagesToRemove() - @can_downgrade.setter - def can_downgrade(self, value: bool) -> None: + def setCanDowngrade(self, value: bool) -> None: if value != self._can_downgrade: self._can_downgrade = value self.stateManageButtonChanged.emit() + @pyqtProperty(bool, fset = setCanDowngrade, notify = stateManageButtonChanged) + def canDowngrade(self) -> bool: + """Flag if the installed package can be downgraded to a bundled version""" + return self._can_downgrade + # --- Updating --- - @pyqtProperty(str, notify = stateManageButtonChanged) - def stateManageUpdateButton(self) -> str: - """The state of the manage Update button for this card """ - if self._is_updating == ManageState.PROCESSING: - return "busy" - if self._is_recently_updated: - return "confirmed" - if self._can_update: - return "primary" - return "hidden" - - @property - def is_updating(self) -> ManageState: - """Flag indicating if the package is being updated""" - return self._is_updating - - @is_updating.setter - def is_updating(self, value: ManageState) -> None: + def setIsUpdating(self, value): if value != self._is_updating: self._is_updating = value - if value == ManageState.HALTED: - self._is_recently_updated = True self.stateManageButtonChanged.emit() + @pyqtProperty(bool, fset = setIsUpdating, notify = stateManageButtonChanged) + def isUpdating(self): + return self._is_updating + + def setIsUpdated(self, value): + if value != self._is_recently_updated: + self._is_recently_updated = value + self.stateManageButtonChanged.emit() + + @pyqtProperty(bool, fset = setIsUpdated, notify = stateManageButtonChanged) + def isUpdated(self): + return self._is_recently_updated + @property def can_update(self) -> bool: """Flag indicating if the package can be updated""" diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index 7228507bf5..5325fc8640 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -123,8 +123,6 @@ class RemotePackageList(PackageList): try: package = PackageModel(package_data, parent = self) self._connectManageButtonSignals(package) - if package_id in self._manager.getPackagesToRemove() or package_id in self._manager.getPackagesToInstall(): - package.installation_status_changed = True self.appendItem({"package": package}) # Add it to this list model. except RuntimeError: # Setting the ownership of this object to not qml can still result in a RuntimeError. Which can occur when quickly toggling diff --git a/plugins/Marketplace/resources/qml/ManageButton.qml b/plugins/Marketplace/resources/qml/ManageButton.qml index 7843805e26..2e2ef294d1 100644 --- a/plugins/Marketplace/resources/qml/ManageButton.qml +++ b/plugins/Marketplace/resources/qml/ManageButton.qml @@ -11,7 +11,7 @@ import Cura 1.6 as Cura Item { id: manageButton - property string button_style + property bool button_style property string text property bool busy property bool confirmed @@ -117,19 +117,10 @@ Item sourceComponent: { - switch (manageButton.button_style) - { - case "primary": - return manageButton.primaryButton; - case "secondary": - return manageButton.secondaryButton; - case "busy": - return manageButton.busyButton; - case "confirmed": - return manageButton.confirmButton; - default: - return; - } + if (busy) { return manageButton.busyButton; } + else if (confirmed) { return manageButton.confirmButton; } + else if (manageButton.button_style) { return manageButton.primaryButton; } + else { return manageButton.secondaryButton; } } } } diff --git a/plugins/Marketplace/resources/qml/PackageCardHeader.qml b/plugins/Marketplace/resources/qml/PackageCardHeader.qml index 4719fcfe13..ca74d9f77d 100644 --- a/plugins/Marketplace/resources/qml/PackageCardHeader.qml +++ b/plugins/Marketplace/resources/qml/PackageCardHeader.qml @@ -181,34 +181,16 @@ Item ManageButton { id: enableManageButton - visible: !(installManageButton.confirmed || updateManageButton.confirmed) || enableManageButton.confirmed + visible: showManageButtons && !(installManageButton.confirmed || updateManageButton.confirmed) + enabled: !(installManageButton.busy || updateManageButton.busy) + + busy: false + confirmed: false + button_style: packageData.stateManageEnableButton Layout.alignment: Qt.AlignTop - busy: packageData.enableManageButton == "busy" - confirmed: packageData.enableManageButton == "confirmed" - text: { - switch (packageData.stateManageEnableButton) { - case "primary": - return catalog.i18nc("@button", "Enable"); - case "secondary": - return catalog.i18nc("@button", "Disable"); - case "busy": - if (packageData.installationStatus) { - return catalog.i18nc("@button", "Enabling..."); - } else { - return catalog.i18nc("@button", "Disabling..."); - } - case "confirmed": - if (packageData.installationStatus) { - return catalog.i18nc("@button", "Enabled"); - } else { - return catalog.i18nc("@button", "Disabled"); - } - default: - return ""; - } - } - enabled: !installManageButton.busy && !updateManageButton.busy + + text: packageData.stateManageEnableButton ? catalog.i18nc("@button", "Enable") : catalog.i18nc("@button", "Disable") onClicked: { @@ -226,34 +208,31 @@ Item ManageButton { id: installManageButton - visible: (showManageButtons || installManageButton.confirmed) && !(enableManageButton.confirmed || updateManageButton.confirmed) + visible: (showManageButtons || confirmed) && ((packageData.isBundled && packageData.canDowngrade) || !packageData.isBundled || !updateManageButton.confirmed) + + enabled: !packageData.isUpdating + + busy: packageData.isInstalling + confirmed: packageData.isInstalled || packageData.isUninstalled + button_style: packageData.stateManageInstallButton - busy: packageData.stateManageInstallButton == "busy" - confirmed: packageData.stateManageInstallButton == "confirmed" Layout.alignment: Qt.AlignTop - text: { - switch (packageData.stateManageInstallButton) { - case "primary": - return catalog.i18nc("@button", "Install"); - case "secondary": - return catalog.i18nc("@button", "Uninstall"); - case "busy": - if (packageData.installationStatus) { - return catalog.i18nc("@button", "Installing..."); - } else { - return catalog.i18nc("@button", "Uninstalling..."); - } - case "confirmed": - if (packageData.installationStatus) { - return catalog.i18nc("@button", "Installed"); - } else { - return catalog.i18nc("@button", "Uninstalled"); - } - default: - return ""; + + text: + { + if (packageData.stateManageInstallButton) + { + if (packageData.isInstalling) { return catalog.i18nc("@button", "Installing..."); } + else if (packageData.isInstalled) { return catalog.i18nc("@button", "Installed"); } + else { return catalog.i18nc("@button", "Install"); } + } + else + { + if (packageData.isInstalling) { return catalog.i18nc("@button", "Uninstalling..."); } + else if (packageData.isUninstalled) { return catalog.i18nc("@button", "Uninstalled"); } + else { return catalog.i18nc("@button", "Uninstall"); } } } - enabled: !enableManageButton.busy && !updateManageButton.busy onClicked: { @@ -271,25 +250,20 @@ Item ManageButton { id: updateManageButton - visible: showManageButtons && (!installManageButton.confirmed || updateManageButton.confirmed) + visible: (showManageButtons && confirmed) && !installManageButton.confirmed + enabled: !installManageButton.busy - button_style: packageData.stateManageUpdateButton - busy: packageData.stateManageUpdateButton == "busy" - confirmed: packageData.stateManageUpdateButton == "confirmed" + busy: packageData.isUpdating + confirmed: packageData.isUpdated + + button_style: true Layout.alignment: Qt.AlignTop - enabled: !installManageButton.busy && !enableManageButton.busy - text: { - switch (packageData.stateManageInstallButton) { - case "primary": - return catalog.i18nc("@button", "Update"); - case "busy": - return catalog.i18nc("@button", "Updating..."); - case "confirmed": - return catalog.i18nc("@button", "Updated"); - default: - return ""; - } + text: + { + if (packageData.isUpdating) { return catalog.i18nc("@button", "Updating..."); } + else if (packageData.isUpdated) { return catalog.i18nc("@button", "Updated"); } + else { return catalog.i18nc("@button", "Update"); } } onClicked: packageData.updatePackageTriggered(packageData.packageId)