From c31665f0691c0e0956cde4f1e3fcccd3a0da49f2 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 28 Oct 2021 17:34:55 +0200 Subject: [PATCH 1/9] Move list of packages QML to a re-usable component We'll need to have the same design for plug-ins and for materials. Contributes to issue CURA-8557. --- .../Marketplace/resources/qml/Packages.qml | 172 ++++++++++++++++++ plugins/Marketplace/resources/qml/Plugins.qml | 169 +---------------- 2 files changed, 175 insertions(+), 166 deletions(-) create mode 100644 plugins/Marketplace/resources/qml/Packages.qml diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml new file mode 100644 index 0000000000..fc58910fa2 --- /dev/null +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -0,0 +1,172 @@ +// 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 UM 1.4 as UM + +ScrollView +{ + id: packages + clip: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + + property alias model: pluginColumn.model + + ListView + { + id: pluginColumn + width: parent.width + + spacing: UM.Theme.getSize("default_margin").height + + delegate: Rectangle + { + width: pluginColumn.width + height: UM.Theme.getSize("card").height + + color: UM.Theme.getColor("main_background") + radius: UM.Theme.getSize("default_radius").width + + Label + { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: Math.round((parent.height - height) / 2) + + text: model.package.displayName + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("text") + } + } + + footer: Item //Wrapper item to add spacing between content and footer. + { + width: parent.width + height: UM.Theme.getSize("card").height + pluginColumn.spacing + Button + { + id: loadMoreButton + width: parent.width + height: UM.Theme.getSize("card").height + anchors.bottom: parent.bottom + + enabled: packages.model.hasMore && !packages.model.isLoading || packages.model.errorMessage != "" + onClicked: packages.model.request() //Load next page in plug-in list. + + background: Rectangle + { + anchors.fill: parent + radius: UM.Theme.getSize("default_radius").width + color: UM.Theme.getColor("main_background") + } + + Row + { + anchors.centerIn: parent + + spacing: UM.Theme.getSize("thin_margin").width + + states: + [ + State + { + name: "Error" + when: packages.model.errorMessage != "" + PropertyChanges + { + target: errorIcon + visible: true + } + PropertyChanges + { + target: loadMoreIcon + visible: false + } + PropertyChanges + { + target: loadMoreLabel + text: catalog.i18nc("@button", "Failed to load plug-ins:") + " " + packages.model.errorMessage + "\n" + catalog.i18nc("@button", "Retry?") + } + }, + State + { + name: "Loading" + when: packages.model.isLoading + PropertyChanges + { + target: loadMoreIcon + source: UM.Theme.getIcon("ArrowDoubleCircleRight") + color: UM.Theme.getColor("action_button_disabled_text") + } + PropertyChanges + { + target: loadMoreLabel + text: catalog.i18nc("@button", "Loading") + color: UM.Theme.getColor("action_button_disabled_text") + } + }, + State + { + name: "LastPage" + when: !packages.model.hasMore + PropertyChanges + { + target: loadMoreIcon + visible: false + } + PropertyChanges + { + target: loadMoreLabel + text: catalog.i18nc("@button", "No more results to load") + color: UM.Theme.getColor("action_button_disabled_text") + } + } + ] + + Item + { + width: (errorIcon.visible || loadMoreIcon.visible) ? UM.Theme.getSize("small_button_icon").width : 0 + height: UM.Theme.getSize("small_button_icon").height + anchors.verticalCenter: loadMoreLabel.verticalCenter + + UM.StatusIcon + { + id: errorIcon + anchors.fill: parent + + status: UM.StatusIcon.Status.ERROR + visible: false + } + UM.RecolorImage + { + id: loadMoreIcon + anchors.fill: parent + + source: UM.Theme.getIcon("ArrowDown") + color: UM.Theme.getColor("secondary_button_text") + + RotationAnimator + { + target: loadMoreIcon + from: 0 + to: 360 + duration: 1000 + loops: Animation.Infinite + running: packages.model.isLoading + alwaysRunToEnd: true + } + } + } + Label + { + id: loadMoreLabel + text: catalog.i18nc("@button", "Load more") + font: UM.Theme.getFont("medium_bold") + color: UM.Theme.getColor("secondary_button_text") + } + } + } + } + } +} diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 0fbe8b7734..7ed5323941 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -1,174 +1,11 @@ // 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 Marketplace 1.0 as Marketplace -import UM 1.4 as UM -ScrollView +Packages { - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - - ListView + model: Marketplace.PackageList { - id: pluginColumn - width: parent.width - - model: Marketplace.PackageList - { - id: pluginList - } - spacing: UM.Theme.getSize("default_margin").height - - delegate: Rectangle - { - width: pluginColumn.width - height: UM.Theme.getSize("card").height - - color: UM.Theme.getColor("main_background") - radius: UM.Theme.getSize("default_radius").width - - Label - { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: Math.round((parent.height - height) / 2) - - text: model.package.displayName - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("text") - } - } - - footer: Item //Wrapper item to add spacing between content and footer. - { - width: parent.width - height: UM.Theme.getSize("card").height + pluginColumn.spacing - Button - { - id: loadMoreButton - width: parent.width - height: UM.Theme.getSize("card").height - anchors.bottom: parent.bottom - - enabled: pluginList.hasMore && !pluginList.isLoading || pluginList.errorMessage != "" - onClicked: pluginList.request() //Load next page in plug-in list. - - background: Rectangle - { - anchors.fill: parent - radius: UM.Theme.getSize("default_radius").width - color: UM.Theme.getColor("main_background") - } - - Row - { - anchors.centerIn: parent - - spacing: UM.Theme.getSize("thin_margin").width - - states: - [ - State - { - name: "Error" - when: pluginList.errorMessage != "" - PropertyChanges - { - target: errorIcon - visible: true - } - PropertyChanges - { - target: loadMoreIcon - visible: false - } - PropertyChanges - { - target: loadMoreLabel - text: catalog.i18nc("@button", "Failed to load plug-ins:") + " " + pluginList.errorMessage + "\n" + catalog.i18nc("@button", "Retry?") - } - }, - State - { - name: "Loading" - when: pluginList.isLoading - PropertyChanges - { - target: loadMoreIcon - source: UM.Theme.getIcon("ArrowDoubleCircleRight") - color: UM.Theme.getColor("action_button_disabled_text") - } - PropertyChanges - { - target: loadMoreLabel - text: catalog.i18nc("@button", "Loading") - color: UM.Theme.getColor("action_button_disabled_text") - } - }, - State - { - name: "LastPage" - when: !pluginList.hasMore - PropertyChanges - { - target: loadMoreIcon - visible: false - } - PropertyChanges - { - target: loadMoreLabel - text: catalog.i18nc("@button", "No more results to load") - color: UM.Theme.getColor("action_button_disabled_text") - } - } - ] - - Item - { - width: (errorIcon.visible || loadMoreIcon.visible) ? UM.Theme.getSize("small_button_icon").width : 0 - height: UM.Theme.getSize("small_button_icon").height - anchors.verticalCenter: loadMoreLabel.verticalCenter - - UM.StatusIcon - { - id: errorIcon - anchors.fill: parent - - status: UM.StatusIcon.Status.ERROR - visible: false - } - UM.RecolorImage - { - id: loadMoreIcon - anchors.fill: parent - - source: UM.Theme.getIcon("ArrowDown") - color: UM.Theme.getColor("secondary_button_text") - - RotationAnimator - { - target: loadMoreIcon - from: 0 - to: 360 - duration: 1000 - loops: Animation.Infinite - running: pluginList.isLoading - alwaysRunToEnd: true - } - } - } - Label - { - id: loadMoreLabel - text: catalog.i18nc("@button", "Load more") - font: UM.Theme.getFont("medium_bold") - color: UM.Theme.getColor("secondary_button_text") - } - } - } - } } -} +} \ No newline at end of file From 38b7f1761599da317b2336a744b2e9e2ca578218 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 28 Oct 2021 17:49:32 +0200 Subject: [PATCH 2/9] Add basic tab bar to select pages Contributes to issue CURA-8557. --- .../Marketplace/resources/qml/Marketplace.qml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 2213b3d456..13780c2aa8 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -66,6 +66,30 @@ Window text: catalog.i18nc("@header", "Install Plugins") } } + + Item + { + Layout.preferredWidth: parent.width + Layout.preferredHeight: childrenRect.height + + TabBar //Page selection. + { + anchors.right: parent.right + anchors.rightMargin: UM.Theme.getSize("default_margin").width + + TabButton + { + width: implicitWidth + text: catalog.i18nc("@button", "Plug-ins") + } + TabButton + { + width: implicitWidth + text: catalog.i18nc("@button", "Materials") + } + } + } + Rectangle //Page contents. { Layout.preferredWidth: parent.width From 5f884321eab555e12ab2d1cf3ec25053399b699e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 28 Oct 2021 18:08:51 +0200 Subject: [PATCH 3/9] Add design for tabs of package type selector The width here is implementation-defined. Looks like it matches the design though. Seems like the design has 0 margins. Contributes to issue CURA-8557. --- .../Marketplace/resources/qml/Marketplace.qml | 6 +++-- .../resources/qml/PackageTypeTab.qml | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 plugins/Marketplace/resources/qml/PackageTypeTab.qml diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 13780c2aa8..308474a242 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -77,12 +77,14 @@ Window anchors.right: parent.right anchors.rightMargin: UM.Theme.getSize("default_margin").width - TabButton + spacing: 0 + + PackageTypeTab { width: implicitWidth text: catalog.i18nc("@button", "Plug-ins") } - TabButton + PackageTypeTab { width: implicitWidth text: catalog.i18nc("@button", "Materials") diff --git a/plugins/Marketplace/resources/qml/PackageTypeTab.qml b/plugins/Marketplace/resources/qml/PackageTypeTab.qml new file mode 100644 index 0000000000..9b6136f1f0 --- /dev/null +++ b/plugins/Marketplace/resources/qml/PackageTypeTab.qml @@ -0,0 +1,26 @@ +// 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 UM 1.0 as UM + +TabButton +{ + background: Rectangle + { + anchors.fill: parent + color: parent.checked ? UM.Theme.getColor("main_background") : UM.Theme.getColor("detail_background") + border.color: UM.Theme.getColor("detail_background") + border.width: UM.Theme.getSize("thick_lining").width + } + + contentItem: Label + { + text: parent.text + font: UM.Theme.getFont("medium") + color: UM.Theme.getColor("text") + width: contentWidth + anchors.centerIn: parent + } +} \ No newline at end of file From 4191f984409847e3d46fb087bd84cb45dd6a4ddc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 28 Oct 2021 18:23:14 +0200 Subject: [PATCH 4/9] Switch pages to Materials when tab is clicked And back to plug-ins when that tab is clicked. Sadly, linking the content dynamically doesn't seem to work, with a custom property. Contributes to issue CURA-8557. --- plugins/Marketplace/resources/qml/Marketplace.qml | 3 +++ plugins/Marketplace/resources/qml/Materials.qml | 11 +++++++++++ 2 files changed, 14 insertions(+) create mode 100644 plugins/Marketplace/resources/qml/Materials.qml diff --git a/plugins/Marketplace/resources/qml/Marketplace.qml b/plugins/Marketplace/resources/qml/Marketplace.qml index 308474a242..33cae9e778 100644 --- a/plugins/Marketplace/resources/qml/Marketplace.qml +++ b/plugins/Marketplace/resources/qml/Marketplace.qml @@ -74,6 +74,7 @@ Window TabBar //Page selection. { + id: pageSelectionTabBar anchors.right: parent.right anchors.rightMargin: UM.Theme.getSize("default_margin").width @@ -83,11 +84,13 @@ Window { width: implicitWidth text: catalog.i18nc("@button", "Plug-ins") + onClicked: content.source = "Plugins.qml" } PackageTypeTab { width: implicitWidth text: catalog.i18nc("@button", "Materials") + onClicked: content.source = "Materials.qml" } } } diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml new file mode 100644 index 0000000000..7ed5323941 --- /dev/null +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -0,0 +1,11 @@ +// Copyright (c) 2021 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import Marketplace 1.0 as Marketplace + +Packages +{ + model: Marketplace.PackageList + { + } +} \ No newline at end of file From cbd1b8fbf738a3e9c09df0a8d8274f60d05eba9d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 28 Oct 2021 18:59:03 +0200 Subject: [PATCH 5/9] Implement filter on PackageList The filter affects the URL. So we can't just start a request in the init. We need to request once all of the properties have been set. We also can't start the request when the filter changes, because there will be more filters and we don't want to start multiple requests. It needs to be manual. Contributes to issue CURA-8557. --- plugins/Marketplace/PackageList.py | 35 +++++++++++++++++-- .../Marketplace/resources/qml/Materials.qml | 1 + .../Marketplace/resources/qml/Packages.qml | 2 ++ plugins/Marketplace/resources/qml/Plugins.qml | 1 + 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index b93cec1183..b34d40c1d2 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -40,12 +40,12 @@ class PackageList(ListModel): self._is_loading = True self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) - self._request_url = f"{Marketplace.PACKAGES_URL}?limit={self.ITEMS_PER_PAGE}" self._error_message = "" - self.addRoleName(self.PackageRole, "package") + self._package_type_filter = "" + self._request_url = self._initialRequestUrl() - self.request() + self.addRoleName(self.PackageRole, "package") @pyqtSlot() def request(self) -> None: @@ -65,6 +65,10 @@ class PackageList(ListModel): error_callback = self._onError ) + def reset(self) -> None: + self.clear() + self._request_url = self._initialRequestUrl() + isLoadingChanged = pyqtSignal() def setIsLoading(self, is_loading: bool) -> None: @@ -91,6 +95,22 @@ class PackageList(ListModel): """ return self._request_url != "" + packageTypeFilterChanged = pyqtSignal() + + def setPackageTypeFilter(self, new_filter: str) -> None: + if new_filter != self._package_type_filter: + self._package_type_filter = new_filter + self.reset() + self.packageTypeFilterChanged.emit() + + @pyqtProperty(str, notify = packageTypeFilterChanged, fset = setPackageTypeFilter) + def packageTypeFilter(self) -> str: + """ + Get the package type this package list is filtering on, like ``plugin`` or ``material``. + :return: The package type this list is filtering on. + """ + return self._package_type_filter + def setErrorMessage(self, error_message: str) -> None: if self._error_message != error_message: self._error_message = error_message @@ -108,6 +128,15 @@ class PackageList(ListModel): """ return self._error_message + def _initialRequestUrl(self) -> str: + """ + Get the URL to request the first paginated page with. + :return: A URL to request. + """ + if self._package_type_filter != "": + return f"{Marketplace.PACKAGES_URL}?package_type={self._package_type_filter}&limit={self.ITEMS_PER_PAGE}" + return f"{Marketplace.PACKAGES_URL}?limit={self.ITEMS_PER_PAGE}" + def _parseResponse(self, reply: "QNetworkReply") -> None: """ Parse the response from the package list API request. diff --git a/plugins/Marketplace/resources/qml/Materials.qml b/plugins/Marketplace/resources/qml/Materials.qml index 7ed5323941..4f3c59d9fb 100644 --- a/plugins/Marketplace/resources/qml/Materials.qml +++ b/plugins/Marketplace/resources/qml/Materials.qml @@ -7,5 +7,6 @@ Packages { model: Marketplace.PackageList { + packageTypeFilter: "material" } } \ No newline at end of file diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index fc58910fa2..390fba2977 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -13,6 +13,8 @@ ScrollView property alias model: pluginColumn.model + Component.onCompleted: model.request() + ListView { id: pluginColumn diff --git a/plugins/Marketplace/resources/qml/Plugins.qml b/plugins/Marketplace/resources/qml/Plugins.qml index 7ed5323941..71814f54ad 100644 --- a/plugins/Marketplace/resources/qml/Plugins.qml +++ b/plugins/Marketplace/resources/qml/Plugins.qml @@ -7,5 +7,6 @@ Packages { model: Marketplace.PackageList { + packageTypeFilter: "plugin" } } \ No newline at end of file From 4b86f7bb29ab48f071cf8cdd339c2beb24bc63f4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 28 Oct 2021 21:45:58 +0200 Subject: [PATCH 6/9] Revert "Only show plugin and material packages" This reverts commit cdf05a56065321d9fc15767490d846680f3dc63f. It's no longer necessary since we filter on package type now anyway. Contributes to issue CURA-8557. --- plugins/Marketplace/PackageList.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index b34d40c1d2..7d5e85a9af 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -33,7 +33,6 @@ class PackageList(ListModel): PackageRole = Qt.UserRole + 1 ITEMS_PER_PAGE = 20 # Pagination of number of elements to download at once. - INCLUDED_PACKAGE_TYPE = ("material", "plugin") # Only show these kind of packages def __init__(self, parent: "QObject" = None) -> None: super().__init__(parent) @@ -151,9 +150,8 @@ class PackageList(ListModel): return for package_data in response_data["data"]: - if package_data["package_type"] in self.INCLUDED_PACKAGE_TYPE: - package = PackageModel(package_data, parent = self) - self.appendItem({"package": package}) # Add it to this list model. + package = PackageModel(package_data, parent = self) + self.appendItem({"package": package}) # Add it to this list model. self._request_url = response_data["links"].get("next", "") # Use empty string to signify that there is no next page. self.hasMoreChanged.emit() From 3e64b7cb6674708b9b4be843f885ce3a69fe8feb Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 29 Oct 2021 10:10:57 +0200 Subject: [PATCH 7/9] Abort request when PackageList gets deleted This is a consequence of lazy loading and the re-loading we do when the Marketplace window gets closed. This solves a crash with reproduction steps: 1. Open the Marketplace. 2. Quickly close the Marketplace. 3. Quickly re-open the Marketplace. 4. The API responds to the request made by the first opening of the Marketplace. This crashed because when the Marketplace first opened, it made a request to the API with the HttpRequestManager. This request takes a while to respond to. If you close and re-open the Marketplace, the PackageList gets destroyed and a new one gets made. The HttpRequestManager eventually gets a response and wants to call the callback of the first PackageList, but that one got destroyed in the Qt engine so it'll throw an error saying that the object doesn't exist any more. Contributes to issue CURA-8557. --- plugins/Marketplace/PackageList.py | 44 ++++++++++++------- .../Marketplace/resources/qml/Packages.qml | 1 + 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 7d5e85a9af..c3452da180 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -2,6 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, Qt +from PyQt5.QtNetwork import QNetworkReply from typing import Optional, TYPE_CHECKING from cura.CuraApplication import CuraApplication @@ -9,7 +10,7 @@ from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To ma from UM.i18n import i18nCatalog from UM.Logger import Logger from UM.Qt.ListModel import ListModel -from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To request the package list from the API. +from UM.TaskManagement.HttpRequestManager import HttpRequestManager, HttpRequestData # To request the package list from the API. from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope # To request JSON responses from the API. from . import Marketplace # To get the list of packages. Imported this way to prevent circular imports. @@ -17,7 +18,6 @@ from .PackageModel import PackageModel # The contents of this list. if TYPE_CHECKING: from PyQt5.QtCore import QObject - from PyQt5.QtNetwork import QNetworkReply catalog = i18nCatalog("cura") @@ -37,7 +37,7 @@ class PackageList(ListModel): def __init__(self, parent: "QObject" = None) -> None: super().__init__(parent) - self._is_loading = True + self._ongoing_request: Optional[HttpRequestData] = None self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) self._error_message = "" @@ -46,6 +46,13 @@ class PackageList(ListModel): self.addRoleName(self.PackageRole, "package") + def __del__(self) -> None: + """ + When deleting this object, abort the request so that we don't get a callback from it later on a deleted C++ + object. + """ + self.abortRequest() + @pyqtSlot() def request(self) -> None: """ @@ -53,16 +60,21 @@ class PackageList(ListModel): When the request is done, the list will get updated with the new package models. """ - self.setIsLoading(True) self.setErrorMessage("") # Clear any previous errors. http = HttpRequestManager.getInstance() - http.get( + self._ongoing_request = http.get( self._request_url, scope = self._scope, callback = self._parseResponse, error_callback = self._onError ) + self.isLoadingChanged.emit() + + @pyqtSlot() + def abortRequest(self) -> None: + HttpRequestManager.getInstance().abortRequest(self._ongoing_request) + self._ongoing_request = None def reset(self) -> None: self.clear() @@ -70,18 +82,13 @@ class PackageList(ListModel): isLoadingChanged = pyqtSignal() - def setIsLoading(self, is_loading: bool) -> None: - if is_loading != self._is_loading: - self._is_loading = is_loading - self.isLoadingChanged.emit() - - @pyqtProperty(bool, notify = isLoadingChanged, fset = setIsLoading) + @pyqtProperty(bool, notify = isLoadingChanged) def isLoading(self) -> bool: """ Gives whether the list is currently loading the first page or loading more pages. :return: ``True`` if the list is downloading, or ``False`` if not downloading. """ - return self._is_loading + return self._ongoing_request is not None hasMoreChanged = pyqtSignal() @@ -155,13 +162,20 @@ class PackageList(ListModel): self._request_url = response_data["links"].get("next", "") # Use empty string to signify that there is no next page. self.hasMoreChanged.emit() - self.setIsLoading(False) + self._ongoing_request = None + self.isLoadingChanged.emit() - def _onError(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"]) -> None: + def _onError(self, reply: "QNetworkReply", error: Optional[QNetworkReply.NetworkError]) -> None: """ Handles networking and server errors when requesting the list of packages. :param reply: The reply with packages. This will most likely be incomplete and should be ignored. :param error: The error status of the request. """ - Logger.error(f"Could not reach Marketplace server.") + if error == QNetworkReply.NetworkError.OperationCanceledError: + Logger.error("Cancelled request for packages.") + self._ongoing_request = None + return # Don't show an error about this to the user. + Logger.error("Could not reach Marketplace server.") self.setErrorMessage(catalog.i18nc("@info:error", "Could not reach Marketplace.")) + self._ongoing_request = None + self.isLoadingChanged.emit() diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 390fba2977..0a3043b87c 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -14,6 +14,7 @@ ScrollView property alias model: pluginColumn.model Component.onCompleted: model.request() + Component.onDestruction: model.abortRequest() ListView { From afe9c0c633015b04785e445602e5379443a7acc5 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 29 Oct 2021 11:22:00 +0200 Subject: [PATCH 8/9] Change naming from plugin to packages CURA-8557 --- plugins/Marketplace/resources/qml/Packages.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml index 0a3043b87c..5442247668 100644 --- a/plugins/Marketplace/resources/qml/Packages.qml +++ b/plugins/Marketplace/resources/qml/Packages.qml @@ -11,21 +11,21 @@ ScrollView clip: true ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - property alias model: pluginColumn.model + property alias model: packagesListview.model Component.onCompleted: model.request() Component.onDestruction: model.abortRequest() ListView { - id: pluginColumn + id: packagesListview width: parent.width spacing: UM.Theme.getSize("default_margin").height delegate: Rectangle { - width: pluginColumn.width + width: packagesListview.width height: UM.Theme.getSize("card").height color: UM.Theme.getColor("main_background") @@ -46,7 +46,7 @@ ScrollView footer: Item //Wrapper item to add spacing between content and footer. { width: parent.width - height: UM.Theme.getSize("card").height + pluginColumn.spacing + height: UM.Theme.getSize("card").height + packagesListview.spacing Button { id: loadMoreButton @@ -89,7 +89,7 @@ ScrollView PropertyChanges { target: loadMoreLabel - text: catalog.i18nc("@button", "Failed to load plug-ins:") + " " + packages.model.errorMessage + "\n" + catalog.i18nc("@button", "Retry?") + text: catalog.i18nc("@button", "Failed to load packages:") + " " + packages.model.errorMessage + "\n" + catalog.i18nc("@button", "Retry?") } }, State From 7e674a18b3c243e277b19f200ba551f1033bb1cf Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 29 Oct 2021 11:34:11 +0200 Subject: [PATCH 9/9] Change the logging of operationcancelled to debug instead of error THe operation being cancelled is not an error; it's an expected action, since it happens after the user closes the window. CURA-8557 --- plugins/Marketplace/PackageList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index c3452da180..424b66fe21 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -172,7 +172,7 @@ class PackageList(ListModel): :param error: The error status of the request. """ if error == QNetworkReply.NetworkError.OperationCanceledError: - Logger.error("Cancelled request for packages.") + Logger.debug("Cancelled request for packages.") self._ongoing_request = None return # Don't show an error about this to the user. Logger.error("Could not reach Marketplace server.")